@regardio/react 0.5.5 → 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 (182) 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 +17 -129
  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/link/link.test.tsx +169 -0
  120. package/src/password-input/index.ts +1 -1
  121. package/src/password-input/password-input.tsx +104 -27
  122. package/src/radio/index.ts +2 -0
  123. package/src/radio/radio.tsx +92 -0
  124. package/src/radio-group/index.ts +2 -0
  125. package/src/radio-group/radio-group.tsx +36 -0
  126. package/src/slider/index.ts +18 -0
  127. package/src/slider/slider.tsx +179 -0
  128. package/src/switch/index.ts +2 -0
  129. package/src/switch/switch.stories.tsx +118 -0
  130. package/src/switch/switch.tsx +101 -0
  131. package/src/toggle/index.ts +2 -0
  132. package/src/toggle/toggle.stories.tsx +232 -0
  133. package/src/toggle/toggle.test.tsx +149 -0
  134. package/src/toggle/toggle.tsx +88 -0
  135. package/src/utils/text/text.test.tsx +110 -0
  136. package/dist/background-slideshow/index.d.ts +0 -24
  137. package/dist/background-slideshow/index.js +0 -165
  138. package/dist/blurry-gradient/index.d.ts +0 -16
  139. package/dist/blurry-gradient/index.js +0 -128
  140. package/dist/carousel/index.d.ts +0 -36
  141. package/dist/carousel/index.js +0 -171
  142. package/dist/countdown/index.d.ts +0 -5
  143. package/dist/countdown/index.js +0 -73
  144. package/dist/generic-error/index.js +0 -47
  145. package/dist/grid/index.d.ts +0 -1196
  146. package/dist/grid/index.js +0 -239
  147. package/dist/heading/index.d.ts +0 -24
  148. package/dist/heading/index.js +0 -99
  149. package/dist/highlight/index.d.ts +0 -13
  150. package/dist/highlight/index.js +0 -59
  151. package/dist/hooks/use-current-route-data.js +0 -16
  152. package/dist/hooks/use-focus-search.js +0 -19
  153. package/dist/hooks/use-matches-data.js +0 -15
  154. package/dist/hooks/use-media-query.js +0 -20
  155. package/dist/hooks/use-mobile.d.ts +0 -3
  156. package/dist/hooks/use-mobile.js +0 -19
  157. package/dist/hooks/use-nonce.d.ts +0 -7
  158. package/dist/hooks/use-nonce.js +0 -8
  159. package/dist/hooks/use-orientation.js +0 -29
  160. package/dist/hooks/use-user.d.ts +0 -50
  161. package/dist/hooks/use-user.js +0 -25
  162. package/dist/icon-button/index.d.ts +0 -9
  163. package/dist/icon-button/index.js +0 -17
  164. package/dist/if/index.d.ts +0 -10
  165. package/dist/if/index.js +0 -24
  166. package/dist/iframe/index.d.ts +0 -10
  167. package/dist/iframe/index.js +0 -17
  168. package/dist/link/index.d.ts +0 -55
  169. package/dist/link/index.js +0 -195
  170. package/dist/list/index.d.ts +0 -69
  171. package/dist/list/index.js +0 -65
  172. package/dist/markdown-container/index.d.ts +0 -22
  173. package/dist/markdown-container/index.js +0 -128
  174. package/dist/password-input/index.d.ts +0 -11
  175. package/dist/password-input/index.js +0 -46
  176. package/dist/picture/index.js +0 -68
  177. package/dist/protected-email/index.js +0 -30
  178. package/dist/text/index.d.ts +0 -20
  179. package/dist/text/index.js +0 -38
  180. package/dist/utils/author/index.d.ts +0 -3
  181. package/dist/utils/author/index.js +0 -33
  182. package/dist/utils/text/index.js +0 -73
@@ -0,0 +1,118 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useState } from 'react';
3
+ import { Field } from '../field';
4
+ import { Checkbox } from './checkbox';
5
+
6
+ const meta: Meta<typeof Checkbox.Root> = {
7
+ argTypes: {
8
+ disabled: {
9
+ control: 'boolean',
10
+ description: 'Disable the checkbox',
11
+ },
12
+ size: {
13
+ control: 'select',
14
+ description: 'Checkbox size',
15
+ options: ['sm', 'md', 'lg'],
16
+ },
17
+ },
18
+ component: Checkbox.Root,
19
+ parameters: {
20
+ layout: 'centered',
21
+ },
22
+ tags: ['autodocs'],
23
+ title: 'Components/Checkbox',
24
+ };
25
+
26
+ export default meta;
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const Default: Story = {
30
+ render: () => (
31
+ <Checkbox.Root>
32
+ <Checkbox.Indicator />
33
+ </Checkbox.Root>
34
+ ),
35
+ };
36
+
37
+ export const Checked: Story = {
38
+ render: () => (
39
+ <Checkbox.Root defaultChecked>
40
+ <Checkbox.Indicator />
41
+ </Checkbox.Root>
42
+ ),
43
+ };
44
+
45
+ export const Small: Story = {
46
+ render: () => (
47
+ <Checkbox.Root size="sm">
48
+ <Checkbox.Indicator />
49
+ </Checkbox.Root>
50
+ ),
51
+ };
52
+
53
+ export const Large: Story = {
54
+ render: () => (
55
+ <Checkbox.Root size="lg">
56
+ <Checkbox.Indicator />
57
+ </Checkbox.Root>
58
+ ),
59
+ };
60
+
61
+ export const Disabled: Story = {
62
+ render: () => (
63
+ <Checkbox.Root disabled>
64
+ <Checkbox.Indicator />
65
+ </Checkbox.Root>
66
+ ),
67
+ };
68
+
69
+ export const WithLabel: Story = {
70
+ render: () => (
71
+ <Field.Root>
72
+ <Field.Label>
73
+ <Checkbox.Root>
74
+ <Checkbox.Indicator />
75
+ </Checkbox.Root>
76
+ Accept terms and conditions
77
+ </Field.Label>
78
+ </Field.Root>
79
+ ),
80
+ };
81
+
82
+ export const Controlled: Story = {
83
+ render: () => {
84
+ const [checked, setChecked] = useState(false);
85
+ return (
86
+ <div className="space-y-4">
87
+ <Field.Root>
88
+ <Field.Label>
89
+ <Checkbox.Root
90
+ checked={checked}
91
+ onCheckedChange={setChecked}
92
+ >
93
+ <Checkbox.Indicator />
94
+ </Checkbox.Root>
95
+ Subscribe to newsletter
96
+ </Field.Label>
97
+ </Field.Root>
98
+ <p className="text-sm text-gray-600">Status: {checked ? 'Subscribed' : 'Not subscribed'}</p>
99
+ </div>
100
+ );
101
+ },
102
+ };
103
+
104
+ export const AllSizes: Story = {
105
+ render: () => (
106
+ <div className="flex items-center gap-4">
107
+ <Checkbox.Root size="sm">
108
+ <Checkbox.Indicator />
109
+ </Checkbox.Root>
110
+ <Checkbox.Root size="md">
111
+ <Checkbox.Indicator />
112
+ </Checkbox.Root>
113
+ <Checkbox.Root size="lg">
114
+ <Checkbox.Indicator />
115
+ </Checkbox.Root>
116
+ </div>
117
+ ),
118
+ };
@@ -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';