@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.
- package/package.json +4 -4
- package/src/components/data-display/Carousel/Carousel.chrome.tsx +83 -0
- package/src/components/data-display/Carousel/Carousel.hooks.ts +88 -0
- package/src/components/data-display/Carousel/Carousel.parts.tsx +346 -0
- package/src/components/data-display/Carousel/Carousel.slide.tsx +190 -0
- package/src/components/data-display/Carousel/Carousel.styles.ts +86 -0
- package/src/components/data-display/Carousel/Carousel.tsx +84 -732
- package/src/components/data-display/HoverCard/HoverCard.animations.ts +73 -0
- package/src/components/data-display/HoverCard/HoverCard.content.tsx +143 -0
- package/src/components/data-display/HoverCard/HoverCard.hooks.ts +180 -0
- package/src/components/data-display/HoverCard/HoverCard.styles.ts +178 -0
- package/src/components/data-display/HoverCard/HoverCard.tsx +117 -631
- package/src/components/data-display/Sheet/Sheet.animations.ts +44 -0
- package/src/components/data-display/Sheet/Sheet.body.tsx +124 -0
- package/src/components/data-display/Sheet/Sheet.drag.ts +203 -0
- package/src/components/data-display/Sheet/Sheet.hooks.ts +318 -0
- package/src/components/data-display/Sheet/Sheet.parts.tsx +224 -0
- package/src/components/data-display/Sheet/Sheet.styles.ts +257 -0
- package/src/components/data-display/Sheet/Sheet.tsx +125 -948
- package/src/components/feedback/Modal/Modal.styles.ts +126 -0
- package/src/components/feedback/Modal/Modal.transitions.tsx +40 -0
- package/src/components/feedback/Modal/Modal.tsx +37 -242
- package/src/components/form/InputOTP/InputOTP.hooks.ts +104 -0
- package/src/components/form/InputOTP/InputOTP.styles.ts +97 -0
- package/src/components/form/InputOTP/InputOTP.tsx +74 -221
- package/src/components/form/Label/Label.parts.tsx +75 -0
- package/src/components/form/Label/Label.styles.ts +206 -0
- package/src/components/form/Label/Label.tsx +73 -325
- package/src/components/form/Menubar/Menubar.hooks.ts +41 -0
- package/src/components/form/Menubar/Menubar.item.tsx +121 -0
- package/src/components/form/Menubar/Menubar.separator.tsx +25 -0
- package/src/components/form/Menubar/Menubar.styles.ts +110 -0
- package/src/components/form/Menubar/Menubar.toolbar.tsx +103 -0
- package/src/components/form/Menubar/Menubar.tsx +64 -347
- package/src/components/form/RadioGroup/RadioGroup.animations.ts +48 -0
- package/src/components/form/RadioGroup/RadioGroup.styles.ts +305 -0
- package/src/components/form/RadioGroup/RadioGroup.tsx +81 -651
- package/src/components/form/RadioGroup/RadioGroup.variants.tsx +291 -0
- package/src/components/form/Slider/Slider.animations.ts +40 -0
- package/src/components/form/Slider/Slider.styles.ts +292 -0
- package/src/components/form/Slider/Slider.tsx +106 -357
- package/src/components/form/Switch/Switch.animations.ts +62 -0
- package/src/components/form/Switch/Switch.parts.tsx +213 -0
- package/src/components/form/Switch/Switch.styles.ts +355 -0
- package/src/components/form/Switch/Switch.tsx +87 -566
- package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +7 -7
- package/src/components/typography/Heading/Heading.styles.ts +101 -0
- package/src/components/typography/Heading/Heading.tsx +48 -164
- package/src/components/utility/AnimatedIcon/AnimatedIcon.animations.ts +274 -0
- package/src/components/utility/AnimatedIcon/AnimatedIcon.styles.ts +231 -0
- package/src/components/utility/AnimatedIcon/AnimatedIcon.tsx +39 -559
- package/src/utils/index.ts +1 -0
- package/src/utils/withDefaults.ts +20 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import CloseIcon from '@mui/icons-material/Close';
|
|
2
|
+
import { alpha, Backdrop, Box, Divider, Fade, IconButton, Typography, useTheme } from '@mui/material';
|
|
3
|
+
import type { Theme } from '@mui/material';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
import { withDefaults } from '../../../utils/withDefaults';
|
|
7
|
+
|
|
8
|
+
import { shimmerAnimation } from './Sheet.animations';
|
|
9
|
+
import type {
|
|
10
|
+
SheetContentProps,
|
|
11
|
+
SheetFooterProps,
|
|
12
|
+
SheetHeaderProps,
|
|
13
|
+
SheetOverlayProps,
|
|
14
|
+
} from './Sheet.types';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The grab handle's own styling. A draggable handle is deliberately larger and
|
|
18
|
+
* tinted, so the affordance reads as draggable before the user tries it; a
|
|
19
|
+
* decorative one stays a neutral bar.
|
|
20
|
+
*/
|
|
21
|
+
const handleSx = (theme: Theme, isDraggable: boolean) => ({
|
|
22
|
+
width: isDraggable ? 48 : 32,
|
|
23
|
+
height: isDraggable ? 6 : 4,
|
|
24
|
+
backgroundColor: alpha(theme.palette.text.primary, 0.3),
|
|
25
|
+
borderRadius: 3,
|
|
26
|
+
transition: theme.transitions.create(['all'], {
|
|
27
|
+
duration: theme.transitions.duration.short,
|
|
28
|
+
easing: theme.transitions.easing.easeInOut,
|
|
29
|
+
}),
|
|
30
|
+
position: 'relative',
|
|
31
|
+
overflow: 'hidden',
|
|
32
|
+
'&::after': {
|
|
33
|
+
content: '""',
|
|
34
|
+
position: 'absolute',
|
|
35
|
+
top: 0,
|
|
36
|
+
left: '-100%',
|
|
37
|
+
width: '100%',
|
|
38
|
+
height: '100%',
|
|
39
|
+
background: `linear-gradient(
|
|
40
|
+
90deg,
|
|
41
|
+
transparent,
|
|
42
|
+
${alpha(theme.palette.common.white, 0.3)},
|
|
43
|
+
transparent
|
|
44
|
+
)`,
|
|
45
|
+
animation: isDraggable ? `${shimmerAnimation} 2s infinite` : 'none',
|
|
46
|
+
},
|
|
47
|
+
...(isDraggable && {
|
|
48
|
+
backgroundColor: alpha(theme.palette.primary.main, 0.4),
|
|
49
|
+
boxShadow: `0 2px 4px ${alpha(theme.palette.primary.main, 0.2)}`,
|
|
50
|
+
'&:hover': {
|
|
51
|
+
backgroundColor: theme.palette.primary.main,
|
|
52
|
+
transform: 'scaleX(1.15) scaleY(1.2)',
|
|
53
|
+
boxShadow: `0 3px 6px ${alpha(theme.palette.primary.main, 0.3)}`,
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const SheetHandle: React.FC<{ isDraggable: boolean }> = ({ isDraggable }) => {
|
|
59
|
+
const theme = useTheme();
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Box
|
|
63
|
+
sx={{
|
|
64
|
+
display: 'flex',
|
|
65
|
+
justifyContent: 'center',
|
|
66
|
+
mb: 1,
|
|
67
|
+
// The handle is decoration on a draggable header: the drag listeners sit
|
|
68
|
+
// on the header itself, so letting the bar swallow the press would open
|
|
69
|
+
// a dead zone in the middle of the grab target.
|
|
70
|
+
pointerEvents: isDraggable ? 'none' : 'auto',
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<Box sx={handleSx(theme, isDraggable)} />
|
|
74
|
+
</Box>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const HEADER_DEFAULTS: Partial<SheetHeaderProps> = {
|
|
79
|
+
isDraggable: false,
|
|
80
|
+
dataTestId: 'sheet-header',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const SheetHeader: React.FC<SheetHeaderProps> = (props) => {
|
|
84
|
+
const {
|
|
85
|
+
title,
|
|
86
|
+
description,
|
|
87
|
+
showCloseButton,
|
|
88
|
+
onClose,
|
|
89
|
+
showHandle,
|
|
90
|
+
isDraggable,
|
|
91
|
+
onDragStart,
|
|
92
|
+
className,
|
|
93
|
+
style,
|
|
94
|
+
children,
|
|
95
|
+
dataTestId,
|
|
96
|
+
} = withDefaults(props, HEADER_DEFAULTS);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<Box
|
|
100
|
+
className={className}
|
|
101
|
+
data-testid={dataTestId}
|
|
102
|
+
sx={{
|
|
103
|
+
p: 2,
|
|
104
|
+
pb: showHandle ? 1 : 2,
|
|
105
|
+
cursor: isDraggable ? 'grab' : 'auto',
|
|
106
|
+
'&:active': isDraggable ? { cursor: 'grabbing' } : {},
|
|
107
|
+
...style,
|
|
108
|
+
}}
|
|
109
|
+
onMouseDown={isDraggable ? onDragStart : undefined}
|
|
110
|
+
onTouchStart={isDraggable ? onDragStart : undefined}
|
|
111
|
+
>
|
|
112
|
+
{showHandle && <SheetHandle isDraggable={Boolean(isDraggable)} />}
|
|
113
|
+
|
|
114
|
+
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between' }}>
|
|
115
|
+
<Box sx={{ flex: 1 }}>
|
|
116
|
+
{title && (
|
|
117
|
+
<Typography variant="h6" sx={{ fontWeight: 600 }} data-testid={`${dataTestId}-title`}>
|
|
118
|
+
{title}
|
|
119
|
+
</Typography>
|
|
120
|
+
)}
|
|
121
|
+
{description && (
|
|
122
|
+
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
|
|
123
|
+
{description}
|
|
124
|
+
</Typography>
|
|
125
|
+
)}
|
|
126
|
+
{children}
|
|
127
|
+
</Box>
|
|
128
|
+
|
|
129
|
+
{showCloseButton && (
|
|
130
|
+
<IconButton
|
|
131
|
+
onClick={onClose}
|
|
132
|
+
size="small"
|
|
133
|
+
sx={{ ml: 1, mt: -0.5 }}
|
|
134
|
+
data-testid={`${dataTestId}-close-button`}
|
|
135
|
+
>
|
|
136
|
+
<CloseIcon data-testid={`${dataTestId}-close-icon`} />
|
|
137
|
+
</IconButton>
|
|
138
|
+
)}
|
|
139
|
+
</Box>
|
|
140
|
+
</Box>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const CONTENT_DEFAULTS: Partial<SheetContentProps> = {
|
|
145
|
+
padded: true,
|
|
146
|
+
dataTestId: 'sheet-content',
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const SheetContent: React.FC<SheetContentProps> = (props) => {
|
|
150
|
+
const { children, className, style, padded, dataTestId } = withDefaults(props, CONTENT_DEFAULTS);
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<Box
|
|
154
|
+
className={className}
|
|
155
|
+
data-testid={dataTestId}
|
|
156
|
+
sx={{ flex: 1, overflow: 'auto', p: padded ? 2 : 0, ...style }}
|
|
157
|
+
>
|
|
158
|
+
{children}
|
|
159
|
+
</Box>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const FOOTER_DEFAULTS: Partial<SheetFooterProps> = {
|
|
164
|
+
sticky: false,
|
|
165
|
+
divider: false,
|
|
166
|
+
dataTestId: 'sheet-footer',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export const SheetFooter: React.FC<SheetFooterProps> = (props) => {
|
|
170
|
+
const { children, className, style, sticky, divider, dataTestId } = withDefaults(
|
|
171
|
+
props,
|
|
172
|
+
FOOTER_DEFAULTS,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<>
|
|
177
|
+
{divider && <Divider />}
|
|
178
|
+
<Box
|
|
179
|
+
className={className}
|
|
180
|
+
data-testid={dataTestId}
|
|
181
|
+
sx={{
|
|
182
|
+
p: 2,
|
|
183
|
+
position: sticky ? 'sticky' : 'relative',
|
|
184
|
+
bottom: sticky ? 0 : 'auto',
|
|
185
|
+
backgroundColor: 'background.paper',
|
|
186
|
+
borderTop: sticky ? '1px solid' : 'none',
|
|
187
|
+
borderColor: 'divider',
|
|
188
|
+
...style,
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
{children}
|
|
192
|
+
</Box>
|
|
193
|
+
</>
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const OVERLAY_DEFAULTS: Partial<SheetOverlayProps> = {
|
|
198
|
+
open: false,
|
|
199
|
+
blur: false,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const SheetOverlay: React.FC<SheetOverlayProps> = (props) => {
|
|
203
|
+
const { open, onClick, className, style, blur } = withDefaults(props, OVERLAY_DEFAULTS);
|
|
204
|
+
const theme = useTheme();
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<Fade in={open} timeout={300}>
|
|
208
|
+
<Backdrop
|
|
209
|
+
open={Boolean(open)}
|
|
210
|
+
onClick={onClick}
|
|
211
|
+
className={className}
|
|
212
|
+
sx={{
|
|
213
|
+
// One below the drawer: this backdrop replaces MUI's own (the drawer
|
|
214
|
+
// is given a no-op BackdropComponent) and must sit under the panel.
|
|
215
|
+
zIndex: theme.zIndex.drawer - 1,
|
|
216
|
+
backgroundColor: alpha(theme.palette.common.black, blur ? 0.6 : 0.5),
|
|
217
|
+
backdropFilter: blur ? 'blur(8px) saturate(180%)' : 'none',
|
|
218
|
+
WebkitBackdropFilter: blur ? 'blur(8px) saturate(180%)' : 'none',
|
|
219
|
+
...style,
|
|
220
|
+
}}
|
|
221
|
+
/>
|
|
222
|
+
</Fade>
|
|
223
|
+
);
|
|
224
|
+
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { alpha } from '@mui/material';
|
|
2
|
+
import type { CSSObject, Theme } from '@mui/material';
|
|
3
|
+
import type React from 'react';
|
|
4
|
+
|
|
5
|
+
import { glowAnimation, pulseAnimation, shimmerAnimation } from './Sheet.animations';
|
|
6
|
+
import type { SheetProps } from './Sheet.types';
|
|
7
|
+
|
|
8
|
+
type Position = NonNullable<SheetProps['position']>;
|
|
9
|
+
type Variant = NonNullable<SheetProps['variant']>;
|
|
10
|
+
type Size = NonNullable<SheetProps['size']>;
|
|
11
|
+
type Color = NonNullable<SheetProps['color']>;
|
|
12
|
+
|
|
13
|
+
const isHorizontalPosition = (position: Position) => position === 'left' || position === 'right';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Horizontal (left/right) sheets are viewport-aware: a px floor keeps the panel
|
|
17
|
+
* usable, `…vw` lets the larger presets grow on wide screens (lg ≥32%, xl ≥40%
|
|
18
|
+
* of the viewport), and the outer `min(92vw, …)` guarantees the panel never
|
|
19
|
+
* overflows a small screen. Vertical presets stay fixed-height.
|
|
20
|
+
*/
|
|
21
|
+
const HORIZONTAL_SIZES: Record<Size, string> = {
|
|
22
|
+
xs: 'min(92vw, 240px)',
|
|
23
|
+
sm: 'min(92vw, 320px)',
|
|
24
|
+
md: 'min(92vw, 400px)',
|
|
25
|
+
lg: 'min(92vw, max(560px, 32vw))',
|
|
26
|
+
xl: 'min(92vw, max(720px, 40vw))',
|
|
27
|
+
full: '100%',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const VERTICAL_SIZES: Record<Size, number | string> = {
|
|
31
|
+
xs: 200,
|
|
32
|
+
sm: 300,
|
|
33
|
+
md: 400,
|
|
34
|
+
lg: 500,
|
|
35
|
+
xl: 600,
|
|
36
|
+
full: '100%',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
interface SizeStyleInput {
|
|
40
|
+
position: Position;
|
|
41
|
+
size: Size;
|
|
42
|
+
isDraggableVariant: boolean;
|
|
43
|
+
isVerticalSheet: boolean;
|
|
44
|
+
currentHeight: number | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const sizeStyles = ({
|
|
48
|
+
position,
|
|
49
|
+
size,
|
|
50
|
+
isDraggableVariant,
|
|
51
|
+
isVerticalSheet,
|
|
52
|
+
currentHeight,
|
|
53
|
+
}: SizeStyleInput) => {
|
|
54
|
+
// The draggable variant owns its own height: the snap point decides it, so a
|
|
55
|
+
// preset would fight the drag.
|
|
56
|
+
if (isDraggableVariant && isVerticalSheet && currentHeight !== null) {
|
|
57
|
+
return { height: currentHeight };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return isHorizontalPosition(position)
|
|
61
|
+
? { width: HORIZONTAL_SIZES[size] ?? HORIZONTAL_SIZES.md }
|
|
62
|
+
: { height: VERTICAL_SIZES[size] ?? VERTICAL_SIZES.md };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** `r, g, b` for a hex colour, so it can feed an `rgba(var(--x), a)` custom property. */
|
|
66
|
+
const toRgbTriplet = (hexColor: string) => {
|
|
67
|
+
const hex = hexColor.replace('#', '');
|
|
68
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
69
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
70
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
71
|
+
return `${r}, ${g}, ${b}`;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const roundedStyles = (theme: Theme, position: Position) => {
|
|
75
|
+
const radius = theme.spacing(2);
|
|
76
|
+
const byPosition: Record<Position, string> = {
|
|
77
|
+
top: `0 0 ${radius} ${radius}`,
|
|
78
|
+
bottom: `${radius} ${radius} 0 0`,
|
|
79
|
+
left: `0 ${radius} ${radius} 0`,
|
|
80
|
+
right: `${radius} 0 0 ${radius}`,
|
|
81
|
+
};
|
|
82
|
+
return { borderRadius: byPosition[position] };
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
interface VariantStyleInput {
|
|
86
|
+
theme: Theme;
|
|
87
|
+
variant: Variant;
|
|
88
|
+
color: Color;
|
|
89
|
+
position: Position;
|
|
90
|
+
elevation: number;
|
|
91
|
+
glow: boolean;
|
|
92
|
+
pulse: boolean;
|
|
93
|
+
glass: boolean;
|
|
94
|
+
gradient: boolean;
|
|
95
|
+
rounded: boolean;
|
|
96
|
+
disabled: boolean;
|
|
97
|
+
isDragging: boolean;
|
|
98
|
+
isAnimating: boolean;
|
|
99
|
+
isDraggableVariant: boolean;
|
|
100
|
+
isVerticalSheet: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
type SurfaceInput = VariantStyleInput & { accent: string };
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Each variant's own additions to the shared surface — never the shared part
|
|
107
|
+
* itself. Every arm of the old switch re-spread the same base, glow, pulse and
|
|
108
|
+
* rounded objects before adding these few rules; `variantStyles` applies that
|
|
109
|
+
* prefix once and lets the table hold only what actually differs.
|
|
110
|
+
*/
|
|
111
|
+
const SURFACES: Record<Variant, (input: SurfaceInput) => Record<string, unknown>> = {
|
|
112
|
+
draggable: ({ theme, elevation, accent }) => ({
|
|
113
|
+
boxShadow: `
|
|
114
|
+
${theme.shadows[Math.min(elevation + 4, 24)]},
|
|
115
|
+
0 -2px 10px 0 ${alpha(theme.palette.common.black, 0.1)}
|
|
116
|
+
`,
|
|
117
|
+
border: `1px solid ${alpha(theme.palette.divider, 0.12)}`,
|
|
118
|
+
borderTop: `2px solid ${alpha(accent, 0.3)}`,
|
|
119
|
+
transition: theme.transitions.create(['transform', 'box-shadow', 'border-color'], {
|
|
120
|
+
duration: theme.transitions.duration.shorter,
|
|
121
|
+
easing: theme.transitions.easing.easeInOut,
|
|
122
|
+
}),
|
|
123
|
+
'&:hover': { borderTopColor: alpha(accent, 0.5) },
|
|
124
|
+
}),
|
|
125
|
+
|
|
126
|
+
glass: ({ theme, glass }) => ({
|
|
127
|
+
backgroundColor: alpha(theme.palette.background.paper, glass ? 0.75 : 0.95),
|
|
128
|
+
backdropFilter: 'blur(24px) saturate(180%)',
|
|
129
|
+
WebkitBackdropFilter: 'blur(24px) saturate(180%)',
|
|
130
|
+
border: `1px solid ${alpha(theme.palette.divider, 0.15)}`,
|
|
131
|
+
boxShadow: `
|
|
132
|
+
0 8px 32px 0 ${alpha(theme.palette.common.black, 0.15)},
|
|
133
|
+
inset 0 0 0 1px ${alpha(theme.palette.common.white, 0.1)}
|
|
134
|
+
`,
|
|
135
|
+
}),
|
|
136
|
+
|
|
137
|
+
gradient: ({ theme, color, gradient, accent }) => ({
|
|
138
|
+
background: gradient
|
|
139
|
+
? `linear-gradient(
|
|
140
|
+
135deg,
|
|
141
|
+
${theme.palette.background.paper} 0%,
|
|
142
|
+
${alpha(accent, 0.08)} 50%,
|
|
143
|
+
${alpha(theme.palette[color]?.dark || accent, 0.12)} 100%
|
|
144
|
+
)`
|
|
145
|
+
: theme.palette.background.paper,
|
|
146
|
+
position: 'relative' as const,
|
|
147
|
+
overflow: 'hidden' as const,
|
|
148
|
+
'&::before': gradient ? shimmerOverlay(theme) : {},
|
|
149
|
+
}),
|
|
150
|
+
|
|
151
|
+
elevated: ({ theme, elevation }) => ({
|
|
152
|
+
boxShadow: `
|
|
153
|
+
${theme.shadows[elevation]},
|
|
154
|
+
0 20px 40px -15px ${alpha(theme.palette.common.black, 0.15)}
|
|
155
|
+
`,
|
|
156
|
+
transform: 'translateZ(0)',
|
|
157
|
+
willChange: 'transform',
|
|
158
|
+
}),
|
|
159
|
+
|
|
160
|
+
minimal: () => ({ boxShadow: 'none', border: 'none' }),
|
|
161
|
+
|
|
162
|
+
default: () => ({}),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/** The sweep of light the gradient variant runs across itself. */
|
|
166
|
+
const shimmerOverlay = (theme: Theme) => ({
|
|
167
|
+
content: '""',
|
|
168
|
+
position: 'absolute',
|
|
169
|
+
top: 0,
|
|
170
|
+
left: '-100%',
|
|
171
|
+
width: '100%',
|
|
172
|
+
height: '100%',
|
|
173
|
+
background: `linear-gradient(
|
|
174
|
+
90deg,
|
|
175
|
+
transparent,
|
|
176
|
+
${alpha(theme.palette.common.white, 0.2)},
|
|
177
|
+
transparent
|
|
178
|
+
)`,
|
|
179
|
+
animation: `${shimmerAnimation} 3s infinite`,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The parts of the surface that do not depend on the variant: the paper colour,
|
|
184
|
+
* the transition suppression while a drag or spring is driving the panel, the
|
|
185
|
+
* disabled and cursor states, and the two colour custom properties the glow and
|
|
186
|
+
* pulse keyframes read.
|
|
187
|
+
*/
|
|
188
|
+
const baseSurface = (input: VariantStyleInput, accent: string) => {
|
|
189
|
+
const { theme, disabled, isDragging, isAnimating, isDraggableVariant, isVerticalSheet } = input;
|
|
190
|
+
const colorRgb = toRgbTriplet(accent);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
backgroundColor: theme.palette.background.paper,
|
|
194
|
+
// A transition would fight the per-frame height writes while a drag or a
|
|
195
|
+
// snap animation is driving the panel.
|
|
196
|
+
transition:
|
|
197
|
+
!isDragging && !isAnimating
|
|
198
|
+
? theme.transitions.create(['all'], {
|
|
199
|
+
duration: theme.transitions.duration.standard,
|
|
200
|
+
easing: theme.transitions.easing.easeInOut,
|
|
201
|
+
})
|
|
202
|
+
: 'none',
|
|
203
|
+
opacity: disabled ? 0.5 : 1,
|
|
204
|
+
pointerEvents: disabled ? ('none' as const) : ('auto' as const),
|
|
205
|
+
cursor: isDraggableVariant && isVerticalSheet ? 'grab' : 'auto',
|
|
206
|
+
'--pulse-color': colorRgb,
|
|
207
|
+
'--glow-color': colorRgb,
|
|
208
|
+
...(isDragging && { cursor: 'grabbing' }),
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const variantStyles = (input: VariantStyleInput) => {
|
|
213
|
+
const { theme, variant, color, position, glow, pulse, rounded } = input;
|
|
214
|
+
const accent = theme.palette[color]?.main || theme.palette.primary.main;
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
...baseSurface(input, accent),
|
|
218
|
+
// glow first, then pulse: with both set the pulse keyframes win the shared
|
|
219
|
+
// `animation` slot, which is the order the original spread produced.
|
|
220
|
+
...(glow && { animation: `${glowAnimation} 2s ease-in-out infinite`, filter: 'brightness(1.05)' }),
|
|
221
|
+
...(pulse && { animation: `${pulseAnimation} 2s infinite`, position: 'relative' as const }),
|
|
222
|
+
...(rounded && roundedStyles(theme, position)),
|
|
223
|
+
...(SURFACES[variant] ?? SURFACES.default)({ ...input, accent }),
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
interface PanelSxInput extends VariantStyleInput {
|
|
228
|
+
size: Size;
|
|
229
|
+
currentHeight: number | null;
|
|
230
|
+
fullHeight: boolean;
|
|
231
|
+
style?: React.CSSProperties;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The drawer paper's complete `sx`: its size, its variant surface, the caller's
|
|
236
|
+
* own `style`, and the full-height rule.
|
|
237
|
+
*
|
|
238
|
+
* Built here rather than in the component so the component holds no style
|
|
239
|
+
* branches of its own — the cross-axis rule below is four on its own.
|
|
240
|
+
*/
|
|
241
|
+
export const panelSx = (input: PanelSxInput): CSSObject => {
|
|
242
|
+
const { position, size, isDraggableVariant, isVerticalSheet, currentHeight, fullHeight, style } =
|
|
243
|
+
input;
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
...sizeStyles({ position, size, isDraggableVariant, isVerticalSheet, currentHeight }),
|
|
247
|
+
...variantStyles(input),
|
|
248
|
+
...style,
|
|
249
|
+
overflow: 'visible',
|
|
250
|
+
// Expand only the cross axis so the chosen `size` still governs the main
|
|
251
|
+
// axis: side sheets keep their (responsive) width and go full-height;
|
|
252
|
+
// top/bottom sheets keep their height and go full-width.
|
|
253
|
+
...(fullHeight &&
|
|
254
|
+
!isDraggableVariant &&
|
|
255
|
+
(isHorizontalPosition(position) ? { height: '100%' } : { width: '100%' })),
|
|
256
|
+
};
|
|
257
|
+
};
|