@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.
Files changed (273) hide show
  1. package/README.md +113 -0
  2. package/package.json +125 -0
  3. package/src/components/accordion/AGENTS.md +192 -0
  4. package/src/components/accordion/accordion-content.tsx +125 -0
  5. package/src/components/accordion/accordion-description.tsx +17 -0
  6. package/src/components/accordion/accordion-indicator.tsx +104 -0
  7. package/src/components/accordion/accordion-item.tsx +78 -0
  8. package/src/components/accordion/accordion-title.tsx +18 -0
  9. package/src/components/accordion/accordion-trigger.tsx +148 -0
  10. package/src/components/accordion/accordion.context.tsx +153 -0
  11. package/src/components/accordion/accordion.tsx +253 -0
  12. package/src/components/accordion/accordion.types.ts +11 -0
  13. package/src/components/accordion/accordion.variants.test.ts +435 -0
  14. package/src/components/accordion/accordion.variants.ts +358 -0
  15. package/src/components/accordion/index.ts +45 -0
  16. package/src/components/badge/AGENTS.md +83 -0
  17. package/src/components/badge/badge-close-button.tsx +48 -0
  18. package/src/components/badge/badge-end-content.tsx +15 -0
  19. package/src/components/badge/badge-label.tsx +24 -0
  20. package/src/components/badge/badge-start-content.tsx +16 -0
  21. package/src/components/badge/badge.context.tsx +64 -0
  22. package/src/components/badge/badge.tsx +192 -0
  23. package/src/components/badge/badge.types.ts +10 -0
  24. package/src/components/badge/badge.variants.test.ts +269 -0
  25. package/src/components/badge/badge.variants.ts +238 -0
  26. package/src/components/badge/index.ts +17 -0
  27. package/src/components/bottom-sheet/AGENTS.md +232 -0
  28. package/src/components/bottom-sheet/bottom-sheet-background.tsx +35 -0
  29. package/src/components/bottom-sheet/bottom-sheet-close.tsx +60 -0
  30. package/src/components/bottom-sheet/bottom-sheet-container.tsx +245 -0
  31. package/src/components/bottom-sheet/bottom-sheet-content.tsx +66 -0
  32. package/src/components/bottom-sheet/bottom-sheet-description.tsx +29 -0
  33. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +148 -0
  34. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +47 -0
  35. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +180 -0
  36. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +72 -0
  37. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +95 -0
  38. package/src/components/bottom-sheet/bottom-sheet-title.tsx +24 -0
  39. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +73 -0
  40. package/src/components/bottom-sheet/bottom-sheet.context.tsx +135 -0
  41. package/src/components/bottom-sheet/bottom-sheet.tsx +125 -0
  42. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +276 -0
  43. package/src/components/bottom-sheet/bottom-sheet.variants.ts +199 -0
  44. package/src/components/bottom-sheet/index.ts +40 -0
  45. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +125 -0
  46. package/src/components/button/AGENTS.md +217 -0
  47. package/src/components/button/button-end-content.tsx +15 -0
  48. package/src/components/button/button-group-separator.tsx +37 -0
  49. package/src/components/button/button-group-text.tsx +101 -0
  50. package/src/components/button/button-group.tsx +158 -0
  51. package/src/components/button/button-label.tsx +24 -0
  52. package/src/components/button/button-start-content.tsx +15 -0
  53. package/src/components/button/button.context.tsx +187 -0
  54. package/src/components/button/button.tsx +273 -0
  55. package/src/components/button/button.types.ts +10 -0
  56. package/src/components/button/button.variants.test.ts +811 -0
  57. package/src/components/button/button.variants.ts +480 -0
  58. package/src/components/button/index.ts +50 -0
  59. package/src/components/chart/AGENTS.md +210 -0
  60. package/src/components/chart/chart-area.tsx +93 -0
  61. package/src/components/chart/chart-bar.tsx +103 -0
  62. package/src/components/chart/chart-bars.tsx +75 -0
  63. package/src/components/chart/chart-candlestick.tsx +60 -0
  64. package/src/components/chart/chart-grid.tsx +33 -0
  65. package/src/components/chart/chart-legend.tsx +41 -0
  66. package/src/components/chart/chart-line.tsx +50 -0
  67. package/src/components/chart/chart-scatter.tsx +35 -0
  68. package/src/components/chart/chart-tooltip-dot.tsx +70 -0
  69. package/src/components/chart/chart-tooltip-x.tsx +121 -0
  70. package/src/components/chart/chart-tooltip-y.tsx +55 -0
  71. package/src/components/chart/chart-tooltip.tsx +133 -0
  72. package/src/components/chart/chart-x-axis.tsx +27 -0
  73. package/src/components/chart/chart-y-axis.tsx +21 -0
  74. package/src/components/chart/chart.context.tsx +127 -0
  75. package/src/components/chart/chart.tsx +448 -0
  76. package/src/components/chart/chart.types.ts +108 -0
  77. package/src/components/chart/chart.variants.test.ts +893 -0
  78. package/src/components/chart/chart.variants.ts +726 -0
  79. package/src/components/chart/index.ts +103 -0
  80. package/src/components/chart/pie-chart-center.tsx +40 -0
  81. package/src/components/chart/pie-chart-label.tsx +56 -0
  82. package/src/components/chart/pie-chart-slice.tsx +45 -0
  83. package/src/components/chart/pie-chart-tooltip.tsx +85 -0
  84. package/src/components/chart/pie-chart.context.tsx +77 -0
  85. package/src/components/chart/pie-chart.tsx +247 -0
  86. package/src/components/chart/use-chart-font.ts +25 -0
  87. package/src/components/chart/use-chart-palette.ts +38 -0
  88. package/src/components/checkbox/AGENTS.md +185 -0
  89. package/src/components/checkbox/checkbox-box.tsx +152 -0
  90. package/src/components/checkbox/checkbox-group.tsx +107 -0
  91. package/src/components/checkbox/checkbox-label.tsx +34 -0
  92. package/src/components/checkbox/checkbox.context.tsx +136 -0
  93. package/src/components/checkbox/checkbox.tsx +258 -0
  94. package/src/components/checkbox/checkbox.types.ts +14 -0
  95. package/src/components/checkbox/checkbox.variants.test.ts +634 -0
  96. package/src/components/checkbox/checkbox.variants.ts +484 -0
  97. package/src/components/checkbox/index.ts +42 -0
  98. package/src/components/field/AGENTS.md +100 -0
  99. package/src/components/field/field-content.tsx +24 -0
  100. package/src/components/field/field-description.tsx +29 -0
  101. package/src/components/field/field-error.tsx +61 -0
  102. package/src/components/field/field-group.tsx +22 -0
  103. package/src/components/field/field-label.tsx +33 -0
  104. package/src/components/field/field-legend.tsx +34 -0
  105. package/src/components/field/field-separator.tsx +81 -0
  106. package/src/components/field/field-set.tsx +22 -0
  107. package/src/components/field/field.context.tsx +78 -0
  108. package/src/components/field/field.tsx +144 -0
  109. package/src/components/field/field.types.ts +15 -0
  110. package/src/components/field/field.variants.test.ts +216 -0
  111. package/src/components/field/field.variants.ts +136 -0
  112. package/src/components/field/index.ts +21 -0
  113. package/src/components/icon/AGENTS.md +104 -0
  114. package/src/components/icon/icon.context.tsx +29 -0
  115. package/src/components/icon/icon.tsx +92 -0
  116. package/src/components/icon/icon.variants.test.ts +111 -0
  117. package/src/components/icon/icon.variants.ts +79 -0
  118. package/src/components/icon/index.ts +11 -0
  119. package/src/components/input/AGENTS.md +126 -0
  120. package/src/components/input/index.ts +27 -0
  121. package/src/components/input/input-group-decorator.tsx +84 -0
  122. package/src/components/input/input-group-prefix.tsx +16 -0
  123. package/src/components/input/input-group-suffix.tsx +15 -0
  124. package/src/components/input/input-group.tsx +140 -0
  125. package/src/components/input/input.context.tsx +87 -0
  126. package/src/components/input/input.tsx +184 -0
  127. package/src/components/input/input.types.ts +10 -0
  128. package/src/components/input/input.variants.test.ts +369 -0
  129. package/src/components/input/input.variants.ts +329 -0
  130. package/src/components/list-group/AGENTS.md +67 -0
  131. package/src/components/list-group/index.ts +20 -0
  132. package/src/components/list-group/list-group-item-content.tsx +10 -0
  133. package/src/components/list-group/list-group-item-description.tsx +12 -0
  134. package/src/components/list-group/list-group-item-prefix.tsx +30 -0
  135. package/src/components/list-group/list-group-item-suffix.tsx +55 -0
  136. package/src/components/list-group/list-group-item-title.tsx +17 -0
  137. package/src/components/list-group/list-group-item.tsx +87 -0
  138. package/src/components/list-group/list-group.context.tsx +65 -0
  139. package/src/components/list-group/list-group.tsx +116 -0
  140. package/src/components/list-group/list-group.types.ts +14 -0
  141. package/src/components/list-group/list-group.variants.test.ts +223 -0
  142. package/src/components/list-group/list-group.variants.ts +107 -0
  143. package/src/components/pressable/AGENTS.md +64 -0
  144. package/src/components/pressable/index.ts +9 -0
  145. package/src/components/pressable/pressable.tsx +256 -0
  146. package/src/components/pressable/pressable.variants.test.ts +99 -0
  147. package/src/components/pressable/pressable.variants.ts +63 -0
  148. package/src/components/provider/AGENTS.md +90 -0
  149. package/src/components/provider/index.ts +1 -0
  150. package/src/components/provider/provider.tsx +105 -0
  151. package/src/components/radio/AGENTS.md +247 -0
  152. package/src/components/radio/index.ts +35 -0
  153. package/src/components/radio/radio-group.tsx +126 -0
  154. package/src/components/radio/radio-indicator.tsx +91 -0
  155. package/src/components/radio/radio-label.tsx +39 -0
  156. package/src/components/radio/radio.context.tsx +132 -0
  157. package/src/components/radio/radio.tsx +215 -0
  158. package/src/components/radio/radio.variants.test.ts +581 -0
  159. package/src/components/radio/radio.variants.ts +272 -0
  160. package/src/components/screen/AGENTS.md +287 -0
  161. package/src/components/screen/index.ts +85 -0
  162. package/src/components/screen/screen-chat-list.tsx +485 -0
  163. package/src/components/screen/screen-content.tsx +69 -0
  164. package/src/components/screen/screen-debug.ts +43 -0
  165. package/src/components/screen/screen-error.tsx +61 -0
  166. package/src/components/screen/screen-flat-list.tsx +88 -0
  167. package/src/components/screen/screen-footer-background.tsx +60 -0
  168. package/src/components/screen/screen-footer.tsx +170 -0
  169. package/src/components/screen/screen-header.tsx +36 -0
  170. package/src/components/screen/screen-legend-list.tsx +105 -0
  171. package/src/components/screen/screen-list-component.tsx +23 -0
  172. package/src/components/screen/screen-loading.tsx +50 -0
  173. package/src/components/screen/screen-navbar-back-button.tsx +79 -0
  174. package/src/components/screen/screen-navbar-background.tsx +45 -0
  175. package/src/components/screen/screen-navbar-subtitle.tsx +26 -0
  176. package/src/components/screen/screen-navbar-title.tsx +31 -0
  177. package/src/components/screen/screen-navbar.tsx +154 -0
  178. package/src/components/screen/screen-root.tsx +38 -0
  179. package/src/components/screen/screen-scroll-area.tsx +116 -0
  180. package/src/components/screen/screen-scroll-shadow.tsx +186 -0
  181. package/src/components/screen/screen-section-list.tsx +81 -0
  182. package/src/components/screen/screen-view.tsx +59 -0
  183. package/src/components/screen/screen.context.tsx +167 -0
  184. package/src/components/screen/screen.tsx +114 -0
  185. package/src/components/screen/screen.types.ts +66 -0
  186. package/src/components/screen/screen.variants.test.ts +602 -0
  187. package/src/components/screen/screen.variants.ts +475 -0
  188. package/src/components/screen/use-screen-scroll-insets.ts +218 -0
  189. package/src/components/separator/AGENTS.md +30 -0
  190. package/src/components/separator/index.ts +8 -0
  191. package/src/components/separator/separator.tsx +85 -0
  192. package/src/components/slider/AGENTS.md +274 -0
  193. package/src/components/slider/index.ts +48 -0
  194. package/src/components/slider/slider-fill.tsx +68 -0
  195. package/src/components/slider/slider-output.tsx +55 -0
  196. package/src/components/slider/slider-thumb.tsx +161 -0
  197. package/src/components/slider/slider-track.tsx +233 -0
  198. package/src/components/slider/slider.context.tsx +118 -0
  199. package/src/components/slider/slider.tsx +321 -0
  200. package/src/components/slider/slider.types.ts +26 -0
  201. package/src/components/slider/slider.variants.test.ts +804 -0
  202. package/src/components/slider/slider.variants.ts +607 -0
  203. package/src/components/spinner/AGENTS.md +73 -0
  204. package/src/components/spinner/index.ts +17 -0
  205. package/src/components/spinner/spinner-arc.tsx +90 -0
  206. package/src/components/spinner/spinner-content.tsx +63 -0
  207. package/src/components/spinner/spinner.context.tsx +52 -0
  208. package/src/components/spinner/spinner.tsx +128 -0
  209. package/src/components/spinner/spinner.variants.test.ts +273 -0
  210. package/src/components/spinner/spinner.variants.ts +187 -0
  211. package/src/components/switch/AGENTS.md +213 -0
  212. package/src/components/switch/index.ts +42 -0
  213. package/src/components/switch/switch-content.tsx +111 -0
  214. package/src/components/switch/switch-end-content.tsx +18 -0
  215. package/src/components/switch/switch-start-content.tsx +20 -0
  216. package/src/components/switch/switch-thumb.tsx +102 -0
  217. package/src/components/switch/switch.context.tsx +79 -0
  218. package/src/components/switch/switch.tsx +423 -0
  219. package/src/components/switch/switch.types.ts +14 -0
  220. package/src/components/switch/switch.variants.test.ts +570 -0
  221. package/src/components/switch/switch.variants.ts +511 -0
  222. package/src/components/tabs/AGENTS.md +286 -0
  223. package/src/components/tabs/index.ts +69 -0
  224. package/src/components/tabs/tabs-content.tsx +65 -0
  225. package/src/components/tabs/tabs-indicator.tsx +97 -0
  226. package/src/components/tabs/tabs-label.tsx +62 -0
  227. package/src/components/tabs/tabs-list.tsx +139 -0
  228. package/src/components/tabs/tabs-pager.tsx +59 -0
  229. package/src/components/tabs/tabs-scroll-view.tsx +171 -0
  230. package/src/components/tabs/tabs-separator.tsx +73 -0
  231. package/src/components/tabs/tabs-trigger.tsx +210 -0
  232. package/src/components/tabs/tabs.context.tsx +294 -0
  233. package/src/components/tabs/tabs.tsx +435 -0
  234. package/src/components/tabs/tabs.types.ts +13 -0
  235. package/src/components/tabs/tabs.variants.test.ts +1020 -0
  236. package/src/components/tabs/tabs.variants.ts +672 -0
  237. package/src/components/text/AGENTS.md +95 -0
  238. package/src/components/text/index.ts +25 -0
  239. package/src/components/text/text.context.tsx +60 -0
  240. package/src/components/text/text.tsx +251 -0
  241. package/src/components/text/text.variants.test.ts +422 -0
  242. package/src/components/text/text.variants.ts +282 -0
  243. package/src/display-name.test.ts +145 -0
  244. package/src/docs.test.ts +98 -0
  245. package/src/expo/navigation-theme.tsx +58 -0
  246. package/src/hooks/use-controllable-state.ts +45 -0
  247. package/src/hooks/use-keyboard-state-sync.tsx +147 -0
  248. package/src/hooks/use-navigation-theme.ts +78 -0
  249. package/src/hooks/use-theme-color.ts +44 -0
  250. package/src/icons/central.ts +1 -0
  251. package/src/lib/cn.test.ts +136 -0
  252. package/src/lib/cn.ts +29 -0
  253. package/src/lib/color.test.ts +80 -0
  254. package/src/lib/color.ts +79 -0
  255. package/src/lib/compose-refs.test.ts +64 -0
  256. package/src/lib/compose-refs.ts +37 -0
  257. package/src/lib/keyboard-animation.test.ts +33 -0
  258. package/src/lib/keyboard-animation.ts +28 -0
  259. package/src/lib/merge-props.test.ts +82 -0
  260. package/src/lib/merge-props.ts +47 -0
  261. package/src/lib/navigation-theme.test.ts +49 -0
  262. package/src/lib/navigation-theme.ts +53 -0
  263. package/src/lib/slot.tsx +45 -0
  264. package/src/lib/tv.ts +20 -0
  265. package/src/styles/base.css +2 -0
  266. package/src/styles/geometry.test.ts +117 -0
  267. package/src/styles/index.css +3 -0
  268. package/src/styles/theme-tokens.test.ts +252 -0
  269. package/src/styles/theme.css +447 -0
  270. package/src/styles/tokens.css +132 -0
  271. package/src/styles/tokens.test.ts +181 -0
  272. package/src/styles/tokens.ts +86 -0
  273. package/src/uniwind-env.d.ts +1 -0
@@ -0,0 +1,125 @@
1
+ import { type ReactElement, type ReactNode, useCallback, useMemo, useRef } from "react";
2
+ import { useControllableState } from "../../hooks/use-controllable-state";
3
+ import { type BottomSheetContextValue, type BottomSheetHandle, BottomSheetProvider } from "./bottom-sheet.context";
4
+ import { BottomSheetClose } from "./bottom-sheet-close";
5
+ import { BottomSheetContainer } from "./bottom-sheet-container";
6
+ import { BottomSheetContent } from "./bottom-sheet-content";
7
+ import { BottomSheetDescription } from "./bottom-sheet-description";
8
+ import { BottomSheetFooter } from "./bottom-sheet-footer";
9
+ import { BottomSheetOverlay } from "./bottom-sheet-overlay";
10
+ import { BottomSheetPortal } from "./bottom-sheet-portal";
11
+ import { BottomSheetScrollView } from "./bottom-sheet-scroll-view";
12
+ import { BottomSheetTitle } from "./bottom-sheet-title";
13
+ import { BottomSheetTrigger } from "./bottom-sheet-trigger";
14
+
15
+ export type BottomSheetProps = {
16
+ children?: ReactNode;
17
+ /** Controlled open state. Leave it off and the sheet holds its own. */
18
+ isOpen?: boolean;
19
+ /** Initial open state while uncontrolled. */
20
+ defaultOpen?: boolean;
21
+ /**
22
+ * Called whenever the sheet opens or closes — by a trigger, a swipe down, a
23
+ * press on the backdrop, `BottomSheet.Close`, or a controlled `isOpen`.
24
+ *
25
+ * One callback for every path, deliberately. gorhom's own `onClose` fires for
26
+ * the gesture alone, which is the shape that makes a caller wire three
27
+ * handlers and still miss one.
28
+ */
29
+ onOpenChange?: (isOpen: boolean) => void;
30
+ };
31
+
32
+ function BottomSheetRoot({ children, defaultOpen = false, isOpen, onOpenChange }: BottomSheetProps): ReactElement {
33
+ const sheetRef = useRef<BottomSheetHandle | null>(null);
34
+ const [isSheetOpen, setOpen] = useControllableState<boolean>({
35
+ defaultValue: defaultOpen,
36
+ onChange: onOpenChange,
37
+ value: isOpen,
38
+ });
39
+
40
+ const open = useCallback(() => setOpen(true), [setOpen]);
41
+ const close = useCallback(() => setOpen(false), [setOpen]);
42
+
43
+ const context = useMemo<BottomSheetContextValue>(
44
+ () => ({ close, isOpen: isSheetOpen, open, setOpen, sheetRef }),
45
+ [close, isSheetOpen, open, setOpen]
46
+ );
47
+
48
+ return <BottomSheetProvider value={context}>{children}</BottomSheetProvider>;
49
+ }
50
+
51
+ /**
52
+ * A panel that slides up from the bottom of the screen, over everything.
53
+ *
54
+ * The library's first overlay, built on `@gorhom/bottom-sheet`'s modal. It
55
+ * renders no view of its own — it owns the open state and hands it to the parts
56
+ * through context, so a trigger anywhere inside can open a sheet declared
57
+ * anywhere else in the same tree.
58
+ *
59
+ * **The app must mount `DelacourProvider` at its root.** The sheet hosts itself in
60
+ * the `BottomSheetModalProvider` that provider's innermost layer supplies, and it
61
+ * needs the gesture root above it for the pan.
62
+ *
63
+ * **Two parts are not drawn where they are written.** `BottomSheet.Overlay` and a
64
+ * `BottomSheet.Footer sticky` are lifted out of the tree and handed to gorhom as
65
+ * render props — see `BottomSheet.Portal` and `BottomSheet.Container` for why the
66
+ * anatomy is worth that. Everything else renders in place.
67
+ *
68
+ * Controlled or not, from one hook: pass `isOpen` and `onOpenChange` to own the
69
+ * state, or nothing at all and let the sheet hold it. Which mode is in play is
70
+ * locked in on first render.
71
+ *
72
+ * @example
73
+ * <BottomSheet>
74
+ * <BottomSheet.Trigger asChild>
75
+ * <Button variant="secondary">Open</Button>
76
+ * </BottomSheet.Trigger>
77
+ * <BottomSheet.Portal>
78
+ * <BottomSheet.Overlay />
79
+ * <BottomSheet.Container>
80
+ * <BottomSheet.Content>
81
+ * <BottomSheet.Close />
82
+ * <BottomSheet.Title>Keep yourself safe</BottomSheet.Title>
83
+ * <BottomSheet.Description>Update to the latest version.</BottomSheet.Description>
84
+ * </BottomSheet.Content>
85
+ * <BottomSheet.Footer sticky>
86
+ * <Button onPress={update}>Update now</Button>
87
+ * </BottomSheet.Footer>
88
+ * </BottomSheet.Container>
89
+ * </BottomSheet.Portal>
90
+ * </BottomSheet>
91
+ *
92
+ * @example
93
+ * // Controlled, and opened from somewhere other than a trigger.
94
+ * <BottomSheet isOpen={isOpen} onOpenChange={setOpen}>
95
+ * <BottomSheet.Portal>
96
+ * <BottomSheet.Overlay />
97
+ * <BottomSheet.Container>
98
+ * <BottomSheet.Content>{…}</BottomSheet.Content>
99
+ * </BottomSheet.Container>
100
+ * </BottomSheet.Portal>
101
+ * </BottomSheet>
102
+ */
103
+ export const BottomSheet = Object.assign(BottomSheetRoot, {
104
+ /** The control that opens the sheet. `asChild` to make a `Button` the trigger. */
105
+ Trigger: BottomSheetTrigger,
106
+ /** Everything drawn above the app. Lifts the overlay out to the container. */
107
+ Portal: BottomSheetPortal,
108
+ /** The scrim. Written beside the container, drawn as its backdrop. */
109
+ Overlay: BottomSheetOverlay,
110
+ /** The sheet itself. Every gorhom prop passes through it. */
111
+ Container: BottomSheetContainer,
112
+ /** The sheet's body, sized to itself under `enableDynamicSizing`. */
113
+ Content: BottomSheetContent,
114
+ /** A scrolling body. Needs `enableDynamicSizing={false}` and snap points. */
115
+ ScrollView: BottomSheetScrollView,
116
+ /** The dismiss control, positioned out of the content's flow. */
117
+ Close: BottomSheetClose,
118
+ /** The sheet's heading — a `Text.Header` with the close control's clearance. */
119
+ Title: BottomSheetTitle,
120
+ /** Supporting copy under the title — a muted `Text.Paragraph`. */
121
+ Description: BottomSheetDescription,
122
+ /** Controls at the bottom. `sticky` pins them above the keyboard. */
123
+ Footer: BottomSheetFooter,
124
+ displayName: "DelacourUI.BottomSheet",
125
+ });
@@ -0,0 +1,276 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { declaredTokens, tokenValue } from "../../styles/theme-tokens.test";
3
+ import {
4
+ BOTTOM_SHEET_BACKDROP_INDICES,
5
+ BOTTOM_SHEET_CLOSE_HIT_SLOP,
6
+ BOTTOM_SHEET_FOOTER_GAP,
7
+ BOTTOM_SHEET_FOOTER_PADDING,
8
+ BOTTOM_SHEET_KEYBOARD_DEFAULTS,
9
+ BOTTOM_SHEET_OVERLAY_OPACITY,
10
+ BOTTOM_SHEET_OVERLAY_TOKEN,
11
+ bottomSheetVariants,
12
+ resolveFooterPlacement,
13
+ resolveSheetBottomInset,
14
+ } from "./bottom-sheet.variants";
15
+
16
+ const LIGHT = declaredTokens("light");
17
+ const DARK = declaredTokens("dark");
18
+
19
+ /** `border-t` and friends set a width, not a colour, and name no token. */
20
+ const STRUCTURAL_BORDER_SUFFIXES = new Set(["t", "b", "l", "r", "x", "y", "s", "e"]);
21
+
22
+ /** Every theme token a class string paints with, with any `/alpha` suffix dropped. */
23
+ function colorTokens(cls: string): string[] {
24
+ const tokens: string[] = [];
25
+
26
+ for (const [, utility, token] of cls.matchAll(/\b(bg|border|text)-([a-z][\w-]*)(?:\/\d+)?\b/g)) {
27
+ if (utility === "border" && STRUCTURAL_BORDER_SUFFIXES.has(token)) continue;
28
+ tokens.push(token);
29
+ }
30
+
31
+ return tokens;
32
+ }
33
+
34
+ const SLOTS = bottomSheetVariants();
35
+
36
+ /**
37
+ * The slots this component declares, pinned rather than derived.
38
+ *
39
+ * `tv` adds a `base` slot of its own that emits nothing, so iterating the
40
+ * returned object sweeps one entry that is not ours. Listing them also means a
41
+ * new slot has to be added here before the checks below can miss it.
42
+ */
43
+ const SLOT_NAMES = [
44
+ "overlay",
45
+ "background",
46
+ "handle",
47
+ "handleIndicator",
48
+ "content",
49
+ "scrollContent",
50
+ "footer",
51
+ "stickyFooter",
52
+ "close",
53
+ "title",
54
+ ] as const;
55
+
56
+ describe("the theme.css reader", () => {
57
+ // The token assertions below are only worth anything if the parse found something.
58
+ test("finds both variants", () => {
59
+ expect(LIGHT.size).toBeGreaterThan(0);
60
+ expect(DARK.size).toBeGreaterThan(0);
61
+ });
62
+ });
63
+
64
+ describe("the overlay token", () => {
65
+ test("is declared in both variants", () => {
66
+ expect(LIGHT.has(BOTTOM_SHEET_OVERLAY_TOKEN)).toBe(true);
67
+ expect(DARK.has(BOTTOM_SHEET_OVERLAY_TOKEN)).toBe(true);
68
+ });
69
+
70
+ test("is the token the overlay slot actually paints", () => {
71
+ expect(SLOTS.overlay()).toContain(`bg-${BOTTOM_SHEET_OVERLAY_TOKEN}`);
72
+ });
73
+
74
+ test("carries its own alpha in both variants, which is what makes the opacity 1", () => {
75
+ for (const variant of ["light", "dark"] as const) {
76
+ const value = tokenValue(variant, BOTTOM_SHEET_OVERLAY_TOKEN);
77
+ if (value === undefined) throw new Error(`theme.css declares no --overlay under ${variant}`);
78
+
79
+ const alpha = Number(value.match(/\/\s*([\d.]+)%\s*\)/)?.[1]) / 100;
80
+ expect(alpha).toBeGreaterThan(0);
81
+ expect(alpha).toBeLessThan(1);
82
+ }
83
+
84
+ // Leaving gorhom's 0.5 in place would multiply against that alpha and land
85
+ // the scrim at roughly a fifth of what the theme asked for.
86
+ expect(BOTTOM_SHEET_OVERLAY_OPACITY).toBe(1);
87
+ });
88
+
89
+ test("the two variants differ — a black scrim over a near-black theme is invisible", () => {
90
+ const light = tokenValue("light", BOTTOM_SHEET_OVERLAY_TOKEN);
91
+ const dark = tokenValue("dark", BOTTOM_SHEET_OVERLAY_TOKEN);
92
+ expect(light).not.toBe(dark);
93
+ });
94
+ });
95
+
96
+ describe("every token the slots name", () => {
97
+ test("is declared in both variants of theme.css", () => {
98
+ for (const name of SLOT_NAMES) {
99
+ for (const token of colorTokens(SLOTS[name]())) {
100
+ expect({ inLight: LIGHT.has(token), slot: name, token }).toEqual({ inLight: true, slot: name, token });
101
+ expect({ inDark: DARK.has(token), slot: name, token }).toEqual({ inDark: true, slot: name, token });
102
+ }
103
+ }
104
+ });
105
+
106
+ test("the reader found tokens at all", () => {
107
+ // Every assertion above passes vacuously if the extractor matches nothing.
108
+ expect(colorTokens(SLOTS.stickyFooter())).toEqual(expect.arrayContaining(["popover", "border"]));
109
+ expect(colorTokens(SLOTS.handleIndicator())).toContain("muted-foreground");
110
+ });
111
+ });
112
+
113
+ describe("the backdrop indices", () => {
114
+ test("show the scrim from the first snap point and hide it only when closed", () => {
115
+ // gorhom's own defaults (1 / 0) suit a persistent sheet resting collapsed
116
+ // on screen. A modal sheet has no resting state.
117
+ expect(BOTTOM_SHEET_BACKDROP_INDICES.appearsOnIndex).toBe(0);
118
+ expect(BOTTOM_SHEET_BACKDROP_INDICES.disappearsOnIndex).toBe(-1);
119
+ expect(BOTTOM_SHEET_BACKDROP_INDICES.disappearsOnIndex).toBeLessThan(BOTTOM_SHEET_BACKDROP_INDICES.appearsOnIndex);
120
+ });
121
+ });
122
+
123
+ describe("the keyboard defaults", () => {
124
+ test("follow the keyboard, restore on blur, and resize the Android window", () => {
125
+ expect(BOTTOM_SHEET_KEYBOARD_DEFAULTS).toEqual({
126
+ keyboardBehavior: "interactive",
127
+ keyboardBlurBehavior: "restore",
128
+ android_keyboardInputMode: "adjustResize",
129
+ });
130
+ });
131
+
132
+ test("does not leave Android on gorhom's adjustPan", () => {
133
+ // `adjustPan` slides the whole window up instead of resizing it, which puts
134
+ // a sheet's footer off-screen. `resize` is what Expo configures and what
135
+ // `KeyboardProvider` requires, so this is the value that matches reality.
136
+ expect(BOTTOM_SHEET_KEYBOARD_DEFAULTS.android_keyboardInputMode).not.toBe("adjustPan");
137
+ });
138
+ });
139
+
140
+ describe("resolveFooterPlacement", () => {
141
+ const footer = (isSticky: boolean) => ({ isFooter: true, isSticky });
142
+ const other = { isFooter: false, isSticky: false };
143
+
144
+ test("reports none for children holding no footer", () => {
145
+ expect(resolveFooterPlacement([])).toBe("none");
146
+ expect(resolveFooterPlacement([other, other])).toBe("none");
147
+ });
148
+
149
+ test("reports inline for a footer that did not ask to be pinned", () => {
150
+ expect(resolveFooterPlacement([other, footer(false)])).toBe("inline");
151
+ });
152
+
153
+ test("reports sticky for one that did, wherever it sits", () => {
154
+ expect(resolveFooterPlacement([footer(true), other])).toBe("sticky");
155
+ expect(resolveFooterPlacement([other, footer(true)])).toBe("sticky");
156
+ });
157
+
158
+ test("lets a single sticky footer win over an inline one", () => {
159
+ // Two footers is not a state worth expressing, and the pinned one is the
160
+ // one a caller meant.
161
+ expect(resolveFooterPlacement([footer(false), footer(true)])).toBe("sticky");
162
+ expect(resolveFooterPlacement([footer(true), footer(false)])).toBe("sticky");
163
+ });
164
+
165
+ test("never reads a sticky flag off a child that is not a footer", () => {
166
+ expect(resolveFooterPlacement([{ isFooter: false, isSticky: true }])).toBe("none");
167
+ });
168
+ });
169
+
170
+ describe("resolveSheetBottomInset", () => {
171
+ test("gives the safe-area band to the content when nothing is pinned below it", () => {
172
+ expect(resolveSheetBottomInset({ bottom: 34, hasStickyFooter: false })).toBe(34);
173
+ });
174
+
175
+ test("withholds it once a pinned footer's own box is carrying it", () => {
176
+ // gorhom adds the footer's whole measured height — band included — to the
177
+ // content's reserve, so asking for it here too counts it twice.
178
+ expect(resolveSheetBottomInset({ bottom: 34, hasStickyFooter: true })).toBe(0);
179
+ });
180
+
181
+ test("is a no-op on a device with no bottom inset", () => {
182
+ expect(resolveSheetBottomInset({ bottom: 0, hasStickyFooter: false })).toBe(0);
183
+ });
184
+ });
185
+
186
+ describe("bottomSheetVariants slots", () => {
187
+ test("the title slot carries layout only, never type", () => {
188
+ // The type comes from the `Text.Header` the part renders. A `text-lg` here
189
+ // would be a second definition of that preset which can drift from it.
190
+ expect(SLOTS.title()).not.toMatch(/\btext-\w/);
191
+ expect(SLOTS.title()).not.toMatch(/\bfont-\w/);
192
+ });
193
+
194
+ test("the title reserves room for the close control on every sheet", () => {
195
+ // Reserved unconditionally, the way `Badge` reserves its border on every
196
+ // variant: conditional clearance reflows the title the moment one is added.
197
+ expect(SLOTS.title()).toMatch(/\bpr-[\d.]+\b/);
198
+ });
199
+
200
+ test("every slot emits something, so none of them is untestable", () => {
201
+ // `tv` returns undefined for an empty class string, and a slot that says
202
+ // nothing is a slot no assertion here can reach.
203
+ for (const name of SLOT_NAMES) {
204
+ expect({ emits: typeof SLOTS[name](), name }).toEqual({ emits: "string", name });
205
+ }
206
+ });
207
+
208
+ test("declares no slot the pinned list has not seen", () => {
209
+ // `base` is tv's own, and is the one entry that is not ours.
210
+ const declared = Object.keys(SLOTS).filter((name) => name !== "base");
211
+ expect(declared.sort()).toEqual([...SLOT_NAMES].sort());
212
+ });
213
+
214
+ test("the sheet's surface rounds its top corners only", () => {
215
+ const background = SLOTS.background();
216
+ expect(background).toMatch(/\brounded-t-/);
217
+ expect(background).not.toMatch(/\brounded-b-/);
218
+ // A bare `rounded-*` would round the bottom edge too, and that edge runs off
219
+ // the screen — the radius shows as two notches of the app behind it.
220
+ expect(background).not.toMatch(/\brounded-(?:none|xs|sm|md|lg|xl|2xl|3xl|full)\b/);
221
+ });
222
+
223
+ test("a pinned footer brings a surface and a line where an inline one does not", () => {
224
+ // It draws OVER the content, so without them the content scrolls straight
225
+ // through it. An inline footer is in the flow and needs neither.
226
+ expect(SLOTS.stickyFooter()).toMatch(/\bbg-\w/);
227
+ expect(SLOTS.stickyFooter()).toMatch(/\bborder-t\b/);
228
+ expect(SLOTS.footer()).not.toMatch(/\bbg-\w/);
229
+ expect(SLOTS.footer()).not.toMatch(/\bborder-t\b/);
230
+ });
231
+
232
+ test("a pinned footer writes its vertical padding as a style, never a class", () => {
233
+ // The bottom padding is animated — the safe-area band collapses into it as
234
+ // the keyboard arrives — and a class cannot carry an animated value. The
235
+ // top is written the same way so the two cannot drift.
236
+ expect(SLOTS.stickyFooter()).not.toMatch(/\bp[tby]?-[\d.]+\b/);
237
+ expect(BOTTOM_SHEET_FOOTER_PADDING).toBeGreaterThan(0);
238
+ });
239
+
240
+ test("the content is held off a pinned footer by a real gap", () => {
241
+ // Without it the last row sits flush against the footer's hairline, which
242
+ // reads as content clipped rather than content ended.
243
+ expect(BOTTOM_SHEET_FOOTER_GAP).toBeGreaterThan(0);
244
+ });
245
+
246
+ test("both footers share their gutter and gap, so pinning one moves nothing sideways", () => {
247
+ const gutter = /\bpx-screen-gutter\b/;
248
+ expect(SLOTS.footer()).toMatch(gutter);
249
+ expect(SLOTS.stickyFooter()).toMatch(gutter);
250
+ expect(SLOTS.footer().match(/\bgap-[\d.]+\b/)?.[0]).toBe(SLOTS.stickyFooter().match(/\bgap-[\d.]+\b/)?.[0]);
251
+ });
252
+
253
+ test("scrolling content sits on the same gutter as static content", () => {
254
+ // A sheet should not shift sideways because its body became scrollable.
255
+ expect(SLOTS.content()).toMatch(/\bpx-screen-gutter\b/);
256
+ expect(SLOTS.scrollContent()).toMatch(/\bpx-screen-gutter\b/);
257
+ });
258
+
259
+ test("the overlay sets no opacity of its own", () => {
260
+ // gorhom animates the scrim's opacity off the sheet's position; a class here
261
+ // would be a second writer of the same style property.
262
+ expect(SLOTS.overlay()).not.toMatch(/\bopacity-\d/);
263
+ });
264
+
265
+ test("the close control is positioned out of the content flow", () => {
266
+ expect(SLOTS.close()).toContain("absolute");
267
+ expect(BOTTOM_SHEET_CLOSE_HIT_SLOP).toBeGreaterThan(0);
268
+ });
269
+
270
+ test("the grabber is a pill with both dimensions set", () => {
271
+ const indicator = SLOTS.handleIndicator();
272
+ expect(indicator).toMatch(/\bh-[\d.]+\b/);
273
+ expect(indicator).toMatch(/\bw-[\d.]+\b/);
274
+ expect(indicator).toContain("rounded-full");
275
+ });
276
+ });
@@ -0,0 +1,199 @@
1
+ import type { VariantProps } from "tailwind-variants";
2
+ import { tv } from "../../lib/tv";
3
+
4
+ /**
5
+ * The scrim token, named here so a test can pin it against `theme.css`.
6
+ *
7
+ * A token rather than a `bg-black/50` written at a call site: the two theme
8
+ * variants carry different alphas, because a pure-black scrim over a near-black
9
+ * dark theme is nearly invisible.
10
+ */
11
+ export const BOTTOM_SHEET_OVERLAY_TOKEN = "overlay";
12
+
13
+ /**
14
+ * The backdrop's opacity at full appearance.
15
+ *
16
+ * **1, not gorhom's 0.5.** The alpha lives in `--overlay`, so leaving the
17
+ * library default in place would multiply the two and land the scrim at roughly
18
+ * a fifth of what the theme asked for. One source for the alpha, and it is the
19
+ * token — which is also what lets the two theme variants differ.
20
+ */
21
+ export const BOTTOM_SHEET_OVERLAY_OPACITY = 1;
22
+
23
+ /**
24
+ * The snap indices the scrim appears and disappears on.
25
+ *
26
+ * gorhom defaults to `{ appearsOnIndex: 1, disappearsOnIndex: 0 }`, which suits
27
+ * a persistent sheet that rests collapsed on the screen and only dims the app
28
+ * once it is expanded. A modal sheet has no resting state: it is either
29
+ * presented or gone, so the scrim belongs from the FIRST snap point (`0`) and
30
+ * is only absent when the sheet is closed (`-1`).
31
+ */
32
+ export const BOTTOM_SHEET_BACKDROP_INDICES = { appearsOnIndex: 0, disappearsOnIndex: -1 } as const;
33
+
34
+ /**
35
+ * Keyboard behaviour every `BottomSheet.Container` starts from.
36
+ *
37
+ * A const rather than three defaults spelled inline, so `bun test` pins them and
38
+ * a retune is one edit rather than three.
39
+ *
40
+ * - `interactive` — the sheet grows to keep its content above the keyboard,
41
+ * following it frame by frame rather than snapping once it has settled.
42
+ * - `restore` — blurring returns the sheet to the snap point it was on. Without
43
+ * it a sheet that grew for the keyboard stays grown over empty screen.
44
+ * - `adjustResize` — gorhom's own default is `adjustPan`, which leaves the
45
+ * Android window height alone and slides the whole view up, so a sheet's
46
+ * footer lands off-screen. `resize` is already what Expo configures and what
47
+ * `KeyboardProvider` requires, so this is the value that matches the window.
48
+ */
49
+ export const BOTTOM_SHEET_KEYBOARD_DEFAULTS = {
50
+ keyboardBehavior: "interactive",
51
+ keyboardBlurBehavior: "restore",
52
+ android_keyboardInputMode: "adjustResize",
53
+ } as const;
54
+
55
+ /**
56
+ * Slop around `BottomSheet.Close`.
57
+ *
58
+ * The glyph is a bare icon in a corner with no padded capsule to absorb the
59
+ * difference against the 44pt minimum — the case `Checkbox` mints slop for and
60
+ * `Badge.CloseButton` does not need.
61
+ */
62
+ export const BOTTOM_SHEET_CLOSE_HIT_SLOP = 8;
63
+
64
+ /**
65
+ * The padding a pinned footer keeps whatever the keyboard is doing.
66
+ *
67
+ * A number rather than a `p-4`, and for `SCREEN_FOOTER_PADDING`'s reason: the
68
+ * footer's bottom padding is an animated value — the safe-area band collapses
69
+ * into it as the keyboard arrives — and a class cannot carry one. Its top
70
+ * padding is written the same way so the two cannot drift.
71
+ */
72
+ export const BOTTOM_SHEET_FOOTER_PADDING = 16;
73
+
74
+ /**
75
+ * The gap between the last of the content and a pinned footer.
76
+ *
77
+ * Also a number, because the reserve it belongs to is computed at runtime from
78
+ * the footer's measured height. Without it the last row of a list sits flush
79
+ * against the footer's hairline, which reads as content clipped rather than
80
+ * content ended.
81
+ */
82
+ export const BOTTOM_SHEET_FOOTER_GAP = 16;
83
+
84
+ export const bottomSheetVariants = tv({
85
+ slots: {
86
+ /**
87
+ * The scrim. Carries the colour and nothing else — the fade is an animated
88
+ * opacity gorhom drives off the sheet's own position.
89
+ */
90
+ overlay: "bg-overlay",
91
+ /**
92
+ * The sheet's surface. `popover` rather than `card`: a sheet is a layer
93
+ * over the app, which is what that token already means, and it is the one
94
+ * surface token nothing else in the package had claimed.
95
+ *
96
+ * Only the top corners round. The bottom edge runs off the screen, and a
97
+ * radius there shows as two notches of the app behind it.
98
+ */
99
+ background: "rounded-t-2xl bg-popover",
100
+ handle: "items-center justify-center pt-3 pb-1",
101
+ handleIndicator: "h-1 w-9 rounded-full bg-muted-foreground/40",
102
+ content: "gap-4 px-screen-gutter pt-2",
103
+ /** A scrollable body's content container. Same treatment as `content`. */
104
+ scrollContent: "gap-4 px-screen-gutter pt-2",
105
+ footer: "gap-3 px-screen-gutter pt-4",
106
+ /**
107
+ * A pinned footer draws OVER the content, so unlike the inline one it has
108
+ * to bring a surface and a line of its own — otherwise the content scrolls
109
+ * straight through it and its buttons are legible only where they happen
110
+ * to overlap blank space. The same reason `Screen.Footer`'s backing lives
111
+ * inside its sticky view.
112
+ *
113
+ * It carries no vertical padding: that is an animated style, because the
114
+ * safe-area band inside it collapses as the keyboard arrives. See
115
+ * {@link BOTTOM_SHEET_FOOTER_PADDING}.
116
+ */
117
+ stickyFooter: "gap-3 border-border border-t bg-popover px-screen-gutter",
118
+ close: "absolute top-4 right-4 z-10",
119
+ /**
120
+ * Clearance for `BottomSheet.Close`, and nothing else.
121
+ *
122
+ * The close control is absolutely positioned, so it is out of the flow and
123
+ * a long title runs straight under it. The gutter is reserved on every
124
+ * sheet rather than only where a close is written, for the reason `Badge`
125
+ * reserves its border on every variant: declaring it conditionally makes
126
+ * the title reflow the moment someone adds one.
127
+ *
128
+ * No type. That comes from the `Text` preset the part renders, the way
129
+ * `Field.Label` *is* a `Text.Label` — a `text-lg font-semibold` here would
130
+ * be a second definition of `Text.Header` that can drift from it.
131
+ */
132
+ title: "pr-8",
133
+ },
134
+ });
135
+
136
+ /**
137
+ * There is no `description` or `scrollView` slot.
138
+ *
139
+ * Neither has any layout of its own — a description is a `Text.Paragraph` in a
140
+ * gap column and a scroll view fills whatever it is given — and `tv` emits
141
+ * `undefined` for a slot whose class string is empty, so a slot that says
142
+ * nothing is a slot no test can assert against. Those two parts merge the
143
+ * caller's `className` with `cn()` instead.
144
+ */
145
+ export type BottomSheetVariantProps = VariantProps<typeof bottomSheetVariants>;
146
+
147
+ /** Where a `BottomSheet.Footer` among a container's children has to be rendered. */
148
+ export type BottomSheetFooterPlacement = "sticky" | "inline" | "none";
149
+
150
+ /** One child of a `BottomSheet.Container`, reduced to what the placement turns on. */
151
+ export type BottomSheetFooterFlag = {
152
+ isFooter: boolean;
153
+ isSticky: boolean;
154
+ };
155
+
156
+ /**
157
+ * Whether a container's children hold a footer, and where it has to go.
158
+ *
159
+ * gorhom takes a footer as a `footerComponent` render prop rather than as a
160
+ * child, so a sticky one has to be lifted out of the tree it was written in and
161
+ * handed over; an inline one simply stays put. This is the decision, kept pure
162
+ * so `bun test` reaches the whole matrix — the walk over `Children.toArray` that
163
+ * produces the flags lives with its caller, the way `withIndicator` does in
164
+ * `radio.tsx`.
165
+ *
166
+ * A single sticky footer anywhere in the children wins, because two footers is
167
+ * not a state worth expressing and the pinned one is the one a caller meant.
168
+ */
169
+ export function resolveFooterPlacement(flags: readonly BottomSheetFooterFlag[]): BottomSheetFooterPlacement {
170
+ let hasFooter = false;
171
+
172
+ for (const flag of flags) {
173
+ if (!flag.isFooter) continue;
174
+ if (flag.isSticky) return "sticky";
175
+ hasFooter = true;
176
+ }
177
+
178
+ return hasFooter ? "inline" : "none";
179
+ }
180
+
181
+ /**
182
+ * The safe-area band `BottomSheet.Content` and `BottomSheet.ScrollView` reserve.
183
+ *
184
+ * Exactly one thing in the sheet pays for the home indicator. A pinned footer is
185
+ * the bottom-most thing there is, and its own box carries the band so its surface
186
+ * reaches the bottom of the screen — gorhom then adds that whole measured height
187
+ * to the content's reserve, so the content asking for the band as well would
188
+ * count it twice and leave a gap the height of the indicator above the footer.
189
+ * With no pinned footer the content IS the bottom-most thing and takes it.
190
+ */
191
+ export function resolveSheetBottomInset({
192
+ hasStickyFooter,
193
+ bottom,
194
+ }: {
195
+ hasStickyFooter: boolean;
196
+ bottom: number;
197
+ }): number {
198
+ return hasStickyFooter ? 0 : bottom;
199
+ }
@@ -0,0 +1,40 @@
1
+ export { BottomSheet, type BottomSheetProps } from "./bottom-sheet";
2
+ export {
3
+ type BottomSheetContainerContextValue,
4
+ BottomSheetContainerProvider,
5
+ type BottomSheetContextValue,
6
+ type BottomSheetHandle,
7
+ type BottomSheetPortalContextValue,
8
+ BottomSheetPortalProvider,
9
+ BottomSheetProvider,
10
+ useBottomSheet,
11
+ useBottomSheetContainerContext,
12
+ useBottomSheetContext,
13
+ useBottomSheetPortalContext,
14
+ } from "./bottom-sheet.context";
15
+ export {
16
+ BOTTOM_SHEET_BACKDROP_INDICES,
17
+ BOTTOM_SHEET_CLOSE_HIT_SLOP,
18
+ BOTTOM_SHEET_FOOTER_GAP,
19
+ BOTTOM_SHEET_FOOTER_PADDING,
20
+ BOTTOM_SHEET_KEYBOARD_DEFAULTS,
21
+ BOTTOM_SHEET_OVERLAY_OPACITY,
22
+ BOTTOM_SHEET_OVERLAY_TOKEN,
23
+ type BottomSheetFooterFlag,
24
+ type BottomSheetFooterPlacement,
25
+ type BottomSheetVariantProps,
26
+ bottomSheetVariants,
27
+ resolveFooterPlacement,
28
+ resolveSheetBottomInset,
29
+ } from "./bottom-sheet.variants";
30
+ export type { BottomSheetCloseProps } from "./bottom-sheet-close";
31
+ export type { BottomSheetContainerProps } from "./bottom-sheet-container";
32
+ export type { BottomSheetContentProps } from "./bottom-sheet-content";
33
+ export type { BottomSheetDescriptionProps } from "./bottom-sheet-description";
34
+ export type { BottomSheetFooterProps } from "./bottom-sheet-footer";
35
+ export type { BottomSheetOverlayProps } from "./bottom-sheet-overlay";
36
+ export type { BottomSheetPortalProps } from "./bottom-sheet-portal";
37
+ export type { BottomSheetScrollViewProps } from "./bottom-sheet-scroll-view";
38
+ export type { BottomSheetTitleProps } from "./bottom-sheet-title";
39
+ export type { BottomSheetTriggerProps } from "./bottom-sheet-trigger";
40
+ export { type BottomSheetInputHandlers, useBottomSheetInput } from "./use-bottom-sheet-input";