import type { ComponentPropsAndSlots, Meta, StoryObj } from '@storybook/vue3-vite' import { ref } from 'vue' import CpIcon from '@/components/CpIcon.vue' import CpInput from '@/components/CpInput.vue' import { docCellStyle, docLabelStyle, docRowColumnStyle } from '@/stories/documentationStyles' /** Native attributes forwarded to the inner `` (not declared as Vue props). */ type CpInputPassthroughAttrs = { autocomplete?: string disabled?: boolean placeholder?: string required?: boolean type?: string } type CpInputStoryArgs = ComponentPropsAndSlots & CpInputPassthroughAttrs const inputSizes = ['sm', 'md', 'lg'] as const const inputTypes = ['text', 'email', 'password', 'number', 'tel', 'url'] as const const inputStackStyle = `${docCellStyle} width: 100%; max-width: 360px;` const meta = { title: 'Atoms/CpInput', component: CpInput, argTypes: { modelValue: { control: 'text', description: 'The input value', }, size: { control: 'select', options: inputSizes, description: 'The size of the input', }, type: { control: 'select', options: inputTypes, description: 'The type of input', }, label: { control: 'text', description: 'Label text for the input', }, placeholder: { control: 'text', description: 'Placeholder text', }, required: { control: 'boolean', description: 'Whether the field is required', }, disabled: { control: 'boolean', description: 'Whether the field is disabled', }, isInvalid: { control: 'boolean', description: 'Whether the field is in an invalid state', }, errorMessage: { control: 'text', description: 'Error message to display', }, autocomplete: { control: 'text', description: 'Autocomplete attribute value', }, }, } satisfies Meta export default meta // Use explicit args type: `StoryObj` would re-infer args from `component` only // and drop native attributes passed through `$attrs`. type Story = StoryObj type StoryArgs = Partial /** * Default input. Use the controls to experiment with each prop in isolation. */ export const Default: Story = { args: { label: 'Input Label', placeholder: 'Enter text here', size: 'md', type: 'text', required: false, disabled: false, autocomplete: 'off', isInvalid: false, errorMessage: '', removeBorder: false, isSearch: false, help: '', }, render: (args: StoryArgs) => ({ components: { CpInput }, setup() { const value = ref('') return { args, value } }, template: `
`, }), } /* -------------------------------------------------------------------------- */ /* Sizes */ /* -------------------------------------------------------------------------- */ /** * All sizes rendered side by side, from `sm` to `lg`. */ export const Sizes: Story = { parameters: { controls: { disable: true } }, render: () => ({ components: { CpInput }, setup() { const value = ref('') return { value, inputSizes, docRowColumnStyle, inputStackStyle, docLabelStyle } }, template: `
{{ size }}
`, }), } /* -------------------------------------------------------------------------- */ /* States */ /* -------------------------------------------------------------------------- */ /** * Default, required, disabled and invalid states compared side by side. */ export const States: Story = { parameters: { controls: { disable: true } }, render: () => ({ components: { CpInput }, setup() { const value = ref('') return { value, docRowColumnStyle, inputStackStyle, docLabelStyle } }, template: `
Default
Required
Disabled
Invalid
`, }), } /* -------------------------------------------------------------------------- */ /* Types */ /* -------------------------------------------------------------------------- */ /** * Native HTML input types wired to the appropriate keyboard and validation. */ export const Types: Story = { parameters: { controls: { disable: true } }, render: () => ({ components: { CpInput }, setup() { const value = ref('') return { value, docRowColumnStyle, inputStackStyle, docLabelStyle } }, template: `
text
email
password
number
`, }), } /* -------------------------------------------------------------------------- */ /* Help & tooltip */ /* -------------------------------------------------------------------------- */ /** * Attach a help text under the field and a tooltip next to the label. */ export const HelpAndTooltip: Story = { args: { ...Default.args, help: 'This is a help text', tooltip: 'This is a tooltip', }, } /* -------------------------------------------------------------------------- */ /* Variants */ /* -------------------------------------------------------------------------- */ /** * Search input with a built-in icon. */ export const Search: Story = { args: { ...Default.args, isSearch: true, }, } /** * Input masked with a pattern (phone numbers, card numbers, etc.). */ export const Mask: Story = { args: { ...Default.args, placeholder: 'Enter your phone number', mask: '## ## ## ## ##', }, } /** * Slot icons on either side of the input. */ export const WithIcon: Story = { args: { ...Default.args }, render: (args: StoryArgs) => ({ components: { CpInput, CpIcon }, setup() { const value = ref('') return { args, value } }, template: `
`, }), } /** * Use the icon slots to render unit labels, perfect for currencies or * measurement units. */ export const WithUnits: Story = { args: { ...Default.args }, render: (args: StoryArgs) => ({ components: { CpInput }, setup() { const value = ref('') return { args, value } }, template: `
`, }), } export const VisualTestingBetweenDisabledAndPlaceholder: Story = { args: { ...Default.args, placeholder: 'Enter something', }, render: (args: StoryArgs) => ({ components: { CpInput }, setup() { const value = ref('Test') return { args, value } }, template: `
`, }), }