@oxyhq/bloom 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/lib/commonjs/alert-dialog/AlertDialog.js +35 -27
  2. package/lib/commonjs/alert-dialog/AlertDialog.js.map +1 -1
  3. package/lib/commonjs/alert-dialog/index.js +1 -1
  4. package/lib/commonjs/alert-dialog/index.js.map +1 -1
  5. package/lib/commonjs/alert-dialog/index.web.js +1 -1
  6. package/lib/commonjs/alert-dialog/index.web.js.map +1 -1
  7. package/lib/commonjs/command/Command.js +27 -16
  8. package/lib/commonjs/command/Command.js.map +1 -1
  9. package/lib/commonjs/command/index.js +1 -1
  10. package/lib/commonjs/command/index.js.map +1 -1
  11. package/lib/commonjs/command/index.web.js +1 -1
  12. package/lib/commonjs/command/index.web.js.map +1 -1
  13. package/lib/commonjs/dialog/Dialog.js +336 -43
  14. package/lib/commonjs/dialog/Dialog.js.map +1 -1
  15. package/lib/commonjs/dialog/Dialog.web.js +420 -66
  16. package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
  17. package/lib/commonjs/dialog/index.js +2 -9
  18. package/lib/commonjs/dialog/index.js.map +1 -1
  19. package/lib/commonjs/dialog/index.web.js +2 -15
  20. package/lib/commonjs/dialog/index.web.js.map +1 -1
  21. package/lib/commonjs/dialog/placement.js +104 -0
  22. package/lib/commonjs/dialog/placement.js.map +1 -0
  23. package/lib/commonjs/index.js +7 -29
  24. package/lib/commonjs/index.js.map +1 -1
  25. package/lib/commonjs/index.web.js +13 -35
  26. package/lib/commonjs/index.web.js.map +1 -1
  27. package/lib/module/alert-dialog/AlertDialog.js +35 -27
  28. package/lib/module/alert-dialog/AlertDialog.js.map +1 -1
  29. package/lib/module/alert-dialog/index.js +2 -2
  30. package/lib/module/alert-dialog/index.js.map +1 -1
  31. package/lib/module/alert-dialog/index.web.js +2 -2
  32. package/lib/module/alert-dialog/index.web.js.map +1 -1
  33. package/lib/module/command/Command.js +27 -16
  34. package/lib/module/command/Command.js.map +1 -1
  35. package/lib/module/command/index.js +2 -2
  36. package/lib/module/command/index.js.map +1 -1
  37. package/lib/module/command/index.web.js +2 -2
  38. package/lib/module/command/index.web.js.map +1 -1
  39. package/lib/module/dialog/Dialog.js +335 -46
  40. package/lib/module/dialog/Dialog.js.map +1 -1
  41. package/lib/module/dialog/Dialog.web.js +418 -68
  42. package/lib/module/dialog/Dialog.web.js.map +1 -1
  43. package/lib/module/dialog/index.js +1 -2
  44. package/lib/module/dialog/index.js.map +1 -1
  45. package/lib/module/dialog/index.web.js +1 -2
  46. package/lib/module/dialog/index.web.js.map +1 -1
  47. package/lib/module/dialog/placement.js +100 -0
  48. package/lib/module/dialog/placement.js.map +1 -0
  49. package/lib/module/index.js +1 -3
  50. package/lib/module/index.js.map +1 -1
  51. package/lib/module/index.web.js +1 -3
  52. package/lib/module/index.web.js.map +1 -1
  53. package/lib/typescript/commonjs/alert-dialog/AlertDialog.d.ts +13 -12
  54. package/lib/typescript/commonjs/alert-dialog/AlertDialog.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/alert-dialog/index.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/alert-dialog/index.web.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/command/Command.d.ts +8 -8
  58. package/lib/typescript/commonjs/command/Command.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/command/index.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/command/index.web.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/dialog/Dialog.d.ts +17 -15
  62. package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts +18 -5
  64. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/dialog/index.d.ts +2 -4
  66. package/lib/typescript/commonjs/dialog/index.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/dialog/index.web.d.ts +2 -4
  68. package/lib/typescript/commonjs/dialog/index.web.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/dialog/placement.d.ts +71 -0
  70. package/lib/typescript/commonjs/dialog/placement.d.ts.map +1 -0
  71. package/lib/typescript/commonjs/dialog/types.d.ts +66 -3
  72. package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
  73. package/lib/typescript/commonjs/index.d.ts +2 -4
  74. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  75. package/lib/typescript/commonjs/index.web.d.ts +2 -4
  76. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  77. package/lib/typescript/module/alert-dialog/AlertDialog.d.ts +13 -12
  78. package/lib/typescript/module/alert-dialog/AlertDialog.d.ts.map +1 -1
  79. package/lib/typescript/module/alert-dialog/index.d.ts.map +1 -1
  80. package/lib/typescript/module/alert-dialog/index.web.d.ts.map +1 -1
  81. package/lib/typescript/module/command/Command.d.ts +8 -8
  82. package/lib/typescript/module/command/Command.d.ts.map +1 -1
  83. package/lib/typescript/module/command/index.d.ts.map +1 -1
  84. package/lib/typescript/module/command/index.web.d.ts.map +1 -1
  85. package/lib/typescript/module/dialog/Dialog.d.ts +17 -15
  86. package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
  87. package/lib/typescript/module/dialog/Dialog.web.d.ts +18 -5
  88. package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
  89. package/lib/typescript/module/dialog/index.d.ts +2 -4
  90. package/lib/typescript/module/dialog/index.d.ts.map +1 -1
  91. package/lib/typescript/module/dialog/index.web.d.ts +2 -4
  92. package/lib/typescript/module/dialog/index.web.d.ts.map +1 -1
  93. package/lib/typescript/module/dialog/placement.d.ts +71 -0
  94. package/lib/typescript/module/dialog/placement.d.ts.map +1 -0
  95. package/lib/typescript/module/dialog/types.d.ts +66 -3
  96. package/lib/typescript/module/dialog/types.d.ts.map +1 -1
  97. package/lib/typescript/module/index.d.ts +2 -4
  98. package/lib/typescript/module/index.d.ts.map +1 -1
  99. package/lib/typescript/module/index.web.d.ts +2 -4
  100. package/lib/typescript/module/index.web.d.ts.map +1 -1
  101. package/package.json +1 -18
  102. package/src/__tests__/AlertDialog.test.tsx +122 -0
  103. package/src/__tests__/Command.test.tsx +107 -0
  104. package/src/__tests__/DialogPlacement.test.tsx +144 -0
  105. package/src/alert-dialog/AlertDialog.tsx +46 -36
  106. package/src/alert-dialog/index.ts +2 -2
  107. package/src/alert-dialog/index.web.ts +2 -2
  108. package/src/command/Command.tsx +30 -19
  109. package/src/command/index.ts +2 -2
  110. package/src/command/index.web.ts +2 -2
  111. package/src/dialog/Dialog.tsx +406 -50
  112. package/src/dialog/Dialog.web.tsx +496 -78
  113. package/src/dialog/index.ts +4 -3
  114. package/src/dialog/index.web.ts +4 -7
  115. package/src/dialog/placement.ts +113 -0
  116. package/src/dialog/types.ts +69 -3
  117. package/src/index.ts +4 -11
  118. package/src/index.web.ts +4 -11
  119. package/lib/commonjs/dialog/CenteredDialog.js +0 -250
  120. package/lib/commonjs/dialog/CenteredDialog.js.map +0 -1
  121. package/lib/commonjs/dialog/CenteredDialog.web.js +0 -328
  122. package/lib/commonjs/dialog/CenteredDialog.web.js.map +0 -1
  123. package/lib/commonjs/dialog/centered-dialog-tokens.js +0 -62
  124. package/lib/commonjs/dialog/centered-dialog-tokens.js.map +0 -1
  125. package/lib/commonjs/dialog/centered-dialog-types.js +0 -6
  126. package/lib/commonjs/dialog/centered-dialog-types.js.map +0 -1
  127. package/lib/commonjs/responsive-sheet/ResponsiveSheet.js +0 -300
  128. package/lib/commonjs/responsive-sheet/ResponsiveSheet.js.map +0 -1
  129. package/lib/commonjs/responsive-sheet/ResponsiveSheet.native.js +0 -226
  130. package/lib/commonjs/responsive-sheet/ResponsiveSheet.native.js.map +0 -1
  131. package/lib/commonjs/responsive-sheet/const.js +0 -60
  132. package/lib/commonjs/responsive-sheet/const.js.map +0 -1
  133. package/lib/commonjs/responsive-sheet/index.js +0 -19
  134. package/lib/commonjs/responsive-sheet/index.js.map +0 -1
  135. package/lib/commonjs/responsive-sheet/index.web.js +0 -19
  136. package/lib/commonjs/responsive-sheet/index.web.js.map +0 -1
  137. package/lib/commonjs/responsive-sheet/types.js +0 -6
  138. package/lib/commonjs/responsive-sheet/types.js.map +0 -1
  139. package/lib/module/dialog/CenteredDialog.js +0 -244
  140. package/lib/module/dialog/CenteredDialog.js.map +0 -1
  141. package/lib/module/dialog/CenteredDialog.web.js +0 -322
  142. package/lib/module/dialog/CenteredDialog.web.js.map +0 -1
  143. package/lib/module/dialog/centered-dialog-tokens.js +0 -58
  144. package/lib/module/dialog/centered-dialog-tokens.js.map +0 -1
  145. package/lib/module/dialog/centered-dialog-types.js +0 -4
  146. package/lib/module/dialog/centered-dialog-types.js.map +0 -1
  147. package/lib/module/responsive-sheet/ResponsiveSheet.js +0 -293
  148. package/lib/module/responsive-sheet/ResponsiveSheet.js.map +0 -1
  149. package/lib/module/responsive-sheet/ResponsiveSheet.native.js +0 -219
  150. package/lib/module/responsive-sheet/ResponsiveSheet.native.js.map +0 -1
  151. package/lib/module/responsive-sheet/const.js +0 -56
  152. package/lib/module/responsive-sheet/const.js.map +0 -1
  153. package/lib/module/responsive-sheet/index.js +0 -4
  154. package/lib/module/responsive-sheet/index.js.map +0 -1
  155. package/lib/module/responsive-sheet/index.web.js +0 -14
  156. package/lib/module/responsive-sheet/index.web.js.map +0 -1
  157. package/lib/module/responsive-sheet/types.js +0 -4
  158. package/lib/module/responsive-sheet/types.js.map +0 -1
  159. package/lib/typescript/commonjs/dialog/CenteredDialog.d.ts +0 -20
  160. package/lib/typescript/commonjs/dialog/CenteredDialog.d.ts.map +0 -1
  161. package/lib/typescript/commonjs/dialog/CenteredDialog.web.d.ts +0 -29
  162. package/lib/typescript/commonjs/dialog/CenteredDialog.web.d.ts.map +0 -1
  163. package/lib/typescript/commonjs/dialog/centered-dialog-tokens.d.ts +0 -47
  164. package/lib/typescript/commonjs/dialog/centered-dialog-tokens.d.ts.map +0 -1
  165. package/lib/typescript/commonjs/dialog/centered-dialog-types.d.ts +0 -70
  166. package/lib/typescript/commonjs/dialog/centered-dialog-types.d.ts.map +0 -1
  167. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.d.ts +0 -26
  168. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.d.ts.map +0 -1
  169. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.native.d.ts +0 -20
  170. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.native.d.ts.map +0 -1
  171. package/lib/typescript/commonjs/responsive-sheet/const.d.ts +0 -43
  172. package/lib/typescript/commonjs/responsive-sheet/const.d.ts.map +0 -1
  173. package/lib/typescript/commonjs/responsive-sheet/index.d.ts +0 -3
  174. package/lib/typescript/commonjs/responsive-sheet/index.d.ts.map +0 -1
  175. package/lib/typescript/commonjs/responsive-sheet/index.web.d.ts +0 -3
  176. package/lib/typescript/commonjs/responsive-sheet/index.web.d.ts.map +0 -1
  177. package/lib/typescript/commonjs/responsive-sheet/types.d.ts +0 -55
  178. package/lib/typescript/commonjs/responsive-sheet/types.d.ts.map +0 -1
  179. package/lib/typescript/module/dialog/CenteredDialog.d.ts +0 -20
  180. package/lib/typescript/module/dialog/CenteredDialog.d.ts.map +0 -1
  181. package/lib/typescript/module/dialog/CenteredDialog.web.d.ts +0 -29
  182. package/lib/typescript/module/dialog/CenteredDialog.web.d.ts.map +0 -1
  183. package/lib/typescript/module/dialog/centered-dialog-tokens.d.ts +0 -47
  184. package/lib/typescript/module/dialog/centered-dialog-tokens.d.ts.map +0 -1
  185. package/lib/typescript/module/dialog/centered-dialog-types.d.ts +0 -70
  186. package/lib/typescript/module/dialog/centered-dialog-types.d.ts.map +0 -1
  187. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.d.ts +0 -26
  188. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.d.ts.map +0 -1
  189. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.native.d.ts +0 -20
  190. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.native.d.ts.map +0 -1
  191. package/lib/typescript/module/responsive-sheet/const.d.ts +0 -43
  192. package/lib/typescript/module/responsive-sheet/const.d.ts.map +0 -1
  193. package/lib/typescript/module/responsive-sheet/index.d.ts +0 -3
  194. package/lib/typescript/module/responsive-sheet/index.d.ts.map +0 -1
  195. package/lib/typescript/module/responsive-sheet/index.web.d.ts +0 -3
  196. package/lib/typescript/module/responsive-sheet/index.web.d.ts.map +0 -1
  197. package/lib/typescript/module/responsive-sheet/types.d.ts +0 -55
  198. package/lib/typescript/module/responsive-sheet/types.d.ts.map +0 -1
  199. package/src/__tests__/CenteredDialog.test.tsx +0 -108
  200. package/src/__tests__/ResponsiveSheet.test.tsx +0 -104
  201. package/src/dialog/CenteredDialog.stories.tsx +0 -125
  202. package/src/dialog/CenteredDialog.tsx +0 -303
  203. package/src/dialog/CenteredDialog.web.tsx +0 -372
  204. package/src/dialog/centered-dialog-tokens.ts +0 -68
  205. package/src/dialog/centered-dialog-types.ts +0 -70
  206. package/src/responsive-sheet/ResponsiveSheet.native.tsx +0 -257
  207. package/src/responsive-sheet/ResponsiveSheet.stories.tsx +0 -54
  208. package/src/responsive-sheet/ResponsiveSheet.tsx +0 -327
  209. package/src/responsive-sheet/const.ts +0 -53
  210. package/src/responsive-sheet/index.ts +0 -6
  211. package/src/responsive-sheet/index.web.ts +0 -16
  212. package/src/responsive-sheet/types.ts +0 -57
@@ -1,68 +0,0 @@
1
- import { borderRadius, space } from '../styles/tokens';
2
-
3
- /**
4
- * Shared layout constants for `<CenteredDialog>`, kept in one module so the
5
- * native and web forks render identically. All values are Bloom theme tokens
6
- * (`styles/tokens`) — no ad-hoc magic numbers.
7
- *
8
- * COMPACT is the priority: the `compact` spacing set is the default and is
9
- * deliberately tight (≈`space.md`), so the card reads snug out of the box.
10
- * The `cozy` set is the opt-out (`compact={false}`) for roomier dialogs.
11
- */
12
-
13
- /** Default max card width — comfortable for a compact dialog. */
14
- export const DEFAULT_MAX_WIDTH = 440;
15
-
16
- /** Card height is capped at this fraction of the viewport; body scrolls. */
17
- export const MAX_HEIGHT_FRACTION = 0.8;
18
-
19
- /** Card corner radius (token: `borderRadius.lg` = 16). */
20
- export const CARD_RADIUS = borderRadius.lg;
21
-
22
- /** Outer gutter between the card edge and the viewport edge (token `space.lg`). */
23
- export const VIEWPORT_GUTTER = space.lg;
24
-
25
- export type DialogSpacing = {
26
- /** Card inner padding (horizontal + vertical). */
27
- pad: number;
28
- /** Gap between the header title row and the body. */
29
- headerGap: number;
30
- /** Gap between the body and the footer. */
31
- footerGap: number;
32
- /** Vertical gap between stacked body items (the content container `gap`). */
33
- contentGap: number;
34
- };
35
-
36
- /**
37
- * Compact (default) — tight, snug spacing built on `space.md` (12).
38
- *
39
- * pad = space.md (12)
40
- * headerGap = space.sm (8)
41
- * footerGap = space.md (12)
42
- * contentGap = space.sm (8)
43
- */
44
- export const COMPACT_SPACING: DialogSpacing = {
45
- pad: space.md,
46
- headerGap: space.sm,
47
- footerGap: space.md,
48
- contentGap: space.sm,
49
- };
50
-
51
- /**
52
- * Cozy (`compact={false}`) — roomier spacing built on `space.xl` (20).
53
- *
54
- * pad = space.xl (20)
55
- * headerGap = space.md (12)
56
- * footerGap = space.lg (16)
57
- * contentGap = space.md (12)
58
- */
59
- export const COZY_SPACING: DialogSpacing = {
60
- pad: space.xl,
61
- headerGap: space.md,
62
- footerGap: space.lg,
63
- contentGap: space.md,
64
- };
65
-
66
- export function resolveSpacing(compact: boolean): DialogSpacing {
67
- return compact ? COMPACT_SPACING : COZY_SPACING;
68
- }
@@ -1,70 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import type { StyleProp, ViewStyle } from 'react-native';
3
-
4
- /**
5
- * Props for the declarative `<CenteredDialog>` primitive.
6
- *
7
- * This is the simple, controlled centered-modal surface — distinct from the
8
- * imperative `useDialogControl()` + `<Dialog control={…}>` API in the same
9
- * module. Reach for `<CenteredDialog>` when you already own the open/closed
10
- * boolean (a `useState`, a query flag, a route param) and just want Bloom to
11
- * render the dimmed backdrop + a snug, theme-styled centered card.
12
- *
13
- * All spacing/colour/radius come from Bloom theme tokens — the card reads
14
- * compact out of the box (`compact` defaults to `true`).
15
- */
16
- export type CenteredDialogProps = {
17
- /** Whether the dialog is mounted + visible. */
18
- visible: boolean;
19
- /**
20
- * Close request — fired on backdrop tap (when `dismissible`), the close
21
- * button, the hardware back button (Android), and `Escape` (web). The
22
- * caller owns the visible state, so it must flip `visible` to `false` here.
23
- */
24
- onClose: () => void;
25
- /**
26
- * Optional header title. When set (or when `showClose` is `true`) Bloom
27
- * renders a tight header row above the content.
28
- */
29
- title?: string;
30
- /**
31
- * Tap-outside / back-button / Escape dismisses the dialog. Defaults to
32
- * `true`. Set `false` for blocking flows (e.g. a destructive confirm that
33
- * must be answered via an explicit action).
34
- */
35
- dismissible?: boolean;
36
- /** Max card width in px. Defaults to {@link DEFAULT_MAX_WIDTH} (440). */
37
- maxWidth?: number;
38
- /**
39
- * Tight, snug spacing (the Bloom default). Set `false` for a roomier card
40
- * with larger padding and header/footer gaps. Defaults to `true`.
41
- */
42
- compact?: boolean;
43
- /**
44
- * Render a close (✕) icon button in the header. Defaults to `true` when a
45
- * `title` is provided, otherwise `false` (a chromeless card owns its own
46
- * dismissal affordances).
47
- */
48
- showClose?: boolean;
49
- /** Card body. Scrolls within the height cap when it overflows. */
50
- children?: ReactNode;
51
- /**
52
- * Optional footer slot for actions (e.g. a Bloom `<Button>` row). Rendered
53
- * below the scrollable body with a hairline separator.
54
- */
55
- footer?: ReactNode;
56
- /**
57
- * Accessibility label for the dialog surface. Falls back to `title` when
58
- * omitted.
59
- */
60
- accessibilityLabel?: string;
61
- /** Accessibility label for the header close (✕) button. */
62
- closeAccessibilityLabel?: string;
63
- /** Accessibility label for the dimmed backdrop's tap-to-dismiss affordance. */
64
- backdropAccessibilityLabel?: string;
65
- /** Style overrides merged onto the centered card container. */
66
- cardStyle?: StyleProp<ViewStyle>;
67
- /** Style overrides merged onto the scrollable content container. */
68
- contentStyle?: StyleProp<ViewStyle>;
69
- testID?: string;
70
- };
@@ -1,257 +0,0 @@
1
- import React, {
2
- useCallback,
3
- useEffect,
4
- useMemo,
5
- useRef,
6
- useState,
7
- } from 'react';
8
- import {
9
- Pressable,
10
- StyleSheet,
11
- useWindowDimensions,
12
- View,
13
- type ViewStyle,
14
- } from 'react-native';
15
- import Animated, {
16
- cancelAnimation,
17
- Easing,
18
- runOnJS,
19
- useAnimatedStyle,
20
- useSharedValue,
21
- withTiming,
22
- } from 'react-native-reanimated';
23
-
24
- import { Z_INDEX } from '../styles/z-index';
25
- import { useTheme } from '../theme/use-theme';
26
- import {
27
- ANIMATION_DURATION,
28
- BACKDROP_OPACITY,
29
- DEFAULT_BREAKPOINT,
30
- DEFAULT_LABEL,
31
- DEFAULT_MAX_HEIGHT_RATIO,
32
- DEFAULT_SIDE,
33
- DEFAULT_WIDTH,
34
- HANDLE_HEIGHT,
35
- HANDLE_RADIUS,
36
- HANDLE_WIDTH,
37
- PANEL_RADIUS,
38
- SIDE_SHEET_MIN_GUTTER,
39
- } from './const';
40
- import type { ResponsiveSheetProps } from './types';
41
-
42
- /** Stable testID for the dimmed backdrop dismiss button. */
43
- export const RESPONSIVE_SHEET_BACKDROP_TESTID = 'bloom-responsive-sheet-backdrop';
44
-
45
- /**
46
- * Native variant of `<ResponsiveSheet>` (selected only by Metro).
47
- *
48
- * Uses `react-native-reanimated` to drive the same motion as the web/CSS
49
- * default fork: a shared `progress` value times 0 -> 1 on open and 1 -> 0 on
50
- * close, mapped to the panel translate/opacity and the backdrop opacity.
51
- * Native exit animations are safe here — the web-only `removeChild` crash does
52
- * not apply — so we keep the node mounted through the close timing and unmount
53
- * from the animation completion callback.
54
- *
55
- * The panel itself is measured (not percentage-translated): we offset by the
56
- * panel's own width/height so the hidden state sits fully off its side, which
57
- * is the reanimated-friendly equivalent of the web `translate(±100%)`.
58
- */
59
- export function ResponsiveSheet({
60
- open,
61
- onClose,
62
- side = DEFAULT_SIDE,
63
- breakpoint = DEFAULT_BREAKPOINT,
64
- width = DEFAULT_WIDTH,
65
- maxHeightRatio = DEFAULT_MAX_HEIGHT_RATIO,
66
- inset,
67
- showHandle = true,
68
- dismissOnBackdrop = true,
69
- panelStyle,
70
- panelClassName,
71
- containerStyle,
72
- containerClassName,
73
- label = DEFAULT_LABEL,
74
- children,
75
- }: ResponsiveSheetProps) {
76
- const theme = useTheme();
77
- const { width: viewportWidth, height: viewportHeight } = useWindowDimensions();
78
-
79
- // Keep the tree mounted through the exit timing; unmount once it settles.
80
- const [mounted, setMounted] = useState(open);
81
- // `progress`: 0 = fully hidden, 1 = fully shown. Drives both transform and
82
- // opacity so a single timing controls the whole transition.
83
- const progress = useSharedValue(open ? 1 : 0);
84
-
85
- const isBottom = viewportWidth < breakpoint;
86
-
87
- // Measured panel extent along the travel axis. Falls back to the geometry
88
- // target so the very first frame is already off-screen before layout lands.
89
- const insetTop = inset?.top ?? 0;
90
- const insetBottom = inset?.bottom ?? 0;
91
- const insetLeft = inset?.left ?? 0;
92
- const insetRight = inset?.right ?? 0;
93
- const anchorInset = side === 'left' ? insetLeft : insetRight;
94
- const oppositeInset = side === 'left' ? insetRight : insetLeft;
95
-
96
- const sideWidth = useMemo(() => {
97
- const available = viewportWidth - anchorInset - oppositeInset - SIDE_SHEET_MIN_GUTTER;
98
- return Math.max(0, Math.min(width, available));
99
- }, [viewportWidth, anchorInset, oppositeInset, width]);
100
-
101
- const bottomMaxHeight = useMemo(
102
- () => Math.round(viewportHeight * maxHeightRatio),
103
- [viewportHeight, maxHeightRatio],
104
- );
105
-
106
- // Travel distance for the hidden state: the panel's measured extent (height
107
- // for the bottom-sheet, width for the side-sheet), with the geometry target
108
- // as the pre-measurement fallback.
109
- const travel = useSharedValue(isBottom ? bottomMaxHeight : sideWidth);
110
- useEffect(() => {
111
- travel.value = isBottom ? bottomMaxHeight : sideWidth;
112
- }, [isBottom, bottomMaxHeight, sideWidth, travel]);
113
-
114
- const finishClose = useCallback(() => {
115
- setMounted(false);
116
- }, []);
117
-
118
- useEffect(() => {
119
- cancelAnimation(progress);
120
- const timing = { duration: ANIMATION_DURATION, easing: Easing.out(Easing.cubic) };
121
- if (open) {
122
- setMounted(true);
123
- progress.value = withTiming(1, timing);
124
- return;
125
- }
126
- if (!mounted) return;
127
- progress.value = withTiming(0, timing, (finished) => {
128
- if (finished) runOnJS(finishClose)();
129
- });
130
- }, [open, mounted, progress, finishClose]);
131
-
132
- const handleBackdropPress = useCallback(() => {
133
- if (dismissOnBackdrop) onClose();
134
- }, [dismissOnBackdrop, onClose]);
135
-
136
- const backdropAnimatedStyle = useAnimatedStyle(() => ({
137
- opacity: progress.value * BACKDROP_OPACITY,
138
- }));
139
-
140
- const hiddenSign = side === 'left' ? -1 : 1;
141
- const panelAnimatedStyle = useAnimatedStyle(() => {
142
- const hidden = 1 - progress.value;
143
- if (isBottom) {
144
- return {
145
- opacity: progress.value,
146
- transform: [{ translateY: hidden * travel.value }],
147
- };
148
- }
149
- return {
150
- opacity: progress.value,
151
- transform: [{ translateX: hidden * travel.value * hiddenSign }],
152
- };
153
- }, [isBottom, hiddenSign]);
154
-
155
- const measurePanel = useCallback(
156
- (e: { nativeEvent: { layout: { width: number; height: number } } }) => {
157
- const measured = isBottom ? e.nativeEvent.layout.height : e.nativeEvent.layout.width;
158
- if (measured > 0) travel.value = measured;
159
- },
160
- [isBottom, travel],
161
- );
162
-
163
- const panelGeometry = useMemo<ViewStyle>(() => {
164
- if (isBottom) {
165
- return {
166
- left: 0,
167
- right: 0,
168
- bottom: 0,
169
- maxHeight: bottomMaxHeight,
170
- borderTopLeftRadius: PANEL_RADIUS,
171
- borderTopRightRadius: PANEL_RADIUS,
172
- };
173
- }
174
- return {
175
- top: insetTop,
176
- bottom: insetBottom,
177
- [side]: anchorInset,
178
- width: sideWidth,
179
- borderRadius: PANEL_RADIUS,
180
- } as ViewStyle;
181
- }, [isBottom, bottomMaxHeight, insetTop, insetBottom, side, anchorInset, sideWidth]);
182
-
183
- if (!mounted) return null;
184
-
185
- return (
186
- <View
187
- style={[styles.root, containerStyle]}
188
- {...(containerClassName ? ({ className: containerClassName } as Record<string, string>) : {})}
189
- pointerEvents="box-none"
190
- >
191
- <Animated.View style={[styles.backdrop, backdropAnimatedStyle]} pointerEvents="auto">
192
- <Pressable
193
- testID={RESPONSIVE_SHEET_BACKDROP_TESTID}
194
- accessibilityRole="button"
195
- accessibilityLabel={`Dismiss ${label}`}
196
- onPress={handleBackdropPress}
197
- disabled={!dismissOnBackdrop}
198
- style={StyleSheet.absoluteFill}
199
- />
200
- </Animated.View>
201
-
202
- <Animated.View
203
- accessibilityViewIsModal
204
- {...(panelClassName ? ({ className: panelClassName } as Record<string, string>) : {})}
205
- onLayout={measurePanel}
206
- style={[
207
- styles.panel,
208
- { backgroundColor: theme.colors.background, shadowColor: theme.colors.shadow },
209
- panelGeometry,
210
- panelAnimatedStyle,
211
- panelStyle,
212
- ]}
213
- pointerEvents="auto"
214
- >
215
- {isBottom && showHandle ? (
216
- <View style={styles.handleRow} pointerEvents="none">
217
- <View style={[styles.handle, { backgroundColor: theme.colors.border }]} />
218
- </View>
219
- ) : null}
220
- {children}
221
- </Animated.View>
222
- </View>
223
- );
224
- }
225
-
226
- const styles = StyleSheet.create({
227
- root: {
228
- ...StyleSheet.absoluteFill,
229
- zIndex: Z_INDEX.fullscreen,
230
- },
231
- backdrop: {
232
- ...StyleSheet.absoluteFill,
233
- backgroundColor: '#000',
234
- },
235
- panel: {
236
- position: 'absolute',
237
- overflow: 'hidden',
238
- shadowOpacity: 0.18,
239
- shadowRadius: 24,
240
- shadowOffset: { width: 0, height: 8 },
241
- elevation: 12,
242
- },
243
- handleRow: {
244
- width: '100%',
245
- alignItems: 'center',
246
- paddingTop: 8,
247
- paddingBottom: 4,
248
- },
249
- handle: {
250
- width: HANDLE_WIDTH,
251
- height: HANDLE_HEIGHT,
252
- borderRadius: HANDLE_RADIUS,
253
- opacity: 0.5,
254
- },
255
- });
256
-
257
- export default ResponsiveSheet;
@@ -1,54 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { View } from 'react-native';
3
- import type { Meta, StoryObj } from '@storybook/react-vite';
4
-
5
- import { ResponsiveSheet } from './index';
6
- import type { ResponsiveSheetSide } from './types';
7
- import { Button } from '../button';
8
- import { Text } from '../typography';
9
-
10
- const meta: Meta = {
11
- title: 'Overlays/ResponsiveSheet',
12
- };
13
-
14
- export default meta;
15
-
16
- type Story = StoryObj;
17
-
18
- function Demo({ side }: { side: ResponsiveSheetSide }) {
19
- const [open, setOpen] = useState(false);
20
-
21
- return (
22
- <View style={{ padding: 24, minHeight: 480 }}>
23
- <Button onPress={() => setOpen(true)}>Open sheet ({side})</Button>
24
- <ResponsiveSheet
25
- open={open}
26
- onClose={() => setOpen(false)}
27
- side={side}
28
- width={420}
29
- inset={{ top: 16, bottom: 16, left: 16 }}
30
- label="Demo sheet"
31
- >
32
- <View style={{ padding: 20, gap: 12 }}>
33
- <Text style={{ fontSize: 20, fontWeight: '700' }}>Responsive sheet</Text>
34
- <Text>
35
- On a wide viewport this is an anchored side-sheet; below the
36
- breakpoint it becomes a bottom-sheet with a drag handle. Resize the
37
- preview to switch modes.
38
- </Text>
39
- <Button variant="secondary" onPress={() => setOpen(false)}>
40
- Close
41
- </Button>
42
- </View>
43
- </ResponsiveSheet>
44
- </View>
45
- );
46
- }
47
-
48
- export const SideLeft: Story = {
49
- render: () => <Demo side="left" />,
50
- };
51
-
52
- export const SideRight: Story = {
53
- render: () => <Demo side="right" />,
54
- };