@duro-app/ui 5.12.0 → 5.14.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/README.md +49 -0
- package/dist/Table-DnaoH-h5.js +1806 -0
- package/dist/Table-DnaoH-h5.js.map +1 -0
- package/dist/components/AppShell/native.css.d.ts +53 -0
- package/dist/components/AppShell/native.css.d.ts.map +1 -0
- package/dist/components/Button/Button.d.ts.map +1 -1
- package/dist/components/ColorMode/ColorMode.d.ts +2 -1
- package/dist/components/ColorMode/ColorMode.d.ts.map +1 -1
- package/dist/components/ColorMode/storage.d.ts +4 -0
- package/dist/components/ColorMode/storage.d.ts.map +1 -0
- package/dist/components/ColorMode/styles.css.d.ts +4 -0
- package/dist/components/ColorMode/styles.css.d.ts.map +1 -1
- package/dist/components/ColorMode/useSystemScheme.d.ts +7 -0
- package/dist/components/ColorMode/useSystemScheme.d.ts.map +1 -0
- package/dist/components/Combobox/Combobox.d.ts +7 -2
- package/dist/components/Combobox/Combobox.d.ts.map +1 -1
- package/dist/components/Combobox/comboboxState.d.ts +29 -0
- package/dist/components/Combobox/comboboxState.d.ts.map +1 -0
- package/dist/components/Combobox/native.css.d.ts +27 -0
- package/dist/components/Combobox/native.css.d.ts.map +1 -0
- package/dist/components/Combobox/useComboboxRoot.d.ts +6 -10
- package/dist/components/Combobox/useComboboxRoot.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.d.ts +12 -37
- package/dist/components/Drawer/Drawer.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.meta.d.ts.map +1 -1
- package/dist/components/Drawer/DrawerScroll.stories.d.ts.map +1 -1
- package/dist/components/Drawer/DrawerSnap.stories.d.ts +22 -0
- package/dist/components/Drawer/DrawerSnap.stories.d.ts.map +1 -0
- package/dist/components/Drawer/native.css.d.ts +59 -0
- package/dist/components/Drawer/native.css.d.ts.map +1 -0
- package/dist/components/Drawer/parts.css.d.ts +96 -0
- package/dist/components/Drawer/parts.css.d.ts.map +1 -0
- package/dist/components/Drawer/parts.d.ts +56 -0
- package/dist/components/Drawer/parts.d.ts.map +1 -0
- package/dist/components/Drawer/sheetPhysics.d.ts +56 -0
- package/dist/components/Drawer/sheetPhysics.d.ts.map +1 -0
- package/dist/components/Drawer/styles.css.d.ts +16 -57
- package/dist/components/Drawer/styles.css.d.ts.map +1 -1
- package/dist/components/Drawer/touchInput.d.ts +35 -0
- package/dist/components/Drawer/touchInput.d.ts.map +1 -0
- package/dist/components/Drawer/useDrawerRoot.d.ts +33 -0
- package/dist/components/Drawer/useDrawerRoot.d.ts.map +1 -0
- package/dist/components/Drawer/useSheetHeights.d.ts +7 -0
- package/dist/components/Drawer/useSheetHeights.d.ts.map +1 -0
- package/dist/components/Drawer/useSwipeDismiss.d.ts +13 -2
- package/dist/components/Drawer/useSwipeDismiss.d.ts.map +1 -1
- package/dist/components/Form/FormSubmitContext.d.ts +8 -0
- package/dist/components/Form/FormSubmitContext.d.ts.map +1 -0
- package/dist/components/Listbox/native.css.d.ts +84 -0
- package/dist/components/Listbox/native.css.d.ts.map +1 -0
- package/dist/components/Menu/Menu.d.ts +7 -1
- package/dist/components/Menu/Menu.d.ts.map +1 -1
- package/dist/components/Menu/native.css.d.ts +14 -0
- package/dist/components/Menu/native.css.d.ts.map +1 -0
- package/dist/components/Menu/useMenuOpen.d.ts +16 -0
- package/dist/components/Menu/useMenuOpen.d.ts.map +1 -0
- package/dist/components/Menu/useMenuRoot.d.ts +3 -6
- package/dist/components/Menu/useMenuRoot.d.ts.map +1 -1
- package/dist/components/Popover/native.css.d.ts +11 -0
- package/dist/components/Popover/native.css.d.ts.map +1 -0
- package/dist/components/Select/Select.d.ts +9 -2
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/components/Select/Select.stories.d.ts +11 -0
- package/dist/components/Select/Select.stories.d.ts.map +1 -1
- package/dist/components/Select/native.css.d.ts +11 -0
- package/dist/components/Select/native.css.d.ts.map +1 -0
- package/dist/components/Select/selectState.d.ts +21 -0
- package/dist/components/Select/selectState.d.ts.map +1 -0
- package/dist/components/Select/styles.css.d.ts +10 -0
- package/dist/components/Select/styles.css.d.ts.map +1 -1
- package/dist/components/Select/useSelectRoot.d.ts +6 -8
- package/dist/components/Select/useSelectRoot.d.ts.map +1 -1
- package/dist/components/SideNav/Chevron.d.ts +4 -0
- package/dist/components/SideNav/Chevron.d.ts.map +1 -0
- package/dist/components/SideNav/SideNav.d.ts.map +1 -1
- package/dist/components/SideNav/styles.css.d.ts +6 -0
- package/dist/components/SideNav/styles.css.d.ts.map +1 -1
- package/dist/components/SideNav/types.d.ts +6 -0
- package/dist/components/SideNav/types.d.ts.map +1 -0
- package/dist/components/SplitPane/SplitPane.d.ts +38 -0
- package/dist/components/SplitPane/SplitPane.d.ts.map +1 -0
- package/dist/components/SplitPane/SplitPane.meta.d.ts +3 -0
- package/dist/components/SplitPane/SplitPane.meta.d.ts.map +1 -0
- package/dist/components/SplitPane/SplitPane.stories.d.ts +17 -0
- package/dist/components/SplitPane/SplitPane.stories.d.ts.map +1 -0
- package/dist/components/SplitPane/styles.css.d.ts +78 -0
- package/dist/components/SplitPane/styles.css.d.ts.map +1 -0
- package/dist/components/Tabs/TabLayout.d.ts +7 -0
- package/dist/components/Tabs/TabLayout.d.ts.map +1 -0
- package/dist/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/components/Tabs/indicator.d.ts +28 -0
- package/dist/components/Tabs/indicator.d.ts.map +1 -0
- package/dist/components/Tabs/styles.css.d.ts +4 -0
- package/dist/components/Tabs/styles.css.d.ts.map +1 -1
- package/dist/components/Tabs/useTabIndicator.d.ts +4 -0
- package/dist/components/Tabs/useTabIndicator.d.ts.map +1 -0
- package/dist/components/Tabs/useTabKeys.d.ts +13 -0
- package/dist/components/Tabs/useTabKeys.d.ts.map +1 -0
- package/dist/components/Tag/RemoveGlyph.d.ts +4 -0
- package/dist/components/Tag/RemoveGlyph.d.ts.map +1 -0
- package/dist/components/Tag/Tag.d.ts +2 -24
- package/dist/components/Tag/Tag.d.ts.map +1 -1
- package/dist/components/Tag/styles.css.d.ts +8 -0
- package/dist/components/Tag/styles.css.d.ts.map +1 -1
- package/dist/components/Tag/types.d.ts +39 -0
- package/dist/components/Tag/types.d.ts.map +1 -0
- package/dist/components/ThemeProvider/ThemeOverrides.stories.d.ts +13 -0
- package/dist/components/ThemeProvider/ThemeOverrides.stories.d.ts.map +1 -0
- package/dist/components/ThemeProvider/ThemeProvider.d.ts +9 -2
- package/dist/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
- package/dist/components/ThemeProvider/overrideRule.d.ts +7 -0
- package/dist/components/ThemeProvider/overrideRule.d.ts.map +1 -0
- package/dist/components/ThemeProvider/overrides.d.ts +16 -0
- package/dist/components/ThemeProvider/overrides.d.ts.map +1 -0
- package/dist/components/Toast/ToastProvider.d.ts +2 -21
- package/dist/components/Toast/ToastProvider.d.ts.map +1 -1
- package/dist/components/Toast/native.css.d.ts +81 -0
- package/dist/components/Toast/native.css.d.ts.map +1 -0
- package/dist/components/Toast/store.d.ts +47 -0
- package/dist/components/Toast/store.d.ts.map +1 -0
- package/dist/components/Tooltip/TooltipHint.d.ts +9 -0
- package/dist/components/Tooltip/TooltipHint.d.ts.map +1 -0
- package/dist/components/Tree/Tree.d.ts.map +1 -1
- package/dist/components/Tree/styles.css.d.ts +6 -0
- package/dist/components/Tree/styles.css.d.ts.map +1 -1
- package/dist/components/Tree/useTreeKeyboard.d.ts +19 -0
- package/dist/components/Tree/useTreeKeyboard.d.ts.map +1 -0
- package/dist/components/VirtualList/VirtualList.d.ts +33 -0
- package/dist/components/VirtualList/VirtualList.d.ts.map +1 -0
- package/dist/components/VirtualList/VirtualList.meta.d.ts +3 -0
- package/dist/components/VirtualList/VirtualList.meta.d.ts.map +1 -0
- package/dist/components/VirtualList/VirtualList.stories.d.ts +15 -0
- package/dist/components/VirtualList/VirtualList.stories.d.ts.map +1 -0
- package/dist/components/VirtualList/styles.css.d.ts +28 -0
- package/dist/components/VirtualList/styles.css.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5090 -4381
- package/dist/index.js.map +1 -1
- package/dist/shared/ButtonHost.d.ts +11 -0
- package/dist/shared/ButtonHost.d.ts.map +1 -0
- package/dist/shared/pressableChild.d.ts +14 -0
- package/dist/shared/pressableChild.d.ts.map +1 -0
- package/dist/shared/textOf.d.ts +7 -0
- package/dist/shared/textOf.d.ts.map +1 -0
- package/dist/table.js +1 -1
- package/package.json +3 -2
- package/src/components/AppShell/AppShell.meta.ts +1 -1
- package/src/components/AppShell/AppShell.native.tsx +174 -0
- package/src/components/AppShell/native.css.ts +56 -0
- package/src/components/Button/Button.tsx +15 -4
- package/src/components/ColorMode/ColorMode.meta.ts +1 -1
- package/src/components/ColorMode/ColorMode.tsx +16 -37
- package/src/components/ColorMode/storage.native.ts +8 -0
- package/src/components/ColorMode/storage.ts +19 -0
- package/src/components/ColorMode/styles.css.ts +6 -0
- package/src/components/ColorMode/useSystemScheme.native.ts +6 -0
- package/src/components/ColorMode/useSystemScheme.ts +19 -0
- package/src/components/Combobox/Combobox.meta.ts +1 -1
- package/src/components/Combobox/Combobox.native.tsx +295 -0
- package/src/components/Combobox/Combobox.tsx +14 -8
- package/src/components/Combobox/comboboxState.ts +107 -0
- package/src/components/Combobox/native.css.ts +33 -0
- package/src/components/Combobox/useComboboxRoot.ts +17 -91
- package/src/components/Drawer/Drawer.meta.ts +2 -1
- package/src/components/Drawer/Drawer.native.tsx +491 -0
- package/src/components/Drawer/Drawer.tsx +65 -143
- package/src/components/Drawer/DrawerScroll.stories.tsx +4 -54
- package/src/components/Drawer/DrawerSnap.stories.tsx +225 -0
- package/src/components/Drawer/native.css.ts +62 -0
- package/src/components/Drawer/parts.css.ts +125 -0
- package/src/components/Drawer/parts.tsx +110 -0
- package/src/components/Drawer/sheetPhysics.ts +103 -0
- package/src/components/Drawer/styles.css.ts +18 -73
- package/src/components/Drawer/touchInput.ts +81 -0
- package/src/components/Drawer/useDrawerRoot.ts +90 -0
- package/src/components/Drawer/useSheetHeights.ts +42 -0
- package/src/components/Drawer/useSwipeDismiss.ts +146 -23
- package/src/components/Form/Form.meta.ts +1 -1
- package/src/components/Form/Form.native.tsx +120 -0
- package/src/components/Form/FormSubmitContext.ts +12 -0
- package/src/components/Listbox/Listbox.meta.ts +1 -1
- package/src/components/Listbox/Listbox.native.tsx +153 -0
- package/src/components/Listbox/native.css.ts +91 -0
- package/src/components/Menu/Menu.meta.ts +1 -1
- package/src/components/Menu/Menu.native.tsx +268 -0
- package/src/components/Menu/Menu.tsx +11 -2
- package/src/components/Menu/native.css.ts +18 -0
- package/src/components/Menu/useMenuOpen.ts +33 -0
- package/src/components/Menu/useMenuRoot.ts +7 -26
- package/src/components/Popover/Popover.meta.ts +1 -1
- package/src/components/Popover/Popover.native.tsx +205 -0
- package/src/components/Popover/native.css.ts +13 -0
- package/src/components/ScrollArea/ScrollArea.meta.ts +1 -1
- package/src/components/ScrollArea/ScrollArea.native.tsx +61 -0
- package/src/components/Select/Select.meta.ts +1 -1
- package/src/components/Select/Select.native.tsx +224 -0
- package/src/components/Select/Select.stories.tsx +49 -1
- package/src/components/Select/Select.tsx +20 -3
- package/src/components/Select/native.css.ts +13 -0
- package/src/components/Select/selectState.ts +36 -0
- package/src/components/Select/styles.css.ts +11 -0
- package/src/components/Select/useSelectRoot.ts +9 -30
- package/src/components/SideNav/Chevron.native.tsx +20 -0
- package/src/components/SideNav/Chevron.tsx +20 -0
- package/src/components/SideNav/SideNav.meta.ts +1 -1
- package/src/components/SideNav/SideNav.tsx +57 -41
- package/src/components/SideNav/styles.css.ts +8 -0
- package/src/components/SideNav/types.ts +6 -0
- package/src/components/SplitPane/SplitPane.meta.ts +40 -0
- package/src/components/SplitPane/SplitPane.native.tsx +47 -0
- package/src/components/SplitPane/SplitPane.stories.tsx +148 -0
- package/src/components/SplitPane/SplitPane.tsx +218 -0
- package/src/components/SplitPane/styles.css.ts +81 -0
- package/src/components/Tabs/TabLayout.native.tsx +9 -0
- package/src/components/Tabs/TabLayout.tsx +6 -0
- package/src/components/Tabs/Tabs.meta.ts +1 -1
- package/src/components/Tabs/Tabs.tsx +38 -136
- package/src/components/Tabs/indicator.ts +36 -0
- package/src/components/Tabs/styles.css.ts +7 -0
- package/src/components/Tabs/useTabIndicator.native.ts +35 -0
- package/src/components/Tabs/useTabIndicator.ts +49 -0
- package/src/components/Tabs/useTabKeys.native.ts +2 -0
- package/src/components/Tabs/useTabKeys.ts +99 -0
- package/src/components/Tag/RemoveGlyph.native.tsx +17 -0
- package/src/components/Tag/RemoveGlyph.tsx +10 -0
- package/src/components/Tag/Tag.meta.ts +1 -1
- package/src/components/Tag/Tag.native.tsx +70 -0
- package/src/components/Tag/Tag.tsx +9 -37
- package/src/components/Tag/styles.css.ts +9 -0
- package/src/components/Tag/types.ts +43 -0
- package/src/components/ThemeProvider/ThemeOverrides.stories.tsx +113 -0
- package/src/components/ThemeProvider/ThemeProvider.meta.ts +1 -1
- package/src/components/ThemeProvider/ThemeProvider.native.tsx +36 -7
- package/src/components/ThemeProvider/ThemeProvider.tsx +42 -6
- package/src/components/ThemeProvider/overrideRule.ts +39 -0
- package/src/components/ThemeProvider/overrides.ts +27 -0
- package/src/components/Toast/Toast.meta.ts +1 -1
- package/src/components/Toast/ToastProvider.native.tsx +226 -0
- package/src/components/Toast/ToastProvider.tsx +22 -84
- package/src/components/Toast/native.css.ts +78 -0
- package/src/components/Toast/store.ts +96 -0
- package/src/components/Toggle/Toggle.tsx +3 -3
- package/src/components/Tooltip/Tooltip.meta.ts +1 -1
- package/src/components/Tooltip/Tooltip.native.tsx +36 -0
- package/src/components/Tooltip/TooltipHint.ts +13 -0
- package/src/components/Tree/Tree.meta.ts +1 -1
- package/src/components/Tree/Tree.tsx +23 -135
- package/src/components/Tree/styles.css.ts +6 -0
- package/src/components/Tree/useTreeKeyboard.native.ts +5 -0
- package/src/components/Tree/useTreeKeyboard.ts +152 -0
- package/src/components/VirtualList/VirtualList.meta.ts +39 -0
- package/src/components/VirtualList/VirtualList.stories.tsx +95 -0
- package/src/components/VirtualList/VirtualList.tsx +103 -0
- package/src/components/VirtualList/styles.css.ts +31 -0
- package/src/form.native.ts +7 -0
- package/src/hooks/useContainerBelow.native.ts +30 -0
- package/src/index.native.ts +48 -0
- package/src/index.ts +9 -1
- package/src/shared/ButtonHost.native.tsx +70 -0
- package/src/shared/ButtonHost.tsx +12 -0
- package/src/shared/focusInput.native.ts +18 -0
- package/src/shared/portal/AnchoredOverlay.native.tsx +117 -0
- package/src/shared/pressableChild.ts +33 -0
- package/src/shared/textOf.ts +13 -0
- package/src/shared/useKeyboardHeight.native.ts +18 -0
- package/src/shared/useScreenReaderEnabled.native.ts +19 -0
- package/dist/Table-ColfpgKV.js +0 -1552
- package/dist/Table-ColfpgKV.js.map +0 -1
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
type RefObject,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
} from 'react'
|
|
10
|
+
import {
|
|
11
|
+
type AccessibilityActionEvent,
|
|
12
|
+
type LayoutChangeEvent,
|
|
13
|
+
Pressable,
|
|
14
|
+
StyleSheet,
|
|
15
|
+
useWindowDimensions,
|
|
16
|
+
type View,
|
|
17
|
+
} from 'react-native'
|
|
18
|
+
import {Gesture, GestureDetector, type GestureType} from 'react-native-gesture-handler'
|
|
19
|
+
import Animated, {
|
|
20
|
+
type AnimatedRef,
|
|
21
|
+
Easing,
|
|
22
|
+
scrollTo,
|
|
23
|
+
useAnimatedRef,
|
|
24
|
+
useAnimatedScrollHandler,
|
|
25
|
+
useAnimatedStyle,
|
|
26
|
+
useSharedValue,
|
|
27
|
+
withTiming,
|
|
28
|
+
} from 'react-native-reanimated'
|
|
29
|
+
import {scheduleOnRN} from 'react-native-worklets'
|
|
30
|
+
import {html} from 'react-strict-dom'
|
|
31
|
+
import {DURATION_MS, SIZES_PX} from '@duro-app/tokens/keys'
|
|
32
|
+
import {ModalContext} from '../../shared/ModalContext'
|
|
33
|
+
import {OverlayModal} from '../../shared/portal/OverlayModal'
|
|
34
|
+
import {focusForAccessibility} from '../../shared/accessibilityFocus'
|
|
35
|
+
import {useSafeAreaInsets} from '../../shared/safeArea'
|
|
36
|
+
import {useKeyboardHeight} from '../../shared/useKeyboardHeight'
|
|
37
|
+
import {withPress} from '../../shared/pressableChild'
|
|
38
|
+
import {ControlContextBoundary} from '../Toolbar/ControlContextBoundary'
|
|
39
|
+
import {Icon} from '../Icon/Icon'
|
|
40
|
+
import {nativeStyles} from './native.css'
|
|
41
|
+
import {partStyles} from './parts.css'
|
|
42
|
+
import {
|
|
43
|
+
DEFAULT_GRABBER_LABELS,
|
|
44
|
+
Description,
|
|
45
|
+
DrawerContext,
|
|
46
|
+
Footer,
|
|
47
|
+
Header,
|
|
48
|
+
Title,
|
|
49
|
+
useDrawer,
|
|
50
|
+
type DrawerSize,
|
|
51
|
+
type GrabberLabels,
|
|
52
|
+
} from './parts'
|
|
53
|
+
import {
|
|
54
|
+
CROSS_SLOP,
|
|
55
|
+
DISMISS_DURATION,
|
|
56
|
+
DRAG_SLOP,
|
|
57
|
+
SNAP_DURATION,
|
|
58
|
+
SNAP_MAX_OVERSHOOT,
|
|
59
|
+
cycleDetent,
|
|
60
|
+
detentHeight,
|
|
61
|
+
rubberBand,
|
|
62
|
+
settleDetent,
|
|
63
|
+
shouldDismissEdge,
|
|
64
|
+
} from './sheetPhysics'
|
|
65
|
+
import {useDrawerRoot, type DrawerRootProps} from './useDrawerRoot'
|
|
66
|
+
|
|
67
|
+
export type {DrawerAnchor, DrawerSize, SnapPoint} from './parts'
|
|
68
|
+
|
|
69
|
+
/** Native-only wiring: where accessibility focus returns on close. */
|
|
70
|
+
const TriggerRefContext = createContext<RefObject<unknown> | null>(null)
|
|
71
|
+
|
|
72
|
+
/** What Drawer.Body needs from its sheet: the scroll the drag defers to. */
|
|
73
|
+
interface SheetScroll {
|
|
74
|
+
scrollRef: AnimatedRef<Animated.ScrollView>
|
|
75
|
+
scrollGesture: GestureType
|
|
76
|
+
onScroll: ReturnType<typeof useAnimatedScrollHandler>
|
|
77
|
+
/** px of a snap sheet below the screen's bottom edge at its detent */
|
|
78
|
+
hiddenBelow: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const SheetScrollContext = createContext<SheetScroll | null>(null)
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Native Drawer (plan N0, Slice B): the same compound API, in an
|
|
85
|
+
* OverlayModal. The panel follows the finger on the UI thread (Gesture
|
|
86
|
+
* Handler + Reanimated); a bottom drawer with `snapPoints` rests at its
|
|
87
|
+
* detents and dismisses below the lowest. The backdrop press and Android's
|
|
88
|
+
* back button close it; the screen reader's focus moves into the panel on
|
|
89
|
+
* open and back to the trigger on close.
|
|
90
|
+
*/
|
|
91
|
+
function Root({children, ...props}: DrawerRootProps) {
|
|
92
|
+
const ctx = useDrawerRoot(props)
|
|
93
|
+
const triggerRef = useRef<unknown>(null)
|
|
94
|
+
|
|
95
|
+
// Back to the trigger once the drawer is gone.
|
|
96
|
+
const wasOpen = useRef(ctx.open)
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (wasOpen.current && !ctx.open) focusForAccessibility(triggerRef.current, 'Drawer')
|
|
99
|
+
wasOpen.current = ctx.open
|
|
100
|
+
}, [ctx.open])
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<DrawerContext.Provider value={ctx}>
|
|
104
|
+
<TriggerRefContext.Provider value={triggerRef}>{children}</TriggerRefContext.Provider>
|
|
105
|
+
</DrawerContext.Provider>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Opens the drawer from its one child (a Button), whose host gets focus back on close. */
|
|
110
|
+
function Trigger({children}: {children: ReactNode}) {
|
|
111
|
+
const {requestOpen} = useDrawer()
|
|
112
|
+
const triggerRef = useContext(TriggerRefContext)
|
|
113
|
+
return withPress(children, requestOpen, triggerRef ?? undefined)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface PortalProps {
|
|
117
|
+
children: ReactNode
|
|
118
|
+
size?: DrawerSize
|
|
119
|
+
/** The grabber's accessible names, by what a press does next. Snap sheets only */
|
|
120
|
+
grabberLabels?: GrabberLabels
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const PANEL_WIDTH = {sm: SIZES_PX.panelSm, md: SIZES_PX.panelMd, lg: SIZES_PX.panelLg} as const
|
|
124
|
+
|
|
125
|
+
/** A content-height bottom drawer is capped at 85% of the window, as the web caps it at 85vh. */
|
|
126
|
+
const CONTENT_SHEET_CAP = 0.85
|
|
127
|
+
|
|
128
|
+
const easeOut = Easing.out(Easing.cubic)
|
|
129
|
+
const easeIn = Easing.in(Easing.cubic)
|
|
130
|
+
|
|
131
|
+
function Portal({children, size = 'md', grabberLabels = DEFAULT_GRABBER_LABELS}: PortalProps) {
|
|
132
|
+
const {
|
|
133
|
+
open,
|
|
134
|
+
closing,
|
|
135
|
+
closeDuration,
|
|
136
|
+
anchor,
|
|
137
|
+
dismissable,
|
|
138
|
+
swipeDismiss,
|
|
139
|
+
requestClose,
|
|
140
|
+
requestCloseImmediate,
|
|
141
|
+
titleId,
|
|
142
|
+
panelRef,
|
|
143
|
+
snapPoints,
|
|
144
|
+
snapIndex,
|
|
145
|
+
setSnapIndex,
|
|
146
|
+
} = useDrawer()
|
|
147
|
+
const window = useWindowDimensions()
|
|
148
|
+
const insets = useSafeAreaInsets()
|
|
149
|
+
// A bottom drawer rides above the on-screen keyboard: its detents measure
|
|
150
|
+
// from the keyboard's top edge, so a search field and its results stay seen.
|
|
151
|
+
const keyboard = useKeyboardHeight()
|
|
152
|
+
const lift = anchor === 'bottom' ? keyboard : 0
|
|
153
|
+
const availableHeight = window.height - lift
|
|
154
|
+
const bottom = anchor === 'bottom'
|
|
155
|
+
const snapping = snapPoints.length > 0
|
|
156
|
+
|
|
157
|
+
const heights = useMemo(
|
|
158
|
+
() => snapPoints.map((p) => detentHeight(p, availableHeight, insets.top)),
|
|
159
|
+
[snapPoints, availableHeight, insets.top],
|
|
160
|
+
)
|
|
161
|
+
const high = heights.length > 0 ? heights[heights.length - 1] : 0
|
|
162
|
+
// Where the panel rests at its detent: its offset below its own full height.
|
|
163
|
+
const rest = snapping ? high - heights[snapIndex] : 0
|
|
164
|
+
// Off screen: the whole panel past its edge (a snap sheet's overshoot skirt included).
|
|
165
|
+
const hidden = snapping ? high + SNAP_MAX_OVERSHOOT : bottom ? window.height : window.width
|
|
166
|
+
|
|
167
|
+
// The panel's displacement toward its edge, in px: 0 shows it whole; a
|
|
168
|
+
// snap sheet rests at restOf(detent); negative only while pulled past its
|
|
169
|
+
// largest detent.
|
|
170
|
+
const offset = useSharedValue(hidden)
|
|
171
|
+
const start = useSharedValue(0)
|
|
172
|
+
const extent = useSharedValue(bottom ? window.height : window.width)
|
|
173
|
+
const index = useSharedValue(snapIndex)
|
|
174
|
+
const owns = useSharedValue(false)
|
|
175
|
+
const inBody = useSharedValue(false)
|
|
176
|
+
const scrollY = useSharedValue(0)
|
|
177
|
+
const scrollRef = useAnimatedRef<Animated.ScrollView>()
|
|
178
|
+
|
|
179
|
+
// Enter, exit, and a detent chosen from React (the grabber): animate there.
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (!open) {
|
|
182
|
+
offset.value = hidden
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
if (closing) {
|
|
186
|
+
offset.value = withTiming(hidden, {duration: DURATION_MS[closeDuration], easing: easeIn})
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
index.value = snapIndex
|
|
190
|
+
offset.value = withTiming(rest, {duration: DURATION_MS.base, easing: easeOut})
|
|
191
|
+
// `rest` and `hidden` follow the window: a rotation re-seats the panel too.
|
|
192
|
+
}, [open, closing, closeDuration, snapIndex, rest, hidden, index, offset])
|
|
193
|
+
|
|
194
|
+
const heightsShared = useSharedValue(heights)
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
heightsShared.value = heights
|
|
197
|
+
}, [heights, heightsShared])
|
|
198
|
+
|
|
199
|
+
const scrollGesture = useMemo(
|
|
200
|
+
() =>
|
|
201
|
+
Gesture.Native().onBegin(() => {
|
|
202
|
+
inBody.value = true
|
|
203
|
+
}),
|
|
204
|
+
[inBody],
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
const onScroll = useAnimatedScrollHandler((e) => {
|
|
208
|
+
scrollY.value = e.contentOffset.y
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
const horizontalSign = anchor === 'left' ? -1 : 1
|
|
212
|
+
const canDismiss = swipeDismiss && dismissable
|
|
213
|
+
const pan = useMemo(() => {
|
|
214
|
+
const toEdge = (x: number, y: number) => {
|
|
215
|
+
'worklet'
|
|
216
|
+
return bottom ? y : x * horizontalSign
|
|
217
|
+
}
|
|
218
|
+
const settleTo = (target: number) => {
|
|
219
|
+
'worklet'
|
|
220
|
+
offset.value = withTiming(target, {duration: SNAP_DURATION, easing: easeOut})
|
|
221
|
+
}
|
|
222
|
+
const dismiss = () => {
|
|
223
|
+
'worklet'
|
|
224
|
+
offset.value = withTiming(hidden, {duration: DISMISS_DURATION, easing: easeIn}, (done) => {
|
|
225
|
+
if (done) scheduleOnRN(requestCloseImmediate)
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
const gesture = Gesture.Pan()
|
|
229
|
+
.enabled(swipeDismiss || snapping)
|
|
230
|
+
.onStart((e) => {
|
|
231
|
+
start.value = offset.value
|
|
232
|
+
const t = toEdge(e.translationX, e.translationY)
|
|
233
|
+
// From the content, the sheet takes the drag only with the list at its
|
|
234
|
+
// top: toward the edge, or upward while a larger detent remains.
|
|
235
|
+
owns.value =
|
|
236
|
+
!bottom || !inBody.value
|
|
237
|
+
? true
|
|
238
|
+
: scrollY.value <= 0 &&
|
|
239
|
+
(t > 0 || (snapping && index.value < heightsShared.value.length - 1))
|
|
240
|
+
})
|
|
241
|
+
.onUpdate((e) => {
|
|
242
|
+
if (!owns.value) return
|
|
243
|
+
if (inBody.value) scrollTo(scrollRef, 0, 0, false)
|
|
244
|
+
const next = start.value + toEdge(e.translationX, e.translationY)
|
|
245
|
+
if (snapping) offset.value = next < 0 ? -rubberBand(-next, SNAP_MAX_OVERSHOOT) : next
|
|
246
|
+
else offset.value = Math.max(0, next)
|
|
247
|
+
})
|
|
248
|
+
.onEnd((e) => {
|
|
249
|
+
if (!owns.value) return
|
|
250
|
+
const velocity = toEdge(e.velocityX, e.velocityY) / 1000
|
|
251
|
+
if (snapping) {
|
|
252
|
+
const hs = heightsShared.value
|
|
253
|
+
const top = hs[hs.length - 1]
|
|
254
|
+
const settled = settleDetent({
|
|
255
|
+
heights: hs,
|
|
256
|
+
from: index.value,
|
|
257
|
+
height: top - offset.value,
|
|
258
|
+
velocity,
|
|
259
|
+
})
|
|
260
|
+
if (settled === 'dismiss') {
|
|
261
|
+
if (canDismiss) dismiss()
|
|
262
|
+
else settleTo(top - hs[0])
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
settleTo(top - hs[settled])
|
|
266
|
+
if (settled !== index.value) {
|
|
267
|
+
index.value = settled
|
|
268
|
+
scheduleOnRN(setSnapIndex, settled)
|
|
269
|
+
}
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
if (canDismiss && shouldDismissEdge(offset.value, extent.value, velocity)) dismiss()
|
|
273
|
+
else settleTo(0)
|
|
274
|
+
})
|
|
275
|
+
.onFinalize(() => {
|
|
276
|
+
inBody.value = false
|
|
277
|
+
owns.value = false
|
|
278
|
+
})
|
|
279
|
+
.simultaneousWithExternalGesture(scrollGesture)
|
|
280
|
+
return bottom
|
|
281
|
+
? gesture.activeOffsetY([-DRAG_SLOP, DRAG_SLOP]).failOffsetX([-CROSS_SLOP, CROSS_SLOP])
|
|
282
|
+
: gesture.activeOffsetX([-DRAG_SLOP, DRAG_SLOP]).failOffsetY([-CROSS_SLOP, CROSS_SLOP])
|
|
283
|
+
}, [
|
|
284
|
+
bottom,
|
|
285
|
+
horizontalSign,
|
|
286
|
+
snapping,
|
|
287
|
+
swipeDismiss,
|
|
288
|
+
canDismiss,
|
|
289
|
+
hidden,
|
|
290
|
+
requestCloseImmediate,
|
|
291
|
+
setSnapIndex,
|
|
292
|
+
scrollGesture,
|
|
293
|
+
offset,
|
|
294
|
+
start,
|
|
295
|
+
extent,
|
|
296
|
+
index,
|
|
297
|
+
owns,
|
|
298
|
+
inBody,
|
|
299
|
+
scrollY,
|
|
300
|
+
scrollRef,
|
|
301
|
+
heightsShared,
|
|
302
|
+
])
|
|
303
|
+
|
|
304
|
+
const panelMotion = useAnimatedStyle(() =>
|
|
305
|
+
bottom
|
|
306
|
+
? {transform: [{translateY: offset.value}]}
|
|
307
|
+
: {transform: [{translateX: offset.value * horizontalSign}]},
|
|
308
|
+
)
|
|
309
|
+
const backdropMotion = useAnimatedStyle(() => {
|
|
310
|
+
if (snapping) {
|
|
311
|
+
const hs = heightsShared.value
|
|
312
|
+
const visible = hs[hs.length - 1] - offset.value
|
|
313
|
+
return {opacity: Math.min(1, Math.max(0, visible / hs[0]))}
|
|
314
|
+
}
|
|
315
|
+
return {opacity: Math.min(1, Math.max(0, 1 - offset.value / extent.value))}
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
const onPanelLayout = (e: LayoutChangeEvent) => {
|
|
319
|
+
const {width, height} = e.nativeEvent.layout
|
|
320
|
+
extent.value = bottom ? height : width
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const placement = bottom
|
|
324
|
+
? snapping
|
|
325
|
+
? [layout.bottom, {height: high + SNAP_MAX_OVERSHOOT, bottom: lift - SNAP_MAX_OVERSHOOT}]
|
|
326
|
+
: [layout.bottom, {bottom: lift, maxHeight: availableHeight * CONTENT_SHEET_CAP}]
|
|
327
|
+
: [layout.side, anchor === 'left' ? layout.left : layout.right, {maxWidth: PANEL_WIDTH[size]}]
|
|
328
|
+
const padTop = bottom ? 0 : insets.top
|
|
329
|
+
const padBottom = (lift > 0 ? 0 : insets.bottom) + (snapping ? SNAP_MAX_OVERSHOOT : 0)
|
|
330
|
+
|
|
331
|
+
const nextIndex = cycleDetent(snapIndex, snapPoints.length)
|
|
332
|
+
const onGrabberAction = (e: AccessibilityActionEvent) => {
|
|
333
|
+
if (e.nativeEvent.actionName === 'increment') {
|
|
334
|
+
setSnapIndex(Math.min(snapIndex + 1, snapPoints.length - 1))
|
|
335
|
+
} else if (e.nativeEvent.actionName === 'decrement') {
|
|
336
|
+
setSnapIndex(Math.max(snapIndex - 1, 0))
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const sheetScroll: SheetScroll = {
|
|
341
|
+
scrollRef,
|
|
342
|
+
scrollGesture,
|
|
343
|
+
onScroll,
|
|
344
|
+
hiddenBelow: snapping ? high - heights[snapIndex] : 0,
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<OverlayModal
|
|
349
|
+
visible={open}
|
|
350
|
+
onRequestClose={() => {
|
|
351
|
+
if (dismissable) requestClose()
|
|
352
|
+
}}
|
|
353
|
+
onShow={() => focusForAccessibility(panelRef.current, 'Drawer')}
|
|
354
|
+
>
|
|
355
|
+
<Animated.View style={[StyleSheet.absoluteFill, backdropMotion]}>
|
|
356
|
+
<Pressable
|
|
357
|
+
style={StyleSheet.absoluteFill}
|
|
358
|
+
onPress={dismissable ? requestClose : undefined}
|
|
359
|
+
accessible={false}
|
|
360
|
+
importantForAccessibility="no"
|
|
361
|
+
>
|
|
362
|
+
<html.div aria-hidden style={[nativeStyles.fill, nativeStyles.scrim]} />
|
|
363
|
+
</Pressable>
|
|
364
|
+
</Animated.View>
|
|
365
|
+
<GestureDetector gesture={pan}>
|
|
366
|
+
<Animated.View
|
|
367
|
+
ref={panelRef as RefObject<View | null>}
|
|
368
|
+
accessibilityViewIsModal
|
|
369
|
+
onLayout={onPanelLayout}
|
|
370
|
+
style={[placement, panelMotion]}
|
|
371
|
+
>
|
|
372
|
+
<html.div
|
|
373
|
+
role="dialog"
|
|
374
|
+
aria-modal
|
|
375
|
+
aria-labelledby={titleId}
|
|
376
|
+
style={[
|
|
377
|
+
nativeStyles.panel,
|
|
378
|
+
bottom && nativeStyles.panelBottom,
|
|
379
|
+
anchor === 'right' && nativeStyles.panelRight,
|
|
380
|
+
anchor === 'left' && nativeStyles.panelLeft,
|
|
381
|
+
nativeStyles.insets(padTop, padBottom),
|
|
382
|
+
]}
|
|
383
|
+
>
|
|
384
|
+
{snapping && (
|
|
385
|
+
<Pressable
|
|
386
|
+
accessibilityRole="adjustable"
|
|
387
|
+
accessibilityLabel={
|
|
388
|
+
nextIndex > snapIndex ? grabberLabels.expand : grabberLabels.collapse
|
|
389
|
+
}
|
|
390
|
+
accessibilityValue={{text: snapPoints[snapIndex]}}
|
|
391
|
+
accessibilityActions={[{name: 'increment'}, {name: 'decrement'}]}
|
|
392
|
+
onAccessibilityAction={onGrabberAction}
|
|
393
|
+
onPress={() => setSnapIndex(nextIndex)}
|
|
394
|
+
>
|
|
395
|
+
<html.div style={partStyles.grabberHit}>
|
|
396
|
+
<html.div style={partStyles.grabber} />
|
|
397
|
+
</html.div>
|
|
398
|
+
</Pressable>
|
|
399
|
+
)}
|
|
400
|
+
<ModalContext.Provider value={true}>
|
|
401
|
+
<ControlContextBoundary>
|
|
402
|
+
<SheetScrollContext.Provider value={sheetScroll}>
|
|
403
|
+
{children}
|
|
404
|
+
</SheetScrollContext.Provider>
|
|
405
|
+
</ControlContextBoundary>
|
|
406
|
+
</ModalContext.Provider>
|
|
407
|
+
</html.div>
|
|
408
|
+
</Animated.View>
|
|
409
|
+
</GestureDetector>
|
|
410
|
+
</OverlayModal>
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Layout-only RN styles: where the moving panel sits before it moves.
|
|
415
|
+
const layout = StyleSheet.create({
|
|
416
|
+
bottom: {position: 'absolute', left: 0, right: 0, bottom: 0},
|
|
417
|
+
side: {position: 'absolute', top: 0, bottom: 0, width: '100%'},
|
|
418
|
+
left: {left: 0},
|
|
419
|
+
right: {right: 0},
|
|
420
|
+
body: {flexGrow: 0, flexShrink: 1},
|
|
421
|
+
bodyFill: {flex: 1},
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Native Body: a ScrollView, since a native div does not scroll its
|
|
426
|
+
* overflow. In a bottom sheet the drag defers to it: the list scrolls, and
|
|
427
|
+
* moves the sheet only from its top.
|
|
428
|
+
*/
|
|
429
|
+
interface BodyProps {
|
|
430
|
+
children: ReactNode
|
|
431
|
+
/** No side padding: rows that run to the panel's edges, as a picker's list. */
|
|
432
|
+
flush?: boolean
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function Body({children, flush = false}: BodyProps) {
|
|
436
|
+
const sheet = useContext(SheetScrollContext)
|
|
437
|
+
const {snapPoints} = useDrawer()
|
|
438
|
+
if (!sheet) return null
|
|
439
|
+
return (
|
|
440
|
+
<GestureDetector gesture={sheet.scrollGesture}>
|
|
441
|
+
<Animated.ScrollView
|
|
442
|
+
ref={sheet.scrollRef}
|
|
443
|
+
onScroll={sheet.onScroll}
|
|
444
|
+
scrollEventThrottle={16}
|
|
445
|
+
bounces={false}
|
|
446
|
+
// Presses inside the sheet keep the keyboard up. A ScrollView above the
|
|
447
|
+
// sheet's Modal can still take the first touch, which is why picker
|
|
448
|
+
// rows use Gesture Handler's Pressable (Listbox.native).
|
|
449
|
+
keyboardShouldPersistTaps="handled"
|
|
450
|
+
style={snapPoints.length > 0 ? layout.bodyFill : layout.body}
|
|
451
|
+
>
|
|
452
|
+
<html.div
|
|
453
|
+
style={[
|
|
454
|
+
nativeStyles.bodyContent,
|
|
455
|
+
flush && nativeStyles.bodyFlush,
|
|
456
|
+
nativeStyles.hiddenBelow(sheet.hiddenBelow),
|
|
457
|
+
]}
|
|
458
|
+
>
|
|
459
|
+
{children}
|
|
460
|
+
</html.div>
|
|
461
|
+
</Animated.ScrollView>
|
|
462
|
+
</GestureDetector>
|
|
463
|
+
)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
interface CloseProps {
|
|
467
|
+
children?: ReactNode
|
|
468
|
+
'aria-label'?: string
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function Close({children, 'aria-label': ariaLabel = 'Close'}: CloseProps) {
|
|
472
|
+
const {requestClose} = useDrawer()
|
|
473
|
+
if (children) return withPress(children, requestClose)
|
|
474
|
+
return (
|
|
475
|
+
<html.button onClick={requestClose} aria-label={ariaLabel} style={partStyles.closeButton}>
|
|
476
|
+
<Icon name="x" size="sm" color="textMuted" />
|
|
477
|
+
</html.button>
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export const Drawer = {
|
|
482
|
+
Root,
|
|
483
|
+
Trigger,
|
|
484
|
+
Portal,
|
|
485
|
+
Header,
|
|
486
|
+
Title,
|
|
487
|
+
Description,
|
|
488
|
+
Body,
|
|
489
|
+
Footer,
|
|
490
|
+
Close,
|
|
491
|
+
}
|