@regardio/react 0.5.7 → 0.7.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 (214) hide show
  1. package/dist/background-slideshow/index.d.mts +34 -0
  2. package/dist/background-slideshow/index.mjs +110 -0
  3. package/dist/blurry-gradient/index.d.mts +16 -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 +74 -0
  9. package/dist/carousel/index.mjs +136 -0
  10. package/dist/checkbox/index.d.mts +28 -0
  11. package/dist/checkbox/index.mjs +70 -0
  12. package/dist/checkbox-group/index.d.mts +16 -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 +4 -0
  16. package/dist/countdown/index.mjs +58 -0
  17. package/dist/field/index.d.mts +68 -0
  18. package/dist/field/index.mjs +115 -0
  19. package/dist/fieldset/index.d.mts +34 -0
  20. package/dist/fieldset/index.mjs +61 -0
  21. package/dist/form/index.d.mts +21 -0
  22. package/dist/form/index.mjs +31 -0
  23. package/dist/generic-error/index.d.mts +44 -0
  24. package/dist/generic-error/index.mjs +57 -0
  25. package/dist/grid/index.d.mts +101 -0
  26. package/dist/grid/index.mjs +219 -0
  27. package/dist/heading/index.d.mts +27 -0
  28. package/dist/heading/index.mjs +28 -0
  29. package/dist/highlight/index.d.mts +17 -0
  30. package/dist/highlight/index.mjs +35 -0
  31. package/dist/hooks/use-current-route-data.d.mts +8 -0
  32. package/dist/hooks/use-current-route-data.mjs +22 -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 +23 -0
  35. package/dist/hooks/use-matches-data.d.mts +10 -0
  36. package/dist/hooks/use-matches-data.mjs +23 -0
  37. package/dist/hooks/use-media-query.d.mts +9 -0
  38. package/dist/hooks/use-media-query.mjs +28 -0
  39. package/dist/hooks/use-mobile.d.mts +4 -0
  40. package/dist/hooks/use-mobile.mjs +22 -0
  41. package/dist/hooks/use-nonce.d.mts +6 -0
  42. package/dist/hooks/use-nonce.mjs +13 -0
  43. package/dist/hooks/use-orientation.d.mts +12 -0
  44. package/dist/hooks/use-orientation.mjs +32 -0
  45. package/dist/hooks/use-user.d.mts +53 -0
  46. package/dist/hooks/use-user.mjs +39 -0
  47. package/dist/icon-button/index.d.mts +28 -0
  48. package/dist/icon-button/index.mjs +36 -0
  49. package/dist/if/index.d.mts +13 -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-Bj5_XfEC.d.mts +29 -0
  54. package/dist/index-C_evL5vG.d.mts +35 -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 +71 -0
  59. package/dist/link/index.mjs +129 -0
  60. package/dist/list/index.d.mts +72 -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 +23 -0
  65. package/dist/password-input/index.mjs +92 -0
  66. package/dist/picture/index.d.mts +39 -0
  67. package/dist/picture/index.mjs +3 -0
  68. package/dist/picture-DkX3W5zl.mjs +69 -0
  69. package/dist/protected-email/index.d.mts +24 -0
  70. package/dist/protected-email/index.mjs +37 -0
  71. package/dist/radio/index.d.mts +28 -0
  72. package/dist/radio/index.mjs +72 -0
  73. package/dist/radio-group/index.d.mts +16 -0
  74. package/dist/radio-group/index.mjs +29 -0
  75. package/dist/slider/index.d.mts +63 -0
  76. package/dist/slider/index.mjs +133 -0
  77. package/dist/switch/index.d.mts +29 -0
  78. package/dist/switch/index.mjs +87 -0
  79. package/dist/text/index.d.mts +25 -0
  80. package/dist/text/index.mjs +32 -0
  81. package/dist/text-EQC4zJbE.mjs +52 -0
  82. package/dist/toggle/index.d.mts +25 -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} +9 -8
  87. package/dist/utils/text/index.mjs +3 -0
  88. package/package.json +131 -83
  89. package/src/background-slideshow/background-slideshow.tsx +1 -2
  90. package/src/blurry-gradient/blurry-gradient.tsx +1 -1
  91. package/src/button/button.stories.tsx +161 -0
  92. package/src/button/button.test.tsx +73 -0
  93. package/src/button/button.tsx +118 -0
  94. package/src/button/index.ts +2 -0
  95. package/src/carousel/carousel-content.tsx +17 -14
  96. package/src/carousel/carousel-item.tsx +18 -18
  97. package/src/carousel/carousel-next.tsx +22 -17
  98. package/src/carousel/carousel-previous.tsx +22 -17
  99. package/src/carousel/carousel-root.tsx +91 -86
  100. package/src/checkbox/checkbox.stories.tsx +118 -0
  101. package/src/checkbox/checkbox.tsx +102 -0
  102. package/src/checkbox/index.ts +2 -0
  103. package/src/checkbox-group/checkbox-group.tsx +40 -0
  104. package/src/checkbox-group/index.ts +2 -0
  105. package/src/countdown/countdown.tsx +1 -1
  106. package/src/field/field.stories.tsx +105 -0
  107. package/src/field/field.test.tsx +61 -0
  108. package/src/field/field.tsx +186 -0
  109. package/src/field/index.ts +12 -0
  110. package/src/fieldset/fieldset.stories.tsx +204 -0
  111. package/src/fieldset/fieldset.test.tsx +63 -0
  112. package/src/fieldset/fieldset.tsx +86 -0
  113. package/src/fieldset/index.ts +7 -0
  114. package/src/form/form.stories.tsx +230 -0
  115. package/src/form/form.test.tsx +68 -0
  116. package/src/form/form.tsx +38 -0
  117. package/src/form/index.ts +2 -0
  118. package/src/generic-error/generic-error.tsx +2 -3
  119. package/src/grid/grid-item.tsx +77 -36
  120. package/src/grid/grid-root.tsx +49 -22
  121. package/src/heading/heading.tsx +7 -3
  122. package/src/highlight/highlight.tsx +1 -1
  123. package/src/hooks/use-current-route-data.ts +4 -2
  124. package/src/hooks/use-focus-search.ts +3 -1
  125. package/src/hooks/use-matches-data.ts +2 -0
  126. package/src/hooks/use-media-query.ts +2 -0
  127. package/src/hooks/use-mobile.ts +3 -1
  128. package/src/hooks/use-nonce.ts +3 -3
  129. package/src/hooks/use-orientation.ts +2 -0
  130. package/src/hooks/use-user.tsx +3 -2
  131. package/src/icon-button/icon-button.stories.tsx +128 -7
  132. package/src/icon-button/icon-button.test.tsx +152 -0
  133. package/src/icon-button/icon-button.tsx +43 -9
  134. package/src/if/if.tsx +3 -1
  135. package/src/input/index.ts +2 -0
  136. package/src/input/input.stories.tsx +151 -0
  137. package/src/input/input.test.tsx +65 -0
  138. package/src/input/input.tsx +113 -0
  139. package/src/link/link.tsx +4 -3
  140. package/src/list/list-item.tsx +10 -13
  141. package/src/list/list-root-context.ts +3 -3
  142. package/src/list/list-root.tsx +10 -13
  143. package/src/password-input/index.ts +1 -1
  144. package/src/password-input/password-input.tsx +104 -27
  145. package/src/protected-email/protected-email.tsx +6 -1
  146. package/src/radio/index.ts +2 -0
  147. package/src/radio/radio.tsx +103 -0
  148. package/src/radio-group/index.ts +2 -0
  149. package/src/radio-group/radio-group.tsx +40 -0
  150. package/src/slider/index.ts +18 -0
  151. package/src/slider/slider.tsx +201 -0
  152. package/src/switch/index.ts +2 -0
  153. package/src/switch/switch.stories.tsx +118 -0
  154. package/src/switch/switch.tsx +112 -0
  155. package/src/text/text.tsx +6 -1
  156. package/src/toggle/index.ts +2 -0
  157. package/src/toggle/toggle.stories.tsx +232 -0
  158. package/src/toggle/toggle.test.tsx +149 -0
  159. package/src/toggle/toggle.tsx +88 -0
  160. package/src/utils/text/text.tsx +8 -16
  161. package/dist/background-slideshow/index.d.ts +0 -24
  162. package/dist/background-slideshow/index.js +0 -165
  163. package/dist/blurry-gradient/index.d.ts +0 -16
  164. package/dist/blurry-gradient/index.js +0 -128
  165. package/dist/carousel/index.d.ts +0 -36
  166. package/dist/carousel/index.js +0 -171
  167. package/dist/countdown/index.d.ts +0 -5
  168. package/dist/countdown/index.js +0 -73
  169. package/dist/generic-error/index.d.ts +0 -43
  170. package/dist/generic-error/index.js +0 -47
  171. package/dist/grid/index.d.ts +0 -1196
  172. package/dist/grid/index.js +0 -239
  173. package/dist/heading/index.d.ts +0 -24
  174. package/dist/heading/index.js +0 -99
  175. package/dist/highlight/index.d.ts +0 -13
  176. package/dist/highlight/index.js +0 -59
  177. package/dist/hooks/use-current-route-data.d.ts +0 -7
  178. package/dist/hooks/use-current-route-data.js +0 -16
  179. package/dist/hooks/use-focus-search.js +0 -19
  180. package/dist/hooks/use-matches-data.d.ts +0 -9
  181. package/dist/hooks/use-matches-data.js +0 -15
  182. package/dist/hooks/use-media-query.d.ts +0 -8
  183. package/dist/hooks/use-media-query.js +0 -20
  184. package/dist/hooks/use-mobile.d.ts +0 -3
  185. package/dist/hooks/use-mobile.js +0 -19
  186. package/dist/hooks/use-nonce.d.ts +0 -7
  187. package/dist/hooks/use-nonce.js +0 -8
  188. package/dist/hooks/use-orientation.d.ts +0 -11
  189. package/dist/hooks/use-orientation.js +0 -29
  190. package/dist/hooks/use-user.d.ts +0 -50
  191. package/dist/hooks/use-user.js +0 -25
  192. package/dist/icon-button/index.d.ts +0 -9
  193. package/dist/icon-button/index.js +0 -17
  194. package/dist/if/index.d.ts +0 -10
  195. package/dist/if/index.js +0 -24
  196. package/dist/iframe/index.d.ts +0 -10
  197. package/dist/iframe/index.js +0 -17
  198. package/dist/link/index.d.ts +0 -55
  199. package/dist/link/index.js +0 -195
  200. package/dist/list/index.d.ts +0 -69
  201. package/dist/list/index.js +0 -65
  202. package/dist/markdown-container/index.d.ts +0 -22
  203. package/dist/markdown-container/index.js +0 -128
  204. package/dist/password-input/index.d.ts +0 -11
  205. package/dist/password-input/index.js +0 -46
  206. package/dist/picture/index.d.ts +0 -38
  207. package/dist/picture/index.js +0 -68
  208. package/dist/protected-email/index.d.ts +0 -20
  209. package/dist/protected-email/index.js +0 -30
  210. package/dist/text/index.d.ts +0 -20
  211. package/dist/text/index.js +0 -38
  212. package/dist/utils/author/index.d.ts +0 -3
  213. package/dist/utils/author/index.js +0 -33
  214. 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 { Switch } from './switch';
5
+
6
+ const meta: Meta<typeof Switch.Root> = {
7
+ argTypes: {
8
+ disabled: {
9
+ control: 'boolean',
10
+ description: 'Disable the switch',
11
+ },
12
+ size: {
13
+ control: 'select',
14
+ description: 'Switch size',
15
+ options: ['sm', 'md', 'lg'],
16
+ },
17
+ },
18
+ component: Switch.Root,
19
+ parameters: {
20
+ layout: 'centered',
21
+ },
22
+ tags: ['autodocs'],
23
+ title: 'Components/Switch',
24
+ };
25
+
26
+ export default meta;
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const Default: Story = {
30
+ render: () => (
31
+ <Switch.Root>
32
+ <Switch.Thumb />
33
+ </Switch.Root>
34
+ ),
35
+ };
36
+
37
+ export const Checked: Story = {
38
+ render: () => (
39
+ <Switch.Root defaultChecked>
40
+ <Switch.Thumb />
41
+ </Switch.Root>
42
+ ),
43
+ };
44
+
45
+ export const Small: Story = {
46
+ render: () => (
47
+ <Switch.Root size="sm">
48
+ <Switch.Thumb size="sm" />
49
+ </Switch.Root>
50
+ ),
51
+ };
52
+
53
+ export const Large: Story = {
54
+ render: () => (
55
+ <Switch.Root size="lg">
56
+ <Switch.Thumb size="lg" />
57
+ </Switch.Root>
58
+ ),
59
+ };
60
+
61
+ export const Disabled: Story = {
62
+ render: () => (
63
+ <Switch.Root disabled>
64
+ <Switch.Thumb />
65
+ </Switch.Root>
66
+ ),
67
+ };
68
+
69
+ export const WithLabel: Story = {
70
+ render: () => (
71
+ <Field.Root>
72
+ <Field.Label>
73
+ <Switch.Root>
74
+ <Switch.Thumb />
75
+ </Switch.Root>
76
+ Enable notifications
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
+ <Switch.Root
90
+ checked={checked}
91
+ onCheckedChange={setChecked}
92
+ >
93
+ <Switch.Thumb />
94
+ </Switch.Root>
95
+ Dark mode
96
+ </Field.Label>
97
+ </Field.Root>
98
+ <p className="text-sm text-gray-600">Mode: {checked ? 'Dark' : 'Light'}</p>
99
+ </div>
100
+ );
101
+ },
102
+ };
103
+
104
+ export const AllSizes: Story = {
105
+ render: () => (
106
+ <div className="flex items-center gap-4">
107
+ <Switch.Root size="sm">
108
+ <Switch.Thumb size="sm" />
109
+ </Switch.Root>
110
+ <Switch.Root size="md">
111
+ <Switch.Thumb size="md" />
112
+ </Switch.Root>
113
+ <Switch.Root size="lg">
114
+ <Switch.Thumb size="lg" />
115
+ </Switch.Root>
116
+ </div>
117
+ ),
118
+ };
@@ -0,0 +1,112 @@
1
+ import { Switch as BaseUISwitch } from '@base-ui/react/switch';
2
+ import { tv } from '@regardio/tailwind/utils';
3
+ import type { ComponentProps } from 'react';
4
+
5
+ const switchRoot = tv({
6
+ base: [
7
+ 'relative',
8
+ 'inline-flex',
9
+ 'items-center',
10
+ 'rounded-full',
11
+ 'transition-colors',
12
+ 'duration-200',
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
+ 'cursor-pointer',
20
+ 'bg-gray-300',
21
+ 'data-[checked]:bg-blue-600',
22
+ ],
23
+ defaultVariants: {
24
+ size: 'md',
25
+ },
26
+ variants: {
27
+ size: {
28
+ lg: ['h-8', 'w-14'],
29
+ md: ['h-6', 'w-11'],
30
+ sm: ['h-4', 'w-7'],
31
+ },
32
+ },
33
+ });
34
+
35
+ const switchThumb = tv({
36
+ base: [
37
+ 'pointer-events-none',
38
+ 'inline-block',
39
+ 'rounded-full',
40
+ 'bg-white',
41
+ 'shadow-lg',
42
+ 'ring-0',
43
+ 'transition-transform',
44
+ 'duration-200',
45
+ 'translate-x-0',
46
+ 'data-[checked]:translate-x-full',
47
+ ],
48
+ defaultVariants: {
49
+ size: 'md',
50
+ },
51
+ variants: {
52
+ size: {
53
+ lg: ['h-6', 'w-6', 'data-[checked]:translate-x-6'],
54
+ md: ['h-5', 'w-5', 'data-[checked]:translate-x-5'],
55
+ sm: ['h-3', 'w-3', 'data-[checked]:translate-x-3'],
56
+ },
57
+ },
58
+ });
59
+
60
+ export type SwitchSize = 'sm' | 'md' | 'lg';
61
+
62
+ export interface SwitchRootProps
63
+ extends Omit<ComponentProps<typeof BaseUISwitch.Root>, 'className'> {
64
+ className?: string;
65
+ size?: SwitchSize;
66
+ }
67
+
68
+ export interface SwitchThumbProps
69
+ extends Omit<ComponentProps<typeof BaseUISwitch.Thumb>, 'className'> {
70
+ className?: string;
71
+ size?: SwitchSize;
72
+ }
73
+
74
+ export const SwitchRoot = ({
75
+ className,
76
+ size = 'md',
77
+ ...props
78
+ }: SwitchRootProps): React.JSX.Element => {
79
+ return (
80
+ <BaseUISwitch.Root
81
+ className={switchRoot({
82
+ className,
83
+ size,
84
+ })}
85
+ {...props}
86
+ />
87
+ );
88
+ };
89
+
90
+ export const SwitchThumb = ({
91
+ className,
92
+ size = 'md',
93
+ ...props
94
+ }: SwitchThumbProps): React.JSX.Element => {
95
+ return (
96
+ <BaseUISwitch.Thumb
97
+ className={switchThumb({
98
+ className,
99
+ size,
100
+ })}
101
+ {...props}
102
+ />
103
+ );
104
+ };
105
+
106
+ export const Switch: {
107
+ Root: typeof SwitchRoot;
108
+ Thumb: typeof SwitchThumb;
109
+ } = {
110
+ Root: SwitchRoot,
111
+ Thumb: SwitchThumb,
112
+ };
package/src/text/text.tsx CHANGED
@@ -31,7 +31,12 @@ export interface TextProps extends ComponentProps<'p'> {
31
31
  variant?: TextVariant;
32
32
  }
33
33
 
34
- export const Text = ({ children, className, variant, themeColor }: TextProps) => {
34
+ export const Text = ({
35
+ children,
36
+ className,
37
+ variant,
38
+ themeColor,
39
+ }: TextProps): React.JSX.Element => {
35
40
  return (
36
41
  <div
37
42
  className={text({
@@ -0,0 +1,2 @@
1
+ export type { ToggleProps, ToggleSize, ToggleVariant } from './toggle';
2
+ export { Toggle } from './toggle';
@@ -0,0 +1,232 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import type React from 'react';
3
+ import { useState } from 'react';
4
+ import { Button } from '../button';
5
+ import { Toggle } from './toggle';
6
+
7
+ const meta: Meta<typeof Toggle> = {
8
+ argTypes: {
9
+ defaultPressed: {
10
+ control: 'boolean',
11
+ description: 'Initial pressed state (uncontrolled)',
12
+ },
13
+ disabled: {
14
+ control: 'boolean',
15
+ description: 'Disable the toggle',
16
+ },
17
+ pressed: {
18
+ control: 'boolean',
19
+ description: 'Pressed state (controlled)',
20
+ },
21
+ size: {
22
+ control: 'select',
23
+ description: 'Toggle size',
24
+ options: ['sm', 'md', 'lg'],
25
+ },
26
+ variant: {
27
+ control: 'select',
28
+ description: 'Toggle variant',
29
+ options: ['default', 'outline', 'ghost'],
30
+ },
31
+ },
32
+ component: Toggle,
33
+ parameters: {
34
+ layout: 'centered',
35
+ },
36
+ tags: ['autodocs'],
37
+ title: 'Components/Toggle',
38
+ };
39
+
40
+ export default meta;
41
+ type Story = StoryObj<typeof meta>;
42
+
43
+ const HeartIcon = ({ filled = false }) => (
44
+ <svg
45
+ fill={filled ? 'currentColor' : 'none'}
46
+ height="16"
47
+ stroke="currentColor"
48
+ strokeWidth="2"
49
+ viewBox="0 0 16 16"
50
+ width="16"
51
+ >
52
+ <path d="M7.99961 13.8667C7.88761 13.8667 7.77561 13.8315 7.68121 13.7611C7.43321 13.5766 1.59961 9.1963 1.59961 5.8667C1.59961 3.80856 3.27481 2.13336 5.33294 2.13336C6.59054 2.13336 7.49934 2.81176 7.99961 3.3131C8.49988 2.81176 9.40868 2.13336 10.6663 2.13336C12.7244 2.13336 14.3996 3.80803 14.3996 5.8667C14.3996 9.1963 8.56601 13.5766 8.31801 13.7616C8.22361 13.8315 8.11161 13.8667 7.99961 13.8667Z" />
53
+ </svg>
54
+ );
55
+
56
+ const StarIcon = ({ filled = false }) => (
57
+ <svg
58
+ fill={filled ? 'currentColor' : 'none'}
59
+ height="16"
60
+ stroke="currentColor"
61
+ strokeWidth="2"
62
+ viewBox="0 0 16 16"
63
+ width="16"
64
+ >
65
+ <path d="m8 1.62 1.94 3.93 4.34.63-3.14 3.06.74 4.32L8 12.22l-3.88 2.34.74-4.32-3.14-3.06 4.34-.63L8 1.62Z" />
66
+ </svg>
67
+ );
68
+
69
+ const SettingsIcon = () => (
70
+ <svg
71
+ fill="none"
72
+ height="16"
73
+ stroke="currentColor"
74
+ strokeWidth="2"
75
+ viewBox="0 0 16 16"
76
+ width="16"
77
+ >
78
+ <circle
79
+ cx="8"
80
+ cy="8"
81
+ r="2"
82
+ />
83
+ <path d="m8 1v3m0 6v3m2.83-9.83 2.12 2.12M1.17 5.17l2.12 2.12m9.42 0 2.12 2.12M1.17 10.83l2.12-2.12" />
84
+ </svg>
85
+ );
86
+
87
+ export const Default: Story = {
88
+ args: {
89
+ children: <HeartIcon />,
90
+ title: 'Favorite',
91
+ },
92
+ };
93
+
94
+ export const Pressed: Story = {
95
+ args: {
96
+ children: <HeartIcon filled />,
97
+ defaultPressed: true,
98
+ title: 'Favorite',
99
+ },
100
+ };
101
+
102
+ export const Controlled: Story = {
103
+ render: () => {
104
+ const [pressed, setPressed] = useState(false);
105
+ return (
106
+ <Toggle
107
+ onPressedChange={setPressed}
108
+ pressed={pressed}
109
+ title="Favorite"
110
+ >
111
+ <HeartIcon filled={pressed} />
112
+ </Toggle>
113
+ );
114
+ },
115
+ };
116
+
117
+ export const Ghost: Story = {
118
+ args: {
119
+ children: <StarIcon />,
120
+ title: 'Star',
121
+ variant: 'ghost',
122
+ },
123
+ };
124
+
125
+ export const Small: Story = {
126
+ args: {
127
+ children: <SettingsIcon />,
128
+ size: 'sm',
129
+ title: 'Settings',
130
+ },
131
+ };
132
+
133
+ export const Large: Story = {
134
+ args: {
135
+ children: <SettingsIcon />,
136
+ size: 'lg',
137
+ title: 'Settings',
138
+ },
139
+ };
140
+
141
+ export const Disabled: Story = {
142
+ args: {
143
+ children: <HeartIcon />,
144
+ disabled: true,
145
+ title: 'Favorite',
146
+ },
147
+ };
148
+
149
+ export const AllVariants: Story = {
150
+ render: () => (
151
+ <div className="flex gap-4">
152
+ <Toggle title="Default">
153
+ <HeartIcon />
154
+ </Toggle>
155
+ <Toggle
156
+ title="Outline"
157
+ variant="outline"
158
+ >
159
+ <StarIcon />
160
+ </Toggle>
161
+ <Toggle
162
+ title="Ghost"
163
+ variant="ghost"
164
+ >
165
+ <SettingsIcon />
166
+ </Toggle>
167
+ </div>
168
+ ),
169
+ };
170
+
171
+ export const AllSizes: Story = {
172
+ render: () => (
173
+ <div className="flex items-center gap-4">
174
+ <Toggle
175
+ size="sm"
176
+ title="Small"
177
+ >
178
+ <HeartIcon />
179
+ </Toggle>
180
+ <Toggle
181
+ size="md"
182
+ title="Medium"
183
+ >
184
+ <HeartIcon />
185
+ </Toggle>
186
+ <Toggle
187
+ size="lg"
188
+ title="Large"
189
+ >
190
+ <HeartIcon />
191
+ </Toggle>
192
+ </div>
193
+ ),
194
+ };
195
+
196
+ export const Interactive: Story = {
197
+ render: () => {
198
+ const [pressed, setPressed] = useState(false);
199
+ return (
200
+ <div className="space-y-4">
201
+ <Toggle
202
+ onPressedChange={setPressed}
203
+ pressed={pressed}
204
+ title="Toggle favorite"
205
+ >
206
+ <HeartIcon filled={pressed} />
207
+ </Toggle>
208
+ <p className="text-sm text-gray-600">State: {pressed ? 'pressed' : 'not pressed'}</p>
209
+ </div>
210
+ );
211
+ },
212
+ };
213
+
214
+ export const CustomRender: Story = {
215
+ args: {
216
+ render: (props: React.ComponentProps<'button'>) => (
217
+ <Button
218
+ {...props}
219
+ className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-blue-500 text-white hover:bg-blue-600 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
220
+ >
221
+ <svg
222
+ fill="currentColor"
223
+ height="16"
224
+ viewBox="0 0 16 16"
225
+ width="16"
226
+ >
227
+ <path d="m6.5 3.5 3 3-3 3" />
228
+ </svg>
229
+ </Button>
230
+ ),
231
+ },
232
+ };
@@ -0,0 +1,149 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { Toggle } from './toggle';
4
+
5
+ const TestIcon = () => (
6
+ <svg
7
+ data-testid="test-icon"
8
+ height="16"
9
+ viewBox="0 0 16 16"
10
+ width="16"
11
+ >
12
+ <circle
13
+ cx="8"
14
+ cy="8"
15
+ r="6"
16
+ />
17
+ </svg>
18
+ );
19
+
20
+ describe('Toggle', () => {
21
+ it('renders with children', () => {
22
+ render(
23
+ <Toggle>
24
+ <TestIcon />
25
+ </Toggle>,
26
+ );
27
+ expect(screen.getByTestId('test-icon')).toBeInTheDocument();
28
+ });
29
+
30
+ it('applies variant styles', () => {
31
+ render(
32
+ <Toggle
33
+ data-testid="variant-toggle"
34
+ variant="ghost"
35
+ >
36
+ <TestIcon />
37
+ </Toggle>,
38
+ );
39
+ const toggle = screen.getByTestId('variant-toggle');
40
+ expect(toggle).toHaveClass('bg-transparent', 'text-gray-700');
41
+ });
42
+
43
+ it('applies size styles', () => {
44
+ render(
45
+ <Toggle
46
+ data-testid="size-toggle"
47
+ size="lg"
48
+ >
49
+ <TestIcon />
50
+ </Toggle>,
51
+ );
52
+ const toggle = screen.getByTestId('size-toggle');
53
+ expect(toggle).toHaveClass('h-12', 'w-12', 'p-3');
54
+ });
55
+
56
+ it('handles pressed state', () => {
57
+ render(
58
+ <Toggle
59
+ data-testid="pressed-toggle"
60
+ defaultPressed
61
+ >
62
+ <TestIcon />
63
+ </Toggle>,
64
+ );
65
+ const toggle = screen.getByTestId('pressed-toggle');
66
+ expect(toggle).toHaveAttribute('data-pressed');
67
+ });
68
+
69
+ it('handles controlled pressed state', () => {
70
+ render(
71
+ <Toggle
72
+ data-testid="controlled-toggle"
73
+ pressed
74
+ >
75
+ <TestIcon />
76
+ </Toggle>,
77
+ );
78
+ const toggle = screen.getByTestId('controlled-toggle');
79
+ expect(toggle).toHaveAttribute('data-pressed');
80
+ });
81
+
82
+ it('handles disabled state', () => {
83
+ render(
84
+ <Toggle
85
+ data-testid="disabled-toggle"
86
+ disabled
87
+ >
88
+ <TestIcon />
89
+ </Toggle>,
90
+ );
91
+ const toggle = screen.getByTestId('disabled-toggle');
92
+ expect(toggle).toBeDisabled();
93
+ expect(toggle).toHaveClass('disabled:opacity-50');
94
+ });
95
+
96
+ it('applies custom className', () => {
97
+ render(
98
+ <Toggle
99
+ aria-label="Custom toggle"
100
+ className="custom-toggle"
101
+ >
102
+ <TestIcon />
103
+ </Toggle>,
104
+ );
105
+ const toggle = screen.getByRole('button', { name: 'Custom toggle' });
106
+ expect(toggle).toHaveClass('custom-toggle');
107
+ });
108
+
109
+ it('sets title attribute', () => {
110
+ render(
111
+ <Toggle
112
+ aria-label="Toggle"
113
+ title="Toggle setting"
114
+ >
115
+ <TestIcon />
116
+ </Toggle>,
117
+ );
118
+ const toggle = screen.getByRole('button', { name: 'Toggle' });
119
+ expect(toggle).toHaveAttribute('title', 'Toggle setting');
120
+ });
121
+
122
+ it('passes through other props', () => {
123
+ render(
124
+ <Toggle
125
+ aria-label="Custom toggle"
126
+ data-testid="custom-toggle"
127
+ >
128
+ <TestIcon />
129
+ </Toggle>,
130
+ );
131
+ const toggle = screen.getByTestId('custom-toggle');
132
+ expect(toggle).toHaveAttribute('aria-label', 'Custom toggle');
133
+ });
134
+
135
+ it('calls onPressedChange when clicked', () => {
136
+ const onPressedChange = vi.fn();
137
+ render(
138
+ <Toggle
139
+ aria-label="Test toggle"
140
+ onPressedChange={onPressedChange}
141
+ >
142
+ <TestIcon />
143
+ </Toggle>,
144
+ );
145
+ const toggle = screen.getByRole('button', { name: 'Test toggle' });
146
+ toggle.click();
147
+ expect(onPressedChange).toHaveBeenCalledWith(true, expect.any(Object));
148
+ });
149
+ });
@@ -0,0 +1,88 @@
1
+ import { Toggle as BaseUIToggle } from '@base-ui/react/toggle';
2
+ import { tv } from '@regardio/tailwind/utils';
3
+ import type { ComponentProps, ReactNode } from 'react';
4
+
5
+ const toggle: ReturnType<typeof tv> = tv({
6
+ base: [
7
+ 'inline-flex',
8
+ 'items-center',
9
+ 'justify-center',
10
+ 'rounded-md',
11
+ 'text-sm',
12
+ 'font-medium',
13
+ 'transition-colors',
14
+ 'duration-200',
15
+ 'focus:outline-none',
16
+ 'focus:ring-2',
17
+ 'focus:ring-blue-500',
18
+ 'focus:ring-offset-2',
19
+ 'disabled:pointer-events-none',
20
+ 'disabled:opacity-50',
21
+ ],
22
+ defaultVariants: {
23
+ size: 'md',
24
+ variant: 'outline',
25
+ },
26
+ variants: {
27
+ size: {
28
+ lg: ['h-12', 'w-12', 'p-3'],
29
+ md: ['h-10', 'w-10', 'p-2'],
30
+ sm: ['h-8', 'w-8', 'p-1'],
31
+ },
32
+ variant: {
33
+ default: [],
34
+ ghost: [
35
+ 'bg-transparent',
36
+ 'text-gray-700',
37
+ 'hover:bg-gray-100',
38
+ 'hover:text-gray-900',
39
+ 'data-[pressed]:bg-gray-200',
40
+ 'data-[pressed]:text-gray-900',
41
+ ],
42
+ outline: [
43
+ 'border',
44
+ 'border-gray-300',
45
+ 'bg-white',
46
+ 'text-gray-700',
47
+ 'hover:bg-gray-50',
48
+ 'hover:text-gray-900',
49
+ 'data-[pressed]:bg-gray-100',
50
+ 'data-[pressed]:text-gray-900',
51
+ ],
52
+ },
53
+ },
54
+ } as const);
55
+
56
+ export type ToggleVariant = keyof typeof toggle.variants.variant;
57
+ export type ToggleSize = keyof typeof toggle.variants.size;
58
+
59
+ export interface ToggleProps extends Omit<ComponentProps<typeof BaseUIToggle>, 'className'> {
60
+ className?: string;
61
+ variant?: ToggleVariant;
62
+ size?: ToggleSize;
63
+ children?: ReactNode;
64
+ title?: string;
65
+ }
66
+
67
+ export const Toggle = ({
68
+ className,
69
+ variant = 'outline',
70
+ size = 'md',
71
+ title,
72
+ children,
73
+ ...props
74
+ }: ToggleProps): React.JSX.Element => {
75
+ return (
76
+ <BaseUIToggle
77
+ className={toggle({
78
+ className,
79
+ size,
80
+ variant,
81
+ })}
82
+ title={title}
83
+ {...props}
84
+ >
85
+ {children}
86
+ </BaseUIToggle>
87
+ );
88
+ };