@delacour/react-native-ui 0.1.0-alpha.20260925053522
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 +113 -0
- package/package.json +125 -0
- package/src/components/accordion/AGENTS.md +193 -0
- package/src/components/accordion/accordion-content.tsx +125 -0
- package/src/components/accordion/accordion-description.tsx +17 -0
- package/src/components/accordion/accordion-indicator.tsx +104 -0
- package/src/components/accordion/accordion-item.tsx +78 -0
- package/src/components/accordion/accordion-title.tsx +18 -0
- package/src/components/accordion/accordion-trigger.tsx +148 -0
- package/src/components/accordion/accordion.context.tsx +153 -0
- package/src/components/accordion/accordion.tsx +253 -0
- package/src/components/accordion/accordion.types.ts +11 -0
- package/src/components/accordion/accordion.variants.test.ts +434 -0
- package/src/components/accordion/accordion.variants.ts +358 -0
- package/src/components/accordion/index.ts +45 -0
- package/src/components/badge/AGENTS.md +83 -0
- package/src/components/badge/badge-close-button.tsx +48 -0
- package/src/components/badge/badge-end-content.tsx +15 -0
- package/src/components/badge/badge-label.tsx +24 -0
- package/src/components/badge/badge-start-content.tsx +16 -0
- package/src/components/badge/badge.context.tsx +64 -0
- package/src/components/badge/badge.tsx +192 -0
- package/src/components/badge/badge.types.ts +10 -0
- package/src/components/badge/badge.variants.test.ts +269 -0
- package/src/components/badge/badge.variants.ts +238 -0
- package/src/components/badge/index.ts +17 -0
- package/src/components/bottom-sheet/AGENTS.md +242 -0
- package/src/components/bottom-sheet/bottom-sheet-background.tsx +35 -0
- package/src/components/bottom-sheet/bottom-sheet-close.tsx +60 -0
- package/src/components/bottom-sheet/bottom-sheet-container.tsx +246 -0
- package/src/components/bottom-sheet/bottom-sheet-content.tsx +66 -0
- package/src/components/bottom-sheet/bottom-sheet-description.tsx +29 -0
- package/src/components/bottom-sheet/bottom-sheet-footer.tsx +150 -0
- package/src/components/bottom-sheet/bottom-sheet-handle.tsx +47 -0
- package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +180 -0
- package/src/components/bottom-sheet/bottom-sheet-portal.tsx +72 -0
- package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +105 -0
- package/src/components/bottom-sheet/bottom-sheet-title.tsx +24 -0
- package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +73 -0
- package/src/components/bottom-sheet/bottom-sheet.context.tsx +135 -0
- package/src/components/bottom-sheet/bottom-sheet.tsx +125 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +293 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +220 -0
- package/src/components/bottom-sheet/index.ts +40 -0
- package/src/components/bottom-sheet/use-bottom-sheet-input.ts +125 -0
- package/src/components/button/AGENTS.md +217 -0
- package/src/components/button/button-end-content.tsx +15 -0
- package/src/components/button/button-group-separator.tsx +37 -0
- package/src/components/button/button-group-text.tsx +101 -0
- package/src/components/button/button-group.tsx +158 -0
- package/src/components/button/button-label.tsx +24 -0
- package/src/components/button/button-start-content.tsx +15 -0
- package/src/components/button/button.context.tsx +187 -0
- package/src/components/button/button.tsx +273 -0
- package/src/components/button/button.types.ts +10 -0
- package/src/components/button/button.variants.test.ts +811 -0
- package/src/components/button/button.variants.ts +480 -0
- package/src/components/button/index.ts +50 -0
- package/src/components/chart/AGENTS.md +210 -0
- package/src/components/chart/chart-area.tsx +93 -0
- package/src/components/chart/chart-bar.tsx +103 -0
- package/src/components/chart/chart-bars.tsx +75 -0
- package/src/components/chart/chart-candlestick.tsx +60 -0
- package/src/components/chart/chart-grid.tsx +33 -0
- package/src/components/chart/chart-legend.tsx +41 -0
- package/src/components/chart/chart-line.tsx +50 -0
- package/src/components/chart/chart-scatter.tsx +35 -0
- package/src/components/chart/chart-tooltip-dot.tsx +70 -0
- package/src/components/chart/chart-tooltip-x.tsx +121 -0
- package/src/components/chart/chart-tooltip-y.tsx +55 -0
- package/src/components/chart/chart-tooltip.tsx +133 -0
- package/src/components/chart/chart-x-axis.tsx +27 -0
- package/src/components/chart/chart-y-axis.tsx +21 -0
- package/src/components/chart/chart.context.tsx +127 -0
- package/src/components/chart/chart.tsx +448 -0
- package/src/components/chart/chart.types.ts +108 -0
- package/src/components/chart/chart.variants.test.ts +893 -0
- package/src/components/chart/chart.variants.ts +726 -0
- package/src/components/chart/index.ts +103 -0
- package/src/components/chart/pie-chart-center.tsx +40 -0
- package/src/components/chart/pie-chart-label.tsx +56 -0
- package/src/components/chart/pie-chart-slice.tsx +45 -0
- package/src/components/chart/pie-chart-tooltip.tsx +85 -0
- package/src/components/chart/pie-chart.context.tsx +77 -0
- package/src/components/chart/pie-chart.tsx +247 -0
- package/src/components/chart/use-chart-font.ts +25 -0
- package/src/components/chart/use-chart-palette.ts +38 -0
- package/src/components/checkbox/AGENTS.md +185 -0
- package/src/components/checkbox/checkbox-box.tsx +152 -0
- package/src/components/checkbox/checkbox-group.tsx +107 -0
- package/src/components/checkbox/checkbox-label.tsx +34 -0
- package/src/components/checkbox/checkbox.context.tsx +136 -0
- package/src/components/checkbox/checkbox.tsx +258 -0
- package/src/components/checkbox/checkbox.types.ts +14 -0
- package/src/components/checkbox/checkbox.variants.test.ts +634 -0
- package/src/components/checkbox/checkbox.variants.ts +484 -0
- package/src/components/checkbox/index.ts +42 -0
- package/src/components/field/AGENTS.md +112 -0
- package/src/components/field/field-content.tsx +24 -0
- package/src/components/field/field-description.tsx +29 -0
- package/src/components/field/field-error.tsx +61 -0
- package/src/components/field/field-group.tsx +22 -0
- package/src/components/field/field-label.tsx +47 -0
- package/src/components/field/field-legend.tsx +34 -0
- package/src/components/field/field-separator.tsx +81 -0
- package/src/components/field/field-set.tsx +22 -0
- package/src/components/field/field.context.tsx +95 -0
- package/src/components/field/field.tsx +157 -0
- package/src/components/field/field.types.ts +15 -0
- package/src/components/field/field.variants.test.ts +240 -0
- package/src/components/field/field.variants.ts +156 -0
- package/src/components/field/index.ts +22 -0
- package/src/components/icon/AGENTS.md +104 -0
- package/src/components/icon/icon.context.tsx +29 -0
- package/src/components/icon/icon.tsx +92 -0
- package/src/components/icon/icon.variants.test.ts +111 -0
- package/src/components/icon/icon.variants.ts +79 -0
- package/src/components/icon/index.ts +11 -0
- package/src/components/input/AGENTS.md +126 -0
- package/src/components/input/index.ts +27 -0
- package/src/components/input/input-group-decorator.tsx +84 -0
- package/src/components/input/input-group-prefix.tsx +16 -0
- package/src/components/input/input-group-suffix.tsx +15 -0
- package/src/components/input/input-group.tsx +140 -0
- package/src/components/input/input.context.tsx +87 -0
- package/src/components/input/input.tsx +184 -0
- package/src/components/input/input.types.ts +10 -0
- package/src/components/input/input.variants.test.ts +369 -0
- package/src/components/input/input.variants.ts +329 -0
- package/src/components/list-group/AGENTS.md +67 -0
- package/src/components/list-group/index.ts +20 -0
- package/src/components/list-group/list-group-item-content.tsx +10 -0
- package/src/components/list-group/list-group-item-description.tsx +12 -0
- package/src/components/list-group/list-group-item-prefix.tsx +30 -0
- package/src/components/list-group/list-group-item-suffix.tsx +55 -0
- package/src/components/list-group/list-group-item-title.tsx +17 -0
- package/src/components/list-group/list-group-item.tsx +87 -0
- package/src/components/list-group/list-group.context.tsx +65 -0
- package/src/components/list-group/list-group.tsx +116 -0
- package/src/components/list-group/list-group.types.ts +14 -0
- package/src/components/list-group/list-group.variants.test.ts +223 -0
- package/src/components/list-group/list-group.variants.ts +107 -0
- package/src/components/pressable/AGENTS.md +74 -0
- package/src/components/pressable/index.ts +9 -0
- package/src/components/pressable/pressable.tsx +261 -0
- package/src/components/pressable/pressable.variants.test.ts +128 -0
- package/src/components/pressable/pressable.variants.ts +80 -0
- package/src/components/provider/AGENTS.md +90 -0
- package/src/components/provider/index.ts +1 -0
- package/src/components/provider/provider.tsx +105 -0
- package/src/components/radio/AGENTS.md +245 -0
- package/src/components/radio/index.ts +35 -0
- package/src/components/radio/radio-group.tsx +126 -0
- package/src/components/radio/radio-indicator.tsx +91 -0
- package/src/components/radio/radio-label.tsx +39 -0
- package/src/components/radio/radio.context.tsx +132 -0
- package/src/components/radio/radio.tsx +215 -0
- package/src/components/radio/radio.variants.test.ts +580 -0
- package/src/components/radio/radio.variants.ts +271 -0
- package/src/components/screen/AGENTS.md +287 -0
- package/src/components/screen/index.ts +85 -0
- package/src/components/screen/screen-chat-list.tsx +485 -0
- package/src/components/screen/screen-content.tsx +69 -0
- package/src/components/screen/screen-debug.ts +43 -0
- package/src/components/screen/screen-error.tsx +61 -0
- package/src/components/screen/screen-flat-list.tsx +88 -0
- package/src/components/screen/screen-footer-background.tsx +60 -0
- package/src/components/screen/screen-footer.tsx +170 -0
- package/src/components/screen/screen-header.tsx +36 -0
- package/src/components/screen/screen-legend-list.tsx +105 -0
- package/src/components/screen/screen-list-component.tsx +23 -0
- package/src/components/screen/screen-loading.tsx +50 -0
- package/src/components/screen/screen-navbar-back-button.tsx +79 -0
- package/src/components/screen/screen-navbar-background.tsx +45 -0
- package/src/components/screen/screen-navbar-subtitle.tsx +26 -0
- package/src/components/screen/screen-navbar-title.tsx +31 -0
- package/src/components/screen/screen-navbar.tsx +154 -0
- package/src/components/screen/screen-root.tsx +38 -0
- package/src/components/screen/screen-scroll-area.tsx +116 -0
- package/src/components/screen/screen-scroll-shadow.tsx +186 -0
- package/src/components/screen/screen-section-list.tsx +81 -0
- package/src/components/screen/screen-view.tsx +59 -0
- package/src/components/screen/screen.context.tsx +167 -0
- package/src/components/screen/screen.tsx +114 -0
- package/src/components/screen/screen.types.ts +66 -0
- package/src/components/screen/screen.variants.test.ts +602 -0
- package/src/components/screen/screen.variants.ts +475 -0
- package/src/components/screen/use-screen-scroll-insets.ts +218 -0
- package/src/components/separator/AGENTS.md +30 -0
- package/src/components/separator/index.ts +8 -0
- package/src/components/separator/separator.tsx +85 -0
- package/src/components/slider/AGENTS.md +274 -0
- package/src/components/slider/index.ts +50 -0
- package/src/components/slider/slider-fill.tsx +68 -0
- package/src/components/slider/slider-output.tsx +55 -0
- package/src/components/slider/slider-thumb.tsx +193 -0
- package/src/components/slider/slider-track.tsx +233 -0
- package/src/components/slider/slider.context.tsx +118 -0
- package/src/components/slider/slider.tsx +321 -0
- package/src/components/slider/slider.types.ts +26 -0
- package/src/components/slider/slider.variants.test.ts +856 -0
- package/src/components/slider/slider.variants.ts +661 -0
- package/src/components/spinner/AGENTS.md +73 -0
- package/src/components/spinner/index.ts +17 -0
- package/src/components/spinner/spinner-arc.tsx +90 -0
- package/src/components/spinner/spinner-content.tsx +63 -0
- package/src/components/spinner/spinner.context.tsx +52 -0
- package/src/components/spinner/spinner.tsx +128 -0
- package/src/components/spinner/spinner.variants.test.ts +273 -0
- package/src/components/spinner/spinner.variants.ts +187 -0
- package/src/components/switch/AGENTS.md +213 -0
- package/src/components/switch/index.ts +42 -0
- package/src/components/switch/switch-content.tsx +111 -0
- package/src/components/switch/switch-end-content.tsx +18 -0
- package/src/components/switch/switch-start-content.tsx +20 -0
- package/src/components/switch/switch-thumb.tsx +102 -0
- package/src/components/switch/switch.context.tsx +79 -0
- package/src/components/switch/switch.tsx +423 -0
- package/src/components/switch/switch.types.ts +14 -0
- package/src/components/switch/switch.variants.test.ts +570 -0
- package/src/components/switch/switch.variants.ts +511 -0
- package/src/components/tabs/AGENTS.md +287 -0
- package/src/components/tabs/index.ts +69 -0
- package/src/components/tabs/tabs-content.tsx +65 -0
- package/src/components/tabs/tabs-indicator.tsx +97 -0
- package/src/components/tabs/tabs-label.tsx +62 -0
- package/src/components/tabs/tabs-list.tsx +139 -0
- package/src/components/tabs/tabs-pager.tsx +59 -0
- package/src/components/tabs/tabs-scroll-view.tsx +171 -0
- package/src/components/tabs/tabs-separator.tsx +73 -0
- package/src/components/tabs/tabs-trigger.tsx +210 -0
- package/src/components/tabs/tabs.context.tsx +294 -0
- package/src/components/tabs/tabs.tsx +435 -0
- package/src/components/tabs/tabs.types.ts +13 -0
- package/src/components/tabs/tabs.variants.test.ts +1020 -0
- package/src/components/tabs/tabs.variants.ts +670 -0
- package/src/components/text/AGENTS.md +95 -0
- package/src/components/text/index.ts +25 -0
- package/src/components/text/text.context.tsx +60 -0
- package/src/components/text/text.tsx +251 -0
- package/src/components/text/text.variants.test.ts +422 -0
- package/src/components/text/text.variants.ts +282 -0
- package/src/display-name.test.ts +145 -0
- package/src/docs.test.ts +98 -0
- package/src/expo/navigation-theme.tsx +58 -0
- package/src/hooks/use-controllable-state.ts +45 -0
- package/src/hooks/use-keyboard-state-sync.tsx +147 -0
- package/src/hooks/use-navigation-theme.ts +78 -0
- package/src/hooks/use-theme-color.ts +44 -0
- package/src/icons/central.ts +1 -0
- package/src/lib/cn.test.ts +136 -0
- package/src/lib/cn.ts +29 -0
- package/src/lib/color.test.ts +80 -0
- package/src/lib/color.ts +79 -0
- package/src/lib/compose-refs.test.ts +64 -0
- package/src/lib/compose-refs.ts +37 -0
- package/src/lib/keyboard-animation.test.ts +33 -0
- package/src/lib/keyboard-animation.ts +28 -0
- package/src/lib/merge-props.test.ts +82 -0
- package/src/lib/merge-props.ts +47 -0
- package/src/lib/navigation-theme.test.ts +49 -0
- package/src/lib/navigation-theme.ts +53 -0
- package/src/lib/slot.tsx +45 -0
- package/src/lib/tv.ts +20 -0
- package/src/styles/base.css +2 -0
- package/src/styles/geometry.test.ts +117 -0
- package/src/styles/index.css +3 -0
- package/src/styles/theme-tokens.test.ts +252 -0
- package/src/styles/theme.css +447 -0
- package/src/styles/tokens.css +132 -0
- package/src/styles/tokens.test.ts +181 -0
- package/src/styles/tokens.ts +86 -0
- package/src/uniwind-env.d.ts +1 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BottomSheetBackdrop,
|
|
3
|
+
type BottomSheetBackdropProps,
|
|
4
|
+
useBottomSheet as useGorhomBottomSheet,
|
|
5
|
+
} from "@gorhom/bottom-sheet";
|
|
6
|
+
import type { ReactElement, ReactNode } from "react";
|
|
7
|
+
import { StyleSheet } from "react-native";
|
|
8
|
+
import Animated, { useAnimatedStyle } from "react-native-reanimated";
|
|
9
|
+
import { withUniwind } from "uniwind";
|
|
10
|
+
import { Pressable } from "../pressable";
|
|
11
|
+
import {
|
|
12
|
+
BOTTOM_SHEET_BACKDROP_INDICES,
|
|
13
|
+
BOTTOM_SHEET_OVERLAY_OPACITY,
|
|
14
|
+
bottomSheetVariants,
|
|
15
|
+
} from "./bottom-sheet.variants";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* gorhom's backdrop is third-party and takes a `style`, never a `className`, and
|
|
19
|
+
* it is a leaf — there is no inner view to hang classes on the way
|
|
20
|
+
* `BottomSheet.Content` and `BottomSheet.Footer` do. So it is wrapped, once, at
|
|
21
|
+
* module scope. In render `withUniwind` would mint a new component type every
|
|
22
|
+
* frame and remount the scrim mid-fade. See AGENTS.md rule 7.
|
|
23
|
+
*/
|
|
24
|
+
const StyledBackdrop = withUniwind(BottomSheetBackdrop);
|
|
25
|
+
|
|
26
|
+
export type BottomSheetOverlayProps = Partial<BottomSheetBackdropProps> & {
|
|
27
|
+
className?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Opacity at full appearance.
|
|
30
|
+
*
|
|
31
|
+
* Defaults to 1 rather than gorhom's 0.5, because `--overlay` carries
|
|
32
|
+
* its own alpha — the two would otherwise multiply and land the scrim at a
|
|
33
|
+
* fifth of what the theme asked for.
|
|
34
|
+
*/
|
|
35
|
+
opacity?: number;
|
|
36
|
+
/** Snap index the scrim is fully in by. @default 0, the first snap point */
|
|
37
|
+
appearsOnIndex?: number;
|
|
38
|
+
/** Snap index the scrim is gone by. @default -1, the closed position */
|
|
39
|
+
disappearsOnIndex?: number;
|
|
40
|
+
/** Let touches reach the app behind the scrim. @default false */
|
|
41
|
+
enableTouchThrough?: boolean;
|
|
42
|
+
/** Close the sheet when the scrim is pressed. @default true */
|
|
43
|
+
isCloseOnPress?: boolean;
|
|
44
|
+
/** Announced on the pressable scrim. Ignored when it does not close. */
|
|
45
|
+
accessibilityLabel?: string;
|
|
46
|
+
onPress?: () => void;
|
|
47
|
+
children?: ReactNode;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The scrim over the app behind the sheet.
|
|
52
|
+
*
|
|
53
|
+
* **It is not rendered where it is written.** gorhom takes the backdrop as a
|
|
54
|
+
* `backdropComponent` render prop rather than as a child, so
|
|
55
|
+
* `BottomSheet.Portal` lifts this element out of its own children, publishes it
|
|
56
|
+
* on context, and `BottomSheet.Container` clones it into that slot with the
|
|
57
|
+
* sheet's animated position attached. Writing it as a sibling of the container is
|
|
58
|
+
* what makes the anatomy read like the thing it draws.
|
|
59
|
+
*
|
|
60
|
+
* Omit it and the sheet has no scrim at all — gorhom's own default for
|
|
61
|
+
* `backdropComponent` is nothing.
|
|
62
|
+
*
|
|
63
|
+
* **gorhom's own press handling is deliberately turned off, and the press area
|
|
64
|
+
* is rebuilt here.** Its backdrop puts a full-screen `Gesture.Tap()` behind the
|
|
65
|
+
* sheet, and Gesture Handler resolves a touch between competing GESTURES: a
|
|
66
|
+
* `Button` in the sheet has one of its own and wins, but a `TextInput` has none,
|
|
67
|
+
* so the backdrop takes the tap and the sheet closes instead of the field
|
|
68
|
+
* focusing. That is why gorhom ships its own `BottomSheetTextInput` — and taking
|
|
69
|
+
* that route would mean this package's `Input`, with its variants, its
|
|
70
|
+
* `Input.Group` and its `Field` cascade, could not be used in a sheet at all.
|
|
71
|
+
*
|
|
72
|
+
* So the scrim keeps `pressBehavior="none"` — no gesture anywhere — and a
|
|
73
|
+
* `Pressable` is laid over the region ABOVE the sheet, sized from the sheet's own
|
|
74
|
+
* `animatedPosition`. It never overlaps the sheet, so there is nothing for it to
|
|
75
|
+
* take, and pressing the part of the scrim anyone can actually see still closes.
|
|
76
|
+
* The colour and the fade stay gorhom's, driven off the sheet's position on the
|
|
77
|
+
* UI thread; the slot therefore carries no `opacity-*`, or a class and an
|
|
78
|
+
* animated style would be two writers of one property.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* <BottomSheet.Overlay />
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* // A scrim that does not close the sheet — a decision the caller has to make.
|
|
85
|
+
* <BottomSheet.Overlay isCloseOnPress={false} />
|
|
86
|
+
*/
|
|
87
|
+
export function BottomSheetOverlay({
|
|
88
|
+
accessibilityLabel = "Close",
|
|
89
|
+
animatedIndex,
|
|
90
|
+
animatedPosition,
|
|
91
|
+
appearsOnIndex = BOTTOM_SHEET_BACKDROP_INDICES.appearsOnIndex,
|
|
92
|
+
className,
|
|
93
|
+
disappearsOnIndex = BOTTOM_SHEET_BACKDROP_INDICES.disappearsOnIndex,
|
|
94
|
+
isCloseOnPress = true,
|
|
95
|
+
opacity = BOTTOM_SHEET_OVERLAY_OPACITY,
|
|
96
|
+
onPress,
|
|
97
|
+
...props
|
|
98
|
+
}: BottomSheetOverlayProps): ReactElement {
|
|
99
|
+
if (animatedIndex === undefined || animatedPosition === undefined) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
"BottomSheet.Overlay must be written inside a <BottomSheet.Portal>, beside its <BottomSheet.Container>."
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<>
|
|
107
|
+
<StyledBackdrop
|
|
108
|
+
animatedIndex={animatedIndex}
|
|
109
|
+
animatedPosition={animatedPosition}
|
|
110
|
+
appearsOnIndex={appearsOnIndex}
|
|
111
|
+
className={bottomSheetVariants().overlay({ className })}
|
|
112
|
+
disappearsOnIndex={disappearsOnIndex}
|
|
113
|
+
opacity={opacity}
|
|
114
|
+
pressBehavior="none"
|
|
115
|
+
{...props}
|
|
116
|
+
/>
|
|
117
|
+
{isCloseOnPress ? (
|
|
118
|
+
<BottomSheetOverlayPressArea
|
|
119
|
+
accessibilityLabel={accessibilityLabel}
|
|
120
|
+
animatedPosition={animatedPosition}
|
|
121
|
+
onPress={onPress}
|
|
122
|
+
/>
|
|
123
|
+
) : null}
|
|
124
|
+
</>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
BottomSheetOverlay.displayName = "DelacourUI.BottomSheet.Overlay";
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The pressable band between the top of the screen and the top of the sheet.
|
|
131
|
+
*
|
|
132
|
+
* Its height is the sheet's own `animatedPosition`, so it grows and shrinks with
|
|
133
|
+
* the sheet on the UI thread and stops exactly where the sheet begins — which is
|
|
134
|
+
* the whole point. The `Pressable` is a child rather than the animated view
|
|
135
|
+
* itself because `Pressable` takes no `style`: its own `Animated.View` already
|
|
136
|
+
* owns one, and a second would fight it for prop ownership every frame.
|
|
137
|
+
*
|
|
138
|
+
* It reads gorhom's `useBottomSheet()` rather than this package's. The backdrop
|
|
139
|
+
* is rendered by gorhom's own container, OUTSIDE the children
|
|
140
|
+
* `BottomSheet.Container` re-publishes its context around, so the sheet's own
|
|
141
|
+
* handle is the one thing in scope here — and closing through it lands on
|
|
142
|
+
* `onDismiss`, which is where `onOpenChange` already is.
|
|
143
|
+
*/
|
|
144
|
+
function BottomSheetOverlayPressArea({
|
|
145
|
+
accessibilityLabel,
|
|
146
|
+
animatedPosition,
|
|
147
|
+
onPress,
|
|
148
|
+
}: {
|
|
149
|
+
accessibilityLabel: string;
|
|
150
|
+
animatedPosition: NonNullable<BottomSheetBackdropProps["animatedPosition"]>;
|
|
151
|
+
onPress?: () => void;
|
|
152
|
+
}): ReactElement {
|
|
153
|
+
const { close } = useGorhomBottomSheet();
|
|
154
|
+
|
|
155
|
+
const style = useAnimatedStyle(() => ({ height: Math.max(0, animatedPosition.value) }));
|
|
156
|
+
|
|
157
|
+
const handlePress = () => {
|
|
158
|
+
onPress?.();
|
|
159
|
+
close();
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<Animated.View style={[styles.pressArea, style]}>
|
|
164
|
+
<Pressable
|
|
165
|
+
accessibilityLabel={accessibilityLabel}
|
|
166
|
+
accessibilityRole="button"
|
|
167
|
+
className="flex-1"
|
|
168
|
+
feedback="none"
|
|
169
|
+
onPress={handlePress}
|
|
170
|
+
/>
|
|
171
|
+
</Animated.View>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
BottomSheetOverlayPressArea.displayName = "DelacourUI.BottomSheet.Overlay.PressArea";
|
|
175
|
+
|
|
176
|
+
// A style rather than a class: the height beside it is an animated value, and the
|
|
177
|
+
// two have to arrive on the same node.
|
|
178
|
+
const styles = StyleSheet.create({
|
|
179
|
+
pressArea: { left: 0, position: "absolute", right: 0, top: 0 },
|
|
180
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Children, isValidElement, type ReactElement, type ReactNode, useMemo } from "react";
|
|
2
|
+
import { BottomSheetPortalProvider } from "./bottom-sheet.context";
|
|
3
|
+
import { BottomSheetOverlay } from "./bottom-sheet-overlay";
|
|
4
|
+
|
|
5
|
+
export type BottomSheetPortalProps = {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Everything that draws above the app once the sheet is open.
|
|
11
|
+
*
|
|
12
|
+
* It renders no view of its own, and it does not portal anything either —
|
|
13
|
+
* `BottomSheetModal` already hosts itself in the `BottomSheetModalProvider` that
|
|
14
|
+
* `DelacourProvider` mounts innermost. What this part does is one thing:
|
|
15
|
+
* **it lifts the `BottomSheet.Overlay` out of its own children** and publishes it
|
|
16
|
+
* on context, so `BottomSheet.Container` can clone it into gorhom's
|
|
17
|
+
* `backdropComponent` slot.
|
|
18
|
+
*
|
|
19
|
+
* That indirection buys the anatomy. The scrim and the sheet are siblings on
|
|
20
|
+
* screen, so they read best as siblings in the tree — but gorhom takes the scrim
|
|
21
|
+
* as a render prop, and a component cannot hand a prop to a sibling. This is the
|
|
22
|
+
* seam where the two shapes are reconciled, and it is why the part exists at all
|
|
23
|
+
* rather than the container simply taking a `backdrop` prop.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* <BottomSheet.Portal>
|
|
27
|
+
* <BottomSheet.Overlay />
|
|
28
|
+
* <BottomSheet.Container>{…}</BottomSheet.Container>
|
|
29
|
+
* </BottomSheet.Portal>
|
|
30
|
+
*/
|
|
31
|
+
export function BottomSheetPortal({ children }: BottomSheetPortalProps): ReactElement {
|
|
32
|
+
const { content, overlay } = useMemo(() => hoistOverlay(children), [children]);
|
|
33
|
+
const value = useMemo(() => ({ overlay }), [overlay]);
|
|
34
|
+
|
|
35
|
+
return <BottomSheetPortalProvider value={value}>{content}</BottomSheetPortalProvider>;
|
|
36
|
+
}
|
|
37
|
+
BottomSheetPortal.displayName = "DelacourUI.BottomSheet.Portal";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Splits a portal's children into the scrim and everything else.
|
|
41
|
+
*
|
|
42
|
+
* Lives here rather than with the parts: it is the portal that lifts its own
|
|
43
|
+
* children, and importing it from `bottom-sheet-overlay.tsx` would close a cycle.
|
|
44
|
+
* The same rule that keeps `withIndicator` in `radio.tsx`. See AGENTS.md.
|
|
45
|
+
*
|
|
46
|
+
* A second overlay is dropped rather than rendered — there is one
|
|
47
|
+
* `backdropComponent` slot to clone into, and silently drawing two scrims at
|
|
48
|
+
* different opacities is worse than the warning.
|
|
49
|
+
*/
|
|
50
|
+
function hoistOverlay(children: ReactNode): { content: ReactNode; overlay: ReactElement | null } {
|
|
51
|
+
const items = Children.toArray(children);
|
|
52
|
+
const content: ReactNode[] = [];
|
|
53
|
+
let overlay: ReactElement | null = null;
|
|
54
|
+
let seen = 0;
|
|
55
|
+
|
|
56
|
+
for (const child of items) {
|
|
57
|
+
if (isValidElement(child) && child.type === BottomSheetOverlay) {
|
|
58
|
+
seen += 1;
|
|
59
|
+
overlay ??= child;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
content.push(child);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (process.env.NODE_ENV !== "production" && seen > 1) {
|
|
66
|
+
console.warn(
|
|
67
|
+
`BottomSheet.Portal: ${seen} <BottomSheet.Overlay> elements were written; only the first is drawn. A sheet has one backdrop.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { content, overlay };
|
|
72
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { BottomSheetScrollView as GorhomBottomSheetScrollView } from "@gorhom/bottom-sheet";
|
|
2
|
+
import type { ComponentProps, ReactElement, ReactNode } from "react";
|
|
3
|
+
import { View } from "react-native";
|
|
4
|
+
import { useAnimatedStyle } from "react-native-reanimated";
|
|
5
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
6
|
+
import { withUniwind } from "uniwind";
|
|
7
|
+
import { cn } from "../../lib/cn";
|
|
8
|
+
import { useBottomSheetContainerContext } from "./bottom-sheet.context";
|
|
9
|
+
import { bottomSheetVariants, resolveSheetScrollEndPadding } from "./bottom-sheet.variants";
|
|
10
|
+
|
|
11
|
+
type GorhomScrollViewProps = ComponentProps<typeof GorhomBottomSheetScrollView>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The one class prop this file uses, restated so the props survive the wrapper.
|
|
15
|
+
*
|
|
16
|
+
* `withUniwind`'s return type maps over a component's props, and over a
|
|
17
|
+
* scrollable's animated ones it collapses to a single entry — `data`,
|
|
18
|
+
* `contentContainerStyle` and the rest would all check against nothing. Writing
|
|
19
|
+
* the signature out keeps them, the way `Screen.LegendList` already has to.
|
|
20
|
+
*
|
|
21
|
+
* `contentContainerClassName` is deliberately absent even though the wrapper adds
|
|
22
|
+
* one at runtime: passing it would put a second writer on `contentContainerStyle`
|
|
23
|
+
* and cost the safe-area band — see the component's doc comment.
|
|
24
|
+
*/
|
|
25
|
+
type StyledScrollViewComponent = (props: GorhomScrollViewProps & { className?: string }) => ReactElement | null;
|
|
26
|
+
|
|
27
|
+
// Third-party, so `className` needs the wrapper — and it has to be built at
|
|
28
|
+
// module scope or every render mints a new component type and remounts the list.
|
|
29
|
+
const StyledScrollView = withUniwind(GorhomBottomSheetScrollView) as unknown as StyledScrollViewComponent;
|
|
30
|
+
|
|
31
|
+
export type BottomSheetScrollViewProps = Omit<
|
|
32
|
+
GorhomScrollViewProps,
|
|
33
|
+
"children" | "contentContainerStyle" | "enableFooterMarginAdjustment"
|
|
34
|
+
> & {
|
|
35
|
+
className?: string;
|
|
36
|
+
/** Classes for the padded box the children sit in. */
|
|
37
|
+
contentContainerClassName?: string;
|
|
38
|
+
children?: ReactNode;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A scrolling body for a sheet taller than its snap point.
|
|
43
|
+
*
|
|
44
|
+
* Use this rather than a plain `ScrollView`: gorhom's scrollable and the sheet's
|
|
45
|
+
* pan negotiate with each other, so dragging a list that is already at its top
|
|
46
|
+
* moves the sheet instead of fighting it. A React Native `ScrollView` in here has
|
|
47
|
+
* no such arrangement, and the sheet stops responding to a drag over the list.
|
|
48
|
+
*
|
|
49
|
+
* **It needs a height to scroll within.** With `enableDynamicSizing` left on, the
|
|
50
|
+
* sheet grows to whatever the content measures and there is nothing to scroll —
|
|
51
|
+
* pass `enableDynamicSizing={false}` and explicit `snapPoints` on the
|
|
52
|
+
* `BottomSheet.Container` around it.
|
|
53
|
+
*
|
|
54
|
+
* The classes go on an inner `View`, not on gorhom's own content container, and
|
|
55
|
+
* that is not tidiness: uniwind compiles a `contentContainerClassName` into an
|
|
56
|
+
* *array* alongside any `contentContainerStyle`, and the two then fight over the
|
|
57
|
+
* one style this component has to own — the safe-area band. One writer for it.
|
|
58
|
+
*
|
|
59
|
+
* **A pinned footer shortens the scroll view; it is not reserved inside it.**
|
|
60
|
+
* The footer draws over the sheet, so a scroll view that ran the sheet's full
|
|
61
|
+
* height would carry its indicator and its overscroll bounce on underneath the
|
|
62
|
+
* footer, the bottom of the track hidden behind the buttons. So the frame gives
|
|
63
|
+
* up the footer's measured height as a `marginBottom` and ends at the footer's
|
|
64
|
+
* hairline, and the content keeps only the gap
|
|
65
|
+
* ({@link resolveSheetScrollEndPadding}). The margin is an animated style off
|
|
66
|
+
* the footer's shared value, never gorhom's `enableFooterMarginAdjustment`,
|
|
67
|
+
* which routes that height through React state and would commit a render on
|
|
68
|
+
* every frame of the keyboard animation — see `BottomSheet.Content`.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* <BottomSheet.Container enableDynamicSizing={false} snapPoints={["60%", "90%"]}>
|
|
72
|
+
* <BottomSheet.ScrollView>{rows}</BottomSheet.ScrollView>
|
|
73
|
+
* </BottomSheet.Container>
|
|
74
|
+
*/
|
|
75
|
+
export function BottomSheetScrollView({
|
|
76
|
+
className,
|
|
77
|
+
contentContainerClassName,
|
|
78
|
+
children,
|
|
79
|
+
style,
|
|
80
|
+
...props
|
|
81
|
+
}: BottomSheetScrollViewProps): ReactElement {
|
|
82
|
+
const container = useBottomSheetContainerContext();
|
|
83
|
+
const { bottom } = useSafeAreaInsets();
|
|
84
|
+
const hasStickyFooter = container?.hasStickyFooter ?? false;
|
|
85
|
+
const footerHeight = container?.footerHeight;
|
|
86
|
+
|
|
87
|
+
// The footer's height, band included, as the frame's own margin — so the
|
|
88
|
+
// indicator's track ends where the footer begins and follows it as the
|
|
89
|
+
// keyboard takes the band back.
|
|
90
|
+
const aboveFooter = useAnimatedStyle(() => ({
|
|
91
|
+
marginBottom: hasStickyFooter && footerHeight !== undefined ? footerHeight.value : 0,
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<StyledScrollView
|
|
96
|
+
className={cn(className)}
|
|
97
|
+
contentContainerStyle={{ paddingBottom: resolveSheetScrollEndPadding({ bottom, hasStickyFooter }) }}
|
|
98
|
+
{...props}
|
|
99
|
+
style={[aboveFooter, style]}
|
|
100
|
+
>
|
|
101
|
+
<View className={bottomSheetVariants().scrollContent({ className: contentContainerClassName })}>{children}</View>
|
|
102
|
+
</StyledScrollView>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
BottomSheetScrollView.displayName = "DelacourUI.BottomSheet.ScrollView";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Text, type TextPresetProps } from "../text";
|
|
3
|
+
import { bottomSheetVariants } from "./bottom-sheet.variants";
|
|
4
|
+
|
|
5
|
+
export type BottomSheetTitleProps = TextPresetProps;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The sheet's heading.
|
|
9
|
+
*
|
|
10
|
+
* *Is* a `Text.Header` — it renders the preset and adds layout, never a size or a
|
|
11
|
+
* weight of its own, the same trade `Field.Label` makes with `Text.Label`. A
|
|
12
|
+
* `text-lg font-semibold` here would be a second definition of that preset which
|
|
13
|
+
* could drift from it, and the tests assert the slot carries neither.
|
|
14
|
+
*
|
|
15
|
+
* The preset already announces itself as a heading, so a screen reader reaches
|
|
16
|
+
* the sheet's purpose first with nothing said at the call site.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <BottomSheet.Title>Keep yourself safe</BottomSheet.Title>
|
|
20
|
+
*/
|
|
21
|
+
export function BottomSheetTitle({ className, ...props }: BottomSheetTitleProps): ReactElement {
|
|
22
|
+
return <Text.Header className={bottomSheetVariants().title({ className })} {...props} />;
|
|
23
|
+
}
|
|
24
|
+
BottomSheetTitle.displayName = "DelacourUI.BottomSheet.Title";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type ReactElement, useCallback } from "react";
|
|
2
|
+
import { Slot } from "../../lib/slot";
|
|
3
|
+
import { Pressable, type PressableProps } from "../pressable";
|
|
4
|
+
import { useBottomSheetPart } from "./bottom-sheet.context";
|
|
5
|
+
|
|
6
|
+
export type BottomSheetTriggerProps = PressableProps;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The control that opens the sheet.
|
|
10
|
+
*
|
|
11
|
+
* On its own it is a `Pressable`, so `feedback`, `haptic` and the rest are
|
|
12
|
+
* inherited rather than restated. It opens through the same state a controlled
|
|
13
|
+
* `isOpen` drives, so a sheet can be opened from here and from elsewhere on the
|
|
14
|
+
* same screen without the two disagreeing, and a caller's own `onPress` still
|
|
15
|
+
* runs after the open.
|
|
16
|
+
*
|
|
17
|
+
* **`asChild` donates the press rather than wrapping the child**, which is where
|
|
18
|
+
* it differs from `Pressable`'s own. `Pressable asChild` keeps its gesture and
|
|
19
|
+
* renders the child inside it — correct for a child that is not itself a
|
|
20
|
+
* control, and wrong here, because the thing anyone wraps in a trigger is a
|
|
21
|
+
* `Button`. Two tap gestures in an ancestor/descendant pair are not
|
|
22
|
+
* simultaneous, so Gesture Handler gives the press to the DESCENDANT: the
|
|
23
|
+
* button's own detector wins, fires the `onPress` it does not have, and the
|
|
24
|
+
* sheet never opens. Nothing announces this — the press simply does nothing.
|
|
25
|
+
*
|
|
26
|
+
* So the trigger hands its `onPress` down as a prop instead, through `Slot`,
|
|
27
|
+
* which chains it ahead of any the child already had. The child keeps its own
|
|
28
|
+
* gesture, its own feedback and its own haptic, and there is still no extra view
|
|
29
|
+
* in the tree.
|
|
30
|
+
*
|
|
31
|
+
* The corollary: **the child has to be something that handles `onPress`.** A
|
|
32
|
+
* `View` or a `Text` takes the prop and ignores it. Wrap a `Button`, a
|
|
33
|
+
* `ListGroup.Item`, or anything else built on `Pressable`.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* <BottomSheet.Trigger asChild>
|
|
37
|
+
* <Button variant="secondary">Open</Button>
|
|
38
|
+
* </BottomSheet.Trigger>
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // No asChild: the trigger is its own pressable.
|
|
42
|
+
* <BottomSheet.Trigger className="p-4">
|
|
43
|
+
* <Text>Open</Text>
|
|
44
|
+
* </BottomSheet.Trigger>
|
|
45
|
+
*/
|
|
46
|
+
export function BottomSheetTrigger({
|
|
47
|
+
asChild = false,
|
|
48
|
+
children,
|
|
49
|
+
onPress,
|
|
50
|
+
...props
|
|
51
|
+
}: BottomSheetTriggerProps): ReactElement {
|
|
52
|
+
const { open } = useBottomSheetPart("BottomSheet.Trigger");
|
|
53
|
+
|
|
54
|
+
const handlePress = useCallback(() => {
|
|
55
|
+
open();
|
|
56
|
+
onPress?.();
|
|
57
|
+
}, [onPress, open]);
|
|
58
|
+
|
|
59
|
+
if (asChild) {
|
|
60
|
+
return (
|
|
61
|
+
<Slot onPress={handlePress} {...props}>
|
|
62
|
+
{children}
|
|
63
|
+
</Slot>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Pressable onPress={handlePress} {...props}>
|
|
69
|
+
{children}
|
|
70
|
+
</Pressable>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
BottomSheetTrigger.displayName = "DelacourUI.BottomSheet.Trigger";
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { BottomSheetModal } from "@gorhom/bottom-sheet";
|
|
2
|
+
import { type ComponentRef, createContext, type ReactElement, type ReactNode, type RefObject, use } from "react";
|
|
3
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The imperative handle a `BottomSheetModal` exposes.
|
|
7
|
+
*
|
|
8
|
+
* Derived from the component rather than imported by name: gorhom re-exports the
|
|
9
|
+
* modal's *value* from its package root but not the `BottomSheetModalMethods`
|
|
10
|
+
* type behind its ref, and reaching into `lib/typescript/` for one would pin this
|
|
11
|
+
* package to a path that is not part of that package's public surface.
|
|
12
|
+
*/
|
|
13
|
+
export type BottomSheetHandle = ComponentRef<typeof BottomSheetModal>;
|
|
14
|
+
|
|
15
|
+
export type BottomSheetContextValue = {
|
|
16
|
+
isOpen: boolean;
|
|
17
|
+
/** The controllable-state setter. Reports upward whether controlled or not. */
|
|
18
|
+
setOpen: (isOpen: boolean) => void;
|
|
19
|
+
open: () => void;
|
|
20
|
+
close: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* The modal's handle, for the imperative calls that have no declarative form —
|
|
23
|
+
* `snapToIndex`, `expand`, `collapse`. Presenting and dismissing are driven by
|
|
24
|
+
* `isOpen` instead, so a caller never has to keep two sources in step.
|
|
25
|
+
*/
|
|
26
|
+
sheetRef: RefObject<BottomSheetHandle | null>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const BottomSheetContext = createContext<BottomSheetContextValue | null>(null);
|
|
30
|
+
|
|
31
|
+
export function BottomSheetProvider({
|
|
32
|
+
value,
|
|
33
|
+
children,
|
|
34
|
+
}: {
|
|
35
|
+
value: BottomSheetContextValue;
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
}): ReactElement {
|
|
38
|
+
return <BottomSheetContext value={value}>{children}</BottomSheetContext>;
|
|
39
|
+
}
|
|
40
|
+
BottomSheetProvider.displayName = "DelacourUI.BottomSheet.Provider";
|
|
41
|
+
|
|
42
|
+
/** The sheet's state, or `null` outside one. For a component that works either way. */
|
|
43
|
+
export function useBottomSheetContext(): BottomSheetContextValue | null {
|
|
44
|
+
return use(BottomSheetContext);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The enclosing sheet's open state and controls.
|
|
49
|
+
*
|
|
50
|
+
* Reach for this to drive a sheet from a control of your own — a row that closes
|
|
51
|
+
* it, a button that snaps it somewhere — without threading props down.
|
|
52
|
+
*/
|
|
53
|
+
export function useBottomSheet(): BottomSheetContextValue {
|
|
54
|
+
const context = use(BottomSheetContext);
|
|
55
|
+
if (context === null) throw new Error("useBottomSheet must be called inside a <BottomSheet>.");
|
|
56
|
+
return context;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The same read, with a message naming the part that is misplaced. Internal. */
|
|
60
|
+
export function useBottomSheetPart(component: string): BottomSheetContextValue {
|
|
61
|
+
const context = use(BottomSheetContext);
|
|
62
|
+
if (context === null) throw new Error(`${component} must be rendered inside a <BottomSheet>.`);
|
|
63
|
+
return context;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type BottomSheetPortalContextValue = {
|
|
67
|
+
/**
|
|
68
|
+
* The `BottomSheet.Overlay` element the portal lifted out of its own children,
|
|
69
|
+
* or `null` when none was written.
|
|
70
|
+
*
|
|
71
|
+
* gorhom takes the scrim as a `backdropComponent` render prop rather than as a
|
|
72
|
+
* child, so the element cannot be rendered where it was declared. The portal
|
|
73
|
+
* publishes it here and `BottomSheet.Container` clones it into that slot.
|
|
74
|
+
*/
|
|
75
|
+
overlay: ReactElement | null;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const BottomSheetPortalContext = createContext<BottomSheetPortalContextValue | null>(null);
|
|
79
|
+
|
|
80
|
+
export function BottomSheetPortalProvider({
|
|
81
|
+
value,
|
|
82
|
+
children,
|
|
83
|
+
}: {
|
|
84
|
+
value: BottomSheetPortalContextValue;
|
|
85
|
+
children: ReactNode;
|
|
86
|
+
}): ReactElement {
|
|
87
|
+
return <BottomSheetPortalContext value={value}>{children}</BottomSheetPortalContext>;
|
|
88
|
+
}
|
|
89
|
+
BottomSheetPortalProvider.displayName = "DelacourUI.BottomSheet.Portal.Provider";
|
|
90
|
+
|
|
91
|
+
/** What the enclosing portal lifted out, or `null` for a container written without one. */
|
|
92
|
+
export function useBottomSheetPortalContext(): BottomSheetPortalContextValue | null {
|
|
93
|
+
return use(BottomSheetPortalContext);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type BottomSheetContainerContextValue = {
|
|
97
|
+
/**
|
|
98
|
+
* Whether a pinned footer is drawing at the bottom of this sheet.
|
|
99
|
+
*
|
|
100
|
+
* `BottomSheet.Content` reads it to decide whether the safe-area band is its
|
|
101
|
+
* to apply. Exactly one part takes it: both applying leaves a gap the height
|
|
102
|
+
* of the home indicator between the content and the footer.
|
|
103
|
+
*/
|
|
104
|
+
hasStickyFooter: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* What a pinned footer covers, measured, in points.
|
|
107
|
+
*
|
|
108
|
+
* A shared value rather than state: it changes on every frame of the keyboard
|
|
109
|
+
* animation as the footer gives up its safe-area band, and the body reserves it
|
|
110
|
+
* with an animated spacer. Routing that through React would commit a dozen
|
|
111
|
+
* renders per keystroke's worth of keyboard travel. `Screen` publishes its own
|
|
112
|
+
* chrome the same way.
|
|
113
|
+
*
|
|
114
|
+
* Zero when there is no pinned footer.
|
|
115
|
+
*/
|
|
116
|
+
footerHeight: SharedValue<number>;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const BottomSheetContainerContext = createContext<BottomSheetContainerContextValue | null>(null);
|
|
120
|
+
|
|
121
|
+
export function BottomSheetContainerProvider({
|
|
122
|
+
value,
|
|
123
|
+
children,
|
|
124
|
+
}: {
|
|
125
|
+
value: BottomSheetContainerContextValue;
|
|
126
|
+
children: ReactNode;
|
|
127
|
+
}): ReactElement {
|
|
128
|
+
return <BottomSheetContainerContext value={value}>{children}</BottomSheetContainerContext>;
|
|
129
|
+
}
|
|
130
|
+
BottomSheetContainerProvider.displayName = "DelacourUI.BottomSheet.Container.Provider";
|
|
131
|
+
|
|
132
|
+
/** The enclosing container's layout facts, or `null` outside one. */
|
|
133
|
+
export function useBottomSheetContainerContext(): BottomSheetContainerContextValue | null {
|
|
134
|
+
return use(BottomSheetContainerContext);
|
|
135
|
+
}
|