apex-design-cli 1.0.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/index.d.ts +1 -0
- package/dist/index.js +747 -0
- package/package.json +57 -0
- package/registry/components/accordion.json +26 -0
- package/registry/components/alert.json +25 -0
- package/registry/components/avatar.json +26 -0
- package/registry/components/badge.json +24 -0
- package/registry/components/breadcrumb.json +28 -0
- package/registry/components/button.json +25 -0
- package/registry/components/card.json +28 -0
- package/registry/components/checkbox.json +23 -0
- package/registry/components/command.json +31 -0
- package/registry/components/dialog.json +32 -0
- package/registry/components/divider.json +22 -0
- package/registry/components/dropdown-menu.json +36 -0
- package/registry/components/empty-state.json +21 -0
- package/registry/components/error-message.json +20 -0
- package/registry/components/field-group.json +20 -0
- package/registry/components/helper-text.json +20 -0
- package/registry/components/input.json +21 -0
- package/registry/components/label.json +20 -0
- package/registry/components/progress.json +22 -0
- package/registry/components/radio.json +23 -0
- package/registry/components/select.json +32 -0
- package/registry/components/spinner.json +20 -0
- package/registry/components/switch.json +22 -0
- package/registry/components/table.json +27 -0
- package/registry/components/tabs.json +25 -0
- package/registry/components/textarea.json +21 -0
- package/registry/components/theme-toggler.json +24 -0
- package/registry/components/toast.json +31 -0
- package/registry/components/tooltip.json +26 -0
- package/registry/components/use-theme.json +19 -0
- package/registry/components/utils.json +21 -0
- package/registry/registry.json +35 -0
- package/registry/source/accordion.tsx +55 -0
- package/registry/source/alert.tsx +102 -0
- package/registry/source/avatar.tsx +137 -0
- package/registry/source/badge.tsx +38 -0
- package/registry/source/breadcrumb.tsx +109 -0
- package/registry/source/button.tsx +58 -0
- package/registry/source/card.tsx +108 -0
- package/registry/source/checkbox.tsx +170 -0
- package/registry/source/command.tsx +195 -0
- package/registry/source/dialog.tsx +133 -0
- package/registry/source/divider.tsx +84 -0
- package/registry/source/dropdown-menu.tsx +209 -0
- package/registry/source/empty-state.tsx +88 -0
- package/registry/source/error-message.tsx +49 -0
- package/registry/source/field-group.tsx +53 -0
- package/registry/source/helper-text.tsx +40 -0
- package/registry/source/input.tsx +219 -0
- package/registry/source/label.tsx +60 -0
- package/registry/source/progress.tsx +84 -0
- package/registry/source/radio.tsx +161 -0
- package/registry/source/select.tsx +278 -0
- package/registry/source/spinner.tsx +84 -0
- package/registry/source/switch.tsx +104 -0
- package/registry/source/table.tsx +116 -0
- package/registry/source/tabs.tsx +55 -0
- package/registry/source/textarea.tsx +129 -0
- package/registry/source/theme-toggler.tsx +94 -0
- package/registry/source/toast.tsx +166 -0
- package/registry/source/tooltip.tsx +55 -0
- package/registry/source/use-theme.tsx +102 -0
- package/registry/source/utils.ts +13 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
|
4
|
+
/**
|
|
5
|
+
* Visual style variant of the input
|
|
6
|
+
*/
|
|
7
|
+
variant?: 'default' | 'error';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Size of the input field
|
|
11
|
+
*/
|
|
12
|
+
size?: 'sm' | 'md' | 'lg';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Label text displayed above the input
|
|
16
|
+
*/
|
|
17
|
+
label?: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Helper text displayed below the input
|
|
21
|
+
*/
|
|
22
|
+
helperText?: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Error message displayed below the input
|
|
26
|
+
*/
|
|
27
|
+
errorMessage?: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Icon to display on the left side of the input
|
|
31
|
+
*/
|
|
32
|
+
leftIcon?: React.ReactNode;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Icon to display on the right side of the input
|
|
36
|
+
*/
|
|
37
|
+
rightIcon?: React.ReactNode;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether the field is required
|
|
41
|
+
*/
|
|
42
|
+
required?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
46
|
+
(
|
|
47
|
+
{
|
|
48
|
+
variant = 'default',
|
|
49
|
+
size = 'md',
|
|
50
|
+
label,
|
|
51
|
+
helperText,
|
|
52
|
+
errorMessage,
|
|
53
|
+
leftIcon,
|
|
54
|
+
rightIcon,
|
|
55
|
+
required,
|
|
56
|
+
disabled,
|
|
57
|
+
className = '',
|
|
58
|
+
id,
|
|
59
|
+
...props
|
|
60
|
+
},
|
|
61
|
+
ref
|
|
62
|
+
) => {
|
|
63
|
+
const generatedId = React.useId();
|
|
64
|
+
const inputId = id || generatedId;
|
|
65
|
+
// Determine if we should show error state
|
|
66
|
+
const hasError = variant === 'error' || !!errorMessage;
|
|
67
|
+
|
|
68
|
+
// Base styles for the input container
|
|
69
|
+
const containerStyles = 'flex flex-col gap-1.5';
|
|
70
|
+
|
|
71
|
+
// Label styles
|
|
72
|
+
const labelStyles = 'text-sm font-medium text-semantic-fg-secondary';
|
|
73
|
+
|
|
74
|
+
// Input wrapper styles (for icon support)
|
|
75
|
+
const wrapperStyles = 'relative flex items-center';
|
|
76
|
+
|
|
77
|
+
// Base input styles
|
|
78
|
+
const baseInputStyles = `
|
|
79
|
+
w-full
|
|
80
|
+
rounded-md
|
|
81
|
+
border
|
|
82
|
+
bg-semantic-control-bg
|
|
83
|
+
text-semantic-control-fg
|
|
84
|
+
placeholder:text-semantic-control-placeholder
|
|
85
|
+
transition-colors
|
|
86
|
+
focus:outline-none
|
|
87
|
+
focus:ring-2
|
|
88
|
+
focus:ring-offset-0
|
|
89
|
+
disabled:cursor-not-allowed
|
|
90
|
+
disabled:bg-semantic-bg-disabled
|
|
91
|
+
disabled:text-semantic-fg-disabled
|
|
92
|
+
`.trim().replace(/\s+/g, ' ');
|
|
93
|
+
|
|
94
|
+
// Variant styles
|
|
95
|
+
const variantStyles = {
|
|
96
|
+
default: `
|
|
97
|
+
border-semantic-control-border
|
|
98
|
+
hover:border-semantic-control-border-hover
|
|
99
|
+
focus:border-semantic-border-focus
|
|
100
|
+
focus:ring-semantic-focus
|
|
101
|
+
`.trim().replace(/\s+/g, ' '),
|
|
102
|
+
error: `
|
|
103
|
+
border-semantic-control-border-error
|
|
104
|
+
hover:border-semantic-control-border-error
|
|
105
|
+
focus:border-semantic-control-border-error
|
|
106
|
+
focus:ring-semantic-control-border-error
|
|
107
|
+
`.trim().replace(/\s+/g, ' '),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Size styles
|
|
111
|
+
const sizeStyles = {
|
|
112
|
+
sm: 'h-8 px-3 text-sm',
|
|
113
|
+
md: 'h-10 px-3 text-sm',
|
|
114
|
+
lg: 'h-12 px-4 text-base',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Adjust padding for icons
|
|
118
|
+
const iconPaddingStyles = {
|
|
119
|
+
left: {
|
|
120
|
+
sm: 'pl-9',
|
|
121
|
+
md: 'pl-10',
|
|
122
|
+
lg: 'pl-12',
|
|
123
|
+
},
|
|
124
|
+
right: {
|
|
125
|
+
sm: 'pr-9',
|
|
126
|
+
md: 'pr-10',
|
|
127
|
+
lg: 'pr-12',
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Icon container styles
|
|
132
|
+
const iconContainerStyles = {
|
|
133
|
+
base: 'absolute flex items-center justify-center text-semantic-control-icon',
|
|
134
|
+
left: {
|
|
135
|
+
sm: 'left-2.5 w-4 h-4',
|
|
136
|
+
md: 'left-3 w-4 h-4',
|
|
137
|
+
lg: 'left-3.5 w-5 h-5',
|
|
138
|
+
},
|
|
139
|
+
right: {
|
|
140
|
+
sm: 'right-2.5 w-4 h-4',
|
|
141
|
+
md: 'right-3 w-4 h-4',
|
|
142
|
+
lg: 'right-3.5 w-5 h-5',
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Combine all input classes
|
|
147
|
+
const inputClassName = [
|
|
148
|
+
baseInputStyles,
|
|
149
|
+
variantStyles[hasError ? 'error' : 'default'],
|
|
150
|
+
sizeStyles[size],
|
|
151
|
+
leftIcon && iconPaddingStyles.left[size],
|
|
152
|
+
rightIcon && iconPaddingStyles.right[size],
|
|
153
|
+
className,
|
|
154
|
+
].filter(Boolean).join(' ');
|
|
155
|
+
|
|
156
|
+
// Helper/Error text styles
|
|
157
|
+
const messageStyles = hasError
|
|
158
|
+
? 'text-sm text-semantic-fg-error'
|
|
159
|
+
: 'text-sm text-semantic-fg-secondary';
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div className={containerStyles}>
|
|
163
|
+
{/* Label */}
|
|
164
|
+
{label && (
|
|
165
|
+
<label htmlFor={inputId} className={labelStyles}>
|
|
166
|
+
{label}
|
|
167
|
+
{required && <span className="text-semantic-fg-error ml-1">*</span>}
|
|
168
|
+
</label>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
{/* Input wrapper with icons */}
|
|
172
|
+
<div className={wrapperStyles}>
|
|
173
|
+
{/* Left icon */}
|
|
174
|
+
{leftIcon && (
|
|
175
|
+
<div
|
|
176
|
+
className={`${iconContainerStyles.base} ${iconContainerStyles.left[size]}`}
|
|
177
|
+
aria-hidden="true"
|
|
178
|
+
>
|
|
179
|
+
{leftIcon}
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
|
|
183
|
+
{/* Input field */}
|
|
184
|
+
<input
|
|
185
|
+
ref={ref}
|
|
186
|
+
id={inputId}
|
|
187
|
+
className={inputClassName}
|
|
188
|
+
disabled={disabled}
|
|
189
|
+
aria-invalid={hasError}
|
|
190
|
+
aria-required={required}
|
|
191
|
+
{...props}
|
|
192
|
+
/>
|
|
193
|
+
|
|
194
|
+
{/* Right icon */}
|
|
195
|
+
{rightIcon && (
|
|
196
|
+
<div
|
|
197
|
+
className={`${iconContainerStyles.base} ${iconContainerStyles.right[size]}`}
|
|
198
|
+
aria-hidden="true"
|
|
199
|
+
>
|
|
200
|
+
{rightIcon}
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
{/* Helper text or error message */}
|
|
206
|
+
{(errorMessage || helperText) && (
|
|
207
|
+
<p className={messageStyles}>
|
|
208
|
+
{errorMessage || helperText}
|
|
209
|
+
</p>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
Input.displayName = 'Input';
|
|
217
|
+
|
|
218
|
+
export { Input };
|
|
219
|
+
export default Input;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '../../lib/utils';
|
|
4
|
+
|
|
5
|
+
const labelVariants = cva(
|
|
6
|
+
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: 'text-semantic-fg-primary',
|
|
11
|
+
muted: 'text-semantic-fg-muted',
|
|
12
|
+
},
|
|
13
|
+
size: {
|
|
14
|
+
sm: 'text-xs',
|
|
15
|
+
md: 'text-sm',
|
|
16
|
+
lg: 'text-base',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: 'default',
|
|
21
|
+
size: 'md',
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export interface LabelProps
|
|
27
|
+
extends React.LabelHTMLAttributes<HTMLLabelElement>,
|
|
28
|
+
VariantProps<typeof labelVariants> {
|
|
29
|
+
/** Whether the field is required */
|
|
30
|
+
required?: boolean;
|
|
31
|
+
/** Whether the field is disabled */
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|
36
|
+
({ className, variant, size, required, disabled, children, ...props }, ref) => {
|
|
37
|
+
return (
|
|
38
|
+
<label
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={cn(
|
|
41
|
+
labelVariants({ variant, size }),
|
|
42
|
+
disabled && 'cursor-not-allowed opacity-70',
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{children}
|
|
48
|
+
{required && (
|
|
49
|
+
<span className="ml-1 text-semantic-fg-error" aria-label="required">
|
|
50
|
+
*
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
53
|
+
</label>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
Label.displayName = 'Label';
|
|
59
|
+
|
|
60
|
+
export { Label, labelVariants };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as ProgressPrimitive from '@radix-ui/react-progress';
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
|
|
6
|
+
const progressVariants = cva('relative h-4 w-full overflow-hidden rounded-full', {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
default: 'bg-semantic-progress-track',
|
|
10
|
+
success: 'bg-semantic-progress-success-track',
|
|
11
|
+
warning: 'bg-semantic-progress-warning-track',
|
|
12
|
+
error: 'bg-semantic-progress-error-track',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
variant: 'default',
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const progressIndicatorVariants = cva(
|
|
21
|
+
'h-full w-full flex-1 transition-all duration-300 ease-in-out',
|
|
22
|
+
{
|
|
23
|
+
variants: {
|
|
24
|
+
variant: {
|
|
25
|
+
default: 'bg-semantic-progress-fill',
|
|
26
|
+
success: 'bg-semantic-progress-success-fill',
|
|
27
|
+
warning: 'bg-semantic-progress-warning-fill',
|
|
28
|
+
error: 'bg-semantic-progress-error-fill',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
defaultVariants: {
|
|
32
|
+
variant: 'default',
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export interface ProgressProps
|
|
38
|
+
extends React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>,
|
|
39
|
+
VariantProps<typeof progressVariants> {
|
|
40
|
+
showLabel?: boolean;
|
|
41
|
+
label?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const Progress = React.forwardRef<
|
|
45
|
+
React.ElementRef<typeof ProgressPrimitive.Root>,
|
|
46
|
+
ProgressProps
|
|
47
|
+
>(({ className, value = 0, variant, showLabel, label, ...props }, ref) => {
|
|
48
|
+
const progressValue = Math.min(Math.max(value || 0, 0), 100);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="w-full space-y-2" aria-live="polite">
|
|
52
|
+
{(showLabel || label) && (
|
|
53
|
+
<div className="flex items-center justify-between text-sm">
|
|
54
|
+
<span className="text-semantic-fg-secondary">
|
|
55
|
+
{label || 'Progress'}
|
|
56
|
+
</span>
|
|
57
|
+
{showLabel && (
|
|
58
|
+
<span className="font-medium text-semantic-fg-primary">
|
|
59
|
+
{progressValue}%
|
|
60
|
+
</span>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
<ProgressPrimitive.Root
|
|
65
|
+
ref={ref}
|
|
66
|
+
className={cn(progressVariants({ variant }), className)}
|
|
67
|
+
value={progressValue}
|
|
68
|
+
aria-label={label || 'Progress'}
|
|
69
|
+
aria-valuemin={0}
|
|
70
|
+
aria-valuemax={100}
|
|
71
|
+
aria-valuenow={progressValue}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
<ProgressPrimitive.Indicator
|
|
75
|
+
className={cn(progressIndicatorVariants({ variant }))}
|
|
76
|
+
style={{ transform: `translateX(-${100 - progressValue}%)` }}
|
|
77
|
+
/>
|
|
78
|
+
</ProgressPrimitive.Root>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
Progress.displayName = ProgressPrimitive.Root.displayName;
|
|
83
|
+
|
|
84
|
+
export { Progress, progressVariants };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
3
|
+
import { Circle } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
|
|
6
|
+
const RadioGroup = React.forwardRef<
|
|
7
|
+
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
|
8
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
|
|
9
|
+
>(({ className, ...props }, ref) => {
|
|
10
|
+
return <RadioGroupPrimitive.Root className={cn('grid gap-2', className)} {...props} ref={ref} />;
|
|
11
|
+
});
|
|
12
|
+
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
|
|
13
|
+
|
|
14
|
+
const RadioGroupItem = React.forwardRef<
|
|
15
|
+
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
|
16
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> & {
|
|
17
|
+
size?: 'sm' | 'md' | 'lg';
|
|
18
|
+
}
|
|
19
|
+
>(({ className, size = 'md', ...props }, ref) => {
|
|
20
|
+
const sizeClasses = {
|
|
21
|
+
sm: 'h-4 w-4',
|
|
22
|
+
md: 'h-5 w-5',
|
|
23
|
+
lg: 'h-6 w-6',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const iconSizes = {
|
|
27
|
+
sm: 8,
|
|
28
|
+
md: 10,
|
|
29
|
+
lg: 12,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<RadioGroupPrimitive.Item
|
|
34
|
+
ref={ref}
|
|
35
|
+
className={cn(
|
|
36
|
+
'aspect-square rounded-full border-2 border-semantic-control-border text-semantic-control-bg-checked ring-offset-semantic-offset',
|
|
37
|
+
'transition-colors',
|
|
38
|
+
'focus:outline-none focus-visible:ring-2 focus-visible:ring-semantic-focus focus-visible:ring-offset-2',
|
|
39
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
40
|
+
'data-[state=checked]:border-semantic-control-bg-checked data-[state=checked]:bg-semantic-control-bg',
|
|
41
|
+
sizeClasses[size],
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
|
47
|
+
<Circle className="fill-current text-current" size={iconSizes[size]} />
|
|
48
|
+
</RadioGroupPrimitive.Indicator>
|
|
49
|
+
</RadioGroupPrimitive.Item>
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
|
|
53
|
+
|
|
54
|
+
export interface RadioOption {
|
|
55
|
+
value: string;
|
|
56
|
+
label: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
disabled?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface RadioGroupWrapperProps
|
|
62
|
+
extends Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>, 'children'> {
|
|
63
|
+
label?: string;
|
|
64
|
+
helperText?: string;
|
|
65
|
+
errorMessage?: string;
|
|
66
|
+
error?: boolean;
|
|
67
|
+
options: RadioOption[];
|
|
68
|
+
size?: 'sm' | 'md' | 'lg';
|
|
69
|
+
orientation?: 'vertical' | 'horizontal';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const RadioGroupWrapper = React.forwardRef<
|
|
73
|
+
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
|
74
|
+
RadioGroupWrapperProps
|
|
75
|
+
>(
|
|
76
|
+
(
|
|
77
|
+
{
|
|
78
|
+
label,
|
|
79
|
+
helperText,
|
|
80
|
+
errorMessage,
|
|
81
|
+
error = false,
|
|
82
|
+
options,
|
|
83
|
+
size = 'md',
|
|
84
|
+
orientation = 'vertical',
|
|
85
|
+
className,
|
|
86
|
+
...props
|
|
87
|
+
},
|
|
88
|
+
ref
|
|
89
|
+
) => {
|
|
90
|
+
const groupId = React.useId();
|
|
91
|
+
const helperTextId = `${groupId}-helper`;
|
|
92
|
+
const errorMessageId = `${groupId}-error`;
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="w-full">
|
|
96
|
+
{label && (
|
|
97
|
+
<label className="mb-2 block text-sm font-medium text-semantic-fg-secondary">
|
|
98
|
+
{label}
|
|
99
|
+
</label>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
<RadioGroup
|
|
103
|
+
ref={ref}
|
|
104
|
+
className={cn(
|
|
105
|
+
orientation === 'horizontal' ? 'flex flex-wrap gap-4' : 'grid gap-3',
|
|
106
|
+
className
|
|
107
|
+
)}
|
|
108
|
+
aria-invalid={error ? 'true' : 'false'}
|
|
109
|
+
aria-describedby={
|
|
110
|
+
error && errorMessage ? errorMessageId : helperText ? helperTextId : undefined
|
|
111
|
+
}
|
|
112
|
+
{...props}
|
|
113
|
+
>
|
|
114
|
+
{options.map((option) => (
|
|
115
|
+
<div key={option.value} className="flex items-start gap-2">
|
|
116
|
+
<RadioGroupItem id={option.value} value={option.value} size={size} disabled={option.disabled} />
|
|
117
|
+
<div className="flex flex-col">
|
|
118
|
+
<label
|
|
119
|
+
htmlFor={option.value}
|
|
120
|
+
className={cn(
|
|
121
|
+
'cursor-pointer select-none font-medium leading-none',
|
|
122
|
+
size === 'sm' ? 'text-sm' : size === 'lg' ? 'text-lg' : 'text-base',
|
|
123
|
+
option.disabled && 'cursor-not-allowed opacity-70',
|
|
124
|
+
'text-semantic-fg-primary'
|
|
125
|
+
)}
|
|
126
|
+
>
|
|
127
|
+
{option.label}
|
|
128
|
+
</label>
|
|
129
|
+
{option.description && (
|
|
130
|
+
<p
|
|
131
|
+
className={cn(
|
|
132
|
+
'text-semantic-fg-secondary',
|
|
133
|
+
size === 'sm' ? 'text-xs mt-0.5' : 'text-sm mt-1'
|
|
134
|
+
)}
|
|
135
|
+
>
|
|
136
|
+
{option.description}
|
|
137
|
+
</p>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
))}
|
|
142
|
+
</RadioGroup>
|
|
143
|
+
|
|
144
|
+
{!error && helperText && (
|
|
145
|
+
<p id={helperTextId} className="mt-2 text-sm text-semantic-fg-secondary">
|
|
146
|
+
{helperText}
|
|
147
|
+
</p>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{error && errorMessage && (
|
|
151
|
+
<p id={errorMessageId} className="mt-2 text-sm text-semantic-fg-error">
|
|
152
|
+
{errorMessage}
|
|
153
|
+
</p>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
RadioGroupWrapper.displayName = 'RadioGroupWrapper';
|
|
160
|
+
|
|
161
|
+
export { RadioGroup, RadioGroupItem, RadioGroupWrapper };
|