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.
- package/NitroConcentricSheet.podspec +31 -0
- package/README.md +95 -0
- package/ios/Bridge.h +8 -0
- package/ios/SheetModalController.swift +294 -0
- package/lib/NativeSheetModal.d.ts +22 -0
- package/lib/NativeSheetModal.js +128 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +1 -0
- package/lib/specs/SheetModalController.nitro.d.ts +34 -0
- package/lib/specs/SheetModalController.nitro.js +1 -0
- package/nitro.json +23 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/ios/NitroConcentricSheet+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroConcentricSheet-Swift-Cxx-Bridge.cpp +33 -0
- package/nitrogen/generated/ios/NitroConcentricSheet-Swift-Cxx-Bridge.hpp +184 -0
- package/nitrogen/generated/ios/NitroConcentricSheet-Swift-Cxx-Umbrella.hpp +63 -0
- package/nitrogen/generated/ios/NitroConcentricSheetAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NitroConcentricSheetAutolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridSheetModalControllerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridSheetModalControllerSpecSwift.hpp +108 -0
- package/nitrogen/generated/ios/swift/HybridSheetModalControllerSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridSheetModalControllerSpec_cxx.swift +150 -0
- package/nitrogen/generated/ios/swift/ModalCornerConfiguration.swift +83 -0
- package/nitrogen/generated/ios/swift/ModalCornerConfigurationType.swift +48 -0
- package/nitrogen/generated/ios/swift/ModalViewBackground.swift +40 -0
- package/nitrogen/generated/ios/swift/PresentedModalConfig.swift +111 -0
- package/nitrogen/generated/ios/swift/SheetDetentIdentifier.swift +40 -0
- package/nitrogen/generated/ios/swift/SheetPresentationConfig.swift +160 -0
- package/nitrogen/generated/shared/c++/HybridSheetModalControllerSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridSheetModalControllerSpec.hpp +64 -0
- package/nitrogen/generated/shared/c++/ModalCornerConfiguration.hpp +97 -0
- package/nitrogen/generated/shared/c++/ModalCornerConfigurationType.hpp +84 -0
- package/nitrogen/generated/shared/c++/ModalViewBackground.hpp +76 -0
- package/nitrogen/generated/shared/c++/PresentedModalConfig.hpp +111 -0
- package/nitrogen/generated/shared/c++/SheetDetentIdentifier.hpp +76 -0
- package/nitrogen/generated/shared/c++/SheetPresentationConfig.hpp +114 -0
- package/package.json +103 -0
- package/react-native.config.js +13 -0
- package/src/NativeSheetModal.tsx +204 -0
- package/src/index.ts +15 -0
- 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
|
+
}
|