@a-type/ui 6.1.16 → 6.1.18

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 (41) hide show
  1. package/dist/components/button/ConfirmedButton.js +2 -2
  2. package/dist/components/button/ConfirmedButton.js.map +1 -1
  3. package/dist/components/datePicker/DatePicker.d.ts +1 -1
  4. package/dist/components/datePicker/DatePicker.stories.d.ts +1 -1
  5. package/dist/components/datePicker/DateRangePicker.d.ts +1 -1
  6. package/dist/components/datePicker/DateRangePicker.js +15 -1
  7. package/dist/components/datePicker/DateRangePicker.js.map +1 -1
  8. package/dist/components/dialog/Dialog.d.ts +20 -34
  9. package/dist/components/dialog/Dialog.js +47 -208
  10. package/dist/components/dialog/Dialog.js.map +1 -1
  11. package/dist/components/dialog/Dialog.module.css +10 -82
  12. package/dist/components/dialog/Dialog.stories.js +8 -7
  13. package/dist/components/dialog/Dialog.stories.js.map +1 -1
  14. package/dist/components/drawer/Drawer.d.ts +25 -0
  15. package/dist/components/drawer/Drawer.js +102 -0
  16. package/dist/components/drawer/Drawer.js.map +1 -0
  17. package/dist/components/drawer/Drawer.module.css +182 -0
  18. package/dist/components/drawer/Drawer.stories.d.ts +7 -0
  19. package/dist/components/drawer/Drawer.stories.js +27 -0
  20. package/dist/components/drawer/Drawer.stories.js.map +1 -0
  21. package/dist/components/drawer/index.d.ts +1 -0
  22. package/dist/components/drawer/index.js +2 -0
  23. package/dist/components/drawer/index.js.map +1 -0
  24. package/dist/components/index.d.ts +1 -0
  25. package/dist/components/index.js +1 -0
  26. package/dist/components/index.js.map +1 -1
  27. package/dist/hooks/withContextVariant.d.ts +8 -0
  28. package/dist/hooks/withContextVariant.js +21 -0
  29. package/dist/hooks/withContextVariant.js.map +1 -0
  30. package/package.json +2 -2
  31. package/src/components/button/ConfirmedButton.tsx +9 -16
  32. package/src/components/datePicker/DateRangePicker.tsx +18 -1
  33. package/src/components/dialog/Dialog.module.css +10 -82
  34. package/src/components/dialog/Dialog.stories.tsx +45 -51
  35. package/src/components/dialog/Dialog.tsx +98 -332
  36. package/src/components/drawer/Drawer.module.css +181 -0
  37. package/src/components/drawer/Drawer.stories.tsx +68 -0
  38. package/src/components/drawer/Drawer.tsx +204 -0
  39. package/src/components/drawer/index.ts +1 -0
  40. package/src/components/index.ts +1 -0
  41. package/src/hooks/withContextVariant.tsx +24 -0
@@ -1,3 +1,4 @@
1
+ import { UseRenderRenderProp } from '@base-ui/react';
1
2
  import {
2
3
  Dialog as BaseDialog,
3
4
  DialogCloseProps,
@@ -5,187 +6,70 @@ import {
5
6
  DialogRootProps,
6
7
  DialogTriggerProps,
7
8
  } from '@base-ui/react/dialog';
9
+ import { DrawerRootProps } from '@base-ui/react/drawer';
8
10
  import { Radio as BaseRadio } from '@base-ui/react/radio';
9
11
  import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';
10
- import { useDrag } from '@use-gesture/react';
11
12
  import clsx from 'clsx';
12
- import {
13
- ComponentPropsWithoutRef,
14
- createContext,
15
- Ref,
16
- TouchEvent,
17
- useCallback,
18
- useContext,
19
- useRef,
20
- useState,
21
- } from 'react';
13
+ import { ComponentPropsWithoutRef, Ref, useCallback, useState } from 'react';
22
14
  import { useMediaQuery } from '../../hooks/useMediaQuery.js';
23
- import useMergedRef from '../../hooks/useMergedRef.js';
24
15
  import { withClassName } from '../../hooks/withClassName.js';
16
+ import { createWithContextVariant } from '../../hooks/withContextVariant.js';
25
17
  import { Box } from '../box/Box.js';
26
18
  import { Button } from '../button/index.js';
19
+ import { Drawer } from '../drawer/Drawer.js';
20
+ import drawerCls from '../drawer/Drawer.module.css';
27
21
  import { Icon } from '../icon/Icon.js';
28
- import { useParticles } from '../particles/index.js';
29
22
  import menuCls from '../primitives/menus.module.css';
30
- import { useConfig } from '../provider/Provider.js';
31
23
  import { ScrollArea } from '../scrollArea/ScrollArea.js';
32
24
  import { selectTriggerClassName } from '../select/index.js';
33
25
  import cls from './Dialog.module.css';
34
26
 
35
- const StyledOverlay = withClassName(BaseDialog.Backdrop, cls.overlay);
27
+ const { Context: MobileDrawerContext, withContextVariant } =
28
+ createWithContextVariant();
36
29
 
30
+ // baseline Dialog-specific component implementations
31
+ const StyledOverlay = withClassName(BaseDialog.Backdrop, cls.overlay);
37
32
  const StyledContent = withClassName(BaseDialog.Popup, cls.content);
38
-
39
- function sheetCloseGestureLogic(
40
- swipeY: number,
41
- dy: number,
42
- last: boolean,
43
- close: () => void,
44
- content: HTMLElement,
45
- ) {
46
- const contentHeight = content.clientHeight;
47
-
48
- const shouldClose = last && (swipeY === 1 || dy > contentHeight / 2);
49
- if (shouldClose) {
50
- close();
51
- }
52
- const gestureY = last ? (shouldClose ? -1000 : 0) : -Math.max(0, dy);
53
- content.style.setProperty('--gesture-y', `${gestureY}px`);
54
- content.style.setProperty('transition', last ? 'bottom 0.2s' : '');
55
- }
56
-
57
- // filter out gestures that are within scrollable descendants
58
- // if those elements are not scrolled to the top already
59
- function filterScrollables(
60
- target: HTMLElement,
61
- root: HTMLElement,
62
- dy: number,
63
- vy: number,
64
- ) {
65
- // always allow upward swipes
66
- if (vy < 0) {
67
- return false;
68
- }
69
-
70
- let cur: HTMLElement | null = target;
71
- while (cur) {
72
- if (cur === root) return false;
73
- cur = cur.parentElement;
74
- if (
75
- cur &&
76
- cur.scrollHeight > cur.clientHeight &&
77
- cur.scrollTop !== 0 &&
78
- vy >= 0
79
- )
80
- return true;
81
- }
82
- return false;
83
- }
84
-
85
- export interface DialogContentProps extends DialogPopupProps {
33
+ export interface DialogContentProps
34
+ extends Omit<DialogPopupProps, 'className' | 'style' | 'render'> {
86
35
  width?: 'sm' | 'md' | 'lg';
87
- disableSheet?: boolean;
88
36
  disableDefaultClose?: boolean;
89
37
  /** @deprecated */
90
38
  outerClassName?: string;
91
39
  ref?: Ref<HTMLDivElement>;
92
40
  innerClassName?: string;
41
+ className?: string;
42
+ style?: React.CSSProperties;
43
+ // minimal state overlap between drawer and dialog primitives
44
+ render?: UseRenderRenderProp<{ open: boolean }>;
93
45
  }
94
-
95
- const SWIPE_VELOCITY_THRESHOLD = 1.5;
96
-
97
- export const Content = function Content({
46
+ const FunctionalContent = function Content({
98
47
  ref,
99
48
  children,
100
49
  width,
101
50
  outerClassName,
102
51
  className,
103
- disableSheet,
104
52
  disableDefaultClose,
105
53
  innerClassName,
106
54
  ...props
107
55
  }: DialogContentProps) {
108
- const particles = useParticles();
109
- const wasOpenRef = useRef(false);
110
- const openRef = useCallback(
111
- (element: HTMLDivElement | null) => {
112
- if (!wasOpenRef.current && element?.hasAttribute('data-open')) {
113
- wasOpenRef.current = true;
114
-
115
- const matchesSmall =
116
- !disableSheet &&
117
- typeof window !== 'undefined' &&
118
- !window.matchMedia('(min-width:600px)').matches;
119
- if (!matchesSmall) return;
120
-
121
- setTimeout(() => {
122
- particles?.addParticles(
123
- particles.elementExplosion({
124
- count: 20,
125
- margin: 40,
126
- borders: ['top'],
127
- color: [
128
- {
129
- space: 'rgb',
130
- values: [0, 0, 0],
131
- opacity: 0.02,
132
- },
133
- {
134
- space: 'rgb',
135
- values: [0, 0, 0],
136
- opacity: 0,
137
- },
138
- ],
139
- element,
140
- startRadius: 15,
141
- endRadius: 0,
142
- lifespan: 1000,
143
- force: 0.5,
144
- drag: 0.01,
145
- forceFuzz: 0.5,
146
- angleFuzz: 0.1,
147
- }),
148
- );
149
- }, 180);
150
- } else if (!element?.hasAttribute('data-open')) {
151
- wasOpenRef.current = false;
152
- }
153
- },
154
- [particles, disableSheet],
155
- );
156
-
157
- const { gestureRef, onTouchStart, onTouchMove, onTouchEnd } =
158
- useDialogInherentSwipe({ disableSheet });
159
- const finalRef = useMergedRef(ref, openRef, gestureRef);
160
-
161
- const { virtualKeyboardBehavior } = useConfig();
162
-
163
56
  return (
164
57
  <BaseDialog.Portal>
165
58
  <StyledOverlay />
166
59
  <StyledContent
167
- data-dialog-content
168
- ref={finalRef}
60
+ ref={ref}
169
61
  {...props}
170
- onTouchStart={onTouchStart}
171
- onTouchMove={onTouchMove}
172
- onTouchEnd={onTouchEnd}
173
- onTouchCancel={onTouchEnd}
174
- data-disable-sheet={disableSheet ? 'true' : undefined}
175
- data-keyboard-behavior={virtualKeyboardBehavior}
176
62
  data-width={width}
177
63
  className={clsx(outerClassName || className)}
178
64
  >
179
- {!disableDefaultClose && (
180
- <DialogDefaultClose showOnMobile={disableSheet} />
181
- )}
182
- {!disableSheet && <DialogSwipeHandle />}
183
- <ScrollArea direction="vertical" className={cls.contentScrollArea}>
65
+ {!disableDefaultClose && <DialogDefaultClose />}
66
+ <ScrollArea
67
+ direction="vertical"
68
+ className={drawerCls.contentScrollArea}
69
+ >
184
70
  <ScrollArea.Content
185
- className={clsx(cls.contentScrollAreaContent, innerClassName)}
186
- style={{
187
- minWidth: undefined,
188
- }}
71
+ className={clsx(drawerCls.contentScrollAreaContent, innerClassName)}
72
+ style={{ minWidth: undefined }}
189
73
  >
190
74
  {children}
191
75
  </ScrollArea.Content>
@@ -194,140 +78,7 @@ export const Content = function Content({
194
78
  </BaseDialog.Portal>
195
79
  );
196
80
  };
197
-
198
- function useDialogInherentSwipe({ disableSheet }: { disableSheet?: boolean }) {
199
- const gestureRef = useRef<HTMLDivElement>(null);
200
-
201
- const close = useContext(DialogCloseContext);
202
- const isSmall = useMediaQuery('(max-width: 640px)');
203
-
204
- const gestureState = useRef({
205
- sy: 0,
206
- dy: 0,
207
- vy: 0,
208
- active: false,
209
- timeStamp: 0,
210
- filtered: false,
211
- });
212
-
213
- const onTouchStart = useCallback(
214
- (event: TouchEvent) => {
215
- if (!isSmall || disableSheet) return;
216
- const touch = event.touches[0];
217
- gestureState.current.sy = touch.clientY;
218
- gestureState.current.timeStamp = event.timeStamp;
219
- },
220
- [isSmall, disableSheet],
221
- );
222
- const onTouchMove = useCallback(
223
- (event: TouchEvent) => {
224
- if (!isSmall || disableSheet) return;
225
-
226
- const touch = event.touches[0];
227
- const dy = touch.clientY - gestureState.current.sy;
228
- const vy =
229
- (dy - gestureState.current.dy) /
230
- (event.timeStamp -
231
- (gestureState.current.timeStamp ?? event.timeStamp - 1));
232
-
233
- gestureState.current.dy = dy;
234
- gestureState.current.vy = vy;
235
-
236
- if (
237
- gestureState.current.filtered ||
238
- filterScrollables(
239
- event.target as HTMLElement,
240
- gestureRef.current!,
241
- dy,
242
- vy,
243
- )
244
- ) {
245
- gestureState.current.filtered = true;
246
- return;
247
- }
248
- if (!gestureState.current.active) {
249
- gestureState.current.active = true;
250
- gestureState.current.sy = touch.clientY;
251
- }
252
-
253
- if (gestureRef.current && gestureRef.current.scrollTop < 3) {
254
- sheetCloseGestureLogic(
255
- Math.abs(vy) > SWIPE_VELOCITY_THRESHOLD ? Math.sign(vy) : 0,
256
- dy,
257
- false,
258
- close,
259
- gestureRef.current,
260
- );
261
- }
262
-
263
- gestureState.current.timeStamp = event.timeStamp;
264
- },
265
- [isSmall, disableSheet, close],
266
- );
267
- const onTouchEnd = useCallback(
268
- (event: TouchEvent) => {
269
- if (gestureState.current.active && gestureRef.current) {
270
- const { vy, dy } = gestureState.current;
271
- sheetCloseGestureLogic(
272
- Math.abs(vy) > SWIPE_VELOCITY_THRESHOLD ? Math.sign(vy) : 0,
273
- dy,
274
- true,
275
- close,
276
- gestureRef.current,
277
- );
278
- }
279
- gestureState.current.active = false;
280
- gestureState.current.filtered = false;
281
- gestureState.current.timeStamp = event.timeStamp;
282
- gestureState.current.vy = 0;
283
- gestureState.current.dy = 0;
284
- gestureState.current.sy = 0;
285
- },
286
- [close],
287
- );
288
-
289
- return { gestureRef, onTouchStart, onTouchMove, onTouchEnd };
290
- }
291
-
292
- export function DialogSwipeHandle({
293
- ref,
294
- className,
295
- ...props
296
- }: ComponentPropsWithoutRef<'div'> & {
297
- ref?: React.Ref<HTMLDivElement>;
298
- }) {
299
- const close = useContext(DialogCloseContext);
300
- const innerRef = useRef<HTMLDivElement>(null);
301
- useDrag(
302
- ({ swipe: [, swipeY], movement: [, dy], last }) => {
303
- const content = findParentDialogContent(innerRef.current);
304
- if (!content) return;
305
- sheetCloseGestureLogic(swipeY, dy, last, close, content);
306
- },
307
- {
308
- target: innerRef,
309
- axis: 'y',
310
- },
311
- );
312
- const finalRef = useMergedRef(ref, innerRef);
313
- return (
314
- <div ref={finalRef} {...props} className={clsx(cls.swipeHandle, className)}>
315
- <div className={cls.swipeHandleBar} />
316
- </div>
317
- );
318
- }
319
-
320
- function findParentDialogContent(
321
- element: HTMLElement | null,
322
- ): HTMLElement | null {
323
- if (!element) return null;
324
- if (element.getAttribute('data-dialog-content')) return element;
325
- return findParentDialogContent(element.parentElement);
326
- }
327
-
328
- const DialogCloseContext = createContext<() => void>(() => {});
329
-
330
- export const DialogDefaultClose = function DialogDefaultClose({
81
+ const DialogDefaultClose = function DialogDefaultClose({
331
82
  ref,
332
83
  className,
333
84
  showOnMobile,
@@ -349,55 +100,15 @@ export const DialogDefaultClose = function DialogDefaultClose({
349
100
  </DialogClose>
350
101
  );
351
102
  };
352
-
353
- const StyledTitle = withClassName(BaseDialog.Title, cls.title);
354
-
103
+ const StyledDialogTitle = withClassName(BaseDialog.Title, cls.title);
355
104
  const StyledDescription = withClassName(
356
105
  BaseDialog.Description,
357
106
  cls.description,
358
107
  );
359
-
360
- // Exports
361
- const DialogRoot = (props: DialogRootProps) => {
362
- const [innerOpen, innerOnOpenChange] = useState(props.defaultOpen || false);
363
- const open = props.open ?? innerOpen;
364
- const onOpenChange = useCallback<
365
- Exclude<DialogRootProps['onOpenChange'], undefined>
366
- >(
367
- (open, eventDetails) => {
368
- innerOnOpenChange(open);
369
- props.onOpenChange?.(open, eventDetails);
370
- },
371
- [props.onOpenChange],
372
- );
373
-
374
- const close = useCallback(() => {
375
- onOpenChange(false, {
376
- allowPropagation() {},
377
- cancel() {},
378
- isCanceled: false,
379
- event: null as any,
380
- isPropagationAllowed: true,
381
- preventUnmountOnClose() {},
382
- reason: 'imperative-action',
383
- trigger: undefined,
384
- });
385
- }, [onOpenChange]);
386
-
387
- return (
388
- <DialogCloseContext.Provider value={close}>
389
- <BaseDialog.Root {...props} open={open} onOpenChange={onOpenChange} />
390
- </DialogCloseContext.Provider>
391
- );
392
- };
393
-
394
- export const DialogTrigger = ({ render, ...props }: DialogTriggerProps) => (
108
+ const StyledTrigger = ({ render, ...props }: DialogTriggerProps) => (
395
109
  <BaseDialog.Trigger render={render || <Button />} {...props} />
396
110
  );
397
- export const DialogContent = Content;
398
- export const DialogTitle = StyledTitle;
399
- export const DialogDescription = StyledDescription;
400
- export const DialogClose = function DialogClose({
111
+ const StyledClose = function DialogClose({
401
112
  children,
402
113
  render,
403
114
  ...props
@@ -413,12 +124,70 @@ export const DialogClose = function DialogClose({
413
124
  </BaseDialog.Close>
414
125
  );
415
126
  };
127
+ const StyledActions = withClassName(Box, cls.actions);
416
128
 
417
- export type { DialogRootProps as DialogProps } from '@base-ui/react/dialog';
129
+ // Variant Dialog/Drawer component wrappers
130
+ const DialogContent = withContextVariant<DialogContentProps>(
131
+ Drawer.Content,
132
+ FunctionalContent,
133
+ );
134
+ const DialogTitle = withContextVariant(Drawer.Title, StyledDialogTitle);
135
+ const DialogDescription = withContextVariant(
136
+ Drawer.Description,
137
+ StyledDescription,
138
+ );
418
139
 
419
- export const DialogActions = withClassName(Box, cls.actions);
140
+ export interface DialogProps extends Omit<DialogRootProps, 'children'> {
141
+ children: React.ReactNode;
142
+ disableSheet?: boolean;
143
+ }
144
+ const DialogRoot = ({
145
+ children,
146
+ defaultOpen,
147
+ disableSheet,
148
+ ...rootProps
149
+ }: DialogProps) => {
150
+ const [innerOpen, innerOnOpenChange] = useState(defaultOpen || false);
151
+ const open = rootProps.open ?? innerOpen;
152
+ const onOpenChange = useCallback<
153
+ Exclude<DialogRootProps['onOpenChange'], undefined>
154
+ >(
155
+ (open, eventDetails) => {
156
+ innerOnOpenChange(open);
157
+ rootProps.onOpenChange?.(open, eventDetails);
158
+ },
159
+ [rootProps.onOpenChange],
160
+ );
161
+
162
+ const isSmall = useMediaQuery('(max-width: 600px)');
163
+ const useDrawer = isSmall && !disableSheet;
164
+
165
+ if (useDrawer) {
166
+ return (
167
+ <MobileDrawerContext.Provider value={true}>
168
+ <Drawer
169
+ {...rootProps}
170
+ open={open}
171
+ onOpenChange={onOpenChange as DrawerRootProps['onOpenChange']}
172
+ >
173
+ {children}
174
+ </Drawer>
175
+ </MobileDrawerContext.Provider>
176
+ );
177
+ }
420
178
 
421
- export const DialogSelectTrigger = function DialogSelectTrigger({
179
+ return (
180
+ <BaseDialog.Root {...rootProps} open={open} onOpenChange={onOpenChange}>
181
+ {children}
182
+ </BaseDialog.Root>
183
+ );
184
+ };
185
+ const DialogTrigger = withContextVariant(Drawer.Trigger, StyledTrigger);
186
+ const DialogClose = withContextVariant(Drawer.Close, StyledClose);
187
+ const DialogActions = withContextVariant(Drawer.Actions, StyledActions);
188
+
189
+ // "Select" stuff - deprecate soon?
190
+ const DialogSelectTrigger = function DialogSelectTrigger({
422
191
  children,
423
192
  className,
424
193
  ...props
@@ -426,21 +195,18 @@ export const DialogSelectTrigger = function DialogSelectTrigger({
426
195
  ref?: React.Ref<HTMLButtonElement>;
427
196
  }) {
428
197
  return (
429
- <BaseDialog.Trigger
198
+ <DialogTrigger
430
199
  className={clsx(selectTriggerClassName, className)}
431
200
  {...props}
432
201
  >
433
202
  <span>{children}</span>
434
203
  <Icon name="chevron" />
435
- </BaseDialog.Trigger>
204
+ </DialogTrigger>
436
205
  );
437
206
  };
438
-
439
- export const DialogSelectList = withClassName(BaseRadioGroup, menuCls.itemList);
440
-
441
- export const DialogSelectItemRoot = withClassName(BaseRadio.Root, menuCls.item);
442
-
443
- export const DialogSelectItem = function DialogSelectItem({
207
+ const DialogSelectList = withClassName(BaseRadioGroup, menuCls.itemList);
208
+ const DialogSelectItemRoot = withClassName(BaseRadio.Root, menuCls.item);
209
+ const DialogSelectItem = function DialogSelectItem({
444
210
  children,
445
211
  ...props
446
212
  }: ComponentPropsWithoutRef<typeof DialogSelectItemRoot> & {
@@ -458,9 +224,9 @@ export const DialogSelectItem = function DialogSelectItem({
458
224
 
459
225
  export const Dialog = Object.assign(DialogRoot, {
460
226
  Trigger: DialogTrigger,
461
- Content,
462
- Title: StyledTitle,
463
- Description: StyledDescription,
227
+ Content: DialogContent,
228
+ Title: DialogTitle,
229
+ Description: DialogDescription,
464
230
  Close: DialogClose,
465
231
  Actions: DialogActions,
466
232
  SelectTrigger: DialogSelectTrigger,
@@ -0,0 +1,181 @@
1
+ .overlay {
2
+ position: fixed;
3
+ inset: 0;
4
+ @apply --mx-shadow;
5
+ @apply --mx-bg(var(--m-gray-ink));
6
+ @apply --mx-bg-faded(5%);
7
+ border-top: var(--m-lw) solid var(--m-gray);
8
+ transition: all var(--m-dur) var(--m-ease);
9
+ backdrop-filter: blur(2px);
10
+
11
+ &[data-starting-style],
12
+ &[data-ending-style] {
13
+ opacity: 0;
14
+ }
15
+ }
16
+
17
+ .viewport {
18
+ --bleed: 100px;
19
+
20
+ position: fixed;
21
+ inset: 0;
22
+ display: flex;
23
+ align-items: flex-end;
24
+ justify-content: center;
25
+ touch-action: none;
26
+
27
+ &::after {
28
+ content: '';
29
+ position: fixed;
30
+ inset-inline: 0;
31
+ bottom: 0;
32
+ height: var(--bleed);
33
+ @apply --mx-bg(var(--m-surface-ambient-bg));
34
+ pointer-events: none;
35
+ }
36
+
37
+ &[data-closed]::after {
38
+ opacity: 0;
39
+ }
40
+ }
41
+
42
+ .popup {
43
+ box-sizing: border-box;
44
+ position: relative;
45
+ z-index: 1;
46
+ max-width: unset;
47
+ width: 100%;
48
+ max-height: 85svh;
49
+
50
+ display: flex;
51
+ flex-direction: column;
52
+ align-items: stretch;
53
+
54
+ touch-action: none;
55
+
56
+ overflow: visible;
57
+
58
+ transition:
59
+ transform var(--m-dur) var(--m-ease),
60
+ opacity var(--m-dur) var(--m-ease);
61
+
62
+ --shadow-dir: -1;
63
+ @apply --mx-shadow;
64
+ --mx-shadow-value: 0 calc(var(--shadow-dir, 1) * var(--m-shadow-lg-y))
65
+ var(--m-shadow-lg-blur) var(--m-shadow-lg-spread) var(--m-shadow-lg-color);
66
+
67
+ @apply --mx-bg(var(--m-surface-ambient-bg));
68
+ @apply --mx-borderColor(var(--m-gray-heavy));
69
+
70
+ border-radius: var(--m-surface-rd) var(--m-surface-rd) 0 0;
71
+ border-bottom-width: 0;
72
+ padding-bottom: max(
73
+ 0px,
74
+ calc(var(--drawer-snap-point-offset) + var(--drawer-swipe-movement-y))
75
+ );
76
+
77
+ /* Use drawer's built-in swipe movement for animation */
78
+ transform: translateY(
79
+ calc(
80
+ var(--drawer-swipe-movement-y, 0px) + var(--drawer-snap-point-offset, 0px)
81
+ )
82
+ );
83
+ will-change: transform;
84
+
85
+ /* Disable transitions while swiping so movement is immediate */
86
+ &[data-swiping] {
87
+ transition: none;
88
+ }
89
+
90
+ &[data-starting-style],
91
+ &[data-ending-style] {
92
+ transform: translateY(100%);
93
+ padding-bottom: 0;
94
+ }
95
+ &[data-ending-style] {
96
+ transition-duration: calc(var(--drawer-swipe-strength) * 400ms);
97
+ }
98
+ }
99
+
100
+ .contentScrollArea {
101
+ width: 100%;
102
+ height: 100%;
103
+ flex: 1 1 auto;
104
+ min-height: 0;
105
+ overscroll-behavior: contain;
106
+ }
107
+
108
+ .contentScrollAreaContent {
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: var(--m-sp-md);
112
+ padding: var(--m-surface-p);
113
+ overscroll-behavior: contain;
114
+ }
115
+
116
+ .swipeHandle {
117
+ position: relative;
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: center;
121
+ width: 100%;
122
+ cursor: grab;
123
+ touch-action: none;
124
+ padding-block: var(--m-sp-lg);
125
+ user-select: none;
126
+ flex-shrink: 0;
127
+ }
128
+
129
+ .swipeHandleBar {
130
+ height: --fn-literal(4px);
131
+ width: 20%;
132
+ @apply --mx-bg(var(--m-gray));
133
+ border-radius: var(--m-rd-lg);
134
+ }
135
+
136
+ .close {
137
+ position: absolute;
138
+ right: var(--m-sp-sm);
139
+ top: var(--m-sp-sm);
140
+ z-index: 101;
141
+ display: flex;
142
+ }
143
+
144
+ .title {
145
+ @apply --mx-fg(var(--m-gray-ink));
146
+ font-size: var(--m-prose-primary-size);
147
+ font-weight: var(--m-prose-primary-weight);
148
+ line-height: var(--m-prose-primary-lineHeight);
149
+ margin-block-end: var(--m-sp-xs);
150
+ margin-block-start: 0;
151
+ }
152
+
153
+ .description {
154
+ @apply --mx-fg(var(--m-gray-heavy));
155
+ font-size: var(--m-prose-ambient-size);
156
+ line-height: var(--m-prose-ambient-lineHeight);
157
+ margin: 0;
158
+ margin-block-end: var(--m-sp-xs);
159
+ }
160
+
161
+ .actions {
162
+ position: sticky;
163
+ bottom: var(--m-sp-md);
164
+ z-index: 100;
165
+ display: flex;
166
+ flex-direction: row;
167
+ flex-wrap: wrap;
168
+ align-items: center;
169
+ justify-content: end;
170
+ align-self: end;
171
+ padding-bottom: --fn-literal(1px);
172
+ @apply --mx-shadow;
173
+ --mx-shadow-value: 0 calc(-1 * var(--m-shadow-md-y)) 16px
174
+ var(--m-shadow-md-spread) var(--m-gray-paper);
175
+ @apply --mx-bg(var(--m-surface-ambient-bg));
176
+ @apply --mx-bg-faded(50%);
177
+ margin-block-start: var(--m-sp-md);
178
+ gap: var(--m-sp-sm);
179
+ border-radius: var(--m-surface-rd);
180
+ transition: bottom var(--m-dur) var(--m-ease);
181
+ }