@12-apps/ui 1.4.0 → 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,578 +1,99 @@
1
- import type {
2
- Theme} from '@mui/material';
3
- import {
4
- alpha,
5
- Box,
6
- FormHelperText,
7
- keyframes,
8
- Switch as MuiSwitch,
9
- Typography,
10
- } from '@mui/material';
11
- import { styled } from '@mui/material/styles';
1
+ import { Box } from '@mui/material';
12
2
  import React, { forwardRef } from 'react';
13
3
 
14
- import type { SwitchProps } from './Switch.types';
15
-
16
- const glowAnimation = keyframes`
17
- 0% {
18
- box-shadow: 0 0 5px currentColor;
19
- }
20
- 50% {
21
- box-shadow: 0 0 15px currentColor, 0 0 25px currentColor;
22
- }
23
- 100% {
24
- box-shadow: 0 0 5px currentColor;
25
- }
26
- `;
27
-
28
- const bounceAnimation = keyframes`
29
- 0%, 100% {
30
- transform: scale(1);
31
- }
32
- 50% {
33
- transform: scale(1.1);
34
- }
35
- `;
36
-
37
- const pulseAnimation = keyframes`
38
- 0%, 100% {
39
- opacity: 1;
40
- transform: scale(1);
41
- }
42
- 50% {
43
- opacity: 0.7;
44
- transform: scale(0.95);
45
- }
46
- `;
4
+ import { withDefaults } from '../../../utils/withDefaults';
47
5
 
48
- const rippleAnimation = keyframes`
49
- 0% {
50
- transform: scale(0);
51
- opacity: 1;
52
- }
53
- 100% {
54
- transform: scale(2);
55
- opacity: 0;
56
- }
57
- `;
58
-
59
- const spinAnimation = keyframes`
60
- 0% {
61
- transform: rotate(0deg);
62
- }
63
- 100% {
64
- transform: rotate(360deg);
65
- }
66
- `;
67
-
68
- const shimmerAnimation = keyframes`
69
- 0% {
70
- background-position: -200% 50%;
71
- }
72
- 100% {
73
- background-position: 200% 50%;
74
- }
75
- `;
76
-
77
- interface ThemePalette {
78
- main: string;
79
- dark?: string;
80
- light?: string;
81
- contrastText?: string;
82
- }
83
-
84
- const getColorFromTheme = (theme: Theme, color: string) => {
85
- if (color === 'neutral') {
86
- return {
87
- main: theme.palette.grey?.[700] || '#616161',
88
- dark: theme.palette.grey?.[800] || '#424242',
89
- light: theme.palette.grey?.[500] || '#9e9e9e',
90
- contrastText: '#fff',
91
- };
92
- }
93
-
94
- const colorMap: Record<string, ThemePalette> = {
95
- primary: theme.palette.primary,
96
- secondary: theme.palette.secondary,
97
- success: theme.palette.success,
98
- warning: theme.palette.warning,
99
- danger: theme.palette.error,
100
- };
101
-
102
- const palette = colorMap[color] || theme.palette.primary;
103
-
104
- // Ensure palette has required properties
105
- return {
106
- main: palette?.main || theme.palette.primary.main,
107
- dark: palette?.dark || palette?.main || theme.palette.primary.dark,
108
- light: palette?.light || palette?.main || theme.palette.primary.light,
109
- contrastText: palette?.contrastText || '#fff',
110
- };
111
- };
112
-
113
- const StyledSwitch = styled(MuiSwitch, {
114
- shouldForwardProp: (prop) =>
115
- ![
116
- 'customVariant',
117
- 'customColor',
118
- 'customSize',
119
- 'glow',
120
- 'glass',
121
- 'gradient',
122
- 'trackWidth',
123
- 'trackHeight',
124
- 'onText',
125
- 'offText',
126
- 'loading',
127
- 'ripple',
128
- 'pulse',
129
- ].includes(prop as string),
130
- })<{
131
- customVariant?: string;
132
- customColor?: string;
133
- customSize?: string;
134
- glow?: boolean;
135
- glass?: boolean;
136
- gradient?: boolean;
137
- trackWidth?: number;
138
- trackHeight?: number;
139
- onText?: string;
140
- offText?: string;
141
- loading?: boolean;
142
- ripple?: boolean;
143
- pulse?: boolean;
144
- }>(({
145
- theme,
146
- customVariant,
147
- customColor = 'primary',
148
- customSize = 'md',
149
- glow,
150
- glass,
151
- gradient,
152
- trackWidth,
153
- trackHeight,
154
- onText,
155
- offText,
156
- loading,
157
- ripple,
158
- pulse,
159
- }) => {
160
- const colorPalette = getColorFromTheme(theme, customColor);
161
-
162
- const sizeMap = {
163
- xs: { width: 34, height: 18, padding: 1, thumbSize: 14 },
164
- sm: { width: 42, height: 22, padding: 1, thumbSize: 18 },
165
- md: { width: 50, height: 26, padding: 1, thumbSize: 22 },
166
- lg: { width: 58, height: 30, padding: 2, thumbSize: 24 },
167
- xl: { width: 66, height: 34, padding: 2, thumbSize: 28 },
168
- };
169
-
170
- const currentSize = sizeMap[customSize as keyof typeof sizeMap];
171
- const width = trackWidth || currentSize.width;
172
- const height = trackHeight || currentSize.height;
173
- const thumbSize = currentSize.thumbSize;
174
-
175
- const iosVariant = customVariant === 'ios';
176
- const androidVariant = customVariant === 'android';
177
- const labelVariant = customVariant === 'label';
178
- const materialVariant = customVariant === 'material';
179
-
180
- return {
181
- width,
182
- height,
183
- padding: 0,
184
- overflow: 'visible',
185
- '& .MuiSwitch-switchBase': {
186
- padding: currentSize.padding,
187
- margin: 0,
188
- transitionDuration: '300ms',
189
- transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
190
- ...(iosVariant && {
191
- transform: 'translateX(2px)',
192
- }),
193
- '&:hover': {
194
- '& .MuiSwitch-thumb': {
195
- transform: loading ? 'none' : 'scale(1.05)',
196
- boxShadow: `${theme.shadows[4]}, 0 0 12px ${alpha(colorPalette.main, 0.2)}`,
197
- },
198
- ...(ripple && {
199
- '&::after': {
200
- content: '""',
201
- position: 'absolute',
202
- top: '50%',
203
- left: '50%',
204
- width: '100%',
205
- height: '100%',
206
- borderRadius: '50%',
207
- background: alpha(colorPalette.main, 0.2),
208
- animation: `${rippleAnimation} 0.6s ease-out`,
209
- transform: 'translate(-50%, -50%)',
210
- pointerEvents: 'none',
211
- },
212
- }),
213
- },
214
- '&.Mui-checked': {
215
- transform: `translateX(${width - thumbSize - currentSize.padding * 2}px)`,
216
- color: '#fff',
217
- '& .MuiSwitch-thumb': {
218
- animation: loading ? 'none' : `${bounceAnimation} 0.3s ease-out`,
219
- },
220
- '& + .MuiSwitch-track': {
221
- backgroundColor: gradient ? colorPalette.main : colorPalette.main,
222
- opacity: 1,
223
- border: 0,
224
- position: 'relative',
225
- overflow: 'hidden',
226
- ...(glow && {
227
- animation: `${glowAnimation} 2s ease-in-out infinite`,
228
- boxShadow: `0 0 10px ${alpha(colorPalette.main, 0.6)}, inset 0 0 10px ${alpha(colorPalette.main, 0.2)}`,
229
- }),
230
- ...(gradient && {
231
- background: `linear-gradient(90deg, ${colorPalette.light || colorPalette.main} 0%, ${colorPalette.main} 50%, ${colorPalette.dark || colorPalette.main} 100%)`,
232
- backgroundSize: '200% 100%',
233
- animation: `${shimmerAnimation} 3s ease infinite`,
234
- }),
235
- },
236
- '&.Mui-disabled + .MuiSwitch-track': {
237
- opacity: 0.5,
238
- },
239
- ...(iosVariant && {
240
- transform: `translateX(${width - thumbSize - 4}px)`,
241
- }),
242
- },
243
- '&.Mui-focusVisible .MuiSwitch-thumb': {
244
- color: colorPalette.main,
245
- border: `6px solid ${alpha(colorPalette.main, 0.2)}`,
246
- },
247
- '&.Mui-disabled .MuiSwitch-thumb': {
248
- color: theme.palette.grey[100],
249
- },
250
- '&.Mui-disabled + .MuiSwitch-track': {
251
- opacity: 0.3,
252
- },
253
- },
254
- '& .MuiSwitch-thumb': {
255
- width: thumbSize,
256
- height: thumbSize,
257
- borderRadius: iosVariant
258
- ? '50%'
259
- : androidVariant
260
- ? 4
261
- : materialVariant
262
- ? thumbSize / 3
263
- : '50%',
264
- backgroundColor: '#fff',
265
- boxShadow: iosVariant
266
- ? `0 3px 1px 0 ${alpha('#000', 0.04)}, 0 3px 8px 0 ${alpha('#000', 0.12)}, 0 1px 0 0 ${alpha('#000', 0.08)}`
267
- : androidVariant
268
- ? theme.shadows[3]
269
- : theme.shadows[2],
270
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
271
- position: 'relative',
272
- display: 'flex',
273
- alignItems: 'center',
274
- justifyContent: 'center',
275
- ...(glass && {
276
- backgroundColor: alpha(theme.palette.background.paper, 0.9),
277
- backdropFilter: 'blur(10px)',
278
- border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
279
- }),
280
- ...(pulse && {
281
- animation: `${pulseAnimation} 2s ease-in-out infinite`,
282
- }),
283
- ...(loading && {
284
- '&::after': {
285
- content: '""',
286
- position: 'absolute',
287
- width: thumbSize * 0.6,
288
- height: thumbSize * 0.6,
289
- border: `2px solid ${colorPalette.main}`,
290
- borderTop: `2px solid transparent`,
291
- borderRadius: '50%',
292
- animation: `${spinAnimation} 1s linear infinite`,
293
- },
294
- }),
295
- ...(materialVariant && {
296
- '&::before': {
297
- content: '""',
298
- position: 'absolute',
299
- width: '100%',
300
- height: '100%',
301
- borderRadius: 'inherit',
302
- backgroundColor: colorPalette.main,
303
- opacity: 0,
304
- transform: 'scale(0)',
305
- transition: 'all 0.3s ease',
306
- },
307
- }),
308
- },
309
- '& .MuiSwitch-track': {
310
- borderRadius: iosVariant
311
- ? height / 2
312
- : androidVariant
313
- ? height / 3
314
- : materialVariant
315
- ? height / 2.5
316
- : height / 2,
317
- backgroundColor: iosVariant
318
- ? alpha(theme.palette.common.black, 0.1)
319
- : androidVariant
320
- ? alpha(theme.palette.action.disabled, 0.2)
321
- : alpha(theme.palette.action.disabled, 0.3),
322
- opacity: 1,
323
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
324
- position: 'relative',
325
- boxShadow: iosVariant
326
- ? `inset 0 0 0 0.5px ${alpha('#000', 0.1)}, inset 0 2px 3px ${alpha('#000', 0.12)}`
327
- : 'none',
328
- ...(glass && {
329
- backgroundColor: alpha(theme.palette.background.paper, 0.1),
330
- backdropFilter: 'blur(20px)',
331
- border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
332
- }),
333
- ...(gradient &&
334
- !glass && {
335
- background: `linear-gradient(135deg, ${alpha(colorPalette.light || colorPalette.main, 0.3)}, ${alpha(colorPalette.main, 0.2)})`,
336
- }),
337
- ...(labelVariant &&
338
- (onText || offText) && {
339
- display: 'flex',
340
- alignItems: 'center',
341
- justifyContent: 'space-between',
342
- paddingLeft: theme.spacing(1),
343
- paddingRight: theme.spacing(1),
344
- fontSize: '0.75rem',
345
- fontWeight: 500,
346
- color: theme.palette.text.secondary,
347
- '&::before, &::after': {
348
- content: '""',
349
- position: 'absolute',
350
- fontSize: '0.75rem',
351
- fontWeight: 500,
352
- top: '50%',
353
- transform: 'translateY(-50%)',
354
- zIndex: 1,
355
- },
356
- ...(onText && {
357
- '&::before': {
358
- content: `"${onText}"`,
359
- left: theme.spacing(1),
360
- color: '#fff',
361
- },
362
- }),
363
- ...(offText && {
364
- '&::after': {
365
- content: `"${offText}"`,
366
- right: theme.spacing(1),
367
- color: theme.palette.text.secondary,
368
- },
369
- }),
370
- }),
371
- },
372
- };
373
- });
374
-
375
- const LabelContainer = styled(Box, {
376
- shouldForwardProp: (prop) => !['labelPosition', 'error'].includes(prop as string),
377
- })<{ labelPosition?: string; error?: boolean }>(({ theme, labelPosition, error }) => ({
378
- display: 'flex',
379
- alignItems: labelPosition === 'top' || labelPosition === 'bottom' ? 'flex-start' : 'center',
380
- flexDirection:
381
- labelPosition === 'top' ? 'column' : labelPosition === 'bottom' ? 'column-reverse' : 'row',
382
- gap: theme.spacing(labelPosition === 'top' || labelPosition === 'bottom' ? 1 : 2),
383
- width: '100%',
384
- ...(error && {
385
- '& .MuiTypography-root': {
386
- color: theme.palette.error.main,
387
- },
388
- }),
389
- }));
390
-
391
- export const Switch = forwardRef<HTMLButtonElement, SwitchProps>(
392
- (
393
- {
394
- variant = 'default',
395
- color = 'primary',
396
- size = 'md',
397
- label,
398
- description,
399
- glow = false,
400
- glass = false,
401
- gradient = false,
402
- labelPosition = 'end',
403
- onIcon,
404
- offIcon,
405
- onText,
406
- offText,
407
- error = false,
408
- helperText,
409
- trackWidth,
410
- trackHeight,
411
- checked,
412
- onChange,
413
- animated = true,
414
- loading = false,
415
- ripple = false,
416
- pulse = false,
417
- dataTestId,
418
- ...props
419
- },
420
- ref,
421
- ) => {
422
- const switchElement = (
423
- <Box sx={{ position: 'relative', display: 'inline-flex' }}>
424
- <StyledSwitch
425
- ref={ref}
426
- customVariant={variant}
427
- customColor={color}
428
- customSize={size}
429
- glow={glow}
430
- glass={glass}
431
- gradient={gradient}
432
- trackWidth={trackWidth}
433
- trackHeight={trackHeight}
434
- onText={onText}
435
- offText={offText}
436
- checked={checked}
437
- onChange={onChange}
438
- loading={loading}
439
- ripple={ripple}
440
- pulse={pulse}
441
- disabled={loading || props.disabled}
442
- inputProps={{
443
- 'aria-label': props['aria-label'],
444
- 'aria-describedby': props['aria-describedby'],
445
- ...props.inputProps,
446
- ...(dataTestId ? { 'data-testid': dataTestId } : { 'data-testid': 'switch' }),
447
- } as React.InputHTMLAttributes<HTMLInputElement>}
448
- {...props}
449
- />
6
+ import {
7
+ LabelContainer,
8
+ SwitchControl,
9
+ SwitchHelper,
10
+ SwitchLabel,
11
+ } from './Switch.parts';
12
+ import type { SwitchProps } from './Switch.types';
450
13
 
451
- {/* Icon overlays */}
452
- {(onIcon || offIcon) && (
453
- <>
454
- {onIcon && (
455
- <Box
456
- sx={{
457
- position: 'absolute',
458
- top: '50%',
459
- left: checked ? 4 : '50%',
460
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
461
- opacity: checked ? 1 : 0,
462
- transform:
463
- checked && animated
464
- ? 'translate(-50%, -50%) scale(1)'
465
- : 'translate(-50%, -50%) scale(0.8)',
466
- pointerEvents: 'none',
467
- zIndex: 2,
468
- color: '#fff',
469
- fontSize:
470
- size === 'xs'
471
- ? 12
472
- : size === 'sm'
473
- ? 14
474
- : size === 'md'
475
- ? 16
476
- : size === 'lg'
477
- ? 18
478
- : 20,
479
- }}
480
- >
481
- {onIcon}
482
- </Box>
483
- )}
484
- {offIcon && (
485
- <Box
486
- sx={{
487
- position: 'absolute',
488
- top: '50%',
489
- right: !checked ? 4 : '50%',
490
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
491
- opacity: !checked ? 1 : 0,
492
- transform:
493
- !checked && animated
494
- ? 'translate(50%, -50%) scale(1)'
495
- : 'translate(50%, -50%) scale(0.8)',
496
- pointerEvents: 'none',
497
- zIndex: 2,
498
- color: 'text.secondary',
499
- fontSize:
500
- size === 'xs'
501
- ? 12
502
- : size === 'sm'
503
- ? 14
504
- : size === 'md'
505
- ? 16
506
- : size === 'lg'
507
- ? 18
508
- : 20,
509
- }}
510
- >
511
- {offIcon}
512
- </Box>
513
- )}
514
- </>
515
- )}
14
+ const DEFAULTS = {
15
+ variant: 'default',
16
+ color: 'primary',
17
+ size: 'md',
18
+ glow: false,
19
+ glass: false,
20
+ gradient: false,
21
+ labelPosition: 'end',
22
+ error: false,
23
+ animated: true,
24
+ loading: false,
25
+ ripple: false,
26
+ pulse: false,
27
+ } satisfies Partial<SwitchProps>;
28
+
29
+ type ResolvedProps = SwitchProps & Required<Pick<SwitchProps, keyof typeof DEFAULTS>>;
30
+
31
+ export const Switch = forwardRef<HTMLButtonElement, SwitchProps>((props, ref) => {
32
+ const {
33
+ variant, color, size, label, description, glow, glass, gradient, labelPosition,
34
+ onIcon, offIcon, onText, offText, error, helperText, trackWidth, trackHeight,
35
+ checked, onChange, animated, loading, ripple, pulse, dataTestId,
36
+ ...rest
37
+ } = withDefaults(props, DEFAULTS) as ResolvedProps;
38
+
39
+ const containerTestId = dataTestId ? `${dataTestId}-container` : 'switch-container';
40
+
41
+ const control = (
42
+ <SwitchControl
43
+ customVariant={variant}
44
+ customColor={color}
45
+ customSize={size}
46
+ glow={glow}
47
+ glass={glass}
48
+ gradient={gradient}
49
+ trackWidth={trackWidth}
50
+ trackHeight={trackHeight}
51
+ onText={onText}
52
+ offText={offText}
53
+ loading={loading}
54
+ ripple={ripple}
55
+ pulse={pulse}
56
+ checked={checked}
57
+ onChange={onChange}
58
+ onIcon={onIcon}
59
+ offIcon={offIcon}
60
+ animated={animated}
61
+ size={size}
62
+ dataTestId={dataTestId}
63
+ switchRef={ref}
64
+ rest={rest}
65
+ />
66
+ );
67
+
68
+ const helper = <SwitchHelper helperText={helperText} error={error} dataTestId={dataTestId} />;
69
+
70
+ if (!label) {
71
+ return (
72
+ <Box data-testid={containerTestId}>
73
+ {control}
74
+ {helper}
516
75
  </Box>
517
76
  );
77
+ }
518
78
 
519
- if (!label) {
520
- return (
521
- <Box data-testid={dataTestId ? `${dataTestId}-container` : 'switch-container'}>
522
- {switchElement}
523
- {helperText && (
524
- <FormHelperText
525
- error={error}
526
- sx={{ mt: 1 }}
527
- data-testid={dataTestId ? `${dataTestId}-helper` : 'switch-helper'}
528
- >
529
- {helperText}
530
- </FormHelperText>
531
- )}
532
- </Box>
533
- );
534
- }
79
+ return (
80
+ <Box data-testid={containerTestId}>
81
+ <LabelContainer labelPosition={labelPosition} error={error}>
82
+ {labelPosition === 'start' && control}
535
83
 
536
- return (
537
- <Box data-testid={dataTestId ? `${dataTestId}-container` : 'switch-container'}>
538
- <LabelContainer labelPosition={labelPosition} error={error}>
539
- {labelPosition === 'start' && switchElement}
540
-
541
- <Box sx={{ flex: 1 }}>
542
- <Typography
543
- variant="body2"
544
- fontWeight={500}
545
- color={error ? 'error.main' : 'text.primary'}
546
- data-testid={dataTestId ? `${dataTestId}-label` : 'switch-label'}
547
- >
548
- {label}
549
- </Typography>
550
- {description && (
551
- <Typography
552
- variant="caption"
553
- color={error ? 'error.main' : 'text.secondary'}
554
- sx={{ display: 'block', mt: 0.5 }}
555
- >
556
- {description}
557
- </Typography>
558
- )}
559
- </Box>
84
+ <SwitchLabel
85
+ label={label}
86
+ description={description}
87
+ error={error}
88
+ dataTestId={dataTestId}
89
+ />
560
90
 
561
- {labelPosition !== 'start' && switchElement}
562
- </LabelContainer>
91
+ {labelPosition !== 'start' && control}
92
+ </LabelContainer>
563
93
 
564
- {helperText && (
565
- <FormHelperText
566
- error={error}
567
- sx={{ mt: 1 }}
568
- data-testid={dataTestId ? `${dataTestId}-helper` : 'switch-helper'}
569
- >
570
- {helperText}
571
- </FormHelperText>
572
- )}
573
- </Box>
574
- );
575
- },
576
- );
94
+ {helper}
95
+ </Box>
96
+ );
97
+ });
577
98
 
578
99
  Switch.displayName = 'Switch';
@@ -165,16 +165,16 @@ export const FocusManagement: Story = {
165
165
  const afterBtn = canvas.getByText('After Blockquote');
166
166
 
167
167
  // Start from before button
168
- beforeBtn.focus();
169
- await expect(beforeBtn).toHaveFocus();
168
+ await userEvent.click(beforeBtn);
169
+ await waitFor(() => expect(beforeBtn).toHaveFocus());
170
170
 
171
171
  // Tab to blockquote
172
172
  await userEvent.tab();
173
- await expect(blockquote).toHaveFocus();
173
+ await waitFor(() => expect(blockquote).toHaveFocus());
174
174
 
175
175
  // Tab to after button
176
176
  await userEvent.tab();
177
- await expect(afterBtn).toHaveFocus();
177
+ await waitFor(() => expect(afterBtn).toHaveFocus());
178
178
  },
179
179
  };
180
180
 
@@ -312,10 +312,10 @@ export const Performance: Story = {
312
312
 
313
313
  // Check that all items rendered
314
314
  for (let i = 1; i <= 10; i++) {
315
+ // Matched on the part that identifies the instance rather than the whole
316
+ // sentence, which would break on any wording change.
315
317
  await expect(
316
- canvas.getByText(
317
- `Performance test blockquote number ${i}. This tests rendering performance with multiple instances.`,
318
- ),
318
+ canvas.getByText(new RegExp(`Performance test blockquote number ${i}\\.`)),
319
319
  ).toBeInTheDocument();
320
320
  await expect(canvas.getByText(`— Author ${i}`)).toBeInTheDocument();
321
321
  }