@astryxdesign/core 0.4.3 → 0.4.4-canary.0e8af82
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/CHANGELOG.md +60 -0
- package/README.md +79 -45
- package/dist/Banner/Banner.d.ts +3 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +13 -3
- package/dist/BottomSheet/BottomSheet.d.ts +75 -0
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheet.js +396 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts +63 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetPanel.js +441 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +61 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetSwitcher.js +410 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts +21 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.js +13 -0
- package/dist/BottomSheet/index.d.ts +11 -0
- package/dist/BottomSheet/index.d.ts.map +1 -0
- package/dist/BottomSheet/index.js +12 -0
- package/dist/BottomSheet/snapOffsets.d.ts +109 -0
- package/dist/BottomSheet/snapOffsets.d.ts.map +1 -0
- package/dist/BottomSheet/snapOffsets.js +206 -0
- package/dist/BottomSheet/useMobileKeyboard.d.ts +35 -0
- package/dist/BottomSheet/useMobileKeyboard.d.ts.map +1 -0
- package/dist/BottomSheet/useMobileKeyboard.js +373 -0
- package/dist/BottomSheet/useSheetGestures.d.ts +185 -0
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -0
- package/dist/BottomSheet/useSheetGestures.js +1038 -0
- package/dist/Calendar/Calendar.d.ts +17 -0
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +25 -1
- package/dist/Calendar/hooks/useCalendarConstraints.d.ts +22 -1
- package/dist/Calendar/hooks/useCalendarConstraints.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarConstraints.js +26 -4
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +3 -4
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +9 -0
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +18 -7
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +4 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +15 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +38 -3
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +38 -3
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +2 -1
- package/dist/Field/FieldLabel.d.ts +2 -1
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +21 -3
- package/dist/Field/InputClearButton.d.ts +2 -1
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +6 -1
- package/dist/FormLayout/FormLayout.d.ts +24 -2
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +7 -2
- package/dist/FormLayout/FormLayoutContext.d.ts +15 -2
- package/dist/FormLayout/FormLayoutContext.d.ts.map +1 -1
- package/dist/FormLayout/FormLayoutContext.js +19 -4
- package/dist/FormLayout/index.d.ts +1 -1
- package/dist/FormLayout/index.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +9 -3
- package/dist/MobileNav/MobileNav.d.ts +12 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +102 -30
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +27 -8
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +15 -2
- package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +12 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +20 -4
- package/dist/StatusDot/StatusDot.d.ts +43 -3
- package/dist/StatusDot/StatusDot.d.ts.map +1 -1
- package/dist/StatusDot/StatusDot.js +42 -5
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +14 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +6 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +6 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +14 -2
- package/dist/Tokenizer/Tokenizer.js +11 -5
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +4 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +5 -3
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +10 -1
- package/dist/astryx.css +21 -0
- package/dist/hooks/index.d.ts +8 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +8 -1
- package/dist/hooks/useFocusTrap.d.ts +0 -11
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +1 -11
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +1 -0
- package/dist/hooks/useResolvedRequired.d.ts +19 -0
- package/dist/hooks/useResolvedRequired.d.ts.map +1 -0
- package/dist/hooks/useResolvedRequired.js +40 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/defineTheme.d.ts +22 -4
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/expandColorScale.d.ts +17 -2
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +63 -33
- package/dist/utils/ime.d.ts +5 -0
- package/dist/utils/ime.d.ts.map +1 -0
- package/dist/utils/ime.js +63 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/plainDate.d.ts +6 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +12 -0
- package/locales/af-ZA.json +958 -0
- package/locales/ar-SA.json +998 -0
- package/locales/ca-ES.json +974 -0
- package/locales/cs-CZ.json +990 -0
- package/locales/da-DK.json +966 -0
- package/locales/de-DE.json +966 -0
- package/locales/el-GR.json +990 -0
- package/locales/en.json +52 -0
- package/locales/es-ES.json +978 -0
- package/locales/fi-FI.json +990 -0
- package/locales/fr-FR.json +928 -0
- package/locales/he-IL.json +990 -0
- package/locales/hu-HU.json +990 -0
- package/locales/it-IT.json +978 -0
- package/locales/ja-JP.json +998 -0
- package/locales/ko-KR.json +990 -0
- package/locales/nl-NL.json +954 -0
- package/locales/no-NO.json +978 -0
- package/locales/pl-PL.json +982 -0
- package/locales/pseudo.json +39 -0
- package/locales/pt-BR.json +982 -0
- package/locales/pt-PT.json +982 -0
- package/locales/ro-RO.json +982 -0
- package/locales/ru-RU.json +990 -0
- package/locales/sr-SP.json +990 -0
- package/locales/sv-SE.json +986 -0
- package/locales/tr-TR.json +990 -0
- package/locales/uk-UA.json +990 -0
- package/locales/vi-VN.json +986 -0
- package/locales/zh-CN.json +998 -0
- package/locales/zh-TW.json +998 -0
- package/package.json +9 -8
- package/src/Banner/Banner.doc.mjs +3 -3
- package/src/Banner/Banner.test.tsx +40 -0
- package/src/Banner/Banner.tsx +27 -2
- package/src/BottomSheet/BottomSheet.doc.mjs +299 -0
- package/src/BottomSheet/BottomSheet.test.tsx +2265 -0
- package/src/BottomSheet/BottomSheet.tsx +559 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +240 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +601 -0
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +219 -0
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +850 -0
- package/src/BottomSheet/BottomSheetSwitcher.tsx +628 -0
- package/src/BottomSheet/BottomSheetSwitcherContext.ts +51 -0
- package/src/BottomSheet/index.ts +19 -0
- package/src/BottomSheet/snapOffsets.test.ts +254 -0
- package/src/BottomSheet/snapOffsets.ts +245 -0
- package/src/BottomSheet/useMobileKeyboard.ts +499 -0
- package/src/BottomSheet/useSheetGestures.test.ts +1112 -0
- package/src/BottomSheet/useSheetGestures.ts +1503 -0
- package/src/Calendar/Calendar.doc.mjs +16 -0
- package/src/Calendar/Calendar.test.tsx +96 -0
- package/src/Calendar/Calendar.tsx +55 -0
- package/src/Calendar/hooks/useCalendarConstraints.ts +54 -3
- package/src/Chat/ChatComposerInput.tsx +3 -4
- package/src/CheckboxInput/CheckboxInput.tsx +6 -0
- package/src/ComplexSelector/ComplexSelector.tsx +15 -6
- package/src/ContextMenu/ContextMenu.tsx +4 -2
- package/src/DateInput/DateInput.test.tsx +21 -0
- package/src/DateInput/DateInput.tsx +12 -2
- package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +81 -1
- package/src/DateRangeInput/DateRangeInput.tsx +72 -1
- package/src/DateTimeInput/DateTimeInput.doc.mjs +4 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +137 -0
- package/src/DateTimeInput/DateTimeInput.tsx +43 -4
- package/src/Dialog/Dialog.tsx +2 -1
- package/src/Field/Field.doc.mjs +1 -0
- package/src/Field/FieldLabel.tsx +20 -4
- package/src/Field/InputClearButton.test.tsx +25 -0
- package/src/Field/InputClearButton.tsx +4 -1
- package/src/FileInput/FileInput.test.tsx +53 -9
- package/src/FormLayout/FormLayout.doc.mjs +13 -0
- package/src/FormLayout/FormLayout.test.tsx +181 -4
- package/src/FormLayout/FormLayout.tsx +33 -2
- package/src/FormLayout/FormLayoutContext.ts +22 -7
- package/src/FormLayout/index.ts +1 -1
- package/src/Lightbox/Lightbox.test.tsx +49 -28
- package/src/Lightbox/Lightbox.tsx +7 -3
- package/src/MobileNav/MobileNav.tsx +128 -37
- package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +426 -0
- package/src/MobileNav/MobileNavCloseTiming.test.ts +58 -0
- package/src/MobileNav/MobileNavCloseVisibility.test.tsx +172 -0
- package/src/MultiSelector/MultiSelector.test.tsx +195 -51
- package/src/MultiSelector/MultiSelector.tsx +25 -9
- package/src/NumberInput/NumberInput.test.tsx +18 -0
- package/src/NumberInput/NumberInput.tsx +12 -2
- package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
- package/src/RadioList/RadioList.tsx +9 -1
- package/src/Selector/Selector.test.tsx +85 -26
- package/src/Selector/Selector.tsx +16 -5
- package/src/StatusDot/StatusDot.doc.mjs +13 -0
- package/src/StatusDot/StatusDot.test.tsx +115 -8
- package/src/StatusDot/StatusDot.tsx +74 -9
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +13 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +20 -2
- package/src/TextArea/TextArea.tsx +3 -1
- package/src/TextInput/TextInput.tsx +3 -1
- package/src/TimeInput/TimeInput.test.tsx +23 -0
- package/src/TimeInput/TimeInput.tsx +11 -1
- package/src/Tokenizer/Tokenizer.test.tsx +50 -32
- package/src/Tokenizer/Tokenizer.tsx +5 -5
- package/src/Tooltip/useTooltip.tsx +4 -1
- package/src/Typeahead/BaseTypeahead.tsx +10 -3
- package/src/Typeahead/Typeahead.test.tsx +116 -7
- package/src/Typeahead/Typeahead.tsx +10 -1
- package/src/__tests__/stubMatchMedia.test.ts +79 -0
- package/src/__tests__/stubMatchMedia.ts +95 -0
- package/src/hooks/index.ts +9 -5
- package/src/hooks/useFocusTrap.ts +1 -14
- package/src/hooks/useInputStatusIcon.test.tsx +19 -0
- package/src/hooks/useInputStatusIcon.tsx +7 -0
- package/src/hooks/useResolvedRequired.ts +42 -0
- package/src/index.ts +1 -0
- package/src/theme/defineTheme.ts +22 -4
- package/src/theme/expandColorScale.test.ts +138 -0
- package/src/theme/expandColorScale.ts +89 -44
- package/src/utils/ime.test.ts +32 -0
- package/src/utils/ime.ts +69 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/plainDate.test.ts +50 -0
- package/src/utils/plainDate.ts +12 -0
- package/dist/astryx.umd.js +0 -147
- package/dist/astryx.umd.js.map +0 -7
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file BottomSheetPanel.tsx
|
|
7
|
+
* @input Uses React, StyleX, theme tokens, useSheetGestures
|
|
8
|
+
* @output Internal BottomSheetPanel surface and motion-state types
|
|
9
|
+
* @position Shared presentation layer for standalone and switcher BottomSheets
|
|
10
|
+
*
|
|
11
|
+
* This component owns everything intrinsic to a sheet surface: height budgets,
|
|
12
|
+
* drag and snap gestures, the handle and scrolling body, motion styles, and
|
|
13
|
+
* transition completion. It deliberately does not own a dialog, focus, inert
|
|
14
|
+
* state, or switcher registration; those belong to the hosting controller.
|
|
15
|
+
*
|
|
16
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
17
|
+
* - /packages/core/src/BottomSheet/BottomSheet.tsx
|
|
18
|
+
* - /packages/core/src/BottomSheet/BottomSheetPanel.test.tsx
|
|
19
|
+
* - /packages/core/src/BottomSheet/snapOffsets.ts
|
|
20
|
+
* - /packages/core/src/BottomSheet/useMobileKeyboard.ts
|
|
21
|
+
* - /packages/core/src/BottomSheet/useSheetGestures.ts
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
useCallback,
|
|
26
|
+
useEffect,
|
|
27
|
+
useImperativeHandle,
|
|
28
|
+
useLayoutEffect,
|
|
29
|
+
useMemo,
|
|
30
|
+
useRef,
|
|
31
|
+
type ReactNode,
|
|
32
|
+
} from 'react';
|
|
33
|
+
import * as stylex from '@stylexjs/stylex';
|
|
34
|
+
import type {BaseProps} from '../BaseProps';
|
|
35
|
+
import {useDevWarning} from '../hooks';
|
|
36
|
+
import {
|
|
37
|
+
colorVars,
|
|
38
|
+
durationVars,
|
|
39
|
+
easeVars,
|
|
40
|
+
radiusVars,
|
|
41
|
+
shadowVars,
|
|
42
|
+
sizeVars,
|
|
43
|
+
spacingVars,
|
|
44
|
+
} from '../theme/tokens.stylex';
|
|
45
|
+
import {mergeProps, themeProps} from '../utils';
|
|
46
|
+
import {
|
|
47
|
+
isValidSnapPoint,
|
|
48
|
+
resolveSnapPoints,
|
|
49
|
+
type BottomSheetSnapPoint,
|
|
50
|
+
} from './snapOffsets';
|
|
51
|
+
import {useMobileKeyboard} from './useMobileKeyboard';
|
|
52
|
+
import {useSheetGestures} from './useSheetGestures';
|
|
53
|
+
|
|
54
|
+
const HEIGHT_BUDGETS = {
|
|
55
|
+
hug: '92dvh',
|
|
56
|
+
capped: '62dvh',
|
|
57
|
+
tall: '92dvh',
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
export type BottomSheetHeight = keyof typeof HEIGHT_BUDGETS;
|
|
61
|
+
export type {BottomSheetSnapPoint};
|
|
62
|
+
|
|
63
|
+
// SYNC: must match OVERSCROLL_MAX in useSheetGestures.ts.
|
|
64
|
+
const OVERSCROLL_PADDING = 48;
|
|
65
|
+
const MOBILE_KEYBOARD_BOTTOM_CLEARANCE = 48;
|
|
66
|
+
const TRANSITION_BACKSTOP_BUFFER_MS = 50;
|
|
67
|
+
|
|
68
|
+
// Measure the same viewport the height budgets are written against. Those are
|
|
69
|
+
// `dvh`, which the virtual keyboard does not shrink, so reading
|
|
70
|
+
// `visualViewport` here would make the two disagree by exactly the keyboard's
|
|
71
|
+
// height: every detent would move while the sheet it measures did not. A
|
|
72
|
+
// keyboard is `useMobileKeyboard`'s business — it holds the sheet still and
|
|
73
|
+
// scrolls the body — and it does not redefine the sheet's detents.
|
|
74
|
+
function layoutViewportHeight(): number {
|
|
75
|
+
return typeof window === 'undefined' ? 0 : window.innerHeight;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A stable identity for a set of snap points. Type-tagged, so the fraction
|
|
80
|
+
* `0.5` and the (invalid) string `'0.5'` cannot collide on one key.
|
|
81
|
+
*
|
|
82
|
+
* Recomputed every render — the panel re-renders on every frame of a drag — so
|
|
83
|
+
* it stays a single pass over a handful of values, and everything derived from
|
|
84
|
+
* the points hangs off it instead of being rebuilt per frame.
|
|
85
|
+
*/
|
|
86
|
+
function snapPointsKeyFor(
|
|
87
|
+
points: ReadonlyArray<BottomSheetSnapPoint> | undefined,
|
|
88
|
+
): string {
|
|
89
|
+
let key = '';
|
|
90
|
+
for (const point of points ?? []) {
|
|
91
|
+
key += `${typeof point}:${point}|`;
|
|
92
|
+
}
|
|
93
|
+
return key;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const styles = stylex.create({
|
|
97
|
+
sheet: {
|
|
98
|
+
pointerEvents: 'auto',
|
|
99
|
+
boxSizing: 'border-box',
|
|
100
|
+
display: 'flex',
|
|
101
|
+
flexDirection: 'column',
|
|
102
|
+
minHeight: 0,
|
|
103
|
+
width: '100%',
|
|
104
|
+
maxWidth: 640,
|
|
105
|
+
backgroundColor: colorVars['--color-background-surface'],
|
|
106
|
+
borderStartStartRadius: radiusVars['--radius-container'],
|
|
107
|
+
borderStartEndRadius: radiusVars['--radius-container'],
|
|
108
|
+
boxShadow: shadowVars['--shadow-high'],
|
|
109
|
+
outline: 'none',
|
|
110
|
+
overflow: 'hidden',
|
|
111
|
+
paddingBlockEnd: `calc(env(safe-area-inset-bottom, 0px) + ${OVERSCROLL_PADDING}px)`,
|
|
112
|
+
marginBlockEnd: `${-OVERSCROLL_PADDING}px`,
|
|
113
|
+
transform: {
|
|
114
|
+
default: 'translateY(0)',
|
|
115
|
+
'@starting-style': 'translateY(100%)',
|
|
116
|
+
},
|
|
117
|
+
opacity: 1,
|
|
118
|
+
transitionProperty: 'transform, opacity',
|
|
119
|
+
transitionDuration: durationVars['--duration-medium'],
|
|
120
|
+
transitionTimingFunction: easeVars['--ease-standard'],
|
|
121
|
+
willChange: 'transform, opacity',
|
|
122
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
123
|
+
transitionDuration: '0.01s',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
sheetClosing: {
|
|
127
|
+
transform: 'translateY(100%)',
|
|
128
|
+
},
|
|
129
|
+
sheetFading: {
|
|
130
|
+
opacity: 0,
|
|
131
|
+
},
|
|
132
|
+
sheetInactive: {
|
|
133
|
+
pointerEvents: 'none',
|
|
134
|
+
},
|
|
135
|
+
handleBar: {
|
|
136
|
+
flexShrink: 0,
|
|
137
|
+
display: 'flex',
|
|
138
|
+
alignItems: 'center',
|
|
139
|
+
justifyContent: 'center',
|
|
140
|
+
height: spacingVars['--spacing-12'],
|
|
141
|
+
touchAction: 'none',
|
|
142
|
+
cursor: 'grab',
|
|
143
|
+
},
|
|
144
|
+
handlePill: {
|
|
145
|
+
width: sizeVars['--size-element-lg'],
|
|
146
|
+
height: spacingVars['--spacing-1'],
|
|
147
|
+
borderRadius: radiusVars['--radius-full'],
|
|
148
|
+
backgroundColor: colorVars['--color-border'],
|
|
149
|
+
},
|
|
150
|
+
body: {
|
|
151
|
+
flexGrow: 1,
|
|
152
|
+
minHeight: 0,
|
|
153
|
+
boxSizing: 'border-box',
|
|
154
|
+
overflowY: 'auto',
|
|
155
|
+
overscrollBehavior: 'none',
|
|
156
|
+
touchAction: 'pan-y',
|
|
157
|
+
paddingBlockEnd: 0,
|
|
158
|
+
},
|
|
159
|
+
tallKeyboardBody: {
|
|
160
|
+
scrollPaddingBlockEnd: MOBILE_KEYBOARD_BOTTOM_CLEARANCE,
|
|
161
|
+
'::after': {
|
|
162
|
+
content: '""',
|
|
163
|
+
display: 'block',
|
|
164
|
+
blockSize: 'var(--_sheet-keyboard-inset, 0px)',
|
|
165
|
+
pointerEvents: 'none',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
budget: {
|
|
169
|
+
height: `calc(var(--_sheet-budget) + ${OVERSCROLL_PADDING}px)`,
|
|
170
|
+
},
|
|
171
|
+
hugHeight: {
|
|
172
|
+
height: 'fit-content',
|
|
173
|
+
maxHeight: `calc(${HEIGHT_BUDGETS.hug} + ${OVERSCROLL_PADDING}px)`,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
export type BottomSheetPanelMotion =
|
|
178
|
+
'entering' | 'aligning' | 'fading' | 'exiting';
|
|
179
|
+
|
|
180
|
+
export type BottomSheetPanelState =
|
|
181
|
+
| {kind: 'hidden'}
|
|
182
|
+
| {kind: 'open'; entering: boolean}
|
|
183
|
+
| {
|
|
184
|
+
kind: 'retained';
|
|
185
|
+
motion: 'covered' | 'aligning' | 'fading';
|
|
186
|
+
alignmentOffset: number;
|
|
187
|
+
}
|
|
188
|
+
| {kind: 'exiting'};
|
|
189
|
+
|
|
190
|
+
interface BottomSheetPanelProps extends BaseProps<HTMLDivElement> {
|
|
191
|
+
/** Ref forwarded to the visual sheet panel. */
|
|
192
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
193
|
+
state: BottomSheetPanelState;
|
|
194
|
+
height: BottomSheetHeight | number | string;
|
|
195
|
+
children: ReactNode;
|
|
196
|
+
snapPoints?: ReadonlyArray<BottomSheetSnapPoint>;
|
|
197
|
+
isSwipeDismissAllowed?: boolean;
|
|
198
|
+
/** Whether the host has locked page scrolling (a modal, scrim-backed sheet). */
|
|
199
|
+
isPageScrollLocked?: boolean;
|
|
200
|
+
onDismiss: () => void;
|
|
201
|
+
onScrimOpacity: (opacity: number) => void;
|
|
202
|
+
onElementChange?: (element: HTMLDivElement | null) => void;
|
|
203
|
+
onMotionStart?: (motion: BottomSheetPanelMotion) => void;
|
|
204
|
+
onMotionComplete?: (motion: BottomSheetPanelMotion) => void;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function motionForState(
|
|
208
|
+
state: BottomSheetPanelState,
|
|
209
|
+
): BottomSheetPanelMotion | null {
|
|
210
|
+
if (state.kind === 'open') {
|
|
211
|
+
return state.entering ? 'entering' : null;
|
|
212
|
+
}
|
|
213
|
+
if (state.kind === 'retained') {
|
|
214
|
+
return state.motion === 'covered' ? null : state.motion;
|
|
215
|
+
}
|
|
216
|
+
return state.kind === 'exiting' ? 'exiting' : null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function waitForTransition(
|
|
220
|
+
element: HTMLElement | null,
|
|
221
|
+
propertyName: 'transform' | 'opacity',
|
|
222
|
+
complete: () => void,
|
|
223
|
+
): () => void {
|
|
224
|
+
if (element == null) {
|
|
225
|
+
complete();
|
|
226
|
+
return () => {};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let done = false;
|
|
230
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
231
|
+
const finish = () => {
|
|
232
|
+
if (done) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
done = true;
|
|
236
|
+
if (timer != null) {
|
|
237
|
+
clearTimeout(timer);
|
|
238
|
+
}
|
|
239
|
+
element.removeEventListener('transitionend', handleTransitionEnd);
|
|
240
|
+
element.removeEventListener('transitioncancel', handleTransitionEnd);
|
|
241
|
+
complete();
|
|
242
|
+
};
|
|
243
|
+
const handleTransitionEnd = (event: TransitionEvent) => {
|
|
244
|
+
if (event.target === element && event.propertyName === propertyName) {
|
|
245
|
+
finish();
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
element.addEventListener('transitionend', handleTransitionEnd);
|
|
249
|
+
element.addEventListener('transitioncancel', handleTransitionEnd);
|
|
250
|
+
|
|
251
|
+
const computedStyle = getComputedStyle(element);
|
|
252
|
+
if (
|
|
253
|
+
element.style.transition.trim() === 'none' ||
|
|
254
|
+
computedStyle.transition.trim() === 'none'
|
|
255
|
+
) {
|
|
256
|
+
finish();
|
|
257
|
+
return () => {};
|
|
258
|
+
}
|
|
259
|
+
const properties = computedStyle.transitionProperty
|
|
260
|
+
.split(',')
|
|
261
|
+
.map(value => value.trim());
|
|
262
|
+
const durations = computedStyle.transitionDuration
|
|
263
|
+
.split(',')
|
|
264
|
+
.map(parseTransitionTime);
|
|
265
|
+
const delays = computedStyle.transitionDelay
|
|
266
|
+
.split(',')
|
|
267
|
+
.map(parseTransitionTime);
|
|
268
|
+
let hasUnresolvedTiming = false;
|
|
269
|
+
const transitionMs = properties.reduce((longest, property, index) => {
|
|
270
|
+
if (property !== propertyName && property !== 'all') {
|
|
271
|
+
return longest;
|
|
272
|
+
}
|
|
273
|
+
const duration = durations[index % durations.length];
|
|
274
|
+
const delay = delays[index % delays.length];
|
|
275
|
+
if (duration == null || delay == null) {
|
|
276
|
+
hasUnresolvedTiming = true;
|
|
277
|
+
return longest;
|
|
278
|
+
}
|
|
279
|
+
return Math.max(longest, duration + delay);
|
|
280
|
+
}, 0);
|
|
281
|
+
|
|
282
|
+
if (hasUnresolvedTiming) {
|
|
283
|
+
// JSDOM can leave CSS variables unresolved. In that case the native event
|
|
284
|
+
// remains authoritative; choosing a fixed timeout here would make an
|
|
285
|
+
// assumption about the consumer's theme.
|
|
286
|
+
return () => {
|
|
287
|
+
element.removeEventListener('transitionend', handleTransitionEnd);
|
|
288
|
+
element.removeEventListener('transitioncancel', handleTransitionEnd);
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (transitionMs <= 0) {
|
|
293
|
+
finish();
|
|
294
|
+
return () => {};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
timer = setTimeout(finish, transitionMs + TRANSITION_BACKSTOP_BUFFER_MS);
|
|
298
|
+
return () => {
|
|
299
|
+
if (timer != null) {
|
|
300
|
+
clearTimeout(timer);
|
|
301
|
+
}
|
|
302
|
+
element.removeEventListener('transitionend', handleTransitionEnd);
|
|
303
|
+
element.removeEventListener('transitioncancel', handleTransitionEnd);
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function parseTransitionTime(value: string): number | null {
|
|
308
|
+
const normalizedValue = value.trim();
|
|
309
|
+
if (!/^-?(?:\d+|\d*\.\d+)(?:ms|s)$/.test(normalizedValue)) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
const time = Number.parseFloat(value);
|
|
313
|
+
return normalizedValue.endsWith('ms') ? time : time * 1000;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Internal visual and gesture surface shared by every BottomSheet host. */
|
|
317
|
+
export function BottomSheetPanel({
|
|
318
|
+
ref,
|
|
319
|
+
state,
|
|
320
|
+
height,
|
|
321
|
+
children,
|
|
322
|
+
snapPoints,
|
|
323
|
+
className,
|
|
324
|
+
style,
|
|
325
|
+
tabIndex,
|
|
326
|
+
xstyle,
|
|
327
|
+
isSwipeDismissAllowed = true,
|
|
328
|
+
isPageScrollLocked = false,
|
|
329
|
+
onDismiss,
|
|
330
|
+
onScrimOpacity,
|
|
331
|
+
onElementChange,
|
|
332
|
+
onMotionStart,
|
|
333
|
+
onMotionComplete,
|
|
334
|
+
...props
|
|
335
|
+
}: BottomSheetPanelProps) {
|
|
336
|
+
const elementRef = useRef<HTMLDivElement | null>(null);
|
|
337
|
+
const previousStateRef = useRef(state);
|
|
338
|
+
const reactivatedEntranceRef = useRef(false);
|
|
339
|
+
const onMotionStartRef = useRef(onMotionStart);
|
|
340
|
+
const onMotionCompleteRef = useRef(onMotionComplete);
|
|
341
|
+
const startedMotionRef = useRef<BottomSheetPanelMotion | null>(null);
|
|
342
|
+
const pendingMotionCompleteRef = useRef<BottomSheetPanelMotion | null>(null);
|
|
343
|
+
|
|
344
|
+
const isEntering = state.kind === 'open' && state.entering;
|
|
345
|
+
const previousState = previousStateRef.current;
|
|
346
|
+
const wasEntering = previousState.kind === 'open' && previousState.entering;
|
|
347
|
+
if (isEntering && !wasEntering) {
|
|
348
|
+
reactivatedEntranceRef.current = previousState.kind === 'retained';
|
|
349
|
+
} else if (!isEntering) {
|
|
350
|
+
reactivatedEntranceRef.current = false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
useLayoutEffect(() => {
|
|
354
|
+
previousStateRef.current = state;
|
|
355
|
+
onMotionStartRef.current = onMotionStart;
|
|
356
|
+
onMotionCompleteRef.current = onMotionComplete;
|
|
357
|
+
}, [onMotionComplete, onMotionStart, state]);
|
|
358
|
+
|
|
359
|
+
const isInteractive = state.kind === 'open';
|
|
360
|
+
const isPresented = state.kind !== 'hidden';
|
|
361
|
+
const isRetained = state.kind === 'retained';
|
|
362
|
+
const isInactive = isRetained || state.kind === 'exiting';
|
|
363
|
+
const isClosing = state.kind === 'exiting';
|
|
364
|
+
const isFading = isRetained && state.motion === 'fading';
|
|
365
|
+
const alignmentOffset = isRetained ? state.alignmentOffset : 0;
|
|
366
|
+
|
|
367
|
+
// Everything derived from the snap points keys off their identity, not the
|
|
368
|
+
// array's: the resolver's identity is the hook's signal that the stops
|
|
369
|
+
// changed, so it must not churn on a re-render the drag caused. The memo
|
|
370
|
+
// reads the points through a ref, so the resolver resolves them at call time
|
|
371
|
+
// — against whatever the viewport is then, not what it was at memo time.
|
|
372
|
+
const snapPointsKey = snapPointsKeyFor(snapPoints);
|
|
373
|
+
const snapPointsRef = useRef(snapPoints);
|
|
374
|
+
snapPointsRef.current = snapPoints;
|
|
375
|
+
const {snapHeights, ignoredSnapPointsMessage} = useMemo(() => {
|
|
376
|
+
if (snapPointsKey === '') {
|
|
377
|
+
return {snapHeights: undefined, ignoredSnapPointsMessage: ''};
|
|
378
|
+
}
|
|
379
|
+
const ignored = (snapPointsRef.current ?? []).filter(
|
|
380
|
+
point => !isValidSnapPoint(point),
|
|
381
|
+
);
|
|
382
|
+
return {
|
|
383
|
+
snapHeights: () =>
|
|
384
|
+
resolveSnapPoints(snapPointsRef.current ?? [], layoutViewportHeight()),
|
|
385
|
+
ignoredSnapPointsMessage:
|
|
386
|
+
ignored.length === 0
|
|
387
|
+
? ''
|
|
388
|
+
: `snapPoints ignored ${JSON.stringify(ignored)}. A snap point is a viewport fraction above 0 and up to 1 (0.5 is half the screen), a px length ('320px'), or a percentage ('50%').`,
|
|
389
|
+
};
|
|
390
|
+
}, [snapPointsKey]);
|
|
391
|
+
useDevWarning(
|
|
392
|
+
'BottomSheet',
|
|
393
|
+
ignoredSnapPointsMessage,
|
|
394
|
+
ignoredSnapPointsMessage !== '',
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const {
|
|
398
|
+
contentProps,
|
|
399
|
+
handleProps,
|
|
400
|
+
bodyProps,
|
|
401
|
+
bodyElementRef,
|
|
402
|
+
sheetRef,
|
|
403
|
+
dragOffset,
|
|
404
|
+
settledOffset,
|
|
405
|
+
isDragging,
|
|
406
|
+
sheetHeight,
|
|
407
|
+
scrollPreservationInset,
|
|
408
|
+
settlingLayoutOffset,
|
|
409
|
+
settledLayoutOffset,
|
|
410
|
+
completeScrollAreaSettle,
|
|
411
|
+
} = useSheetGestures({
|
|
412
|
+
isOpen: isInteractive,
|
|
413
|
+
canDismiss: isSwipeDismissAllowed,
|
|
414
|
+
offscreenBlockEndInset: OVERSCROLL_PADDING,
|
|
415
|
+
onDismiss,
|
|
416
|
+
snapHeights,
|
|
417
|
+
onScrimOpacity,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const setElement = useCallback(
|
|
421
|
+
(element: HTMLDivElement | null) => {
|
|
422
|
+
sheetRef(element);
|
|
423
|
+
elementRef.current = element;
|
|
424
|
+
onElementChange?.(element);
|
|
425
|
+
},
|
|
426
|
+
[onElementChange, sheetRef],
|
|
427
|
+
);
|
|
428
|
+
useMobileKeyboard({
|
|
429
|
+
bodyRef: bodyElementRef,
|
|
430
|
+
bottomClearance: MOBILE_KEYBOARD_BOTTOM_CLEARANCE,
|
|
431
|
+
isEnabled: height === 'tall',
|
|
432
|
+
isFullyExpanded: settledOffset === 0,
|
|
433
|
+
isPageScrollLocked,
|
|
434
|
+
isSheetTraveling: isDragging && dragOffset !== settledOffset,
|
|
435
|
+
isOpen: isInteractive,
|
|
436
|
+
isPresented,
|
|
437
|
+
sheetRef: elementRef,
|
|
438
|
+
});
|
|
439
|
+
// Keep controller registration attached to one stable host ref. React
|
|
440
|
+
// detaches an old callback ref when a consumer supplies a new identity; if
|
|
441
|
+
// that public ref were merged with setElement, an ordinary parent rerender
|
|
442
|
+
// could be mistaken for the panel unmounting and cancel a sheet handoff.
|
|
443
|
+
useImperativeHandle(ref, () => elementRef.current as HTMLDivElement, []);
|
|
444
|
+
|
|
445
|
+
const motion = motionForState(state);
|
|
446
|
+
useLayoutEffect(() => {
|
|
447
|
+
if (motion == null) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
startedMotionRef.current = null;
|
|
451
|
+
pendingMotionCompleteRef.current = null;
|
|
452
|
+
if (motion === 'entering' && reactivatedEntranceRef.current) {
|
|
453
|
+
pendingMotionCompleteRef.current = motion;
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
return waitForTransition(
|
|
457
|
+
elementRef.current,
|
|
458
|
+
motion === 'fading' ? 'opacity' : 'transform',
|
|
459
|
+
() => {
|
|
460
|
+
if (startedMotionRef.current === motion) {
|
|
461
|
+
onMotionCompleteRef.current?.(motion);
|
|
462
|
+
} else {
|
|
463
|
+
pendingMotionCompleteRef.current = motion;
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
);
|
|
467
|
+
}, [motion]);
|
|
468
|
+
// The snap is transform-only; the layout height reconciles when it lands.
|
|
469
|
+
// waitForTransition — the same helper the motion states use — resolves that
|
|
470
|
+
// even when no `transitionend` is coming: inline or computed
|
|
471
|
+
// `transition: none`, a zero duration, and a timer backstop otherwise.
|
|
472
|
+
useLayoutEffect(() => {
|
|
473
|
+
if (settlingLayoutOffset == null) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
return waitForTransition(
|
|
477
|
+
elementRef.current,
|
|
478
|
+
'transform',
|
|
479
|
+
completeScrollAreaSettle,
|
|
480
|
+
);
|
|
481
|
+
}, [completeScrollAreaSettle, settlingLayoutOffset]);
|
|
482
|
+
useEffect(() => {
|
|
483
|
+
if (motion == null) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
onMotionStartRef.current?.(motion);
|
|
487
|
+
startedMotionRef.current = motion;
|
|
488
|
+
if (pendingMotionCompleteRef.current === motion) {
|
|
489
|
+
pendingMotionCompleteRef.current = null;
|
|
490
|
+
onMotionCompleteRef.current?.(motion);
|
|
491
|
+
}
|
|
492
|
+
return () => {
|
|
493
|
+
if (startedMotionRef.current === motion) {
|
|
494
|
+
startedMotionRef.current = null;
|
|
495
|
+
}
|
|
496
|
+
if (pendingMotionCompleteRef.current === motion) {
|
|
497
|
+
pendingMotionCompleteRef.current = null;
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}, [motion]);
|
|
501
|
+
|
|
502
|
+
const isNamedHeight = typeof height === 'string' && height in HEIGHT_BUDGETS;
|
|
503
|
+
const budget = isNamedHeight
|
|
504
|
+
? HEIGHT_BUDGETS[height as BottomSheetHeight]
|
|
505
|
+
: typeof height === 'number'
|
|
506
|
+
? `${height}px`
|
|
507
|
+
: height;
|
|
508
|
+
const hasMeasuredSheet = sheetHeight > 0;
|
|
509
|
+
let gestureTransform = contentProps.style.transform;
|
|
510
|
+
let resizedHeight: string | undefined;
|
|
511
|
+
if (hasMeasuredSheet) {
|
|
512
|
+
// The sheet's travel is split across two properties: `layoutOffset` is the
|
|
513
|
+
// part the scrolling area gives up as layout height, and the remainder is
|
|
514
|
+
// a compositor transform. Live gestures and snaps only ever move the
|
|
515
|
+
// transform — the layout height changes at rest, in one reconciling render
|
|
516
|
+
// whose visible geometry is identical (see useSheetGestures).
|
|
517
|
+
// - dragging above the base restores the full height below the viewport
|
|
518
|
+
// - a peek settles with layout 0, so it slides rather than reflowing
|
|
519
|
+
const layoutOffset = isDragging
|
|
520
|
+
? dragOffset < settledOffset
|
|
521
|
+
? 0
|
|
522
|
+
: settledLayoutOffset
|
|
523
|
+
: (settlingLayoutOffset ?? settledLayoutOffset);
|
|
524
|
+
const activeOffset = isDragging ? dragOffset : settledOffset;
|
|
525
|
+
const translation = activeOffset - layoutOffset;
|
|
526
|
+
|
|
527
|
+
// At layout 0 the sheet is its natural height, so leave that to CSS —
|
|
528
|
+
// `hug` sheets must stay fit-content. While the sheet is in motion it is
|
|
529
|
+
// pinned in px instead: content reflowing mid-gesture would move the
|
|
530
|
+
// surface out from under the finger, or under the snap.
|
|
531
|
+
const isTraveling = isDragging || settlingLayoutOffset != null;
|
|
532
|
+
resizedHeight =
|
|
533
|
+
layoutOffset > 0 || isTraveling
|
|
534
|
+
? `${Math.max(0, sheetHeight - Math.max(0, layoutOffset))}px`
|
|
535
|
+
: undefined;
|
|
536
|
+
gestureTransform =
|
|
537
|
+
translation !== 0 ? `translateY(${translation}px)` : undefined;
|
|
538
|
+
}
|
|
539
|
+
const retainedTransform =
|
|
540
|
+
alignmentOffset > 0
|
|
541
|
+
? [gestureTransform, `translateY(${alignmentOffset}px)`]
|
|
542
|
+
.filter(Boolean)
|
|
543
|
+
.join(' ')
|
|
544
|
+
: gestureTransform;
|
|
545
|
+
const gestureStyle = {
|
|
546
|
+
...contentProps.style,
|
|
547
|
+
transform: gestureTransform,
|
|
548
|
+
height: resizedHeight,
|
|
549
|
+
};
|
|
550
|
+
return (
|
|
551
|
+
<div
|
|
552
|
+
{...props}
|
|
553
|
+
ref={setElement}
|
|
554
|
+
tabIndex={tabIndex ?? -1}
|
|
555
|
+
{...mergeProps(
|
|
556
|
+
themeProps('bottom-sheet'),
|
|
557
|
+
stylex.props(
|
|
558
|
+
styles.sheet,
|
|
559
|
+
height === 'hug' ? styles.hugHeight : styles.budget,
|
|
560
|
+
isClosing && styles.sheetClosing,
|
|
561
|
+
isFading && styles.sheetFading,
|
|
562
|
+
isInactive && styles.sheetInactive,
|
|
563
|
+
xstyle,
|
|
564
|
+
),
|
|
565
|
+
className,
|
|
566
|
+
{
|
|
567
|
+
['--_sheet-budget' as string]: budget,
|
|
568
|
+
...(isInteractive
|
|
569
|
+
? gestureStyle
|
|
570
|
+
: isRetained
|
|
571
|
+
? {transform: retainedTransform, height: resizedHeight}
|
|
572
|
+
: isClosing
|
|
573
|
+
? {height: resizedHeight}
|
|
574
|
+
: {}),
|
|
575
|
+
...style,
|
|
576
|
+
},
|
|
577
|
+
)}>
|
|
578
|
+
<div
|
|
579
|
+
{...stylex.props(styles.handleBar)}
|
|
580
|
+
{...handleProps}
|
|
581
|
+
aria-hidden="true">
|
|
582
|
+
<div {...stylex.props(styles.handlePill)} />
|
|
583
|
+
</div>
|
|
584
|
+
<div
|
|
585
|
+
{...mergeProps(
|
|
586
|
+
stylex.props(
|
|
587
|
+
styles.body,
|
|
588
|
+
height === 'tall' && styles.tallKeyboardBody,
|
|
589
|
+
),
|
|
590
|
+
scrollPreservationInset > 0
|
|
591
|
+
? {style: {paddingBlockEnd: `${scrollPreservationInset}px`}}
|
|
592
|
+
: {},
|
|
593
|
+
)}
|
|
594
|
+
{...bodyProps}>
|
|
595
|
+
{children}
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
BottomSheetPanel.displayName = 'BottomSheetPanel';
|