concentric-sheet 0.0.1

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.

Potentially problematic release.


This version of concentric-sheet might be problematic. Click here for more details.

Files changed (41) hide show
  1. package/NitroConcentricSheet.podspec +31 -0
  2. package/README.md +95 -0
  3. package/ios/Bridge.h +8 -0
  4. package/ios/SheetModalController.swift +294 -0
  5. package/lib/NativeSheetModal.d.ts +22 -0
  6. package/lib/NativeSheetModal.js +128 -0
  7. package/lib/index.d.ts +3 -0
  8. package/lib/index.js +1 -0
  9. package/lib/specs/SheetModalController.nitro.d.ts +34 -0
  10. package/lib/specs/SheetModalController.nitro.js +1 -0
  11. package/nitro.json +23 -0
  12. package/nitrogen/generated/.gitattributes +1 -0
  13. package/nitrogen/generated/ios/NitroConcentricSheet+autolinking.rb +60 -0
  14. package/nitrogen/generated/ios/NitroConcentricSheet-Swift-Cxx-Bridge.cpp +33 -0
  15. package/nitrogen/generated/ios/NitroConcentricSheet-Swift-Cxx-Bridge.hpp +184 -0
  16. package/nitrogen/generated/ios/NitroConcentricSheet-Swift-Cxx-Umbrella.hpp +63 -0
  17. package/nitrogen/generated/ios/NitroConcentricSheetAutolinking.mm +33 -0
  18. package/nitrogen/generated/ios/NitroConcentricSheetAutolinking.swift +26 -0
  19. package/nitrogen/generated/ios/c++/HybridSheetModalControllerSpecSwift.cpp +11 -0
  20. package/nitrogen/generated/ios/c++/HybridSheetModalControllerSpecSwift.hpp +108 -0
  21. package/nitrogen/generated/ios/swift/HybridSheetModalControllerSpec.swift +56 -0
  22. package/nitrogen/generated/ios/swift/HybridSheetModalControllerSpec_cxx.swift +150 -0
  23. package/nitrogen/generated/ios/swift/ModalCornerConfiguration.swift +83 -0
  24. package/nitrogen/generated/ios/swift/ModalCornerConfigurationType.swift +48 -0
  25. package/nitrogen/generated/ios/swift/ModalViewBackground.swift +40 -0
  26. package/nitrogen/generated/ios/swift/PresentedModalConfig.swift +111 -0
  27. package/nitrogen/generated/ios/swift/SheetDetentIdentifier.swift +40 -0
  28. package/nitrogen/generated/ios/swift/SheetPresentationConfig.swift +160 -0
  29. package/nitrogen/generated/shared/c++/HybridSheetModalControllerSpec.cpp +22 -0
  30. package/nitrogen/generated/shared/c++/HybridSheetModalControllerSpec.hpp +64 -0
  31. package/nitrogen/generated/shared/c++/ModalCornerConfiguration.hpp +97 -0
  32. package/nitrogen/generated/shared/c++/ModalCornerConfigurationType.hpp +84 -0
  33. package/nitrogen/generated/shared/c++/ModalViewBackground.hpp +76 -0
  34. package/nitrogen/generated/shared/c++/PresentedModalConfig.hpp +111 -0
  35. package/nitrogen/generated/shared/c++/SheetDetentIdentifier.hpp +76 -0
  36. package/nitrogen/generated/shared/c++/SheetPresentationConfig.hpp +114 -0
  37. package/package.json +103 -0
  38. package/react-native.config.js +13 -0
  39. package/src/NativeSheetModal.tsx +204 -0
  40. package/src/index.ts +15 -0
  41. package/src/specs/SheetModalController.nitro.ts +41 -0
package/package.json ADDED
@@ -0,0 +1,103 @@
1
+ {
2
+ "name": "concentric-sheet",
3
+ "version": "0.0.1",
4
+ "description": "concentric-sheet",
5
+ "main": "lib/index",
6
+ "module": "lib/index",
7
+ "types": "lib/index.d.ts",
8
+ "react-native": "src/index",
9
+ "source": "src/index",
10
+ "files": [
11
+ "src",
12
+ "react-native.config.js",
13
+ "lib",
14
+ "nitrogen",
15
+ "ios/**/*.h",
16
+ "ios/**/*.m",
17
+ "ios/**/*.mm",
18
+ "ios/**/*.cpp",
19
+ "ios/**/*.swift",
20
+ "app.plugin.js",
21
+ "nitro.json",
22
+ "*.podspec",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "typecheck": "tsc --noEmit",
27
+ "build": "bun run typescript",
28
+ "clean": "rm -rf node_modules/**/android/build lib",
29
+ "lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
30
+ "lint-ci": "eslint \"**/*.{js,ts,tsx}\" -f @jamesacarr/github-actions",
31
+ "typescript": "tsc",
32
+ "specs": "tsc --noEmit false && nitrogen --logLevel=\"debug\""
33
+ },
34
+ "keywords": [
35
+ "react-native",
36
+ "nitro"
37
+ ],
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/mrousavy/nitro.git"
41
+ },
42
+ "author": "Marc Rousavy <me@mrousavy.com> (https://github.com/mrousavy)",
43
+ "license": "MIT",
44
+ "bugs": {
45
+ "url": "https://github.com/mrousavy/nitro/issues"
46
+ },
47
+ "homepage": "https://github.com/mrousavy/nitro#readme",
48
+ "publishConfig": {
49
+ "registry": "https://registry.npmjs.org/"
50
+ },
51
+ "devDependencies": {
52
+ "@react-native/eslint-config": "0.83.0",
53
+ "@types/react": "^19.1.03",
54
+ "eslint": "^8.57.0",
55
+ "eslint-config-prettier": "^9.1.0",
56
+ "eslint-plugin-prettier": "^5.2.1",
57
+ "nitrogen": "*",
58
+ "prettier": "^3.3.3",
59
+ "react": "19.2.0",
60
+ "react-native": "0.83.0",
61
+ "react-native-nitro-modules": "*",
62
+ "typescript": "^5.8.3"
63
+ },
64
+ "peerDependencies": {
65
+ "react": "*",
66
+ "react-native": "*",
67
+ "react-native-nitro-modules": "*"
68
+ },
69
+ "eslintConfig": {
70
+ "root": true,
71
+ "extends": [
72
+ "@react-native",
73
+ "prettier"
74
+ ],
75
+ "plugins": [
76
+ "prettier"
77
+ ],
78
+ "rules": {
79
+ "prettier/prettier": [
80
+ "warn",
81
+ {
82
+ "quoteProps": "consistent",
83
+ "singleQuote": true,
84
+ "tabWidth": 2,
85
+ "trailingComma": "es5",
86
+ "useTabs": false
87
+ }
88
+ ]
89
+ }
90
+ },
91
+ "eslintIgnore": [
92
+ "node_modules/",
93
+ "lib/"
94
+ ],
95
+ "prettier": {
96
+ "quoteProps": "consistent",
97
+ "singleQuote": true,
98
+ "tabWidth": 2,
99
+ "trailingComma": "es5",
100
+ "useTabs": false,
101
+ "semi": false
102
+ }
103
+ }
@@ -0,0 +1,13 @@
1
+ // https://github.com/react-native-community/cli/blob/main/docs/dependencies.md
2
+
3
+ module.exports = {
4
+ dependency: {
5
+ platforms: {
6
+ /**
7
+ * @type {import('@react-native-community/cli-types').IOSDependencyParams}
8
+ */
9
+ ios: {},
10
+ android: null,
11
+ },
12
+ },
13
+ }
@@ -0,0 +1,204 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react'
2
+ import {
3
+ Modal as RNModal,
4
+ type ModalProps as RNModalProps,
5
+ Platform,
6
+ } from 'react-native'
7
+ import { getHybridObjectConstructor } from 'react-native-nitro-modules'
8
+ import type {
9
+ ModalCornerConfiguration,
10
+ ModalViewBackground,
11
+ PresentedModalConfig,
12
+ SheetDetentIdentifier,
13
+ SheetModalController,
14
+ SheetPresentationConfig,
15
+ } from './specs/SheetModalController.nitro'
16
+
17
+ const APPLY_RETRY_LIMIT = 8
18
+ const APPLY_RETRY_DELAY_MS = 16
19
+
20
+ let cachedController: SheetModalController | null = null
21
+ let didFailToCreateController = false
22
+
23
+ function getSheetModalController(): SheetModalController | null {
24
+ if (Platform.OS !== 'ios') return null
25
+ if (cachedController != null) return cachedController
26
+ if (didFailToCreateController) return null
27
+
28
+ try {
29
+ const SheetModalControllerCtor =
30
+ getHybridObjectConstructor<SheetModalController>('SheetModalController')
31
+ cachedController = new SheetModalControllerCtor()
32
+ return cachedController
33
+ } catch {
34
+ didFailToCreateController = true
35
+ return null
36
+ }
37
+ }
38
+
39
+ export interface NativeSheetModalProps extends RNModalProps {
40
+ detents?: SheetDetentIdentifier[]
41
+ selectedDetentIdentifier?: SheetDetentIdentifier
42
+ largestUndimmedDetentIdentifier?: SheetDetentIdentifier
43
+ prefersGrabberVisible?: boolean
44
+ preferredCornerRadius?: number
45
+ prefersScrollingExpandsWhenScrolledToEdge?: boolean
46
+ prefersEdgeAttachedInCompactHeight?: boolean
47
+ widthFollowsPreferredContentSizeWhenEdgeAttached?: boolean
48
+ isModalInPresentation?: boolean
49
+ preferredContentWidth?: number
50
+ preferredContentHeight?: number
51
+ modalViewBackground?: ModalViewBackground
52
+ cornerConfiguration?: ModalCornerConfiguration
53
+ }
54
+
55
+ type ModalOnShow = NonNullable<RNModalProps['onShow']>
56
+
57
+ export function applyPresentedModalConfig(
58
+ config: PresentedModalConfig
59
+ ): boolean {
60
+ const controller = getSheetModalController()
61
+ if (controller == null) return false
62
+ return controller.applyPresentedModalConfig(config)
63
+ }
64
+
65
+ export function dismissPresentedNativeModal(animated = true): boolean {
66
+ const controller = getSheetModalController()
67
+ if (controller == null) return false
68
+ return controller.dismissPresentedModal(animated)
69
+ }
70
+
71
+ export function Modal(props: NativeSheetModalProps) {
72
+ const {
73
+ detents,
74
+ selectedDetentIdentifier,
75
+ largestUndimmedDetentIdentifier,
76
+ prefersGrabberVisible,
77
+ preferredCornerRadius,
78
+ prefersScrollingExpandsWhenScrolledToEdge,
79
+ prefersEdgeAttachedInCompactHeight,
80
+ widthFollowsPreferredContentSizeWhenEdgeAttached,
81
+ isModalInPresentation,
82
+ preferredContentWidth,
83
+ preferredContentHeight,
84
+ modalViewBackground,
85
+ cornerConfiguration,
86
+ allowSwipeDismissal,
87
+ onShow: onShowProp,
88
+ visible,
89
+ ...modalProps
90
+ } = props
91
+
92
+ const resolvedAllowSwipeDismissal =
93
+ allowSwipeDismissal ??
94
+ (isModalInPresentation == null ? true : !isModalInPresentation)
95
+
96
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
97
+ const isVisibleRef = useRef(visible)
98
+
99
+ useEffect(() => {
100
+ isVisibleRef.current = visible
101
+ }, [visible])
102
+
103
+ const nativeConfig = useMemo<PresentedModalConfig>(() => {
104
+ const sheet: SheetPresentationConfig | undefined =
105
+ detents == null &&
106
+ selectedDetentIdentifier == null &&
107
+ largestUndimmedDetentIdentifier == null &&
108
+ prefersGrabberVisible == null &&
109
+ preferredCornerRadius == null &&
110
+ prefersScrollingExpandsWhenScrolledToEdge == null &&
111
+ prefersEdgeAttachedInCompactHeight == null &&
112
+ widthFollowsPreferredContentSizeWhenEdgeAttached == null
113
+ ? undefined
114
+ : {
115
+ detents,
116
+ selectedDetentIdentifier,
117
+ largestUndimmedDetentIdentifier,
118
+ prefersGrabberVisible,
119
+ preferredCornerRadius,
120
+ prefersScrollingExpandsWhenScrolledToEdge,
121
+ prefersEdgeAttachedInCompactHeight,
122
+ widthFollowsPreferredContentSizeWhenEdgeAttached,
123
+ }
124
+
125
+ return {
126
+ isModalInPresentation,
127
+ preferredContentWidth,
128
+ preferredContentHeight,
129
+ modalViewBackground,
130
+ cornerConfiguration,
131
+ sheet,
132
+ }
133
+ }, [
134
+ detents,
135
+ selectedDetentIdentifier,
136
+ largestUndimmedDetentIdentifier,
137
+ prefersGrabberVisible,
138
+ preferredCornerRadius,
139
+ prefersScrollingExpandsWhenScrolledToEdge,
140
+ prefersEdgeAttachedInCompactHeight,
141
+ widthFollowsPreferredContentSizeWhenEdgeAttached,
142
+ isModalInPresentation,
143
+ preferredContentWidth,
144
+ preferredContentHeight,
145
+ modalViewBackground,
146
+ cornerConfiguration,
147
+ ])
148
+
149
+ const clearRetryTimeout = useCallback(() => {
150
+ if (timeoutRef.current != null) {
151
+ clearTimeout(timeoutRef.current)
152
+ timeoutRef.current = null
153
+ }
154
+ }, [])
155
+
156
+ const applyNativeConfigWithRetry = useCallback(() => {
157
+ if (Platform.OS !== 'ios' || !visible) return
158
+ const controller = getSheetModalController()
159
+ if (controller == null) return
160
+
161
+ clearRetryTimeout()
162
+
163
+ let attempts = 0
164
+ const attempt = () => {
165
+ if (!isVisibleRef.current) return
166
+ const didApply = controller.applyPresentedModalConfig(nativeConfig)
167
+ attempts += 1
168
+ if (!didApply && attempts < APPLY_RETRY_LIMIT) {
169
+ timeoutRef.current = setTimeout(attempt, APPLY_RETRY_DELAY_MS)
170
+ }
171
+ }
172
+
173
+ attempt()
174
+ }, [clearRetryTimeout, nativeConfig, visible])
175
+
176
+ useEffect(() => {
177
+ applyNativeConfigWithRetry()
178
+ }, [applyNativeConfigWithRetry])
179
+
180
+ useEffect(() => {
181
+ return () => {
182
+ clearRetryTimeout()
183
+ }
184
+ }, [clearRetryTimeout])
185
+
186
+ const onShow = useCallback<ModalOnShow>(
187
+ (event) => {
188
+ applyNativeConfigWithRetry()
189
+ onShowProp?.(event)
190
+ },
191
+ [applyNativeConfigWithRetry, onShowProp]
192
+ )
193
+
194
+ return (
195
+ <RNModal
196
+ {...modalProps}
197
+ allowSwipeDismissal={resolvedAllowSwipeDismissal}
198
+ onShow={onShow}
199
+ visible={visible}
200
+ />
201
+ )
202
+ }
203
+
204
+ export default Modal
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export {
2
+ Modal,
3
+ Modal as NativeSheetModal,
4
+ applyPresentedModalConfig,
5
+ dismissPresentedNativeModal,
6
+ } from './NativeSheetModal'
7
+ export type { NativeSheetModalProps } from './NativeSheetModal'
8
+ export type {
9
+ ModalCornerConfiguration,
10
+ ModalCornerConfigurationType,
11
+ ModalViewBackground,
12
+ PresentedModalConfig,
13
+ SheetDetentIdentifier,
14
+ SheetPresentationConfig,
15
+ } from './specs/SheetModalController.nitro'
@@ -0,0 +1,41 @@
1
+ import type { HybridObject } from 'react-native-nitro-modules'
2
+
3
+ export type SheetDetentIdentifier = 'medium' | 'large'
4
+ export type ModalViewBackground = 'clear' | 'systemBackground'
5
+ export type ModalCornerConfigurationType =
6
+ | 'none'
7
+ | 'fixed'
8
+ | 'containerConcentric'
9
+ | 'capsule'
10
+
11
+ export interface ModalCornerConfiguration {
12
+ type: ModalCornerConfigurationType
13
+ radius?: number
14
+ minimumRadius?: number
15
+ maximumRadius?: number
16
+ }
17
+
18
+ export interface SheetPresentationConfig {
19
+ detents?: SheetDetentIdentifier[]
20
+ selectedDetentIdentifier?: SheetDetentIdentifier
21
+ largestUndimmedDetentIdentifier?: SheetDetentIdentifier
22
+ prefersGrabberVisible?: boolean
23
+ preferredCornerRadius?: number
24
+ prefersScrollingExpandsWhenScrolledToEdge?: boolean
25
+ prefersEdgeAttachedInCompactHeight?: boolean
26
+ widthFollowsPreferredContentSizeWhenEdgeAttached?: boolean
27
+ }
28
+
29
+ export interface PresentedModalConfig {
30
+ isModalInPresentation?: boolean
31
+ preferredContentWidth?: number
32
+ preferredContentHeight?: number
33
+ modalViewBackground?: ModalViewBackground
34
+ cornerConfiguration?: ModalCornerConfiguration
35
+ sheet?: SheetPresentationConfig
36
+ }
37
+
38
+ export interface SheetModalController extends HybridObject<{ ios: 'swift' }> {
39
+ applyPresentedModalConfig(config: PresentedModalConfig): boolean
40
+ dismissPresentedModal(animated: boolean): boolean
41
+ }