@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.
- 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 +17 -129
- 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/link/link.test.tsx +169 -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/src/utils/text/text.test.tsx +110 -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
|
@@ -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
|
|
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
|
|
141
|
+
export const Disabled: Story = {
|
|
77
142
|
args: {
|
|
78
|
-
|
|
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
|
|
156
|
+
export const AllVariants: Story = {
|
|
84
157
|
render: () => (
|
|
85
|
-
<div
|
|
86
|
-
<IconButton
|
|
87
|
-
|
|
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
|
-
|
|
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 = (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
45
|
+
<Button
|
|
46
|
+
aria-label={finalAriaLabel}
|
|
47
|
+
className={iconButton({ className, size })}
|
|
48
|
+
title={finalTitle}
|
|
49
|
+
{...props}
|
|
16
50
|
>
|
|
17
51
|
{icon}
|
|
18
|
-
</
|
|
52
|
+
</Button>
|
|
19
53
|
);
|
|
20
54
|
};
|
|
@@ -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
|
+
});
|