add storybook, fix ui, v0.1.6
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2026-02-23 13:54:08 +01:00
parent 0f09155876
commit 64628bf068
22 changed files with 1986 additions and 61 deletions

View File

@@ -0,0 +1,215 @@
import { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { MagnifyingGlassIcon } from '@heroicons/react/24/solid';
import { InputField } from './InputField';
const meta = {
title: 'Components/InputField',
component: InputField,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component: 'Text input field with optional label, validation state, size/layout variants, and password visibility toggle.'
}
}
},
argTypes: {
label: {
description: 'Label text shown above (stacked) or on the left (inline).',
control: 'text',
table: { type: { summary: 'string' } }
},
placeholder: {
description: 'Input placeholder text.',
control: 'text',
table: { type: { summary: 'string' } }
},
type: {
description: 'Native input type.',
options: ['text', 'password', 'email'],
control: 'inline-radio',
table: { type: { summary: "'text' | 'password' | 'email'" } }
},
size: {
description: 'Input size.',
options: ['sm', 'md', 'lg', 'full'],
control: 'inline-radio',
table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } }
},
layout: {
description: 'Label/input layout mode.',
options: ['stacked', 'inline'],
control: 'inline-radio',
table: { type: { summary: "'stacked' | 'inline'" } }
},
value: {
description: 'Controlled input value.',
control: 'text',
table: { type: { summary: 'string' } }
},
name: {
description: 'Native input `name` attribute.',
control: 'text',
table: { type: { summary: 'string' } }
},
disabled: {
description: 'Disables the input.',
control: 'boolean',
table: { type: { summary: 'boolean' } }
},
required: {
description: 'Sets the native HTML `required` attribute.',
control: 'boolean',
table: { type: { summary: 'boolean' } }
},
error: {
description: 'Validation message shown below the field.',
control: 'text',
table: { type: { summary: 'string' } }
},
rightIcon: {
description: 'Optional trailing icon node (ignored for password type because toggle icon is used).',
control: false,
table: { type: { summary: 'ReactNode' } }
},
className: {
description: 'Extra CSS classes for the outer wrapper.',
control: 'text',
table: { type: { summary: 'string' } }
},
inputClassName: {
description: 'Extra CSS classes for the `<input>` element.',
control: 'text',
table: { type: { summary: 'string' } }
},
onChange: {
description: 'Change handler callback.',
action: 'changed',
table: { type: { summary: 'ChangeEventHandler<HTMLInputElement>' } }
},
onBlur: {
description: 'Blur handler callback.',
control: false,
table: { type: { summary: 'FocusEventHandler<HTMLInputElement>' } }
},
inputRef: {
description: 'Ref forwarded to the native `<input>` element.',
control: false,
table: { type: { summary: 'Ref<HTMLInputElement>' } }
}
},
args: {
label: 'Email',
type: 'email',
placeholder: 'name@example.com',
value: '',
size: 'md',
layout: 'stacked'
}
} satisfies Meta<typeof InputField>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Text: Story = {
args: {
type: 'text',
label: 'Title',
placeholder: 'Write a title'
},
render: (args) => {
const [value, setValue] = useState('Storybook integration');
return (
<InputField
{...args}
value={value}
onChange={(event) => {
setValue(event.target.value);
args.onChange?.(event);
}}
/>
);
}
};
export const PasswordWithToggle: Story = {
args: {
type: 'password',
label: 'Password',
placeholder: 'Type a strong password'
},
render: (args) => {
const [value, setValue] = useState('pa55word');
return (
<InputField
{...args}
value={value}
onChange={(event) => {
setValue(event.target.value);
args.onChange?.(event);
}}
/>
);
}
};
export const InlineWithIcon: Story = {
args: {
type: 'text',
label: 'Search',
layout: 'inline',
size: 'sm',
rightIcon: <MagnifyingGlassIcon className="h-4 w-4 ui-body-secondary" />
},
render: (args) => {
const [value, setValue] = useState('posts');
return (
<InputField
{...args}
value={value}
onChange={(event) => {
setValue(event.target.value);
args.onChange?.(event);
}}
/>
);
}
};
export const Error: Story = {
args: {
type: 'email',
label: 'Email',
value: 'invalid.mail',
error: 'Enter a valid email address'
}
};
export const Disabled: Story = {
args: {
type: 'text',
label: 'Read only field',
value: 'Locked content',
disabled: true
}
};
export const SizeMatrix: Story = {
args: {
type: 'text',
label: 'Name',
placeholder: 'Enter value'
},
render: (args) => {
const [value, setValue] = useState('Beatrice');
return (
<div className="grid grid-cols-1 gap-3">
<InputField {...args} value={value} size="sm" onChange={(event) => setValue(event.target.value)} />
<InputField {...args} value={value} size="md" onChange={(event) => setValue(event.target.value)} />
<InputField {...args} value={value} size="lg" onChange={(event) => setValue(event.target.value)} />
<InputField {...args} value={value} size="full" onChange={(event) => setValue(event.target.value)} />
</div>
);
}
};