@ankhorage/zora 4.5.0 → 6.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 (206) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +13 -10
  3. package/dist/components/button/resolveButtonThemeRecipe.d.ts +1 -1
  4. package/dist/components/date-picker/DatePicker.d.ts.map +1 -1
  5. package/dist/components/date-picker/DatePicker.js +92 -86
  6. package/dist/components/date-picker/DatePicker.js.map +1 -1
  7. package/dist/components/date-picker/meta.d.ts +1 -1
  8. package/dist/components/date-picker/meta.js +1 -1
  9. package/dist/components/date-picker/meta.js.map +1 -1
  10. package/dist/components/heading/resolveHeadingThemeRecipe.d.ts +1 -1
  11. package/dist/components/text/resolveTextThemeRecipe.d.ts +1 -1
  12. package/dist/components/time-picker/TimePicker.d.ts +1 -3
  13. package/dist/components/time-picker/TimePicker.d.ts.map +1 -1
  14. package/dist/components/time-picker/TimePicker.js +55 -32
  15. package/dist/components/time-picker/TimePicker.js.map +1 -1
  16. package/dist/components/time-picker/meta.d.ts +1 -1
  17. package/dist/components/time-picker/meta.js +1 -1
  18. package/dist/components/time-picker/meta.js.map +1 -1
  19. package/dist/features/bottom-sheet/public.d.ts +3 -0
  20. package/dist/features/bottom-sheet/public.d.ts.map +1 -0
  21. package/dist/features/bottom-sheet/public.js +2 -0
  22. package/dist/features/bottom-sheet/public.js.map +1 -0
  23. package/dist/index.d.ts +0 -14
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +0 -7
  26. package/dist/index.js.map +1 -1
  27. package/dist/metadata/componentMeta.d.ts.map +1 -1
  28. package/dist/metadata/componentMeta.js +0 -15
  29. package/dist/metadata/componentMeta.js.map +1 -1
  30. package/dist/registry.d.ts.map +1 -1
  31. package/dist/registry.js +0 -15
  32. package/dist/registry.js.map +1 -1
  33. package/dist/theme/ZoraProvider.d.ts +1 -1
  34. package/dist/theme/ZoraProvider.d.ts.map +1 -1
  35. package/dist/theme/ZoraProvider.js +3 -2
  36. package/dist/theme/ZoraProvider.js.map +1 -1
  37. package/dist/theme/useZoraTheme.d.ts +1 -1
  38. package/package.json +16 -3
  39. package/src/audit.test.ts +0 -29
  40. package/src/components/date-picker/DatePicker.tsx +197 -129
  41. package/src/components/date-picker/meta.ts +1 -1
  42. package/src/components/time-picker/TimePicker.tsx +147 -60
  43. package/src/components/time-picker/meta.ts +1 -1
  44. package/src/expo57Migration.test.ts +56 -0
  45. package/src/features/bottom-sheet/public.test.ts +39 -0
  46. package/src/features/bottom-sheet/public.ts +5 -0
  47. package/src/index.ts +0 -25
  48. package/src/interactionPolicy.test.ts +0 -17
  49. package/src/metadata/componentMeta.ts +0 -15
  50. package/src/platformAcceptance.test.ts +1 -1
  51. package/src/registry.ts +0 -15
  52. package/src/showcaseCoverage.test.ts +0 -6
  53. package/src/theme/ZoraProvider.tsx +3 -2
  54. package/src/theme/themeScopeStructure.test.ts +0 -4
  55. package/dist/components/action-sheet/ActionSheet.d.ts +0 -7
  56. package/dist/components/action-sheet/ActionSheet.d.ts.map +0 -1
  57. package/dist/components/action-sheet/ActionSheet.js +0 -11
  58. package/dist/components/action-sheet/ActionSheet.js.map +0 -1
  59. package/dist/components/action-sheet/ActionSheetItem.d.ts +0 -7
  60. package/dist/components/action-sheet/ActionSheetItem.d.ts.map +0 -1
  61. package/dist/components/action-sheet/ActionSheetItem.js +0 -17
  62. package/dist/components/action-sheet/ActionSheetItem.js.map +0 -1
  63. package/dist/components/action-sheet/index.d.ts +0 -4
  64. package/dist/components/action-sheet/index.d.ts.map +0 -1
  65. package/dist/components/action-sheet/index.js +0 -3
  66. package/dist/components/action-sheet/index.js.map +0 -1
  67. package/dist/components/action-sheet/meta.d.ts +0 -17
  68. package/dist/components/action-sheet/meta.d.ts.map +0 -1
  69. package/dist/components/action-sheet/meta.js +0 -17
  70. package/dist/components/action-sheet/meta.js.map +0 -1
  71. package/dist/components/action-sheet/types.d.ts +0 -13
  72. package/dist/components/action-sheet/types.d.ts.map +0 -1
  73. package/dist/components/action-sheet/types.js +0 -2
  74. package/dist/components/action-sheet/types.js.map +0 -1
  75. package/dist/components/drawer/Drawer.d.ts +0 -7
  76. package/dist/components/drawer/Drawer.d.ts.map +0 -1
  77. package/dist/components/drawer/Drawer.js +0 -36
  78. package/dist/components/drawer/Drawer.js.map +0 -1
  79. package/dist/components/drawer/index.d.ts +0 -3
  80. package/dist/components/drawer/index.d.ts.map +0 -1
  81. package/dist/components/drawer/index.js +0 -2
  82. package/dist/components/drawer/index.js.map +0 -1
  83. package/dist/components/drawer/meta.d.ts +0 -9
  84. package/dist/components/drawer/meta.d.ts.map +0 -1
  85. package/dist/components/drawer/meta.js +0 -9
  86. package/dist/components/drawer/meta.js.map +0 -1
  87. package/dist/components/drawer/types.d.ts +0 -10
  88. package/dist/components/drawer/types.d.ts.map +0 -1
  89. package/dist/components/drawer/types.js +0 -2
  90. package/dist/components/drawer/types.js.map +0 -1
  91. package/dist/components/navigation-item/NavigationItem.d.ts +0 -7
  92. package/dist/components/navigation-item/NavigationItem.d.ts.map +0 -1
  93. package/dist/components/navigation-item/NavigationItem.js +0 -21
  94. package/dist/components/navigation-item/NavigationItem.js.map +0 -1
  95. package/dist/components/navigation-item/index.d.ts +0 -3
  96. package/dist/components/navigation-item/index.d.ts.map +0 -1
  97. package/dist/components/navigation-item/index.js +0 -2
  98. package/dist/components/navigation-item/index.js.map +0 -1
  99. package/dist/components/navigation-item/meta.d.ts +0 -9
  100. package/dist/components/navigation-item/meta.d.ts.map +0 -1
  101. package/dist/components/navigation-item/meta.js +0 -9
  102. package/dist/components/navigation-item/meta.js.map +0 -1
  103. package/dist/components/navigation-item/types.d.ts +0 -23
  104. package/dist/components/navigation-item/types.d.ts.map +0 -1
  105. package/dist/components/navigation-item/types.js +0 -2
  106. package/dist/components/navigation-item/types.js.map +0 -1
  107. package/dist/components/navigation-list/NavigationList.d.ts +0 -7
  108. package/dist/components/navigation-list/NavigationList.d.ts.map +0 -1
  109. package/dist/components/navigation-list/NavigationList.js +0 -29
  110. package/dist/components/navigation-list/NavigationList.js.map +0 -1
  111. package/dist/components/navigation-list/index.d.ts +0 -3
  112. package/dist/components/navigation-list/index.d.ts.map +0 -1
  113. package/dist/components/navigation-list/index.js +0 -2
  114. package/dist/components/navigation-list/index.js.map +0 -1
  115. package/dist/components/navigation-list/meta.d.ts +0 -9
  116. package/dist/components/navigation-list/meta.d.ts.map +0 -1
  117. package/dist/components/navigation-list/meta.js +0 -9
  118. package/dist/components/navigation-list/meta.js.map +0 -1
  119. package/dist/components/navigation-list/types.d.ts +0 -15
  120. package/dist/components/navigation-list/types.d.ts.map +0 -1
  121. package/dist/components/navigation-list/types.js +0 -2
  122. package/dist/components/navigation-list/types.js.map +0 -1
  123. package/dist/internal/resolveZoraNavigationItems.d.ts +0 -65
  124. package/dist/internal/resolveZoraNavigationItems.d.ts.map +0 -1
  125. package/dist/internal/resolveZoraNavigationItems.js +0 -56
  126. package/dist/internal/resolveZoraNavigationItems.js.map +0 -1
  127. package/dist/patterns/responsive-panel/ResponsivePanel.d.ts +0 -16
  128. package/dist/patterns/responsive-panel/ResponsivePanel.d.ts.map +0 -1
  129. package/dist/patterns/responsive-panel/ResponsivePanel.js +0 -101
  130. package/dist/patterns/responsive-panel/ResponsivePanel.js.map +0 -1
  131. package/dist/patterns/responsive-panel/index.d.ts +0 -3
  132. package/dist/patterns/responsive-panel/index.d.ts.map +0 -1
  133. package/dist/patterns/responsive-panel/index.js +0 -3
  134. package/dist/patterns/responsive-panel/index.js.map +0 -1
  135. package/dist/patterns/responsive-panel/meta.d.ts +0 -9
  136. package/dist/patterns/responsive-panel/meta.d.ts.map +0 -1
  137. package/dist/patterns/responsive-panel/meta.js +0 -9
  138. package/dist/patterns/responsive-panel/meta.js.map +0 -1
  139. package/dist/patterns/responsive-panel/types.d.ts +0 -23
  140. package/dist/patterns/responsive-panel/types.d.ts.map +0 -1
  141. package/dist/patterns/responsive-panel/types.js +0 -2
  142. package/dist/patterns/responsive-panel/types.js.map +0 -1
  143. package/dist/patterns/zora-drawer-content/ZoraDrawerContent.d.ts +0 -9
  144. package/dist/patterns/zora-drawer-content/ZoraDrawerContent.d.ts.map +0 -1
  145. package/dist/patterns/zora-drawer-content/ZoraDrawerContent.js +0 -31
  146. package/dist/patterns/zora-drawer-content/ZoraDrawerContent.js.map +0 -1
  147. package/dist/patterns/zora-drawer-content/index.d.ts +0 -3
  148. package/dist/patterns/zora-drawer-content/index.d.ts.map +0 -1
  149. package/dist/patterns/zora-drawer-content/index.js +0 -2
  150. package/dist/patterns/zora-drawer-content/index.js.map +0 -1
  151. package/dist/patterns/zora-drawer-content/meta.d.ts +0 -9
  152. package/dist/patterns/zora-drawer-content/meta.d.ts.map +0 -1
  153. package/dist/patterns/zora-drawer-content/meta.js +0 -9
  154. package/dist/patterns/zora-drawer-content/meta.js.map +0 -1
  155. package/dist/patterns/zora-drawer-content/types.d.ts +0 -15
  156. package/dist/patterns/zora-drawer-content/types.d.ts.map +0 -1
  157. package/dist/patterns/zora-drawer-content/types.js +0 -2
  158. package/dist/patterns/zora-drawer-content/types.js.map +0 -1
  159. package/dist/patterns/zora-tab-bar/ZoraTabBar.d.ts +0 -9
  160. package/dist/patterns/zora-tab-bar/ZoraTabBar.d.ts.map +0 -1
  161. package/dist/patterns/zora-tab-bar/ZoraTabBar.js +0 -89
  162. package/dist/patterns/zora-tab-bar/ZoraTabBar.js.map +0 -1
  163. package/dist/patterns/zora-tab-bar/index.d.ts +0 -3
  164. package/dist/patterns/zora-tab-bar/index.d.ts.map +0 -1
  165. package/dist/patterns/zora-tab-bar/index.js +0 -2
  166. package/dist/patterns/zora-tab-bar/index.js.map +0 -1
  167. package/dist/patterns/zora-tab-bar/meta.d.ts +0 -9
  168. package/dist/patterns/zora-tab-bar/meta.d.ts.map +0 -1
  169. package/dist/patterns/zora-tab-bar/meta.js +0 -9
  170. package/dist/patterns/zora-tab-bar/meta.js.map +0 -1
  171. package/dist/patterns/zora-tab-bar/types.d.ts +0 -19
  172. package/dist/patterns/zora-tab-bar/types.d.ts.map +0 -1
  173. package/dist/patterns/zora-tab-bar/types.js +0 -2
  174. package/dist/patterns/zora-tab-bar/types.js.map +0 -1
  175. package/src/components/action-sheet/ActionSheet.tsx +0 -14
  176. package/src/components/action-sheet/ActionSheetItem.tsx +0 -30
  177. package/src/components/action-sheet/index.ts +0 -3
  178. package/src/components/action-sheet/meta.ts +0 -19
  179. package/src/components/action-sheet/types.ts +0 -23
  180. package/src/components/drawer/Drawer.tsx +0 -59
  181. package/src/components/drawer/index.ts +0 -2
  182. package/src/components/drawer/meta.ts +0 -10
  183. package/src/components/drawer/types.ts +0 -17
  184. package/src/components/navigation-item/NavigationItem.tsx +0 -41
  185. package/src/components/navigation-item/index.ts +0 -6
  186. package/src/components/navigation-item/meta.ts +0 -10
  187. package/src/components/navigation-item/types.ts +0 -26
  188. package/src/components/navigation-list/NavigationList.tsx +0 -68
  189. package/src/components/navigation-list/index.ts +0 -2
  190. package/src/components/navigation-list/meta.ts +0 -10
  191. package/src/components/navigation-list/types.ts +0 -17
  192. package/src/internal/resolveZoraNavigationItems.test.ts +0 -174
  193. package/src/internal/resolveZoraNavigationItems.ts +0 -157
  194. package/src/navigationExports.test.ts +0 -15
  195. package/src/patterns/responsive-panel/ResponsivePanel.tsx +0 -172
  196. package/src/patterns/responsive-panel/index.ts +0 -2
  197. package/src/patterns/responsive-panel/meta.ts +0 -10
  198. package/src/patterns/responsive-panel/types.ts +0 -25
  199. package/src/patterns/zora-drawer-content/ZoraDrawerContent.tsx +0 -60
  200. package/src/patterns/zora-drawer-content/index.ts +0 -2
  201. package/src/patterns/zora-drawer-content/meta.ts +0 -10
  202. package/src/patterns/zora-drawer-content/types.ts +0 -20
  203. package/src/patterns/zora-tab-bar/ZoraTabBar.tsx +0 -157
  204. package/src/patterns/zora-tab-bar/index.ts +0 -2
  205. package/src/patterns/zora-tab-bar/meta.ts +0 -10
  206. package/src/patterns/zora-tab-bar/types.ts +0 -18
@@ -1,10 +1,10 @@
1
1
  import { Field } from '@ankhorage/surface';
2
2
  import React from 'react';
3
- import { ScrollView } from 'react-native';
3
+ import { ScrollView, StyleSheet } from 'react-native';
4
4
 
5
+ import { useBottomSheet } from '../../features/bottom-sheet/public';
5
6
  import { Stack } from '../../foundation';
6
7
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
7
- import { ActionSheet, ActionSheetItem } from '../action-sheet';
8
8
  import { Button } from '../button';
9
9
  import { Text } from '../text';
10
10
  import type { TimePickerProps } from './types';
@@ -12,6 +12,7 @@ import type { TimePickerProps } from './types';
12
12
  const MINUTES_PER_DAY = 24 * 60;
13
13
  const DEFAULT_STEP_MINUTES = 30;
14
14
 
15
+ /*** Render the field label and optional supporting description. */
15
16
  function renderLabel(label: React.ReactNode, description: React.ReactNode | undefined) {
16
17
  return (
17
18
  <Stack gap="xs">
@@ -27,39 +28,34 @@ function renderLabel(label: React.ReactNode, description: React.ReactNode | unde
27
28
  );
28
29
  }
29
30
 
31
+ /*** Parse one HH:mm value to minutes since midnight. */
30
32
  function parseTimeToMinutes(value: string | undefined): number | undefined {
31
- if (!value) {
32
- return undefined;
33
- }
34
-
33
+ if (!value) return undefined;
35
34
  const match = /^(\d{2}):(\d{2})$/.exec(value);
36
- if (!match) {
37
- return undefined;
38
- }
35
+ if (!match) return undefined;
39
36
 
40
37
  const hours = Number(match[1]);
41
38
  const minutes = Number(match[2]);
42
- if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
43
- return undefined;
44
- }
45
-
39
+ if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return undefined;
46
40
  return hours * 60 + minutes;
47
41
  }
48
42
 
43
+ /*** Format minutes since midnight as HH:mm. */
49
44
  function formatMinutes(value: number): string {
50
45
  const hours = Math.floor(value / 60);
51
46
  const minutes = value % 60;
52
47
  return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
53
48
  }
54
49
 
50
+ /*** Normalize the configured time step to a positive whole minute. */
55
51
  function resolveStepMinutes(stepMinutes: number | undefined): number {
56
52
  if (stepMinutes === undefined || !Number.isFinite(stepMinutes) || stepMinutes <= 0) {
57
53
  return DEFAULT_STEP_MINUTES;
58
54
  }
59
-
60
55
  return Math.max(1, Math.floor(stepMinutes));
61
56
  }
62
57
 
58
+ /*** Generate selectable HH:mm values within the configured range. */
63
59
  function generateTimeOptions({
64
60
  minTime,
65
61
  maxTime,
@@ -71,11 +67,8 @@ function generateTimeOptions({
71
67
  const options: string[] = [];
72
68
 
73
69
  for (let minute = 0; minute < MINUTES_PER_DAY; minute += step) {
74
- if (minute >= min && minute <= max) {
75
- options.push(formatMinutes(minute));
76
- }
70
+ if (minute >= min && minute <= max) options.push(formatMinutes(minute));
77
71
  }
78
-
79
72
  return options;
80
73
  }
81
74
 
@@ -97,18 +90,33 @@ function TimePickerInner({
97
90
  testID,
98
91
  interactionPolicy,
99
92
  }: TimePickerProps) {
93
+ const { dismiss, present } = useBottomSheet();
100
94
  const passive = interactionPolicy === 'passive';
101
- const [visible, setVisible] = React.useState(false);
102
- const options = React.useMemo(
103
- () => generateTimeOptions({ maxTime, minTime, stepMinutes }),
104
- [maxTime, minTime, stepMinutes],
105
- );
106
95
  const displayValue = value ? (formatTime ? formatTime(value) : value) : placeholder;
107
96
 
97
+ /*** Present the time options through the shared BottomSheet controller. */
108
98
  function openPicker() {
109
99
  if (passive) return;
110
-
111
- setVisible(true);
100
+ present({
101
+ content: (
102
+ <TimePickerSheet
103
+ description={description}
104
+ formatTime={formatTime}
105
+ interactionPolicy={interactionPolicy}
106
+ label={label}
107
+ maxTime={maxTime}
108
+ minTime={minTime}
109
+ onDismiss={dismiss}
110
+ onSelect={(time) => {
111
+ onValueChange?.(time);
112
+ dismiss();
113
+ }}
114
+ stepMinutes={stepMinutes}
115
+ testID={testID}
116
+ value={value}
117
+ />
118
+ ),
119
+ });
112
120
  }
113
121
 
114
122
  return (
@@ -130,45 +138,124 @@ function TimePickerInner({
130
138
  >
131
139
  {displayValue}
132
140
  </Button>
133
- <ActionSheet
134
- description={description}
135
- interactionPolicy={interactionPolicy}
136
- onDismiss={() => {
137
- if (passive) return;
138
-
139
- setVisible(false);
140
- }}
141
- testID={testID ? `${testID}-sheet` : undefined}
142
- title={label ?? 'Choose time'}
143
- visible={visible}
144
- >
145
- <ScrollView style={{ maxHeight: 360 }}>
146
- <Stack gap="xxs">
147
- {options.map((option) => (
148
- <ActionSheetItem
149
- interactionPolicy={interactionPolicy}
150
- key={option}
151
- label={formatTime ? formatTime(option) : option}
152
- onPress={() => {
153
- if (passive) return;
154
-
155
- onValueChange?.(option);
156
- setVisible(false);
157
- }}
158
- selected={option === value}
159
- testID={testID ? `${testID}-option-${option}` : undefined}
160
- />
161
- ))}
162
- </Stack>
163
- </ScrollView>
164
- </ActionSheet>
165
141
  </Field>
166
142
  );
167
143
  }
168
144
 
145
+ interface TimePickerSheetProps extends Pick<
146
+ TimePickerProps,
147
+ | 'description'
148
+ | 'formatTime'
149
+ | 'interactionPolicy'
150
+ | 'label'
151
+ | 'maxTime'
152
+ | 'minTime'
153
+ | 'stepMinutes'
154
+ | 'testID'
155
+ | 'value'
156
+ > {
157
+ onDismiss: () => void;
158
+ onSelect: (value: string) => void;
159
+ }
160
+
161
+ /*** Render time selection content inside the shared BottomSheet host. */
162
+ function TimePickerSheet({
163
+ description,
164
+ formatTime,
165
+ interactionPolicy,
166
+ label,
167
+ maxTime,
168
+ minTime,
169
+ onDismiss,
170
+ onSelect,
171
+ stepMinutes,
172
+ testID,
173
+ value,
174
+ }: TimePickerSheetProps) {
175
+ const options = React.useMemo(
176
+ () => generateTimeOptions({ maxTime, minTime, stepMinutes }),
177
+ [maxTime, minTime, stepMinutes],
178
+ );
179
+
180
+ return (
181
+ <Stack gap="m" p="m" testID={testID ? `${testID}-sheet` : undefined}>
182
+ <PickerSheetHeader description={description} title={label ?? 'Choose time'} />
183
+ <ScrollView style={styles.options}>
184
+ <Stack gap="xxs">
185
+ {options.map((option) => (
186
+ <TimePickerOption
187
+ formatTime={formatTime}
188
+ interactionPolicy={interactionPolicy}
189
+ key={option}
190
+ onSelect={onSelect}
191
+ option={option}
192
+ selected={option === value}
193
+ testID={testID}
194
+ />
195
+ ))}
196
+ </Stack>
197
+ </ScrollView>
198
+ <Button fullWidth interactionPolicy={interactionPolicy} onPress={onDismiss} variant="ghost">
199
+ Cancel
200
+ </Button>
201
+ </Stack>
202
+ );
203
+ }
204
+
205
+ /*** Render the shared title block used by the time sheet. */
206
+ function PickerSheetHeader({
207
+ description,
208
+ title,
209
+ }: {
210
+ description: React.ReactNode | undefined;
211
+ title: React.ReactNode;
212
+ }) {
213
+ return (
214
+ <Stack gap="xxs">
215
+ <Text align="center" variant="label" weight="semiBold">
216
+ {title}
217
+ </Text>
218
+ {description ? (
219
+ <Text align="center" emphasis="muted" variant="bodySmall">
220
+ {description}
221
+ </Text>
222
+ ) : null}
223
+ </Stack>
224
+ );
225
+ }
226
+
227
+ /*** Render one accessible selectable time option. */
228
+ function TimePickerOption({
229
+ formatTime,
230
+ interactionPolicy,
231
+ onSelect,
232
+ option,
233
+ selected,
234
+ testID,
235
+ }: Pick<TimePickerSheetProps, 'formatTime' | 'interactionPolicy' | 'testID'> & {
236
+ onSelect: (value: string) => void;
237
+ option: string;
238
+ selected: boolean;
239
+ }) {
240
+ return (
241
+ <Button
242
+ accessibilityState={{ selected }}
243
+ fullWidth
244
+ interactionPolicy={interactionPolicy}
245
+ onPress={() => onSelect(option)}
246
+ testID={testID ? `${testID}-option-${option}` : undefined}
247
+ variant={selected ? 'soft' : 'ghost'}
248
+ >
249
+ {formatTime ? formatTime(option) : option}
250
+ </Button>
251
+ );
252
+ }
253
+
254
+ const styles = StyleSheet.create({
255
+ options: { maxHeight: 360 },
256
+ });
257
+
169
258
  /***
170
- * Time input control with wheel selection and formatted display value.
171
- *
172
-
259
+ * Time input control with bottom-sheet selection and formatted display value.
173
260
  */
174
261
  export const TimePicker = withZoraThemeScope(TimePickerInner);
@@ -5,6 +5,6 @@ export const timePickerMeta = {
5
5
  category: 'component',
6
6
  directManifestNode: false,
7
7
  allowedChildren: [],
8
- note: 'Code-facing ActionSheet-backed time picker; not represented as a manifest node in v1.',
8
+ note: 'Code-facing BottomSheet-backed time picker; not represented as a manifest node in v1.',
9
9
  props: {},
10
10
  } as const satisfies ZoraComponentMeta;
@@ -14,6 +14,14 @@ const EXPO_EXAMPLE_DIRS = [
14
14
  'examples/social_community/private-messaging',
15
15
  'examples/social_community/visual-discovery',
16
16
  ] as const;
17
+ const ROUTER_EXAMPLE_DIRS = EXPO_EXAMPLE_DIRS.filter(
18
+ (directory) => directory !== 'examples/expo-showcase',
19
+ );
20
+ const ZORA_PROVIDER_HOSTS = [
21
+ ['examples/basic-app', 'App.tsx'],
22
+ ['examples/expo-showcase', 'App.tsx'],
23
+ ...ROUTER_EXAMPLE_DIRS.map((directory) => [directory, 'app', '_layout.tsx'] as const),
24
+ ] as const;
17
25
  const RNVI_PLUGINS = [
18
26
  '@react-native-vector-icons/fontawesome',
19
27
  '@react-native-vector-icons/fontawesome5',
@@ -63,6 +71,9 @@ describe('portable ZORA package boundary', () => {
63
71
  expect(readValue(peers, 'react-native-svg')).toBe('15.15.4');
64
72
  expect(readValue(development, 'react-native-svg')).toBe('15.15.4');
65
73
  expect(readValue(peers, 'react-native-web')).toMatch(TILDE_SEMVER_RANGE);
74
+ expect(readValue(peers, 'react-native-gesture-handler')).toMatch(TILDE_SEMVER_RANGE);
75
+ expect(readValue(peers, 'react-native-reanimated')).toMatch(EXACT_SEMVER_VERSION);
76
+ expect(readValue(peers, 'react-native-worklets')).toMatch(EXACT_SEMVER_VERSION);
66
77
  expect(development.typescript).toMatch(TILDE_SEMVER_RANGE);
67
78
 
68
79
  for (const expoPackage of ['@expo/vector-icons', 'expo-font', 'expo-linear-gradient']) {
@@ -101,6 +112,9 @@ describe('Expo 57 example boundary', () => {
101
112
  expect(expoVersion).toMatch(EXACT_SEMVER_VERSION);
102
113
  expect(dependencies.react).toMatch(EXACT_SEMVER_VERSION);
103
114
  expect(readValue(dependencies, 'react-native')).toMatch(EXACT_SEMVER_VERSION);
115
+ expect(readValue(dependencies, 'react-native-gesture-handler')).toMatch(TILDE_SEMVER_RANGE);
116
+ expect(readValue(dependencies, 'react-native-reanimated')).toMatch(EXACT_SEMVER_VERSION);
117
+ expect(readValue(dependencies, 'react-native-worklets')).toMatch(EXACT_SEMVER_VERSION);
104
118
  expect(readValue(dependencies, 'react-native-web')).toMatch(TILDE_SEMVER_RANGE);
105
119
  expect(development.typescript).toMatch(TILDE_SEMVER_RANGE);
106
120
  expect(readValue(dependencies, '@expo/vector-icons')).toBeUndefined();
@@ -117,6 +131,48 @@ describe('Expo 57 example boundary', () => {
117
131
  'utf8',
118
132
  );
119
133
  expect(scaffoldSource).toContain(`expo: '${expoVersion}',`);
134
+ expect(scaffoldSource).toContain("navigator: '^3.2.3'");
135
+ expect(scaffoldSource).toContain("from 'expo-router/unstable-native-tabs'");
136
+ expect(scaffoldSource).toContain('<GestureHandlerRootView style={{ flex: 1 }}>');
137
+ expect(scaffoldSource).not.toContain('ZoraTabBar');
138
+ });
139
+
140
+ test('uses Navigator native tabs in every router example', () => {
141
+ for (const directory of ROUTER_EXAMPLE_DIRS) {
142
+ const packageJson = readExamplePackage(directory);
143
+ const dependencies = readRecord(packageJson, 'dependencies');
144
+ const layoutSource = readFileSync(
145
+ join(ROOT, directory, 'app', '(tabs)', '_layout.tsx'),
146
+ 'utf8',
147
+ );
148
+
149
+ expect(readValue(dependencies, '@ankhorage/navigator')).toMatch(CARET_SEMVER_RANGE);
150
+ expect(layoutSource).toContain("from '@ankhorage/navigator/tabs/native-icons'");
151
+ expect(layoutSource).toContain("from 'expo-router/unstable-native-tabs'");
152
+ expect(layoutSource).toContain("Platform.OS === 'web'");
153
+ expect(layoutSource).toContain('Promise.resolve(null)');
154
+ expect(layoutSource).toContain('<NativeTabs>');
155
+ expect(layoutSource).not.toContain('ZoraTabBar');
156
+ expect(layoutSource).not.toContain('ZoraNavigationRouteMap');
157
+ }
158
+ });
159
+
160
+ test('keeps GestureHandlerRootView outside ZoraProvider in every host app', () => {
161
+ for (const sourcePath of ZORA_PROVIDER_HOSTS) {
162
+ const [directory] = sourcePath;
163
+ const source = readFileSync(join(ROOT, ...sourcePath), 'utf8');
164
+ const packageJson = readExamplePackage(directory);
165
+ const dependencies = readRecord(packageJson, 'dependencies');
166
+ const gestureRootPosition = source.indexOf('<GestureHandlerRootView');
167
+ const zoraProviderPosition = source.indexOf('<ZoraProvider');
168
+
169
+ expect(readValue(dependencies, 'react-native-gesture-handler')).toMatch(TILDE_SEMVER_RANGE);
170
+ expect(readValue(dependencies, 'react-native-reanimated')).toMatch(EXACT_SEMVER_VERSION);
171
+ expect(readValue(dependencies, 'react-native-worklets')).toMatch(EXACT_SEMVER_VERSION);
172
+ expect(source).toContain("from 'react-native-gesture-handler'");
173
+ expect(gestureRootPosition).toBeGreaterThan(-1);
174
+ expect(zoraProviderPosition).toBeGreaterThan(gestureRootPosition);
175
+ }
120
176
  });
121
177
 
122
178
  test('registers all scoped RNVI packages in each Expo app config', () => {
@@ -0,0 +1,39 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ describe('ZORA BottomSheet public contract', () => {
4
+ test('publishes an explicit ZORA subpath backed by Surface', async () => {
5
+ const packageJson = (await Bun.file('package.json').json()) as {
6
+ dependencies: Readonly<Record<string, string>>;
7
+ exports: Readonly<Record<string, unknown>>;
8
+ peerDependencies: Readonly<Record<string, string>>;
9
+ };
10
+ const source = await Bun.file('src/features/bottom-sheet/public.ts').text();
11
+
12
+ expect(packageJson.exports['./bottom-sheet']).toBeDefined();
13
+ expect(packageJson.dependencies['@ankhorage/surface']).toMatch(/^\^4\./u);
14
+ expect(source).toContain("from '@ankhorage/surface/bottom-sheet'");
15
+ const {
16
+ 'react-native-gesture-handler': gestureHandlerPeer,
17
+ 'react-native-reanimated': reanimatedPeer,
18
+ 'react-native-worklets': workletsPeer,
19
+ } = packageJson.peerDependencies;
20
+ expect(gestureHandlerPeer).toBeDefined();
21
+ expect(reanimatedPeer).toBeDefined();
22
+ expect(workletsPeer).toBeDefined();
23
+ });
24
+
25
+ test('installs the shared provider and removes picker ActionSheet dependencies', async () => {
26
+ const [provider, datePicker, timePicker] = await Promise.all([
27
+ Bun.file('src/theme/ZoraProvider.tsx').text(),
28
+ Bun.file('src/components/date-picker/DatePicker.tsx').text(),
29
+ Bun.file('src/components/time-picker/TimePicker.tsx').text(),
30
+ ]);
31
+
32
+ expect(provider).toContain('<BottomSheetProvider>{children}</BottomSheetProvider>');
33
+ expect(provider).not.toContain('GestureHandlerRootView');
34
+ expect(datePicker).toContain('useBottomSheet()');
35
+ expect(timePicker).toContain('useBottomSheet()');
36
+ expect(datePicker).not.toContain("from '../action-sheet'");
37
+ expect(timePicker).not.toContain("from '../action-sheet'");
38
+ });
39
+ });
@@ -0,0 +1,5 @@
1
+ export type {
2
+ BottomSheetController,
3
+ BottomSheetPresentOptions,
4
+ } from '@ankhorage/surface/bottom-sheet';
5
+ export { useBottomSheet } from '@ankhorage/surface/bottom-sheet';
package/src/index.ts CHANGED
@@ -1,5 +1,3 @@
1
- export type { ActionSheetItemProps, ActionSheetProps } from './components/action-sheet';
2
- export { ActionSheet, ActionSheetItem } from './components/action-sheet';
3
1
  export type { AppBarMode, AppBarOverflowAction, AppBarProps } from './components/app-bar';
4
2
  export { AppBar } from './components/app-bar';
5
3
  export type { AvatarProps, AvatarShape, AvatarSize } from './components/avatar';
@@ -39,8 +37,6 @@ export type {
39
37
  export { DataTable } from './components/data-table';
40
38
  export type { DatePickerProps, DatePickerValue } from './components/date-picker';
41
39
  export { DatePicker } from './components/date-picker';
42
- export type { DrawerProps } from './components/drawer';
43
- export { Drawer } from './components/drawer';
44
40
  export type {
45
41
  FormActionsProps,
46
42
  FormErrorProps,
@@ -105,14 +101,6 @@ export type { MetricCardProps } from './components/metric-card';
105
101
  export { MetricCard } from './components/metric-card';
106
102
  export type { ModalProps } from './components/modal';
107
103
  export { Modal } from './components/modal';
108
- export type {
109
- NavigationItemProps,
110
- ZoraNavigationRouteMetadata,
111
- ZoraNavigationRouteState,
112
- } from './components/navigation-item';
113
- export { NavigationItem } from './components/navigation-item';
114
- export type { NavigationListProps, ZoraNavigationRouteMap } from './components/navigation-list';
115
- export { NavigationList } from './components/navigation-list';
116
104
  export type { PaginationProps } from './components/pagination';
117
105
  export { Pagination } from './components/pagination';
118
106
  export type { ProgressProps, ProgressRingProps } from './components/progress';
@@ -356,15 +344,6 @@ export type {
356
344
  ReaderSurfaceProps,
357
345
  } from './patterns/reader';
358
346
  export { ReaderSurface, resolveReaderProgress } from './patterns/reader';
359
- export type {
360
- ResponsivePanelDesktopMode,
361
- ResponsivePanelMobileMode,
362
- ResponsivePanelProps,
363
- ResponsivePanelScroll,
364
- ResponsivePanelSide,
365
- ResponsivePanelSize,
366
- } from './patterns/responsive-panel';
367
- export { ResponsivePanel } from './patterns/responsive-panel';
368
347
  export type {
369
348
  BarcodeScannerViewProps,
370
349
  BarcodeScanResult,
@@ -396,10 +375,6 @@ export type { TimelineItem, TimelineProps } from './patterns/timeline';
396
375
  export { Timeline } from './patterns/timeline';
397
376
  export type { TreeItemNode, TreeItemRenderProps, TreeViewProps } from './patterns/tree-view';
398
377
  export { TreeItem, TreeView } from './patterns/tree-view';
399
- export type { ZoraDrawerContentProps } from './patterns/zora-drawer-content';
400
- export { ZoraDrawerContent } from './patterns/zora-drawer-content';
401
- export type { ZoraTabBarProps } from './patterns/zora-tab-bar';
402
- export { ZoraTabBar } from './patterns/zora-tab-bar';
403
378
  export type {
404
379
  ComposedZoraPluginCatalog,
405
380
  ComposedZoraPluginMetadataCatalog,
@@ -44,7 +44,6 @@ describe('InteractionPolicy declaration', () => {
44
44
  readPattern('product-card', 'types.ts'),
45
45
  readPattern('reader', 'types.ts'),
46
46
  readPattern('scanner', 'types.ts'),
47
- readPattern('zora-tab-bar', 'types.ts'),
48
47
  readComponent('select', 'types.ts'),
49
48
  readPattern('tree-view', 'types.ts'),
50
49
  ];
@@ -229,22 +228,6 @@ describe('BarcodeScannerView', () => {
229
228
  });
230
229
  });
231
230
 
232
- describe('ZoraTabBar', () => {
233
- test('does not discard interactionPolicy', () => {
234
- const source = readPattern('zora-tab-bar', 'ZoraTabBar.tsx');
235
-
236
- expect(source).not.toMatch(/interactionPolicy:\s*_interactionPolicy/);
237
- });
238
-
239
- test('prevents navigation under passive without disabled semantics', () => {
240
- const source = readPattern('zora-tab-bar', 'ZoraTabBar.tsx');
241
-
242
- expect(source).toMatch(/onPress=\{[\s\S]*?interactionPolicy === 'passive'\s*\?\s*undefined/);
243
- expect(source).not.toMatch(/disabled:\s*interactionPolicy === 'passive'/);
244
- expect(source).not.toMatch(/accessibilityState.*disabled.*interactionPolicy === 'passive'/);
245
- });
246
- });
247
-
248
231
  describe('Select', () => {
249
232
  test('keeps Picker enabled controlled only by real disabled prop', () => {
250
233
  const source = readComponent('select', 'Select.tsx');
@@ -1,4 +1,3 @@
1
- import { actionSheetItemMeta, actionSheetMeta } from '../components/action-sheet/meta';
2
1
  import { appBarMeta } from '../components/app-bar/meta';
3
2
  import { avatarMeta } from '../components/avatar/meta';
4
3
  import { avatarGroupMeta } from '../components/avatar-group/meta';
@@ -12,7 +11,6 @@ import { chipMeta } from '../components/chip/meta';
12
11
  import { chipGroupMeta } from '../components/chip-group/meta';
13
12
  import { dataTableMeta } from '../components/data-table/meta';
14
13
  import { datePickerMeta } from '../components/date-picker/meta';
15
- import { drawerMeta } from '../components/drawer/meta';
16
14
  import { formActionsMeta, formErrorMeta, formFieldMeta, formMeta } from '../components/form/meta';
17
15
  import { gradientMeta } from '../components/gradient/meta';
18
16
  import { headingMeta } from '../components/heading/meta';
@@ -24,8 +22,6 @@ import { mediaCardMeta } from '../components/media-card/meta';
24
22
  import { dropdownMenuMeta, menuMeta } from '../components/menu/meta';
25
23
  import { metricCardMeta } from '../components/metric-card/meta';
26
24
  import { modalMeta } from '../components/modal/meta';
27
- import { navigationItemMeta } from '../components/navigation-item/meta';
28
- import { navigationListMeta } from '../components/navigation-list/meta';
29
25
  import { paginationMeta } from '../components/pagination/meta';
30
26
  import { progressMeta, progressRingMeta } from '../components/progress/meta';
31
27
  import { radioGroupMeta, radioMeta } from '../components/radio/meta';
@@ -79,7 +75,6 @@ import { panelMeta } from '../patterns/panel/meta';
79
75
  import { postCardMeta } from '../patterns/post-card/meta';
80
76
  import { productCardMeta } from '../patterns/product-card/meta';
81
77
  import { readerSurfaceMeta } from '../patterns/reader/meta';
82
- import { responsivePanelMeta } from '../patterns/responsive-panel/meta';
83
78
  import {
84
79
  barcodeScannerViewMeta,
85
80
  cameraPermissionViewMeta,
@@ -93,15 +88,11 @@ import { themeComposerMeta } from '../patterns/theme-composer/meta';
93
88
  import { paletteItemMeta, tileGridMeta } from '../patterns/tile-grid/meta';
94
89
  import { timelineMeta } from '../patterns/timeline/meta';
95
90
  import { treeItemMeta, treeViewMeta } from '../patterns/tree-view/meta';
96
- import { zoraDrawerContentMeta } from '../patterns/zora-drawer-content/meta';
97
- import { zoraTabBarMeta } from '../patterns/zora-tab-bar/meta';
98
91
  import { themeModeToggleMeta } from '../theme/ThemeModeToggle.meta';
99
92
  import type { ZoraComponentMetaRegistry } from './types';
100
93
 
101
94
  export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
102
95
  ...foundationMetas,
103
- ActionSheet: actionSheetMeta,
104
- ActionSheetItem: actionSheetItemMeta,
105
96
  AppBar: appBarMeta,
106
97
  Avatar: avatarMeta,
107
98
  AvatarGroup: avatarGroupMeta,
@@ -116,7 +107,6 @@ export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
116
107
  ChipGroup: chipGroupMeta,
117
108
  DataTable: dataTableMeta,
118
109
  DatePicker: datePickerMeta,
119
- Drawer: drawerMeta,
120
110
  DropdownMenu: dropdownMenuMeta,
121
111
  Form: formMeta,
122
112
  FormActions: formActionsMeta,
@@ -132,8 +122,6 @@ export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
132
122
  Menu: menuMeta,
133
123
  MetricCard: metricCardMeta,
134
124
  Modal: modalMeta,
135
- NavigationItem: navigationItemMeta,
136
- NavigationList: navigationListMeta,
137
125
  Pagination: paginationMeta,
138
126
  Progress: progressMeta,
139
127
  ProgressRing: progressRingMeta,
@@ -188,7 +176,6 @@ export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
188
176
  Panel: panelMeta,
189
177
  PostCard: postCardMeta,
190
178
  ProductCard: productCardMeta,
191
- ResponsivePanel: responsivePanelMeta,
192
179
  ReaderSurface: readerSurfaceMeta,
193
180
  BarcodeScannerView: barcodeScannerViewMeta,
194
181
  CameraPermissionView: cameraPermissionViewMeta,
@@ -204,6 +191,4 @@ export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
204
191
  Timeline: timelineMeta,
205
192
  TreeItem: treeItemMeta,
206
193
  TreeView: treeViewMeta,
207
- ZoraDrawerContent: zoraDrawerContentMeta,
208
- ZoraTabBar: zoraTabBarMeta,
209
194
  };
@@ -1,6 +1,6 @@
1
1
  import { expect, test } from 'bun:test';
2
2
 
3
- test('passes the isolated Surface 3 and RN Web 0.21 acceptance suite', () => {
3
+ test('passes the isolated Surface 4 and RN Web 0.21 acceptance suite', () => {
4
4
  const subprocess = Bun.spawnSync({
5
5
  cmd: [process.execPath, 'test', 'test-fixtures/platformAcceptance.test.tsx'],
6
6
  stderr: 'pipe',
package/src/registry.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { InteractionPolicyProps } from '@ankhorage/surface';
2
2
  import type React from 'react';
3
3
 
4
- import { ActionSheet, ActionSheetItem } from './components/action-sheet';
5
4
  import { AppBar } from './components/app-bar';
6
5
  import { Avatar } from './components/avatar';
7
6
  import { AvatarGroup } from './components/avatar-group';
@@ -15,7 +14,6 @@ import { Chip } from './components/chip';
15
14
  import { ChipGroup } from './components/chip-group';
16
15
  import { DataTable } from './components/data-table';
17
16
  import { DatePicker } from './components/date-picker';
18
- import { Drawer } from './components/drawer';
19
17
  import { Form, FormActions, FormError, FormField } from './components/form';
20
18
  import { Gradient } from './components/gradient';
21
19
  import { Heading } from './components/heading';
@@ -27,8 +25,6 @@ import { MediaCard } from './components/media-card';
27
25
  import { DropdownMenu, Menu } from './components/menu';
28
26
  import { MetricCard } from './components/metric-card';
29
27
  import { Modal } from './components/modal';
30
- import { NavigationItem } from './components/navigation-item';
31
- import { NavigationList } from './components/navigation-list';
32
28
  import { Pagination } from './components/pagination';
33
29
  import { Progress, ProgressRing } from './components/progress';
34
30
  import { Radio, RadioGroup } from './components/radio';
@@ -88,7 +84,6 @@ import { Panel } from './patterns/panel';
88
84
  import { PostCard } from './patterns/post-card';
89
85
  import { ProductCard } from './patterns/product-card';
90
86
  import { ReaderSurface } from './patterns/reader';
91
- import { ResponsivePanel } from './patterns/responsive-panel';
92
87
  import { BarcodeScannerView, CameraPermissionView, ScanOverlay } from './patterns/scanner';
93
88
  import { SectionHeader } from './patterns/section-header';
94
89
  import { SelectableItem } from './patterns/selection';
@@ -98,8 +93,6 @@ import { ThemeComposer } from './patterns/theme-composer';
98
93
  import { PaletteItem, TileGrid } from './patterns/tile-grid';
99
94
  import { Timeline } from './patterns/timeline';
100
95
  import { TreeItem, TreeView } from './patterns/tree-view';
101
- import { ZoraDrawerContent } from './patterns/zora-drawer-content';
102
- import { ZoraTabBar } from './patterns/zora-tab-bar';
103
96
  import { ThemeModeToggle } from './theme/ThemeModeToggle';
104
97
 
105
98
  export type ZoraComponentRegistry = Readonly<Record<string, React.ElementType>>;
@@ -128,8 +121,6 @@ type _RegistryInteractionPolicyCheck = _AssertTrue<
128
121
  >;
129
122
 
130
123
  const _ZORA_COMPONENT_REGISTRY = {
131
- ActionSheet,
132
- ActionSheetItem,
133
124
  AppBar,
134
125
  Avatar,
135
126
  AvatarGroup,
@@ -144,7 +135,6 @@ const _ZORA_COMPONENT_REGISTRY = {
144
135
  ChipGroup,
145
136
  DataTable,
146
137
  DatePicker,
147
- Drawer,
148
138
  DropdownMenu,
149
139
  Form,
150
140
  FormActions,
@@ -160,8 +150,6 @@ const _ZORA_COMPONENT_REGISTRY = {
160
150
  Menu,
161
151
  MetricCard,
162
152
  Modal,
163
- NavigationItem,
164
- NavigationList,
165
153
  Pagination,
166
154
  Progress,
167
155
  ProgressRing,
@@ -225,7 +213,6 @@ const _ZORA_COMPONENT_REGISTRY = {
225
213
  Panel,
226
214
  PostCard,
227
215
  ProductCard,
228
- ResponsivePanel,
229
216
  ReaderSurface,
230
217
  BarcodeScannerView,
231
218
  CameraPermissionView,
@@ -240,8 +227,6 @@ const _ZORA_COMPONENT_REGISTRY = {
240
227
  Timeline,
241
228
  TreeItem,
242
229
  TreeView,
243
- ZoraDrawerContent,
244
- ZoraTabBar,
245
230
  } as const satisfies ZoraComponentRegistry;
246
231
 
247
232
  export const ZORA_COMPONENT_REGISTRY: ZoraComponentRegistry = _ZORA_COMPONENT_REGISTRY;