@12-apps/ui 1.4.0 → 1.5.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 (66) hide show
  1. package/eslint.config.js +12 -0
  2. package/package.json +5 -4
  3. package/src/components/data-display/AlertDialog/AlertDialog.test.stories.tsx +16 -6
  4. package/src/components/data-display/Banner/Banner.test.stories.tsx +26 -18
  5. package/src/components/data-display/Carousel/Carousel.chrome.tsx +83 -0
  6. package/src/components/data-display/Carousel/Carousel.hooks.ts +88 -0
  7. package/src/components/data-display/Carousel/Carousel.parts.tsx +346 -0
  8. package/src/components/data-display/Carousel/Carousel.slide.tsx +190 -0
  9. package/src/components/data-display/Carousel/Carousel.styles.ts +86 -0
  10. package/src/components/data-display/Carousel/Carousel.test.stories.tsx +15 -5
  11. package/src/components/data-display/Carousel/Carousel.tsx +84 -732
  12. package/src/components/data-display/HoverCard/HoverCard.animations.ts +73 -0
  13. package/src/components/data-display/HoverCard/HoverCard.content.tsx +143 -0
  14. package/src/components/data-display/HoverCard/HoverCard.hooks.ts +180 -0
  15. package/src/components/data-display/HoverCard/HoverCard.styles.ts +178 -0
  16. package/src/components/data-display/HoverCard/HoverCard.tsx +117 -631
  17. package/src/components/data-display/Sheet/Sheet.animations.ts +44 -0
  18. package/src/components/data-display/Sheet/Sheet.body.tsx +124 -0
  19. package/src/components/data-display/Sheet/Sheet.drag.ts +203 -0
  20. package/src/components/data-display/Sheet/Sheet.hooks.ts +318 -0
  21. package/src/components/data-display/Sheet/Sheet.parts.tsx +224 -0
  22. package/src/components/data-display/Sheet/Sheet.styles.ts +257 -0
  23. package/src/components/data-display/Sheet/Sheet.test.stories.tsx +25 -21
  24. package/src/components/data-display/Sheet/Sheet.tsx +125 -948
  25. package/src/components/feedback/Dialog/Dialog.test.stories.tsx +37 -28
  26. package/src/components/feedback/Modal/Modal.styles.ts +126 -0
  27. package/src/components/feedback/Modal/Modal.test.stories.tsx +5 -5
  28. package/src/components/feedback/Modal/Modal.transitions.tsx +40 -0
  29. package/src/components/feedback/Modal/Modal.tsx +37 -242
  30. package/src/components/feedback/StackedModal/StackedModal.test.stories.tsx +9 -11
  31. package/src/components/form/InputOTP/InputOTP.hooks.ts +104 -0
  32. package/src/components/form/InputOTP/InputOTP.styles.ts +97 -0
  33. package/src/components/form/InputOTP/InputOTP.tsx +74 -221
  34. package/src/components/form/Label/Label.parts.tsx +75 -0
  35. package/src/components/form/Label/Label.styles.ts +206 -0
  36. package/src/components/form/Label/Label.test.stories.tsx +20 -14
  37. package/src/components/form/Label/Label.tsx +73 -325
  38. package/src/components/form/Menubar/Menubar.hooks.ts +41 -0
  39. package/src/components/form/Menubar/Menubar.item.tsx +121 -0
  40. package/src/components/form/Menubar/Menubar.separator.tsx +25 -0
  41. package/src/components/form/Menubar/Menubar.styles.ts +110 -0
  42. package/src/components/form/Menubar/Menubar.toolbar.tsx +103 -0
  43. package/src/components/form/Menubar/Menubar.tsx +64 -347
  44. package/src/components/form/RadioGroup/RadioGroup.animations.ts +48 -0
  45. package/src/components/form/RadioGroup/RadioGroup.styles.ts +305 -0
  46. package/src/components/form/RadioGroup/RadioGroup.tsx +81 -651
  47. package/src/components/form/RadioGroup/RadioGroup.variants.tsx +291 -0
  48. package/src/components/form/Slider/Slider.animations.ts +40 -0
  49. package/src/components/form/Slider/Slider.styles.ts +292 -0
  50. package/src/components/form/Slider/Slider.tsx +106 -357
  51. package/src/components/form/Switch/Switch.animations.ts +62 -0
  52. package/src/components/form/Switch/Switch.parts.tsx +213 -0
  53. package/src/components/form/Switch/Switch.styles.ts +355 -0
  54. package/src/components/form/Switch/Switch.tsx +87 -566
  55. package/src/components/form/ToggleGroup/ToggleGroup.test.stories.tsx +26 -29
  56. package/src/components/layout/Resizable/Resizable.test.stories.tsx +11 -7
  57. package/src/components/layout/Spacer/Spacer.test.stories.tsx +11 -9
  58. package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +7 -7
  59. package/src/components/typography/Heading/Heading.styles.ts +101 -0
  60. package/src/components/typography/Heading/Heading.tsx +48 -164
  61. package/src/components/typography/Text/Text.test.stories.tsx +27 -30
  62. package/src/components/utility/AnimatedIcon/AnimatedIcon.animations.ts +274 -0
  63. package/src/components/utility/AnimatedIcon/AnimatedIcon.styles.ts +231 -0
  64. package/src/components/utility/AnimatedIcon/AnimatedIcon.tsx +39 -559
  65. package/src/utils/index.ts +1 -0
  66. package/src/utils/withDefaults.ts +20 -0
@@ -1,562 +1,43 @@
1
- import { alpha, Box, keyframes, styled } from '@mui/material';
1
+ import { Box, styled } from '@mui/material';
2
2
  import type { FC } from 'react';
3
-
4
- import type { AnimatedIconProps, AnimationSize,AnimationVariant } from './AnimatedIcon.types';
5
-
6
- // Animation keyframes
7
- const rotateAnimation = keyframes`
8
- from {
9
- transform: rotate(0deg);
10
- }
11
- to {
12
- transform: rotate(360deg);
13
- }
14
- `;
15
-
16
- const pulseAnimation = keyframes`
17
- 0% {
18
- transform: scale(1);
19
- opacity: 1;
20
- }
21
- 50% {
22
- transform: scale(1.1);
23
- opacity: 0.8;
24
- }
25
- 100% {
26
- transform: scale(1);
27
- opacity: 1;
28
- }
29
- `;
30
-
31
- const translateAnimation = keyframes`
32
- 0% {
33
- transform: translateY(0px);
34
- }
35
- 50% {
36
- transform: translateY(-8px);
37
- }
38
- 100% {
39
- transform: translateY(0px);
40
- }
41
- `;
42
-
43
- const glowPulseAnimation = keyframes`
44
- 0% {
45
- box-shadow: 0 0 5px currentColor, 0 0 10px currentColor, 0 0 15px currentColor;
46
- }
47
- 50% {
48
- box-shadow: 0 0 10px currentColor, 0 0 20px currentColor, 0 0 30px currentColor;
49
- }
50
- 100% {
51
- box-shadow: 0 0 5px currentColor, 0 0 10px currentColor, 0 0 15px currentColor;
52
- }
53
- `;
54
-
55
- // New animation keyframes
56
- const bounceAnimation = keyframes`
57
- 0%, 20%, 50%, 80%, 100% {
58
- transform: translateY(0);
59
- }
60
- 40% {
61
- transform: translateY(-20px);
62
- }
63
- 60% {
64
- transform: translateY(-10px);
65
- }
66
- `;
67
-
68
- const shakeAnimation = keyframes`
69
- 0%, 100% {
70
- transform: translateX(0);
71
- }
72
- 10%, 30%, 50%, 70%, 90% {
73
- transform: translateX(-4px);
74
- }
75
- 20%, 40%, 60%, 80% {
76
- transform: translateX(4px);
77
- }
78
- `;
79
-
80
- const flipAnimation = keyframes`
81
- 0% {
82
- transform: perspective(400px) rotateY(0);
83
- }
84
- 100% {
85
- transform: perspective(400px) rotateY(360deg);
86
- }
87
- `;
88
-
89
- const spinAnimation = keyframes`
90
- 0% {
91
- transform: rotate(0deg) scale(1);
92
- }
93
- 50% {
94
- transform: rotate(180deg) scale(1.2);
95
- }
96
- 100% {
97
- transform: rotate(360deg) scale(1);
98
- }
99
- `;
100
-
101
- const fadeInOutAnimation = keyframes`
102
- 0%, 100% {
103
- opacity: 0.3;
104
- }
105
- 50% {
106
- opacity: 1;
107
- }
108
- `;
109
-
110
- const heartbeatAnimation = keyframes`
111
- 0%, 100% {
112
- transform: scale(1);
113
- }
114
- 5% {
115
- transform: scale(1.25);
116
- }
117
- 10% {
118
- transform: scale(1);
119
- }
120
- 15% {
121
- transform: scale(1.25);
122
- }
123
- 20% {
124
- transform: scale(1);
125
- }
126
- `;
127
-
128
- const wobbleAnimation = keyframes`
129
- 0%, 100% {
130
- transform: translateX(0) rotate(0deg);
131
- }
132
- 15% {
133
- transform: translateX(-10px) rotate(-5deg);
134
- }
135
- 30% {
136
- transform: translateX(8px) rotate(3deg);
137
- }
138
- 45% {
139
- transform: translateX(-6px) rotate(-3deg);
140
- }
141
- 60% {
142
- transform: translateX(4px) rotate(2deg);
143
- }
144
- 75% {
145
- transform: translateX(-2px) rotate(-1deg);
146
- }
147
- `;
148
-
149
- const morphAnimation = keyframes`
150
- 0%, 100% {
151
- border-radius: 50%;
152
- transform: scale(1);
153
- }
154
- 25% {
155
- border-radius: 30%;
156
- transform: scale(1.1);
157
- }
158
- 50% {
159
- border-radius: 20%;
160
- transform: scale(0.95);
161
- }
162
- 75% {
163
- border-radius: 40%;
164
- transform: scale(1.05);
165
- }
166
- `;
167
-
168
- const swingAnimation = keyframes`
169
- 20% {
170
- transform: rotate(15deg);
171
- }
172
- 40% {
173
- transform: rotate(-10deg);
174
- }
175
- 60% {
176
- transform: rotate(5deg);
177
- }
178
- 80% {
179
- transform: rotate(-5deg);
180
- }
181
- 100% {
182
- transform: rotate(0deg);
183
- }
184
- `;
185
-
186
- const floatAnimation = keyframes`
187
- 0%, 100% {
188
- transform: translateY(0) translateX(0);
189
- }
190
- 33% {
191
- transform: translateY(-10px) translateX(-5px);
192
- }
193
- 66% {
194
- transform: translateY(5px) translateX(5px);
195
- }
196
- `;
197
-
198
- const jelloAnimation = keyframes`
199
- 0%, 100% {
200
- transform: scale(1, 1);
201
- }
202
- 25% {
203
- transform: scale(0.9, 1.1);
204
- }
205
- 50% {
206
- transform: scale(1.1, 0.9);
207
- }
208
- 75% {
209
- transform: scale(0.95, 1.05);
210
- }
211
- `;
212
-
213
- const rippleAnimation = keyframes`
214
- 0% {
215
- transform: scale(0.8);
216
- opacity: 1;
217
- }
218
- 100% {
219
- transform: scale(2);
220
- opacity: 0;
221
- }
222
- `;
223
-
224
- const neonFlickerAnimation = keyframes`
225
- 0%, 100% {
226
- opacity: 1;
227
- filter: brightness(1) drop-shadow(0 0 10px currentColor);
228
- }
229
- 10% {
230
- opacity: 0.8;
231
- filter: brightness(0.8) drop-shadow(0 0 5px currentColor);
232
- }
233
- 20% {
234
- opacity: 1;
235
- filter: brightness(1.2) drop-shadow(0 0 15px currentColor);
236
- }
237
- 30% {
238
- opacity: 0.9;
239
- filter: brightness(0.9) drop-shadow(0 0 8px currentColor);
240
- }
241
- 40% {
242
- opacity: 1;
243
- filter: brightness(1.1) drop-shadow(0 0 12px currentColor);
244
- }
245
- 50% {
246
- opacity: 0.95;
247
- filter: brightness(1) drop-shadow(0 0 10px currentColor);
248
- }
249
- 60% {
250
- opacity: 0.85;
251
- filter: brightness(0.85) drop-shadow(0 0 6px currentColor);
252
- }
253
- 70% {
254
- opacity: 1;
255
- filter: brightness(1.15) drop-shadow(0 0 14px currentColor);
256
- }
257
- 80% {
258
- opacity: 0.9;
259
- filter: brightness(0.95) drop-shadow(0 0 9px currentColor);
260
- }
261
- 90% {
262
- opacity: 1;
263
- filter: brightness(1.05) drop-shadow(0 0 11px currentColor);
264
- }
265
- `;
266
-
267
- const breatheAnimation = keyframes`
268
- 0%, 100% {
269
- transform: scale(1);
270
- filter: brightness(1);
271
- }
272
- 50% {
273
- transform: scale(1.05);
274
- filter: brightness(1.2);
275
- }
276
- `;
277
-
278
- // Size configurations
279
- const sizeConfigs: Record<AnimationSize, { size: number; fontSize: number }> = {
280
- sm: { size: 24, fontSize: 20 },
281
- md: { size: 32, fontSize: 28 },
282
- lg: { size: 48, fontSize: 44 },
283
- xl: { size: 64, fontSize: 60 },
284
- };
285
-
286
- // Styled components
287
- const AnimationContainer = styled(Box)<{
288
- $size: number;
289
- $fontSize: number;
290
- $animationVariant: AnimationVariant;
291
- $duration: number;
292
- $delay: number;
293
- $loop: boolean;
294
- $glow: boolean;
295
- $glass: boolean;
296
- $glowColor?: string;
297
- $customColor?: string;
298
- $metallic?: boolean;
299
- $gradient?: boolean;
300
- $shadow?: 'soft' | 'hard' | 'elevated' | 'none';
301
- $ripple?: boolean;
302
- $neon?: boolean;
303
- $holographic?: boolean;
304
- }>(({
305
- theme,
306
- $size,
307
- $fontSize,
308
- $animationVariant,
309
- $duration,
310
- $delay,
311
- $loop,
312
- $glow,
313
- $glass,
314
- $glowColor,
315
- $customColor,
316
- $metallic,
317
- $gradient,
318
- $shadow,
319
- $ripple,
320
- $neon,
321
- $holographic,
322
- }) => {
323
- const getAnimation = () => {
324
- const iterationCount = $loop ? 'infinite' : '1';
325
- const animationDelay = $delay > 0 ? `${$delay}s` : '0s';
326
-
327
- switch ($animationVariant) {
328
- case 'rotate':
329
- return `${rotateAnimation} ${$duration}s linear ${iterationCount} ${animationDelay}`;
330
- case 'pulse':
331
- return `${pulseAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
332
- case 'translate':
333
- return `${translateAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
334
- case 'bounce':
335
- return `${bounceAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
336
- case 'shake':
337
- return `${shakeAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
338
- case 'flip':
339
- return `${flipAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
340
- case 'spin':
341
- return `${spinAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
342
- case 'fadeInOut':
343
- return `${fadeInOutAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
344
- case 'heartbeat':
345
- return `${heartbeatAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
346
- case 'wobble':
347
- return `${wobbleAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
348
- case 'morph':
349
- return `${morphAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
350
- case 'swing':
351
- return `${swingAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
352
- case 'float':
353
- return `${floatAnimation} ${$duration * 2}s ease-in-out ${iterationCount} ${animationDelay}`;
354
- case 'jello':
355
- return `${jelloAnimation} ${$duration}s ease-in-out ${iterationCount} ${animationDelay}`;
356
- case 'neonFlicker':
357
- return `${neonFlickerAnimation} ${$duration * 2}s ease-in-out ${iterationCount} ${animationDelay}`;
358
- case 'breathe':
359
- return `${breatheAnimation} ${$duration * 1.5}s ease-in-out ${iterationCount} ${animationDelay}`;
360
- default:
361
- return 'none';
362
- }
363
- };
364
-
365
- const getColor = () => {
366
- if ($customColor) return $customColor;
367
- return theme.palette.primary.main;
368
- };
369
-
370
- const getGlowColor = () => {
371
- if ($glowColor) return $glowColor;
372
- return getColor();
373
- };
374
-
375
- const getShadow = () => {
376
- switch ($shadow) {
377
- case 'soft':
378
- return `0 4px 20px ${alpha(theme.palette.common.black, 0.15)}`;
379
- case 'hard':
380
- return `4px 4px 0px ${alpha(theme.palette.common.black, 0.25)}`;
381
- case 'elevated':
382
- return `0 10px 40px ${alpha(theme.palette.common.black, 0.2)}, 0 2px 10px ${alpha(theme.palette.common.black, 0.1)}`;
383
- default:
384
- return 'none';
385
- }
386
- };
387
-
388
- const baseStyles = {
389
- display: 'inline-flex',
390
- alignItems: 'center',
391
- justifyContent: 'center',
392
- width: $size,
393
- height: $size,
394
- color: getColor(),
395
- animation: getAnimation(),
396
- position: 'relative' as const,
397
- cursor: 'default',
398
- borderRadius: '50%',
399
- transition: 'all 0.3s ease',
400
- boxShadow: getShadow(),
401
- };
402
-
403
- const glowStyles = $glow
404
- ? {
405
- '&::before': {
406
- content: '""',
407
- position: 'absolute' as const,
408
- inset: -8,
409
- borderRadius: '50%',
410
- background: `radial-gradient(circle, ${alpha(getGlowColor(), 0.3)} 0%, transparent 70%)`,
411
- animation: `${glowPulseAnimation} ${$duration * 1.2}s ease-in-out infinite`,
412
- zIndex: -1,
413
- },
414
- filter: `drop-shadow(0 0 8px ${alpha(getGlowColor(), 0.5)})`,
415
- }
416
- : {};
417
-
418
- const glassStyles = $glass
419
- ? {
420
- background: `linear-gradient(135deg, ${alpha(theme.palette.background.paper, 0.2)}, ${alpha(theme.palette.background.paper, 0.1)})`,
421
- backdropFilter: 'blur(10px)',
422
- border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
423
- boxShadow: `inset 0 1px 1px ${alpha(theme.palette.common.white, 0.3)}, 0 8px 32px ${alpha(theme.palette.common.black, 0.1)}`,
424
- }
425
- : {};
426
-
427
- const metallicStyles = $metallic
428
- ? {
429
- background: `linear-gradient(145deg,
430
- ${theme.palette.grey[300]} 0%,
431
- ${theme.palette.grey[500]} 25%,
432
- ${theme.palette.grey[400]} 50%,
433
- ${theme.palette.grey[600]} 75%,
434
- ${theme.palette.grey[400]} 100%)`,
435
- backgroundSize: '200% 200%',
436
- animation: `${getAnimation()}, ${breatheAnimation} 4s ease-in-out infinite`,
437
- boxShadow: `inset 0 2px 4px ${alpha(theme.palette.common.white, 0.5)},
438
- inset 0 -2px 4px ${alpha(theme.palette.common.black, 0.3)},
439
- 0 4px 8px ${alpha(theme.palette.common.black, 0.2)}`,
440
- }
441
- : {};
442
-
443
- const gradientStyles = $gradient
444
- ? {
445
- background: `linear-gradient(135deg,
446
- ${theme.palette.primary.main} 0%,
447
- ${theme.palette.secondary.main} 100%)`,
448
- backgroundSize: '200% 200%',
449
- animation: `${getAnimation()}, ${breatheAnimation} 3s ease-in-out infinite`,
450
- }
451
- : {};
452
-
453
- const rippleStyles = $ripple
454
- ? {
455
- '&::after': {
456
- content: '""',
457
- position: 'absolute' as const,
458
- inset: 0,
459
- borderRadius: '50%',
460
- border: `2px solid ${getColor()}`,
461
- animation: `${rippleAnimation} ${$duration * 1.5}s ease-out infinite`,
462
- },
463
- }
464
- : {};
465
-
466
- const neonStyles = $neon
467
- ? {
468
- color: getColor(),
469
- textShadow: `0 0 10px ${getColor()}, 0 0 20px ${getColor()}, 0 0 30px ${getColor()}`,
470
- filter: `brightness(1.2) contrast(1.2)`,
471
- animation: `${getAnimation()}, ${neonFlickerAnimation} 3s ease-in-out infinite`,
472
- }
473
- : {};
474
-
475
- const holographicStyles = $holographic
476
- ? {
477
- background: `linear-gradient(45deg,
478
- ${theme.palette.primary.main} 0%,
479
- ${theme.palette.secondary.main} 25%,
480
- ${theme.palette.error.main} 50%,
481
- ${theme.palette.warning.main} 75%,
482
- ${theme.palette.primary.main} 100%)`,
483
- backgroundSize: '400% 400%',
484
- animation: `${getAnimation()}, ${floatAnimation} 6s ease-in-out infinite`,
485
- backgroundClip: 'text',
486
- WebkitBackgroundClip: 'text',
487
- WebkitTextFillColor: 'transparent',
488
- filter: `hue-rotate(0deg)`,
489
- '@keyframes hueRotate': {
490
- '0%': { filter: 'hue-rotate(0deg)' },
491
- '100%': { filter: 'hue-rotate(360deg)' },
492
- },
493
- '&::before': {
494
- content: '""',
495
- position: 'absolute' as const,
496
- inset: -2,
497
- borderRadius: '50%',
498
- background: 'inherit',
499
- filter: 'blur(10px)',
500
- opacity: 0.5,
501
- zIndex: -1,
502
- },
503
- }
504
- : {};
505
-
506
- return {
507
- ...baseStyles,
508
- ...glowStyles,
509
- ...glassStyles,
510
- ...metallicStyles,
511
- ...gradientStyles,
512
- ...rippleStyles,
513
- ...neonStyles,
514
- ...holographicStyles,
515
- '& .MuiSvgIcon-root': {
516
- fontSize: $fontSize,
517
- width: $fontSize,
518
- height: $fontSize,
519
- },
520
- '& svg': {
521
- fontSize: $fontSize,
522
- width: $fontSize,
523
- height: $fontSize,
524
- },
525
- '&:hover': {
526
- transform: 'scale(1.05)',
527
- filter: $neon ? `brightness(1.4) contrast(1.3)` : undefined,
528
- },
529
- };
530
- });
531
-
532
- // Main component
533
- export const AnimatedIcon: FC<AnimatedIconProps> = ({
534
- children,
535
- variant = 'none',
536
- size = 'md',
537
- color,
538
- duration = 2,
539
- delay = 0,
540
- loop = true,
541
- glow = false,
542
- glass = false,
543
- glowColor,
544
- metallic = false,
545
- gradient = false,
546
- shadow = 'none',
547
- ripple = false,
548
- neon = false,
549
- holographic = false,
550
- className,
551
- style,
552
- 'aria-label': ariaLabel,
553
- onClick,
554
- tabIndex,
555
- onFocus,
556
- onBlur,
557
- dataTestId,
558
- }) => {
559
- const config = sizeConfigs[size];
3
+ import React from 'react';
4
+
5
+ import { withDefaults } from '../../../utils/withDefaults';
6
+
7
+ import { iconSx, sizeConfigs } from './AnimatedIcon.styles';
8
+ import type { IconStyleFlags } from './AnimatedIcon.styles';
9
+ import type { AnimatedIconProps } from './AnimatedIcon.types';
10
+
11
+ const AnimationContainer = styled(Box)<IconStyleFlags>(({ theme, ...flags }) => ({
12
+ ...iconSx(theme, flags),
13
+ }));
14
+
15
+ const DEFAULTS = {
16
+ variant: 'none',
17
+ size: 'md',
18
+ duration: 2,
19
+ delay: 0,
20
+ loop: true,
21
+ glow: false,
22
+ glass: false,
23
+ metallic: false,
24
+ gradient: false,
25
+ shadow: 'none',
26
+ ripple: false,
27
+ neon: false,
28
+ holographic: false,
29
+ } satisfies Partial<AnimatedIconProps>;
30
+
31
+ type ResolvedProps = AnimatedIconProps & Required<Pick<AnimatedIconProps, keyof typeof DEFAULTS>>;
32
+
33
+ export const AnimatedIcon: FC<AnimatedIconProps> = (props) => {
34
+ const {
35
+ children, variant, size, color, duration, delay, loop, glow, glass, glowColor,
36
+ metallic, gradient, shadow, ripple, neon, holographic, className, style,
37
+ 'aria-label': ariaLabel, onClick, tabIndex, onFocus, onBlur, dataTestId,
38
+ } = withDefaults(props, DEFAULTS) as ResolvedProps;
39
+
40
+ const config = sizeConfigs[size] ?? sizeConfigs.md;
560
41
 
561
42
  return (
562
43
  <AnimationContainer
@@ -591,5 +72,4 @@ export const AnimatedIcon: FC<AnimatedIconProps> = ({
591
72
  );
592
73
  };
593
74
 
594
- // Export default
595
75
  export default AnimatedIcon;
@@ -1,2 +1,3 @@
1
1
  export * from './cn';
2
2
  export * from './viewport';
3
+ export * from './withDefaults';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Fills in the defaults for props the caller left `undefined`.
3
+ *
4
+ * A table plus one loop, rather than a `= default` per destructured prop. Each
5
+ * of those defaults is a branch, and a component with fifteen of them is over
6
+ * the cyclomatic bar before it renders anything.
7
+ *
8
+ * Only `undefined` is replaced, so an explicit `prop={undefined}` still falls
9
+ * back to the default exactly as a destructuring default would, while an
10
+ * explicit `null`, `0`, `''` or `false` is preserved as the caller's choice.
11
+ */
12
+ export function withDefaults<T extends object>(props: T, defaults: Partial<T>): T {
13
+ const resolved = { ...props };
14
+ for (const [key, value] of Object.entries(defaults)) {
15
+ if (resolved[key as keyof T] === undefined) {
16
+ Object.assign(resolved, { [key]: value });
17
+ }
18
+ }
19
+ return resolved;
20
+ }