@ankhorage/zora 5.0.0 → 7.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.
- package/CHANGELOG.md +15 -0
- package/README.md +14 -10
- package/dist/components/button/resolveButtonThemeRecipe.d.ts +1 -1
- package/dist/components/date-picker/DatePicker.d.ts.map +1 -1
- package/dist/components/date-picker/DatePicker.js +92 -86
- package/dist/components/date-picker/DatePicker.js.map +1 -1
- package/dist/components/date-picker/meta.d.ts +1 -1
- package/dist/components/date-picker/meta.js +1 -1
- package/dist/components/date-picker/meta.js.map +1 -1
- package/dist/components/heading/resolveHeadingThemeRecipe.d.ts +1 -1
- package/dist/components/image/Image.d.ts +1 -5
- package/dist/components/image/Image.d.ts.map +1 -1
- package/dist/components/image/Image.js +79 -8
- package/dist/components/image/Image.js.map +1 -1
- package/dist/components/image/types.d.ts +4 -2
- package/dist/components/image/types.d.ts.map +1 -1
- package/dist/components/image/types.js.map +1 -1
- package/dist/components/text/resolveTextThemeRecipe.d.ts +1 -1
- package/dist/components/time-picker/TimePicker.d.ts +1 -3
- package/dist/components/time-picker/TimePicker.d.ts.map +1 -1
- package/dist/components/time-picker/TimePicker.js +55 -32
- package/dist/components/time-picker/TimePicker.js.map +1 -1
- package/dist/components/time-picker/meta.d.ts +1 -1
- package/dist/components/time-picker/meta.js +1 -1
- package/dist/components/time-picker/meta.js.map +1 -1
- package/dist/features/bottom-sheet/public.d.ts +3 -0
- package/dist/features/bottom-sheet/public.d.ts.map +1 -0
- package/dist/features/bottom-sheet/public.js +2 -0
- package/dist/features/bottom-sheet/public.js.map +1 -0
- package/dist/features/uploader/adapters/inbound/Uploader.d.ts +5 -0
- package/dist/features/uploader/adapters/inbound/Uploader.d.ts.map +1 -0
- package/dist/features/uploader/adapters/inbound/Uploader.js +274 -0
- package/dist/features/uploader/adapters/inbound/Uploader.js.map +1 -0
- package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.d.ts +5 -0
- package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.d.ts.map +1 -0
- package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.js +38 -0
- package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.js.map +1 -0
- package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.d.ts +5 -0
- package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.d.ts.map +1 -0
- package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.js +24 -0
- package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.js.map +1 -0
- package/dist/features/uploader/application/ports/outbound/UploadPickerPort.d.ts +9 -0
- package/dist/features/uploader/application/ports/outbound/UploadPickerPort.d.ts.map +1 -0
- package/dist/features/uploader/application/ports/outbound/UploadPickerPort.js +2 -0
- package/dist/features/uploader/application/ports/outbound/UploadPickerPort.js.map +1 -0
- package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.d.ts +5 -0
- package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.d.ts.map +1 -0
- package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.js +6 -0
- package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.js.map +1 -0
- package/dist/features/uploader/application/use-cases/validateUploadAsset.d.ts +4 -0
- package/dist/features/uploader/application/use-cases/validateUploadAsset.d.ts.map +1 -0
- package/dist/features/uploader/application/use-cases/validateUploadAsset.js +77 -0
- package/dist/features/uploader/application/use-cases/validateUploadAsset.js.map +1 -0
- package/dist/features/uploader/composition/createUploadPicker.d.ts +4 -0
- package/dist/features/uploader/composition/createUploadPicker.d.ts.map +1 -0
- package/dist/features/uploader/composition/createUploadPicker.js +12 -0
- package/dist/features/uploader/composition/createUploadPicker.js.map +1 -0
- package/dist/features/uploader/meta.d.ts +10 -0
- package/dist/features/uploader/meta.d.ts.map +1 -0
- package/dist/features/uploader/meta.js +10 -0
- package/dist/features/uploader/meta.js.map +1 -0
- package/dist/features/uploader/public.d.ts +4 -0
- package/dist/features/uploader/public.d.ts.map +1 -0
- package/dist/features/uploader/public.js +3 -0
- package/dist/features/uploader/public.js.map +1 -0
- package/dist/index.d.ts +2 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/metadata/componentMeta.d.ts.map +1 -1
- package/dist/metadata/componentMeta.js +2 -7
- package/dist/metadata/componentMeta.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +2 -7
- package/dist/registry.js.map +1 -1
- package/dist/theme/ZoraProvider.d.ts +1 -1
- package/dist/theme/ZoraProvider.d.ts.map +1 -1
- package/dist/theme/ZoraProvider.js +3 -2
- package/dist/theme/ZoraProvider.js.map +1 -1
- package/dist/theme/useZoraTheme.d.ts +1 -1
- package/dist/types/upload.d.ts +53 -0
- package/dist/types/upload.d.ts.map +1 -0
- package/dist/types/upload.js +2 -0
- package/dist/types/upload.js.map +1 -0
- package/package.json +25 -3
- package/src/components/date-picker/DatePicker.tsx +197 -129
- package/src/components/date-picker/meta.ts +1 -1
- package/src/components/image/Image.tsx +136 -8
- package/src/components/image/types.ts +6 -2
- package/src/components/time-picker/TimePicker.tsx +147 -60
- package/src/components/time-picker/meta.ts +1 -1
- package/src/expo57Migration.test.ts +56 -0
- package/src/features/bottom-sheet/public.test.ts +39 -0
- package/src/features/bottom-sheet/public.ts +5 -0
- package/src/features/uploader/adapters/inbound/Uploader.tsx +403 -0
- package/src/features/uploader/adapters/outbound/pickDocumentOrFileAsync.ts +50 -0
- package/src/features/uploader/adapters/outbound/pickImageOrVideoAsync.ts +30 -0
- package/src/features/uploader/application/ports/outbound/UploadPickerPort.ts +10 -0
- package/src/features/uploader/application/use-cases/pickUploadAssetAsync.test.ts +48 -0
- package/src/features/uploader/application/use-cases/pickUploadAssetAsync.ts +14 -0
- package/src/features/uploader/application/use-cases/validateUploadAsset.test.ts +51 -0
- package/src/features/uploader/application/use-cases/validateUploadAsset.ts +111 -0
- package/src/features/uploader/composition/createUploadPicker.ts +14 -0
- package/src/features/uploader/meta.ts +11 -0
- package/src/features/uploader/public.test.ts +34 -0
- package/src/features/uploader/public.ts +10 -0
- package/src/index.ts +9 -14
- package/src/metadata/componentMeta.ts +2 -7
- package/src/platformAcceptance.test.ts +1 -1
- package/src/registry.test.ts +1 -1
- package/src/registry.ts +2 -7
- package/src/showcaseCoverage.test.ts +1 -2
- package/src/theme/ZoraProvider.tsx +3 -2
- package/src/types/upload.ts +62 -0
- package/dist/components/action-sheet/ActionSheet.d.ts +0 -7
- package/dist/components/action-sheet/ActionSheet.d.ts.map +0 -1
- package/dist/components/action-sheet/ActionSheet.js +0 -11
- package/dist/components/action-sheet/ActionSheet.js.map +0 -1
- package/dist/components/action-sheet/ActionSheetItem.d.ts +0 -7
- package/dist/components/action-sheet/ActionSheetItem.d.ts.map +0 -1
- package/dist/components/action-sheet/ActionSheetItem.js +0 -17
- package/dist/components/action-sheet/ActionSheetItem.js.map +0 -1
- package/dist/components/action-sheet/index.d.ts +0 -4
- package/dist/components/action-sheet/index.d.ts.map +0 -1
- package/dist/components/action-sheet/index.js +0 -3
- package/dist/components/action-sheet/index.js.map +0 -1
- package/dist/components/action-sheet/meta.d.ts +0 -17
- package/dist/components/action-sheet/meta.d.ts.map +0 -1
- package/dist/components/action-sheet/meta.js +0 -17
- package/dist/components/action-sheet/meta.js.map +0 -1
- package/dist/components/action-sheet/types.d.ts +0 -13
- package/dist/components/action-sheet/types.d.ts.map +0 -1
- package/dist/components/action-sheet/types.js +0 -2
- package/dist/components/action-sheet/types.js.map +0 -1
- package/dist/patterns/image-preview/ImagePreview.d.ts +0 -9
- package/dist/patterns/image-preview/ImagePreview.d.ts.map +0 -1
- package/dist/patterns/image-preview/ImagePreview.js +0 -46
- package/dist/patterns/image-preview/ImagePreview.js.map +0 -1
- package/dist/patterns/image-preview/index.d.ts +0 -3
- package/dist/patterns/image-preview/index.d.ts.map +0 -1
- package/dist/patterns/image-preview/index.js +0 -2
- package/dist/patterns/image-preview/index.js.map +0 -1
- package/dist/patterns/image-preview/meta.d.ts +0 -9
- package/dist/patterns/image-preview/meta.d.ts.map +0 -1
- package/dist/patterns/image-preview/meta.js +0 -9
- package/dist/patterns/image-preview/meta.js.map +0 -1
- package/dist/patterns/image-preview/types.d.ts +0 -36
- package/dist/patterns/image-preview/types.d.ts.map +0 -1
- package/dist/patterns/image-preview/types.js +0 -2
- package/dist/patterns/image-preview/types.js.map +0 -1
- package/dist/patterns/image-upload-field/ImageUploadField.d.ts +0 -9
- package/dist/patterns/image-upload-field/ImageUploadField.d.ts.map +0 -1
- package/dist/patterns/image-upload-field/ImageUploadField.js +0 -222
- package/dist/patterns/image-upload-field/ImageUploadField.js.map +0 -1
- package/dist/patterns/image-upload-field/index.d.ts +0 -3
- package/dist/patterns/image-upload-field/index.d.ts.map +0 -1
- package/dist/patterns/image-upload-field/index.js +0 -2
- package/dist/patterns/image-upload-field/index.js.map +0 -1
- package/dist/patterns/image-upload-field/meta.d.ts +0 -9
- package/dist/patterns/image-upload-field/meta.d.ts.map +0 -1
- package/dist/patterns/image-upload-field/meta.js +0 -9
- package/dist/patterns/image-upload-field/meta.js.map +0 -1
- package/dist/patterns/image-upload-field/types.d.ts +0 -35
- package/dist/patterns/image-upload-field/types.d.ts.map +0 -1
- package/dist/patterns/image-upload-field/types.js +0 -2
- package/dist/patterns/image-upload-field/types.js.map +0 -1
- package/dist/patterns/image-upload-field/uploadFlow.d.ts +0 -18
- package/dist/patterns/image-upload-field/uploadFlow.d.ts.map +0 -1
- package/dist/patterns/image-upload-field/uploadFlow.js +0 -106
- package/dist/patterns/image-upload-field/uploadFlow.js.map +0 -1
- package/src/components/action-sheet/ActionSheet.tsx +0 -14
- package/src/components/action-sheet/ActionSheetItem.tsx +0 -30
- package/src/components/action-sheet/index.ts +0 -3
- package/src/components/action-sheet/meta.ts +0 -19
- package/src/components/action-sheet/types.ts +0 -23
- package/src/patterns/image-preview/ImagePreview.tsx +0 -82
- package/src/patterns/image-preview/index.ts +0 -2
- package/src/patterns/image-preview/meta.ts +0 -10
- package/src/patterns/image-preview/types.ts +0 -41
- package/src/patterns/image-upload-field/ImageUploadField.tsx +0 -319
- package/src/patterns/image-upload-field/index.ts +0 -2
- package/src/patterns/image-upload-field/meta.ts +0 -10
- package/src/patterns/image-upload-field/types.ts +0 -41
- package/src/patterns/image-upload-field/uploadFlow.test.ts +0 -117
- package/src/patterns/image-upload-field/uploadFlow.ts +0 -145
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
+
});
|