@ankhorage/zora 5.0.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 (75) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +12 -9
  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 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +0 -1
  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 -3
  29. package/dist/metadata/componentMeta.js.map +1 -1
  30. package/dist/registry.d.ts.map +1 -1
  31. package/dist/registry.js +0 -3
  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/components/date-picker/DatePicker.tsx +197 -129
  40. package/src/components/date-picker/meta.ts +1 -1
  41. package/src/components/time-picker/TimePicker.tsx +147 -60
  42. package/src/components/time-picker/meta.ts +1 -1
  43. package/src/expo57Migration.test.ts +56 -0
  44. package/src/features/bottom-sheet/public.test.ts +39 -0
  45. package/src/features/bottom-sheet/public.ts +5 -0
  46. package/src/index.ts +0 -2
  47. package/src/metadata/componentMeta.ts +0 -3
  48. package/src/platformAcceptance.test.ts +1 -1
  49. package/src/registry.ts +0 -3
  50. package/src/theme/ZoraProvider.tsx +3 -2
  51. package/dist/components/action-sheet/ActionSheet.d.ts +0 -7
  52. package/dist/components/action-sheet/ActionSheet.d.ts.map +0 -1
  53. package/dist/components/action-sheet/ActionSheet.js +0 -11
  54. package/dist/components/action-sheet/ActionSheet.js.map +0 -1
  55. package/dist/components/action-sheet/ActionSheetItem.d.ts +0 -7
  56. package/dist/components/action-sheet/ActionSheetItem.d.ts.map +0 -1
  57. package/dist/components/action-sheet/ActionSheetItem.js +0 -17
  58. package/dist/components/action-sheet/ActionSheetItem.js.map +0 -1
  59. package/dist/components/action-sheet/index.d.ts +0 -4
  60. package/dist/components/action-sheet/index.d.ts.map +0 -1
  61. package/dist/components/action-sheet/index.js +0 -3
  62. package/dist/components/action-sheet/index.js.map +0 -1
  63. package/dist/components/action-sheet/meta.d.ts +0 -17
  64. package/dist/components/action-sheet/meta.d.ts.map +0 -1
  65. package/dist/components/action-sheet/meta.js +0 -17
  66. package/dist/components/action-sheet/meta.js.map +0 -1
  67. package/dist/components/action-sheet/types.d.ts +0 -13
  68. package/dist/components/action-sheet/types.d.ts.map +0 -1
  69. package/dist/components/action-sheet/types.js +0 -2
  70. package/dist/components/action-sheet/types.js.map +0 -1
  71. package/src/components/action-sheet/ActionSheet.tsx +0 -14
  72. package/src/components/action-sheet/ActionSheetItem.tsx +0 -30
  73. package/src/components/action-sheet/index.ts +0 -3
  74. package/src/components/action-sheet/meta.ts +0 -19
  75. package/src/components/action-sheet/types.ts +0 -23
@@ -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';
@@ -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';
@@ -94,8 +93,6 @@ import type { ZoraComponentMetaRegistry } from './types';
94
93
 
95
94
  export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
96
95
  ...foundationMetas,
97
- ActionSheet: actionSheetMeta,
98
- ActionSheetItem: actionSheetItemMeta,
99
96
  AppBar: appBarMeta,
100
97
  Avatar: avatarMeta,
101
98
  AvatarGroup: avatarGroupMeta,
@@ -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';
@@ -122,8 +121,6 @@ type _RegistryInteractionPolicyCheck = _AssertTrue<
122
121
  >;
123
122
 
124
123
  const _ZORA_COMPONENT_REGISTRY = {
125
- ActionSheet,
126
- ActionSheetItem,
127
124
  AppBar,
128
125
  Avatar,
129
126
  AvatarGroup,
@@ -1,5 +1,6 @@
1
1
  import type { ThemeConfig } from '@ankhorage/contracts';
2
2
  import { ThemeProvider } from '@ankhorage/surface';
3
+ import { BottomSheetProvider } from '@ankhorage/surface/bottom-sheet';
3
4
  import React from 'react';
4
5
 
5
6
  import { createZoraThemeConfig } from './createZoraThemeConfig';
@@ -14,7 +15,7 @@ export interface ZoraProviderProps {
14
15
  initialMode?: ZoraThemeMode;
15
16
  }
16
17
 
17
- /** Installs the ZORA theme runtime and underlying Surface theme provider. */
18
+ /** Installs the ZORA theme runtime, Surface theme, and shared bottom-sheet controller. */
18
19
  export function ZoraProvider({
19
20
  children,
20
21
  theme = zoraDefaultTheme,
@@ -30,7 +31,7 @@ export function ZoraProvider({
30
31
  return (
31
32
  <ZoraThemeRuntimeContext.Provider value={runtimeValue}>
32
33
  <ThemeProvider initialConfig={resolvedConfig} initialMode={initialMode}>
33
- {children}
34
+ <BottomSheetProvider>{children}</BottomSheetProvider>
34
35
  </ThemeProvider>
35
36
  </ZoraThemeRuntimeContext.Provider>
36
37
  );
@@ -1,7 +0,0 @@
1
- import React from 'react';
2
- import type { ActionSheetProps } from './types';
3
- /***
4
- * Presents a modal bottom sheet with a list of actions.
5
- */
6
- export declare const ActionSheet: (props: ActionSheetProps) => React.ReactElement | null;
7
- //# sourceMappingURL=ActionSheet.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ActionSheet.d.ts","sourceRoot":"","sources":["../../../src/components/action-sheet/ActionSheet.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAMhD;;GAEG;AACH,eAAO,MAAM,WAAW,wDAAuC,CAAC"}
@@ -1,11 +0,0 @@
1
- import { ActionSheet as SurfaceActionSheet } from '@ankhorage/surface';
2
- import React from 'react';
3
- import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
4
- function ActionSheetInner({ mode: _mode, themeId: _themeId, ...props }) {
5
- return <SurfaceActionSheet {...props}/>;
6
- }
7
- /***
8
- * Presents a modal bottom sheet with a list of actions.
9
- */
10
- export const ActionSheet = withZoraThemeScope(ActionSheetInner);
11
- //# sourceMappingURL=ActionSheet.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ActionSheet.js","sourceRoot":"","sources":["../../../src/components/action-sheet/ActionSheet.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,IAAI,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAGpE,SAAS,gBAAgB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAoB;IACtF,OAAO,CAAC,kBAAkB,CAAC,IAAI,KAAK,CAAC,EAAG,CAAC;AAC3C,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,CAAC","sourcesContent":["import { ActionSheet as SurfaceActionSheet } from '@ankhorage/surface';\nimport React from 'react';\n\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport type { ActionSheetProps } from './types';\n\nfunction ActionSheetInner({ mode: _mode, themeId: _themeId, ...props }: ActionSheetProps) {\n return <SurfaceActionSheet {...props} />;\n}\n\n/***\n * Presents a modal bottom sheet with a list of actions.\n */\nexport const ActionSheet = withZoraThemeScope(ActionSheetInner);\n"]}
@@ -1,7 +0,0 @@
1
- import React from 'react';
2
- import type { ActionSheetItemProps } from './types';
3
- /***
4
- * Renders a single selectable action row within an `ActionSheet`.
5
- */
6
- export declare const ActionSheetItem: (props: ActionSheetItemProps) => React.ReactElement | null;
7
- //# sourceMappingURL=ActionSheetItem.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ActionSheetItem.d.ts","sourceRoot":"","sources":["../../../src/components/action-sheet/ActionSheetItem.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAmBpD;;GAEG;AACH,eAAO,MAAM,eAAe,4DAA2C,CAAC"}
@@ -1,17 +0,0 @@
1
- import { ActionSheetItem as SurfaceActionSheetItem } from '@ankhorage/surface';
2
- import React from 'react';
3
- import { resolveIconSize } from '../../internal/recipes';
4
- import { useZoraTheme } from '../../theme/useZoraTheme';
5
- import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
6
- import { Icon } from '../icon';
7
- function ActionSheetItemInner({ themeId: _themeId, mode: _mode, icon, leading, ...props }) {
8
- const { theme } = useZoraTheme();
9
- const resolvedLeading = leading ??
10
- (icon ? (<Icon {...icon} color={theme.semantics.content.muted} size={resolveIconSize('s')}/>) : undefined);
11
- return <SurfaceActionSheetItem {...props} leading={resolvedLeading}/>;
12
- }
13
- /***
14
- * Renders a single selectable action row within an `ActionSheet`.
15
- */
16
- export const ActionSheetItem = withZoraThemeScope(ActionSheetItemInner);
17
- //# sourceMappingURL=ActionSheetItem.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ActionSheetItem.js","sourceRoot":"","sources":["../../../src/components/action-sheet/ActionSheetItem.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG/B,SAAS,oBAAoB,CAAC,EAC5B,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,EACX,IAAI,EACJ,OAAO,EACP,GAAG,KAAK,EACa;IACrB,MAAM,EAAE,KAAK,EAAE,GAAG,YAAY,EAAE,CAAC;IACjC,MAAM,eAAe,GACnB,OAAO;QACP,CAAC,IAAI,CAAC,CAAC,CAAC,CACN,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,EAAG,CACrF,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAEjB,OAAO,CAAC,sBAAsB,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,eAAe,CAAC,EAAG,CAAC;AACzE,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,kBAAkB,CAAC,oBAAoB,CAAC,CAAC","sourcesContent":["import { ActionSheetItem as SurfaceActionSheetItem } from '@ankhorage/surface';\nimport React from 'react';\n\nimport { resolveIconSize } from '../../internal/recipes';\nimport { useZoraTheme } from '../../theme/useZoraTheme';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport { Icon } from '../icon';\nimport type { ActionSheetItemProps } from './types';\n\nfunction ActionSheetItemInner({\n themeId: _themeId,\n mode: _mode,\n icon,\n leading,\n ...props\n}: ActionSheetItemProps) {\n const { theme } = useZoraTheme();\n const resolvedLeading =\n leading ??\n (icon ? (\n <Icon {...icon} color={theme.semantics.content.muted} size={resolveIconSize('s')} />\n ) : undefined);\n\n return <SurfaceActionSheetItem {...props} leading={resolvedLeading} />;\n}\n\n/***\n * Renders a single selectable action row within an `ActionSheet`.\n */\nexport const ActionSheetItem = withZoraThemeScope(ActionSheetItemInner);\n"]}
@@ -1,4 +0,0 @@
1
- export { ActionSheet } from './ActionSheet';
2
- export { ActionSheetItem } from './ActionSheetItem';
3
- export type { ActionSheetItemProps, ActionSheetProps } from './types';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/action-sheet/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"}
@@ -1,3 +0,0 @@
1
- export { ActionSheet } from './ActionSheet';
2
- export { ActionSheetItem } from './ActionSheetItem';
3
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/action-sheet/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["export { ActionSheet } from './ActionSheet';\nexport { ActionSheetItem } from './ActionSheetItem';\nexport type { ActionSheetItemProps, ActionSheetProps } from './types';\n"]}
@@ -1,17 +0,0 @@
1
- export declare const actionSheetMeta: {
2
- readonly name: "ActionSheet";
3
- readonly category: "component";
4
- readonly directManifestNode: false;
5
- readonly allowedChildren: readonly [];
6
- readonly note: "Surface-backed contextual action sheet; not represented as a manifest node in v1.";
7
- readonly props: {};
8
- };
9
- export declare const actionSheetItemMeta: {
10
- readonly name: "ActionSheetItem";
11
- readonly category: "component";
12
- readonly directManifestNode: false;
13
- readonly allowedChildren: readonly [];
14
- readonly note: "Surface-backed contextual action sheet row; not represented as a manifest node in v1.";
15
- readonly props: {};
16
- };
17
- //# sourceMappingURL=meta.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../../src/components/action-sheet/meta.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe;;;;;;;CAOU,CAAC;AAEvC,eAAO,MAAM,mBAAmB;;;;;;;CAOM,CAAC"}
@@ -1,17 +0,0 @@
1
- export const actionSheetMeta = {
2
- name: 'ActionSheet',
3
- category: 'component',
4
- directManifestNode: false,
5
- allowedChildren: [],
6
- note: 'Surface-backed contextual action sheet; not represented as a manifest node in v1.',
7
- props: {},
8
- };
9
- export const actionSheetItemMeta = {
10
- name: 'ActionSheetItem',
11
- category: 'component',
12
- directManifestNode: false,
13
- allowedChildren: [],
14
- note: 'Surface-backed contextual action sheet row; not represented as a manifest node in v1.',
15
- props: {},
16
- };
17
- //# sourceMappingURL=meta.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"meta.js","sourceRoot":"","sources":["../../../src/components/action-sheet/meta.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,IAAI,EAAE,aAAa;IACnB,QAAQ,EAAE,WAAW;IACrB,kBAAkB,EAAE,KAAK;IACzB,eAAe,EAAE,EAAE;IACnB,IAAI,EAAE,mFAAmF;IACzF,KAAK,EAAE,EAAE;CAC2B,CAAC;AAEvC,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,iBAAiB;IACvB,QAAQ,EAAE,WAAW;IACrB,kBAAkB,EAAE,KAAK;IACzB,eAAe,EAAE,EAAE;IACnB,IAAI,EAAE,uFAAuF;IAC7F,KAAK,EAAE,EAAE;CAC2B,CAAC","sourcesContent":["import type { ZoraComponentMeta } from '../../metadata';\n\nexport const actionSheetMeta = {\n name: 'ActionSheet',\n category: 'component',\n directManifestNode: false,\n allowedChildren: [],\n note: 'Surface-backed contextual action sheet; not represented as a manifest node in v1.',\n props: {},\n} as const satisfies ZoraComponentMeta;\n\nexport const actionSheetItemMeta = {\n name: 'ActionSheetItem',\n category: 'component',\n directManifestNode: false,\n allowedChildren: [],\n note: 'Surface-backed contextual action sheet row; not represented as a manifest node in v1.',\n props: {},\n} as const satisfies ZoraComponentMeta;\n"]}
@@ -1,13 +0,0 @@
1
- import type { ActionSheetItemProps as SurfaceActionSheetItemProps, ActionSheetProps as SurfaceActionSheetProps, ButtonIconSpec } from '@ankhorage/surface';
2
- import type React from 'react';
3
- import type { ZoraColor } from '../../internal/recipes';
4
- import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
5
- export interface ActionSheetProps extends ZoraBaseProps, Omit<SurfaceActionSheetProps, 'children' | 'mode' | 'themeId'> {
6
- children?: React.ReactNode;
7
- }
8
- export interface ActionSheetItemProps extends ZoraBaseProps, Omit<SurfaceActionSheetItemProps, 'color' | 'leading' | 'mode' | 'themeId'> {
9
- color?: ZoraColor;
10
- icon?: ButtonIconSpec;
11
- leading?: React.ReactNode;
12
- }
13
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/action-sheet/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,IAAI,2BAA2B,EACnD,gBAAgB,IAAI,uBAAuB,EAC3C,cAAc,EACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,WAAW,gBACf,SAAQ,aAAa,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;IACrF,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,oBACf,SACE,aAAa,EACb,IAAI,CAAC,2BAA2B,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;IAC7E,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/components/action-sheet/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n ActionSheetItemProps as SurfaceActionSheetItemProps,\n ActionSheetProps as SurfaceActionSheetProps,\n ButtonIconSpec,\n} from '@ankhorage/surface';\nimport type React from 'react';\n\nimport type { ZoraColor } from '../../internal/recipes';\nimport type { ZoraBaseProps } from '../../theme/ZoraBaseProps';\n\nexport interface ActionSheetProps\n extends ZoraBaseProps, Omit<SurfaceActionSheetProps, 'children' | 'mode' | 'themeId'> {\n children?: React.ReactNode;\n}\n\nexport interface ActionSheetItemProps\n extends\n ZoraBaseProps,\n Omit<SurfaceActionSheetItemProps, 'color' | 'leading' | 'mode' | 'themeId'> {\n color?: ZoraColor;\n icon?: ButtonIconSpec;\n leading?: React.ReactNode;\n}\n"]}