panelui-native 0.22.6 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/lib/module/components/accordion/index.js +8 -8
- package/lib/module/components/accordion/index.js.map +1 -1
- package/lib/module/components/alert/index.js +2 -2
- package/lib/module/components/alert/index.js.map +1 -1
- package/lib/module/components/area-chart/index.js +958 -0
- package/lib/module/components/area-chart/index.js.map +1 -0
- package/lib/module/components/attachment/index.js +2 -1
- package/lib/module/components/attachment/index.js.map +1 -1
- package/lib/module/components/avatar/index.js +4 -4
- package/lib/module/components/avatar/index.js.map +1 -1
- package/lib/module/components/badge/index.js +4 -4
- package/lib/module/components/badge/index.js.map +1 -1
- package/lib/module/components/bar-chart/index.js +994 -0
- package/lib/module/components/bar-chart/index.js.map +1 -0
- package/lib/module/components/bottom-sheet/index.js +213 -24
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/breadcrumb/index.js +275 -0
- package/lib/module/components/breadcrumb/index.js.map +1 -0
- package/lib/module/components/button/index.js +11 -6
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/calendar/index.js +557 -0
- package/lib/module/components/calendar/index.js.map +1 -0
- package/lib/module/components/carousel/index.js +680 -0
- package/lib/module/components/carousel/index.js.map +1 -0
- package/lib/module/components/chip/index.js +6 -6
- package/lib/module/components/chip/index.js.map +1 -1
- package/lib/module/components/date-picker/index.js +158 -0
- package/lib/module/components/date-picker/index.js.map +1 -0
- package/lib/module/components/dialog/index.js +7 -2
- package/lib/module/components/dialog/index.js.map +1 -1
- package/lib/module/components/direction/index.js +15 -20
- package/lib/module/components/direction/index.js.map +1 -1
- package/lib/module/components/empty-state/index.js +2 -2
- package/lib/module/components/empty-state/index.js.map +1 -1
- package/lib/module/components/field/index.js +5 -5
- package/lib/module/components/field/index.js.map +1 -1
- package/lib/module/components/flow/index.js +3 -3
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/frame/index.js +22 -5
- package/lib/module/components/frame/index.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +28 -8
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/input-group/index.js +5 -4
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/item/index.js +9 -9
- package/lib/module/components/item/index.js.map +1 -1
- package/lib/module/components/label/index.js +4 -4
- package/lib/module/components/label/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +165 -184
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/loader/index.js +758 -0
- package/lib/module/components/loader/index.js.map +1 -0
- package/lib/module/components/map/basemap.js +265 -0
- package/lib/module/components/map/basemap.js.map +1 -0
- package/lib/module/components/map/index.js +850 -0
- package/lib/module/components/map/index.js.map +1 -0
- package/lib/module/components/map/maplibre.js +87 -0
- package/lib/module/components/map/maplibre.js.map +1 -0
- package/lib/module/components/marker/index.js +3 -3
- package/lib/module/components/marker/index.js.map +1 -1
- package/lib/module/components/message/index.js +15 -14
- package/lib/module/components/message/index.js.map +1 -1
- package/lib/module/components/message-scroller/index.js +5 -4
- package/lib/module/components/message-scroller/index.js.map +1 -1
- package/lib/module/components/otp-input/index.js +2 -2
- package/lib/module/components/popover/index.js +39 -7
- package/lib/module/components/popover/index.js.map +1 -1
- package/lib/module/components/progress/index.js +8 -2
- package/lib/module/components/progress/index.js.map +1 -1
- package/lib/module/components/radio-group/index.js +2 -2
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/ring-chart/index.js +423 -0
- package/lib/module/components/ring-chart/index.js.map +1 -0
- package/lib/module/components/section-rail/index.js +6 -6
- package/lib/module/components/section-rail/index.js.map +1 -1
- package/lib/module/components/select/index.js +10 -4
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/separator/index.js +76 -22
- package/lib/module/components/separator/index.js.map +1 -1
- package/lib/module/components/shimmer/index.js +10 -1
- package/lib/module/components/shimmer/index.js.map +1 -1
- package/lib/module/components/slider/index.js +19 -5
- package/lib/module/components/slider/index.js.map +1 -1
- package/lib/module/components/steps/index.js +5 -5
- package/lib/module/components/steps/index.js.map +1 -1
- package/lib/module/components/switch/index.js +6 -1
- package/lib/module/components/switch/index.js.map +1 -1
- package/lib/module/components/tabs/index.js +7 -7
- package/lib/module/components/tabs/index.js.map +1 -1
- package/lib/module/components/timeline/index.js +4 -4
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/components/toast/index.js +2 -2
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/components/toggle-button/index.js +5 -5
- package/lib/module/components/toggle-button/index.js.map +1 -1
- package/lib/module/components/tooltip/index.js +492 -0
- package/lib/module/components/tooltip/index.js.map +1 -0
- package/lib/module/components/typography/index.js +7 -2
- package/lib/module/components/typography/index.js.map +1 -1
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/use-back-handler.js +37 -0
- package/lib/module/hooks/use-back-handler.js.map +1 -0
- package/lib/module/hooks/use-direction.js +43 -0
- package/lib/module/hooks/use-direction.js.map +1 -0
- package/lib/module/icons/index.js +180 -2
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +12 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/keyboard-avoider.js +2 -1
- package/lib/module/primitives/keyboard-avoider.js.map +1 -1
- package/lib/module/primitives/text.js +55 -11
- package/lib/module/primitives/text.js.map +1 -1
- package/lib/module/utils/chart.js +279 -0
- package/lib/module/utils/chart.js.map +1 -0
- package/lib/module/utils/date.js +386 -0
- package/lib/module/utils/date.js.map +1 -0
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
- package/lib/typescript/src/components/alert/index.d.ts.map +1 -1
- package/lib/typescript/src/components/area-chart/index.d.ts +237 -0
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/attachment/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bar-chart/index.d.ts +270 -0
- package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +75 -4
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/breadcrumb/index.d.ts +100 -0
- package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -0
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/calendar/index.d.ts +108 -0
- package/lib/typescript/src/components/calendar/index.d.ts.map +1 -0
- package/lib/typescript/src/components/carousel/index.d.ts +179 -0
- package/lib/typescript/src/components/carousel/index.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/index.d.ts.map +1 -1
- package/lib/typescript/src/components/date-picker/index.d.ts +91 -0
- package/lib/typescript/src/components/date-picker/index.d.ts.map +1 -0
- package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
- package/lib/typescript/src/components/direction/index.d.ts +11 -10
- package/lib/typescript/src/components/direction/index.d.ts.map +1 -1
- package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -1
- package/lib/typescript/src/components/field/index.d.ts.map +1 -1
- package/lib/typescript/src/components/frame/index.d.ts +17 -0
- package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts +22 -3
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/item/index.d.ts.map +1 -1
- package/lib/typescript/src/components/label/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts +42 -2
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/loader/index.d.ts +38 -0
- package/lib/typescript/src/components/loader/index.d.ts.map +1 -0
- package/lib/typescript/src/components/map/basemap.d.ts +76 -0
- package/lib/typescript/src/components/map/basemap.d.ts.map +1 -0
- package/lib/typescript/src/components/map/index.d.ts +367 -0
- package/lib/typescript/src/components/map/index.d.ts.map +1 -0
- package/lib/typescript/src/components/map/maplibre.d.ts +193 -0
- package/lib/typescript/src/components/map/maplibre.d.ts.map +1 -0
- package/lib/typescript/src/components/marker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -1
- package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
- package/lib/typescript/src/components/progress/index.d.ts.map +1 -1
- package/lib/typescript/src/components/ring-chart/index.d.ts +156 -0
- package/lib/typescript/src/components/ring-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/separator/index.d.ts +36 -1
- package/lib/typescript/src/components/separator/index.d.ts.map +1 -1
- package/lib/typescript/src/components/shimmer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
- package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
- package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toggle-button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tooltip/index.d.ts +125 -0
- package/lib/typescript/src/components/tooltip/index.d.ts.map +1 -0
- package/lib/typescript/src/components/typography/index.d.ts +6 -0
- package/lib/typescript/src/components/typography/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/index.d.ts +1 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-back-handler.d.ts +19 -0
- package/lib/typescript/src/hooks/use-back-handler.d.ts.map +1 -0
- package/lib/typescript/src/hooks/use-direction.d.ts +20 -0
- package/lib/typescript/src/hooks/use-direction.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +8 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +14 -4
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/keyboard-avoider.d.ts.map +1 -1
- package/lib/typescript/src/primitives/text.d.ts +20 -0
- package/lib/typescript/src/primitives/text.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +96 -0
- package/lib/typescript/src/utils/chart.d.ts.map +1 -0
- package/lib/typescript/src/utils/date.d.ts +116 -0
- package/lib/typescript/src/utils/date.d.ts.map +1 -0
- package/package.json +9 -1
- package/src/components/accordion/index.tsx +8 -12
- package/src/components/alert/index.tsx +2 -2
- package/src/components/area-chart/index.tsx +1147 -0
- package/src/components/attachment/index.tsx +2 -2
- package/src/components/avatar/index.tsx +4 -4
- package/src/components/badge/index.tsx +6 -6
- package/src/components/bar-chart/index.tsx +1208 -0
- package/src/components/bottom-sheet/index.tsx +282 -21
- package/src/components/breadcrumb/index.tsx +363 -0
- package/src/components/button/index.tsx +12 -8
- package/src/components/calendar/index.tsx +703 -0
- package/src/components/carousel/index.tsx +788 -0
- package/src/components/chip/index.tsx +6 -8
- package/src/components/date-picker/index.tsx +237 -0
- package/src/components/dialog/index.tsx +7 -2
- package/src/components/direction/index.tsx +22 -22
- package/src/components/empty-state/index.tsx +2 -2
- package/src/components/field/index.tsx +3 -5
- package/src/components/flow/index.tsx +3 -3
- package/src/components/frame/index.tsx +22 -7
- package/src/components/heatmap-chart/index.tsx +58 -11
- package/src/components/input-group/index.tsx +5 -4
- package/src/components/item/index.tsx +9 -9
- package/src/components/label/index.tsx +2 -2
- package/src/components/line-chart/index.tsx +194 -223
- package/src/components/loader/index.tsx +812 -0
- package/src/components/map/basemap.ts +304 -0
- package/src/components/map/index.tsx +1116 -0
- package/src/components/map/maplibre.ts +201 -0
- package/src/components/marker/index.tsx +3 -3
- package/src/components/message/index.tsx +15 -14
- package/src/components/message-scroller/index.tsx +5 -4
- package/src/components/otp-input/index.tsx +2 -2
- package/src/components/popover/index.tsx +43 -7
- package/src/components/progress/index.tsx +10 -2
- package/src/components/radio-group/index.tsx +2 -2
- package/src/components/ring-chart/index.tsx +543 -0
- package/src/components/section-rail/index.tsx +6 -6
- package/src/components/select/index.tsx +10 -4
- package/src/components/separator/index.tsx +77 -19
- package/src/components/shimmer/index.tsx +10 -1
- package/src/components/slider/index.tsx +20 -5
- package/src/components/steps/index.tsx +5 -5
- package/src/components/switch/index.tsx +6 -1
- package/src/components/tabs/index.tsx +7 -9
- package/src/components/timeline/index.tsx +4 -4
- package/src/components/toast/index.tsx +2 -2
- package/src/components/toggle-button/index.tsx +5 -7
- package/src/components/tooltip/index.tsx +610 -0
- package/src/components/typography/index.tsx +7 -2
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-back-handler.ts +37 -0
- package/src/hooks/use-direction.ts +45 -0
- package/src/icons/index.tsx +111 -0
- package/src/index.ts +129 -0
- package/src/primitives/keyboard-avoider.tsx +2 -1
- package/src/primitives/text.tsx +53 -8
- package/src/utils/chart.ts +355 -0
- package/src/utils/date.ts +446 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
useEffect,
|
|
8
8
|
useMemo,
|
|
9
9
|
useState,
|
|
10
|
+
type ComponentProps,
|
|
10
11
|
type ReactElement,
|
|
11
12
|
type ReactNode,
|
|
12
13
|
} from 'react';
|
|
@@ -19,10 +20,12 @@ import Animated, {
|
|
|
19
20
|
SlideInDown,
|
|
20
21
|
SlideOutDown,
|
|
21
22
|
runOnJS,
|
|
23
|
+
useAnimatedScrollHandler,
|
|
22
24
|
useAnimatedStyle,
|
|
23
25
|
useSharedValue,
|
|
24
26
|
withSpring,
|
|
25
27
|
withTiming,
|
|
28
|
+
type SharedValue,
|
|
26
29
|
} from 'react-native-reanimated';
|
|
27
30
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
28
31
|
import { tv } from 'tailwind-variants';
|
|
@@ -30,6 +33,8 @@ import { getNativeUI } from '../../native';
|
|
|
30
33
|
import { XIcon } from '../../icons';
|
|
31
34
|
import { Portal } from '../../primitives/portal';
|
|
32
35
|
import { Scrim } from '../../primitives/scrim';
|
|
36
|
+
import { Text, textChildren } from '../../primitives/text';
|
|
37
|
+
import { useBackHandler } from '../../hooks/use-back-handler';
|
|
33
38
|
import { cn } from '../../utils/cn';
|
|
34
39
|
|
|
35
40
|
const SPRING = { damping: 22, stiffness: 280, mass: 0.7 } as const;
|
|
@@ -134,6 +139,24 @@ const NativeSheetContext = createContext<{
|
|
|
134
139
|
snapPoints: BottomSheetProps['snapPoints'];
|
|
135
140
|
} | null>(null);
|
|
136
141
|
|
|
142
|
+
/**
|
|
143
|
+
* The wiring between the sheet's drag and a scrolling body inside it. Set by
|
|
144
|
+
* `Content`, filled in by `Body` — the two gestures have to agree on where the
|
|
145
|
+
* list is scrolled to before either can decide whose drag it is.
|
|
146
|
+
*/
|
|
147
|
+
/*
|
|
148
|
+
* Taken off the factory rather than imported by name: the package exports two
|
|
149
|
+
* different `NativeGesture` types, and the one reachable from its entry point
|
|
150
|
+
* is not the one `Gesture.Native()` returns.
|
|
151
|
+
*/
|
|
152
|
+
type ScrollGesture = ReturnType<typeof Gesture.Native>;
|
|
153
|
+
|
|
154
|
+
const SheetSurfaceContext = createContext<{
|
|
155
|
+
scrollGesture: ScrollGesture;
|
|
156
|
+
scrollOffset: SharedValue<number>;
|
|
157
|
+
hasScrollable: SharedValue<boolean>;
|
|
158
|
+
} | null>(null);
|
|
159
|
+
|
|
137
160
|
function BottomSheetRoot({
|
|
138
161
|
children,
|
|
139
162
|
open,
|
|
@@ -195,8 +218,10 @@ export interface BottomSheetContentProps extends ViewProps {
|
|
|
195
218
|
/** Tap on the backdrop closes the sheet. Default true. */
|
|
196
219
|
dismissible?: boolean;
|
|
197
220
|
/**
|
|
198
|
-
* Show a close button in the top
|
|
199
|
-
*
|
|
221
|
+
* Show a close button in the top trailing corner — the right in a
|
|
222
|
+
* left-to-right app, the left in a right-to-left one. On by default for the
|
|
223
|
+
* styled sheet; ignored by the native sheet, which has its own dismiss
|
|
224
|
+
* affordances.
|
|
200
225
|
*/
|
|
201
226
|
showClose?: boolean;
|
|
202
227
|
/**
|
|
@@ -213,15 +238,35 @@ export interface BottomSheetContentProps extends ViewProps {
|
|
|
213
238
|
* optional `expo-blur`; without it this dims, rather than failing.
|
|
214
239
|
*/
|
|
215
240
|
blur?: boolean;
|
|
241
|
+
/**
|
|
242
|
+
* How tall the sheet opens.
|
|
243
|
+
*
|
|
244
|
+
* `auto` sizes to the content, which is right for a sheet that is a handful
|
|
245
|
+
* of rows. `half` and `full` fix the height instead, for content that has to
|
|
246
|
+
* be given the room rather than allowed to ask for it — a list, a form, a
|
|
247
|
+
* document. Either way the sheet is clamped to leave the status bar clear,
|
|
248
|
+
* so `full` is as tall as the screen allows rather than as tall as the screen.
|
|
249
|
+
*
|
|
250
|
+
* On the native sheet this maps onto the platform's detents.
|
|
251
|
+
*/
|
|
252
|
+
size?: 'auto' | 'half' | 'full';
|
|
216
253
|
children?: ReactNode;
|
|
217
254
|
}
|
|
218
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Fractions of the screen the sized sheets aim for. `full` is not 1: a sheet
|
|
258
|
+
* that reached the top would have nothing behind it to read as laid *over*,
|
|
259
|
+
* and the gap is what says the app is still there underneath.
|
|
260
|
+
*/
|
|
261
|
+
const SIZE_FRACTION = { half: 0.5, full: 0.94 } as const;
|
|
262
|
+
|
|
219
263
|
function BottomSheetContent({
|
|
220
264
|
className,
|
|
221
265
|
dismissible = true,
|
|
222
266
|
showClose = true,
|
|
223
267
|
detached = false,
|
|
224
268
|
blur = false,
|
|
269
|
+
size = 'auto',
|
|
225
270
|
children,
|
|
226
271
|
...props
|
|
227
272
|
}: BottomSheetContentProps) {
|
|
@@ -233,8 +278,23 @@ function BottomSheetContent({
|
|
|
233
278
|
const translateY = useSharedValue(0);
|
|
234
279
|
const closeTint = useCSSVariable('--color-muted-foreground');
|
|
235
280
|
|
|
281
|
+
/*
|
|
282
|
+
* The scrolling body's gesture, if there is one. It is built here rather
|
|
283
|
+
* than in `BottomSheet.Body` because the drag below has to name it, and a
|
|
284
|
+
* gesture is composed before its children have mounted. `Body` attaches it
|
|
285
|
+
* to its own scroll view; with no `Body` it is simply never attached, and
|
|
286
|
+
* the drag reverts to owning every touch.
|
|
287
|
+
*/
|
|
288
|
+
const scrollGesture = useMemo(() => Gesture.Native(), []);
|
|
289
|
+
const scrollOffset = useSharedValue(0);
|
|
290
|
+
const hasScrollable = useSharedValue(false);
|
|
291
|
+
|
|
236
292
|
const close = useCallback(() => setOpen(false), [setOpen]);
|
|
237
293
|
|
|
294
|
+
// The sheet closes on the Android back button while it is up, the same as a
|
|
295
|
+
// backdrop tap — but only when it is dismissible.
|
|
296
|
+
useBackHandler(open && dismissible, close);
|
|
297
|
+
|
|
238
298
|
/*
|
|
239
299
|
* The offset survives a close — the early return below sits after every
|
|
240
300
|
* hook, so this component keeps its state while the sheet is hidden. A
|
|
@@ -256,7 +316,25 @@ function BottomSheetContent({
|
|
|
256
316
|
* is in the middle of, so a tap with any finger travel does nothing.
|
|
257
317
|
*/
|
|
258
318
|
.activeOffsetY([-12, 12])
|
|
319
|
+
/*
|
|
320
|
+
* A scrolling body runs its own gesture, and without this the two
|
|
321
|
+
* compete: whichever wins takes the touch outright, so either the
|
|
322
|
+
* list never scrolls or the sheet never drags. Running them together
|
|
323
|
+
* lets the rule below decide which one a given drag belongs to.
|
|
324
|
+
*/
|
|
325
|
+
.simultaneousWithExternalGesture(scrollGesture)
|
|
259
326
|
.onChange((event) => {
|
|
327
|
+
/*
|
|
328
|
+
* The list gets the drag until it has nothing left to scroll. Once
|
|
329
|
+
* it is at the top the sheet takes over, which is the gesture people
|
|
330
|
+
* already expect: pulling down on a list that cannot go further
|
|
331
|
+
* pulls the sheet instead. The second clause keeps a sheet that is
|
|
332
|
+
* already part-way dragged following the finger, so releasing the
|
|
333
|
+
* drag halfway does not strand it.
|
|
334
|
+
*/
|
|
335
|
+
if (hasScrollable.value && scrollOffset.value > 0 && translateY.value <= 0) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
260
338
|
// Rubber-band when dragging upward, follow the finger downward.
|
|
261
339
|
const next = translateY.value + event.changeY;
|
|
262
340
|
translateY.value = next > 0 ? next : next / 3;
|
|
@@ -276,15 +354,40 @@ function BottomSheetContent({
|
|
|
276
354
|
// Rebuilt only when one of these changes. Built inline it would be a new
|
|
277
355
|
// gesture on every render — and the sheet re-renders while it is being
|
|
278
356
|
// used, each time re-attaching the handler and dropping the live touch.
|
|
279
|
-
[close, screenHeight, translateY]
|
|
357
|
+
[close, screenHeight, translateY, scrollGesture, scrollOffset, hasScrollable]
|
|
280
358
|
);
|
|
281
359
|
|
|
282
360
|
const sheetStyle = useAnimatedStyle(() => ({
|
|
283
361
|
transform: [{ translateY: translateY.value }],
|
|
284
362
|
}));
|
|
285
363
|
|
|
364
|
+
/*
|
|
365
|
+
* A sized sheet is clamped rather than handed its fraction outright.
|
|
366
|
+
* `insets.top` is the status bar and the notch, and content that runs under
|
|
367
|
+
* those is unreadable at exactly the moment the sheet is at its tallest.
|
|
368
|
+
*/
|
|
369
|
+
const sizedHeight =
|
|
370
|
+
size === 'auto'
|
|
371
|
+
? undefined
|
|
372
|
+
: Math.min(
|
|
373
|
+
screenHeight * SIZE_FRACTION[size],
|
|
374
|
+
screenHeight - insets.top - (detached ? 24 : 8)
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
const surface = useMemo(
|
|
378
|
+
() => ({ scrollGesture, scrollOffset, hasScrollable }),
|
|
379
|
+
[scrollGesture, scrollOffset, hasScrollable]
|
|
380
|
+
);
|
|
381
|
+
|
|
286
382
|
if (nativeSheet) {
|
|
287
383
|
const { Host, BottomSheet: NativeBottomSheet, RNHostView } = nativeSheet.nativeUI;
|
|
384
|
+
/*
|
|
385
|
+
* `snapPoints` on the root is the finer control and wins; `size` is the
|
|
386
|
+
* shorthand, and maps onto the platform's own detents rather than a
|
|
387
|
+
* height, so a native sheet keeps the system's snapping.
|
|
388
|
+
*/
|
|
389
|
+
const snapPoints =
|
|
390
|
+
nativeSheet.snapPoints ?? (size === 'auto' ? undefined : [size]);
|
|
288
391
|
// The platform owns presentation, so this stays mounted and toggles
|
|
289
392
|
// isPresented rather than unmounting on close.
|
|
290
393
|
//
|
|
@@ -296,7 +399,7 @@ function BottomSheetContent({
|
|
|
296
399
|
<NativeBottomSheet
|
|
297
400
|
isPresented={open}
|
|
298
401
|
onDismiss={dismissible ? close : () => {}}
|
|
299
|
-
snapPoints={
|
|
402
|
+
snapPoints={snapPoints}
|
|
300
403
|
>
|
|
301
404
|
<RNHostView matchContents>
|
|
302
405
|
<BottomSheetContext.Provider value={context}>
|
|
@@ -316,11 +419,11 @@ function BottomSheetContent({
|
|
|
316
419
|
// against, so `100%` measures against nothing and the
|
|
317
420
|
// content lays out wider than the sheet it sits in.
|
|
318
421
|
width: screenWidth,
|
|
319
|
-
minHeight: detentFloor(
|
|
422
|
+
minHeight: detentFloor(snapPoints, screenHeight),
|
|
320
423
|
paddingBottom: Math.max(insets.bottom, 16),
|
|
321
424
|
}}
|
|
322
425
|
>
|
|
323
|
-
{children}
|
|
426
|
+
{textChildren(children)}
|
|
324
427
|
</View>
|
|
325
428
|
</BottomSheetContext.Provider>
|
|
326
429
|
</RNHostView>
|
|
@@ -364,24 +467,42 @@ function BottomSheetContent({
|
|
|
364
467
|
// indicator, so it takes plain padding rather than stacking the
|
|
365
468
|
// inset on top of the gap.
|
|
366
469
|
{ paddingBottom: detached ? 16 : Math.max(insets.bottom, 16) },
|
|
470
|
+
sizedHeight === undefined ? null : { height: sizedHeight },
|
|
367
471
|
props.style,
|
|
368
472
|
]}
|
|
369
473
|
>
|
|
370
|
-
<
|
|
371
|
-
<View className="
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
474
|
+
<SheetSurfaceContext.Provider value={surface}>
|
|
475
|
+
<View className="mb-3 self-center">
|
|
476
|
+
<View className="h-1 w-10 rounded-full bg-muted-foreground/30" />
|
|
477
|
+
</View>
|
|
478
|
+
{textChildren(children)}
|
|
479
|
+
{/*
|
|
480
|
+
* Last, and lifted above the content.
|
|
481
|
+
*
|
|
482
|
+
* Drawn before the children it was covered by whichever of them
|
|
483
|
+
* reached the top-right corner — a title spanning the sheet's
|
|
484
|
+
* width is enough — and in React Native a later sibling wins
|
|
485
|
+
* the touch. The button still looked untouched, so the failure
|
|
486
|
+
* read as intermittent: taps landing on the sliver above the
|
|
487
|
+
* title worked, taps anywhere else went to the title and did
|
|
488
|
+
* nothing. `hitSlop` made it worse by growing the part of the
|
|
489
|
+
* target that was already buried.
|
|
490
|
+
*/}
|
|
491
|
+
{showClose ? (
|
|
492
|
+
<Pressable
|
|
493
|
+
accessibilityRole="button"
|
|
494
|
+
accessibilityLabel="Close"
|
|
495
|
+
onPress={close}
|
|
496
|
+
hitSlop={8}
|
|
497
|
+
className="absolute end-4 top-3 z-10 h-8 w-8 items-center justify-center rounded-full bg-muted active:opacity-70"
|
|
498
|
+
>
|
|
499
|
+
<XIcon
|
|
500
|
+
size={16}
|
|
501
|
+
color={typeof closeTint === 'string' ? closeTint : undefined}
|
|
502
|
+
/>
|
|
503
|
+
</Pressable>
|
|
504
|
+
) : null}
|
|
505
|
+
</SheetSurfaceContext.Provider>
|
|
385
506
|
</Animated.View>
|
|
386
507
|
</GestureDetector>
|
|
387
508
|
</View>
|
|
@@ -390,7 +511,147 @@ function BottomSheetContent({
|
|
|
390
511
|
);
|
|
391
512
|
}
|
|
392
513
|
|
|
514
|
+
export interface BottomSheetHeaderProps extends ViewProps {
|
|
515
|
+
className?: string;
|
|
516
|
+
/** Heading for the sheet. Strings are wrapped; anything else is drawn as given. */
|
|
517
|
+
title?: ReactNode;
|
|
518
|
+
/** A line under the title, for what the sheet is asking. */
|
|
519
|
+
description?: ReactNode;
|
|
520
|
+
children?: ReactNode;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* A fixed heading above the sheet's body.
|
|
525
|
+
*
|
|
526
|
+
* It exists mostly to reserve the top-right corner. The close button is
|
|
527
|
+
* absolutely positioned there, so anything full-width at the top of a sheet
|
|
528
|
+
* sits under it — a title is exactly that shape, and the end padding here is
|
|
529
|
+
* what keeps the two from meeting. Being outside `Body` is the other half:
|
|
530
|
+
* it stays put while the content scrolls under it, so the sheet still says
|
|
531
|
+
* what it is once the list has moved.
|
|
532
|
+
*/
|
|
533
|
+
function BottomSheetHeader({
|
|
534
|
+
className,
|
|
535
|
+
title,
|
|
536
|
+
description,
|
|
537
|
+
children,
|
|
538
|
+
...props
|
|
539
|
+
}: BottomSheetHeaderProps) {
|
|
540
|
+
return (
|
|
541
|
+
<View className={cn('gap-1 pb-3 pe-12', className)} {...props}>
|
|
542
|
+
{textChildren(title, (text) => (
|
|
543
|
+
<Text size="lg" weight="semibold">
|
|
544
|
+
{text}
|
|
545
|
+
</Text>
|
|
546
|
+
))}
|
|
547
|
+
{textChildren(description, (text) => (
|
|
548
|
+
<Text size="sm" muted>
|
|
549
|
+
{text}
|
|
550
|
+
</Text>
|
|
551
|
+
))}
|
|
552
|
+
{textChildren(children)}
|
|
553
|
+
</View>
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
BottomSheetHeader.displayName = 'BottomSheet.Header';
|
|
557
|
+
|
|
558
|
+
export interface BottomSheetBodyProps
|
|
559
|
+
extends Omit<ComponentProps<typeof Animated.ScrollView>, 'ref'> {
|
|
560
|
+
className?: string;
|
|
561
|
+
children?: ReactNode;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* The scrolling part of a sized sheet.
|
|
566
|
+
*
|
|
567
|
+
* A plain `ScrollView` works here too, but only one of the two gestures can
|
|
568
|
+
* win a given drag and neither knows about the other, so the list and the
|
|
569
|
+
* sheet fight over every downward swipe. This one reports where it is
|
|
570
|
+
* scrolled to, which is what lets the sheet hold off until the list has run
|
|
571
|
+
* out — pull down on a list at its top and the sheet comes with you, pull
|
|
572
|
+
* down anywhere else and the list scrolls.
|
|
573
|
+
*/
|
|
574
|
+
function BottomSheetBody({
|
|
575
|
+
className,
|
|
576
|
+
children,
|
|
577
|
+
onScroll,
|
|
578
|
+
...props
|
|
579
|
+
}: BottomSheetBodyProps) {
|
|
580
|
+
const surface = useContext(SheetSurfaceContext);
|
|
581
|
+
|
|
582
|
+
useEffect(() => {
|
|
583
|
+
if (!surface) return;
|
|
584
|
+
surface.hasScrollable.value = true;
|
|
585
|
+
return () => {
|
|
586
|
+
surface.hasScrollable.value = false;
|
|
587
|
+
surface.scrollOffset.value = 0;
|
|
588
|
+
};
|
|
589
|
+
}, [surface]);
|
|
590
|
+
|
|
591
|
+
/*
|
|
592
|
+
* Pulled out before the handler rather than read off `surface` inside it.
|
|
593
|
+
* A scroll handler is a worklet, and everything it closes over is copied to
|
|
594
|
+
* the UI thread — closing over the context object would drag the gesture
|
|
595
|
+
* along with it, and a gesture is not a value that can be copied.
|
|
596
|
+
*/
|
|
597
|
+
const scrollOffset = surface?.scrollOffset;
|
|
598
|
+
|
|
599
|
+
const handler = useAnimatedScrollHandler((event) => {
|
|
600
|
+
if (scrollOffset) scrollOffset.value = event.contentOffset.y;
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
const body = (
|
|
604
|
+
<Animated.ScrollView
|
|
605
|
+
onScroll={handler}
|
|
606
|
+
scrollEventThrottle={16}
|
|
607
|
+
showsVerticalScrollIndicator={false}
|
|
608
|
+
className={cn('flex-1', className)}
|
|
609
|
+
{...props}
|
|
610
|
+
>
|
|
611
|
+
{textChildren(children)}
|
|
612
|
+
</Animated.ScrollView>
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
/*
|
|
616
|
+
* The detector is what gives the scroll a handler the sheet's drag can name.
|
|
617
|
+
* Without one the two are strangers, and the first to activate takes the
|
|
618
|
+
* touch outright — which is the whole bug this part exists to solve.
|
|
619
|
+
*/
|
|
620
|
+
if (!surface) return body;
|
|
621
|
+
return <GestureDetector gesture={surface.scrollGesture}>{body}</GestureDetector>;
|
|
622
|
+
}
|
|
623
|
+
BottomSheetBody.displayName = 'BottomSheet.Body';
|
|
624
|
+
|
|
625
|
+
export interface BottomSheetFooterProps extends ViewProps {
|
|
626
|
+
className?: string;
|
|
627
|
+
children?: ReactNode;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* A row pinned below the body, for whatever the sheet is asking to be decided.
|
|
632
|
+
* Outside `Body` so it stays reachable however far the content scrolls — an
|
|
633
|
+
* action that has to be scrolled to is one people do not find.
|
|
634
|
+
*/
|
|
635
|
+
function BottomSheetFooter({
|
|
636
|
+
className,
|
|
637
|
+
children,
|
|
638
|
+
...props
|
|
639
|
+
}: BottomSheetFooterProps) {
|
|
640
|
+
return (
|
|
641
|
+
<View
|
|
642
|
+
className={cn('gap-2 border-t border-border pt-3', className)}
|
|
643
|
+
{...props}
|
|
644
|
+
>
|
|
645
|
+
{textChildren(children)}
|
|
646
|
+
</View>
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
BottomSheetFooter.displayName = 'BottomSheet.Footer';
|
|
650
|
+
|
|
393
651
|
export const BottomSheet = Object.assign(BottomSheetRoot, {
|
|
394
652
|
Trigger: BottomSheetTrigger,
|
|
395
653
|
Content: BottomSheetContent,
|
|
654
|
+
Header: BottomSheetHeader,
|
|
655
|
+
Body: BottomSheetBody,
|
|
656
|
+
Footer: BottomSheetFooter,
|
|
396
657
|
});
|