import type { Meta, StoryObj } from '@storybook/vue3-vite' import { ref, toValue } from 'vue' import CpMultiselect from '@/components/CpMultiselect.vue' interface IOption { disabled?: boolean iata_code?: string id: number name: string } const supplierOptions: IOption[] = [ { id: 1, name: 'MGA AIRLINES' }, { id: 2, name: 'ZENITH - EUROATLANTIC AIRWAYS' }, { id: 3, name: 'ZENITH - SOUTHERN AIRWAYS EXPRESS MOKULELE AIRLINES AND SURF AIR MOBILITY' }, { id: 4, name: 'EDO TUNISAIR ITALY' }, { id: 5, name: 'LMX SUISSE SA' }, { id: 6, name: 'LMX VOYAGES SAS' }, { id: 7, name: 'FLY KHIVA TRAVEL' }, { id: 8, name: 'ZENITH - AIR CHATHAMS' }, { id: 9, name: 'CP EMPLOYEES' }, { id: 10, name: 'MONDIAL TOURISME' }, { id: 11, name: 'UNITRAVEL UTAZÁSI IRODA' }, ] const airlineOptions: IOption[] = [ { id: 1, name: 'United Airlines', iata_code: 'UA' }, { id: 2, name: 'Delta Airlines', iata_code: 'DL' }, { id: 3, name: 'American Airlines', iata_code: 'AA' }, { id: 4, name: 'Southwest Airlines', iata_code: 'WN' }, { id: 5, name: 'Alaska Airlines', iata_code: 'AS' }, { id: 6, name: 'JetBlue Airways', iata_code: 'B6' }, { id: 7, name: 'Spirit Airlines', iata_code: 'NK' }, { id: 8, name: 'Frontier Airlines', iata_code: 'F9' }, { id: 9, name: 'Hawaiian Airlines', iata_code: 'HA' }, { id: 10, name: 'SkyWest Airlines', iata_code: 'OO' }, { id: 11, name: 'Allegiant Air', iata_code: 'G4' }, { id: 12, name: 'Atlantic Southeast Airlines', iata_code: 'EV' }, { id: 13, name: 'American Eagle Airlines', iata_code: 'MQ' }, { id: 14, name: 'Alaska Airlines', iata_code: 'AS' }, { id: 15, name: 'Air France', iata_code: 'AF', disabled: true }, { id: 16, name: 'Air Canada', iata_code: 'AC' }, { id: 17, name: 'Air New Zealand', iata_code: 'NZ' }, { id: 18, name: 'Air China', iata_code: 'CA' }, { id: 19, name: 'Air India', iata_code: 'AI' }, { id: 20, name: 'Air Berlin', iata_code: 'AB' }, { id: 21, name: 'AirAsia', iata_code: 'AK' }, { id: 22, name: 'Very Long Text To Test The Multiselect Component', iata_code: 'DD' }, ] const meta = { title: 'Organisms/CpMultiSelect', component: CpMultiselect, argTypes: { label: { control: 'text', description: 'Label of the select' }, required: { control: 'boolean', description: 'Whether the select is required' }, name: { control: 'text', description: 'Name of the select' }, placeholder: { control: 'text', description: 'Placeholder of the select' }, isInvalid: { control: 'boolean', description: 'Whether the select is invalid' }, multiple: { control: 'boolean', description: 'Whether the select should be multiple' }, options: { control: 'object', description: 'Options of the select' }, errorMessage: { control: 'text', description: 'Error message of the select' }, disabled: { control: 'boolean', description: 'Whether the select is disabled' }, isLoading: { control: 'boolean', description: 'Whether the select is loading' }, emptyMessage: { control: 'text', description: 'Message displayed when no options are found' }, trackBy: { control: 'text', description: 'Property to track the selected option' }, size: { control: 'select', options: ['sm', 'md'], description: 'Size of the multiselect' }, }, } satisfies Meta export default meta type Story = StoryObj /** * Single-value multiselect with the built-in search filter. The `#prefix` * slot adds an icon before the input. */ export const Single: Story = { args: { placeholder: 'Select a supplier', multiple: false, options: supplierOptions, trackBy: 'id', isClearable: true, }, render: (args) => ({ components: { CpMultiselect }, setup() { const selectedSupplier = ref(null) const getOptionIataCode = (option?: { iata_code?: string | null; icao_code?: string | null }) => { return option?.iata_code || option?.icao_code || undefined } return { args, selectedSupplier, getOptionIataCode } }, template: `
`, }), } /** * Multi-value selection with a supplier prefix badge. Selected options are * rendered as chips. */ export const Multiple: Story = { args: { placeholder: 'Select suppliers', multiple: true, options: supplierOptions, trackBy: 'id', }, render: (args) => ({ components: { CpMultiselect }, setup() { const selectedSuppliers = ref([]) return { args, selectedSuppliers } }, template: `
`, }), } /** * Multi-value selection with an async search handler. Selected options are * rendered as chips and can be customized through `#tag-leading-icon` and * `#option`. */ export const MultipleWithAsyncSearch: Story = { args: { required: false, name: 'select', placeholder: 'All airlines', isInvalid: false, disabled: false, multiple: true, emptyMessage: 'No airlines found', options: airlineOptions, }, render: (args) => ({ components: { CpMultiselect }, setup() { const searchQuery = ref('') const isLoading = ref(false) const originalOptions = ref(args.options) const dynamicOptions = ref(toValue(originalOptions)) const selectedAirlines = ref([]) const handleSearch = async (query: string) => { isLoading.value = true searchQuery.value = query await new Promise((resolve) => setTimeout(resolve, 500)) dynamicOptions.value = originalOptions.value?.filter((option) => { return (option as IOption).name.toLowerCase().includes(searchQuery.value.toLowerCase()) }) isLoading.value = false } return { args, selectedAirlines, dynamicOptions, handleSearch, isLoading } }, template: `
`, }), } /** * Invalid state with a label, required indicator and an error message * underneath the input. */ export const Invalid: Story = { args: { placeholder: 'Select a supplier', disabled: false, isInvalid: true, label: 'Supplier', required: true, errorMessage: 'This field is required', options: [], }, render: (args) => ({ components: { CpMultiselect }, setup() { const selectedSupplier = ref(null) return { args, selectedSupplier } }, template: `
`, }), } /** * Disabled state — the field is non-interactive and its menu cannot be * opened. */ export const Disabled: Story = { args: { placeholder: 'Select a supplier', disabled: true, isInvalid: false, options: [], }, render: (args) => ({ components: { CpMultiselect }, setup() { const selectedSupplier = ref(null) return { args, selectedSupplier } }, template: `
`, }), } /** * Free-form tag input. `withoutTypeahead` removes the search affordance so * users can enter arbitrary values that become chips. */ export const WithoutTypeahead: Story = { args: { placeholder: 'Select a supplier', options: [], withoutTypeahead: true, multiple: true, }, render: (args) => ({ components: { CpMultiselect }, setup() { const selectedSupplier = ref([]) return { args, selectedSupplier } }, template: `
`, }), }