@regardio/react 0.5.7 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/background-slideshow/index.d.mts +36 -0
- package/dist/background-slideshow/index.mjs +110 -0
- package/dist/blurry-gradient/index.d.mts +17 -0
- package/dist/blurry-gradient/index.mjs +93 -0
- package/dist/button/index.d.mts +2 -0
- package/dist/button/index.mjs +3 -0
- package/dist/button-BiSQpBbc.mjs +129 -0
- package/dist/carousel/index.d.mts +40 -0
- package/dist/carousel/index.mjs +141 -0
- package/dist/checkbox/index.d.mts +37 -0
- package/dist/checkbox/index.mjs +70 -0
- package/dist/checkbox-group/index.d.mts +17 -0
- package/dist/checkbox-group/index.mjs +29 -0
- package/dist/chunk-BTpB_u-K.mjs +18 -0
- package/dist/countdown/index.d.mts +6 -0
- package/dist/countdown/index.mjs +58 -0
- package/dist/field/index.d.mts +66 -0
- package/dist/field/index.mjs +115 -0
- package/dist/fieldset/index.d.mts +33 -0
- package/dist/fieldset/index.mjs +61 -0
- package/dist/form/index.d.mts +22 -0
- package/dist/form/index.mjs +31 -0
- package/dist/generic-error/{index.d.ts → index.d.mts} +22 -18
- package/dist/generic-error/index.mjs +57 -0
- package/dist/grid/index.d.mts +1197 -0
- package/dist/grid/index.mjs +221 -0
- package/dist/heading/index.d.mts +31 -0
- package/dist/heading/index.mjs +29 -0
- package/dist/highlight/index.d.mts +18 -0
- package/dist/highlight/index.mjs +35 -0
- package/dist/hooks/{use-current-route-data.d.ts → use-current-route-data.d.mts} +3 -2
- package/dist/hooks/use-current-route-data.mjs +20 -0
- package/dist/hooks/{use-focus-search.d.ts → use-focus-search.d.mts} +4 -3
- package/dist/hooks/use-focus-search.mjs +21 -0
- package/dist/hooks/{use-matches-data.d.ts → use-matches-data.d.mts} +3 -2
- package/dist/hooks/use-matches-data.mjs +21 -0
- package/dist/hooks/{use-media-query.d.ts → use-media-query.d.mts} +3 -2
- package/dist/hooks/use-media-query.mjs +26 -0
- package/dist/hooks/use-mobile.d.mts +4 -0
- package/dist/hooks/use-mobile.mjs +20 -0
- package/dist/hooks/use-nonce.d.mts +8 -0
- package/dist/hooks/use-nonce.mjs +13 -0
- package/dist/hooks/{use-orientation.d.ts → use-orientation.d.mts} +3 -2
- package/dist/hooks/use-orientation.mjs +30 -0
- package/dist/hooks/use-user.d.mts +55 -0
- package/dist/hooks/use-user.mjs +39 -0
- package/dist/icon-button/index.d.mts +29 -0
- package/dist/icon-button/index.mjs +36 -0
- package/dist/if/index.d.mts +15 -0
- package/dist/if/index.mjs +21 -0
- package/dist/iframe/index.d.mts +11 -0
- package/dist/iframe/index.mjs +15 -0
- package/dist/index-Bm-tWhsb.d.mts +30 -0
- package/dist/index-YT2CkvL6.d.mts +36 -0
- package/dist/input/index.d.mts +2 -0
- package/dist/input/index.mjs +3 -0
- package/dist/input-CtR6aRVi.mjs +73 -0
- package/dist/link/index.d.mts +73 -0
- package/dist/link/index.mjs +129 -0
- package/dist/list/index.d.mts +71 -0
- package/dist/list/index.mjs +54 -0
- package/dist/markdown-container/index.d.mts +23 -0
- package/dist/markdown-container/index.mjs +71 -0
- package/dist/password-input/index.d.mts +24 -0
- package/dist/password-input/index.mjs +92 -0
- package/dist/picture/{index.d.ts → index.d.mts} +21 -20
- package/dist/picture/index.mjs +3 -0
- package/dist/picture-DkX3W5zl.mjs +69 -0
- package/dist/protected-email/{index.d.ts → index.d.mts} +14 -8
- package/dist/protected-email/index.mjs +37 -0
- package/dist/radio/index.d.mts +37 -0
- package/dist/radio/index.mjs +72 -0
- package/dist/radio-group/index.d.mts +17 -0
- package/dist/radio-group/index.mjs +29 -0
- package/dist/slider/index.d.mts +85 -0
- package/dist/slider/index.mjs +133 -0
- package/dist/switch/index.d.mts +38 -0
- package/dist/switch/index.mjs +87 -0
- package/dist/text/index.d.mts +26 -0
- package/dist/text/index.mjs +32 -0
- package/dist/text-CPlUND-Z.mjs +58 -0
- package/dist/toggle/index.d.mts +59 -0
- package/dist/toggle/index.mjs +82 -0
- package/dist/utils/author/index.d.mts +4 -0
- package/dist/utils/author/index.mjs +26 -0
- package/dist/utils/text/{index.d.ts → index.d.mts} +4 -3
- package/dist/utils/text/index.mjs +3 -0
- package/package.json +5 -117
- package/src/button/button.stories.tsx +161 -0
- package/src/button/button.test.tsx +73 -0
- package/src/button/button.tsx +112 -0
- package/src/button/index.ts +2 -0
- package/src/carousel/carousel-next.tsx +2 -2
- package/src/carousel/carousel-previous.tsx +2 -2
- package/src/checkbox/checkbox.stories.tsx +118 -0
- package/src/checkbox/checkbox.tsx +91 -0
- package/src/checkbox/index.ts +2 -0
- package/src/checkbox-group/checkbox-group.tsx +40 -0
- package/src/checkbox-group/index.ts +2 -0
- package/src/field/field.stories.tsx +105 -0
- package/src/field/field.test.tsx +61 -0
- package/src/field/field.tsx +165 -0
- package/src/field/index.ts +12 -0
- package/src/fieldset/fieldset.stories.tsx +204 -0
- package/src/fieldset/fieldset.test.tsx +63 -0
- package/src/fieldset/fieldset.tsx +75 -0
- package/src/fieldset/index.ts +7 -0
- package/src/form/form.stories.tsx +230 -0
- package/src/form/form.test.tsx +68 -0
- package/src/form/form.tsx +38 -0
- package/src/form/index.ts +2 -0
- package/src/icon-button/icon-button.stories.tsx +128 -7
- package/src/icon-button/icon-button.test.tsx +152 -0
- package/src/icon-button/icon-button.tsx +43 -9
- package/src/input/index.ts +2 -0
- package/src/input/input.stories.tsx +151 -0
- package/src/input/input.test.tsx +65 -0
- package/src/input/input.tsx +113 -0
- package/src/password-input/index.ts +1 -1
- package/src/password-input/password-input.tsx +104 -27
- package/src/radio/index.ts +2 -0
- package/src/radio/radio.tsx +92 -0
- package/src/radio-group/index.ts +2 -0
- package/src/radio-group/radio-group.tsx +36 -0
- package/src/slider/index.ts +18 -0
- package/src/slider/slider.tsx +179 -0
- package/src/switch/index.ts +2 -0
- package/src/switch/switch.stories.tsx +118 -0
- package/src/switch/switch.tsx +101 -0
- package/src/toggle/index.ts +2 -0
- package/src/toggle/toggle.stories.tsx +232 -0
- package/src/toggle/toggle.test.tsx +149 -0
- package/src/toggle/toggle.tsx +88 -0
- package/dist/background-slideshow/index.d.ts +0 -24
- package/dist/background-slideshow/index.js +0 -165
- package/dist/blurry-gradient/index.d.ts +0 -16
- package/dist/blurry-gradient/index.js +0 -128
- package/dist/carousel/index.d.ts +0 -36
- package/dist/carousel/index.js +0 -171
- package/dist/countdown/index.d.ts +0 -5
- package/dist/countdown/index.js +0 -73
- package/dist/generic-error/index.js +0 -47
- package/dist/grid/index.d.ts +0 -1196
- package/dist/grid/index.js +0 -239
- package/dist/heading/index.d.ts +0 -24
- package/dist/heading/index.js +0 -99
- package/dist/highlight/index.d.ts +0 -13
- package/dist/highlight/index.js +0 -59
- package/dist/hooks/use-current-route-data.js +0 -16
- package/dist/hooks/use-focus-search.js +0 -19
- package/dist/hooks/use-matches-data.js +0 -15
- package/dist/hooks/use-media-query.js +0 -20
- package/dist/hooks/use-mobile.d.ts +0 -3
- package/dist/hooks/use-mobile.js +0 -19
- package/dist/hooks/use-nonce.d.ts +0 -7
- package/dist/hooks/use-nonce.js +0 -8
- package/dist/hooks/use-orientation.js +0 -29
- package/dist/hooks/use-user.d.ts +0 -50
- package/dist/hooks/use-user.js +0 -25
- package/dist/icon-button/index.d.ts +0 -9
- package/dist/icon-button/index.js +0 -17
- package/dist/if/index.d.ts +0 -10
- package/dist/if/index.js +0 -24
- package/dist/iframe/index.d.ts +0 -10
- package/dist/iframe/index.js +0 -17
- package/dist/link/index.d.ts +0 -55
- package/dist/link/index.js +0 -195
- package/dist/list/index.d.ts +0 -69
- package/dist/list/index.js +0 -65
- package/dist/markdown-container/index.d.ts +0 -22
- package/dist/markdown-container/index.js +0 -128
- package/dist/password-input/index.d.ts +0 -11
- package/dist/password-input/index.js +0 -46
- package/dist/picture/index.js +0 -68
- package/dist/protected-email/index.js +0 -30
- package/dist/text/index.d.ts +0 -20
- package/dist/text/index.js +0 -38
- package/dist/utils/author/index.d.ts +0 -3
- package/dist/utils/author/index.js +0 -33
- package/dist/utils/text/index.js +0 -73
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Checkbox as BaseUICheckbox } from '@base-ui/react/checkbox';
|
|
2
|
+
import { tv } from '@regardio/tailwind/utils';
|
|
3
|
+
import type { ComponentProps } from 'react';
|
|
4
|
+
|
|
5
|
+
const checkboxRoot = tv({
|
|
6
|
+
base: [
|
|
7
|
+
'h-4',
|
|
8
|
+
'w-4',
|
|
9
|
+
'rounded',
|
|
10
|
+
'border',
|
|
11
|
+
'border-gray-300',
|
|
12
|
+
'bg-white',
|
|
13
|
+
'focus:outline-none',
|
|
14
|
+
'focus:ring-2',
|
|
15
|
+
'focus:ring-blue-500',
|
|
16
|
+
'focus:ring-offset-2',
|
|
17
|
+
'disabled:cursor-not-allowed',
|
|
18
|
+
'disabled:opacity-50',
|
|
19
|
+
'data-[checked]:bg-blue-600',
|
|
20
|
+
'data-[checked]:border-blue-600',
|
|
21
|
+
'transition-colors',
|
|
22
|
+
'duration-200',
|
|
23
|
+
'cursor-pointer',
|
|
24
|
+
],
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
size: 'md',
|
|
27
|
+
},
|
|
28
|
+
variants: {
|
|
29
|
+
size: {
|
|
30
|
+
lg: ['h-5', 'w-5'],
|
|
31
|
+
md: ['h-4', 'w-4'],
|
|
32
|
+
sm: ['h-3', 'w-3'],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const checkboxIndicator = tv({
|
|
38
|
+
base: ['flex', 'items-center', 'justify-center', 'text-white', 'data-[unchecked]:invisible'],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export type CheckboxSize = 'sm' | 'md' | 'lg';
|
|
42
|
+
|
|
43
|
+
export interface CheckboxRootProps
|
|
44
|
+
extends Omit<ComponentProps<typeof BaseUICheckbox.Root>, 'className'> {
|
|
45
|
+
className?: string;
|
|
46
|
+
size?: CheckboxSize;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface CheckboxIndicatorProps
|
|
50
|
+
extends Omit<ComponentProps<typeof BaseUICheckbox.Indicator>, 'className'> {
|
|
51
|
+
className?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const CheckboxRoot = ({ className, size = 'md', ...props }: CheckboxRootProps) => {
|
|
55
|
+
return (
|
|
56
|
+
<BaseUICheckbox.Root
|
|
57
|
+
className={checkboxRoot({
|
|
58
|
+
className,
|
|
59
|
+
size,
|
|
60
|
+
})}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const CheckboxIndicator = ({ className, children, ...props }: CheckboxIndicatorProps) => {
|
|
67
|
+
return (
|
|
68
|
+
<BaseUICheckbox.Indicator
|
|
69
|
+
className={checkboxIndicator({ className })}
|
|
70
|
+
{...props}
|
|
71
|
+
>
|
|
72
|
+
{children || (
|
|
73
|
+
<svg
|
|
74
|
+
fill="none"
|
|
75
|
+
height="12"
|
|
76
|
+
stroke="currentColor"
|
|
77
|
+
strokeWidth="2"
|
|
78
|
+
viewBox="0 0 12 12"
|
|
79
|
+
width="12"
|
|
80
|
+
>
|
|
81
|
+
<polyline points="2,6 5,9 10,3" />
|
|
82
|
+
</svg>
|
|
83
|
+
)}
|
|
84
|
+
</BaseUICheckbox.Indicator>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const Checkbox = {
|
|
89
|
+
Indicator: CheckboxIndicator,
|
|
90
|
+
Root: CheckboxRoot,
|
|
91
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CheckboxGroup as BaseUICheckboxGroup } from '@base-ui/react/checkbox-group';
|
|
2
|
+
import { tv } from '@regardio/tailwind/utils';
|
|
3
|
+
import type { ComponentProps } from 'react';
|
|
4
|
+
|
|
5
|
+
const checkboxGroup = tv({
|
|
6
|
+
base: ['flex', 'flex-col', 'gap-2'],
|
|
7
|
+
defaultVariants: {
|
|
8
|
+
orientation: 'vertical',
|
|
9
|
+
},
|
|
10
|
+
variants: {
|
|
11
|
+
orientation: {
|
|
12
|
+
horizontal: ['flex-row', 'gap-4'],
|
|
13
|
+
vertical: ['flex-col', 'gap-2'],
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export type CheckboxGroupOrientation = 'horizontal' | 'vertical';
|
|
19
|
+
|
|
20
|
+
export interface CheckboxGroupProps
|
|
21
|
+
extends Omit<ComponentProps<typeof BaseUICheckboxGroup>, 'className'> {
|
|
22
|
+
className?: string;
|
|
23
|
+
orientation?: CheckboxGroupOrientation;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const CheckboxGroup = ({
|
|
27
|
+
className,
|
|
28
|
+
orientation = 'vertical',
|
|
29
|
+
...props
|
|
30
|
+
}: CheckboxGroupProps) => {
|
|
31
|
+
return (
|
|
32
|
+
<BaseUICheckboxGroup
|
|
33
|
+
className={checkboxGroup({
|
|
34
|
+
className,
|
|
35
|
+
orientation,
|
|
36
|
+
})}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Checkbox } from '../checkbox';
|
|
3
|
+
import { Input } from '../input';
|
|
4
|
+
import { Field } from './field';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
argTypes: {
|
|
8
|
+
variant: {
|
|
9
|
+
control: 'select',
|
|
10
|
+
description: 'Field variant',
|
|
11
|
+
options: ['default', 'required'],
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
component: Field.Root,
|
|
15
|
+
parameters: {
|
|
16
|
+
layout: 'centered',
|
|
17
|
+
},
|
|
18
|
+
tags: ['autodocs'],
|
|
19
|
+
title: 'Components/Field',
|
|
20
|
+
} satisfies Meta<typeof Field.Root>;
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof meta>;
|
|
24
|
+
|
|
25
|
+
export const Default: Story = {
|
|
26
|
+
render: () => (
|
|
27
|
+
<Field.Root>
|
|
28
|
+
<Field.Label>Email Address</Field.Label>
|
|
29
|
+
<Input placeholder="Enter your email" />
|
|
30
|
+
<Field.Description>We'll never share your email with anyone else.</Field.Description>
|
|
31
|
+
</Field.Root>
|
|
32
|
+
),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const WithError: Story = {
|
|
36
|
+
render: () => (
|
|
37
|
+
<Field.Root>
|
|
38
|
+
<Field.Label variant="error">Email Address</Field.Label>
|
|
39
|
+
<Input
|
|
40
|
+
placeholder="Enter your email"
|
|
41
|
+
variant="error"
|
|
42
|
+
/>
|
|
43
|
+
<Field.Error>This field is required</Field.Error>
|
|
44
|
+
</Field.Root>
|
|
45
|
+
),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const Required: Story = {
|
|
49
|
+
render: () => (
|
|
50
|
+
<Field.Root variant="required">
|
|
51
|
+
<Field.Label>Full Name</Field.Label>
|
|
52
|
+
<Input placeholder="Enter your full name" />
|
|
53
|
+
<Field.Description>Please enter your first and last name.</Field.Description>
|
|
54
|
+
</Field.Root>
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const HorizontalLayout: Story = {
|
|
59
|
+
render: () => (
|
|
60
|
+
<Field.Root>
|
|
61
|
+
<Field.Item layout="horizontal">
|
|
62
|
+
<Checkbox.Root>
|
|
63
|
+
<Checkbox.Indicator />
|
|
64
|
+
</Checkbox.Root>
|
|
65
|
+
<Field.Label>Subscribe to newsletter</Field.Label>
|
|
66
|
+
</Field.Item>
|
|
67
|
+
<Field.Description>Receive updates about new features</Field.Description>
|
|
68
|
+
</Field.Root>
|
|
69
|
+
),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const MultipleFields: Story = {
|
|
73
|
+
render: () => (
|
|
74
|
+
<div className="space-y-6">
|
|
75
|
+
<Field.Root>
|
|
76
|
+
<Field.Label>First Name</Field.Label>
|
|
77
|
+
<Input placeholder="Enter your first name" />
|
|
78
|
+
</Field.Root>
|
|
79
|
+
<Field.Root>
|
|
80
|
+
<Field.Label>Last Name</Field.Label>
|
|
81
|
+
<Input placeholder="Enter your last name" />
|
|
82
|
+
</Field.Root>
|
|
83
|
+
<Field.Root variant="required">
|
|
84
|
+
<Field.Label>Email</Field.Label>
|
|
85
|
+
<Input
|
|
86
|
+
placeholder="Enter your email"
|
|
87
|
+
type="email"
|
|
88
|
+
/>
|
|
89
|
+
<Field.Description>We'll never share your email.</Field.Description>
|
|
90
|
+
</Field.Root>
|
|
91
|
+
</div>
|
|
92
|
+
),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const WithCustomClass: Story = {
|
|
96
|
+
render: () => (
|
|
97
|
+
<Field.Root className="bg-gray-50 p-4 rounded-lg">
|
|
98
|
+
<Field.Label className="text-blue-600">Custom Field</Field.Label>
|
|
99
|
+
<Input
|
|
100
|
+
className="bg-white border-blue-300"
|
|
101
|
+
placeholder="Custom styled input"
|
|
102
|
+
/>
|
|
103
|
+
</Field.Root>
|
|
104
|
+
),
|
|
105
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { Field } from './field';
|
|
4
|
+
|
|
5
|
+
describe('Field', () => {
|
|
6
|
+
it('renders FieldRoot with children', () => {
|
|
7
|
+
render(
|
|
8
|
+
<Field.Root>
|
|
9
|
+
<Field.Label>Test Label</Field.Label>
|
|
10
|
+
<Field.Control />
|
|
11
|
+
</Field.Root>,
|
|
12
|
+
);
|
|
13
|
+
expect(screen.getByText('Test Label')).toBeInTheDocument();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('renders FieldDescription', () => {
|
|
17
|
+
render(
|
|
18
|
+
<Field.Root>
|
|
19
|
+
<Field.Description>Test Description</Field.Description>
|
|
20
|
+
</Field.Root>,
|
|
21
|
+
);
|
|
22
|
+
expect(screen.getByText('Test Description')).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders FieldError', () => {
|
|
26
|
+
render(
|
|
27
|
+
<Field.Root invalid>
|
|
28
|
+
<Field.Error match={true}>Test Error</Field.Error>
|
|
29
|
+
</Field.Root>,
|
|
30
|
+
);
|
|
31
|
+
expect(screen.getByText('Test Error')).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders FieldItem with layout', () => {
|
|
35
|
+
render(
|
|
36
|
+
<Field.Item layout="horizontal">
|
|
37
|
+
<span>Test Content</span>
|
|
38
|
+
</Field.Item>,
|
|
39
|
+
);
|
|
40
|
+
expect(screen.getByText('Test Content')).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('applies custom className to FieldRoot', () => {
|
|
44
|
+
render(
|
|
45
|
+
<Field.Root className="custom-field">
|
|
46
|
+
<Field.Label>Test</Field.Label>
|
|
47
|
+
</Field.Root>,
|
|
48
|
+
);
|
|
49
|
+
const fieldRoot = screen.getByText('Test').parentElement;
|
|
50
|
+
expect(fieldRoot).toHaveClass('custom-field');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('applies variant styles to FieldLabel', () => {
|
|
54
|
+
render(
|
|
55
|
+
<Field.Root>
|
|
56
|
+
<Field.Label variant="error">Error Label</Field.Label>
|
|
57
|
+
</Field.Root>,
|
|
58
|
+
);
|
|
59
|
+
expect(screen.getByText('Error Label')).toHaveClass('text-red-600');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Field as BaseUIField } from '@base-ui/react/field';
|
|
2
|
+
import { tv } from '@regardio/tailwind/utils';
|
|
3
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
const fieldRootVariants = {
|
|
6
|
+
default: [],
|
|
7
|
+
required: ['after:content-["*"]', 'after:ml-1', 'after:text-red-500'],
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
const fieldRoot = tv({
|
|
11
|
+
base: ['space-y-1'],
|
|
12
|
+
defaultVariants: {
|
|
13
|
+
variant: 'default',
|
|
14
|
+
},
|
|
15
|
+
variants: {
|
|
16
|
+
variant: fieldRootVariants,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const fieldLabel = tv({
|
|
21
|
+
base: ['block', 'text-sm', 'font-medium', 'text-gray-700', 'mb-1'],
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: 'default',
|
|
24
|
+
},
|
|
25
|
+
variants: {
|
|
26
|
+
variant: {
|
|
27
|
+
default: [],
|
|
28
|
+
error: ['text-red-600'],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const fieldDescription = tv({
|
|
34
|
+
base: ['text-sm', 'text-gray-500', 'mt-1'],
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
variant: 'default',
|
|
37
|
+
},
|
|
38
|
+
variants: {
|
|
39
|
+
variant: {
|
|
40
|
+
default: [],
|
|
41
|
+
error: ['text-red-600'],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const fieldError = tv({
|
|
47
|
+
base: ['text-sm', 'text-red-600', 'mt-1'],
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const fieldItem = tv({
|
|
51
|
+
base: ['flex', 'flex-col'],
|
|
52
|
+
defaultVariants: {
|
|
53
|
+
layout: 'default',
|
|
54
|
+
},
|
|
55
|
+
variants: {
|
|
56
|
+
layout: {
|
|
57
|
+
default: [],
|
|
58
|
+
horizontal: ['flex-row', 'items-center', 'gap-2'],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export type FieldRootVariant = keyof typeof fieldRootVariants;
|
|
64
|
+
export type FieldLabelVariant = 'default' | 'error';
|
|
65
|
+
export type FieldDescriptionVariant = 'default' | 'error';
|
|
66
|
+
export type FieldItemLayout = 'default' | 'horizontal';
|
|
67
|
+
|
|
68
|
+
export interface FieldRootProps extends Omit<ComponentProps<typeof BaseUIField.Root>, 'className'> {
|
|
69
|
+
className?: string;
|
|
70
|
+
variant?: FieldRootVariant;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface FieldLabelProps
|
|
74
|
+
extends Omit<ComponentProps<typeof BaseUIField.Label>, 'className'> {
|
|
75
|
+
className?: string;
|
|
76
|
+
variant?: FieldLabelVariant;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface FieldDescriptionProps
|
|
80
|
+
extends Omit<ComponentProps<typeof BaseUIField.Description>, 'className'> {
|
|
81
|
+
className?: string;
|
|
82
|
+
variant?: FieldDescriptionVariant;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface FieldErrorProps
|
|
86
|
+
extends Omit<ComponentProps<typeof BaseUIField.Error>, 'className'> {
|
|
87
|
+
className?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface FieldItemProps extends ComponentProps<'div'> {
|
|
91
|
+
className?: string;
|
|
92
|
+
layout?: FieldItemLayout;
|
|
93
|
+
children: ReactNode;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const FieldRoot = ({ className, variant, ...props }: FieldRootProps) => {
|
|
97
|
+
return (
|
|
98
|
+
<BaseUIField.Root
|
|
99
|
+
className={fieldRoot({
|
|
100
|
+
className,
|
|
101
|
+
variant,
|
|
102
|
+
})}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const FieldLabel = ({ className, variant, ...props }: FieldLabelProps) => {
|
|
109
|
+
return (
|
|
110
|
+
<BaseUIField.Label
|
|
111
|
+
className={fieldLabel({
|
|
112
|
+
className,
|
|
113
|
+
variant,
|
|
114
|
+
})}
|
|
115
|
+
{...props}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const FieldDescription = ({ className, variant, ...props }: FieldDescriptionProps) => {
|
|
121
|
+
return (
|
|
122
|
+
<BaseUIField.Description
|
|
123
|
+
className={fieldDescription({
|
|
124
|
+
className,
|
|
125
|
+
variant,
|
|
126
|
+
})}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const FieldError = ({ className, ...props }: FieldErrorProps) => {
|
|
133
|
+
return (
|
|
134
|
+
<BaseUIField.Error
|
|
135
|
+
className={fieldError({
|
|
136
|
+
className,
|
|
137
|
+
})}
|
|
138
|
+
{...props}
|
|
139
|
+
/>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const FieldItem = ({ className, layout, children, ...props }: FieldItemProps) => {
|
|
144
|
+
return (
|
|
145
|
+
<div
|
|
146
|
+
className={fieldItem({
|
|
147
|
+
className,
|
|
148
|
+
layout,
|
|
149
|
+
})}
|
|
150
|
+
{...props}
|
|
151
|
+
>
|
|
152
|
+
{children}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const Field = {
|
|
158
|
+
Control: BaseUIField.Control,
|
|
159
|
+
Description: FieldDescription,
|
|
160
|
+
Error: FieldError,
|
|
161
|
+
Item: FieldItem,
|
|
162
|
+
Label: FieldLabel,
|
|
163
|
+
Root: FieldRoot,
|
|
164
|
+
Validity: BaseUIField.Validity,
|
|
165
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
FieldDescriptionProps,
|
|
3
|
+
FieldDescriptionVariant,
|
|
4
|
+
FieldErrorProps,
|
|
5
|
+
FieldItemLayout,
|
|
6
|
+
FieldItemProps,
|
|
7
|
+
FieldLabelProps,
|
|
8
|
+
FieldLabelVariant,
|
|
9
|
+
FieldRootProps,
|
|
10
|
+
FieldRootVariant,
|
|
11
|
+
} from './field';
|
|
12
|
+
export { Field } from './field';
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Button } from '../button';
|
|
3
|
+
import { Fieldset } from './fieldset';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
argTypes: {
|
|
7
|
+
variant: {
|
|
8
|
+
control: 'select',
|
|
9
|
+
description: 'Fieldset variant',
|
|
10
|
+
options: ['default', 'compact'],
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
component: Fieldset.Root,
|
|
14
|
+
parameters: {
|
|
15
|
+
layout: 'centered',
|
|
16
|
+
},
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
title: 'Components/Fieldset',
|
|
19
|
+
} satisfies Meta<typeof Fieldset.Root>;
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj<typeof meta>;
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<Fieldset.Root>
|
|
27
|
+
<Fieldset.Legend>Personal Information</Fieldset.Legend>
|
|
28
|
+
<div className="space-y-4">
|
|
29
|
+
<label className="block text-sm font-medium text-gray-700">
|
|
30
|
+
First Name
|
|
31
|
+
<input
|
|
32
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md mt-1"
|
|
33
|
+
placeholder="Enter first name"
|
|
34
|
+
type="text"
|
|
35
|
+
/>
|
|
36
|
+
</label>
|
|
37
|
+
<label className="block text-sm font-medium text-gray-700">
|
|
38
|
+
Last Name
|
|
39
|
+
<input
|
|
40
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md mt-1"
|
|
41
|
+
placeholder="Enter last name"
|
|
42
|
+
type="text"
|
|
43
|
+
/>
|
|
44
|
+
</label>
|
|
45
|
+
</div>
|
|
46
|
+
</Fieldset.Root>
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const Compact: Story = {
|
|
51
|
+
render: () => (
|
|
52
|
+
<Fieldset.Root variant="compact">
|
|
53
|
+
<Fieldset.Legend>Contact Details</Fieldset.Legend>
|
|
54
|
+
<div className="space-y-2">
|
|
55
|
+
<label className="block text-sm font-medium text-gray-700">
|
|
56
|
+
Email
|
|
57
|
+
<input
|
|
58
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md mt-1"
|
|
59
|
+
placeholder="Enter email"
|
|
60
|
+
type="email"
|
|
61
|
+
/>
|
|
62
|
+
</label>
|
|
63
|
+
<label className="block text-sm font-medium text-gray-700">
|
|
64
|
+
Phone
|
|
65
|
+
<input
|
|
66
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md mt-1"
|
|
67
|
+
placeholder="Enter phone number"
|
|
68
|
+
type="tel"
|
|
69
|
+
/>
|
|
70
|
+
</label>
|
|
71
|
+
</div>
|
|
72
|
+
</Fieldset.Root>
|
|
73
|
+
),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const SmallLegend: Story = {
|
|
77
|
+
render: () => (
|
|
78
|
+
<Fieldset.Root>
|
|
79
|
+
<Fieldset.Legend size="small">Settings</Fieldset.Legend>
|
|
80
|
+
<div className="space-y-4">
|
|
81
|
+
<label className="flex items-center">
|
|
82
|
+
<input
|
|
83
|
+
className="mr-2"
|
|
84
|
+
type="checkbox"
|
|
85
|
+
/>
|
|
86
|
+
<span className="text-sm">Enable notifications</span>
|
|
87
|
+
</label>
|
|
88
|
+
<label className="flex items-center">
|
|
89
|
+
<input
|
|
90
|
+
className="mr-2"
|
|
91
|
+
type="checkbox"
|
|
92
|
+
/>
|
|
93
|
+
<span className="text-sm">Allow marketing emails</span>
|
|
94
|
+
</label>
|
|
95
|
+
</div>
|
|
96
|
+
</Fieldset.Root>
|
|
97
|
+
),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const WithCustomClass: Story = {
|
|
101
|
+
render: () => (
|
|
102
|
+
<Fieldset.Root className="bg-blue-50 border-blue-200">
|
|
103
|
+
<Fieldset.Legend className="text-blue-900">Blue Theme</Fieldset.Legend>
|
|
104
|
+
<div className="space-y-4">
|
|
105
|
+
<input
|
|
106
|
+
className="w-full px-3 py-2 border border-blue-300 rounded-md bg-white"
|
|
107
|
+
placeholder="Custom styled input"
|
|
108
|
+
type="text"
|
|
109
|
+
/>
|
|
110
|
+
<input
|
|
111
|
+
className="w-full px-3 py-2 border border-blue-300 rounded-md bg-white"
|
|
112
|
+
placeholder="Another input"
|
|
113
|
+
type="text"
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
</Fieldset.Root>
|
|
117
|
+
),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const NestedFieldsets: Story = {
|
|
121
|
+
render: () => (
|
|
122
|
+
<Fieldset.Root>
|
|
123
|
+
<Fieldset.Legend>Account Settings</Fieldset.Legend>
|
|
124
|
+
<div className="space-y-6">
|
|
125
|
+
<Fieldset.Root className="border-gray-300">
|
|
126
|
+
<Fieldset.Legend size="small">Profile Information</Fieldset.Legend>
|
|
127
|
+
<div className="space-y-3">
|
|
128
|
+
<input
|
|
129
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md"
|
|
130
|
+
placeholder="Username"
|
|
131
|
+
type="text"
|
|
132
|
+
/>
|
|
133
|
+
<input
|
|
134
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md"
|
|
135
|
+
placeholder="Email"
|
|
136
|
+
type="email"
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
</Fieldset.Root>
|
|
140
|
+
<Fieldset.Root className="border-gray-300">
|
|
141
|
+
<Fieldset.Legend size="small">Privacy Settings</Fieldset.Legend>
|
|
142
|
+
<div className="space-y-2">
|
|
143
|
+
<label className="flex items-center">
|
|
144
|
+
<input
|
|
145
|
+
className="mr-2"
|
|
146
|
+
type="checkbox"
|
|
147
|
+
/>
|
|
148
|
+
<span className="text-sm">Public profile</span>
|
|
149
|
+
</label>
|
|
150
|
+
<label className="flex items-center">
|
|
151
|
+
<input
|
|
152
|
+
className="mr-2"
|
|
153
|
+
type="checkbox"
|
|
154
|
+
/>
|
|
155
|
+
<span className="text-sm">Show email</span>
|
|
156
|
+
</label>
|
|
157
|
+
</div>
|
|
158
|
+
</Fieldset.Root>
|
|
159
|
+
</div>
|
|
160
|
+
</Fieldset.Root>
|
|
161
|
+
),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const FormExample: Story = {
|
|
165
|
+
render: () => (
|
|
166
|
+
<form className="space-y-6">
|
|
167
|
+
<Fieldset.Root>
|
|
168
|
+
<Fieldset.Legend>User Registration</Fieldset.Legend>
|
|
169
|
+
<div className="space-y-4">
|
|
170
|
+
<label className="block text-sm font-medium text-gray-700">
|
|
171
|
+
Full Name
|
|
172
|
+
<input
|
|
173
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md mt-1"
|
|
174
|
+
placeholder="Enter your full name"
|
|
175
|
+
type="text"
|
|
176
|
+
/>
|
|
177
|
+
</label>
|
|
178
|
+
<label className="block text-sm font-medium text-gray-700">
|
|
179
|
+
Email Address
|
|
180
|
+
<input
|
|
181
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md mt-1"
|
|
182
|
+
placeholder="Enter your email"
|
|
183
|
+
type="email"
|
|
184
|
+
/>
|
|
185
|
+
</label>
|
|
186
|
+
<label className="block text-sm font-medium text-gray-700">
|
|
187
|
+
Password
|
|
188
|
+
<input
|
|
189
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md mt-1"
|
|
190
|
+
placeholder="Enter password"
|
|
191
|
+
type="password"
|
|
192
|
+
/>
|
|
193
|
+
</label>
|
|
194
|
+
</div>
|
|
195
|
+
</Fieldset.Root>
|
|
196
|
+
<Button
|
|
197
|
+
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
|
198
|
+
type="submit"
|
|
199
|
+
>
|
|
200
|
+
Register
|
|
201
|
+
</Button>
|
|
202
|
+
</form>
|
|
203
|
+
),
|
|
204
|
+
};
|