@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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://www.schemastore.org/package.json",
3
3
  "name": "@regardio/react",
4
- "version": "0.5.5",
4
+ "version": "0.6.0",
5
5
  "private": false,
6
6
  "description": "Regardio React UI components",
7
7
  "keywords": [
@@ -26,128 +26,16 @@
26
26
  ],
27
27
  "type": "module",
28
28
  "exports": {
29
- "./background-slideshow": {
30
- "types": "./dist/background-slideshow/index.d.ts",
31
- "import": "./dist/background-slideshow/index.js"
32
- },
33
- "./blurry-gradient": {
34
- "types": "./dist/blurry-gradient/index.d.ts",
35
- "import": "./dist/blurry-gradient/index.js"
36
- },
37
- "./carousel": {
38
- "types": "./dist/carousel/index.d.ts",
39
- "import": "./dist/carousel/index.js"
40
- },
41
- "./countdown": {
42
- "types": "./dist/countdown/index.d.ts",
43
- "import": "./dist/countdown/index.js"
44
- },
45
- "./generic-error": {
46
- "types": "./dist/generic-error/index.d.ts",
47
- "import": "./dist/generic-error/index.js"
48
- },
49
- "./grid": {
50
- "types": "./dist/grid/index.d.ts",
51
- "import": "./dist/grid/index.js"
52
- },
53
- "./heading": {
54
- "types": "./dist/heading/index.d.ts",
55
- "import": "./dist/heading/index.js"
56
- },
57
- "./highlight": {
58
- "types": "./dist/highlight/index.d.ts",
59
- "import": "./dist/highlight/index.js"
60
- },
61
- "./hooks/use-current-route-data": {
62
- "types": "./dist/hooks/use-current-route-data.d.ts",
63
- "import": "./dist/hooks/use-current-route-data.js"
64
- },
65
- "./hooks/use-focus-search": {
66
- "types": "./dist/hooks/use-focus-search.d.ts",
67
- "import": "./dist/hooks/use-focus-search.js"
68
- },
69
- "./hooks/use-matches-data": {
70
- "types": "./dist/hooks/use-matches-data.d.ts",
71
- "import": "./dist/hooks/use-matches-data.js"
72
- },
73
- "./hooks/use-media-query": {
74
- "types": "./dist/hooks/use-media-query.d.ts",
75
- "import": "./dist/hooks/use-media-query.js"
76
- },
77
- "./hooks/use-mobile": {
78
- "types": "./dist/hooks/use-mobile.d.ts",
79
- "import": "./dist/hooks/use-mobile.js"
80
- },
81
- "./hooks/use-nonce": {
82
- "types": "./dist/hooks/use-nonce.d.ts",
83
- "import": "./dist/hooks/use-nonce.js"
84
- },
85
- "./hooks/use-orientation": {
86
- "types": "./dist/hooks/use-orientation.d.ts",
87
- "import": "./dist/hooks/use-orientation.js"
88
- },
89
- "./hooks/use-user": {
90
- "types": "./dist/hooks/use-user.d.ts",
91
- "import": "./dist/hooks/use-user.js"
92
- },
93
- "./icon-button": {
94
- "types": "./dist/icon-button/index.d.ts",
95
- "import": "./dist/icon-button/index.js"
96
- },
97
- "./if": {
98
- "types": "./dist/if/index.d.ts",
99
- "import": "./dist/if/index.js"
100
- },
101
- "./iframe": {
102
- "types": "./dist/iframe/index.d.ts",
103
- "import": "./dist/iframe/index.js"
104
- },
105
- "./link": {
106
- "types": "./dist/link/index.d.ts",
107
- "import": "./dist/link/index.js"
108
- },
109
- "./list": {
110
- "types": "./dist/list/index.d.ts",
111
- "import": "./dist/list/index.js"
112
- },
113
- "./markdown-container": {
114
- "types": "./dist/markdown-container/index.d.ts",
115
- "import": "./dist/markdown-container/index.js"
116
- },
117
- "./password-input": {
118
- "types": "./dist/password-input/index.d.ts",
119
- "import": "./dist/password-input/index.js"
120
- },
121
- "./picture": {
122
- "types": "./dist/picture/index.d.ts",
123
- "import": "./dist/picture/index.js"
124
- },
125
- "./protected-email": {
126
- "types": "./dist/protected-email/index.d.ts",
127
- "import": "./dist/protected-email/index.js"
128
- },
129
- "./tailwind.css": "./src/tailwind.css",
130
- "./text": {
131
- "types": "./dist/text/index.d.ts",
132
- "import": "./dist/text/index.js"
133
- },
134
- "./utils/author": {
135
- "types": "./dist/utils/author/index.d.ts",
136
- "import": "./dist/utils/author/index.js"
137
- },
138
- "./utils/text": {
139
- "types": "./dist/utils/text/index.d.ts",
140
- "import": "./dist/utils/text/index.js"
141
- }
29
+ "./tailwind.css": "./src/tailwind.css"
142
30
  },
143
31
  "files": [
144
32
  "dist",
145
33
  "src"
146
34
  ],
147
35
  "scripts": {
148
- "build": "tsup && post-build-exports --preserve \"./tailwind.css\" && pnpm fix",
36
+ "build": "tsdown && post-build-exports --preserve \"./tailwind.css\" && pnpm fix",
149
37
  "clean": "exec-clean .turbo dist",
150
- "dev": "tsup --watch",
38
+ "dev": "tsdown --watch",
151
39
  "fix": "exec-p fix:*",
152
40
  "fix:biome": "lint-biome check --write --unsafe .",
153
41
  "fix:md": "lint-md --fix",
@@ -172,8 +60,8 @@
172
60
  "@maptiler/sdk": "3.9.0",
173
61
  "@mdx-js/react": "3.1.1",
174
62
  "@regardio/js": "0.6.0",
175
- "@regardio/tailwind": "0.1.3",
176
- "@supabase/supabase-js": "2.90.0",
63
+ "@regardio/tailwind": "0.2.0",
64
+ "@supabase/supabase-js": "2.90.1",
177
65
  "cmdk": "1.1.1",
178
66
  "embla-carousel": "8.6.0",
179
67
  "embla-carousel-react": "8.6.0",
@@ -181,19 +69,19 @@
181
69
  "intl-parse-accept-language": "1.0.0",
182
70
  "leaflet": "alpha",
183
71
  "lucide-react": "0.562.0",
184
- "markdown-to-jsx": "9.5.1",
72
+ "markdown-to-jsx": "9.5.7",
185
73
  "react": "19.2.3",
186
74
  "react-day-picker": "9.13.0",
187
75
  "react-dom": "19.2.3",
188
- "react-hook-form": "7.70.0",
189
- "react-resizable-panels": "4.3.1",
76
+ "react-hook-form": "7.71.0",
77
+ "react-resizable-panels": "4.4.0",
190
78
  "react-router": "7.12.0",
191
79
  "tailwind-variants": "3.2.2",
192
80
  "vaul": "1.1.2",
193
81
  "zod": "4.3.5"
194
82
  },
195
83
  "devDependencies": {
196
- "@regardio/dev": "1.11.1",
84
+ "@regardio/dev": "1.11.4",
197
85
  "@storybook/addon-a11y": "10.1.11",
198
86
  "@storybook/addon-docs": "10.1.11",
199
87
  "@storybook/addon-vitest": "10.1.11",
@@ -203,21 +91,21 @@
203
91
  "@testing-library/react": "16.3.1",
204
92
  "@total-typescript/ts-reset": "0.6.1",
205
93
  "@types/leaflet": "1.9.21",
206
- "@types/node": "25.0.3",
207
- "@types/react": "19.2.7",
94
+ "@types/node": "25.0.8",
95
+ "@types/react": "19.2.8",
208
96
  "@types/react-dom": "19.2.3",
209
97
  "@vitejs/plugin-react": "5.1.2",
210
- "@vitest/browser-playwright": "4.0.16",
211
- "@vitest/coverage-v8": "4.0.16",
212
- "@vitest/ui": "4.0.16",
98
+ "@vitest/browser-playwright": "4.0.17",
99
+ "@vitest/coverage-v8": "4.0.17",
100
+ "@vitest/ui": "4.0.17",
213
101
  "jsdom": "27.4.0",
214
102
  "playwright": "1.57.0",
215
103
  "storybook": "10.1.11",
216
104
  "tailwindcss": "4.1.18",
217
- "tsup": "8.5.1",
105
+ "tsdown": "0.20.0-beta.1",
218
106
  "typescript": "5.9.3",
219
107
  "vite": "7.3.1",
220
- "vitest": "4.0.16"
108
+ "vitest": "4.0.17"
221
109
  },
222
110
  "engines": {
223
111
  "node": ">=18"
@@ -0,0 +1,161 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Button } from './button';
3
+
4
+ const meta = {
5
+ argTypes: {
6
+ children: {
7
+ control: 'text',
8
+ description: 'Button content',
9
+ },
10
+ disabled: {
11
+ control: 'boolean',
12
+ description: 'Disable the button',
13
+ },
14
+ size: {
15
+ control: 'select',
16
+ description: 'Button size',
17
+ options: ['sm', 'md', 'lg', 'xl', '2xl'],
18
+ },
19
+ variant: {
20
+ control: 'select',
21
+ description: 'Button style variant',
22
+ options: ['primary', 'secondary', 'outline', 'ghost', 'destructive'],
23
+ },
24
+ },
25
+ component: Button,
26
+ parameters: {
27
+ layout: 'centered',
28
+ },
29
+ tags: ['autodocs'],
30
+ title: 'Components/Button',
31
+ } satisfies Meta<typeof Button>;
32
+
33
+ export default meta;
34
+ type Story = StoryObj<typeof meta>;
35
+
36
+ export const Default: Story = {
37
+ args: {
38
+ children: 'Button',
39
+ },
40
+ };
41
+
42
+ export const Primary: Story = {
43
+ args: {
44
+ children: 'Primary Button',
45
+ variant: 'primary',
46
+ },
47
+ };
48
+
49
+ export const Secondary: Story = {
50
+ args: {
51
+ children: 'Secondary Button',
52
+ variant: 'secondary',
53
+ },
54
+ };
55
+
56
+ export const Outline: Story = {
57
+ args: {
58
+ children: 'Outline Button',
59
+ variant: 'outline',
60
+ },
61
+ };
62
+
63
+ export const Ghost: Story = {
64
+ args: {
65
+ children: 'Ghost Button',
66
+ variant: 'ghost',
67
+ },
68
+ };
69
+
70
+ export const Destructive: Story = {
71
+ args: {
72
+ children: 'Destructive Button',
73
+ variant: 'destructive',
74
+ },
75
+ };
76
+
77
+ export const Small: Story = {
78
+ args: {
79
+ children: 'Small Button',
80
+ size: 'sm',
81
+ },
82
+ };
83
+
84
+ export const Large: Story = {
85
+ args: {
86
+ children: 'Large Button',
87
+ size: 'lg',
88
+ },
89
+ };
90
+
91
+ export const ExtraLarge: Story = {
92
+ args: {
93
+ children: 'Extra Large Button',
94
+ size: 'xl',
95
+ },
96
+ };
97
+
98
+ export const DoubleExtraLarge: Story = {
99
+ args: {
100
+ children: '2XL Button',
101
+ size: '2xl',
102
+ },
103
+ };
104
+
105
+ export const Disabled: Story = {
106
+ args: {
107
+ children: 'Disabled Button',
108
+ disabled: true,
109
+ },
110
+ };
111
+
112
+ export const WithCustomClass: Story = {
113
+ args: {
114
+ children: 'Button with shadow',
115
+ className: 'shadow-lg',
116
+ },
117
+ };
118
+
119
+ export const AsLink: Story = {
120
+ args: {
121
+ children: 'Link Button',
122
+ nativeButton: false,
123
+ render: (props: React.ComponentProps<'a'>) => (
124
+ <a
125
+ {...props}
126
+ href="#example"
127
+ />
128
+ ),
129
+ },
130
+ };
131
+
132
+ export const AllVariants: Story = {
133
+ render: () => (
134
+ <div className="flex flex-wrap gap-4">
135
+ <Button variant="primary">Primary</Button>
136
+ <Button variant="secondary">Secondary</Button>
137
+ <Button variant="outline">Outline</Button>
138
+ <Button variant="ghost">Ghost</Button>
139
+ <Button variant="destructive">Destructive</Button>
140
+ </div>
141
+ ),
142
+ };
143
+
144
+ export const AllSizes: Story = {
145
+ render: () => (
146
+ <div className="flex flex-wrap items-center gap-4">
147
+ <Button size="sm">Small</Button>
148
+ <Button size="md">Medium</Button>
149
+ <Button size="lg">Large</Button>
150
+ <Button size="xl">Extra Large</Button>
151
+ <Button size="2xl">2XL</Button>
152
+ </div>
153
+ ),
154
+ };
155
+
156
+ export const Interactive: Story = {
157
+ args: {
158
+ children: 'Click me!',
159
+ onClick: () => alert('Button clicked!'),
160
+ },
161
+ };
@@ -0,0 +1,73 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { Button } from './button';
4
+
5
+ describe('Button', () => {
6
+ it('renders children correctly', () => {
7
+ render(<Button>Click me</Button>);
8
+ expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
9
+ });
10
+
11
+ it('applies default variant and size classes', () => {
12
+ render(<Button>Default button</Button>);
13
+ const button = screen.getByRole('button', { name: 'Default button' });
14
+ expect(button).toHaveClass('bg-blue-600', 'text-white', 'px-4', 'py-2');
15
+ });
16
+
17
+ it('applies variant classes correctly', () => {
18
+ render(<Button variant="secondary">Secondary button</Button>);
19
+ const button = screen.getByRole('button', { name: 'Secondary button' });
20
+ expect(button).toHaveClass('bg-gray-100', 'text-gray-900');
21
+ });
22
+
23
+ it('applies size classes correctly', () => {
24
+ render(<Button size="lg">Large button</Button>);
25
+ const button = screen.getByRole('button', { name: 'Large button' });
26
+ expect(button).toHaveClass('px-6', 'py-3', 'text-lg');
27
+ });
28
+
29
+ it('applies custom className', () => {
30
+ render(<Button className="custom-class">Custom button</Button>);
31
+ const button = screen.getByRole('button', { name: 'Custom button' });
32
+ expect(button).toHaveClass('custom-class');
33
+ });
34
+
35
+ it('handles disabled state', () => {
36
+ render(<Button disabled>Disabled button</Button>);
37
+ const button = screen.getByRole('button', { name: 'Disabled button' });
38
+ expect(button).toBeDisabled();
39
+ expect(button).toHaveClass('disabled:opacity-50', 'disabled:cursor-not-allowed');
40
+ });
41
+
42
+ it('passes through other props', () => {
43
+ render(
44
+ <Button
45
+ aria-label="Test button"
46
+ data-testid="test-button"
47
+ >
48
+ Test
49
+ </Button>,
50
+ );
51
+ const button = screen.getByTestId('test-button');
52
+ expect(button).toHaveAttribute('aria-label', 'Test button');
53
+ });
54
+
55
+ it('renders as different element when render prop is provided', () => {
56
+ render(
57
+ <Button
58
+ nativeButton={false}
59
+ render={(props) => (
60
+ <a
61
+ {...props}
62
+ href="#test"
63
+ />
64
+ )}
65
+ >
66
+ Link button
67
+ </Button>,
68
+ );
69
+ const link = screen.getByRole('button', { name: 'Link button' });
70
+ expect(link).toHaveAttribute('href', '#test');
71
+ expect(link.tagName).toBe('A');
72
+ });
73
+ });
@@ -0,0 +1,112 @@
1
+ import { Button as BaseUIButton } from '@base-ui/react/button';
2
+ import { tv } from '@regardio/tailwind/utils';
3
+ import type { ComponentProps } from 'react';
4
+
5
+ const buttonVariants = {
6
+ destructive: [
7
+ 'bg-red-600',
8
+ 'text-white',
9
+ 'border-red-600',
10
+ 'hover:bg-red-700',
11
+ 'hover:border-red-700',
12
+ 'focus-visible:ring-2',
13
+ 'focus-visible:ring-red-500',
14
+ 'focus-visible:ring-offset-2',
15
+ ],
16
+ ghost: [
17
+ 'bg-transparent',
18
+ 'text-gray-900',
19
+ 'border-transparent',
20
+ 'hover:bg-gray-100',
21
+ 'hover:border-transparent',
22
+ 'focus-visible:ring-2',
23
+ 'focus-visible:ring-gray-500',
24
+ 'focus-visible:ring-offset-2',
25
+ ],
26
+ outline: [
27
+ 'bg-transparent',
28
+ 'text-gray-900',
29
+ 'border-gray-300',
30
+ 'hover:bg-gray-50',
31
+ 'hover:border-gray-400',
32
+ 'focus-visible:ring-2',
33
+ 'focus-visible:ring-gray-500',
34
+ 'focus-visible:ring-offset-2',
35
+ ],
36
+ primary: [
37
+ 'bg-blue-600',
38
+ 'text-white',
39
+ 'border-blue-600',
40
+ 'hover:bg-blue-700',
41
+ 'hover:border-blue-700',
42
+ 'focus-visible:ring-2',
43
+ 'focus-visible:ring-blue-500',
44
+ 'focus-visible:ring-offset-2',
45
+ ],
46
+ secondary: [
47
+ 'bg-gray-100',
48
+ 'text-gray-900',
49
+ 'border-gray-300',
50
+ 'hover:bg-gray-200',
51
+ 'hover:border-gray-400',
52
+ 'focus-visible:ring-2',
53
+ 'focus-visible:ring-gray-500',
54
+ 'focus-visible:ring-offset-2',
55
+ ],
56
+ } as const;
57
+
58
+ const buttonSizes = {
59
+ '2xl': ['px-10', 'py-5', 'text-2xl', 'font-medium', 'rounded-lg'],
60
+ lg: ['px-6', 'py-3', 'text-lg', 'font-medium', 'rounded-lg'],
61
+ md: ['px-4', 'py-2', 'text-base', 'font-medium', 'rounded-md'],
62
+ sm: ['px-3', 'py-1.5', 'text-sm', 'font-medium', 'rounded-md'],
63
+ xl: ['px-8', 'py-4', 'text-xl', 'font-medium', 'rounded-lg'],
64
+ } as const;
65
+
66
+ const button = tv({
67
+ base: [
68
+ 'inline-flex',
69
+ 'items-center',
70
+ 'justify-center',
71
+ 'border',
72
+ 'transition-colors',
73
+ 'duration-200',
74
+ 'ease-in-out',
75
+ 'disabled:opacity-50',
76
+ 'disabled:cursor-not-allowed',
77
+ 'disabled:pointer-events-none',
78
+ ],
79
+ defaultVariants: {
80
+ size: 'md',
81
+ variant: 'primary',
82
+ },
83
+ variants: {
84
+ size: buttonSizes,
85
+ variant: buttonVariants,
86
+ },
87
+ });
88
+
89
+ export type ButtonVariant = keyof typeof buttonVariants;
90
+ export type ButtonSize = keyof typeof buttonSizes;
91
+
92
+ export interface ButtonProps extends Omit<ComponentProps<typeof BaseUIButton>, 'className'> {
93
+ variant?: ButtonVariant;
94
+ size?: ButtonSize;
95
+ className?: string;
96
+ type?: 'button' | 'submit' | 'reset';
97
+ }
98
+
99
+ export const Button = ({ children, className, variant, size, ...props }: ButtonProps) => {
100
+ return (
101
+ <BaseUIButton
102
+ className={button({
103
+ className,
104
+ size,
105
+ variant,
106
+ })}
107
+ {...props}
108
+ >
109
+ {children}
110
+ </BaseUIButton>
111
+ );
112
+ };
@@ -0,0 +1,2 @@
1
+ export type { ButtonProps, ButtonSize, ButtonVariant } from './button';
2
+ export { Button } from './button';
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { forwardRef, type HTMLAttributes } from 'react';
4
+ import { Button } from '../button';
4
5
  import { useCarousel } from './carousel-root';
5
6
 
6
7
  export const CarouselNext = forwardRef<HTMLButtonElement, HTMLAttributes<HTMLButtonElement>>(
@@ -8,12 +9,11 @@ export const CarouselNext = forwardRef<HTMLButtonElement, HTMLAttributes<HTMLBut
8
9
  const { canScrollNext, scrollNext } = useCarousel();
9
10
 
10
11
  return (
11
- <button
12
+ <Button
12
13
  className={className}
13
14
  disabled={!canScrollNext}
14
15
  onClick={scrollNext}
15
16
  ref={ref}
16
- type="button"
17
17
  {...props}
18
18
  />
19
19
  );
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { forwardRef, type HTMLAttributes } from 'react';
4
+ import { Button } from '../button';
4
5
  import { useCarousel } from './carousel-root';
5
6
 
6
7
  export const CarouselPrevious = forwardRef<HTMLButtonElement, HTMLAttributes<HTMLButtonElement>>(
@@ -8,12 +9,11 @@ export const CarouselPrevious = forwardRef<HTMLButtonElement, HTMLAttributes<HTM
8
9
  const { canScrollPrev, scrollPrev } = useCarousel();
9
10
 
10
11
  return (
11
- <button
12
+ <Button
12
13
  className={className}
13
14
  disabled={!canScrollPrev}
14
15
  onClick={scrollPrev}
15
16
  ref={ref}
16
- type="button"
17
17
  {...props}
18
18
  />
19
19
  );