@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
@@ -2,6 +2,26 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { IconButton } from './icon-button';
3
3
 
4
4
  const meta: Meta<typeof IconButton> = {
5
+ argTypes: {
6
+ disabled: {
7
+ control: 'boolean',
8
+ description: 'Disable the button',
9
+ },
10
+ size: {
11
+ control: 'select',
12
+ description: 'Icon button size',
13
+ options: ['sm', 'md', 'lg'],
14
+ },
15
+ title: {
16
+ control: 'text',
17
+ description: 'Title for tooltip and accessibility',
18
+ },
19
+ variant: {
20
+ control: 'select',
21
+ description: 'Button variant',
22
+ options: ['primary', 'secondary', 'outline', 'ghost', 'destructive'],
23
+ },
24
+ },
5
25
  component: IconButton,
6
26
  parameters: {
7
27
  layout: 'centered',
@@ -11,7 +31,7 @@ const meta: Meta<typeof IconButton> = {
11
31
  };
12
32
 
13
33
  export default meta;
14
- type Story = StoryObj<typeof IconButton>;
34
+ type Story = StoryObj<typeof meta>;
15
35
 
16
36
  const PlusIcon = () => (
17
37
  <svg
@@ -61,30 +81,131 @@ const CloseIcon = () => (
61
81
  </svg>
62
82
  );
63
83
 
84
+ const SettingsIcon = () => (
85
+ <svg
86
+ fill="none"
87
+ height="24"
88
+ stroke="currentColor"
89
+ strokeWidth="2"
90
+ viewBox="0 0 24 24"
91
+ width="24"
92
+ >
93
+ <circle
94
+ cx="12"
95
+ cy="12"
96
+ r="3"
97
+ />
98
+ <path d="m12 1v6m0 6v6m4.22-13.22 4.24 4.24M1.54 8.96l4.24 4.24m12.44 0 4.24 4.24M1.54 15.04l4.24-4.24" />
99
+ </svg>
100
+ );
101
+
64
102
  export const Default: Story = {
65
103
  args: {
66
104
  icon: <PlusIcon />,
105
+ title: 'Add',
67
106
  },
68
107
  };
69
108
 
70
109
  export const Close: Story = {
71
110
  args: {
72
111
  icon: <CloseIcon />,
112
+ title: 'Close',
113
+ variant: 'ghost',
114
+ },
115
+ };
116
+
117
+ export const Settings: Story = {
118
+ args: {
119
+ icon: <SettingsIcon />,
120
+ title: 'Settings',
121
+ variant: 'secondary',
122
+ },
123
+ };
124
+
125
+ export const Small: Story = {
126
+ args: {
127
+ icon: <PlusIcon />,
128
+ size: 'sm',
129
+ title: 'Add',
130
+ },
131
+ };
132
+
133
+ export const Large: Story = {
134
+ args: {
135
+ icon: <PlusIcon />,
136
+ size: 'lg',
137
+ title: 'Add',
73
138
  },
74
139
  };
75
140
 
76
- export const WithCustomClass: Story = {
141
+ export const Disabled: Story = {
77
142
  args: {
78
- className: 'p-2 bg-gray-100 rounded hover:bg-gray-200',
143
+ disabled: true,
79
144
  icon: <PlusIcon />,
145
+ title: 'Add',
146
+ },
147
+ };
148
+
149
+ export const WithAriaLabel: Story = {
150
+ args: {
151
+ 'aria-label': 'Close dialog',
152
+ icon: <CloseIcon />,
80
153
  },
81
154
  };
82
155
 
83
- export const AllIcons: Story = {
156
+ export const AllVariants: Story = {
84
157
  render: () => (
85
- <div style={{ display: 'flex', gap: '16px' }}>
86
- <IconButton icon={<PlusIcon />} />
87
- <IconButton icon={<CloseIcon />} />
158
+ <div className="flex gap-4">
159
+ <IconButton
160
+ icon={<PlusIcon />}
161
+ title="Add"
162
+ variant="primary"
163
+ />
164
+ <IconButton
165
+ icon={<SettingsIcon />}
166
+ title="Settings"
167
+ variant="secondary"
168
+ />
169
+ <IconButton
170
+ icon={<CloseIcon />}
171
+ title="Close"
172
+ variant="ghost"
173
+ />
174
+ <IconButton
175
+ icon={<PlusIcon />}
176
+ title="Delete"
177
+ variant="destructive"
178
+ />
88
179
  </div>
89
180
  ),
90
181
  };
182
+
183
+ export const AllSizes: Story = {
184
+ render: () => (
185
+ <div className="flex items-center gap-4">
186
+ <IconButton
187
+ icon={<PlusIcon />}
188
+ size="sm"
189
+ title="Small"
190
+ />
191
+ <IconButton
192
+ icon={<PlusIcon />}
193
+ size="md"
194
+ title="Medium"
195
+ />
196
+ <IconButton
197
+ icon={<PlusIcon />}
198
+ size="lg"
199
+ title="Large"
200
+ />
201
+ </div>
202
+ ),
203
+ };
204
+
205
+ export const Interactive: Story = {
206
+ args: {
207
+ icon: <PlusIcon />,
208
+ onClick: () => alert('Icon button clicked!'),
209
+ title: 'Add item',
210
+ },
211
+ };
@@ -0,0 +1,152 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { IconButton } from './icon-button';
4
+
5
+ const TestIcon = () => (
6
+ <svg
7
+ data-testid="test-icon"
8
+ height="24"
9
+ viewBox="0 0 24 24"
10
+ width="24"
11
+ >
12
+ <circle
13
+ cx="12"
14
+ cy="12"
15
+ r="10"
16
+ />
17
+ </svg>
18
+ );
19
+
20
+ describe('IconButton', () => {
21
+ it('renders with icon', () => {
22
+ render(
23
+ <IconButton
24
+ icon={<TestIcon />}
25
+ title="Test"
26
+ />,
27
+ );
28
+ expect(screen.getByTestId('test-icon')).toBeInTheDocument();
29
+ });
30
+
31
+ it('applies size variants', () => {
32
+ render(
33
+ <IconButton
34
+ data-testid="size-button"
35
+ icon={<TestIcon />}
36
+ size="lg"
37
+ title="Test"
38
+ />,
39
+ );
40
+ const button = screen.getByTestId('size-button');
41
+ expect(button).toHaveClass('p-3');
42
+ });
43
+
44
+ it('applies variant styles from Button', () => {
45
+ render(
46
+ <IconButton
47
+ data-testid="variant-button"
48
+ icon={<TestIcon />}
49
+ title="Test"
50
+ variant="secondary"
51
+ />,
52
+ );
53
+ const button = screen.getByTestId('variant-button');
54
+ expect(button).toHaveClass('bg-gray-100');
55
+ });
56
+
57
+ it('uses title as aria-label when provided', () => {
58
+ render(
59
+ <IconButton
60
+ icon={<TestIcon />}
61
+ title="Add item"
62
+ />,
63
+ );
64
+ const button = screen.getByRole('button', { name: 'Add item' });
65
+ expect(button).toHaveAttribute('aria-label', 'Add item');
66
+ });
67
+
68
+ it('uses aria-label when provided', () => {
69
+ render(
70
+ <IconButton
71
+ aria-label="Custom label"
72
+ icon={<TestIcon />}
73
+ />,
74
+ );
75
+ const button = screen.getByRole('button', { name: 'Custom label' });
76
+ expect(button).toHaveAttribute('aria-label', 'Custom label');
77
+ });
78
+
79
+ it('prioritizes aria-label over title', () => {
80
+ render(
81
+ <IconButton
82
+ aria-label="Custom label"
83
+ data-testid="priority-button"
84
+ icon={<TestIcon />}
85
+ title="Add item"
86
+ />,
87
+ );
88
+ const button = screen.getByTestId('priority-button');
89
+ expect(button).toHaveAttribute('aria-label', 'Custom label');
90
+ });
91
+
92
+ it('handles disabled state', () => {
93
+ render(
94
+ <IconButton
95
+ data-testid="disabled-button"
96
+ disabled
97
+ icon={<TestIcon />}
98
+ title="Test"
99
+ />,
100
+ );
101
+ const button = screen.getByTestId('disabled-button');
102
+ expect(button).toBeDisabled();
103
+ });
104
+
105
+ it('passes through other props', () => {
106
+ render(
107
+ <IconButton
108
+ data-testid="custom-button"
109
+ icon={<TestIcon />}
110
+ title="Test"
111
+ />,
112
+ );
113
+ expect(screen.getByTestId('custom-button')).toBeInTheDocument();
114
+ });
115
+
116
+ it('applies custom className', () => {
117
+ render(
118
+ <IconButton
119
+ className="custom-class"
120
+ data-testid="custom-class-button"
121
+ icon={<TestIcon />}
122
+ title="Test"
123
+ />,
124
+ );
125
+ const button = screen.getByTestId('custom-class-button');
126
+ expect(button).toHaveClass('custom-class');
127
+ });
128
+
129
+ it('centers icon properly', () => {
130
+ render(
131
+ <IconButton
132
+ data-testid="center-button"
133
+ icon={<TestIcon />}
134
+ title="Test"
135
+ />,
136
+ );
137
+ const button = screen.getByTestId('center-button');
138
+ expect(button).toHaveClass('flex', 'items-center', 'justify-center');
139
+ });
140
+
141
+ it('sets title attribute', () => {
142
+ render(
143
+ <IconButton
144
+ data-testid="title-button"
145
+ icon={<TestIcon />}
146
+ title="Tooltip text"
147
+ />,
148
+ );
149
+ const button = screen.getByTestId('title-button');
150
+ expect(button).toHaveAttribute('title', 'Tooltip text');
151
+ });
152
+ });
@@ -1,20 +1,54 @@
1
+ import { tv } from '@regardio/tailwind/utils';
1
2
  import type { ComponentProps, ReactNode } from 'react';
3
+ import { Button } from '../button';
2
4
 
3
- export interface IconButtonProps extends ComponentProps<'button'> {
5
+ const iconButtonVariants = {
6
+ default: ['p-2'],
7
+ lg: ['p-3'],
8
+ md: ['p-2'],
9
+ sm: ['p-1'],
10
+ } as const;
11
+
12
+ const iconButton = tv({
13
+ base: ['flex', 'items-center', 'justify-center'],
14
+ defaultVariants: {
15
+ size: 'md',
16
+ },
17
+ variants: {
18
+ size: iconButtonVariants,
19
+ },
20
+ });
21
+
22
+ export type IconButtonSize = keyof typeof iconButtonVariants;
23
+
24
+ export interface IconButtonProps extends Omit<ComponentProps<typeof Button>, 'size'> {
4
25
  icon: ReactNode;
26
+ size?: IconButtonSize;
27
+ title?: string;
28
+ 'aria-label'?: string;
29
+ children?: never; // Prevent children, only icon allowed
5
30
  }
6
31
 
7
- export const IconButton = (props: IconButtonProps) => {
8
- const { className, icon } = props;
9
-
10
- const defaultClassName = 'p-0';
32
+ export const IconButton = ({
33
+ icon,
34
+ size = 'md',
35
+ title,
36
+ 'aria-label': ariaLabel,
37
+ className,
38
+ ...props
39
+ }: IconButtonProps) => {
40
+ // Use title for both title and aria-label if aria-label not provided
41
+ const finalAriaLabel = ariaLabel || title;
42
+ const finalTitle = title || ariaLabel;
11
43
 
12
44
  return (
13
- <button
14
- className={className ? className : defaultClassName}
15
- type={'button'}
45
+ <Button
46
+ aria-label={finalAriaLabel}
47
+ className={iconButton({ className, size })}
48
+ title={finalTitle}
49
+ {...props}
16
50
  >
17
51
  {icon}
18
- </button>
52
+ </Button>
19
53
  );
20
54
  };
@@ -0,0 +1,2 @@
1
+ export type { InputProps, InputSize, InputVariant } from './input';
2
+ export { Input } from './input';
@@ -0,0 +1,151 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Input } from './input';
3
+
4
+ const meta = {
5
+ argTypes: {
6
+ disabled: {
7
+ control: 'boolean',
8
+ description: 'Disable the input',
9
+ },
10
+ placeholder: {
11
+ control: 'text',
12
+ description: 'Input placeholder',
13
+ },
14
+ size: {
15
+ control: 'select',
16
+ description: 'Input size',
17
+ options: ['sm', 'md', 'lg'],
18
+ },
19
+ variant: {
20
+ control: 'select',
21
+ description: 'Input variant',
22
+ options: ['default', 'error', 'success'],
23
+ },
24
+ },
25
+ component: Input,
26
+ parameters: {
27
+ layout: 'centered',
28
+ },
29
+ tags: ['autodocs'],
30
+ title: 'Components/Input',
31
+ } satisfies Meta<typeof Input>;
32
+
33
+ export default meta;
34
+ type Story = StoryObj<typeof meta>;
35
+
36
+ export const Default: Story = {
37
+ args: {
38
+ placeholder: 'Enter text...',
39
+ },
40
+ };
41
+
42
+ export const ErrorState: Story = {
43
+ args: {
44
+ placeholder: 'Error state',
45
+ variant: 'error',
46
+ },
47
+ };
48
+
49
+ export const Success: Story = {
50
+ args: {
51
+ placeholder: 'Success state',
52
+ variant: 'success',
53
+ },
54
+ };
55
+
56
+ export const Small: Story = {
57
+ args: {
58
+ placeholder: 'Small input',
59
+ size: 'sm',
60
+ },
61
+ };
62
+
63
+ export const Large: Story = {
64
+ args: {
65
+ placeholder: 'Large input',
66
+ size: 'lg',
67
+ },
68
+ };
69
+
70
+ export const Disabled: Story = {
71
+ args: {
72
+ disabled: true,
73
+ placeholder: 'Disabled input',
74
+ },
75
+ };
76
+
77
+ export const WithValue: Story = {
78
+ args: {
79
+ defaultValue: 'Default value',
80
+ },
81
+ };
82
+
83
+ export const EmailInput: Story = {
84
+ args: {
85
+ placeholder: 'Enter your email',
86
+ type: 'email',
87
+ },
88
+ };
89
+
90
+ export const PasswordInput: Story = {
91
+ args: {
92
+ placeholder: 'Enter your password',
93
+ type: 'password',
94
+ },
95
+ };
96
+
97
+ export const NumberInput: Story = {
98
+ args: {
99
+ placeholder: 'Enter a number',
100
+ type: 'number',
101
+ },
102
+ };
103
+
104
+ export const WithCustomClass: Story = {
105
+ args: {
106
+ className: 'shadow-lg border-blue-300',
107
+ placeholder: 'Custom styled input',
108
+ },
109
+ };
110
+
111
+ export const AllVariants: Story = {
112
+ render: () => (
113
+ <div className="space-y-4 w-64">
114
+ <Input placeholder="Default input" />
115
+ <Input
116
+ placeholder="Error input"
117
+ variant="error"
118
+ />
119
+ <Input
120
+ placeholder="Success input"
121
+ variant="success"
122
+ />
123
+ </div>
124
+ ),
125
+ };
126
+
127
+ export const AllSizes: Story = {
128
+ render: () => (
129
+ <div className="space-y-4 w-64">
130
+ <Input
131
+ placeholder="Small input"
132
+ size="sm"
133
+ />
134
+ <Input
135
+ placeholder="Medium input"
136
+ size="md"
137
+ />
138
+ <Input
139
+ placeholder="Large input"
140
+ size="lg"
141
+ />
142
+ </div>
143
+ ),
144
+ };
145
+
146
+ export const Interactive: Story = {
147
+ args: {
148
+ onValueChange: (value: string) => console.log('Input changed:', value),
149
+ placeholder: 'Type something...',
150
+ },
151
+ };
@@ -0,0 +1,65 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { Input } from './input';
4
+
5
+ describe('Input', () => {
6
+ it('renders input with default props', () => {
7
+ render(<Input placeholder="Test input" />);
8
+ expect(screen.getByPlaceholderText('Test input')).toBeInTheDocument();
9
+ });
10
+
11
+ it('applies variant styles', () => {
12
+ render(
13
+ <Input
14
+ placeholder="Error input"
15
+ variant="error"
16
+ />,
17
+ );
18
+ expect(screen.getByPlaceholderText('Error input')).toHaveClass('border-red-300');
19
+ });
20
+
21
+ it('applies size styles', () => {
22
+ render(
23
+ <Input
24
+ placeholder="Large input"
25
+ size="lg"
26
+ />,
27
+ );
28
+ expect(screen.getByPlaceholderText('Large input')).toHaveClass('px-4', 'py-3', 'text-lg');
29
+ });
30
+
31
+ it('applies custom className', () => {
32
+ render(
33
+ <Input
34
+ className="custom-input"
35
+ placeholder="Custom input"
36
+ />,
37
+ );
38
+ expect(screen.getByPlaceholderText('Custom input')).toHaveClass('custom-input');
39
+ });
40
+
41
+ it('passes through other props', () => {
42
+ render(
43
+ <Input
44
+ data-testid="test-input"
45
+ name="test"
46
+ />,
47
+ );
48
+ expect(screen.getByTestId('test-input')).toHaveAttribute('name', 'test');
49
+ });
50
+
51
+ it('handles disabled state', () => {
52
+ render(
53
+ <Input
54
+ disabled
55
+ placeholder="Disabled input"
56
+ />,
57
+ );
58
+ expect(screen.getByPlaceholderText('Disabled input')).toBeDisabled();
59
+ });
60
+
61
+ it('handles value changes', () => {
62
+ render(<Input defaultValue="test value" />);
63
+ expect(screen.getByDisplayValue('test value')).toBeInTheDocument();
64
+ });
65
+ });