@regardio/react 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/background-slideshow/index.d.mts +36 -0
  2. package/dist/background-slideshow/index.mjs +110 -0
  3. package/dist/blurry-gradient/index.d.mts +17 -0
  4. package/dist/blurry-gradient/index.mjs +93 -0
  5. package/dist/button/index.d.mts +2 -0
  6. package/dist/button/index.mjs +3 -0
  7. package/dist/button-BiSQpBbc.mjs +129 -0
  8. package/dist/carousel/index.d.mts +40 -0
  9. package/dist/carousel/index.mjs +141 -0
  10. package/dist/checkbox/index.d.mts +37 -0
  11. package/dist/checkbox/index.mjs +70 -0
  12. package/dist/checkbox-group/index.d.mts +17 -0
  13. package/dist/checkbox-group/index.mjs +29 -0
  14. package/dist/chunk-BTpB_u-K.mjs +18 -0
  15. package/dist/countdown/index.d.mts +6 -0
  16. package/dist/countdown/index.mjs +58 -0
  17. package/dist/field/index.d.mts +66 -0
  18. package/dist/field/index.mjs +115 -0
  19. package/dist/fieldset/index.d.mts +33 -0
  20. package/dist/fieldset/index.mjs +61 -0
  21. package/dist/form/index.d.mts +22 -0
  22. package/dist/form/index.mjs +31 -0
  23. package/dist/generic-error/{index.d.ts → index.d.mts} +22 -18
  24. package/dist/generic-error/index.mjs +57 -0
  25. package/dist/grid/index.d.mts +1197 -0
  26. package/dist/grid/index.mjs +221 -0
  27. package/dist/heading/index.d.mts +31 -0
  28. package/dist/heading/index.mjs +29 -0
  29. package/dist/highlight/index.d.mts +18 -0
  30. package/dist/highlight/index.mjs +35 -0
  31. package/dist/hooks/{use-current-route-data.d.ts → use-current-route-data.d.mts} +3 -2
  32. package/dist/hooks/use-current-route-data.mjs +20 -0
  33. package/dist/hooks/{use-focus-search.d.ts → use-focus-search.d.mts} +4 -3
  34. package/dist/hooks/use-focus-search.mjs +21 -0
  35. package/dist/hooks/{use-matches-data.d.ts → use-matches-data.d.mts} +3 -2
  36. package/dist/hooks/use-matches-data.mjs +21 -0
  37. package/dist/hooks/{use-media-query.d.ts → use-media-query.d.mts} +3 -2
  38. package/dist/hooks/use-media-query.mjs +26 -0
  39. package/dist/hooks/use-mobile.d.mts +4 -0
  40. package/dist/hooks/use-mobile.mjs +20 -0
  41. package/dist/hooks/use-nonce.d.mts +8 -0
  42. package/dist/hooks/use-nonce.mjs +13 -0
  43. package/dist/hooks/{use-orientation.d.ts → use-orientation.d.mts} +3 -2
  44. package/dist/hooks/use-orientation.mjs +30 -0
  45. package/dist/hooks/use-user.d.mts +55 -0
  46. package/dist/hooks/use-user.mjs +39 -0
  47. package/dist/icon-button/index.d.mts +29 -0
  48. package/dist/icon-button/index.mjs +36 -0
  49. package/dist/if/index.d.mts +15 -0
  50. package/dist/if/index.mjs +21 -0
  51. package/dist/iframe/index.d.mts +11 -0
  52. package/dist/iframe/index.mjs +15 -0
  53. package/dist/index-Bm-tWhsb.d.mts +30 -0
  54. package/dist/index-YT2CkvL6.d.mts +36 -0
  55. package/dist/input/index.d.mts +2 -0
  56. package/dist/input/index.mjs +3 -0
  57. package/dist/input-CtR6aRVi.mjs +73 -0
  58. package/dist/link/index.d.mts +73 -0
  59. package/dist/link/index.mjs +129 -0
  60. package/dist/list/index.d.mts +71 -0
  61. package/dist/list/index.mjs +54 -0
  62. package/dist/markdown-container/index.d.mts +23 -0
  63. package/dist/markdown-container/index.mjs +71 -0
  64. package/dist/password-input/index.d.mts +24 -0
  65. package/dist/password-input/index.mjs +92 -0
  66. package/dist/picture/{index.d.ts → index.d.mts} +21 -20
  67. package/dist/picture/index.mjs +3 -0
  68. package/dist/picture-DkX3W5zl.mjs +69 -0
  69. package/dist/protected-email/{index.d.ts → index.d.mts} +14 -8
  70. package/dist/protected-email/index.mjs +37 -0
  71. package/dist/radio/index.d.mts +37 -0
  72. package/dist/radio/index.mjs +72 -0
  73. package/dist/radio-group/index.d.mts +17 -0
  74. package/dist/radio-group/index.mjs +29 -0
  75. package/dist/slider/index.d.mts +85 -0
  76. package/dist/slider/index.mjs +133 -0
  77. package/dist/switch/index.d.mts +38 -0
  78. package/dist/switch/index.mjs +87 -0
  79. package/dist/text/index.d.mts +26 -0
  80. package/dist/text/index.mjs +32 -0
  81. package/dist/text-CPlUND-Z.mjs +58 -0
  82. package/dist/toggle/index.d.mts +59 -0
  83. package/dist/toggle/index.mjs +82 -0
  84. package/dist/utils/author/index.d.mts +4 -0
  85. package/dist/utils/author/index.mjs +26 -0
  86. package/dist/utils/text/{index.d.ts → index.d.mts} +4 -3
  87. package/dist/utils/text/index.mjs +3 -0
  88. package/package.json +17 -129
  89. package/src/button/button.stories.tsx +161 -0
  90. package/src/button/button.test.tsx +73 -0
  91. package/src/button/button.tsx +112 -0
  92. package/src/button/index.ts +2 -0
  93. package/src/carousel/carousel-next.tsx +2 -2
  94. package/src/carousel/carousel-previous.tsx +2 -2
  95. package/src/checkbox/checkbox.stories.tsx +118 -0
  96. package/src/checkbox/checkbox.tsx +91 -0
  97. package/src/checkbox/index.ts +2 -0
  98. package/src/checkbox-group/checkbox-group.tsx +40 -0
  99. package/src/checkbox-group/index.ts +2 -0
  100. package/src/field/field.stories.tsx +105 -0
  101. package/src/field/field.test.tsx +61 -0
  102. package/src/field/field.tsx +165 -0
  103. package/src/field/index.ts +12 -0
  104. package/src/fieldset/fieldset.stories.tsx +204 -0
  105. package/src/fieldset/fieldset.test.tsx +63 -0
  106. package/src/fieldset/fieldset.tsx +75 -0
  107. package/src/fieldset/index.ts +7 -0
  108. package/src/form/form.stories.tsx +230 -0
  109. package/src/form/form.test.tsx +68 -0
  110. package/src/form/form.tsx +38 -0
  111. package/src/form/index.ts +2 -0
  112. package/src/icon-button/icon-button.stories.tsx +128 -7
  113. package/src/icon-button/icon-button.test.tsx +152 -0
  114. package/src/icon-button/icon-button.tsx +43 -9
  115. package/src/input/index.ts +2 -0
  116. package/src/input/input.stories.tsx +151 -0
  117. package/src/input/input.test.tsx +65 -0
  118. package/src/input/input.tsx +113 -0
  119. package/src/link/link.test.tsx +169 -0
  120. package/src/password-input/index.ts +1 -1
  121. package/src/password-input/password-input.tsx +104 -27
  122. package/src/radio/index.ts +2 -0
  123. package/src/radio/radio.tsx +92 -0
  124. package/src/radio-group/index.ts +2 -0
  125. package/src/radio-group/radio-group.tsx +36 -0
  126. package/src/slider/index.ts +18 -0
  127. package/src/slider/slider.tsx +179 -0
  128. package/src/switch/index.ts +2 -0
  129. package/src/switch/switch.stories.tsx +118 -0
  130. package/src/switch/switch.tsx +101 -0
  131. package/src/toggle/index.ts +2 -0
  132. package/src/toggle/toggle.stories.tsx +232 -0
  133. package/src/toggle/toggle.test.tsx +149 -0
  134. package/src/toggle/toggle.tsx +88 -0
  135. package/src/utils/text/text.test.tsx +110 -0
  136. package/dist/background-slideshow/index.d.ts +0 -24
  137. package/dist/background-slideshow/index.js +0 -165
  138. package/dist/blurry-gradient/index.d.ts +0 -16
  139. package/dist/blurry-gradient/index.js +0 -128
  140. package/dist/carousel/index.d.ts +0 -36
  141. package/dist/carousel/index.js +0 -171
  142. package/dist/countdown/index.d.ts +0 -5
  143. package/dist/countdown/index.js +0 -73
  144. package/dist/generic-error/index.js +0 -47
  145. package/dist/grid/index.d.ts +0 -1196
  146. package/dist/grid/index.js +0 -239
  147. package/dist/heading/index.d.ts +0 -24
  148. package/dist/heading/index.js +0 -99
  149. package/dist/highlight/index.d.ts +0 -13
  150. package/dist/highlight/index.js +0 -59
  151. package/dist/hooks/use-current-route-data.js +0 -16
  152. package/dist/hooks/use-focus-search.js +0 -19
  153. package/dist/hooks/use-matches-data.js +0 -15
  154. package/dist/hooks/use-media-query.js +0 -20
  155. package/dist/hooks/use-mobile.d.ts +0 -3
  156. package/dist/hooks/use-mobile.js +0 -19
  157. package/dist/hooks/use-nonce.d.ts +0 -7
  158. package/dist/hooks/use-nonce.js +0 -8
  159. package/dist/hooks/use-orientation.js +0 -29
  160. package/dist/hooks/use-user.d.ts +0 -50
  161. package/dist/hooks/use-user.js +0 -25
  162. package/dist/icon-button/index.d.ts +0 -9
  163. package/dist/icon-button/index.js +0 -17
  164. package/dist/if/index.d.ts +0 -10
  165. package/dist/if/index.js +0 -24
  166. package/dist/iframe/index.d.ts +0 -10
  167. package/dist/iframe/index.js +0 -17
  168. package/dist/link/index.d.ts +0 -55
  169. package/dist/link/index.js +0 -195
  170. package/dist/list/index.d.ts +0 -69
  171. package/dist/list/index.js +0 -65
  172. package/dist/markdown-container/index.d.ts +0 -22
  173. package/dist/markdown-container/index.js +0 -128
  174. package/dist/password-input/index.d.ts +0 -11
  175. package/dist/password-input/index.js +0 -46
  176. package/dist/picture/index.js +0 -68
  177. package/dist/protected-email/index.js +0 -30
  178. package/dist/text/index.d.ts +0 -20
  179. package/dist/text/index.js +0 -38
  180. package/dist/utils/author/index.d.ts +0 -3
  181. package/dist/utils/author/index.js +0 -33
  182. package/dist/utils/text/index.js +0 -73
@@ -0,0 +1,113 @@
1
+ import { Input as BaseUIInput } from '@base-ui/react/input';
2
+ import { tv } from '@regardio/tailwind/utils';
3
+ import type { ComponentProps } from 'react';
4
+
5
+ const inputVariants = {
6
+ default: [
7
+ 'w-full',
8
+ 'px-3',
9
+ 'py-2',
10
+ 'border',
11
+ 'border-gray-300',
12
+ 'rounded-md',
13
+ 'bg-white',
14
+ 'text-gray-900',
15
+ 'placeholder-gray-500',
16
+ 'focus:outline-none',
17
+ 'focus:ring-2',
18
+ 'focus:ring-blue-500',
19
+ 'focus:border-blue-500',
20
+ 'transition-colors',
21
+ 'duration-200',
22
+ ],
23
+ error: [
24
+ 'border-red-300',
25
+ 'text-red-900',
26
+ 'placeholder-red-500',
27
+ 'focus:ring-red-500',
28
+ 'focus:border-red-500',
29
+ ],
30
+ success: [
31
+ 'border-green-300',
32
+ 'text-green-900',
33
+ 'placeholder-green-500',
34
+ 'focus:ring-green-500',
35
+ 'focus:border-green-500',
36
+ ],
37
+ } as const;
38
+
39
+ const inputSizes = {
40
+ lg: ['px-4', 'py-3', 'text-lg'],
41
+ md: ['px-3', 'py-2', 'text-base'],
42
+ sm: ['px-2', 'py-1', 'text-sm'],
43
+ } as const;
44
+
45
+ const input = tv({
46
+ base: [
47
+ 'w-full',
48
+ 'px-3',
49
+ 'py-2',
50
+ 'border',
51
+ 'border-gray-300',
52
+ 'rounded-md',
53
+ 'bg-white',
54
+ 'text-gray-900',
55
+ 'placeholder-gray-500',
56
+ 'focus:outline-none',
57
+ 'focus:ring-2',
58
+ 'focus:ring-blue-500',
59
+ 'focus:border-blue-500',
60
+ 'transition-colors',
61
+ 'duration-200',
62
+ ],
63
+ defaultVariants: {
64
+ size: 'md',
65
+ variant: 'default',
66
+ },
67
+ variants: {
68
+ size: {
69
+ lg: ['px-4', 'py-3', 'text-lg'],
70
+ md: [],
71
+ sm: ['px-2', 'py-1', 'text-sm'],
72
+ },
73
+ variant: {
74
+ default: [],
75
+ error: [
76
+ 'border-red-300',
77
+ 'text-red-900',
78
+ 'placeholder-red-500',
79
+ 'focus:ring-red-500',
80
+ 'focus:border-red-500',
81
+ ],
82
+ success: [
83
+ 'border-green-300',
84
+ 'text-green-900',
85
+ 'placeholder-green-500',
86
+ 'focus:ring-green-500',
87
+ 'focus:border-green-500',
88
+ ],
89
+ },
90
+ },
91
+ });
92
+
93
+ export type InputVariant = keyof typeof inputVariants;
94
+ export type InputSize = keyof typeof inputSizes;
95
+
96
+ export interface InputProps extends Omit<ComponentProps<typeof BaseUIInput>, 'className' | 'size'> {
97
+ className?: string;
98
+ variant?: InputVariant;
99
+ size?: InputSize;
100
+ }
101
+
102
+ export const Input = ({ className, variant, size, ...props }: InputProps) => {
103
+ return (
104
+ <BaseUIInput
105
+ className={input({
106
+ className,
107
+ size,
108
+ variant,
109
+ })}
110
+ {...props}
111
+ />
112
+ );
113
+ };
@@ -0,0 +1,169 @@
1
+ import { cleanup, fireEvent, render, screen } from '@testing-library/react';
2
+ import { MemoryRouter } from 'react-router';
3
+ import { afterEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import { Link, LinkBase, MarkdownLink, PathResolverProvider } from './link';
6
+
7
+ afterEach(() => {
8
+ cleanup();
9
+ });
10
+
11
+ const renderWithRouter = (ui: React.ReactNode) => {
12
+ return render(<MemoryRouter>{ui}</MemoryRouter>);
13
+ };
14
+
15
+ describe('LinkBase', () => {
16
+ it('renders internal link with NavLink', () => {
17
+ renderWithRouter(<LinkBase to="/about">About</LinkBase>);
18
+ expect(screen.getByText('About')).toBeDefined();
19
+ });
20
+
21
+ it('renders external http link as anchor', () => {
22
+ renderWithRouter(<LinkBase to="https://example.com">External</LinkBase>);
23
+ const link = screen.getByText('External');
24
+ expect(link.tagName).toBe('A');
25
+ expect(link.getAttribute('href')).toBe('https://example.com');
26
+ });
27
+
28
+ it('renders mailto link as anchor', () => {
29
+ renderWithRouter(<LinkBase to="mailto:test@example.com">Email</LinkBase>);
30
+ const link = screen.getByText('Email');
31
+ expect(link.tagName).toBe('A');
32
+ expect(link.getAttribute('href')).toBe('mailto:test@example.com');
33
+ });
34
+
35
+ it('renders tel link as anchor', () => {
36
+ renderWithRouter(<LinkBase to="tel:+1234567890">Call</LinkBase>);
37
+ const link = screen.getByText('Call');
38
+ expect(link.tagName).toBe('A');
39
+ expect(link.getAttribute('href')).toBe('tel:+1234567890');
40
+ });
41
+
42
+ it('uses pathResolver when routeKey is provided', () => {
43
+ const resolver = vi.fn().mockReturnValue('/resolved-path');
44
+ renderWithRouter(
45
+ <PathResolverProvider value={resolver}>
46
+ <LinkBase routeKey="home">Home</LinkBase>
47
+ </PathResolverProvider>,
48
+ );
49
+ expect(resolver).toHaveBeenCalledWith('home');
50
+ expect(screen.getByText('Home')).toBeDefined();
51
+ });
52
+
53
+ it('handles object to prop with pathname, search, and hash', () => {
54
+ renderWithRouter(
55
+ <LinkBase to={{ hash: '#section', pathname: '/page', search: '?q=test' }}>Complex</LinkBase>,
56
+ );
57
+ expect(screen.getByText('Complex')).toBeDefined();
58
+ });
59
+
60
+ it('renders children only when path is empty', () => {
61
+ renderWithRouter(<LinkBase>No Link</LinkBase>);
62
+ expect(screen.getByText('No Link')).toBeDefined();
63
+ });
64
+
65
+ it('calls onClick handler', () => {
66
+ const handleClick = vi.fn();
67
+ renderWithRouter(
68
+ <LinkBase
69
+ onClick={handleClick}
70
+ to="/test"
71
+ >
72
+ Click Me
73
+ </LinkBase>,
74
+ );
75
+ fireEvent.click(screen.getByText('Click Me'));
76
+ expect(handleClick).toHaveBeenCalled();
77
+ });
78
+
79
+ it('opens external http link in new window', () => {
80
+ const windowOpen = vi.spyOn(window, 'open').mockImplementation(() => null);
81
+ renderWithRouter(<LinkBase to="https://example.com">External</LinkBase>);
82
+ fireEvent.click(screen.getByText('External'));
83
+ expect(windowOpen).toHaveBeenCalledWith('https://example.com', '_blank', 'noopener,noreferrer');
84
+ windowOpen.mockRestore();
85
+ });
86
+
87
+ it('scrolls to element for hash links', () => {
88
+ const scrollIntoView = vi.fn();
89
+ const element = document.createElement('div');
90
+ element.id = 'section';
91
+ element.scrollIntoView = scrollIntoView;
92
+ document.body.appendChild(element);
93
+
94
+ renderWithRouter(<LinkBase to="#section">Jump</LinkBase>);
95
+ fireEvent.click(screen.getByText('Jump'));
96
+ expect(scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth' });
97
+
98
+ document.body.removeChild(element);
99
+ });
100
+
101
+ it('handles hash link when element not found', () => {
102
+ renderWithRouter(<LinkBase to="#nonexistent">Jump</LinkBase>);
103
+ fireEvent.click(screen.getByText('Jump'));
104
+ });
105
+
106
+ it('does not prevent default for tel links', () => {
107
+ renderWithRouter(<LinkBase to="tel:+1234567890">Call</LinkBase>);
108
+ const link = screen.getByText('Call');
109
+ const event = fireEvent.click(link);
110
+ expect(event).toBe(true);
111
+ });
112
+
113
+ it('respects defaultPrevented in onClick', () => {
114
+ const handleClick = vi.fn((e: React.MouseEvent) => e.preventDefault());
115
+ const windowOpen = vi.spyOn(window, 'open').mockImplementation(() => null);
116
+
117
+ renderWithRouter(
118
+ <LinkBase
119
+ onClick={handleClick}
120
+ to="https://example.com"
121
+ >
122
+ External
123
+ </LinkBase>,
124
+ );
125
+ fireEvent.click(screen.getByText('External'));
126
+ expect(handleClick).toHaveBeenCalled();
127
+ expect(windowOpen).not.toHaveBeenCalled();
128
+
129
+ windowOpen.mockRestore();
130
+ });
131
+ });
132
+
133
+ describe('Link', () => {
134
+ it('renders with variant', () => {
135
+ renderWithRouter(
136
+ <Link
137
+ to="/test"
138
+ variant="button"
139
+ >
140
+ Button Link
141
+ </Link>,
142
+ );
143
+ expect(screen.getByText('Button Link')).toBeDefined();
144
+ });
145
+
146
+ it('renders with arrow', () => {
147
+ renderWithRouter(
148
+ <Link
149
+ arrow="rarr"
150
+ to="/test"
151
+ >
152
+ Arrow Link
153
+ </Link>,
154
+ );
155
+ expect(screen.getByText('Arrow Link')).toBeDefined();
156
+ });
157
+ });
158
+
159
+ describe('MarkdownLink', () => {
160
+ it('renders Link when href is provided', () => {
161
+ renderWithRouter(<MarkdownLink href="/page">Markdown</MarkdownLink>);
162
+ expect(screen.getByText('Markdown')).toBeDefined();
163
+ });
164
+
165
+ it('returns null when href is not provided', () => {
166
+ const { container } = renderWithRouter(<MarkdownLink>No Link</MarkdownLink>);
167
+ expect(container.innerHTML).toBe('');
168
+ });
169
+ });
@@ -1,2 +1,2 @@
1
- export type { InputProps, PasswordInputProps } from './password-input';
1
+ export type { PasswordInputProps } from './password-input';
2
2
  export { PasswordInput } from './password-input';
@@ -1,17 +1,66 @@
1
1
  'use client';
2
2
 
3
- import { Button } from '@base-ui/react/button';
4
- import { Input } from '@base-ui/react/input';
5
- import { cn } from '@regardio/tailwind/utils';
3
+ import { tv } from '@regardio/tailwind/utils';
6
4
  import { useState } from 'react';
5
+ import { Button } from '../button';
6
+ import { Input } from '../input';
7
7
 
8
- export interface InputProps extends React.ComponentPropsWithoutRef<typeof Input> {}
8
+ const passwordInputVariants = {
9
+ default: [],
10
+ withToggle: [],
11
+ } as const;
9
12
 
10
- export interface PasswordInputProps extends InputProps {
13
+ const passwordInput = tv({
14
+ base: ['relative'],
15
+ defaultVariants: {
16
+ variant: 'default',
17
+ },
18
+ variants: {
19
+ variant: passwordInputVariants,
20
+ },
21
+ });
22
+
23
+ const toggleButton = tv({
24
+ base: [
25
+ 'absolute',
26
+ 'right-3',
27
+ 'top-1/2',
28
+ '-translate-y-1/2',
29
+ 'flex',
30
+ 'h-5',
31
+ 'w-5',
32
+ 'cursor-pointer',
33
+ 'items-center',
34
+ 'justify-center',
35
+ 'text-gray-500',
36
+ 'hover:text-gray-700',
37
+ 'focus:outline-none',
38
+ 'focus:ring-2',
39
+ 'focus:ring-blue-500',
40
+ 'focus:ring-offset-2',
41
+ 'rounded',
42
+ 'transition-colors',
43
+ 'duration-200',
44
+ ],
45
+ });
46
+
47
+ export type PasswordInputVariant = keyof typeof passwordInputVariants;
48
+
49
+ export interface PasswordInputProps
50
+ extends Omit<React.ComponentProps<typeof Input>, 'type' | 'variant'> {
11
51
  className?: string;
52
+ variant?: PasswordInputVariant;
53
+ inputVariant?: 'default' | 'error' | 'success';
54
+ showToggle?: boolean;
12
55
  }
13
56
 
14
- export const PasswordInput = ({ className, ...props }: PasswordInputProps) => {
57
+ export const PasswordInput = ({
58
+ className,
59
+ variant = 'default',
60
+ inputVariant = 'default',
61
+ showToggle = true,
62
+ ...props
63
+ }: PasswordInputProps) => {
15
64
  const [showPassword, setShowPassword] = useState(false);
16
65
 
17
66
  const togglePasswordVisibility = () => {
@@ -19,31 +68,59 @@ export const PasswordInput = ({ className, ...props }: PasswordInputProps) => {
19
68
  };
20
69
 
21
70
  return (
22
- <div className={'relative'}>
71
+ <div className={passwordInput({ className, variant })}>
23
72
  <Input
24
- autoComplete={'off'}
25
- className={cn(className)}
73
+ autoComplete="off"
74
+ className={showToggle ? 'pr-10' : ''}
26
75
  type={showPassword ? 'text' : 'password'}
76
+ variant={inputVariant}
27
77
  {...props}
28
78
  />
29
- <Button
30
- className={cn(
31
- 'absolute',
32
- 'right-[14px]',
33
- 'top-[35px]',
34
- 'flex',
35
- 'h-6',
36
- 'w-[20px]',
37
- 'cursor-pointer',
38
- 'flex-col',
39
- 'items-end',
40
- 'justify-center',
41
- 'text-gray-500',
42
- )}
43
- onClick={togglePasswordVisibility}
44
- >
45
- {showPassword ? 'Off' : 'On'}
46
- </Button>
79
+ {showToggle && (
80
+ <Button
81
+ aria-label={showPassword ? 'Hide password' : 'Show password'}
82
+ className={toggleButton()}
83
+ onClick={togglePasswordVisibility}
84
+ size="sm"
85
+ variant="ghost"
86
+ >
87
+ {showPassword ? (
88
+ <svg
89
+ className="h-4 w-4"
90
+ fill="none"
91
+ stroke="currentColor"
92
+ viewBox="0 0 24 24"
93
+ >
94
+ <path
95
+ d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
96
+ strokeLinecap="round"
97
+ strokeLinejoin="round"
98
+ strokeWidth={2}
99
+ />
100
+ </svg>
101
+ ) : (
102
+ <svg
103
+ className="h-4 w-4"
104
+ fill="none"
105
+ stroke="currentColor"
106
+ viewBox="0 0 24 24"
107
+ >
108
+ <path
109
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
110
+ strokeLinecap="round"
111
+ strokeLinejoin="round"
112
+ strokeWidth={2}
113
+ />
114
+ <path
115
+ d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
116
+ strokeLinecap="round"
117
+ strokeLinejoin="round"
118
+ strokeWidth={2}
119
+ />
120
+ </svg>
121
+ )}
122
+ </Button>
123
+ )}
47
124
  </div>
48
125
  );
49
126
  };
@@ -0,0 +1,2 @@
1
+ export type { RadioIndicatorProps, RadioRootProps, RadioSize } from './radio';
2
+ export { Radio, RadioIndicator, RadioRoot } from './radio';
@@ -0,0 +1,92 @@
1
+ import { Radio as BaseUIRadio } from '@base-ui/react/radio';
2
+ import { tv } from '@regardio/tailwind/utils';
3
+ import type { ComponentProps } from 'react';
4
+
5
+ const radioRoot = tv({
6
+ base: [
7
+ 'h-4',
8
+ 'w-4',
9
+ 'rounded-full',
10
+ 'border',
11
+ 'border-gray-300',
12
+ 'bg-white',
13
+ 'focus:outline-none',
14
+ 'focus:ring-2',
15
+ 'focus:ring-blue-500',
16
+ 'focus:ring-offset-2',
17
+ 'disabled:cursor-not-allowed',
18
+ 'disabled:opacity-50',
19
+ 'data-[checked]:bg-blue-600',
20
+ 'data-[checked]:border-blue-600',
21
+ 'transition-colors',
22
+ 'duration-200',
23
+ 'cursor-pointer',
24
+ ],
25
+ defaultVariants: {
26
+ size: 'md',
27
+ },
28
+ variants: {
29
+ size: {
30
+ lg: ['h-5', 'w-5'],
31
+ md: ['h-4', 'w-4'],
32
+ sm: ['h-3', 'w-3'],
33
+ },
34
+ },
35
+ });
36
+
37
+ const radioIndicator = tv({
38
+ base: ['flex', 'items-center', 'justify-center', 'text-white', 'data-[unchecked]:invisible'],
39
+ });
40
+
41
+ export type RadioSize = 'sm' | 'md' | 'lg';
42
+
43
+ export interface RadioRootProps extends Omit<ComponentProps<typeof BaseUIRadio.Root>, 'className'> {
44
+ className?: string;
45
+ size?: RadioSize;
46
+ }
47
+
48
+ export interface RadioIndicatorProps
49
+ extends Omit<ComponentProps<typeof BaseUIRadio.Indicator>, 'className'> {
50
+ className?: string;
51
+ }
52
+
53
+ export const RadioRoot = ({ className, size = 'md', ...props }: RadioRootProps) => {
54
+ return (
55
+ <BaseUIRadio.Root
56
+ className={radioRoot({
57
+ className,
58
+ size,
59
+ })}
60
+ {...props}
61
+ />
62
+ );
63
+ };
64
+
65
+ export const RadioIndicator = ({ className, children, ...props }: RadioIndicatorProps) => {
66
+ return (
67
+ <BaseUIRadio.Indicator
68
+ className={radioIndicator({ className })}
69
+ {...props}
70
+ >
71
+ {children || (
72
+ <svg
73
+ fill="currentColor"
74
+ height="8"
75
+ viewBox="0 0 8 8"
76
+ width="8"
77
+ >
78
+ <circle
79
+ cx="4"
80
+ cy="4"
81
+ r="3"
82
+ />
83
+ </svg>
84
+ )}
85
+ </BaseUIRadio.Indicator>
86
+ );
87
+ };
88
+
89
+ export const Radio = {
90
+ Indicator: RadioIndicator,
91
+ Root: RadioRoot,
92
+ };
@@ -0,0 +1,2 @@
1
+ export type { RadioGroupOrientation, RadioGroupProps } from './radio-group';
2
+ export { RadioGroup } from './radio-group';
@@ -0,0 +1,36 @@
1
+ import { RadioGroup as BaseUIRadioGroup } from '@base-ui/react/radio-group';
2
+ import { tv } from '@regardio/tailwind/utils';
3
+ import type { ComponentProps } from 'react';
4
+
5
+ const radioGroup = tv({
6
+ base: ['flex', 'flex-col', 'gap-2'],
7
+ defaultVariants: {
8
+ orientation: 'vertical',
9
+ },
10
+ variants: {
11
+ orientation: {
12
+ horizontal: ['flex-row', 'gap-4'],
13
+ vertical: ['flex-col', 'gap-2'],
14
+ },
15
+ },
16
+ });
17
+
18
+ export type RadioGroupOrientation = 'horizontal' | 'vertical';
19
+
20
+ export interface RadioGroupProps
21
+ extends Omit<ComponentProps<typeof BaseUIRadioGroup>, 'className'> {
22
+ className?: string;
23
+ orientation?: RadioGroupOrientation;
24
+ }
25
+
26
+ export const RadioGroup = ({ className, orientation = 'vertical', ...props }: RadioGroupProps) => {
27
+ return (
28
+ <BaseUIRadioGroup
29
+ className={radioGroup({
30
+ className,
31
+ orientation,
32
+ })}
33
+ {...props}
34
+ />
35
+ );
36
+ };
@@ -0,0 +1,18 @@
1
+ export type {
2
+ SliderControlProps,
3
+ SliderIndicatorProps,
4
+ SliderRootProps,
5
+ SliderSize,
6
+ SliderThumbProps,
7
+ SliderTrackProps,
8
+ SliderValueProps,
9
+ } from './slider';
10
+ export {
11
+ Slider,
12
+ SliderControl,
13
+ SliderIndicator,
14
+ SliderRoot,
15
+ SliderThumb,
16
+ SliderTrack,
17
+ SliderValue,
18
+ } from './slider';