@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.
- package/eslint.config.js +12 -0
- package/package.json +5 -4
- package/src/components/data-display/AlertDialog/AlertDialog.test.stories.tsx +16 -6
- package/src/components/data-display/Banner/Banner.test.stories.tsx +26 -18
- 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.test.stories.tsx +15 -5
- 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.test.stories.tsx +25 -21
- package/src/components/data-display/Sheet/Sheet.tsx +125 -948
- package/src/components/feedback/Dialog/Dialog.test.stories.tsx +37 -28
- package/src/components/feedback/Modal/Modal.styles.ts +126 -0
- package/src/components/feedback/Modal/Modal.test.stories.tsx +5 -5
- package/src/components/feedback/Modal/Modal.transitions.tsx +40 -0
- package/src/components/feedback/Modal/Modal.tsx +37 -242
- package/src/components/feedback/StackedModal/StackedModal.test.stories.tsx +9 -11
- 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.test.stories.tsx +20 -14
- 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/form/ToggleGroup/ToggleGroup.test.stories.tsx +26 -29
- package/src/components/layout/Resizable/Resizable.test.stories.tsx +11 -7
- package/src/components/layout/Spacer/Spacer.test.stories.tsx +11 -9
- 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/typography/Text/Text.test.stories.tsx +27 -30
- 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
|
@@ -1,615 +1,136 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
alpha,
|
|
4
|
-
Backdrop,
|
|
5
|
-
Box,
|
|
6
|
-
CircularProgress,
|
|
7
|
-
Divider,
|
|
8
|
-
Drawer,
|
|
9
|
-
Fade,
|
|
10
|
-
IconButton,
|
|
11
|
-
keyframes,
|
|
12
|
-
SwipeableDrawer,
|
|
13
|
-
Typography,
|
|
14
|
-
useTheme,
|
|
15
|
-
} from '@mui/material';
|
|
1
|
+
import { Drawer, SwipeableDrawer, useTheme } from '@mui/material';
|
|
16
2
|
import type { BackdropProps } from '@mui/material';
|
|
17
|
-
import React
|
|
18
|
-
|
|
19
|
-
import type {
|
|
20
|
-
SheetContentProps,
|
|
21
|
-
SheetFooterProps,
|
|
22
|
-
SheetHeaderProps,
|
|
23
|
-
SheetOverlayProps,
|
|
24
|
-
SheetProps,
|
|
25
|
-
} from './Sheet.types';
|
|
26
|
-
|
|
27
|
-
// Spring physics configuration for smooth animations
|
|
28
|
-
const SPRING_CONFIG = {
|
|
29
|
-
tension: 200,
|
|
30
|
-
friction: 25,
|
|
31
|
-
velocity: 0,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Velocity threshold for snap detection (pixels per millisecond)
|
|
35
|
-
const DEFAULT_VELOCITY_THRESHOLD = 0.5;
|
|
36
|
-
|
|
37
|
-
// Drag resistance factor at boundaries
|
|
38
|
-
const DEFAULT_DRAG_RESISTANCE = 0.3;
|
|
39
|
-
|
|
40
|
-
// Keyframe animations
|
|
41
|
-
const pulseAnimation = keyframes`
|
|
42
|
-
0% {
|
|
43
|
-
box-shadow: 0 0 0 0 rgba(var(--pulse-color), 0.4);
|
|
44
|
-
}
|
|
45
|
-
70% {
|
|
46
|
-
box-shadow: 0 0 0 20px rgba(var(--pulse-color), 0);
|
|
47
|
-
}
|
|
48
|
-
100% {
|
|
49
|
-
box-shadow: 0 0 0 0 rgba(var(--pulse-color), 0);
|
|
50
|
-
}
|
|
51
|
-
`;
|
|
52
|
-
|
|
53
|
-
const shimmerAnimation = keyframes`
|
|
54
|
-
0% {
|
|
55
|
-
background-position: -1000px 0;
|
|
56
|
-
}
|
|
57
|
-
100% {
|
|
58
|
-
background-position: 1000px 0;
|
|
59
|
-
}
|
|
60
|
-
`;
|
|
3
|
+
import React from 'react';
|
|
61
4
|
|
|
62
|
-
|
|
63
|
-
0%, 100% {
|
|
64
|
-
box-shadow: 0 0 20px 5px rgba(var(--glow-color), 0.3);
|
|
65
|
-
}
|
|
66
|
-
50% {
|
|
67
|
-
box-shadow: 0 0 35px 10px rgba(var(--glow-color), 0.5);
|
|
68
|
-
}
|
|
69
|
-
`;
|
|
5
|
+
import { withDefaults } from '../../../utils/withDefaults';
|
|
70
6
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_DRAG_RESISTANCE,
|
|
9
|
+
DEFAULT_VELOCITY_THRESHOLD,
|
|
10
|
+
SPRING_CONFIG,
|
|
11
|
+
} from './Sheet.animations';
|
|
12
|
+
import { SheetBody } from './Sheet.body';
|
|
13
|
+
import { useSheetDrag, useSheetOpen } from './Sheet.hooks';
|
|
14
|
+
import { SheetOverlay } from './Sheet.parts';
|
|
15
|
+
import { panelSx } from './Sheet.styles';
|
|
16
|
+
import type { SheetProps } from './Sheet.types';
|
|
17
|
+
|
|
18
|
+
export { SheetContent, SheetFooter, SheetHeader, SheetOverlay } from './Sheet.parts';
|
|
19
|
+
|
|
20
|
+
const DEFAULTS = {
|
|
21
|
+
open: false,
|
|
22
|
+
position: 'bottom',
|
|
23
|
+
variant: 'default',
|
|
24
|
+
size: 'md',
|
|
25
|
+
color: 'primary',
|
|
26
|
+
glow: false,
|
|
27
|
+
pulse: false,
|
|
28
|
+
glass: false,
|
|
29
|
+
gradient: false,
|
|
30
|
+
loading: false,
|
|
31
|
+
disabled: false,
|
|
32
|
+
showOverlay: true,
|
|
33
|
+
closeOnOverlayClick: true,
|
|
34
|
+
closeOnEscape: true,
|
|
35
|
+
showCloseButton: true,
|
|
36
|
+
showHandle: true,
|
|
37
|
+
swipeable: true,
|
|
38
|
+
snapPoints: [0.25, 0.5, 0.75, 1],
|
|
39
|
+
defaultSnapPoint: 0.5,
|
|
40
|
+
minSnapPoint: 0,
|
|
41
|
+
maxSnapPoint: 1,
|
|
42
|
+
velocityThreshold: DEFAULT_VELOCITY_THRESHOLD,
|
|
43
|
+
dragResistance: DEFAULT_DRAG_RESISTANCE,
|
|
44
|
+
animationConfig: SPRING_CONFIG,
|
|
45
|
+
persistent: false,
|
|
46
|
+
fullHeight: false,
|
|
47
|
+
rounded: true,
|
|
48
|
+
elevation: 16,
|
|
49
|
+
dataTestId: 'sheet',
|
|
50
|
+
} satisfies Partial<SheetProps>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The props with every defaulted key now guaranteed present. Without this the
|
|
54
|
+
* component would need a `?? fallback` at each use site, and each of those is a
|
|
55
|
+
* branch the complexity gate counts.
|
|
56
|
+
*/
|
|
57
|
+
type ResolvedSheetProps = SheetProps & Required<Pick<SheetProps, keyof typeof DEFAULTS>>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The drawer supplies its own backdrop, which would sit on top of `SheetOverlay`
|
|
61
|
+
* and swallow its click. Rendering nothing in its place leaves this component's
|
|
62
|
+
* overlay — the one that knows about the `glass` blur — as the only one.
|
|
63
|
+
*
|
|
64
|
+
* Defined at module scope: a component built inside a render is a new type on
|
|
65
|
+
* every pass, which remounts the subtree it belongs to.
|
|
66
|
+
*/
|
|
67
|
+
const NoBackdrop = React.forwardRef<HTMLDivElement, BackdropProps>(
|
|
68
|
+
(_props, _ref) => null,
|
|
69
|
+
) as React.ComponentType<BackdropProps>;
|
|
70
|
+
NoBackdrop.displayName = 'NoBackdrop';
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Which way the sheet is anchored. Four flags rather than four repeated
|
|
74
|
+
* comparisons, and the only thing the hooks and styles need to know about
|
|
75
|
+
* `position` and `variant`.
|
|
76
|
+
*/
|
|
77
|
+
const orientationOf = ({ position, variant }: ResolvedSheetProps) => {
|
|
138
78
|
const isBottomSheet = position === 'bottom';
|
|
139
79
|
const isTopSheet = position === 'top';
|
|
140
|
-
const isVerticalSheet = isBottomSheet || isTopSheet;
|
|
141
|
-
|
|
142
|
-
// Sort snap points for easier navigation
|
|
143
|
-
const sortedSnapPoints = useMemo(() => {
|
|
144
|
-
const points = [...snapPoints].sort((a, b) => a - b);
|
|
145
|
-
// Ensure snap points are within bounds
|
|
146
|
-
return points.filter((point) => point >= minSnapPoint && point <= maxSnapPoint);
|
|
147
|
-
}, [snapPoints, minSnapPoint, maxSnapPoint]);
|
|
148
|
-
|
|
149
|
-
const handleClose = useCallback(() => {
|
|
150
|
-
if (!persistent && !disabled) {
|
|
151
|
-
setIsOpen(false);
|
|
152
|
-
onOpenChange?.(false);
|
|
153
|
-
onClose?.();
|
|
154
|
-
}
|
|
155
|
-
}, [persistent, disabled, onOpenChange, onClose]);
|
|
156
|
-
|
|
157
|
-
const updateSheetHeight = useCallback(
|
|
158
|
-
(snapPoint: number) => {
|
|
159
|
-
if (!sheetRef.current || !isVerticalSheet) return;
|
|
160
|
-
|
|
161
|
-
const viewportHeight = window.innerHeight;
|
|
162
|
-
const height = Math.round(viewportHeight * snapPoint);
|
|
163
|
-
|
|
164
|
-
if (isBottomSheet) {
|
|
165
|
-
sheetRef.current.style.height = `${height}px`;
|
|
166
|
-
sheetRef.current.style.transform = 'translateY(0)';
|
|
167
|
-
} else if (isTopSheet) {
|
|
168
|
-
sheetRef.current.style.height = `${height}px`;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
setCurrentHeight(height);
|
|
172
|
-
},
|
|
173
|
-
[isBottomSheet, isTopSheet, isVerticalSheet],
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
const handleOverlayClick = useCallback(() => {
|
|
177
|
-
if (closeOnOverlayClick && !persistent) {
|
|
178
|
-
handleClose();
|
|
179
|
-
}
|
|
180
|
-
}, [closeOnOverlayClick, persistent, handleClose]);
|
|
181
|
-
|
|
182
|
-
const animateToSnapPoint = useCallback(
|
|
183
|
-
(targetSnapPoint: number, velocity = 0) => {
|
|
184
|
-
if (!sheetRef.current || !isVerticalSheet) return;
|
|
185
|
-
|
|
186
|
-
setIsAnimating(true);
|
|
187
|
-
const startSnapPoint = currentSnapPoint;
|
|
188
|
-
const distance = targetSnapPoint - startSnapPoint;
|
|
189
|
-
const startTime = globalThis.performance.now();
|
|
190
|
-
|
|
191
|
-
// Spring animation parameters
|
|
192
|
-
const { tension, friction } = { ...SPRING_CONFIG, ...animationConfig };
|
|
193
|
-
const springLength = 0;
|
|
194
|
-
let currentVelocity = velocity * 1000; // Convert to pixels per second
|
|
195
|
-
|
|
196
|
-
const animate = () => {
|
|
197
|
-
const now = globalThis.performance.now();
|
|
198
|
-
const elapsed = (now - startTime) / 1000; // Convert to seconds
|
|
199
|
-
|
|
200
|
-
// Spring physics calculations
|
|
201
|
-
const springForce = -tension * (startSnapPoint - targetSnapPoint + springLength);
|
|
202
|
-
const dampingForce = -friction * currentVelocity;
|
|
203
|
-
const acceleration = springForce + dampingForce;
|
|
204
|
-
|
|
205
|
-
currentVelocity += acceleration * 0.016; // 60fps timestep
|
|
206
|
-
const progress = Math.min(1, elapsed * 4); // Smooth easing
|
|
207
|
-
|
|
208
|
-
const currentPosition = startSnapPoint + distance * progress;
|
|
209
|
-
|
|
210
|
-
updateSheetHeight(currentPosition);
|
|
211
|
-
|
|
212
|
-
if (progress < 1) {
|
|
213
|
-
animationFrameRef.current = window.requestAnimationFrame(animate);
|
|
214
|
-
} else {
|
|
215
|
-
setIsAnimating(false);
|
|
216
|
-
setCurrentSnapPoint(targetSnapPoint);
|
|
217
|
-
onSnapPointChange?.(targetSnapPoint);
|
|
218
|
-
updateSheetHeight(targetSnapPoint);
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
animate();
|
|
223
|
-
},
|
|
224
|
-
[currentSnapPoint, isVerticalSheet, updateSheetHeight, animationConfig, onSnapPointChange],
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
const findClosestSnapPoint = useCallback(
|
|
228
|
-
(currentPosition: number, velocity: number = 0) => {
|
|
229
|
-
// If velocity is significant, snap to next/previous point
|
|
230
|
-
if (Math.abs(velocity) > velocityThreshold) {
|
|
231
|
-
const currentIndex = sortedSnapPoints.findIndex(
|
|
232
|
-
(point) => Math.abs(point - currentSnapPoint) < 0.01,
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
if (velocity > 0 && currentIndex < sortedSnapPoints.length - 1) {
|
|
236
|
-
// Dragging up (for bottom sheet) - go to next higher snap point
|
|
237
|
-
return sortedSnapPoints[currentIndex + 1];
|
|
238
|
-
} else if (velocity < 0 && currentIndex > 0) {
|
|
239
|
-
// Dragging down (for bottom sheet) - go to next lower snap point
|
|
240
|
-
return sortedSnapPoints[currentIndex - 1];
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Find closest snap point
|
|
245
|
-
return sortedSnapPoints.reduce((closest, point) => {
|
|
246
|
-
const currentDistance = Math.abs(currentPosition - closest);
|
|
247
|
-
const pointDistance = Math.abs(currentPosition - point);
|
|
248
|
-
return pointDistance < currentDistance ? point : closest;
|
|
249
|
-
}, sortedSnapPoints[0] || currentSnapPoint);
|
|
250
|
-
},
|
|
251
|
-
[sortedSnapPoints, currentSnapPoint, velocityThreshold],
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
const handleDragStart = useCallback(
|
|
255
|
-
(e: React.MouseEvent | React.TouchEvent) => {
|
|
256
|
-
if (!isDraggableVariant || !isVerticalSheet || isAnimating) return;
|
|
257
|
-
|
|
258
|
-
e.preventDefault();
|
|
259
|
-
e.stopPropagation();
|
|
260
|
-
|
|
261
|
-
const clientY = 'touches' in e ? (e.touches[0]?.clientY ?? 0) : e.clientY;
|
|
262
|
-
|
|
263
|
-
setIsDragging(true);
|
|
264
|
-
setDragStartY(clientY);
|
|
265
|
-
lastYRef.current = clientY;
|
|
266
|
-
lastTimeRef.current = globalThis.performance.now();
|
|
267
|
-
velocityRef.current = 0;
|
|
268
|
-
|
|
269
|
-
if (animationFrameRef.current) {
|
|
270
|
-
window.cancelAnimationFrame(animationFrameRef.current);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
onDragStart?.();
|
|
274
|
-
},
|
|
275
|
-
[isDraggableVariant, isVerticalSheet, isAnimating, onDragStart],
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
const handleDragMove = useCallback(
|
|
279
|
-
(e: globalThis.MouseEvent | globalThis.TouchEvent) => {
|
|
280
|
-
if (!isDragging || !sheetRef.current || !isVerticalSheet) return;
|
|
281
|
-
|
|
282
|
-
e.preventDefault();
|
|
283
|
-
const clientY = 'touches' in e ? (e.touches[0]?.clientY ?? 0) : e.clientY;
|
|
284
|
-
const currentTime = globalThis.performance.now();
|
|
285
|
-
|
|
286
|
-
// Calculate velocity
|
|
287
|
-
const deltaY = clientY - lastYRef.current;
|
|
288
|
-
const deltaTime = currentTime - lastTimeRef.current;
|
|
289
|
-
if (deltaTime > 0) {
|
|
290
|
-
velocityRef.current = deltaY / deltaTime;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
lastYRef.current = clientY;
|
|
294
|
-
lastTimeRef.current = currentTime;
|
|
295
|
-
|
|
296
|
-
const viewportHeight = window.innerHeight;
|
|
297
|
-
const dragDelta = isBottomSheet ? dragStartY - clientY : clientY - dragStartY;
|
|
298
|
-
const deltaSnapPoint = dragDelta / viewportHeight;
|
|
299
|
-
let newSnapPoint = currentSnapPoint + deltaSnapPoint;
|
|
300
|
-
|
|
301
|
-
// Apply resistance at boundaries
|
|
302
|
-
if (newSnapPoint < minSnapPoint) {
|
|
303
|
-
const overshoot = minSnapPoint - newSnapPoint;
|
|
304
|
-
newSnapPoint = minSnapPoint - overshoot * dragResistance;
|
|
305
|
-
} else if (newSnapPoint > maxSnapPoint) {
|
|
306
|
-
const overshoot = newSnapPoint - maxSnapPoint;
|
|
307
|
-
newSnapPoint = maxSnapPoint + overshoot * dragResistance;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
updateSheetHeight(newSnapPoint);
|
|
311
|
-
},
|
|
312
|
-
[
|
|
313
|
-
isDragging,
|
|
314
|
-
isVerticalSheet,
|
|
315
|
-
isBottomSheet,
|
|
316
|
-
dragStartY,
|
|
317
|
-
currentSnapPoint,
|
|
318
|
-
minSnapPoint,
|
|
319
|
-
maxSnapPoint,
|
|
320
|
-
dragResistance,
|
|
321
|
-
updateSheetHeight,
|
|
322
|
-
],
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
useEffect(() => {
|
|
326
|
-
setIsOpen(open);
|
|
327
|
-
if (open) {
|
|
328
|
-
onOpen?.();
|
|
329
|
-
if (isDraggableVariant && isVerticalSheet) {
|
|
330
|
-
// Initialize at default snap point
|
|
331
|
-
setCurrentSnapPoint(defaultSnapPoint);
|
|
332
|
-
updateSheetHeight(defaultSnapPoint);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}, [open, onOpen, isDraggableVariant, isVerticalSheet, defaultSnapPoint, updateSheetHeight]);
|
|
336
80
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
if (isOpen) {
|
|
345
|
-
document.addEventListener('keydown', handleEscape);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return () => {
|
|
349
|
-
document.removeEventListener('keydown', handleEscape);
|
|
350
|
-
};
|
|
351
|
-
}, [isOpen, closeOnEscape, handleClose]);
|
|
352
|
-
|
|
353
|
-
const handleDragEnd = useCallback(
|
|
354
|
-
(e: globalThis.MouseEvent | globalThis.TouchEvent) => {
|
|
355
|
-
if (!isDragging || !isVerticalSheet) return;
|
|
356
|
-
|
|
357
|
-
e.preventDefault();
|
|
358
|
-
setIsDragging(false);
|
|
359
|
-
|
|
360
|
-
const clientY = 'touches' in e ? (e.touches[0]?.clientY ?? 0) : e.clientY;
|
|
361
|
-
const viewportHeight = window.innerHeight;
|
|
362
|
-
const totalDragDelta = isBottomSheet ? dragStartY - clientY : clientY - dragStartY;
|
|
363
|
-
const deltaSnapPoint = totalDragDelta / viewportHeight;
|
|
364
|
-
const currentPosition = currentSnapPoint + deltaSnapPoint;
|
|
365
|
-
|
|
366
|
-
// Calculate final velocity for momentum-based snapping
|
|
367
|
-
const velocity = isBottomSheet ? -(velocityRef.current || 0) : velocityRef.current || 0;
|
|
368
|
-
|
|
369
|
-
// Find and animate to closest snap point
|
|
370
|
-
const targetSnapPoint = findClosestSnapPoint(currentPosition, velocity);
|
|
371
|
-
if (targetSnapPoint !== undefined) {
|
|
372
|
-
animateToSnapPoint(targetSnapPoint, velocity);
|
|
373
|
-
onDragEnd?.(targetSnapPoint);
|
|
374
|
-
}
|
|
375
|
-
},
|
|
376
|
-
[
|
|
377
|
-
isDragging,
|
|
378
|
-
isVerticalSheet,
|
|
379
|
-
isBottomSheet,
|
|
380
|
-
dragStartY,
|
|
381
|
-
currentSnapPoint,
|
|
382
|
-
findClosestSnapPoint,
|
|
383
|
-
animateToSnapPoint,
|
|
384
|
-
onDragEnd,
|
|
385
|
-
],
|
|
386
|
-
);
|
|
387
|
-
|
|
388
|
-
// Add drag event listeners
|
|
389
|
-
useEffect(() => {
|
|
390
|
-
if (isDragging) {
|
|
391
|
-
const handleMouseMove = (e: globalThis.MouseEvent) => handleDragMove(e);
|
|
392
|
-
const handleMouseUp = (e: globalThis.MouseEvent) => handleDragEnd(e);
|
|
393
|
-
const handleTouchMove = (e: globalThis.TouchEvent) => handleDragMove(e);
|
|
394
|
-
const handleTouchEnd = (e: globalThis.TouchEvent) => handleDragEnd(e);
|
|
395
|
-
|
|
396
|
-
document.addEventListener('mousemove', handleMouseMove);
|
|
397
|
-
document.addEventListener('mouseup', handleMouseUp);
|
|
398
|
-
document.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
399
|
-
document.addEventListener('touchend', handleTouchEnd);
|
|
400
|
-
|
|
401
|
-
return () => {
|
|
402
|
-
document.removeEventListener('mousemove', handleMouseMove);
|
|
403
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
404
|
-
document.removeEventListener('touchmove', handleTouchMove);
|
|
405
|
-
document.removeEventListener('touchend', handleTouchEnd);
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
}, [isDragging, handleDragMove, handleDragEnd]);
|
|
409
|
-
|
|
410
|
-
const getSizeStyles = () => {
|
|
411
|
-
const isHorizontal = position === 'left' || position === 'right';
|
|
412
|
-
|
|
413
|
-
// For draggable variant, use dynamic height based on snap point
|
|
414
|
-
if (isDraggableVariant && isVerticalSheet && currentHeight !== null) {
|
|
415
|
-
return { height: currentHeight };
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Horizontal (left/right) sheets are viewport-aware: a px floor keeps the
|
|
419
|
-
// panel usable, `…vw` lets the larger presets grow on wide screens (lg ≥32%,
|
|
420
|
-
// xl ≥40% of the viewport), and the outer `min(92vw, …)` guarantees the
|
|
421
|
-
// panel never overflows a small screen. Vertical presets stay fixed-height.
|
|
422
|
-
switch (size) {
|
|
423
|
-
case 'xs':
|
|
424
|
-
return isHorizontal ? { width: 'min(92vw, 240px)' } : { height: 200 };
|
|
425
|
-
case 'sm':
|
|
426
|
-
return isHorizontal ? { width: 'min(92vw, 320px)' } : { height: 300 };
|
|
427
|
-
case 'md':
|
|
428
|
-
return isHorizontal ? { width: 'min(92vw, 400px)' } : { height: 400 };
|
|
429
|
-
case 'lg':
|
|
430
|
-
return isHorizontal ? { width: 'min(92vw, max(560px, 32vw))' } : { height: 500 };
|
|
431
|
-
case 'xl':
|
|
432
|
-
return isHorizontal ? { width: 'min(92vw, max(720px, 40vw))' } : { height: 600 };
|
|
433
|
-
case 'full':
|
|
434
|
-
return isHorizontal ? { width: '100%' } : { height: '100%' };
|
|
435
|
-
default:
|
|
436
|
-
return isHorizontal ? { width: 'min(92vw, 400px)' } : { height: 400 };
|
|
437
|
-
}
|
|
81
|
+
return {
|
|
82
|
+
isBottomSheet,
|
|
83
|
+
isTopSheet,
|
|
84
|
+
isVerticalSheet: isBottomSheet || isTopSheet,
|
|
85
|
+
isDraggableVariant: variant === 'draggable',
|
|
438
86
|
};
|
|
87
|
+
};
|
|
439
88
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const hex = hexColor.replace('#', '');
|
|
444
|
-
const r = parseInt(hex.substring(0, 2), 16);
|
|
445
|
-
const g = parseInt(hex.substring(2, 4), 16);
|
|
446
|
-
const b = parseInt(hex.substring(4, 6), 16);
|
|
447
|
-
return `${r}, ${g}, ${b}`;
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
const colorMain = theme.palette[color]?.main || theme.palette.primary.main;
|
|
451
|
-
const colorRgb = getRgbValues(colorMain);
|
|
452
|
-
|
|
453
|
-
const baseStyles = {
|
|
454
|
-
backgroundColor: theme.palette.background.paper,
|
|
455
|
-
transition:
|
|
456
|
-
!isDragging && !isAnimating
|
|
457
|
-
? theme.transitions.create(['all'], {
|
|
458
|
-
duration: theme.transitions.duration.standard,
|
|
459
|
-
easing: theme.transitions.easing.easeInOut,
|
|
460
|
-
})
|
|
461
|
-
: 'none',
|
|
462
|
-
opacity: disabled ? 0.5 : 1,
|
|
463
|
-
pointerEvents: disabled ? ('none' as const) : ('auto' as const),
|
|
464
|
-
cursor: isDraggableVariant && isVerticalSheet ? 'grab' : 'auto',
|
|
465
|
-
'--pulse-color': colorRgb,
|
|
466
|
-
'--glow-color': colorRgb,
|
|
467
|
-
...(isDragging && { cursor: 'grabbing' }),
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
const glowStyles = glow
|
|
471
|
-
? {
|
|
472
|
-
animation: `${glowAnimation} 2s ease-in-out infinite`,
|
|
473
|
-
filter: 'brightness(1.05)',
|
|
474
|
-
}
|
|
475
|
-
: {};
|
|
476
|
-
|
|
477
|
-
const pulseStyles = pulse
|
|
478
|
-
? {
|
|
479
|
-
animation: `${pulseAnimation} 2s infinite`,
|
|
480
|
-
position: 'relative' as const,
|
|
481
|
-
}
|
|
482
|
-
: {};
|
|
483
|
-
|
|
484
|
-
const roundedStyles =
|
|
485
|
-
rounded && (position === 'top' || position === 'bottom')
|
|
486
|
-
? {
|
|
487
|
-
borderRadius:
|
|
488
|
-
position === 'top'
|
|
489
|
-
? `0 0 ${theme.spacing(2)} ${theme.spacing(2)}`
|
|
490
|
-
: `${theme.spacing(2)} ${theme.spacing(2)} 0 0`,
|
|
491
|
-
}
|
|
492
|
-
: rounded && (position === 'left' || position === 'right')
|
|
493
|
-
? {
|
|
494
|
-
borderRadius:
|
|
495
|
-
position === 'left'
|
|
496
|
-
? `0 ${theme.spacing(2)} ${theme.spacing(2)} 0`
|
|
497
|
-
: `${theme.spacing(2)} 0 0 ${theme.spacing(2)}`,
|
|
498
|
-
}
|
|
499
|
-
: {};
|
|
500
|
-
|
|
501
|
-
switch (variant) {
|
|
502
|
-
case 'draggable':
|
|
503
|
-
return {
|
|
504
|
-
...baseStyles,
|
|
505
|
-
...glowStyles,
|
|
506
|
-
...pulseStyles,
|
|
507
|
-
...roundedStyles,
|
|
508
|
-
boxShadow: `
|
|
509
|
-
${theme.shadows[Math.min(elevation + 4, 24)]},
|
|
510
|
-
0 -2px 10px 0 ${alpha(theme.palette.common.black, 0.1)}
|
|
511
|
-
`,
|
|
512
|
-
border: `1px solid ${alpha(theme.palette.divider, 0.12)}`,
|
|
513
|
-
borderTop: `2px solid ${alpha(theme.palette[color]?.main || theme.palette.primary.main, 0.3)}`,
|
|
514
|
-
transition: theme.transitions.create(['transform', 'box-shadow', 'border-color'], {
|
|
515
|
-
duration: theme.transitions.duration.shorter,
|
|
516
|
-
easing: theme.transitions.easing.easeInOut,
|
|
517
|
-
}),
|
|
518
|
-
'&:hover': {
|
|
519
|
-
borderTopColor: alpha(theme.palette[color]?.main || theme.palette.primary.main, 0.5),
|
|
520
|
-
},
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
case 'glass':
|
|
524
|
-
return {
|
|
525
|
-
...baseStyles,
|
|
526
|
-
...glowStyles,
|
|
527
|
-
...pulseStyles,
|
|
528
|
-
...roundedStyles,
|
|
529
|
-
backgroundColor: alpha(theme.palette.background.paper, glass ? 0.75 : 0.95),
|
|
530
|
-
backdropFilter: 'blur(24px) saturate(180%)',
|
|
531
|
-
WebkitBackdropFilter: 'blur(24px) saturate(180%)',
|
|
532
|
-
border: `1px solid ${alpha(theme.palette.divider, 0.15)}`,
|
|
533
|
-
boxShadow: `
|
|
534
|
-
0 8px 32px 0 ${alpha(theme.palette.common.black, 0.15)},
|
|
535
|
-
inset 0 0 0 1px ${alpha(theme.palette.common.white, 0.1)}
|
|
536
|
-
`,
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
case 'gradient':
|
|
540
|
-
return {
|
|
541
|
-
...baseStyles,
|
|
542
|
-
...glowStyles,
|
|
543
|
-
...pulseStyles,
|
|
544
|
-
...roundedStyles,
|
|
545
|
-
background: gradient
|
|
546
|
-
? `linear-gradient(
|
|
547
|
-
135deg,
|
|
548
|
-
${theme.palette.background.paper} 0%,
|
|
549
|
-
${alpha(theme.palette[color]?.main || theme.palette.primary.main, 0.08)} 50%,
|
|
550
|
-
${alpha(theme.palette[color]?.dark || theme.palette[color]?.main || theme.palette.primary.main, 0.12)} 100%
|
|
551
|
-
)`
|
|
552
|
-
: theme.palette.background.paper,
|
|
553
|
-
position: 'relative' as const,
|
|
554
|
-
overflow: 'hidden' as const,
|
|
555
|
-
'&::before': gradient
|
|
556
|
-
? {
|
|
557
|
-
content: '""',
|
|
558
|
-
position: 'absolute',
|
|
559
|
-
top: 0,
|
|
560
|
-
left: '-100%',
|
|
561
|
-
width: '100%',
|
|
562
|
-
height: '100%',
|
|
563
|
-
background: `linear-gradient(
|
|
564
|
-
90deg,
|
|
565
|
-
transparent,
|
|
566
|
-
${alpha(theme.palette.common.white, 0.2)},
|
|
567
|
-
transparent
|
|
568
|
-
)`,
|
|
569
|
-
animation: `${shimmerAnimation} 3s infinite`,
|
|
570
|
-
}
|
|
571
|
-
: {},
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
case 'elevated':
|
|
575
|
-
return {
|
|
576
|
-
...baseStyles,
|
|
577
|
-
...glowStyles,
|
|
578
|
-
...pulseStyles,
|
|
579
|
-
...roundedStyles,
|
|
580
|
-
boxShadow: `
|
|
581
|
-
${theme.shadows[elevation]},
|
|
582
|
-
0 20px 40px -15px ${alpha(theme.palette.common.black, 0.15)}
|
|
583
|
-
`,
|
|
584
|
-
transform: 'translateZ(0)',
|
|
585
|
-
willChange: 'transform',
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
case 'minimal':
|
|
589
|
-
return {
|
|
590
|
-
...baseStyles,
|
|
591
|
-
...glowStyles,
|
|
592
|
-
...pulseStyles,
|
|
593
|
-
...roundedStyles,
|
|
594
|
-
boxShadow: 'none',
|
|
595
|
-
border: 'none',
|
|
596
|
-
};
|
|
89
|
+
export const Sheet: React.FC<SheetProps> = (props) => {
|
|
90
|
+
const resolved = withDefaults(props, DEFAULTS) as ResolvedSheetProps;
|
|
91
|
+
const { position, variant, showOverlay, swipeable, className, dataTestId } = resolved;
|
|
597
92
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
93
|
+
const theme = useTheme();
|
|
94
|
+
const orientation = orientationOf(resolved);
|
|
95
|
+
|
|
96
|
+
// Support both the dataTestId prop and a plain data-testid attribute.
|
|
97
|
+
const testId = (resolved as { 'data-testid'?: string })['data-testid'] || dataTestId;
|
|
98
|
+
|
|
99
|
+
const { sheetRef, currentHeight, isDragging, isAnimating, handleDragStart, resetToDefaultSnapPoint } =
|
|
100
|
+
useSheetDrag({ ...resolved, ...orientation });
|
|
101
|
+
|
|
102
|
+
const { isOpen, handleClose, handleOverlayClick, handleSwipeOpen } = useSheetOpen({
|
|
103
|
+
...resolved,
|
|
104
|
+
...orientation,
|
|
105
|
+
resetToDefaultSnapPoint,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const drawerProps = {
|
|
109
|
+
anchor: position,
|
|
110
|
+
open: isOpen,
|
|
111
|
+
className,
|
|
112
|
+
onClose: handleClose,
|
|
113
|
+
PaperProps: {
|
|
114
|
+
ref: sheetRef,
|
|
115
|
+
sx: panelSx({ ...resolved, ...orientation, theme, currentHeight, isDragging, isAnimating }),
|
|
116
|
+
},
|
|
117
|
+
ModalProps: { keepMounted: true, BackdropComponent: NoBackdrop },
|
|
606
118
|
};
|
|
607
119
|
|
|
608
|
-
const
|
|
120
|
+
const body = (
|
|
121
|
+
<SheetBody
|
|
122
|
+
{...resolved}
|
|
123
|
+
{...orientation}
|
|
124
|
+
testId={testId}
|
|
125
|
+
isDragging={isDragging}
|
|
126
|
+
onClose={handleClose}
|
|
127
|
+
onDragStart={handleDragStart}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
609
130
|
|
|
610
|
-
//
|
|
611
|
-
|
|
612
|
-
|
|
131
|
+
// The draggable variant drives the panel's height itself; MUI's swipe handling
|
|
132
|
+
// would be a second thing moving it.
|
|
133
|
+
const isSwipeable = swipeable && !orientation.isDraggableVariant;
|
|
613
134
|
|
|
614
135
|
return (
|
|
615
136
|
<>
|
|
@@ -619,360 +140,16 @@ export const Sheet: React.FC<SheetProps> = ({
|
|
|
619
140
|
|
|
620
141
|
{isSwipeable ? (
|
|
621
142
|
<SwipeableDrawer
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
onOpenChange?.(true);
|
|
625
|
-
onOpen?.();
|
|
626
|
-
}}
|
|
627
|
-
onClose={handleClose}
|
|
143
|
+
{...drawerProps}
|
|
144
|
+
onOpen={handleSwipeOpen}
|
|
628
145
|
disableSwipeToOpen={false}
|
|
629
146
|
swipeAreaWidth={20}
|
|
630
|
-
anchor={position}
|
|
631
|
-
open={isOpen}
|
|
632
|
-
className={className}
|
|
633
|
-
PaperProps={{
|
|
634
|
-
ref: sheetRef,
|
|
635
|
-
sx: {
|
|
636
|
-
...getSizeStyles(),
|
|
637
|
-
...getVariantStyles(),
|
|
638
|
-
...style,
|
|
639
|
-
overflow: 'visible',
|
|
640
|
-
// Expand only the cross axis so the chosen `size` still governs
|
|
641
|
-
// the main axis: side sheets keep their (responsive) width and go
|
|
642
|
-
// full-height; top/bottom sheets keep their height and go full-width.
|
|
643
|
-
...(fullHeight &&
|
|
644
|
-
!isDraggableVariant &&
|
|
645
|
-
(position === 'left' || position === 'right'
|
|
646
|
-
? { height: '100%' }
|
|
647
|
-
: { width: '100%' })),
|
|
648
|
-
},
|
|
649
|
-
}}
|
|
650
|
-
ModalProps={{
|
|
651
|
-
keepMounted: true,
|
|
652
|
-
BackdropComponent: NoBackdrop,
|
|
653
|
-
}}
|
|
654
147
|
>
|
|
655
|
-
|
|
656
|
-
ref={contentRef}
|
|
657
|
-
onClick={onClick}
|
|
658
|
-
onFocus={onFocus}
|
|
659
|
-
onBlur={onBlur}
|
|
660
|
-
data-testid={testId}
|
|
661
|
-
sx={{
|
|
662
|
-
height: '100%',
|
|
663
|
-
display: 'flex',
|
|
664
|
-
flexDirection: 'column',
|
|
665
|
-
position: 'relative',
|
|
666
|
-
userSelect: isDragging ? 'none' : 'auto',
|
|
667
|
-
}}
|
|
668
|
-
>
|
|
669
|
-
{(showHandle || title || description || showCloseButton || header) && (
|
|
670
|
-
<SheetHeader
|
|
671
|
-
title={title}
|
|
672
|
-
description={description}
|
|
673
|
-
showCloseButton={showCloseButton}
|
|
674
|
-
onClose={handleClose}
|
|
675
|
-
showHandle={showHandle && isVerticalSheet}
|
|
676
|
-
isDraggable={isDraggableVariant && isVerticalSheet}
|
|
677
|
-
onDragStart={handleDragStart}
|
|
678
|
-
dataTestId={`${testId}-header`}
|
|
679
|
-
>
|
|
680
|
-
{header}
|
|
681
|
-
</SheetHeader>
|
|
682
|
-
)}
|
|
683
|
-
|
|
684
|
-
<SheetContent padded dataTestId={`${testId}-content`}>
|
|
685
|
-
{loading ? (
|
|
686
|
-
<Box
|
|
687
|
-
sx={{
|
|
688
|
-
display: 'flex',
|
|
689
|
-
justifyContent: 'center',
|
|
690
|
-
alignItems: 'center',
|
|
691
|
-
minHeight: 200,
|
|
692
|
-
}}
|
|
693
|
-
>
|
|
694
|
-
<CircularProgress color={color} />
|
|
695
|
-
</Box>
|
|
696
|
-
) : (
|
|
697
|
-
children
|
|
698
|
-
)}
|
|
699
|
-
</SheetContent>
|
|
700
|
-
|
|
701
|
-
{footer && (
|
|
702
|
-
<SheetFooter sticky divider dataTestId={`${testId}-footer`}>
|
|
703
|
-
{footer}
|
|
704
|
-
</SheetFooter>
|
|
705
|
-
)}
|
|
706
|
-
</Box>
|
|
148
|
+
{body}
|
|
707
149
|
</SwipeableDrawer>
|
|
708
150
|
) : (
|
|
709
|
-
<Drawer
|
|
710
|
-
onClose={handleClose}
|
|
711
|
-
anchor={position}
|
|
712
|
-
open={isOpen}
|
|
713
|
-
className={className}
|
|
714
|
-
PaperProps={{
|
|
715
|
-
ref: sheetRef,
|
|
716
|
-
sx: {
|
|
717
|
-
...getSizeStyles(),
|
|
718
|
-
...getVariantStyles(),
|
|
719
|
-
...style,
|
|
720
|
-
overflow: 'visible',
|
|
721
|
-
// Expand only the cross axis so the chosen `size` still governs
|
|
722
|
-
// the main axis: side sheets keep their (responsive) width and go
|
|
723
|
-
// full-height; top/bottom sheets keep their height and go full-width.
|
|
724
|
-
...(fullHeight &&
|
|
725
|
-
!isDraggableVariant &&
|
|
726
|
-
(position === 'left' || position === 'right'
|
|
727
|
-
? { height: '100%' }
|
|
728
|
-
: { width: '100%' })),
|
|
729
|
-
},
|
|
730
|
-
}}
|
|
731
|
-
ModalProps={{
|
|
732
|
-
keepMounted: true,
|
|
733
|
-
BackdropComponent: NoBackdrop,
|
|
734
|
-
}}
|
|
735
|
-
>
|
|
736
|
-
<Box
|
|
737
|
-
ref={contentRef}
|
|
738
|
-
onClick={onClick}
|
|
739
|
-
onFocus={onFocus}
|
|
740
|
-
onBlur={onBlur}
|
|
741
|
-
data-testid={testId}
|
|
742
|
-
sx={{
|
|
743
|
-
height: '100%',
|
|
744
|
-
display: 'flex',
|
|
745
|
-
flexDirection: 'column',
|
|
746
|
-
position: 'relative',
|
|
747
|
-
userSelect: isDragging ? 'none' : 'auto',
|
|
748
|
-
}}
|
|
749
|
-
>
|
|
750
|
-
{(showHandle || title || description || showCloseButton || header) && (
|
|
751
|
-
<SheetHeader
|
|
752
|
-
title={title}
|
|
753
|
-
description={description}
|
|
754
|
-
showCloseButton={showCloseButton}
|
|
755
|
-
onClose={handleClose}
|
|
756
|
-
showHandle={showHandle && isVerticalSheet}
|
|
757
|
-
isDraggable={isDraggableVariant && isVerticalSheet}
|
|
758
|
-
onDragStart={handleDragStart}
|
|
759
|
-
dataTestId={`${testId}-header`}
|
|
760
|
-
>
|
|
761
|
-
{header}
|
|
762
|
-
</SheetHeader>
|
|
763
|
-
)}
|
|
764
|
-
|
|
765
|
-
<SheetContent padded dataTestId={`${testId}-content`}>
|
|
766
|
-
{loading ? (
|
|
767
|
-
<Box
|
|
768
|
-
sx={{
|
|
769
|
-
display: 'flex',
|
|
770
|
-
justifyContent: 'center',
|
|
771
|
-
alignItems: 'center',
|
|
772
|
-
minHeight: 200,
|
|
773
|
-
}}
|
|
774
|
-
>
|
|
775
|
-
<CircularProgress color={color} />
|
|
776
|
-
</Box>
|
|
777
|
-
) : (
|
|
778
|
-
children
|
|
779
|
-
)}
|
|
780
|
-
</SheetContent>
|
|
781
|
-
|
|
782
|
-
{footer && (
|
|
783
|
-
<SheetFooter sticky divider dataTestId={`${testId}-footer`}>
|
|
784
|
-
{footer}
|
|
785
|
-
</SheetFooter>
|
|
786
|
-
)}
|
|
787
|
-
</Box>
|
|
788
|
-
</Drawer>
|
|
789
|
-
)}
|
|
790
|
-
</>
|
|
791
|
-
);
|
|
792
|
-
};
|
|
793
|
-
|
|
794
|
-
export const SheetHeader: React.FC<SheetHeaderProps> = ({
|
|
795
|
-
title,
|
|
796
|
-
description,
|
|
797
|
-
showCloseButton,
|
|
798
|
-
onClose,
|
|
799
|
-
showHandle,
|
|
800
|
-
isDraggable = false,
|
|
801
|
-
onDragStart,
|
|
802
|
-
className,
|
|
803
|
-
style,
|
|
804
|
-
children,
|
|
805
|
-
dataTestId = 'sheet-header',
|
|
806
|
-
}) => {
|
|
807
|
-
const theme = useTheme();
|
|
808
|
-
|
|
809
|
-
return (
|
|
810
|
-
<Box
|
|
811
|
-
className={className}
|
|
812
|
-
data-testid={dataTestId}
|
|
813
|
-
sx={{
|
|
814
|
-
p: 2,
|
|
815
|
-
pb: showHandle ? 1 : 2,
|
|
816
|
-
cursor: isDraggable ? 'grab' : 'auto',
|
|
817
|
-
'&:active': isDraggable ? { cursor: 'grabbing' } : {},
|
|
818
|
-
...style,
|
|
819
|
-
}}
|
|
820
|
-
onMouseDown={isDraggable ? onDragStart : undefined}
|
|
821
|
-
onTouchStart={isDraggable ? onDragStart : undefined}
|
|
822
|
-
>
|
|
823
|
-
{showHandle && (
|
|
824
|
-
<Box
|
|
825
|
-
sx={{
|
|
826
|
-
display: 'flex',
|
|
827
|
-
justifyContent: 'center',
|
|
828
|
-
mb: 1,
|
|
829
|
-
pointerEvents: isDraggable ? 'none' : 'auto',
|
|
830
|
-
}}
|
|
831
|
-
>
|
|
832
|
-
<Box
|
|
833
|
-
sx={{
|
|
834
|
-
width: isDraggable ? 48 : 32,
|
|
835
|
-
height: isDraggable ? 6 : 4,
|
|
836
|
-
backgroundColor: alpha(theme.palette.text.primary, 0.3),
|
|
837
|
-
borderRadius: 3,
|
|
838
|
-
transition: theme.transitions.create(['all'], {
|
|
839
|
-
duration: theme.transitions.duration.short,
|
|
840
|
-
easing: theme.transitions.easing.easeInOut,
|
|
841
|
-
}),
|
|
842
|
-
position: 'relative',
|
|
843
|
-
overflow: 'hidden',
|
|
844
|
-
'&::after': {
|
|
845
|
-
content: '""',
|
|
846
|
-
position: 'absolute',
|
|
847
|
-
top: 0,
|
|
848
|
-
left: '-100%',
|
|
849
|
-
width: '100%',
|
|
850
|
-
height: '100%',
|
|
851
|
-
background: `linear-gradient(
|
|
852
|
-
90deg,
|
|
853
|
-
transparent,
|
|
854
|
-
${alpha(theme.palette.common.white, 0.3)},
|
|
855
|
-
transparent
|
|
856
|
-
)`,
|
|
857
|
-
animation: isDraggable ? `${shimmerAnimation} 2s infinite` : 'none',
|
|
858
|
-
},
|
|
859
|
-
...(isDraggable && {
|
|
860
|
-
backgroundColor: alpha(theme.palette.primary.main, 0.4),
|
|
861
|
-
boxShadow: `0 2px 4px ${alpha(theme.palette.primary.main, 0.2)}`,
|
|
862
|
-
'&:hover': {
|
|
863
|
-
backgroundColor: theme.palette.primary.main,
|
|
864
|
-
transform: 'scaleX(1.15) scaleY(1.2)',
|
|
865
|
-
boxShadow: `0 3px 6px ${alpha(theme.palette.primary.main, 0.3)}`,
|
|
866
|
-
},
|
|
867
|
-
}),
|
|
868
|
-
}}
|
|
869
|
-
/>
|
|
870
|
-
</Box>
|
|
151
|
+
<Drawer {...drawerProps}>{body}</Drawer>
|
|
871
152
|
)}
|
|
872
|
-
|
|
873
|
-
<Box
|
|
874
|
-
sx={{
|
|
875
|
-
display: 'flex',
|
|
876
|
-
alignItems: 'flex-start',
|
|
877
|
-
justifyContent: 'space-between',
|
|
878
|
-
}}
|
|
879
|
-
>
|
|
880
|
-
<Box sx={{ flex: 1 }}>
|
|
881
|
-
{title && (
|
|
882
|
-
<Typography variant="h6" sx={{ fontWeight: 600 }} data-testid={`${dataTestId}-title`}>
|
|
883
|
-
{title}
|
|
884
|
-
</Typography>
|
|
885
|
-
)}
|
|
886
|
-
{description && (
|
|
887
|
-
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
|
|
888
|
-
{description}
|
|
889
|
-
</Typography>
|
|
890
|
-
)}
|
|
891
|
-
{children}
|
|
892
|
-
</Box>
|
|
893
|
-
|
|
894
|
-
{showCloseButton && (
|
|
895
|
-
<IconButton onClick={onClose} size="small" sx={{ ml: 1, mt: -0.5 }} data-testid={`${dataTestId}-close-button`}>
|
|
896
|
-
<CloseIcon data-testid={`${dataTestId}-close-icon`} />
|
|
897
|
-
</IconButton>
|
|
898
|
-
)}
|
|
899
|
-
</Box>
|
|
900
|
-
</Box>
|
|
901
|
-
);
|
|
902
|
-
};
|
|
903
|
-
|
|
904
|
-
export const SheetContent: React.FC<SheetContentProps> = ({
|
|
905
|
-
children,
|
|
906
|
-
className,
|
|
907
|
-
style,
|
|
908
|
-
padded = true,
|
|
909
|
-
dataTestId = 'sheet-content',
|
|
910
|
-
}) => (
|
|
911
|
-
<Box
|
|
912
|
-
className={className}
|
|
913
|
-
data-testid={dataTestId}
|
|
914
|
-
sx={{
|
|
915
|
-
flex: 1,
|
|
916
|
-
overflow: 'auto',
|
|
917
|
-
p: padded ? 2 : 0,
|
|
918
|
-
...style,
|
|
919
|
-
}}
|
|
920
|
-
>
|
|
921
|
-
{children}
|
|
922
|
-
</Box>
|
|
923
|
-
);
|
|
924
|
-
|
|
925
|
-
export const SheetFooter: React.FC<SheetFooterProps> = ({
|
|
926
|
-
children,
|
|
927
|
-
className,
|
|
928
|
-
style,
|
|
929
|
-
sticky = false,
|
|
930
|
-
divider = false,
|
|
931
|
-
dataTestId = 'sheet-footer',
|
|
932
|
-
}) => (
|
|
933
|
-
<>
|
|
934
|
-
{divider && <Divider />}
|
|
935
|
-
<Box
|
|
936
|
-
className={className}
|
|
937
|
-
data-testid={dataTestId}
|
|
938
|
-
sx={{
|
|
939
|
-
p: 2,
|
|
940
|
-
position: sticky ? 'sticky' : 'relative',
|
|
941
|
-
bottom: sticky ? 0 : 'auto',
|
|
942
|
-
backgroundColor: 'background.paper',
|
|
943
|
-
borderTop: sticky ? '1px solid' : 'none',
|
|
944
|
-
borderColor: 'divider',
|
|
945
|
-
...style,
|
|
946
|
-
}}
|
|
947
|
-
>
|
|
948
|
-
{children}
|
|
949
|
-
</Box>
|
|
950
153
|
</>
|
|
951
154
|
);
|
|
952
|
-
|
|
953
|
-
export const SheetOverlay: React.FC<SheetOverlayProps> = ({
|
|
954
|
-
open = false,
|
|
955
|
-
onClick,
|
|
956
|
-
className,
|
|
957
|
-
style,
|
|
958
|
-
blur = false,
|
|
959
|
-
}) => {
|
|
960
|
-
const theme = useTheme();
|
|
961
|
-
|
|
962
|
-
return (
|
|
963
|
-
<Fade in={open} timeout={300}>
|
|
964
|
-
<Backdrop
|
|
965
|
-
open={open}
|
|
966
|
-
onClick={onClick}
|
|
967
|
-
className={className}
|
|
968
|
-
sx={{
|
|
969
|
-
zIndex: theme.zIndex.drawer - 1,
|
|
970
|
-
backgroundColor: alpha(theme.palette.common.black, blur ? 0.6 : 0.5),
|
|
971
|
-
backdropFilter: blur ? 'blur(8px) saturate(180%)' : 'none',
|
|
972
|
-
WebkitBackdropFilter: blur ? 'blur(8px) saturate(180%)' : 'none',
|
|
973
|
-
...style,
|
|
974
|
-
}}
|
|
975
|
-
/>
|
|
976
|
-
</Fade>
|
|
977
|
-
);
|
|
978
155
|
};
|