vueless
Version:
Vue Styleless UI Component Library, powered by Tailwind CSS.
449 lines (404 loc) • 12 kB
text/typescript
import { ref } from "vue";
import {
getArgTypes,
getSlotNames,
getSlotsFragment,
getDocsDescription,
} from "../../utils/storybook.ts";
import USelect from "../../ui.form-select/USelect.vue";
import URow from "../../ui.container-row/URow.vue";
import UCol from "../../ui.container-col/UCol.vue";
import UBadge from "../../ui.text-badge/UBadge.vue";
import UIcon from "../../ui.image-icon/UIcon.vue";
import ULink from "../../ui.button-link/ULink.vue";
import UAvatar from "../../ui.image-avatar/UAvatar.vue";
import type { Meta, StoryFn } from "@storybook/vue3";
import type { Props } from "../types.ts";
interface USelectArgs extends Props {
slotTemplate?: string;
enum: "size" | "openDirection" | "labelAlign";
}
interface SelectOption {
id: string | number;
label: string;
badge?: string;
}
export default {
id: "3080",
title: "Form Inputs & Controls / Select",
component: USelect,
args: {
label: "Choose a city",
modelValue: null,
options: [
{ label: "New York", id: "1" },
{ label: "Los Angeles", id: "2" },
{ label: "Chicago", id: "3" },
{ label: "Houston", id: "4" },
{ label: "San Francisco", id: "5" },
],
},
argTypes: {
...getArgTypes(USelect.__name),
},
parameters: {
docs: {
...getDocsDescription(USelect.__name),
story: {
height: "300px",
},
},
},
} as Meta;
const DefaultTemplate: StoryFn<USelectArgs> = (args: USelectArgs) => ({
components: { USelect, UIcon, UBadge, ULink, UAvatar },
setup() {
function getSelectedBadge(options: SelectOption[], currentValue: string | number) {
return options?.find((option) => option.id === currentValue);
}
const slots = getSlotNames(USelect.__name);
const showAlert = (message: string) => alert(message);
return { args, slots, getSelectedBadge, showAlert };
},
template: `
<USelect
v-bind="args"
v-model="args.modelValue"
class="max-w-96"
@add="showAlert('You triggered the add action!')"
>
${args.slotTemplate || getSlotsFragment("")}
</USelect>
`,
});
const EnumVariantTemplate: StoryFn<USelectArgs> = (args: USelectArgs, { argTypes }) => ({
components: { USelect, UCol },
setup() {
let filteredOptions = argTypes?.[args.enum]?.options;
if (args.enum === "labelAlign") {
filteredOptions = argTypes?.[args.enum]?.options?.filter(
(item) => item !== "right" && item !== "topWithDesc",
);
}
return { args, filteredOptions };
},
template: `
<UCol>
<USelect
v-for="(option, index) in filteredOptions"
:key="index"
v-bind="args"
v-model="args.modelValue"
:[args.enum]="option"
:description="option"
class="max-w-96"
/>
</UCol>
`,
});
const GroupValuesTemplate: StoryFn<USelectArgs> = (args: USelectArgs) => ({
components: { USelect },
setup() {
return {
args,
};
},
template: `
<USelect
v-bind="args"
v-model="args.modelValue"
label="Single"
/>
<USelect
class="mt-5"
v-bind="args"
v-model="args.modelValueMultiple"
label="Multiple"
multiple
/>
`,
});
export const Default = DefaultTemplate.bind({});
Default.args = {};
export const Placeholder = DefaultTemplate.bind({});
Placeholder.args = { placeholder: "Start typing to search for a city..." };
export const Description = DefaultTemplate.bind({});
Description.args = { description: "You can only select a city from the list." };
export const Error = DefaultTemplate.bind({});
Error.args = { error: "Please select a city from the list" };
export const Disabled = DefaultTemplate.bind({});
Disabled.args = { disabled: true };
export const LabelPlacement = EnumVariantTemplate.bind({});
LabelPlacement.args = { enum: "labelAlign" };
export const Sizes = EnumVariantTemplate.bind({});
Sizes.args = { enum: "size", multiple: true, modelValue: [] };
export const LargeItemList = DefaultTemplate.bind({});
LargeItemList.args = {
options: [...new Array(1000)].map((_, index) => {
return { id: index + 1, label: `value ${index + 1}`, badge: "badge" };
}),
};
export const Multiple = DefaultTemplate.bind({});
Multiple.args = { multiple: true, modelValue: [] };
export const ClearableAndSearchable = DefaultTemplate.bind({});
ClearableAndSearchable.args = { clearable: false, searchable: false };
ClearableAndSearchable.parameters = {
docs: {
description: {
story:
// eslint-disable-next-line vue/max-len
"The `clearable` and `searchable` props control whether users can clear the selected value or search within the list. <br/> In this example, both are set to `false`, meaning the selection cannot be cleared, and searching is disabled.",
},
},
};
export const OpenDirection = EnumVariantTemplate.bind({});
OpenDirection.args = { enum: "openDirection" };
export const GroupValue = GroupValuesTemplate.bind({});
GroupValue.args = {
modelValue: "",
groupValueKey: "libs",
groupLabelKey: "language",
labelKey: "name",
valueKey: "name",
// TODO: Implement recursion nesting and add ability to use group (nested) options with default options.
options: [
{
language: "Javascript",
libs: [{ name: "Vue.js" }, { name: "Adonis" }],
},
{
language: "Ruby",
libs: [
{ name: "Frameworks", isSubGroup: true, level: 2 },
{ name: "Rails", level: 3 },
{ name: "Sinatra", level: 3 },
],
},
{
language: "Other",
libs: [{ name: "Laravel" }, { name: "Phoenix" }],
},
],
};
export const OptionsLimit2 = DefaultTemplate.bind({});
OptionsLimit2.args = { optionsLimit: 2 };
OptionsLimit2.parameters = {
docs: {
description: {
story: "`optionsLimit` prop controls the number of options displayed in the dropdown.",
},
},
};
export const VisibleOptions = DefaultTemplate.bind({});
VisibleOptions.args = { visibleOptions: 3 };
VisibleOptions.parameters = {
docs: {
description: {
story: "`visibleOptions` prop controls the number of options you can see without a scroll.",
},
},
};
export const AddOption = DefaultTemplate.bind({});
AddOption.args = { addOption: true };
AddOption.parameters = {
docs: {
description: {
story:
// eslint-disable-next-line vue/max-len
"The `addOption` prop displays an 'Add option' button, while the `add` event allows handling custom functionality when the button is clicked.",
},
},
};
export const OptionSettings = DefaultTemplate.bind({});
OptionSettings.args = {
options: [
{ label: "1. New York", id: "1" },
{ label: "2. Los Angeles", id: "2", isHidden: true },
{
label: "3. Chicago",
id: "3",
onClick: (option) =>
alert("onClick function for the third option: " + JSON.stringify(option)),
},
{ label: "4. Houston", id: "4" },
{ label: "5. San Francisco", id: "5" },
],
};
OptionSettings.parameters = {
docs: {
description: {
story:
// eslint-disable-next-line vue/max-len
"The second option of the array is hidden (`isHidden` object property is set to `true`). <br/> The third option has `onClick` event handler: <br/> `onClick: (option: Option) => alert('onClick function for option 3: ' + JSON.stringify(option))`",
},
},
};
export const IconProps: StoryFn<USelectArgs> = (args) => ({
components: { USelect, URow },
setup() {
const levelOptions = [
{ label: "Awesome", id: "1" },
{ label: "Good", id: "2" },
{ label: "Could be better", id: "3" },
{ label: "Terrible", id: "4" },
];
const roleOptions = [
{ label: "Admin", id: "1" },
{ label: "CEO", id: "2" },
{ label: "Manager", id: "3" },
{ label: "Guest", id: "4" },
];
return { args, levelOptions, roleOptions };
},
template: `
<URow no-mobile>
<USelect
left-icon="feedback"
label="Choose the level of our services"
placeholder="Share your feedback with us"
:options="levelOptions"
/>
<USelect
right-icon="person"
label="Select your role"
placeholder="Choose a role from the list"
:options="roleOptions"
/>
</URow>
`,
});
export const Slots: StoryFn<USelectArgs> = (args) => ({
components: { USelect, UCol, URow, ULink, UBadge, UAvatar },
setup() {
const clearModel = ref(null);
const clearMultipleModel = ref([]);
const beforeToggleModel = ref(null);
const afterToggleModel = ref(null);
const leftModel = ref(null);
const rightModel = ref(null);
return {
args,
clearModel,
clearMultipleModel,
beforeToggleModel,
afterToggleModel,
leftModel,
rightModel,
};
},
template: `
<UCol no-mobile>
<USelect v-bind="args" v-model="args.clearModel" label="Slot clear">
<template
<ULink label="Close" />
</template>
</USelect>
<USelect
v-bind="args"
v-model="args.clearMultipleModel"
multiple
label="Slot clear-multiple"
>
<template
<ULink label="Close" color="green" />
</template>
</USelect>
<URow no-mobile>
<USelect v-bind="args" v-model="args.beforeToggleModel" label="Slot before-toggle">
<template
<UAvatar />
</template>
</USelect>
<USelect
v-bind="args"
v-model="args.afterToggleModel"
:config="{ afterToggle: 'pt-0 items-center' }"
label="Slot after-toggle"
>
<template
<UAvatar />
</template>
</USelect>
</URow>
<URow no-mobile>
<USelect v-bind="args" v-model="args.leftModel" label="Slot left">
<template
<UAvatar />
</template>
</USelect>
<USelect v-bind="args" v-model="args.rightModel" label="Slot right">
<template
<UAvatar />
</template>
</USelect>
</URow>
</UCol>
`,
});
Slots.parameters = {
docs: {
story: {
height: "500px",
},
},
};
export const SlotToggle = DefaultTemplate.bind({});
SlotToggle.args = {
slotTemplate: `
<template
<UIcon
name="expand_circle_down"
:class="{ 'rotate-180': opened }"
/>
</template>
`,
};
export const SlotSelectedValueLabel = DefaultTemplate.bind({});
SlotSelectedValueLabel.args = {
slotTemplate: `
<template
<UBadge :label="selectedLabel" color="green" />
</template>
`,
};
export const SlotSelectedValueLabelAfter = DefaultTemplate.bind({});
SlotSelectedValueLabelAfter.args = {
options: [
{ label: "Venice", id: "1", icon: "sailing", color: "green" },
{ label: "Paris", id: "2", icon: "flight", color: "orange" },
],
slotTemplate: `
<template
<UIcon
:name="option.icon"
:color="option.color"
size="xs"
class="ml-1"
/>
</template>
`,
};
export const SlotBeforeOption = DefaultTemplate.bind({});
SlotBeforeOption.args = {
slotTemplate: `
<template
<UBadge v-if="index === 3" label="Special offer!" color="blue" class="mr-1" />
</template>
`,
};
export const SlotOption = DefaultTemplate.bind({});
SlotOption.args = {
slotTemplate: `
<template
<UBadge v-if="index === 1" :label="option.label" />
</template>
`,
};
export const SlotAfterOption = DefaultTemplate.bind({});
SlotAfterOption.args = {
slotTemplate: `
<template
<UBadge v-if="index === 2" label="Special offer!" color="blue" class="ml-1" />
</template>
`,
};