@delacour/react-native-ui 0.1.0-alpha.3
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 +192 -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 +435 -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 +232 -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 +245 -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 +148 -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 +95 -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 +276 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +199 -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 +100 -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 +33 -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 +78 -0
- package/src/components/field/field.tsx +144 -0
- package/src/components/field/field.types.ts +15 -0
- package/src/components/field/field.variants.test.ts +216 -0
- package/src/components/field/field.variants.ts +136 -0
- package/src/components/field/index.ts +21 -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 +64 -0
- package/src/components/pressable/index.ts +9 -0
- package/src/components/pressable/pressable.tsx +256 -0
- package/src/components/pressable/pressable.variants.test.ts +99 -0
- package/src/components/pressable/pressable.variants.ts +63 -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 +247 -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 +581 -0
- package/src/components/radio/radio.variants.ts +272 -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 +48 -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 +161 -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 +804 -0
- package/src/components/slider/slider.variants.ts +607 -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 +286 -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 +672 -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,245 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BottomSheetBackdropProps,
|
|
3
|
+
BottomSheetModal,
|
|
4
|
+
type BottomSheetModalProps,
|
|
5
|
+
type BottomSheetBackgroundProps as GorhomBottomSheetBackgroundProps,
|
|
6
|
+
type BottomSheetFooterProps as GorhomBottomSheetFooterProps,
|
|
7
|
+
type BottomSheetHandleProps as GorhomBottomSheetHandleProps,
|
|
8
|
+
} from "@gorhom/bottom-sheet";
|
|
9
|
+
import {
|
|
10
|
+
Children,
|
|
11
|
+
cloneElement,
|
|
12
|
+
isValidElement,
|
|
13
|
+
type ReactElement,
|
|
14
|
+
type ReactNode,
|
|
15
|
+
useCallback,
|
|
16
|
+
useEffect,
|
|
17
|
+
useMemo,
|
|
18
|
+
useRef,
|
|
19
|
+
} from "react";
|
|
20
|
+
import { useSharedValue } from "react-native-reanimated";
|
|
21
|
+
import { useKeyboardAnimationGuard } from "../../hooks/use-keyboard-state-sync";
|
|
22
|
+
import {
|
|
23
|
+
BottomSheetContainerProvider,
|
|
24
|
+
BottomSheetProvider,
|
|
25
|
+
useBottomSheetPart,
|
|
26
|
+
useBottomSheetPortalContext,
|
|
27
|
+
} from "./bottom-sheet.context";
|
|
28
|
+
import {
|
|
29
|
+
BOTTOM_SHEET_KEYBOARD_DEFAULTS,
|
|
30
|
+
type BottomSheetFooterFlag,
|
|
31
|
+
type BottomSheetFooterPlacement,
|
|
32
|
+
resolveFooterPlacement,
|
|
33
|
+
} from "./bottom-sheet.variants";
|
|
34
|
+
import { BottomSheetBackground } from "./bottom-sheet-background";
|
|
35
|
+
import { BottomSheetFooter, type BottomSheetFooterProps } from "./bottom-sheet-footer";
|
|
36
|
+
import { BottomSheetHandle } from "./bottom-sheet-handle";
|
|
37
|
+
|
|
38
|
+
export type BottomSheetContainerProps = Omit<
|
|
39
|
+
BottomSheetModalProps,
|
|
40
|
+
"backdropComponent" | "backgroundComponent" | "children" | "footerComponent" | "handleComponent" | "onDismiss" | "ref"
|
|
41
|
+
> & {
|
|
42
|
+
children?: ReactNode;
|
|
43
|
+
/** Classes for the sheet's surface — the box behind the handle and the content. */
|
|
44
|
+
backgroundClassName?: string;
|
|
45
|
+
/** Classes for the row the grabber sits in. */
|
|
46
|
+
handleClassName?: string;
|
|
47
|
+
/** Classes for the grabber itself. */
|
|
48
|
+
handleIndicatorClassName?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The sheet itself: the surface that slides up, and everything inside it.
|
|
53
|
+
*
|
|
54
|
+
* Every gorhom prop passes through, so snap points, dynamic sizing, the pan
|
|
55
|
+
* gestures and the animation configs are all reachable. Four are not, because
|
|
56
|
+
* this component supplies them: the backdrop and the footer are cloned from the
|
|
57
|
+
* parts a caller wrote, and the background and the handle are rendered as views
|
|
58
|
+
* of this package's own so they can take a `className` rather than a style.
|
|
59
|
+
*
|
|
60
|
+
* **It takes no `className`.** gorhom's modal wears an animated style that the
|
|
61
|
+
* sheet's own position writes to every frame, so there is nothing here for a
|
|
62
|
+
* class to reach that `backgroundClassName` does not already reach better. The
|
|
63
|
+
* same trade `Screen.Footer` makes — that file takes a `style` and never a
|
|
64
|
+
* `className`, and so wraps nothing in `withUniwind`.
|
|
65
|
+
*
|
|
66
|
+
* **Presenting is driven by state, never by the ref.** `isOpen` on the root is
|
|
67
|
+
* the one source, so a swipe-down, a backdrop press, `BottomSheet.Close` and a
|
|
68
|
+
* programmatic close all arrive at the same `onOpenChange`. gorhom's own
|
|
69
|
+
* `onDismiss` is therefore not forwarded: two callbacks for one event is two
|
|
70
|
+
* things that can disagree. `useBottomSheet().sheetRef` is still there for the
|
|
71
|
+
* imperative calls with no declarative form — `snapToIndex`, `expand`.
|
|
72
|
+
*
|
|
73
|
+
* **`accessible` defaults to `false`, and that is not a small thing.** gorhom
|
|
74
|
+
* marks the sheet's content container `accessible` with the label "Bottom
|
|
75
|
+
* Sheet" and the `adjustable` role. On iOS an `accessible` container collapses
|
|
76
|
+
* its whole subtree into ONE element, so every field, button and line of copy in
|
|
77
|
+
* the sheet becomes unreachable to VoiceOver — a form in a sheet cannot be
|
|
78
|
+
* filled in at all. This is `Field`'s rule one component along: the row is
|
|
79
|
+
* `accessible={false}` so the control stays the element a screen reader sees.
|
|
80
|
+
* Pass `accessible` to opt back into gorhom's behaviour for a sheet whose body
|
|
81
|
+
* really is one adjustable thing.
|
|
82
|
+
*
|
|
83
|
+
* Three defaults differ from gorhom's. `enablePanDownToClose` is on, because a
|
|
84
|
+
* modal sheet has no collapsed resting state to pan down to. And the keyboard
|
|
85
|
+
* trio comes from `BOTTOM_SHEET_KEYBOARD_DEFAULTS` — see that const for why
|
|
86
|
+
* Android must not be left on `adjustPan`. Both are ordinary props, so either
|
|
87
|
+
* can be turned back off.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* <BottomSheet.Container>
|
|
91
|
+
* <BottomSheet.Content>{…}</BottomSheet.Content>
|
|
92
|
+
* </BottomSheet.Container>
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // A scrollable sheet needs a fixed height to scroll within.
|
|
96
|
+
* <BottomSheet.Container enableDynamicSizing={false} snapPoints={["50%", "90%"]}>
|
|
97
|
+
* <BottomSheet.ScrollView>{rows}</BottomSheet.ScrollView>
|
|
98
|
+
* <BottomSheet.Footer sticky>{actions}</BottomSheet.Footer>
|
|
99
|
+
* </BottomSheet.Container>
|
|
100
|
+
*/
|
|
101
|
+
export function BottomSheetContainer({
|
|
102
|
+
accessible = false,
|
|
103
|
+
backgroundClassName,
|
|
104
|
+
children,
|
|
105
|
+
enablePanDownToClose = true,
|
|
106
|
+
handleClassName,
|
|
107
|
+
handleIndicatorClassName,
|
|
108
|
+
...props
|
|
109
|
+
}: BottomSheetContainerProps): ReactElement {
|
|
110
|
+
const sheet = useBottomSheetPart("BottomSheet.Container");
|
|
111
|
+
const { isOpen, setOpen, sheetRef } = sheet;
|
|
112
|
+
const portal = useBottomSheetPortalContext();
|
|
113
|
+
// A sheet that opens while `KeyboardProvider`'s shared values are pinned open
|
|
114
|
+
// by a keyboard that vanished without a `will` event would size itself for a
|
|
115
|
+
// keyboard that is not there. `Screen.Footer` runs the same repair on mount.
|
|
116
|
+
useKeyboardAnimationGuard();
|
|
117
|
+
|
|
118
|
+
// Whether this sheet has ever been presented. A JS ref rather than state:
|
|
119
|
+
// nothing renders differently for it, and it has to be readable inside the
|
|
120
|
+
// effect that sets it.
|
|
121
|
+
const hasPresentedRef = useRef(false);
|
|
122
|
+
|
|
123
|
+
const { content, footer, placement } = useMemo(() => hoistFooter(children), [children]);
|
|
124
|
+
const hasStickyFooter = placement === "sticky";
|
|
125
|
+
const overlay = portal?.overlay ?? null;
|
|
126
|
+
|
|
127
|
+
// Written by the pinned footer's own onLayout, read by the body's spacer. It
|
|
128
|
+
// lives here because the two are siblings in gorhom's tree and neither can see
|
|
129
|
+
// the other — the footer is rendered outside the children this file wraps.
|
|
130
|
+
const footerHeight = useSharedValue(0);
|
|
131
|
+
|
|
132
|
+
const containerValue = useMemo(() => ({ footerHeight, hasStickyFooter }), [footerHeight, hasStickyFooter]);
|
|
133
|
+
|
|
134
|
+
const backdropComponent = useMemo(
|
|
135
|
+
() =>
|
|
136
|
+
overlay === null ? undefined : (backdropProps: BottomSheetBackdropProps) => cloneElement(overlay, backdropProps),
|
|
137
|
+
[overlay]
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const footerComponent = useMemo(
|
|
141
|
+
() =>
|
|
142
|
+
footer === null
|
|
143
|
+
? undefined
|
|
144
|
+
: (footerProps: GorhomBottomSheetFooterProps) => cloneElement(footer, { ...footerProps, footerHeight }),
|
|
145
|
+
[footer, footerHeight]
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const backgroundComponent = useCallback(
|
|
149
|
+
(backgroundProps: GorhomBottomSheetBackgroundProps) => (
|
|
150
|
+
<BottomSheetBackground {...backgroundProps} className={backgroundClassName} />
|
|
151
|
+
),
|
|
152
|
+
[backgroundClassName]
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const handleComponent = useCallback(
|
|
156
|
+
(handleProps: GorhomBottomSheetHandleProps) => (
|
|
157
|
+
<BottomSheetHandle {...handleProps} className={handleClassName} indicatorClassName={handleIndicatorClassName} />
|
|
158
|
+
),
|
|
159
|
+
[handleClassName, handleIndicatorClassName]
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const handleDismiss = useCallback(() => {
|
|
163
|
+
hasPresentedRef.current = false;
|
|
164
|
+
setOpen(false);
|
|
165
|
+
}, [setOpen]);
|
|
166
|
+
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (isOpen) {
|
|
169
|
+
hasPresentedRef.current = true;
|
|
170
|
+
sheetRef.current?.present();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Dismissing a modal that was never presented is NOT a no-op, and this is
|
|
175
|
+
// the sharpest edge in the component. gorhom's modal starts at status
|
|
176
|
+
// `INITIAL`; `dismiss()` reads that as a live sheet being closed and moves
|
|
177
|
+
// it to `DISMISSING`, where it stays. The next `present()` then mounts the
|
|
178
|
+
// portal and renders the sheet — and it never animates in, never lays out
|
|
179
|
+
// its content, and never fires `onChange`. Nothing is logged, nothing
|
|
180
|
+
// throws, and the screen simply does not change.
|
|
181
|
+
if (!hasPresentedRef.current) return;
|
|
182
|
+
hasPresentedRef.current = false;
|
|
183
|
+
sheetRef.current?.dismiss();
|
|
184
|
+
}, [isOpen, sheetRef]);
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<BottomSheetModal
|
|
188
|
+
accessible={accessible}
|
|
189
|
+
backdropComponent={backdropComponent}
|
|
190
|
+
backgroundComponent={backgroundComponent}
|
|
191
|
+
enablePanDownToClose={enablePanDownToClose}
|
|
192
|
+
footerComponent={footerComponent}
|
|
193
|
+
handleComponent={handleComponent}
|
|
194
|
+
onDismiss={handleDismiss}
|
|
195
|
+
ref={sheetRef}
|
|
196
|
+
{...BOTTOM_SHEET_KEYBOARD_DEFAULTS}
|
|
197
|
+
{...props}
|
|
198
|
+
>
|
|
199
|
+
<BottomSheetProvider value={sheet}>
|
|
200
|
+
<BottomSheetContainerProvider value={containerValue}>{content}</BottomSheetContainerProvider>
|
|
201
|
+
</BottomSheetProvider>
|
|
202
|
+
</BottomSheetModal>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
BottomSheetContainer.displayName = "DelacourUI.BottomSheet.Container";
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Splits a container's children into the body and a footer to be pinned.
|
|
209
|
+
*
|
|
210
|
+
* gorhom takes a footer as a `footerComponent` render prop rather than as a
|
|
211
|
+
* child, so a sticky one has to leave the tree it was written in; an inline one
|
|
212
|
+
* stays put and is simply part of the body. {@link resolveFooterPlacement} is the
|
|
213
|
+
* decision and is pure, so `bun test` reaches the whole matrix — this walk only
|
|
214
|
+
* turns children into the flags it reads.
|
|
215
|
+
*
|
|
216
|
+
* Lives here rather than with the parts: it is the container that lifts its own
|
|
217
|
+
* children, and importing it from `bottom-sheet-footer.tsx` would close a cycle.
|
|
218
|
+
* See AGENTS.md.
|
|
219
|
+
*/
|
|
220
|
+
function hoistFooter(children: ReactNode): {
|
|
221
|
+
content: ReactNode;
|
|
222
|
+
footer: ReactElement<BottomSheetFooterProps> | null;
|
|
223
|
+
placement: BottomSheetFooterPlacement;
|
|
224
|
+
} {
|
|
225
|
+
const items = Children.toArray(children);
|
|
226
|
+
const flags: BottomSheetFooterFlag[] = [];
|
|
227
|
+
let footer: ReactElement<BottomSheetFooterProps> | null = null;
|
|
228
|
+
|
|
229
|
+
for (const child of items) {
|
|
230
|
+
if (!isValidElement(child) || child.type !== BottomSheetFooter) {
|
|
231
|
+
flags.push({ isFooter: false, isSticky: false });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const element = child as ReactElement<BottomSheetFooterProps>;
|
|
236
|
+
const isSticky = element.props.sticky === true;
|
|
237
|
+
flags.push({ isFooter: true, isSticky });
|
|
238
|
+
if (isSticky) footer ??= element;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const placement = resolveFooterPlacement(flags);
|
|
242
|
+
if (placement !== "sticky" || footer === null) return { content: items, footer: null, placement };
|
|
243
|
+
|
|
244
|
+
return { content: items.filter((child) => child !== footer), footer, placement };
|
|
245
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { BottomSheetView } from "@gorhom/bottom-sheet";
|
|
2
|
+
import type { ReactElement } from "react";
|
|
3
|
+
import { View, type ViewProps } from "react-native";
|
|
4
|
+
import Animated, { useAnimatedStyle } from "react-native-reanimated";
|
|
5
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
6
|
+
import { useBottomSheetContainerContext } from "./bottom-sheet.context";
|
|
7
|
+
import { BOTTOM_SHEET_FOOTER_GAP, bottomSheetVariants, resolveSheetBottomInset } from "./bottom-sheet.variants";
|
|
8
|
+
|
|
9
|
+
export type BottomSheetContentProps = ViewProps & {
|
|
10
|
+
className?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The sheet's body — what a caller writes the title, the copy and the controls
|
|
15
|
+
* into.
|
|
16
|
+
*
|
|
17
|
+
* Two boxes, and neither is a `withUniwind` wrapper. gorhom's `BottomSheetView`
|
|
18
|
+
* is the outer one: it measures itself into the sheet's layout state, which is
|
|
19
|
+
* what makes `enableDynamicSizing` size the sheet to whatever is inside. The
|
|
20
|
+
* inner `View` is an ordinary React Native one and carries every class, so this
|
|
21
|
+
* file wraps nothing — the same move `Screen.Footer` makes with
|
|
22
|
+
* `KeyboardStickyView`.
|
|
23
|
+
*
|
|
24
|
+
* **A pinned footer is reserved by a spacer, not by gorhom's
|
|
25
|
+
* `enableFooterMarginAdjustment`.** That prop turns the footer's measured height
|
|
26
|
+
* into content-container padding through React state — a commit on every frame
|
|
27
|
+
* of the keyboard animation, because the footer changes height as it gives up
|
|
28
|
+
* its safe-area band. A spacer whose height is an animated style off the same
|
|
29
|
+
* shared value costs nothing on the JS thread, and `Screen` already reserves its
|
|
30
|
+
* own chrome exactly this way. The spacer sits OUTSIDE the classed box so the
|
|
31
|
+
* content's own `gap` does not land in front of it and quietly double the
|
|
32
|
+
* {@link BOTTOM_SHEET_FOOTER_GAP} the footer is held off by.
|
|
33
|
+
*
|
|
34
|
+
* **The safe-area band goes on the OUTER box, and only when nothing else is
|
|
35
|
+
* paying for it** — a pinned footer's own box carries it, so
|
|
36
|
+
* {@link resolveSheetBottomInset} withholds it here. The band comes from
|
|
37
|
+
* `useSafeAreaInsets()` and never from `pb-safe`, which compiles to
|
|
38
|
+
* `env(safe-area-inset-bottom)` and resolves to zero on React Native.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* <BottomSheet.Content>
|
|
42
|
+
* <BottomSheet.Title>Delete this file?</BottomSheet.Title>
|
|
43
|
+
* <BottomSheet.Description>This cannot be undone.</BottomSheet.Description>
|
|
44
|
+
* </BottomSheet.Content>
|
|
45
|
+
*/
|
|
46
|
+
export function BottomSheetContent({ className, children, style, ...props }: BottomSheetContentProps): ReactElement {
|
|
47
|
+
const container = useBottomSheetContainerContext();
|
|
48
|
+
const { bottom } = useSafeAreaInsets();
|
|
49
|
+
const hasStickyFooter = container?.hasStickyFooter ?? false;
|
|
50
|
+
const footerHeight = container?.footerHeight;
|
|
51
|
+
const paddingBottom = resolveSheetBottomInset({ bottom, hasStickyFooter });
|
|
52
|
+
|
|
53
|
+
const reserve = useAnimatedStyle(() => ({
|
|
54
|
+
height: footerHeight === undefined ? 0 : footerHeight.value + BOTTOM_SHEET_FOOTER_GAP,
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<BottomSheetView style={{ paddingBottom }}>
|
|
59
|
+
<View className={bottomSheetVariants().content({ className })} style={style} {...props}>
|
|
60
|
+
{children}
|
|
61
|
+
</View>
|
|
62
|
+
{hasStickyFooter ? <Animated.View style={reserve} /> : null}
|
|
63
|
+
</BottomSheetView>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
BottomSheetContent.displayName = "DelacourUI.BottomSheet.Content";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { cn } from "../../lib/cn";
|
|
3
|
+
import { Text, type TextPresetProps } from "../text";
|
|
4
|
+
|
|
5
|
+
export type BottomSheetDescriptionProps = TextPresetProps;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Supporting copy under the sheet's title.
|
|
9
|
+
*
|
|
10
|
+
* *Is* a `Text.Paragraph`, defaulting to the muted token — the title is what the
|
|
11
|
+
* sheet is about and this is what it means, so the two need to read as a
|
|
12
|
+
* hierarchy rather than as two equal lines. `color` is an ordinary prop, so a
|
|
13
|
+
* description that needs the page colour asks for it.
|
|
14
|
+
*
|
|
15
|
+
* It has no slot in `bottomSheetVariants`, and that is deliberate: it carries no
|
|
16
|
+
* layout of its own, and `tv` emits `undefined` for an empty class string, so a
|
|
17
|
+
* slot here would be one nothing could assert against.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* <BottomSheet.Description>Update to the latest version.</BottomSheet.Description>
|
|
21
|
+
*/
|
|
22
|
+
export function BottomSheetDescription({
|
|
23
|
+
className,
|
|
24
|
+
color = "muted",
|
|
25
|
+
...props
|
|
26
|
+
}: BottomSheetDescriptionProps): ReactElement {
|
|
27
|
+
return <Text.Paragraph className={cn(className)} color={color} {...props} />;
|
|
28
|
+
}
|
|
29
|
+
BottomSheetDescription.displayName = "DelacourUI.BottomSheet.Description";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { BottomSheetFooter as GorhomBottomSheetFooter } from "@gorhom/bottom-sheet";
|
|
2
|
+
import { type ComponentProps, type ReactElement, type ReactNode, useCallback, useEffect } from "react";
|
|
3
|
+
import { type LayoutChangeEvent, View, type ViewProps } from "react-native";
|
|
4
|
+
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
|
|
5
|
+
import Animated, { type SharedValue, useAnimatedStyle } from "react-native-reanimated";
|
|
6
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
7
|
+
import { BOTTOM_SHEET_FOOTER_PADDING, bottomSheetVariants } from "./bottom-sheet.variants";
|
|
8
|
+
|
|
9
|
+
type GorhomFooterProps = ComponentProps<typeof GorhomBottomSheetFooter>;
|
|
10
|
+
|
|
11
|
+
export type BottomSheetFooterProps = Omit<ViewProps, "children"> & {
|
|
12
|
+
/**
|
|
13
|
+
* Pin the footer to the bottom of the sheet, above the keyboard, instead of
|
|
14
|
+
* leaving it in the content's flow.
|
|
15
|
+
*
|
|
16
|
+
* Off by default, matching `Screen.Footer` — a footer holding a submit button
|
|
17
|
+
* under a short sheet wants to sit where it was written, and a sheet only
|
|
18
|
+
* needs the pinned treatment once its body can scroll under one.
|
|
19
|
+
*
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
sticky?: boolean;
|
|
23
|
+
className?: string;
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* The sheet's own footer position. Injected by `BottomSheet.Container` when it
|
|
27
|
+
* hoists a sticky footer; never written at a call site.
|
|
28
|
+
*/
|
|
29
|
+
animatedFooterPosition?: GorhomFooterProps["animatedFooterPosition"];
|
|
30
|
+
/**
|
|
31
|
+
* Where this footer publishes its measured height. Injected alongside the
|
|
32
|
+
* position, and read by whatever the sheet's body is so it can reserve exactly
|
|
33
|
+
* what the footer covers. Never written at a call site.
|
|
34
|
+
*/
|
|
35
|
+
footerHeight?: SharedValue<number>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A row of controls at the bottom of the sheet — pinned, or in the flow.
|
|
40
|
+
*
|
|
41
|
+
* **A sticky footer is not rendered where it is written.** gorhom takes it as a
|
|
42
|
+
* `footerComponent` render prop rather than as a child, so
|
|
43
|
+
* `BottomSheet.Container` lifts this element out of its children and clones it
|
|
44
|
+
* into that slot with the sheet's animated footer position attached. An inline
|
|
45
|
+
* footer needs none of that and stays exactly where it was put.
|
|
46
|
+
*
|
|
47
|
+
* The two branches deliberately look different. An inline footer is in the flow
|
|
48
|
+
* and inherits the sheet's surface; a pinned one draws OVER the content, so it
|
|
49
|
+
* brings a background and a top hairline of its own — without them the content
|
|
50
|
+
* scrolls straight through it and its buttons are legible only where they happen
|
|
51
|
+
* to overlap blank space. That is `Screen.Footer`'s rule about its backing, one
|
|
52
|
+
* component along.
|
|
53
|
+
*
|
|
54
|
+
* **A pinned footer is TALLER by the safe-area band; it is not moved up by it.**
|
|
55
|
+
* gorhom's own `bottomInset` translates the whole footer up instead, which
|
|
56
|
+
* leaves a band the height of the home indicator between the footer and the
|
|
57
|
+
* bottom of the sheet — and the content scrolls through it in plain view. So
|
|
58
|
+
* `bottomInset` stays 0 and the band becomes the footer's own `paddingBottom`,
|
|
59
|
+
* which puts its surface against the bottom of the screen where it belongs.
|
|
60
|
+
*
|
|
61
|
+
* The keyboard then needs that band back, and **only that band**. Its own
|
|
62
|
+
* padding stays, so the controls never end up flush against the keyboard, and
|
|
63
|
+
* the footer genuinely gets shorter rather than sliding down — which is what
|
|
64
|
+
* lets the body's reserve shrink with it instead of over-reserving by the height
|
|
65
|
+
* of a home indicator that is no longer there.
|
|
66
|
+
*
|
|
67
|
+
* It publishes its measured height into the sheet, and `BottomSheet.Content` and
|
|
68
|
+
* `BottomSheet.ScrollView` reserve exactly that plus a gap. The height is a
|
|
69
|
+
* shared value read by an animated spacer, never content-container padding —
|
|
70
|
+
* `Screen` reserves its own chrome the same way and for the same reason.
|
|
71
|
+
*
|
|
72
|
+
* **Do not put a `KeyboardStickyView` in here.** gorhom's `animatedFooterPosition`
|
|
73
|
+
* already carries the footer clear of the keyboard; this translate only gives up
|
|
74
|
+
* the safe-area band on top of that. A third mechanism moving the same view
|
|
75
|
+
* would fight both for prop ownership every frame.
|
|
76
|
+
*
|
|
77
|
+
* Written with `sticky` but rendered outside a container, it falls back to the
|
|
78
|
+
* inline branch rather than throwing: there is no footer position to ride, and a
|
|
79
|
+
* row of buttons in the flow is the useful failure.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* <BottomSheet.Footer>
|
|
83
|
+
* <Button onPress={save}>Save</Button>
|
|
84
|
+
* </BottomSheet.Footer>
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* <BottomSheet.Footer sticky>
|
|
88
|
+
* <Button onPress={submit}>Submit</Button>
|
|
89
|
+
* </BottomSheet.Footer>
|
|
90
|
+
*/
|
|
91
|
+
export function BottomSheetFooter({
|
|
92
|
+
animatedFooterPosition,
|
|
93
|
+
className,
|
|
94
|
+
children,
|
|
95
|
+
footerHeight,
|
|
96
|
+
onLayout,
|
|
97
|
+
sticky = false,
|
|
98
|
+
style,
|
|
99
|
+
...props
|
|
100
|
+
}: BottomSheetFooterProps): ReactElement {
|
|
101
|
+
const { bottom } = useSafeAreaInsets();
|
|
102
|
+
const { progress } = useReanimatedKeyboardAnimation();
|
|
103
|
+
const slots = bottomSheetVariants();
|
|
104
|
+
|
|
105
|
+
// The footer keeps its own padding and gives up only the safe-area band, which
|
|
106
|
+
// collapses as the keyboard arrives. `progress` runs 0 → 1 over the keyboard's
|
|
107
|
+
// own animation, so the two travel together.
|
|
108
|
+
const padding = useAnimatedStyle(() => ({
|
|
109
|
+
paddingBottom: BOTTOM_SHEET_FOOTER_PADDING + bottom * (1 - progress.value),
|
|
110
|
+
paddingTop: BOTTOM_SHEET_FOOTER_PADDING,
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
const handleLayout = useCallback(
|
|
114
|
+
(event: LayoutChangeEvent) => {
|
|
115
|
+
if (footerHeight) footerHeight.value = event.nativeEvent.layout.height;
|
|
116
|
+
onLayout?.(event);
|
|
117
|
+
},
|
|
118
|
+
[footerHeight, onLayout]
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
return () => {
|
|
123
|
+
if (footerHeight) footerHeight.value = 0;
|
|
124
|
+
};
|
|
125
|
+
}, [footerHeight]);
|
|
126
|
+
|
|
127
|
+
if (!sticky || animatedFooterPosition === undefined) {
|
|
128
|
+
return (
|
|
129
|
+
<View className={slots.footer({ className })} style={style} {...props}>
|
|
130
|
+
{children}
|
|
131
|
+
</View>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<GorhomBottomSheetFooter animatedFooterPosition={animatedFooterPosition} bottomInset={0}>
|
|
137
|
+
<Animated.View
|
|
138
|
+
className={slots.stickyFooter({ className })}
|
|
139
|
+
onLayout={handleLayout}
|
|
140
|
+
style={[padding, style]}
|
|
141
|
+
{...props}
|
|
142
|
+
>
|
|
143
|
+
{children}
|
|
144
|
+
</Animated.View>
|
|
145
|
+
</GorhomBottomSheetFooter>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
BottomSheetFooter.displayName = "DelacourUI.BottomSheet.Footer";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { BottomSheetHandleProps as GorhomBottomSheetHandleProps } from "@gorhom/bottom-sheet";
|
|
2
|
+
import type { ReactElement } from "react";
|
|
3
|
+
import { View, type ViewProps } from "react-native";
|
|
4
|
+
import { bottomSheetVariants } from "./bottom-sheet.variants";
|
|
5
|
+
|
|
6
|
+
export type BottomSheetHandleProps = Partial<GorhomBottomSheetHandleProps> &
|
|
7
|
+
Omit<ViewProps, "children"> & {
|
|
8
|
+
className?: string;
|
|
9
|
+
/** Style for the pill itself. What gorhom's `handleIndicatorStyle` becomes. */
|
|
10
|
+
indicatorStyle?: ViewProps["style"];
|
|
11
|
+
indicatorClassName?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The grabber at the top of the sheet.
|
|
16
|
+
*
|
|
17
|
+
* Supplied to gorhom as `handleComponent` for the same reason the background is:
|
|
18
|
+
* this package paints with classNames, and gorhom's `handleStyle` /
|
|
19
|
+
* `handleIndicatorStyle` cannot carry one. Both still arrive — gorhom passes
|
|
20
|
+
* them down as `style` and `indicatorStyle` — so a caller who has a style object
|
|
21
|
+
* rather than a class is not locked out.
|
|
22
|
+
*
|
|
23
|
+
* The pill is drawn but never announced: gorhom's own handle container wraps this
|
|
24
|
+
* in the `Animated.View` that owns the pan and carries the accessibility surface,
|
|
25
|
+
* so a second announced element here would put a nameless control in front of
|
|
26
|
+
* every sheet.
|
|
27
|
+
*
|
|
28
|
+
* The two animated variables are destructured off rather than spread — see
|
|
29
|
+
* `BottomSheetBackground`.
|
|
30
|
+
*/
|
|
31
|
+
export function BottomSheetHandle({
|
|
32
|
+
animatedIndex: _animatedIndex,
|
|
33
|
+
animatedPosition: _animatedPosition,
|
|
34
|
+
className,
|
|
35
|
+
indicatorClassName,
|
|
36
|
+
indicatorStyle,
|
|
37
|
+
...props
|
|
38
|
+
}: BottomSheetHandleProps): ReactElement {
|
|
39
|
+
const slots = bottomSheetVariants();
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<View className={slots.handle({ className })} {...props}>
|
|
43
|
+
<View className={slots.handleIndicator({ className: indicatorClassName })} style={indicatorStyle} />
|
|
44
|
+
</View>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
BottomSheetHandle.displayName = "DelacourUI.BottomSheet.Handle";
|