@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
@@ -0,0 +1,190 @@
1
+ import { Box, Paper, Typography, useTheme } from '@mui/material';
2
+ import React from 'react';
3
+
4
+ import type { CarouselItem, CarouselProps } from './Carousel.types';
5
+
6
+ export interface CarouselSlideProps {
7
+ item: CarouselItem;
8
+ index: number;
9
+ isActive: boolean;
10
+ variant: NonNullable<CarouselProps['variant']>;
11
+ clickable: boolean;
12
+ onSelect: (item: CarouselItem, index: number) => void;
13
+ }
14
+
15
+ /**
16
+ * A slide's caption. Over an image it is white with a shadow so it stays legible
17
+ * against whatever the photograph happens to be; on a bare surface it uses the
18
+ * ordinary text colours.
19
+ */
20
+ const SlideCaption: React.FC<Pick<CarouselItem, 'title' | 'description' | 'content' | 'image'>> = ({
21
+ title,
22
+ description,
23
+ content,
24
+ image,
25
+ }) => (
26
+ <Box sx={{ position: 'relative', zIndex: 1, textAlign: 'center', p: 3 }}>
27
+ {title && (
28
+ <Typography
29
+ variant="h5"
30
+ sx={{
31
+ color: image ? 'white' : 'text.primary',
32
+ textShadow: image ? '0 2px 4px rgba(0,0,0,0.5)' : 'none',
33
+ mb: 1,
34
+ }}
35
+ >
36
+ {title}
37
+ </Typography>
38
+ )}
39
+
40
+ {description && (
41
+ <Typography
42
+ variant="body1"
43
+ sx={{
44
+ color: image ? 'white' : 'text.secondary',
45
+ textShadow: image ? '0 1px 2px rgba(0,0,0,0.5)' : 'none',
46
+ }}
47
+ >
48
+ {description}
49
+ </Typography>
50
+ )}
51
+
52
+ {content}
53
+ </Box>
54
+ );
55
+
56
+ export const CarouselSlide: React.FC<CarouselSlideProps> = ({
57
+ item,
58
+ index,
59
+ isActive,
60
+ variant,
61
+ clickable,
62
+ onSelect,
63
+ }) => {
64
+ const theme = useTheme();
65
+
66
+ const content = (
67
+ <Box
68
+ sx={{
69
+ width: '100%',
70
+ height: '100%',
71
+ display: 'flex',
72
+ flexDirection: 'column',
73
+ justifyContent: 'center',
74
+ alignItems: 'center',
75
+ cursor: clickable ? 'pointer' : 'default',
76
+ position: 'relative',
77
+ }}
78
+ onClick={() => onSelect(item, index)}
79
+ >
80
+ {item.image && (
81
+ <Box
82
+ component="img"
83
+ src={item.image}
84
+ alt={item.alt || item.title}
85
+ sx={{
86
+ width: '100%',
87
+ height: '100%',
88
+ objectFit: 'cover',
89
+ position: 'absolute',
90
+ top: 0,
91
+ left: 0,
92
+ }}
93
+ />
94
+ )}
95
+
96
+ <SlideCaption
97
+ title={item.title}
98
+ description={item.description}
99
+ content={item.content}
100
+ image={item.image}
101
+ />
102
+ </Box>
103
+ );
104
+
105
+ // The cards variant gives each slide its own raised surface, and lifts the
106
+ // active one further so the stack reads as a deck rather than a strip.
107
+ if (variant === 'cards') {
108
+ return (
109
+ <Paper
110
+ elevation={isActive ? 8 : 2}
111
+ sx={{
112
+ width: '100%',
113
+ height: '100%',
114
+ overflow: 'hidden',
115
+ transform: isActive ? 'scale(1)' : 'scale(0.9)',
116
+ transition: theme.transitions.create(['transform', 'box-shadow']),
117
+ }}
118
+ >
119
+ {content}
120
+ </Paper>
121
+ );
122
+ }
123
+
124
+ return content;
125
+ };
126
+
127
+ export interface CarouselTrackProps {
128
+ items: CarouselItem[];
129
+ activeIndex: number;
130
+ variant: NonNullable<CarouselProps['variant']>;
131
+ animation: NonNullable<CarouselProps['animation']>;
132
+ transition: React.ElementType;
133
+ clickable: boolean;
134
+ onSelect: (item: CarouselItem, index: number) => void;
135
+ }
136
+
137
+ /**
138
+ * The slides themselves. Every slide stays mounted and absolutely positioned so
139
+ * the transition has both the outgoing and incoming one to work with; `display`
140
+ * is what actually hides the rest.
141
+ */
142
+ export const CarouselTrack: React.FC<CarouselTrackProps> = ({
143
+ items,
144
+ activeIndex,
145
+ variant,
146
+ animation,
147
+ transition: Transition,
148
+ clickable,
149
+ onSelect,
150
+ }) => (
151
+ <Box
152
+ data-testid="carousel-track"
153
+ sx={{
154
+ width: '100%',
155
+ height: '100%',
156
+ position: 'relative',
157
+ display: 'flex',
158
+ alignItems: 'center',
159
+ }}
160
+ >
161
+ {items.map((item, index) => (
162
+ <Transition
163
+ key={item.id}
164
+ in={index === activeIndex}
165
+ timeout={600}
166
+ {...(animation === 'flip' && { direction: 'left' as const })}
167
+ >
168
+ <Box
169
+ data-testid={`carousel-slide-${index}`}
170
+ data-active={index === activeIndex}
171
+ sx={{
172
+ position: 'absolute',
173
+ width: '100%',
174
+ height: '100%',
175
+ display: index === activeIndex ? 'block' : 'none',
176
+ }}
177
+ >
178
+ <CarouselSlide
179
+ item={item}
180
+ index={index}
181
+ isActive={index === activeIndex}
182
+ variant={variant}
183
+ clickable={clickable}
184
+ onSelect={onSelect}
185
+ />
186
+ </Box>
187
+ </Transition>
188
+ ))}
189
+ </Box>
190
+ );
@@ -0,0 +1,86 @@
1
+ import { alpha } from '@mui/material';
2
+ import type { CSSObject, Theme } from '@mui/material';
3
+
4
+ import type { CarouselProps } from './Carousel.types';
5
+
6
+ type Variant = NonNullable<CarouselProps['variant']>;
7
+ type Size = NonNullable<CarouselProps['size']>;
8
+ type Color = NonNullable<CarouselProps['color']>;
9
+
10
+ const SIZES: Record<Size, { height: number; fontSize: string }> = {
11
+ xs: { height: 200, fontSize: '0.75rem' },
12
+ sm: { height: 300, fontSize: '0.875rem' },
13
+ md: { height: 400, fontSize: '1rem' },
14
+ lg: { height: 500, fontSize: '1.125rem' },
15
+ xl: { height: 600, fontSize: '1.25rem' },
16
+ };
17
+
18
+ const sizeStyles = (size: Size) => SIZES[size] ?? SIZES.md;
19
+
20
+ interface ContainerStyleInput {
21
+ theme: Theme;
22
+ variant: Variant;
23
+ size: Size;
24
+ color: Color;
25
+ height: number | string;
26
+ width: number | string;
27
+ glow: boolean;
28
+ pulse: boolean;
29
+ glass: boolean;
30
+ gradient: boolean;
31
+ }
32
+
33
+ /**
34
+ * Each variant's own additions to the shared frame. The old switch re-spread the
35
+ * same base, glow and pulse objects in all six arms before adding these; only
36
+ * these differ, so the shared prefix is applied once by `containerStyles`.
37
+ */
38
+ const SURFACES: Record<Variant, (input: ContainerStyleInput) => CSSObject> = {
39
+ glass: ({ theme, color, glass }) => ({
40
+ backgroundColor: alpha(theme.palette.background.paper, glass ? 0.1 : 0.9),
41
+ backdropFilter: 'blur(20px)',
42
+ border: `1px solid ${alpha(theme.palette[color].main, 0.2)}`,
43
+ }),
44
+
45
+ gradient: ({ theme, color, gradient }) => ({
46
+ background: gradient
47
+ ? `linear-gradient(135deg, ${alpha(theme.palette[color].light, 0.1)}, ${alpha(theme.palette[color].dark, 0.1)})`
48
+ : 'transparent',
49
+ }),
50
+
51
+ elevated: ({ theme }) => ({ boxShadow: theme.shadows[8] }),
52
+
53
+ minimal: () => ({ border: 'none', boxShadow: 'none' }),
54
+
55
+ cards: ({ theme }) => ({ padding: theme.spacing(2) }),
56
+
57
+ default: () => ({}),
58
+ };
59
+
60
+ export const containerStyles = (input: ContainerStyleInput): CSSObject => {
61
+ const { theme, variant, size, color, height, width, glow, pulse } = input;
62
+ const accent = theme.palette[color].main;
63
+
64
+ return {
65
+ position: 'relative' as const,
66
+ width,
67
+ // A numeric height is taken literally; anything else falls back to the size
68
+ // preset, since a percentage cannot resolve against an unsized parent here.
69
+ height: typeof height === 'number' ? height : sizeStyles(size).height,
70
+ overflow: 'hidden',
71
+ borderRadius: theme.spacing(1),
72
+ transition: theme.transitions.create(['all'], {
73
+ duration: theme.transitions.duration.standard,
74
+ }),
75
+ ...(glow && { boxShadow: `0 0 30px ${alpha(accent, 0.4)}` }),
76
+ ...(pulse && {
77
+ animation: 'pulse 2s infinite',
78
+ '@keyframes pulse': {
79
+ '0%': { boxShadow: `0 0 0 0 ${alpha(accent, 0.4)}` },
80
+ '70%': { boxShadow: `0 0 0 20px ${alpha(accent, 0)}` },
81
+ '100%': { boxShadow: `0 0 0 0 ${alpha(accent, 0)}` },
82
+ },
83
+ }),
84
+ ...(SURFACES[variant] ?? SURFACES.default)(input),
85
+ };
86
+ };