@12-apps/ui 1.3.1 → 1.4.1

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 (53) hide show
  1. package/package.json +4 -4
  2. package/src/components/data-display/Carousel/Carousel.chrome.tsx +83 -0
  3. package/src/components/data-display/Carousel/Carousel.hooks.ts +88 -0
  4. package/src/components/data-display/Carousel/Carousel.parts.tsx +346 -0
  5. package/src/components/data-display/Carousel/Carousel.slide.tsx +190 -0
  6. package/src/components/data-display/Carousel/Carousel.styles.ts +86 -0
  7. package/src/components/data-display/Carousel/Carousel.tsx +84 -732
  8. package/src/components/data-display/HoverCard/HoverCard.animations.ts +73 -0
  9. package/src/components/data-display/HoverCard/HoverCard.content.tsx +143 -0
  10. package/src/components/data-display/HoverCard/HoverCard.hooks.ts +180 -0
  11. package/src/components/data-display/HoverCard/HoverCard.styles.ts +178 -0
  12. package/src/components/data-display/HoverCard/HoverCard.tsx +117 -631
  13. package/src/components/data-display/Sheet/Sheet.animations.ts +44 -0
  14. package/src/components/data-display/Sheet/Sheet.body.tsx +124 -0
  15. package/src/components/data-display/Sheet/Sheet.drag.ts +203 -0
  16. package/src/components/data-display/Sheet/Sheet.hooks.ts +318 -0
  17. package/src/components/data-display/Sheet/Sheet.parts.tsx +224 -0
  18. package/src/components/data-display/Sheet/Sheet.styles.ts +257 -0
  19. package/src/components/data-display/Sheet/Sheet.tsx +125 -948
  20. package/src/components/feedback/Modal/Modal.styles.ts +126 -0
  21. package/src/components/feedback/Modal/Modal.transitions.tsx +40 -0
  22. package/src/components/feedback/Modal/Modal.tsx +37 -242
  23. package/src/components/form/InputOTP/InputOTP.hooks.ts +104 -0
  24. package/src/components/form/InputOTP/InputOTP.styles.ts +97 -0
  25. package/src/components/form/InputOTP/InputOTP.tsx +74 -221
  26. package/src/components/form/Label/Label.parts.tsx +75 -0
  27. package/src/components/form/Label/Label.styles.ts +206 -0
  28. package/src/components/form/Label/Label.tsx +73 -325
  29. package/src/components/form/Menubar/Menubar.hooks.ts +41 -0
  30. package/src/components/form/Menubar/Menubar.item.tsx +121 -0
  31. package/src/components/form/Menubar/Menubar.separator.tsx +25 -0
  32. package/src/components/form/Menubar/Menubar.styles.ts +110 -0
  33. package/src/components/form/Menubar/Menubar.toolbar.tsx +103 -0
  34. package/src/components/form/Menubar/Menubar.tsx +64 -347
  35. package/src/components/form/RadioGroup/RadioGroup.animations.ts +48 -0
  36. package/src/components/form/RadioGroup/RadioGroup.styles.ts +305 -0
  37. package/src/components/form/RadioGroup/RadioGroup.tsx +81 -651
  38. package/src/components/form/RadioGroup/RadioGroup.variants.tsx +291 -0
  39. package/src/components/form/Slider/Slider.animations.ts +40 -0
  40. package/src/components/form/Slider/Slider.styles.ts +292 -0
  41. package/src/components/form/Slider/Slider.tsx +106 -357
  42. package/src/components/form/Switch/Switch.animations.ts +62 -0
  43. package/src/components/form/Switch/Switch.parts.tsx +213 -0
  44. package/src/components/form/Switch/Switch.styles.ts +355 -0
  45. package/src/components/form/Switch/Switch.tsx +87 -566
  46. package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +7 -7
  47. package/src/components/typography/Heading/Heading.styles.ts +101 -0
  48. package/src/components/typography/Heading/Heading.tsx +48 -164
  49. package/src/components/utility/AnimatedIcon/AnimatedIcon.animations.ts +274 -0
  50. package/src/components/utility/AnimatedIcon/AnimatedIcon.styles.ts +231 -0
  51. package/src/components/utility/AnimatedIcon/AnimatedIcon.tsx +39 -559
  52. package/src/utils/index.ts +1 -0
  53. package/src/utils/withDefaults.ts +20 -0
@@ -1,664 +1,94 @@
1
- import {
2
- alpha,
3
- Box,
4
- ButtonBase,
5
- Card,
6
- CardContent,
7
- FormControlLabel,
8
- FormHelperText,
9
- FormLabel,
10
- keyframes,
11
- Radio,
12
- RadioGroup as MuiRadioGroup,
13
- Typography,
14
- } from '@mui/material';
15
- import type { Theme } from '@mui/material/styles';
16
- import { styled } from '@mui/material/styles';
1
+ import { Box, FormHelperText } from '@mui/material';
17
2
  import React, { forwardRef } from 'react';
18
3
 
19
- import type { RadioGroupProps } from './RadioGroup.types';
20
-
21
- const glowAnimation = keyframes`
22
- 0% {
23
- box-shadow: 0 0 5px currentColor;
24
- }
25
- 50% {
26
- box-shadow: 0 0 15px currentColor, 0 0 25px currentColor;
27
- }
28
- 100% {
29
- box-shadow: 0 0 5px currentColor;
30
- }
31
- `;
32
-
33
- const slideAnimation = keyframes`
34
- from {
35
- opacity: 0;
36
- transform: translateX(-20px);
37
- }
38
- to {
39
- opacity: 1;
40
- transform: translateX(0);
41
- }
42
- `;
43
-
44
- const scaleAnimation = keyframes`
45
- 0% {
46
- transform: scale(0.95);
47
- }
48
- 50% {
49
- transform: scale(1.05);
50
- }
51
- 100% {
52
- transform: scale(1);
53
- }
54
- `;
55
-
56
- const rippleAnimation = keyframes`
57
- 0% {
58
- transform: scale(0);
59
- opacity: 1;
60
- }
61
- 100% {
62
- transform: scale(4);
63
- opacity: 0;
64
- }
65
- `;
66
-
67
- const getColorFromTheme = (theme: Theme, color: string) => {
68
- if (color === 'neutral') {
69
- return {
70
- main: theme.palette.grey?.[700] || '#616161',
71
- dark: theme.palette.grey?.[800] || '#424242',
72
- light: theme.palette.grey?.[500] || '#9e9e9e',
73
- contrastText: '#fff',
74
- };
75
- }
76
-
77
- const colorMap: Record<
78
- string,
79
- { main: string; dark?: string; light?: string; contrastText?: string }
80
- > = {
81
- primary: theme.palette.primary,
82
- secondary: theme.palette.secondary,
83
- success: theme.palette.success,
84
- warning: theme.palette.warning,
85
- danger: theme.palette.error,
86
- };
87
-
88
- const palette = colorMap[color] || theme.palette.primary;
4
+ import { withDefaults } from '../../../utils/withDefaults';
89
5
 
90
- // Ensure palette has required properties
91
- return {
92
- main: palette?.main || theme.palette.primary.main,
93
- dark: palette?.dark || palette?.main || theme.palette.primary.dark,
94
- light: palette?.light || palette?.main || theme.palette.primary.light,
95
- contrastText: palette?.contrastText || '#fff',
96
- };
6
+ import type { RadioOption, RadioGroupProps } from './RadioGroup.types';
7
+ import {
8
+ ButtonRadios,
9
+ CardRadios,
10
+ DefaultRadios,
11
+ GroupLabel,
12
+ SegmentRadios,
13
+ testId,
14
+ } from './RadioGroup.variants';
15
+ import type { VariantProps } from './RadioGroup.variants';
16
+
17
+ const DEFAULTS = {
18
+ variant: 'default',
19
+ color: 'primary',
20
+ size: 'md',
21
+ error: false,
22
+ glassLabel: false,
23
+ glow: false,
24
+ glass: false,
25
+ gradient: false,
26
+ direction: 'column',
27
+ showDescriptions: true,
28
+ } satisfies Partial<RadioGroupProps>;
29
+
30
+ type ResolvedProps = RadioGroupProps & Required<Pick<RadioGroupProps, keyof typeof DEFAULTS>>;
31
+
32
+ const VARIANTS: Record<string, React.FC<VariantProps>> = {
33
+ cards: CardRadios,
34
+ buttons: ButtonRadios,
35
+ segments: SegmentRadios,
36
+ default: DefaultRadios,
97
37
  };
98
38
 
99
- const StyledFormLabel = styled(FormLabel, {
100
- shouldForwardProp: (prop) => !['glass', 'error'].includes(prop as string),
101
- })<{ glass?: boolean; error?: boolean }>(({ theme, glass, error }) => ({
102
- marginBottom: theme.spacing(2),
103
- fontWeight: 600,
104
- color: error ? theme.palette.error.main : theme.palette.text.primary,
105
- ...(glass && {
106
- backgroundColor: alpha(theme.palette.background.paper, 0.1),
107
- backdropFilter: 'blur(10px)',
108
- padding: '8px 12px',
109
- borderRadius: '8px',
110
- border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
111
- display: 'inline-block',
112
- }),
113
- }));
114
-
115
- const StyledRadioCard = styled(Card, {
116
- shouldForwardProp: (prop) =>
117
- !['selected', 'customColor', 'glass', 'gradient', 'glow', 'customSize', 'animated'].includes(
118
- prop as string,
119
- ),
120
- })<{
121
- selected?: boolean;
122
- customColor?: string;
123
- glass?: boolean;
124
- gradient?: boolean;
125
- glow?: boolean;
126
- customSize?: string;
127
- animated?: boolean;
128
- }>(({
129
- theme,
130
- selected,
131
- customColor = 'primary',
132
- glass,
133
- gradient,
134
- glow,
135
- customSize = 'md',
136
- animated,
137
- }) => {
138
- const colorPalette = getColorFromTheme(theme, customColor);
139
-
140
- const sizeMap = {
141
- xs: { padding: '8px', minHeight: '60px' },
142
- sm: { padding: '12px', minHeight: '70px' },
143
- md: { padding: '16px', minHeight: '80px' },
144
- lg: { padding: '20px', minHeight: '90px' },
145
- xl: { padding: '24px', minHeight: '100px' },
146
- };
147
-
148
- const baseStyles = {
149
- cursor: 'pointer',
150
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
151
- border: `2px solid ${selected ? colorPalette.main : alpha(theme.palette.divider, 0.3)}`,
152
- backgroundColor: selected ? alpha(colorPalette.main, 0.05) : theme.palette.background.paper,
153
- position: 'relative' as const,
154
- overflow: 'hidden' as const,
155
-
156
- ...(animated && {
157
- animation: `${slideAnimation} 0.4s ease-out`,
158
- }),
159
-
160
- '&::before': {
161
- content: '""',
162
- position: 'absolute',
163
- top: '50%',
164
- left: '50%',
165
- width: selected ? '100%' : '0',
166
- height: selected ? '100%' : '0',
167
- background: `radial-gradient(circle, ${alpha(colorPalette.main, 0.1)} 0%, transparent 70%)`,
168
- transform: 'translate(-50%, -50%)',
169
- transition: 'all 0.5s ease',
170
- borderRadius: '50%',
171
- },
172
-
173
- '&:hover': {
174
- borderColor: colorPalette.main,
175
- backgroundColor: alpha(colorPalette.main, 0.02),
176
- transform: 'translateY(-2px) scale(1.02)',
177
- boxShadow: `${theme.shadows[4]}, 0 10px 30px -5px ${alpha(colorPalette.main, 0.2)}`,
178
-
179
- '&::before': {
180
- width: '120%',
181
- height: '120%',
182
- },
183
- },
184
-
185
- '&:active': {
186
- transform: 'scale(0.98)',
187
- },
188
-
189
- ...sizeMap[customSize as keyof typeof sizeMap],
190
- };
191
-
192
- const glassStyles = glass
193
- ? {
194
- backgroundColor: selected
195
- ? alpha(colorPalette.main, 0.1)
196
- : alpha(theme.palette.background.paper, 0.1),
197
- backdropFilter: 'blur(20px)',
198
- border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
199
- }
200
- : {};
201
-
202
- const gradientStyles =
203
- gradient && selected
204
- ? {
205
- background: `linear-gradient(135deg, ${alpha(colorPalette.main, 0.1)}, ${alpha(colorPalette.light || colorPalette.main, 0.05)})`,
206
- borderColor: colorPalette.main,
207
- }
208
- : {};
209
-
210
- const glowStyles =
211
- glow && selected
212
- ? {
213
- animation: `${glowAnimation} 2s ease-in-out infinite`,
214
- boxShadow: `0 0 15px ${alpha(colorPalette.main, 0.4)}`,
215
- }
216
- : {};
217
-
218
- return {
219
- ...baseStyles,
220
- ...glassStyles,
221
- ...gradientStyles,
222
- ...glowStyles,
39
+ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>((props, ref) => {
40
+ const {
41
+ variant, color, size, options, label, error, helperText, glassLabel,
42
+ glow, glass, gradient, direction, showDescriptions, value, onChange, dataTestId,
43
+ ...rest
44
+ } = withDefaults(props, DEFAULTS) as ResolvedProps;
45
+
46
+ // One place that turns a click into the ChangeEvent MUI's onChange expects.
47
+ // The cards, buttons and segments variants each had this written out.
48
+ const handleSelect = (option: RadioOption) => {
49
+ if (option.disabled || !onChange) return;
50
+ const event = { target: { value: option.value } } as React.ChangeEvent<HTMLInputElement>;
51
+ onChange(event, option.value);
223
52
  };
224
- });
225
-
226
- const StyledButtonRadio = styled(ButtonBase, {
227
- shouldForwardProp: (prop) =>
228
- !['selected', 'customColor', 'glass', 'gradient', 'customSize', 'animated'].includes(
229
- prop as string,
230
- ),
231
- })<{
232
- selected?: boolean;
233
- customColor?: string;
234
- glass?: boolean;
235
- gradient?: boolean;
236
- customSize?: string;
237
- animated?: boolean;
238
- }>(({ theme, selected, customColor = 'primary', glass, gradient, customSize = 'md', animated }) => {
239
- const colorPalette = getColorFromTheme(theme, customColor);
240
-
241
- const sizeMap = {
242
- xs: { padding: '6px 12px', fontSize: '0.75rem', minHeight: '32px' },
243
- sm: { padding: '8px 16px', fontSize: '0.875rem', minHeight: '36px' },
244
- md: { padding: '10px 20px', fontSize: '1rem', minHeight: '40px' },
245
- lg: { padding: '12px 24px', fontSize: '1.125rem', minHeight: '44px' },
246
- xl: { padding: '14px 28px', fontSize: '1.25rem', minHeight: '48px' },
247
- };
248
-
249
- const baseStyles = {
250
- borderRadius: theme.spacing(1),
251
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
252
- fontWeight: 500,
253
- border: `2px solid ${selected ? colorPalette.main : alpha(theme.palette.divider, 0.5)}`,
254
- backgroundColor: selected ? colorPalette.main : 'transparent',
255
- color: selected ? colorPalette.contrastText || '#fff' : theme.palette.text.primary,
256
- position: 'relative' as const,
257
- overflow: 'hidden' as const,
258
-
259
- ...(animated &&
260
- selected && {
261
- animation: `${scaleAnimation} 0.3s ease-out`,
262
- }),
263
-
264
- '&::after': {
265
- content: '""',
266
- position: 'absolute',
267
- top: '50%',
268
- left: '50%',
269
- width: 0,
270
- height: 0,
271
- borderRadius: '50%',
272
- backgroundColor: alpha(colorPalette.contrastText || '#fff', 0.3),
273
- transform: 'translate(-50%, -50%)',
274
- pointerEvents: 'none' as const,
275
- },
276
-
277
- '&:active::after': {
278
- animation: `${rippleAnimation} 0.6s ease-out`,
279
- width: '100%',
280
- height: '100%',
281
- },
282
53
 
283
- '&:hover': {
284
- borderColor: colorPalette.main,
285
- backgroundColor: selected ? colorPalette.dark : alpha(colorPalette.main, 0.1),
286
- transform: 'translateY(-1px) scale(1.02)',
287
- boxShadow: `0 4px 12px ${alpha(colorPalette.main, 0.2)}`,
288
- },
54
+ const Variant = VARIANTS[variant] ?? DefaultRadios;
289
55
 
290
- '&:active': {
291
- transform: 'scale(0.98)',
292
- },
56
+ return (
57
+ <Box data-testid={dataTestId}>
58
+ {label && (
59
+ <GroupLabel glass={glassLabel} error={error} dataTestId={dataTestId}>
60
+ {label}
61
+ </GroupLabel>
62
+ )}
293
63
 
294
- ...sizeMap[customSize as keyof typeof sizeMap],
295
- };
296
-
297
- const glassStyles = glass
298
- ? {
299
- backgroundColor: selected
300
- ? alpha(colorPalette.main, 0.8)
301
- : alpha(theme.palette.background.paper, 0.1),
302
- backdropFilter: 'blur(20px)',
303
- border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
304
- }
305
- : {};
306
-
307
- const gradientStyles =
308
- gradient && selected
309
- ? {
310
- background: `linear-gradient(135deg, ${colorPalette.main}, ${colorPalette.dark})`,
311
- border: 'none',
312
- }
313
- : {};
314
-
315
- return {
316
- ...baseStyles,
317
- ...glassStyles,
318
- ...gradientStyles,
319
- };
320
- });
321
-
322
- const StyledSegmentContainer = styled(Box, {
323
- shouldForwardProp: (prop) => !['glass', 'customColor'].includes(prop as string),
324
- })<{ glass?: boolean; customColor?: string }>(({ theme, glass, customColor = 'primary' }) => {
325
- const colorPalette = getColorFromTheme(theme, customColor);
326
-
327
- return {
328
- padding: '4px',
329
- borderRadius: theme.spacing(1.5),
330
- backgroundColor: glass
331
- ? alpha(theme.palette.background.paper, 0.1)
332
- : alpha(colorPalette.main, 0.05),
333
- backdropFilter: glass ? 'blur(20px)' : 'none',
334
- border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
335
- display: 'flex',
336
- gap: '2px',
337
- };
338
- });
339
-
340
- const StyledSegmentButton = styled(ButtonBase, {
341
- shouldForwardProp: (prop) =>
342
- !['selected', 'customColor', 'customSize', 'animated'].includes(prop as string),
343
- })<{
344
- selected?: boolean;
345
- customColor?: string;
346
- customSize?: string;
347
- animated?: boolean;
348
- }>(({ theme, selected, customColor = 'primary', customSize = 'md', animated }) => {
349
- const colorPalette = getColorFromTheme(theme, customColor);
350
-
351
- const sizeMap = {
352
- xs: { padding: '4px 8px', fontSize: '0.75rem' },
353
- sm: { padding: '6px 12px', fontSize: '0.875rem' },
354
- md: { padding: '8px 16px', fontSize: '1rem' },
355
- lg: { padding: '10px 20px', fontSize: '1.125rem' },
356
- xl: { padding: '12px 24px', fontSize: '1.25rem' },
357
- };
358
-
359
- return {
360
- flex: 1,
361
- borderRadius: theme.spacing(1),
362
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
363
- fontWeight: 500,
364
- backgroundColor: selected ? theme.palette.background.paper : 'transparent',
365
- color: selected ? colorPalette.main : theme.palette.text.secondary,
366
- boxShadow: selected
367
- ? `${theme.shadows[2]}, inset 0 1px 3px ${alpha(colorPalette.main, 0.1)}`
368
- : 'none',
369
- position: 'relative' as const,
370
- overflow: 'hidden' as const,
371
-
372
- ...(animated &&
373
- selected && {
374
- animation: `${scaleAnimation} 0.3s ease-out`,
375
- }),
376
-
377
- '&::before': {
378
- content: '""',
379
- position: 'absolute',
380
- bottom: 0,
381
- left: selected ? 0 : '50%',
382
- width: selected ? '100%' : 0,
383
- height: '2px',
384
- backgroundColor: colorPalette.main,
385
- transition: 'all 0.3s ease',
386
- transform: selected ? 'translateX(0)' : 'translateX(-50%)',
387
- },
388
-
389
- '&:hover': {
390
- backgroundColor: selected
391
- ? theme.palette.background.paper
392
- : alpha(theme.palette.action.hover, 0.08),
393
- color: colorPalette.main,
394
-
395
- '&::before': {
396
- width: '100%',
397
- left: 0,
398
- transform: 'translateX(0)',
399
- },
400
- },
401
-
402
- '&:active': {
403
- transform: 'scale(0.98)',
404
- },
405
-
406
- ...sizeMap[customSize as keyof typeof sizeMap],
407
- };
408
- });
409
-
410
- export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
411
- (
412
- {
413
- variant = 'default',
414
- color = 'primary',
415
- size = 'md',
416
- options,
417
- label,
418
- error = false,
419
- helperText,
420
- glassLabel = false,
421
- glow = false,
422
- glass = false,
423
- gradient = false,
424
- direction = 'column',
425
- showDescriptions = true,
426
- value,
427
- onChange,
428
- dataTestId,
429
- ...props
430
- },
431
- ref,
432
- ) => {
433
- const renderDefaultRadio = () => (
434
- <MuiRadioGroup
435
- ref={ref}
64
+ <Variant
65
+ options={options}
436
66
  value={value}
437
- onChange={onChange}
438
- {...props}
439
- sx={{
440
- flexDirection: direction,
441
- gap: 1,
442
- }}
443
- data-testid={dataTestId ? `${dataTestId}-group` : undefined}
444
- >
445
- {options.map((option, index) => (
446
- <FormControlLabel
447
- key={option.value}
448
- value={option.value}
449
- disabled={option.disabled}
450
- data-testid={dataTestId ? `${dataTestId}-option-${index}` : undefined}
451
- control={
452
- <Radio
453
- color={
454
- color === 'danger'
455
- ? 'error'
456
- : (color as 'primary' | 'secondary' | 'success' | 'warning' | 'error')
457
- }
458
- data-testid={dataTestId ? `${dataTestId}-radio-${index}` : undefined}
459
- />
460
- }
461
- label={
462
- <Box
463
- sx={{ display: 'flex', alignItems: 'center', gap: 1 }}
464
- data-testid={dataTestId ? `${dataTestId}-label-${index}` : undefined}
465
- >
466
- {option.icon}
467
- <Box>
468
- <Typography>{option.label}</Typography>
469
- {option.description && showDescriptions && (
470
- <Typography variant="caption" color="text.secondary">
471
- {option.description}
472
- </Typography>
473
- )}
474
- </Box>
475
- </Box>
476
- }
477
- />
478
- ))}
479
- </MuiRadioGroup>
480
- );
481
-
482
- const renderCardRadio = () => (
483
- <Box
484
- sx={{
485
- display: 'flex',
486
- flexDirection: direction,
487
- gap: 2,
488
- }}
489
- data-testid={dataTestId ? `${dataTestId}-container` : undefined}
490
- >
491
- {options.map((option, index) => (
492
- <StyledRadioCard
493
- key={option.value}
494
- selected={value === option.value}
495
- customColor={color}
496
- glass={glass}
497
- gradient={gradient}
498
- glow={glow}
499
- customSize={size}
500
- animated
501
- data-testid={dataTestId ? `${dataTestId}-card-${index}` : undefined}
502
- onClick={() => {
503
- if (!option.disabled && onChange) {
504
- const event = {
505
- target: { value: option.value },
506
- } as React.ChangeEvent<HTMLInputElement>;
507
- onChange(event, option.value);
508
- }
509
- }}
510
- >
511
- <CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
512
- <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
513
- {option.icon && (
514
- <Box
515
- sx={{
516
- mt: 0.5,
517
- color: value === option.value ? `${color}.main` : 'text.secondary',
518
- }}
519
- >
520
- {option.icon}
521
- </Box>
522
- )}
523
- <Box sx={{ flex: 1 }}>
524
- <Typography
525
- variant="subtitle1"
526
- fontWeight={600}
527
- color={value === option.value ? `${color}.main` : 'text.primary'}
528
- data-testid={dataTestId ? `${dataTestId}-label-${index}` : undefined}
529
- >
530
- {option.label}
531
- </Typography>
532
- {option.description && showDescriptions && (
533
- <Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
534
- {option.description}
535
- </Typography>
536
- )}
537
- </Box>
538
- </Box>
539
- </CardContent>
540
- </StyledRadioCard>
541
- ))}
542
- </Box>
543
- );
544
-
545
- const renderButtonRadio = () => (
546
- <Box
547
- sx={{
548
- display: 'flex',
549
- flexDirection: direction,
550
- gap: 1,
551
- flexWrap: 'wrap',
552
- }}
553
- data-testid={dataTestId ? `${dataTestId}-container` : undefined}
554
- >
555
- {options.map((option, index) => (
556
- <StyledButtonRadio
557
- key={option.value}
558
- selected={value === option.value}
559
- customColor={color}
560
- glass={glass}
561
- gradient={gradient}
562
- customSize={size}
563
- animated
564
- disabled={option.disabled}
565
- data-testid={dataTestId ? `${dataTestId}-button-${index}` : undefined}
566
- onClick={() => {
567
- if (!option.disabled && onChange) {
568
- const event = {
569
- target: { value: option.value },
570
- } as React.ChangeEvent<HTMLInputElement>;
571
- onChange(event, option.value);
572
- }
573
- }}
574
- >
575
- <Box
576
- sx={{ display: 'flex', alignItems: 'center', gap: 1 }}
577
- data-testid={dataTestId ? `${dataTestId}-label-${index}` : undefined}
578
- >
579
- {option.icon}
580
- {option.label}
581
- </Box>
582
- </StyledButtonRadio>
583
- ))}
584
- </Box>
585
- );
586
-
587
- const renderSegmentRadio = () => (
588
- <StyledSegmentContainer
67
+ color={color}
68
+ size={size}
69
+ direction={direction}
70
+ showDescriptions={showDescriptions}
589
71
  glass={glass}
590
- customColor={color}
591
- data-testid={dataTestId ? `${dataTestId}-container` : undefined}
592
- >
593
- {options.map((option, index) => (
594
- <StyledSegmentButton
595
- key={option.value}
596
- selected={value === option.value}
597
- customColor={color}
598
- customSize={size}
599
- animated
600
- disabled={option.disabled}
601
- data-testid={dataTestId ? `${dataTestId}-segment-${index}` : undefined}
602
- onClick={() => {
603
- if (!option.disabled && onChange) {
604
- const event = {
605
- target: { value: option.value },
606
- } as React.ChangeEvent<HTMLInputElement>;
607
- onChange(event, option.value);
608
- }
609
- }}
610
- >
611
- <Box
612
- sx={{ display: 'flex', alignItems: 'center', gap: 1 }}
613
- data-testid={dataTestId ? `${dataTestId}-label-${index}` : undefined}
614
- >
615
- {option.icon}
616
- {option.label}
617
- </Box>
618
- </StyledSegmentButton>
619
- ))}
620
- </StyledSegmentContainer>
621
- );
622
-
623
- const renderRadioGroup = () => {
624
- switch (variant) {
625
- case 'cards':
626
- return renderCardRadio();
627
- case 'buttons':
628
- return renderButtonRadio();
629
- case 'segments':
630
- return renderSegmentRadio();
631
- default:
632
- return renderDefaultRadio();
633
- }
634
- };
635
-
636
- return (
637
- <Box data-testid={dataTestId}>
638
- {label && (
639
- <StyledFormLabel
640
- glass={glassLabel}
641
- error={error}
642
- data-testid={dataTestId ? `${dataTestId}-label` : undefined}
643
- >
644
- {label}
645
- </StyledFormLabel>
646
- )}
647
-
648
- {renderRadioGroup()}
649
-
650
- {helperText && (
651
- <FormHelperText
652
- error={error}
653
- sx={{ mt: 1 }}
654
- data-testid={dataTestId ? `${dataTestId}-helper-text` : undefined}
655
- >
656
- {helperText}
657
- </FormHelperText>
658
- )}
659
- </Box>
660
- );
661
- },
662
- );
72
+ gradient={gradient}
73
+ glow={glow}
74
+ dataTestId={dataTestId}
75
+ onSelect={handleSelect}
76
+ groupRef={ref}
77
+ onChange={onChange}
78
+ rest={rest}
79
+ />
80
+
81
+ {helperText && (
82
+ <FormHelperText
83
+ error={error}
84
+ sx={{ mt: 1 }}
85
+ data-testid={testId(dataTestId, 'helper-text')}
86
+ >
87
+ {helperText}
88
+ </FormHelperText>
89
+ )}
90
+ </Box>
91
+ );
92
+ });
663
93
 
664
94
  RadioGroup.displayName = 'RadioGroup';