@ankhorage/surface 4.0.0 → 4.1.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 (30) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +1 -1
  3. package/dist/features/bottom-sheet/BottomSheetContext.d.ts +4 -0
  4. package/dist/features/bottom-sheet/BottomSheetContext.d.ts.map +1 -0
  5. package/dist/features/bottom-sheet/BottomSheetContext.js +3 -0
  6. package/dist/features/bottom-sheet/BottomSheetContext.js.map +1 -0
  7. package/dist/features/bottom-sheet/BottomSheetProvider.d.ts +6 -0
  8. package/dist/features/bottom-sheet/BottomSheetProvider.d.ts.map +1 -0
  9. package/dist/features/bottom-sheet/BottomSheetProvider.js +61 -0
  10. package/dist/features/bottom-sheet/BottomSheetProvider.js.map +1 -0
  11. package/dist/features/bottom-sheet/public.d.ts +4 -0
  12. package/dist/features/bottom-sheet/public.d.ts.map +1 -0
  13. package/dist/features/bottom-sheet/public.js +3 -0
  14. package/dist/features/bottom-sheet/public.js.map +1 -0
  15. package/dist/features/bottom-sheet/useBottomSheet.d.ts +4 -0
  16. package/dist/features/bottom-sheet/useBottomSheet.d.ts.map +1 -0
  17. package/dist/features/bottom-sheet/useBottomSheet.js +11 -0
  18. package/dist/features/bottom-sheet/useBottomSheet.js.map +1 -0
  19. package/dist/types/bottomSheet.d.ts +18 -0
  20. package/dist/types/bottomSheet.d.ts.map +1 -0
  21. package/dist/types/bottomSheet.js +2 -0
  22. package/dist/types/bottomSheet.js.map +1 -0
  23. package/package.json +17 -3
  24. package/src/features/bottom-sheet/BottomSheetContext.ts +5 -0
  25. package/src/features/bottom-sheet/BottomSheetProvider.tsx +129 -0
  26. package/src/features/bottom-sheet/public.ts +3 -0
  27. package/src/features/bottom-sheet/useBottomSheet.test.tsx +18 -0
  28. package/src/features/bottom-sheet/useBottomSheet.ts +15 -0
  29. package/src/index.test.ts +18 -1
  30. package/src/types/bottomSheet.ts +19 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @ankhorage/surface
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6476bf4: Add the provider-backed BottomSheetModal controller with `BottomSheetProvider` and
8
+ `useBottomSheet()` through the dedicated `@ankhorage/surface/bottom-sheet` entrypoint.
9
+
3
10
  ## 4.0.0
4
11
 
5
12
  ### Major Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @ankhorage/surface
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v3.4.6](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v4.0.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
7
7
 
8
8
  Cross-platform UI foundation for React Native and React Native Web. Provides theming, layout primitives, and core building blocks for modern apps.
9
9
 
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import type { BottomSheetController } from '../../types/bottomSheet';
3
+ export declare const BottomSheetContext: React.Context<BottomSheetController | null>;
4
+ //# sourceMappingURL=BottomSheetContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BottomSheetContext.d.ts","sourceRoot":"","sources":["../../../src/features/bottom-sheet/BottomSheetContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE,eAAO,MAAM,kBAAkB,6CAA0D,CAAC"}
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ export const BottomSheetContext = React.createContext(null);
3
+ //# sourceMappingURL=BottomSheetContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BottomSheetContext.js","sourceRoot":"","sources":["../../../src/features/bottom-sheet/BottomSheetContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC,aAAa,CAA+B,IAAI,CAAC,CAAC","sourcesContent":["import React from 'react';\n\nimport type { BottomSheetController } from '../../types/bottomSheet';\n\nexport const BottomSheetContext = React.createContext<BottomSheetController | null>(null);\n"]}
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ /*** Installs the shared bottom-sheet modal host and controller for descendant consumers. */
3
+ export declare function BottomSheetProvider({ children }: {
4
+ children: React.ReactNode;
5
+ }): React.JSX.Element;
6
+ //# sourceMappingURL=BottomSheetProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BottomSheetProvider.d.ts","sourceRoot":"","sources":["../../../src/features/bottom-sheet/BottomSheetProvider.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,qBAiD9E"}
@@ -0,0 +1,61 @@
1
+ import { BottomSheetBackdrop, BottomSheetModal, BottomSheetModalProvider, BottomSheetView, } from '@gorhom/bottom-sheet';
2
+ import React from 'react';
3
+ import { BottomSheetContext } from './BottomSheetContext';
4
+ /*** Installs the shared bottom-sheet modal host and controller for descendant consumers. */
5
+ export function BottomSheetProvider({ children }) {
6
+ const modalRef = React.useRef(null);
7
+ const activeRequestRef = React.useRef(null);
8
+ const presentedRef = React.useRef(false);
9
+ const [activeRequest, setActiveRequest] = React.useState(null);
10
+ const present = React.useCallback((options) => {
11
+ activeRequestRef.current = options;
12
+ setActiveRequest(options);
13
+ }, []);
14
+ const dismiss = React.useCallback(() => {
15
+ if (presentedRef.current) {
16
+ modalRef.current?.dismiss();
17
+ return;
18
+ }
19
+ const request = activeRequestRef.current;
20
+ activeRequestRef.current = null;
21
+ setActiveRequest(null);
22
+ request?.onDismiss?.();
23
+ }, []);
24
+ const handleDismiss = React.useCallback(() => {
25
+ const request = activeRequestRef.current;
26
+ presentedRef.current = false;
27
+ activeRequestRef.current = null;
28
+ setActiveRequest(null);
29
+ request?.onDismiss?.();
30
+ }, []);
31
+ const controller = React.useMemo(() => ({ dismiss, present }), [dismiss, present]);
32
+ return (<BottomSheetContext.Provider value={controller}>
33
+ <BottomSheetModalProvider>
34
+ {children}
35
+ <BottomSheetHost activeRequest={activeRequest} handleDismiss={handleDismiss} modalRef={modalRef} presentedRef={presentedRef}/>
36
+ </BottomSheetModalProvider>
37
+ </BottomSheetContext.Provider>);
38
+ }
39
+ /*** Renders and presents the single Gorhom modal owned by the provider. */
40
+ function BottomSheetHost({ activeRequest, handleDismiss, modalRef, presentedRef, }) {
41
+ React.useEffect(() => {
42
+ if (!activeRequest || presentedRef.current) {
43
+ return;
44
+ }
45
+ presentedRef.current = true;
46
+ modalRef.current?.present();
47
+ }, [activeRequest, modalRef, presentedRef]);
48
+ const renderBackdrop = useBottomSheetBackdrop(activeRequest?.dismissOnBackdropPress);
49
+ const snapPoints = React.useMemo(() => (activeRequest?.snapPoints ? [...activeRequest.snapPoints] : undefined), [activeRequest?.snapPoints]);
50
+ if (!activeRequest) {
51
+ return null;
52
+ }
53
+ return (<BottomSheetModal ref={modalRef} backdropComponent={renderBackdrop} enableDynamicSizing={activeRequest.enableDynamicSizing ?? true} enablePanDownToClose={activeRequest.enablePanDownToClose ?? true} index={activeRequest.initialIndex ?? 0} keyboardBehavior={activeRequest.keyboardBehavior ?? 'interactive'} keyboardBlurBehavior={activeRequest.keyboardBlurBehavior ?? 'restore'} onChange={activeRequest.onIndexChange} onDismiss={handleDismiss} snapPoints={snapPoints}>
54
+ <BottomSheetView>{activeRequest.content}</BottomSheetView>
55
+ </BottomSheetModal>);
56
+ }
57
+ /*** Creates the backdrop renderer for the active bottom-sheet request. */
58
+ function useBottomSheetBackdrop(dismissOnBackdropPress) {
59
+ return React.useCallback((props) => (<BottomSheetBackdrop {...props} appearsOnIndex={0} disappearsOnIndex={-1} pressBehavior={dismissOnBackdropPress === false ? 'none' : 'close'}/>), [dismissOnBackdropPress]);
60
+ }
61
+ //# sourceMappingURL=BottomSheetProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BottomSheetProvider.js","sourceRoot":"","sources":["../../../src/features/bottom-sheet/BottomSheetProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n BottomSheetBackdrop,\n type BottomSheetBackdropProps,\n BottomSheetModal,\n BottomSheetModalProvider,\n BottomSheetView,\n} from '@gorhom/bottom-sheet';\nimport React from 'react';\n\nimport type { BottomSheetController, BottomSheetPresentOptions } from '../../types/bottomSheet';\nimport { BottomSheetContext } from './BottomSheetContext';\n\n/*** Installs the shared bottom-sheet modal host and controller for descendant consumers. */\nexport function BottomSheetProvider({ children }: { children: React.ReactNode }) {\n const modalRef = React.useRef<BottomSheetModal>(null);\n const activeRequestRef = React.useRef<BottomSheetPresentOptions | null>(null);\n const presentedRef = React.useRef(false);\n const [activeRequest, setActiveRequest] = React.useState<BottomSheetPresentOptions | null>(null);\n\n const present = React.useCallback((options: BottomSheetPresentOptions) => {\n activeRequestRef.current = options;\n setActiveRequest(options);\n }, []);\n\n const dismiss = React.useCallback(() => {\n if (presentedRef.current) {\n modalRef.current?.dismiss();\n return;\n }\n\n const request = activeRequestRef.current;\n activeRequestRef.current = null;\n setActiveRequest(null);\n request?.onDismiss?.();\n }, []);\n\n const handleDismiss = React.useCallback(() => {\n const request = activeRequestRef.current;\n presentedRef.current = false;\n activeRequestRef.current = null;\n setActiveRequest(null);\n request?.onDismiss?.();\n }, []);\n\n const controller = React.useMemo<BottomSheetController>(\n () => ({ dismiss, present }),\n [dismiss, present],\n );\n\n return (\n <BottomSheetContext.Provider value={controller}>\n <BottomSheetModalProvider>\n {children}\n <BottomSheetHost\n activeRequest={activeRequest}\n handleDismiss={handleDismiss}\n modalRef={modalRef}\n presentedRef={presentedRef}\n />\n </BottomSheetModalProvider>\n </BottomSheetContext.Provider>\n );\n}\n\ninterface BottomSheetHostProps {\n readonly activeRequest: BottomSheetPresentOptions | null;\n readonly handleDismiss: () => void;\n readonly modalRef: React.RefObject<BottomSheetModal | null>;\n readonly presentedRef: React.RefObject<boolean>;\n}\n\n/*** Renders and presents the single Gorhom modal owned by the provider. */\nfunction BottomSheetHost({\n activeRequest,\n handleDismiss,\n modalRef,\n presentedRef,\n}: BottomSheetHostProps) {\n React.useEffect(() => {\n if (!activeRequest || presentedRef.current) {\n return;\n }\n\n presentedRef.current = true;\n modalRef.current?.present();\n }, [activeRequest, modalRef, presentedRef]);\n\n const renderBackdrop = useBottomSheetBackdrop(activeRequest?.dismissOnBackdropPress);\n const snapPoints = React.useMemo(\n () => (activeRequest?.snapPoints ? [...activeRequest.snapPoints] : undefined),\n [activeRequest?.snapPoints],\n );\n\n if (!activeRequest) {\n return null;\n }\n\n return (\n <BottomSheetModal\n ref={modalRef}\n backdropComponent={renderBackdrop}\n enableDynamicSizing={activeRequest.enableDynamicSizing ?? true}\n enablePanDownToClose={activeRequest.enablePanDownToClose ?? true}\n index={activeRequest.initialIndex ?? 0}\n keyboardBehavior={activeRequest.keyboardBehavior ?? 'interactive'}\n keyboardBlurBehavior={activeRequest.keyboardBlurBehavior ?? 'restore'}\n onChange={activeRequest.onIndexChange}\n onDismiss={handleDismiss}\n snapPoints={snapPoints}\n >\n <BottomSheetView>{activeRequest.content}</BottomSheetView>\n </BottomSheetModal>\n );\n}\n\n/*** Creates the backdrop renderer for the active bottom-sheet request. */\nfunction useBottomSheetBackdrop(dismissOnBackdropPress: boolean | undefined) {\n return React.useCallback(\n (props: BottomSheetBackdropProps) => (\n <BottomSheetBackdrop\n {...props}\n appearsOnIndex={0}\n disappearsOnIndex={-1}\n pressBehavior={dismissOnBackdropPress === false ? 'none' : 'close'}\n />\n ),\n [dismissOnBackdropPress],\n );\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export type { BottomSheetController, BottomSheetPresentOptions } from '../../types/bottomSheet';
2
+ export { BottomSheetProvider } from './BottomSheetProvider';
3
+ export { useBottomSheet } from './useBottomSheet';
4
+ //# sourceMappingURL=public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/features/bottom-sheet/public.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAChG,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { BottomSheetProvider } from './BottomSheetProvider';
2
+ export { useBottomSheet } from './useBottomSheet';
3
+ //# sourceMappingURL=public.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.js","sourceRoot":"","sources":["../../../src/features/bottom-sheet/public.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["export type { BottomSheetController, BottomSheetPresentOptions } from '../../types/bottomSheet';\nexport { BottomSheetProvider } from './BottomSheetProvider';\nexport { useBottomSheet } from './useBottomSheet';\n"]}
@@ -0,0 +1,4 @@
1
+ import type { BottomSheetController } from '../../types/bottomSheet';
2
+ /*** Returns the shared bottom-sheet controller installed by BottomSheetProvider. */
3
+ export declare function useBottomSheet(): BottomSheetController;
4
+ //# sourceMappingURL=useBottomSheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBottomSheet.d.ts","sourceRoot":"","sources":["../../../src/features/bottom-sheet/useBottomSheet.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAGrE,oFAAoF;AACpF,wBAAgB,cAAc,IAAI,qBAAqB,CAQtD"}
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { BottomSheetContext } from './BottomSheetContext';
3
+ /*** Returns the shared bottom-sheet controller installed by BottomSheetProvider. */
4
+ export function useBottomSheet() {
5
+ const controller = React.useContext(BottomSheetContext);
6
+ if (!controller) {
7
+ throw new Error('useBottomSheet must be used within BottomSheetProvider.');
8
+ }
9
+ return controller;
10
+ }
11
+ //# sourceMappingURL=useBottomSheet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBottomSheet.js","sourceRoot":"","sources":["../../../src/features/bottom-sheet/useBottomSheet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,oFAAoF;AACpF,MAAM,UAAU,cAAc;IAC5B,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAExD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC","sourcesContent":["import React from 'react';\n\nimport type { BottomSheetController } from '../../types/bottomSheet';\nimport { BottomSheetContext } from './BottomSheetContext';\n\n/*** Returns the shared bottom-sheet controller installed by BottomSheetProvider. */\nexport function useBottomSheet(): BottomSheetController {\n const controller = React.useContext(BottomSheetContext);\n\n if (!controller) {\n throw new Error('useBottomSheet must be used within BottomSheetProvider.');\n }\n\n return controller;\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import type React from 'react';
2
+ export interface BottomSheetPresentOptions {
3
+ content: React.ReactNode;
4
+ snapPoints?: readonly (number | string)[];
5
+ initialIndex?: number;
6
+ enableDynamicSizing?: boolean;
7
+ enablePanDownToClose?: boolean;
8
+ dismissOnBackdropPress?: boolean;
9
+ keyboardBehavior?: 'extend' | 'fillParent' | 'interactive';
10
+ keyboardBlurBehavior?: 'none' | 'restore';
11
+ onIndexChange?: (index: number) => void;
12
+ onDismiss?: () => void;
13
+ }
14
+ export interface BottomSheetController {
15
+ present: (options: BottomSheetPresentOptions) => void;
16
+ dismiss: () => void;
17
+ }
18
+ //# sourceMappingURL=bottomSheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bottomSheet.d.ts","sourceRoot":"","sources":["../../src/types/bottomSheet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,UAAU,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,gBAAgB,CAAC,EAAE,QAAQ,GAAG,YAAY,GAAG,aAAa,CAAC;IAC3D,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACtD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bottomSheet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bottomSheet.js","sourceRoot":"","sources":["../../src/types/bottomSheet.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nexport interface BottomSheetPresentOptions {\n content: React.ReactNode;\n snapPoints?: readonly (number | string)[];\n initialIndex?: number;\n enableDynamicSizing?: boolean;\n enablePanDownToClose?: boolean;\n dismissOnBackdropPress?: boolean;\n keyboardBehavior?: 'extend' | 'fillParent' | 'interactive';\n keyboardBlurBehavior?: 'none' | 'restore';\n onIndexChange?: (index: number) => void;\n onDismiss?: () => void;\n}\n\nexport interface BottomSheetController {\n present: (options: BottomSheetPresentOptions) => void;\n dismiss: () => void;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/surface",
3
3
  "type": "module",
4
- "version": "4.0.0",
4
+ "version": "4.1.0",
5
5
  "description": "Stable UI foundation for React Native and React Native Web.",
6
6
  "homepage": "https://github.com/ankhorage/surface#readme",
7
7
  "bugs": {
@@ -34,13 +34,17 @@
34
34
  "@react-native-vector-icons/material-design-icons": "^13.1.3",
35
35
  "react": "19.2.3",
36
36
  "react-native": "0.86.x",
37
+ "react-native-gesture-handler": "~2.32.0",
38
+ "react-native-reanimated": "4.5.1",
37
39
  "react-native-safe-area-context": "~5.7.0",
38
40
  "react-native-svg": "15.15.4",
39
- "react-native-web": "~0.21.0"
41
+ "react-native-web": "~0.21.0",
42
+ "react-native-worklets": "0.10.1"
40
43
  },
41
44
  "dependencies": {
42
45
  "@ankhorage/color-theory": "^0.3.0",
43
- "@ankhorage/contracts": "^12.0.1"
46
+ "@ankhorage/contracts": "^12.0.1",
47
+ "@gorhom/bottom-sheet": "^5.2.13"
44
48
  },
45
49
  "files": [
46
50
  "dist",
@@ -57,6 +61,13 @@
57
61
  "import": "./dist/index.js",
58
62
  "default": "./dist/index.js"
59
63
  },
64
+ "./bottom-sheet": {
65
+ "react-native": "./src/features/bottom-sheet/public.ts",
66
+ "browser": "./src/features/bottom-sheet/public.ts",
67
+ "types": "./dist/features/bottom-sheet/public.d.ts",
68
+ "import": "./dist/features/bottom-sheet/public.js",
69
+ "default": "./dist/features/bottom-sheet/public.js"
70
+ },
60
71
  "./theme": {
61
72
  "bun": "./src/theme/public.ts",
62
73
  "react-native": "./src/theme/public.ts",
@@ -102,9 +113,12 @@
102
113
  "react": "19.2.3",
103
114
  "react-dom": "19.2.3",
104
115
  "react-native": "0.86.3",
116
+ "react-native-gesture-handler": "~2.32.0",
117
+ "react-native-reanimated": "4.5.1",
105
118
  "react-native-safe-area-context": "~5.7.0",
106
119
  "react-native-svg": "15.15.4",
107
120
  "react-native-web": "~0.21.0",
121
+ "react-native-worklets": "0.10.1",
108
122
  "typescript": "~6.0.3"
109
123
  },
110
124
  "packageManager": "bun@1.4.2"
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+
3
+ import type { BottomSheetController } from '../../types/bottomSheet';
4
+
5
+ export const BottomSheetContext = React.createContext<BottomSheetController | null>(null);
@@ -0,0 +1,129 @@
1
+ import {
2
+ BottomSheetBackdrop,
3
+ type BottomSheetBackdropProps,
4
+ BottomSheetModal,
5
+ BottomSheetModalProvider,
6
+ BottomSheetView,
7
+ } from '@gorhom/bottom-sheet';
8
+ import React from 'react';
9
+
10
+ import type { BottomSheetController, BottomSheetPresentOptions } from '../../types/bottomSheet';
11
+ import { BottomSheetContext } from './BottomSheetContext';
12
+
13
+ /*** Installs the shared bottom-sheet modal host and controller for descendant consumers. */
14
+ export function BottomSheetProvider({ children }: { children: React.ReactNode }) {
15
+ const modalRef = React.useRef<BottomSheetModal>(null);
16
+ const activeRequestRef = React.useRef<BottomSheetPresentOptions | null>(null);
17
+ const presentedRef = React.useRef(false);
18
+ const [activeRequest, setActiveRequest] = React.useState<BottomSheetPresentOptions | null>(null);
19
+
20
+ const present = React.useCallback((options: BottomSheetPresentOptions) => {
21
+ activeRequestRef.current = options;
22
+ setActiveRequest(options);
23
+ }, []);
24
+
25
+ const dismiss = React.useCallback(() => {
26
+ if (presentedRef.current) {
27
+ modalRef.current?.dismiss();
28
+ return;
29
+ }
30
+
31
+ const request = activeRequestRef.current;
32
+ activeRequestRef.current = null;
33
+ setActiveRequest(null);
34
+ request?.onDismiss?.();
35
+ }, []);
36
+
37
+ const handleDismiss = React.useCallback(() => {
38
+ const request = activeRequestRef.current;
39
+ presentedRef.current = false;
40
+ activeRequestRef.current = null;
41
+ setActiveRequest(null);
42
+ request?.onDismiss?.();
43
+ }, []);
44
+
45
+ const controller = React.useMemo<BottomSheetController>(
46
+ () => ({ dismiss, present }),
47
+ [dismiss, present],
48
+ );
49
+
50
+ return (
51
+ <BottomSheetContext.Provider value={controller}>
52
+ <BottomSheetModalProvider>
53
+ {children}
54
+ <BottomSheetHost
55
+ activeRequest={activeRequest}
56
+ handleDismiss={handleDismiss}
57
+ modalRef={modalRef}
58
+ presentedRef={presentedRef}
59
+ />
60
+ </BottomSheetModalProvider>
61
+ </BottomSheetContext.Provider>
62
+ );
63
+ }
64
+
65
+ interface BottomSheetHostProps {
66
+ readonly activeRequest: BottomSheetPresentOptions | null;
67
+ readonly handleDismiss: () => void;
68
+ readonly modalRef: React.RefObject<BottomSheetModal | null>;
69
+ readonly presentedRef: React.RefObject<boolean>;
70
+ }
71
+
72
+ /*** Renders and presents the single Gorhom modal owned by the provider. */
73
+ function BottomSheetHost({
74
+ activeRequest,
75
+ handleDismiss,
76
+ modalRef,
77
+ presentedRef,
78
+ }: BottomSheetHostProps) {
79
+ React.useEffect(() => {
80
+ if (!activeRequest || presentedRef.current) {
81
+ return;
82
+ }
83
+
84
+ presentedRef.current = true;
85
+ modalRef.current?.present();
86
+ }, [activeRequest, modalRef, presentedRef]);
87
+
88
+ const renderBackdrop = useBottomSheetBackdrop(activeRequest?.dismissOnBackdropPress);
89
+ const snapPoints = React.useMemo(
90
+ () => (activeRequest?.snapPoints ? [...activeRequest.snapPoints] : undefined),
91
+ [activeRequest?.snapPoints],
92
+ );
93
+
94
+ if (!activeRequest) {
95
+ return null;
96
+ }
97
+
98
+ return (
99
+ <BottomSheetModal
100
+ ref={modalRef}
101
+ backdropComponent={renderBackdrop}
102
+ enableDynamicSizing={activeRequest.enableDynamicSizing ?? true}
103
+ enablePanDownToClose={activeRequest.enablePanDownToClose ?? true}
104
+ index={activeRequest.initialIndex ?? 0}
105
+ keyboardBehavior={activeRequest.keyboardBehavior ?? 'interactive'}
106
+ keyboardBlurBehavior={activeRequest.keyboardBlurBehavior ?? 'restore'}
107
+ onChange={activeRequest.onIndexChange}
108
+ onDismiss={handleDismiss}
109
+ snapPoints={snapPoints}
110
+ >
111
+ <BottomSheetView>{activeRequest.content}</BottomSheetView>
112
+ </BottomSheetModal>
113
+ );
114
+ }
115
+
116
+ /*** Creates the backdrop renderer for the active bottom-sheet request. */
117
+ function useBottomSheetBackdrop(dismissOnBackdropPress: boolean | undefined) {
118
+ return React.useCallback(
119
+ (props: BottomSheetBackdropProps) => (
120
+ <BottomSheetBackdrop
121
+ {...props}
122
+ appearsOnIndex={0}
123
+ disappearsOnIndex={-1}
124
+ pressBehavior={dismissOnBackdropPress === false ? 'none' : 'close'}
125
+ />
126
+ ),
127
+ [dismissOnBackdropPress],
128
+ );
129
+ }
@@ -0,0 +1,3 @@
1
+ export type { BottomSheetController, BottomSheetPresentOptions } from '../../types/bottomSheet';
2
+ export { BottomSheetProvider } from './BottomSheetProvider';
3
+ export { useBottomSheet } from './useBottomSheet';
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import React from 'react';
3
+ import { renderToStaticMarkup } from 'react-dom/server';
4
+
5
+ import { useBottomSheet } from './useBottomSheet';
6
+
7
+ describe('useBottomSheet', () => {
8
+ it('requires BottomSheetProvider', () => {
9
+ function Consumer() {
10
+ useBottomSheet();
11
+ return null;
12
+ }
13
+
14
+ expect(() => renderToStaticMarkup(<Consumer />)).toThrow(
15
+ 'useBottomSheet must be used within BottomSheetProvider.',
16
+ );
17
+ });
18
+ });
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+
3
+ import type { BottomSheetController } from '../../types/bottomSheet';
4
+ import { BottomSheetContext } from './BottomSheetContext';
5
+
6
+ /*** Returns the shared bottom-sheet controller installed by BottomSheetProvider. */
7
+ export function useBottomSheet(): BottomSheetController {
8
+ const controller = React.useContext(BottomSheetContext);
9
+
10
+ if (!controller) {
11
+ throw new Error('useBottomSheet must be used within BottomSheetProvider.');
12
+ }
13
+
14
+ return controller;
15
+ }
package/src/index.test.ts CHANGED
@@ -9,6 +9,7 @@ const indexSource = readFileSync(new URL('./index.ts', import.meta.url), 'utf8')
9
9
  const packageJson = JSON.parse(
10
10
  readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
11
11
  ) as {
12
+ dependencies: Record<string, string>;
12
13
  devDependencies: Record<string, string>;
13
14
  exports: Record<string, unknown>;
14
15
  files: string[];
@@ -80,7 +81,7 @@ describe('public root barrel contract', () => {
80
81
  });
81
82
 
82
83
  describe('public package metadata contract', () => {
83
- it('keeps package metadata aligned with the single-entry public surface', () => {
84
+ it('keeps package metadata aligned with deliberate public entrypoints', () => {
84
85
  expect(packageJson.files).toEqual(['dist', 'src', 'README.md', 'CHANGELOG.md', 'LICENSE']);
85
86
  expect(packageJson.exports).toEqual({
86
87
  '.': {
@@ -90,6 +91,13 @@ describe('public package metadata contract', () => {
90
91
  import: './dist/index.js',
91
92
  types: './dist/index.d.ts',
92
93
  },
94
+ './bottom-sheet': {
95
+ 'react-native': './src/features/bottom-sheet/public.ts',
96
+ browser: './src/features/bottom-sheet/public.ts',
97
+ default: './dist/features/bottom-sheet/public.js',
98
+ import: './dist/features/bottom-sheet/public.js',
99
+ types: './dist/features/bottom-sheet/public.d.ts',
100
+ },
93
101
  './theme': {
94
102
  bun: './src/theme/public.ts',
95
103
  'react-native': './src/theme/public.ts',
@@ -109,3 +117,12 @@ describe('public package metadata contract', () => {
109
117
  expect(packageJson.devDependencies['react-native-svg']).toBe('15.15.4');
110
118
  });
111
119
  });
120
+
121
+ describe('bottom-sheet package contract', () => {
122
+ it('keeps the implementation and Expo 57 runtime boundary explicit', () => {
123
+ expect(packageJson.dependencies['@gorhom/bottom-sheet']).toMatch(/^\^5\./);
124
+ expect(packageJson.peerDependencies['react-native-gesture-handler']).toMatch(/^~2\.32\./);
125
+ expect(packageJson.peerDependencies['react-native-reanimated']).toBe('4.5.1');
126
+ expect(packageJson.peerDependencies['react-native-worklets']).toBe('0.10.1');
127
+ });
128
+ });
@@ -0,0 +1,19 @@
1
+ import type React from 'react';
2
+
3
+ export interface BottomSheetPresentOptions {
4
+ content: React.ReactNode;
5
+ snapPoints?: readonly (number | string)[];
6
+ initialIndex?: number;
7
+ enableDynamicSizing?: boolean;
8
+ enablePanDownToClose?: boolean;
9
+ dismissOnBackdropPress?: boolean;
10
+ keyboardBehavior?: 'extend' | 'fillParent' | 'interactive';
11
+ keyboardBlurBehavior?: 'none' | 'restore';
12
+ onIndexChange?: (index: number) => void;
13
+ onDismiss?: () => void;
14
+ }
15
+
16
+ export interface BottomSheetController {
17
+ present: (options: BottomSheetPresentOptions) => void;
18
+ dismiss: () => void;
19
+ }