@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.
Files changed (180) hide show
  1. package/dist/background-slideshow/index.d.mts +36 -0
  2. package/dist/background-slideshow/index.mjs +110 -0
  3. package/dist/blurry-gradient/index.d.mts +17 -0
  4. package/dist/blurry-gradient/index.mjs +93 -0
  5. package/dist/button/index.d.mts +2 -0
  6. package/dist/button/index.mjs +3 -0
  7. package/dist/button-BiSQpBbc.mjs +129 -0
  8. package/dist/carousel/index.d.mts +40 -0
  9. package/dist/carousel/index.mjs +141 -0
  10. package/dist/checkbox/index.d.mts +37 -0
  11. package/dist/checkbox/index.mjs +70 -0
  12. package/dist/checkbox-group/index.d.mts +17 -0
  13. package/dist/checkbox-group/index.mjs +29 -0
  14. package/dist/chunk-BTpB_u-K.mjs +18 -0
  15. package/dist/countdown/index.d.mts +6 -0
  16. package/dist/countdown/index.mjs +58 -0
  17. package/dist/field/index.d.mts +66 -0
  18. package/dist/field/index.mjs +115 -0
  19. package/dist/fieldset/index.d.mts +33 -0
  20. package/dist/fieldset/index.mjs +61 -0
  21. package/dist/form/index.d.mts +22 -0
  22. package/dist/form/index.mjs +31 -0
  23. package/dist/generic-error/{index.d.ts → index.d.mts} +22 -18
  24. package/dist/generic-error/index.mjs +57 -0
  25. package/dist/grid/index.d.mts +1197 -0
  26. package/dist/grid/index.mjs +221 -0
  27. package/dist/heading/index.d.mts +31 -0
  28. package/dist/heading/index.mjs +29 -0
  29. package/dist/highlight/index.d.mts +18 -0
  30. package/dist/highlight/index.mjs +35 -0
  31. package/dist/hooks/{use-current-route-data.d.ts → use-current-route-data.d.mts} +3 -2
  32. package/dist/hooks/use-current-route-data.mjs +20 -0
  33. package/dist/hooks/{use-focus-search.d.ts → use-focus-search.d.mts} +4 -3
  34. package/dist/hooks/use-focus-search.mjs +21 -0
  35. package/dist/hooks/{use-matches-data.d.ts → use-matches-data.d.mts} +3 -2
  36. package/dist/hooks/use-matches-data.mjs +21 -0
  37. package/dist/hooks/{use-media-query.d.ts → use-media-query.d.mts} +3 -2
  38. package/dist/hooks/use-media-query.mjs +26 -0
  39. package/dist/hooks/use-mobile.d.mts +4 -0
  40. package/dist/hooks/use-mobile.mjs +20 -0
  41. package/dist/hooks/use-nonce.d.mts +8 -0
  42. package/dist/hooks/use-nonce.mjs +13 -0
  43. package/dist/hooks/{use-orientation.d.ts → use-orientation.d.mts} +3 -2
  44. package/dist/hooks/use-orientation.mjs +30 -0
  45. package/dist/hooks/use-user.d.mts +55 -0
  46. package/dist/hooks/use-user.mjs +39 -0
  47. package/dist/icon-button/index.d.mts +29 -0
  48. package/dist/icon-button/index.mjs +36 -0
  49. package/dist/if/index.d.mts +15 -0
  50. package/dist/if/index.mjs +21 -0
  51. package/dist/iframe/index.d.mts +11 -0
  52. package/dist/iframe/index.mjs +15 -0
  53. package/dist/index-Bm-tWhsb.d.mts +30 -0
  54. package/dist/index-YT2CkvL6.d.mts +36 -0
  55. package/dist/input/index.d.mts +2 -0
  56. package/dist/input/index.mjs +3 -0
  57. package/dist/input-CtR6aRVi.mjs +73 -0
  58. package/dist/link/index.d.mts +73 -0
  59. package/dist/link/index.mjs +129 -0
  60. package/dist/list/index.d.mts +71 -0
  61. package/dist/list/index.mjs +54 -0
  62. package/dist/markdown-container/index.d.mts +23 -0
  63. package/dist/markdown-container/index.mjs +71 -0
  64. package/dist/password-input/index.d.mts +24 -0
  65. package/dist/password-input/index.mjs +92 -0
  66. package/dist/picture/{index.d.ts → index.d.mts} +21 -20
  67. package/dist/picture/index.mjs +3 -0
  68. package/dist/picture-DkX3W5zl.mjs +69 -0
  69. package/dist/protected-email/{index.d.ts → index.d.mts} +14 -8
  70. package/dist/protected-email/index.mjs +37 -0
  71. package/dist/radio/index.d.mts +37 -0
  72. package/dist/radio/index.mjs +72 -0
  73. package/dist/radio-group/index.d.mts +17 -0
  74. package/dist/radio-group/index.mjs +29 -0
  75. package/dist/slider/index.d.mts +85 -0
  76. package/dist/slider/index.mjs +133 -0
  77. package/dist/switch/index.d.mts +38 -0
  78. package/dist/switch/index.mjs +87 -0
  79. package/dist/text/index.d.mts +26 -0
  80. package/dist/text/index.mjs +32 -0
  81. package/dist/text-CPlUND-Z.mjs +58 -0
  82. package/dist/toggle/index.d.mts +59 -0
  83. package/dist/toggle/index.mjs +82 -0
  84. package/dist/utils/author/index.d.mts +4 -0
  85. package/dist/utils/author/index.mjs +26 -0
  86. package/dist/utils/text/{index.d.ts → index.d.mts} +4 -3
  87. package/dist/utils/text/index.mjs +3 -0
  88. package/package.json +5 -117
  89. package/src/button/button.stories.tsx +161 -0
  90. package/src/button/button.test.tsx +73 -0
  91. package/src/button/button.tsx +112 -0
  92. package/src/button/index.ts +2 -0
  93. package/src/carousel/carousel-next.tsx +2 -2
  94. package/src/carousel/carousel-previous.tsx +2 -2
  95. package/src/checkbox/checkbox.stories.tsx +118 -0
  96. package/src/checkbox/checkbox.tsx +91 -0
  97. package/src/checkbox/index.ts +2 -0
  98. package/src/checkbox-group/checkbox-group.tsx +40 -0
  99. package/src/checkbox-group/index.ts +2 -0
  100. package/src/field/field.stories.tsx +105 -0
  101. package/src/field/field.test.tsx +61 -0
  102. package/src/field/field.tsx +165 -0
  103. package/src/field/index.ts +12 -0
  104. package/src/fieldset/fieldset.stories.tsx +204 -0
  105. package/src/fieldset/fieldset.test.tsx +63 -0
  106. package/src/fieldset/fieldset.tsx +75 -0
  107. package/src/fieldset/index.ts +7 -0
  108. package/src/form/form.stories.tsx +230 -0
  109. package/src/form/form.test.tsx +68 -0
  110. package/src/form/form.tsx +38 -0
  111. package/src/form/index.ts +2 -0
  112. package/src/icon-button/icon-button.stories.tsx +128 -7
  113. package/src/icon-button/icon-button.test.tsx +152 -0
  114. package/src/icon-button/icon-button.tsx +43 -9
  115. package/src/input/index.ts +2 -0
  116. package/src/input/input.stories.tsx +151 -0
  117. package/src/input/input.test.tsx +65 -0
  118. package/src/input/input.tsx +113 -0
  119. package/src/password-input/index.ts +1 -1
  120. package/src/password-input/password-input.tsx +104 -27
  121. package/src/radio/index.ts +2 -0
  122. package/src/radio/radio.tsx +92 -0
  123. package/src/radio-group/index.ts +2 -0
  124. package/src/radio-group/radio-group.tsx +36 -0
  125. package/src/slider/index.ts +18 -0
  126. package/src/slider/slider.tsx +179 -0
  127. package/src/switch/index.ts +2 -0
  128. package/src/switch/switch.stories.tsx +118 -0
  129. package/src/switch/switch.tsx +101 -0
  130. package/src/toggle/index.ts +2 -0
  131. package/src/toggle/toggle.stories.tsx +232 -0
  132. package/src/toggle/toggle.test.tsx +149 -0
  133. package/src/toggle/toggle.tsx +88 -0
  134. package/dist/background-slideshow/index.d.ts +0 -24
  135. package/dist/background-slideshow/index.js +0 -165
  136. package/dist/blurry-gradient/index.d.ts +0 -16
  137. package/dist/blurry-gradient/index.js +0 -128
  138. package/dist/carousel/index.d.ts +0 -36
  139. package/dist/carousel/index.js +0 -171
  140. package/dist/countdown/index.d.ts +0 -5
  141. package/dist/countdown/index.js +0 -73
  142. package/dist/generic-error/index.js +0 -47
  143. package/dist/grid/index.d.ts +0 -1196
  144. package/dist/grid/index.js +0 -239
  145. package/dist/heading/index.d.ts +0 -24
  146. package/dist/heading/index.js +0 -99
  147. package/dist/highlight/index.d.ts +0 -13
  148. package/dist/highlight/index.js +0 -59
  149. package/dist/hooks/use-current-route-data.js +0 -16
  150. package/dist/hooks/use-focus-search.js +0 -19
  151. package/dist/hooks/use-matches-data.js +0 -15
  152. package/dist/hooks/use-media-query.js +0 -20
  153. package/dist/hooks/use-mobile.d.ts +0 -3
  154. package/dist/hooks/use-mobile.js +0 -19
  155. package/dist/hooks/use-nonce.d.ts +0 -7
  156. package/dist/hooks/use-nonce.js +0 -8
  157. package/dist/hooks/use-orientation.js +0 -29
  158. package/dist/hooks/use-user.d.ts +0 -50
  159. package/dist/hooks/use-user.js +0 -25
  160. package/dist/icon-button/index.d.ts +0 -9
  161. package/dist/icon-button/index.js +0 -17
  162. package/dist/if/index.d.ts +0 -10
  163. package/dist/if/index.js +0 -24
  164. package/dist/iframe/index.d.ts +0 -10
  165. package/dist/iframe/index.js +0 -17
  166. package/dist/link/index.d.ts +0 -55
  167. package/dist/link/index.js +0 -195
  168. package/dist/list/index.d.ts +0 -69
  169. package/dist/list/index.js +0 -65
  170. package/dist/markdown-container/index.d.ts +0 -22
  171. package/dist/markdown-container/index.js +0 -128
  172. package/dist/password-input/index.d.ts +0 -11
  173. package/dist/password-input/index.js +0 -46
  174. package/dist/picture/index.js +0 -68
  175. package/dist/protected-email/index.js +0 -30
  176. package/dist/text/index.d.ts +0 -20
  177. package/dist/text/index.js +0 -38
  178. package/dist/utils/author/index.d.ts +0 -3
  179. package/dist/utils/author/index.js +0 -33
  180. 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,2 @@
1
+ export type { CheckboxIndicatorProps, CheckboxRootProps, CheckboxSize } from './checkbox';
2
+ export { Checkbox, CheckboxIndicator, CheckboxRoot } from './checkbox';
@@ -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,2 @@
1
+ export type { CheckboxGroupOrientation, CheckboxGroupProps } from './checkbox-group';
2
+ export { CheckboxGroup } from './checkbox-group';
@@ -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
+ };