zkit-ui 1.0.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 (267) hide show
  1. package/README.md +267 -0
  2. package/dist/ComponentLibProvider.d.ts +15 -0
  3. package/dist/ComponentLibProvider.d.ts.map +1 -0
  4. package/dist/ComponentLibProvider.js +18 -0
  5. package/dist/assets/animations/spinner.json +1 -0
  6. package/dist/assets/icons/check.svg +4 -0
  7. package/dist/assets/icons/toast/error.webp +0 -0
  8. package/dist/assets/icons/toast/success.webp +0 -0
  9. package/dist/assets/icons/toast/warning.webp +0 -0
  10. package/dist/config.d.ts +16 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +42 -0
  13. package/dist/i18n/I18nProvider.d.ts +11 -0
  14. package/dist/i18n/I18nProvider.d.ts.map +1 -0
  15. package/dist/i18n/I18nProvider.js +84 -0
  16. package/dist/i18n/locale.d.ts +7 -0
  17. package/dist/i18n/locale.d.ts.map +1 -0
  18. package/dist/i18n/locale.js +87 -0
  19. package/dist/i18n/locales/de.d.ts +51 -0
  20. package/dist/i18n/locales/de.d.ts.map +1 -0
  21. package/dist/i18n/locales/de.js +62 -0
  22. package/dist/i18n/locales/en-US.d.ts +51 -0
  23. package/dist/i18n/locales/en-US.d.ts.map +1 -0
  24. package/dist/i18n/locales/en-US.js +62 -0
  25. package/dist/i18n/locales/index.d.ts +9 -0
  26. package/dist/i18n/locales/index.d.ts.map +1 -0
  27. package/dist/i18n/locales/index.js +23 -0
  28. package/dist/i18n/locales/ja.d.ts +51 -0
  29. package/dist/i18n/locales/ja.d.ts.map +1 -0
  30. package/dist/i18n/locales/ja.js +62 -0
  31. package/dist/i18n/locales/zh-CN.d.ts +51 -0
  32. package/dist/i18n/locales/zh-CN.d.ts.map +1 -0
  33. package/dist/i18n/locales/zh-CN.js +62 -0
  34. package/dist/i18n/locales/zh-TW.d.ts +51 -0
  35. package/dist/i18n/locales/zh-TW.d.ts.map +1 -0
  36. package/dist/i18n/locales/zh-TW.js +62 -0
  37. package/dist/i18n/types.d.ts +11 -0
  38. package/dist/i18n/types.d.ts.map +1 -0
  39. package/dist/i18n/types.js +2 -0
  40. package/dist/i18n/useI18n.d.ts +2 -0
  41. package/dist/i18n/useI18n.d.ts.map +1 -0
  42. package/dist/i18n/useI18n.js +41 -0
  43. package/dist/index.d.ts +57 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +90 -0
  46. package/dist/services/ActionDialogService/ActionDialog.d.ts +4 -0
  47. package/dist/services/ActionDialogService/ActionDialog.d.ts.map +1 -0
  48. package/dist/services/ActionDialogService/ActionDialog.js +531 -0
  49. package/dist/services/ActionDialogService/index.d.ts +4 -0
  50. package/dist/services/ActionDialogService/index.d.ts.map +1 -0
  51. package/dist/services/ActionDialogService/index.js +8 -0
  52. package/dist/services/ActionDialogService/service.d.ts +7 -0
  53. package/dist/services/ActionDialogService/service.d.ts.map +1 -0
  54. package/dist/services/ActionDialogService/service.js +309 -0
  55. package/dist/services/ActionDialogService/shared.d.ts +17 -0
  56. package/dist/services/ActionDialogService/shared.d.ts.map +1 -0
  57. package/dist/services/ActionDialogService/shared.js +167 -0
  58. package/dist/services/ActionDialogService/types.d.ts +160 -0
  59. package/dist/services/ActionDialogService/types.d.ts.map +1 -0
  60. package/dist/services/ActionDialogService/types.js +2 -0
  61. package/dist/services/CardToastService/index.d.ts +115 -0
  62. package/dist/services/CardToastService/index.d.ts.map +1 -0
  63. package/dist/services/CardToastService/index.js +899 -0
  64. package/dist/services/ImagePreviewService/index.d.ts +179 -0
  65. package/dist/services/ImagePreviewService/index.d.ts.map +1 -0
  66. package/dist/services/ImagePreviewService/index.js +1383 -0
  67. package/dist/services/LoadingService/index.d.ts +146 -0
  68. package/dist/services/LoadingService/index.d.ts.map +1 -0
  69. package/dist/services/LoadingService/index.js +827 -0
  70. package/dist/services/PermissionPurposeDialogService/index.d.ts +99 -0
  71. package/dist/services/PermissionPurposeDialogService/index.d.ts.map +1 -0
  72. package/dist/services/PermissionPurposeDialogService/index.js +360 -0
  73. package/dist/services/PickerService/index.d.ts +128 -0
  74. package/dist/services/PickerService/index.d.ts.map +1 -0
  75. package/dist/services/PickerService/index.js +250 -0
  76. package/dist/theme/ThemeProvider.d.ts +15 -0
  77. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  78. package/dist/theme/ThemeProvider.js +61 -0
  79. package/dist/theme/defaultTheme.d.ts +3 -0
  80. package/dist/theme/defaultTheme.d.ts.map +1 -0
  81. package/dist/theme/defaultTheme.js +28 -0
  82. package/dist/theme/mergeTheme.d.ts +3 -0
  83. package/dist/theme/mergeTheme.d.ts.map +1 -0
  84. package/dist/theme/mergeTheme.js +19 -0
  85. package/dist/theme/types.d.ts +17 -0
  86. package/dist/theme/types.d.ts.map +1 -0
  87. package/dist/theme/types.js +2 -0
  88. package/dist/theme/useTheme.d.ts +2 -0
  89. package/dist/theme/useTheme.d.ts.map +1 -0
  90. package/dist/theme/useTheme.js +46 -0
  91. package/dist/ui/Accordion/index.d.ts +99 -0
  92. package/dist/ui/Accordion/index.d.ts.map +1 -0
  93. package/dist/ui/Accordion/index.js +777 -0
  94. package/dist/ui/AddressCascader/index.d.ts +58 -0
  95. package/dist/ui/AddressCascader/index.d.ts.map +1 -0
  96. package/dist/ui/AddressCascader/index.js +710 -0
  97. package/dist/ui/BetweenTime/index.d.ts +82 -0
  98. package/dist/ui/BetweenTime/index.d.ts.map +1 -0
  99. package/dist/ui/BetweenTime/index.js +702 -0
  100. package/dist/ui/Button/index.d.ts +93 -0
  101. package/dist/ui/Button/index.d.ts.map +1 -0
  102. package/dist/ui/Button/index.js +832 -0
  103. package/dist/ui/Checkbox/index.d.ts +103 -0
  104. package/dist/ui/Checkbox/index.d.ts.map +1 -0
  105. package/dist/ui/Checkbox/index.js +694 -0
  106. package/dist/ui/DatePicker/index.d.ts +5 -0
  107. package/dist/ui/DatePicker/index.d.ts.map +1 -0
  108. package/dist/ui/DatePicker/index.js +157 -0
  109. package/dist/ui/DatePicker/types.d.ts +99 -0
  110. package/dist/ui/DatePicker/types.d.ts.map +1 -0
  111. package/dist/ui/DatePicker/types.js +2 -0
  112. package/dist/ui/DatePicker/utils.d.ts +32 -0
  113. package/dist/ui/DatePicker/utils.d.ts.map +1 -0
  114. package/dist/ui/DatePicker/utils.js +316 -0
  115. package/dist/ui/LinkedScroll/index.d.ts +78 -0
  116. package/dist/ui/LinkedScroll/index.d.ts.map +1 -0
  117. package/dist/ui/LinkedScroll/index.js +470 -0
  118. package/dist/ui/LoadingSpinner/index.d.ts +8 -0
  119. package/dist/ui/LoadingSpinner/index.d.ts.map +1 -0
  120. package/dist/ui/LoadingSpinner/index.js +122 -0
  121. package/dist/ui/Picker/actionBar.d.ts +14 -0
  122. package/dist/ui/Picker/actionBar.d.ts.map +1 -0
  123. package/dist/ui/Picker/actionBar.js +28 -0
  124. package/dist/ui/Picker/index.d.ts +5 -0
  125. package/dist/ui/Picker/index.d.ts.map +1 -0
  126. package/dist/ui/Picker/index.js +412 -0
  127. package/dist/ui/Picker/types.d.ts +80 -0
  128. package/dist/ui/Picker/types.d.ts.map +1 -0
  129. package/dist/ui/Picker/types.js +2 -0
  130. package/dist/ui/Picker/utils.d.ts +27 -0
  131. package/dist/ui/Picker/utils.d.ts.map +1 -0
  132. package/dist/ui/Picker/utils.js +182 -0
  133. package/dist/ui/Radio/index.d.ts +95 -0
  134. package/dist/ui/Radio/index.d.ts.map +1 -0
  135. package/dist/ui/Radio/index.js +633 -0
  136. package/dist/ui/Sheet/index.d.ts +12 -0
  137. package/dist/ui/Sheet/index.d.ts.map +1 -0
  138. package/dist/ui/Sheet/index.js +832 -0
  139. package/dist/ui/Sheet/nativeBottom.d.ts +52 -0
  140. package/dist/ui/Sheet/nativeBottom.d.ts.map +1 -0
  141. package/dist/ui/Sheet/nativeBottom.js +442 -0
  142. package/dist/ui/Sheet/nativeBottom.web.d.ts +18 -0
  143. package/dist/ui/Sheet/nativeBottom.web.d.ts.map +1 -0
  144. package/dist/ui/Sheet/nativeBottom.web.js +46 -0
  145. package/dist/ui/Sheet/slots.d.ts +5 -0
  146. package/dist/ui/Sheet/slots.d.ts.map +1 -0
  147. package/dist/ui/Sheet/slots.js +40 -0
  148. package/dist/ui/Sheet/types.d.ts +131 -0
  149. package/dist/ui/Sheet/types.d.ts.map +1 -0
  150. package/dist/ui/Sheet/types.js +2 -0
  151. package/dist/ui/SliderCaptcha/index.d.ts +57 -0
  152. package/dist/ui/SliderCaptcha/index.d.ts.map +1 -0
  153. package/dist/ui/SliderCaptcha/index.js +586 -0
  154. package/dist/ui/Switch/index.d.ts +72 -0
  155. package/dist/ui/Switch/index.d.ts.map +1 -0
  156. package/dist/ui/Switch/index.js +671 -0
  157. package/dist/ui/Text/index.d.ts +41 -0
  158. package/dist/ui/Text/index.d.ts.map +1 -0
  159. package/dist/ui/Text/index.js +325 -0
  160. package/dist/ui/TextInput/TextInputPrimitive.d.ts +8 -0
  161. package/dist/ui/TextInput/TextInputPrimitive.d.ts.map +1 -0
  162. package/dist/ui/TextInput/TextInputPrimitive.js +54 -0
  163. package/dist/ui/TextInput/index.d.ts +89 -0
  164. package/dist/ui/TextInput/index.d.ts.map +1 -0
  165. package/dist/ui/TextInput/index.js +545 -0
  166. package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.d.ts +39 -0
  167. package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.d.ts.map +1 -0
  168. package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.js +60 -0
  169. package/dist/ui/WheelColumn/constants.d.ts +2 -0
  170. package/dist/ui/WheelColumn/constants.d.ts.map +1 -0
  171. package/dist/ui/WheelColumn/constants.js +4 -0
  172. package/dist/ui/WheelColumn/index.d.ts +59 -0
  173. package/dist/ui/WheelColumn/index.d.ts.map +1 -0
  174. package/dist/ui/WheelColumn/index.js +547 -0
  175. package/ios/ZKitWheelPicker.h +18 -0
  176. package/ios/ZKitWheelPicker.mm +230 -0
  177. package/ios/ZKitWheelPickerLabel.h +10 -0
  178. package/ios/ZKitWheelPickerLabel.mm +31 -0
  179. package/ios/ZKitWheelPickerManager.h +5 -0
  180. package/ios/ZKitWheelPickerManager.mm +53 -0
  181. package/package.json +75 -0
  182. package/react-native.config.js +10 -0
  183. package/src/ComponentLibProvider.tsx +56 -0
  184. package/src/assets/animations/spinner.json +1 -0
  185. package/src/assets/icons/check.svg +4 -0
  186. package/src/assets/icons/toast/error.webp +0 -0
  187. package/src/assets/icons/toast/success.webp +0 -0
  188. package/src/assets/icons/toast/warning.webp +0 -0
  189. package/src/config.ts +65 -0
  190. package/src/i18n/I18nProvider.tsx +69 -0
  191. package/src/i18n/locale.ts +108 -0
  192. package/src/i18n/locales/de.ts +67 -0
  193. package/src/i18n/locales/en-US.ts +67 -0
  194. package/src/i18n/locales/index.ts +19 -0
  195. package/src/i18n/locales/ja.ts +67 -0
  196. package/src/i18n/locales/zh-CN.ts +67 -0
  197. package/src/i18n/locales/zh-TW.ts +67 -0
  198. package/src/i18n/types.ts +21 -0
  199. package/src/i18n/useI18n.ts +7 -0
  200. package/src/index.ts +395 -0
  201. package/src/services/ActionDialogService/ActionDialog.tsx +756 -0
  202. package/src/services/ActionDialogService/README.md +146 -0
  203. package/src/services/ActionDialogService/index.tsx +39 -0
  204. package/src/services/ActionDialogService/service.tsx +355 -0
  205. package/src/services/ActionDialogService/shared.ts +204 -0
  206. package/src/services/ActionDialogService/types.ts +197 -0
  207. package/src/services/CardToastService/README.md +128 -0
  208. package/src/services/CardToastService/index.tsx +1284 -0
  209. package/src/services/ImagePreviewService/README.md +67 -0
  210. package/src/services/ImagePreviewService/index.tsx +2148 -0
  211. package/src/services/LoadingService/README.md +128 -0
  212. package/src/services/LoadingService/index.tsx +1113 -0
  213. package/src/services/PermissionPurposeDialogService/README.md +80 -0
  214. package/src/services/PermissionPurposeDialogService/index.tsx +449 -0
  215. package/src/services/PickerService/README.md +154 -0
  216. package/src/services/PickerService/index.tsx +410 -0
  217. package/src/theme/ThemeProvider.tsx +40 -0
  218. package/src/theme/defaultTheme.ts +30 -0
  219. package/src/theme/mergeTheme.ts +16 -0
  220. package/src/theme/types.ts +36 -0
  221. package/src/theme/useTheme.ts +10 -0
  222. package/src/types/svg.d.ts +9 -0
  223. package/src/types/vant-area-data.d.ts +19 -0
  224. package/src/ui/Accordion/README.md +67 -0
  225. package/src/ui/Accordion/index.tsx +1221 -0
  226. package/src/ui/AddressCascader/README.md +144 -0
  227. package/src/ui/AddressCascader/index.tsx +1084 -0
  228. package/src/ui/BetweenTime/README.md +490 -0
  229. package/src/ui/BetweenTime/index.tsx +1001 -0
  230. package/src/ui/Button/README.md +186 -0
  231. package/src/ui/Button/index.tsx +1356 -0
  232. package/src/ui/Checkbox/README.md +211 -0
  233. package/src/ui/Checkbox/index.tsx +1231 -0
  234. package/src/ui/DatePicker/README.md +117 -0
  235. package/src/ui/DatePicker/index.tsx +295 -0
  236. package/src/ui/DatePicker/types.ts +115 -0
  237. package/src/ui/DatePicker/utils.ts +405 -0
  238. package/src/ui/LinkedScroll/README.md +55 -0
  239. package/src/ui/LinkedScroll/index.tsx +809 -0
  240. package/src/ui/LoadingSpinner/index.tsx +104 -0
  241. package/src/ui/Picker/README.md +190 -0
  242. package/src/ui/Picker/actionBar.tsx +70 -0
  243. package/src/ui/Picker/index.tsx +694 -0
  244. package/src/ui/Picker/types.ts +94 -0
  245. package/src/ui/Picker/utils.ts +239 -0
  246. package/src/ui/Radio/README.md +207 -0
  247. package/src/ui/Radio/index.tsx +1117 -0
  248. package/src/ui/Sheet/README.md +82 -0
  249. package/src/ui/Sheet/index.tsx +1163 -0
  250. package/src/ui/Sheet/nativeBottom.tsx +683 -0
  251. package/src/ui/Sheet/nativeBottom.web.tsx +40 -0
  252. package/src/ui/Sheet/slots.tsx +75 -0
  253. package/src/ui/Sheet/types.ts +195 -0
  254. package/src/ui/SliderCaptcha/README.md +61 -0
  255. package/src/ui/SliderCaptcha/index.tsx +816 -0
  256. package/src/ui/Switch/README.md +143 -0
  257. package/src/ui/Switch/index.tsx +1092 -0
  258. package/src/ui/Text/README.md +157 -0
  259. package/src/ui/Text/index.tsx +450 -0
  260. package/src/ui/TextInput/README.md +197 -0
  261. package/src/ui/TextInput/TextInputPrimitive.tsx +44 -0
  262. package/src/ui/TextInput/index.tsx +942 -0
  263. package/src/ui/WheelColumn/README.md +104 -0
  264. package/src/ui/WheelColumn/ZKitWheelPickerNativeComponent.tsx +68 -0
  265. package/src/ui/WheelColumn/constants.ts +1 -0
  266. package/src/ui/WheelColumn/index.tsx +826 -0
  267. package/zkit-ui.podspec +19 -0
@@ -0,0 +1,683 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Animated,
4
+ Easing,
5
+ Modal,
6
+ Platform,
7
+ Pressable,
8
+ StyleSheet,
9
+ View,
10
+ type StyleProp,
11
+ type ViewProps,
12
+ type ViewStyle,
13
+ } from 'react-native';
14
+ import {
15
+ TrueSheet,
16
+ type DetentInfoEventPayload,
17
+ type SheetDetent as NativeSheetDetent,
18
+ type TrueSheetProps,
19
+ } from '@lodev09/react-native-true-sheet';
20
+ import { wp } from 'zkit-tools';
21
+ import { useTheme } from '../../theme/useTheme';
22
+ import { SheetHeader } from './slots';
23
+ import type {
24
+ SheetBackdropConfig,
25
+ SheetCloseOptions,
26
+ SheetCloseReason,
27
+ SheetDetent,
28
+ SheetDetentChangePayload,
29
+ SheetHandleConfig,
30
+ SheetNativeProps,
31
+ SheetOpenChangeReason,
32
+ SheetOpenOptions,
33
+ SheetRenderContext,
34
+ SheetSlot,
35
+ SheetState,
36
+ } from './types';
37
+
38
+ export type NativeBottomSheetOpenChangeDetails = {
39
+ reason: SheetOpenChangeReason;
40
+ detentIndex: number;
41
+ };
42
+
43
+ export type NativeBottomSheetCloseCompleteDetails = {
44
+ reason: SheetCloseReason;
45
+ detentIndex: number;
46
+ };
47
+
48
+ export type NativeBottomSheetRef = {
49
+ open: (options?: SheetOpenOptions) => Promise<void>;
50
+ close: (options?: SheetCloseOptions) => Promise<void>;
51
+ snapTo: (detentIndex: number) => Promise<void>;
52
+ getState: () => SheetState;
53
+ };
54
+
55
+ export type NativeBottomSheetProps = {
56
+ open?: boolean;
57
+ defaultOpen?: boolean;
58
+ onOpenChange?: (open: boolean, details: NativeBottomSheetOpenChangeDetails) => void;
59
+ onOpenComplete?: (payload: SheetDetentChangePayload) => void;
60
+ onCloseComplete?: (details: NativeBottomSheetCloseCompleteDetails) => void;
61
+
62
+ detents?: readonly SheetDetent[];
63
+ detentIndex?: number;
64
+ defaultDetentIndex?: number;
65
+ onDetentChange?: (index: number, payload: SheetDetentChangePayload) => void;
66
+
67
+ disabled?: boolean;
68
+ dismissible?: boolean;
69
+ draggable?: boolean;
70
+ backdrop?: boolean | SheetBackdropConfig;
71
+
72
+ title?: React.ReactNode;
73
+ description?: React.ReactNode;
74
+ header?: SheetSlot;
75
+ footer?: SheetSlot;
76
+ children?: SheetSlot;
77
+
78
+ backgroundColor?: TrueSheetProps['backgroundColor'];
79
+ cornerRadius?: number;
80
+ maxHeight?: number;
81
+ maxWidth?: number;
82
+ handle?: boolean | SheetHandleConfig;
83
+ style?: StyleProp<ViewStyle>;
84
+ headerStyle?: StyleProp<ViewStyle>;
85
+ footerStyle?: StyleProp<ViewStyle>;
86
+
87
+ testID?: ViewProps['testID'];
88
+ accessibilityLabel?: ViewProps['accessibilityLabel'];
89
+ backdropAccessibilityLabel?: string;
90
+
91
+ nativeProps?: SheetNativeProps;
92
+ };
93
+
94
+ type NativePhase = 'idle' | 'presenting' | 'presented' | 'dismissing';
95
+ type ResolvedBackdrop = Required<SheetBackdropConfig>;
96
+
97
+ const DEFAULT_DETENTS: readonly SheetDetent[] = ['content'];
98
+ const DEFAULT_BACKDROP_OPACITY = 0.42;
99
+ const DEFAULT_DISMISSIBLE = true;
100
+ const DEFAULT_DRAGGABLE = true;
101
+ const DEFAULT_HANDLE_VISIBLE = true;
102
+ const IOS_SYSTEM_CORNER_RADIUS_MAJOR_VERSION = 26;
103
+
104
+ function clampNumber(value: number, min: number, max: number) {
105
+ if (!Number.isFinite(value)) return min;
106
+ return Math.max(min, Math.min(max, value));
107
+ }
108
+
109
+ function silentlyCatchPromise(value: unknown) {
110
+ const maybePromise = value as { catch?: (onRejected: () => void) => unknown } | null | undefined;
111
+ if (typeof maybePromise?.catch === 'function') {
112
+ maybePromise.catch(() => {});
113
+ }
114
+ }
115
+
116
+ function normalizeDetent(detent: SheetDetent): NativeSheetDetent {
117
+ if (detent === 'content' || detent === 'auto') return 'auto';
118
+ if (detent === 'medium') return 0.5;
119
+ if (detent === 'large') return 0.9;
120
+ if (detent === 'full') return 1;
121
+
122
+ if (typeof detent === 'string' && detent.endsWith('%')) {
123
+ const percent = Number.parseFloat(detent.slice(0, -1));
124
+ if (Number.isFinite(percent) && percent > 0) {
125
+ return clampNumber(percent / 100, 0.1, 1);
126
+ }
127
+ return 'auto';
128
+ }
129
+
130
+ if (typeof detent === 'number' && Number.isFinite(detent) && detent > 0) {
131
+ return clampNumber(detent, 0.1, 1);
132
+ }
133
+
134
+ return 'auto';
135
+ }
136
+
137
+ function normalizeDetents(detents: readonly SheetDetent[] | undefined) {
138
+ const source = detents?.length ? detents : DEFAULT_DETENTS;
139
+ const semantic = source.slice(0, 3);
140
+ return {
141
+ semantic,
142
+ native: semantic.map(normalizeDetent),
143
+ };
144
+ }
145
+
146
+ function normalizeDetentIndex(index: number | undefined, length: number) {
147
+ if (!Number.isFinite(index)) return 0;
148
+ return clampNumber(Math.round(index ?? 0), 0, Math.max(0, length - 1));
149
+ }
150
+
151
+ function getIOSMajorVersion() {
152
+ if (Platform.OS !== 'ios') return undefined;
153
+ const version = Platform.Version;
154
+ const major = typeof version === 'number' ? version : Number.parseInt(String(version).split('.')[0] ?? '', 10);
155
+ return Number.isFinite(major) ? major : undefined;
156
+ }
157
+
158
+ function getDefaultCornerRadius() {
159
+ const iosMajorVersion = getIOSMajorVersion();
160
+ if (iosMajorVersion != null && iosMajorVersion >= IOS_SYSTEM_CORNER_RADIUS_MAJOR_VERSION) {
161
+ return undefined;
162
+ }
163
+
164
+ return wp(22);
165
+ }
166
+
167
+ function resolveBackdrop(backdrop: NativeBottomSheetProps['backdrop']): ResolvedBackdrop {
168
+ if (backdrop === false) {
169
+ return {
170
+ visible: false,
171
+ dismissOnPress: false,
172
+ color: '#000000',
173
+ opacity: 0,
174
+ };
175
+ }
176
+
177
+ if (backdrop && typeof backdrop === 'object') {
178
+ return {
179
+ visible: backdrop.visible !== false,
180
+ dismissOnPress: backdrop.dismissOnPress !== false,
181
+ color: backdrop.color ?? '#000000',
182
+ opacity: backdrop.opacity ?? DEFAULT_BACKDROP_OPACITY,
183
+ };
184
+ }
185
+
186
+ return {
187
+ visible: true,
188
+ dismissOnPress: true,
189
+ color: '#000000',
190
+ opacity: DEFAULT_BACKDROP_OPACITY,
191
+ };
192
+ }
193
+
194
+ function renderSlot(slot: SheetSlot | undefined, context: SheetRenderContext) {
195
+ if (typeof slot === 'function') {
196
+ return (slot as (context: SheetRenderContext) => React.ReactNode)(context);
197
+ }
198
+ return slot;
199
+ }
200
+
201
+ function toNativeSlot(slot: React.ReactNode): TrueSheetProps['header'] {
202
+ if (slot == null || typeof slot === 'boolean') return undefined;
203
+ if (React.isValidElement(slot)) return slot;
204
+ return <View>{slot}</View>;
205
+ }
206
+
207
+ function createDetentPayload(
208
+ event: DetentInfoEventPayload,
209
+ semanticDetents: readonly SheetDetent[],
210
+ nativeDetents: readonly NativeSheetDetent[]
211
+ ): SheetDetentChangePayload {
212
+ const index = normalizeDetentIndex(event.index, semanticDetents.length);
213
+ return {
214
+ index,
215
+ detent: semanticDetents[index] ?? DEFAULT_DETENTS[0],
216
+ nativeDetent: nativeDetents[index] ?? 'auto',
217
+ position: event.position,
218
+ placement: 'bottom',
219
+ };
220
+ }
221
+
222
+ export const NativeBottomSheet = React.forwardRef<NativeBottomSheetRef, NativeBottomSheetProps>(
223
+ function NativeBottomSheet(
224
+ {
225
+ open: openProp,
226
+ defaultOpen = false,
227
+ onOpenChange,
228
+ onOpenComplete,
229
+ onCloseComplete,
230
+ detents,
231
+ detentIndex: detentIndexProp,
232
+ defaultDetentIndex = 0,
233
+ onDetentChange,
234
+ disabled = false,
235
+ dismissible = DEFAULT_DISMISSIBLE,
236
+ draggable = DEFAULT_DRAGGABLE,
237
+ backdrop,
238
+ title,
239
+ description,
240
+ header,
241
+ footer,
242
+ children,
243
+ backgroundColor,
244
+ cornerRadius,
245
+ maxHeight,
246
+ maxWidth,
247
+ handle = DEFAULT_HANDLE_VISIBLE,
248
+ style,
249
+ headerStyle,
250
+ footerStyle,
251
+ testID,
252
+ accessibilityLabel,
253
+ backdropAccessibilityLabel = 'Close sheet',
254
+ nativeProps,
255
+ },
256
+ ref
257
+ ) {
258
+ const theme = useTheme();
259
+ const isOpenControlled = openProp !== undefined;
260
+ const [innerOpen, setInnerOpen] = React.useState(defaultOpen);
261
+ const isOpen = isOpenControlled ? !!openProp : innerOpen;
262
+ const { semantic: semanticDetents, native: nativeDetents } = React.useMemo(
263
+ () => normalizeDetents(detents),
264
+ [detents]
265
+ );
266
+ const defaultIndex = normalizeDetentIndex(defaultDetentIndex, nativeDetents.length);
267
+ const controlledDetentIndex =
268
+ detentIndexProp == null ? undefined : normalizeDetentIndex(detentIndexProp, nativeDetents.length);
269
+ const initialDetentIndex = controlledDetentIndex ?? defaultIndex;
270
+ const [currentDetentIndex, setCurrentDetentIndex] = React.useState(initialDetentIndex);
271
+ const [sheetState, setSheetState] = React.useState<SheetState>(isOpen ? 'opening' : 'closed');
272
+ const [shellMounted, setShellMounted] = React.useState(isOpen);
273
+
274
+ const nativeRef = React.useRef<TrueSheet>(null);
275
+ const phaseRef = React.useRef<NativePhase>(isOpen ? 'presenting' : 'idle');
276
+ const pendingDismissRef = React.useRef(false);
277
+ const activeLifecycleRef = React.useRef(isOpen);
278
+ const isOpenRef = React.useRef(isOpen);
279
+ const stateRef = React.useRef<SheetState>(sheetState);
280
+ const currentDetentIndexRef = React.useRef(currentDetentIndex);
281
+ const requestedOpenIndexRef = React.useRef(initialDetentIndex);
282
+ const presentAnimatedRef = React.useRef(true);
283
+ const dismissAnimatedRef = React.useRef(true);
284
+ const closeReasonRef = React.useRef<SheetCloseReason>('system');
285
+ const backdropConfig = React.useMemo(() => resolveBackdrop(backdrop), [backdrop]);
286
+ const usesManualBackdrop = Platform.OS === 'ios' && backdropConfig.visible;
287
+ const backdropOpacity = React.useRef(
288
+ new Animated.Value(usesManualBackdrop && isOpen ? backdropConfig.opacity : 0)
289
+ ).current;
290
+ const [manualBackdropMounted, setManualBackdropMounted] = React.useState(usesManualBackdrop && isOpen);
291
+
292
+ React.useEffect(() => {
293
+ isOpenRef.current = isOpen;
294
+ }, [isOpen]);
295
+
296
+ React.useEffect(() => {
297
+ stateRef.current = sheetState;
298
+ }, [sheetState]);
299
+
300
+ React.useEffect(() => {
301
+ currentDetentIndexRef.current = currentDetentIndex;
302
+ }, [currentDetentIndex]);
303
+
304
+ React.useEffect(() => {
305
+ if (isOpen) {
306
+ activeLifecycleRef.current = true;
307
+ requestedOpenIndexRef.current = controlledDetentIndex ?? requestedOpenIndexRef.current;
308
+ if (!shellMounted) setShellMounted(true);
309
+ return;
310
+ }
311
+
312
+ if (shellMounted) {
313
+ requestDismiss('system');
314
+ }
315
+ }, [controlledDetentIndex, isOpen, shellMounted]);
316
+
317
+ React.useEffect(() => {
318
+ if (!isOpen || !shellMounted) return undefined;
319
+
320
+ const rafId = requestAnimationFrame(() => {
321
+ if (!isOpenRef.current) return;
322
+ if (phaseRef.current !== 'idle' && phaseRef.current !== 'presenting') return;
323
+
324
+ const sheet = nativeRef.current;
325
+ if (!sheet) return;
326
+
327
+ const targetIndex = normalizeDetentIndex(
328
+ controlledDetentIndex ?? requestedOpenIndexRef.current,
329
+ nativeDetents.length
330
+ );
331
+
332
+ pendingDismissRef.current = false;
333
+ phaseRef.current = 'presenting';
334
+ setSheetState('opening');
335
+ setCurrentDetentIndex(targetIndex);
336
+ silentlyCatchPromise(sheet.present(targetIndex, presentAnimatedRef.current));
337
+ });
338
+
339
+ return () => cancelAnimationFrame(rafId);
340
+ }, [controlledDetentIndex, isOpen, nativeDetents.length, shellMounted]);
341
+
342
+ React.useEffect(() => {
343
+ if (controlledDetentIndex == null) return;
344
+ requestedOpenIndexRef.current = controlledDetentIndex;
345
+
346
+ if (!isOpen || phaseRef.current !== 'presented') return;
347
+ if (controlledDetentIndex === currentDetentIndexRef.current) return;
348
+
349
+ silentlyCatchPromise(nativeRef.current?.resize(controlledDetentIndex));
350
+ }, [controlledDetentIndex, isOpen]);
351
+
352
+ React.useEffect(() => {
353
+ if (!usesManualBackdrop) return;
354
+
355
+ backdropOpacity.stopAnimation();
356
+
357
+ if (isOpen) {
358
+ setManualBackdropMounted(true);
359
+ Animated.timing(backdropOpacity, {
360
+ toValue: backdropConfig.opacity,
361
+ duration: 180,
362
+ easing: Easing.out(Easing.cubic),
363
+ useNativeDriver: true,
364
+ }).start();
365
+ return;
366
+ }
367
+
368
+ Animated.timing(backdropOpacity, {
369
+ toValue: 0,
370
+ duration: 120,
371
+ easing: Easing.in(Easing.quad),
372
+ useNativeDriver: true,
373
+ }).start(({ finished }) => {
374
+ if (finished && !isOpenRef.current) {
375
+ setManualBackdropMounted(false);
376
+ }
377
+ });
378
+ }, [backdropConfig.opacity, backdropOpacity, isOpen, usesManualBackdrop]);
379
+
380
+ const emitOpenChange = React.useCallback(
381
+ (nextOpen: boolean, reason: SheetOpenChangeReason, detentIndex?: number) => {
382
+ onOpenChange?.(nextOpen, {
383
+ reason,
384
+ detentIndex: normalizeDetentIndex(detentIndex ?? currentDetentIndexRef.current, nativeDetents.length),
385
+ });
386
+ },
387
+ [nativeDetents.length, onOpenChange]
388
+ );
389
+
390
+ const setOpen = React.useCallback(
391
+ (nextOpen: boolean, reason: SheetOpenChangeReason, detentIndex?: number) => {
392
+ if (disabled && nextOpen) return;
393
+
394
+ if (nextOpen) {
395
+ requestedOpenIndexRef.current = normalizeDetentIndex(
396
+ detentIndex ?? requestedOpenIndexRef.current,
397
+ nativeDetents.length
398
+ );
399
+ presentAnimatedRef.current = true;
400
+ activeLifecycleRef.current = true;
401
+ pendingDismissRef.current = false;
402
+ if (!isOpenControlled) setInnerOpen(true);
403
+ emitOpenChange(true, 'api', requestedOpenIndexRef.current);
404
+ return;
405
+ }
406
+
407
+ closeReasonRef.current = reason === 'api' ? 'api' : reason;
408
+ if (!isOpenControlled) setInnerOpen(false);
409
+ emitOpenChange(false, reason, currentDetentIndexRef.current);
410
+ },
411
+ [disabled, emitOpenChange, isOpenControlled, nativeDetents.length]
412
+ );
413
+
414
+ const finishClosedLifecycle = React.useCallback(
415
+ (shouldSyncOpenState: boolean) => {
416
+ phaseRef.current = 'idle';
417
+ pendingDismissRef.current = false;
418
+ presentAnimatedRef.current = true;
419
+ dismissAnimatedRef.current = true;
420
+ setSheetState('closed');
421
+
422
+ if (shouldSyncOpenState) {
423
+ if (!isOpenControlled) setInnerOpen(false);
424
+ emitOpenChange(false, closeReasonRef.current, currentDetentIndexRef.current);
425
+ }
426
+
427
+ setShellMounted(false);
428
+
429
+ if (activeLifecycleRef.current) {
430
+ activeLifecycleRef.current = false;
431
+ onCloseComplete?.({
432
+ reason: closeReasonRef.current,
433
+ detentIndex: currentDetentIndexRef.current,
434
+ });
435
+ }
436
+ },
437
+ [emitOpenChange, isOpenControlled, onCloseComplete]
438
+ );
439
+
440
+ function requestDismiss(reason: SheetCloseReason) {
441
+ const phase = phaseRef.current;
442
+ closeReasonRef.current = reason;
443
+
444
+ if (phase === 'dismissing') return;
445
+
446
+ if (phase === 'presenting') {
447
+ pendingDismissRef.current = true;
448
+ return;
449
+ }
450
+
451
+ if (phase === 'presented') {
452
+ const sheet = nativeRef.current;
453
+ if (!sheet) {
454
+ finishClosedLifecycle(false);
455
+ return;
456
+ }
457
+
458
+ pendingDismissRef.current = false;
459
+ phaseRef.current = 'dismissing';
460
+ setSheetState('closing');
461
+ silentlyCatchPromise(sheet.dismiss(dismissAnimatedRef.current));
462
+ return;
463
+ }
464
+
465
+ if (activeLifecycleRef.current) {
466
+ finishClosedLifecycle(false);
467
+ }
468
+ }
469
+
470
+ const openSheet = React.useCallback(
471
+ async (options?: SheetOpenOptions) => {
472
+ const targetIndex = normalizeDetentIndex(
473
+ options?.detentIndex ?? requestedOpenIndexRef.current,
474
+ nativeDetents.length
475
+ );
476
+ requestedOpenIndexRef.current = targetIndex;
477
+ presentAnimatedRef.current = options?.animated !== false;
478
+
479
+ if (isOpenRef.current && phaseRef.current === 'presented') {
480
+ await nativeRef.current?.resize(targetIndex);
481
+ return;
482
+ }
483
+
484
+ setOpen(true, 'api', targetIndex);
485
+ },
486
+ [nativeDetents.length, setOpen]
487
+ );
488
+
489
+ const closeSheet = React.useCallback(
490
+ async (options?: SheetCloseOptions) => {
491
+ dismissAnimatedRef.current = options?.animated !== false;
492
+ setOpen(false, 'api', currentDetentIndexRef.current);
493
+ },
494
+ [setOpen]
495
+ );
496
+
497
+ const snapTo = React.useCallback(
498
+ async (detentIndex: number) => {
499
+ const targetIndex = normalizeDetentIndex(detentIndex, nativeDetents.length);
500
+ requestedOpenIndexRef.current = targetIndex;
501
+ if (!isOpenRef.current || phaseRef.current !== 'presented') return;
502
+ await nativeRef.current?.resize(targetIndex);
503
+ },
504
+ [nativeDetents.length]
505
+ );
506
+
507
+ React.useImperativeHandle(
508
+ ref,
509
+ () => ({
510
+ open: openSheet,
511
+ close: closeSheet,
512
+ snapTo,
513
+ getState: () => stateRef.current,
514
+ }),
515
+ [closeSheet, openSheet, snapTo]
516
+ );
517
+
518
+ const context = React.useMemo<SheetRenderContext>(
519
+ () => ({
520
+ isOpen,
521
+ state: sheetState,
522
+ placement: 'bottom',
523
+ detentIndex: currentDetentIndex,
524
+ open: openSheet,
525
+ close: closeSheet,
526
+ snapTo,
527
+ }),
528
+ [closeSheet, currentDetentIndex, isOpen, openSheet, sheetState, snapTo]
529
+ );
530
+
531
+ const handleDidPresent = React.useCallback(
532
+ (event: { nativeEvent: DetentInfoEventPayload }) => {
533
+ const payload = createDetentPayload(event.nativeEvent, semanticDetents, nativeDetents);
534
+ phaseRef.current = 'presented';
535
+ setSheetState('open');
536
+ setCurrentDetentIndex(payload.index);
537
+ currentDetentIndexRef.current = payload.index;
538
+ onOpenComplete?.(payload);
539
+
540
+ if (pendingDismissRef.current || !isOpenRef.current) {
541
+ requestDismiss(closeReasonRef.current);
542
+ }
543
+ },
544
+ [nativeDetents, onOpenComplete, semanticDetents]
545
+ );
546
+
547
+ const handleDidDismiss = React.useCallback(() => {
548
+ const wasRequestedDismiss = phaseRef.current === 'dismissing' || pendingDismissRef.current;
549
+ const shouldSyncOpenState = isOpenRef.current && !wasRequestedDismiss;
550
+ if (shouldSyncOpenState) {
551
+ closeReasonRef.current = closeReasonRef.current === 'system' ? 'gesture' : closeReasonRef.current;
552
+ }
553
+ finishClosedLifecycle(shouldSyncOpenState);
554
+ }, [finishClosedLifecycle]);
555
+
556
+ const handleDetentChange = React.useCallback(
557
+ (event: { nativeEvent: DetentInfoEventPayload }) => {
558
+ const payload = createDetentPayload(event.nativeEvent, semanticDetents, nativeDetents);
559
+ if (detentIndexProp == null) {
560
+ setCurrentDetentIndex(payload.index);
561
+ }
562
+ currentDetentIndexRef.current = payload.index;
563
+ onDetentChange?.(payload.index, payload);
564
+ },
565
+ [detentIndexProp, nativeDetents, onDetentChange, semanticDetents]
566
+ );
567
+
568
+ const handleBackPress = React.useCallback(() => {
569
+ closeReasonRef.current = 'back';
570
+ return nativeProps?.onBackPress?.() ?? true;
571
+ }, [nativeProps]);
572
+
573
+ const handleBackdropPress = React.useCallback(() => {
574
+ if (disabled || !dismissible || !backdropConfig.dismissOnPress || !isOpenRef.current) return;
575
+ closeReasonRef.current = 'backdrop';
576
+ setOpen(false, 'backdrop', currentDetentIndexRef.current);
577
+ }, [backdropConfig.dismissOnPress, disabled, dismissible, setOpen]);
578
+
579
+ const resolvedHeader = toNativeSlot(
580
+ header !== undefined || title != null || description != null ? (
581
+ header !== undefined ? (
582
+ renderSlot(header, context)
583
+ ) : (
584
+ <SheetHeader title={title} description={description} />
585
+ )
586
+ ) : undefined
587
+ );
588
+ const resolvedFooter = toNativeSlot(footer !== undefined ? renderSlot(footer, context) : undefined);
589
+ const resolvedChildren = renderSlot(children, context);
590
+ const handleConfig = handle && typeof handle === 'object' ? handle : {};
591
+ const handleVisible = handle !== false;
592
+ const surfaceColor = backgroundColor ?? theme.colors.surface;
593
+ const resolvedCornerRadius = cornerRadius ?? getDefaultCornerRadius();
594
+
595
+ const sheetNode = (
596
+ <TrueSheet
597
+ {...nativeProps}
598
+ ref={nativeRef}
599
+ testID={testID}
600
+ accessibilityLabel={accessibilityLabel}
601
+ detents={nativeDetents}
602
+ dimmed={backdropConfig.visible && !usesManualBackdrop}
603
+ dimmedDetentIndex={0}
604
+ dismissible={!disabled && dismissible}
605
+ draggable={!disabled && draggable}
606
+ backgroundColor={surfaceColor}
607
+ cornerRadius={resolvedCornerRadius}
608
+ grabber={handleVisible}
609
+ grabberOptions={
610
+ handleVisible
611
+ ? {
612
+ width: handleConfig.width ?? wp(36),
613
+ height: handleConfig.height ?? wp(4),
614
+ topMargin: handleConfig.topMargin ?? wp(10),
615
+ cornerRadius: handleConfig.radius ?? wp(2),
616
+ color: handleConfig.color ?? theme.colors.border,
617
+ }
618
+ : undefined
619
+ }
620
+ maxContentHeight={maxHeight}
621
+ maxContentWidth={maxWidth}
622
+ header={resolvedHeader}
623
+ headerStyle={headerStyle}
624
+ footer={resolvedFooter}
625
+ footerStyle={footerStyle}
626
+ style={style}
627
+ onDidPresent={handleDidPresent}
628
+ onDidDismiss={handleDidDismiss}
629
+ onDetentChange={handleDetentChange}
630
+ onBackPress={handleBackPress}
631
+ >
632
+ {resolvedChildren}
633
+ </TrueSheet>
634
+ );
635
+
636
+ if (!shellMounted) return null;
637
+
638
+ if (usesManualBackdrop) {
639
+ return (
640
+ <Modal
641
+ visible
642
+ transparent
643
+ animationType="none"
644
+ statusBarTranslucent
645
+ presentationStyle="overFullScreen"
646
+ onRequestClose={handleBackdropPress}
647
+ >
648
+ <View style={styles.modalRoot} pointerEvents="box-none">
649
+ {manualBackdropMounted ? (
650
+ <Pressable
651
+ accessibilityRole="button"
652
+ accessibilityLabel={backdropAccessibilityLabel}
653
+ disabled={disabled || !dismissible || !backdropConfig.dismissOnPress}
654
+ onPress={handleBackdropPress}
655
+ style={StyleSheet.absoluteFill}
656
+ >
657
+ <Animated.View
658
+ pointerEvents="none"
659
+ style={[
660
+ StyleSheet.absoluteFill,
661
+ {
662
+ backgroundColor: backdropConfig.color,
663
+ opacity: backdropOpacity,
664
+ },
665
+ ]}
666
+ />
667
+ </Pressable>
668
+ ) : null}
669
+ {sheetNode}
670
+ </View>
671
+ </Modal>
672
+ );
673
+ }
674
+
675
+ return sheetNode;
676
+ }
677
+ );
678
+
679
+ const styles = StyleSheet.create({
680
+ modalRoot: {
681
+ ...StyleSheet.absoluteFillObject,
682
+ },
683
+ });