@ankhorage/zora 13.0.0 → 14.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 (185) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +11 -5
  3. package/dist/features/accordion/adapters/inbound/Accordion.js +4 -4
  4. package/dist/features/accordion/adapters/inbound/Accordion.js.map +1 -1
  5. package/dist/features/accordion/adapters/inbound/AccordionItem.js +1 -1
  6. package/dist/features/accordion/adapters/inbound/AccordionItem.js.map +1 -1
  7. package/dist/features/date-picker/adapters/inbound/DatePicker.d.ts +1 -6
  8. package/dist/features/date-picker/adapters/inbound/DatePicker.d.ts.map +1 -1
  9. package/dist/features/date-picker/adapters/inbound/DatePicker.js +1 -175
  10. package/dist/features/date-picker/adapters/inbound/DatePicker.js.map +1 -1
  11. package/dist/features/date-picker/adapters/inbound/DatePicker.native.d.ts +5 -0
  12. package/dist/features/date-picker/adapters/inbound/DatePicker.native.d.ts.map +1 -0
  13. package/dist/features/date-picker/adapters/inbound/DatePicker.native.js +27 -0
  14. package/dist/features/date-picker/adapters/inbound/DatePicker.native.js.map +1 -0
  15. package/dist/features/date-picker/adapters/inbound/DatePicker.web.d.ts +5 -0
  16. package/dist/features/date-picker/adapters/inbound/DatePicker.web.d.ts.map +1 -0
  17. package/dist/features/date-picker/adapters/inbound/DatePicker.web.js +25 -0
  18. package/dist/features/date-picker/adapters/inbound/DatePicker.web.js.map +1 -0
  19. package/dist/features/date-picker/composition/DatePickerContent.d.ts +10 -0
  20. package/dist/features/date-picker/composition/DatePickerContent.d.ts.map +1 -0
  21. package/dist/features/date-picker/composition/DatePickerContent.js +129 -0
  22. package/dist/features/date-picker/composition/DatePickerContent.js.map +1 -0
  23. package/dist/features/date-picker/composition/DatePickerField.d.ts +9 -0
  24. package/dist/features/date-picker/composition/DatePickerField.d.ts.map +1 -0
  25. package/dist/features/date-picker/composition/DatePickerField.js +21 -0
  26. package/dist/features/date-picker/composition/DatePickerField.js.map +1 -0
  27. package/dist/features/date-picker/composition/DatePickerTrigger.d.ts +9 -0
  28. package/dist/features/date-picker/composition/DatePickerTrigger.d.ts.map +1 -0
  29. package/dist/features/date-picker/composition/DatePickerTrigger.js +18 -0
  30. package/dist/features/date-picker/composition/DatePickerTrigger.js.map +1 -0
  31. package/dist/features/time-picker/adapters/inbound/TimePicker.d.ts +1 -6
  32. package/dist/features/time-picker/adapters/inbound/TimePicker.d.ts.map +1 -1
  33. package/dist/features/time-picker/adapters/inbound/TimePicker.js +1 -116
  34. package/dist/features/time-picker/adapters/inbound/TimePicker.js.map +1 -1
  35. package/dist/features/time-picker/adapters/inbound/TimePicker.native.d.ts +5 -0
  36. package/dist/features/time-picker/adapters/inbound/TimePicker.native.d.ts.map +1 -0
  37. package/dist/features/time-picker/adapters/inbound/TimePicker.native.js +26 -0
  38. package/dist/features/time-picker/adapters/inbound/TimePicker.native.js.map +1 -0
  39. package/dist/features/time-picker/adapters/inbound/TimePicker.web.d.ts +5 -0
  40. package/dist/features/time-picker/adapters/inbound/TimePicker.web.d.ts.map +1 -0
  41. package/dist/features/time-picker/adapters/inbound/TimePicker.web.js +24 -0
  42. package/dist/features/time-picker/adapters/inbound/TimePicker.web.js.map +1 -0
  43. package/dist/features/time-picker/composition/TimePickerContent.d.ts +10 -0
  44. package/dist/features/time-picker/composition/TimePickerContent.d.ts.map +1 -0
  45. package/dist/features/time-picker/composition/TimePickerContent.js +78 -0
  46. package/dist/features/time-picker/composition/TimePickerContent.js.map +1 -0
  47. package/dist/features/time-picker/composition/TimePickerField.d.ts +9 -0
  48. package/dist/features/time-picker/composition/TimePickerField.d.ts.map +1 -0
  49. package/dist/features/time-picker/composition/TimePickerField.js +21 -0
  50. package/dist/features/time-picker/composition/TimePickerField.js.map +1 -0
  51. package/dist/features/time-picker/composition/TimePickerTrigger.d.ts +8 -0
  52. package/dist/features/time-picker/composition/TimePickerTrigger.d.ts.map +1 -0
  53. package/dist/features/time-picker/composition/TimePickerTrigger.js +9 -0
  54. package/dist/features/time-picker/composition/TimePickerTrigger.js.map +1 -0
  55. package/dist/features/toast/adapters/inbound/Toast.d.ts +5 -0
  56. package/dist/features/toast/adapters/inbound/Toast.d.ts.map +1 -0
  57. package/dist/features/toast/adapters/inbound/Toast.js +10 -0
  58. package/dist/features/toast/adapters/inbound/Toast.js.map +1 -0
  59. package/dist/features/toast/adapters/inbound/ToastProvider.d.ts +5 -0
  60. package/dist/features/toast/adapters/inbound/ToastProvider.d.ts.map +1 -0
  61. package/dist/features/toast/adapters/inbound/ToastProvider.js +7 -0
  62. package/dist/features/toast/adapters/inbound/ToastProvider.js.map +1 -0
  63. package/dist/features/toast/public.d.ts +5 -0
  64. package/dist/features/toast/public.d.ts.map +1 -0
  65. package/dist/features/toast/public.js +4 -0
  66. package/dist/features/toast/public.js.map +1 -0
  67. package/dist/features/toast/toastMeta.d.ts +9 -0
  68. package/dist/features/toast/toastMeta.d.ts.map +1 -0
  69. package/dist/features/toast/toastMeta.js +9 -0
  70. package/dist/features/toast/toastMeta.js.map +1 -0
  71. package/dist/features/toast/toastProviderMeta.d.ts +9 -0
  72. package/dist/features/toast/toastProviderMeta.d.ts.map +1 -0
  73. package/dist/features/toast/toastProviderMeta.js +9 -0
  74. package/dist/features/toast/toastProviderMeta.js.map +1 -0
  75. package/dist/index.d.ts +2 -2
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +1 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/metadata/componentMeta.d.ts.map +1 -1
  80. package/dist/metadata/componentMeta.js +2 -1
  81. package/dist/metadata/componentMeta.js.map +1 -1
  82. package/dist/patterns/selection/SelectionProvider.d.ts +0 -4
  83. package/dist/patterns/selection/SelectionProvider.d.ts.map +1 -1
  84. package/dist/patterns/selection/SelectionProvider.js +4 -6
  85. package/dist/patterns/selection/SelectionProvider.js.map +1 -1
  86. package/dist/registry.js +1 -1
  87. package/dist/registry.js.map +1 -1
  88. package/dist/theme/ZoraProvider.d.ts +4 -10
  89. package/dist/theme/ZoraProvider.d.ts.map +1 -1
  90. package/dist/theme/ZoraProvider.js +8 -6
  91. package/dist/theme/ZoraProvider.js.map +1 -1
  92. package/dist/theme/ZoraRuntimeCapabilities.d.ts +2 -0
  93. package/dist/theme/ZoraRuntimeCapabilities.d.ts.map +1 -0
  94. package/dist/theme/ZoraRuntimeCapabilities.js +2 -0
  95. package/dist/theme/ZoraRuntimeCapabilities.js.map +1 -0
  96. package/dist/theme/ZoraRuntimeCapabilities.native.d.ts +4 -0
  97. package/dist/theme/ZoraRuntimeCapabilities.native.d.ts.map +1 -0
  98. package/dist/theme/ZoraRuntimeCapabilities.native.js +8 -0
  99. package/dist/theme/ZoraRuntimeCapabilities.native.js.map +1 -0
  100. package/dist/theme/ZoraRuntimeCapabilities.web.d.ts +4 -0
  101. package/dist/theme/ZoraRuntimeCapabilities.web.d.ts.map +1 -0
  102. package/dist/theme/ZoraRuntimeCapabilities.web.js +8 -0
  103. package/dist/theme/ZoraRuntimeCapabilities.web.js.map +1 -0
  104. package/dist/theme/ZoraThemeRuntimeContext.d.ts +1 -0
  105. package/dist/theme/ZoraThemeRuntimeContext.d.ts.map +1 -1
  106. package/dist/theme/ZoraThemeRuntimeContext.js +3 -2
  107. package/dist/theme/ZoraThemeRuntimeContext.js.map +1 -1
  108. package/dist/theme/ZoraThemeScope.d.ts +1 -0
  109. package/dist/theme/ZoraThemeScope.d.ts.map +1 -1
  110. package/dist/theme/ZoraThemeScope.js +12 -10
  111. package/dist/theme/ZoraThemeScope.js.map +1 -1
  112. package/dist/theme/index.d.ts +1 -1
  113. package/dist/theme/index.d.ts.map +1 -1
  114. package/dist/theme/index.js.map +1 -1
  115. package/dist/types/provider.d.ts +19 -0
  116. package/dist/types/provider.d.ts.map +1 -0
  117. package/dist/types/provider.js +2 -0
  118. package/dist/types/provider.js.map +1 -0
  119. package/dist/types/toast.d.ts +9 -0
  120. package/dist/types/toast.d.ts.map +1 -0
  121. package/dist/types/toast.js +2 -0
  122. package/dist/types/toast.js.map +1 -0
  123. package/package.json +2 -2
  124. package/src/features/accordion/adapters/inbound/Accordion.tsx +4 -4
  125. package/src/features/accordion/adapters/inbound/AccordionItem.tsx +1 -1
  126. package/src/features/bottom-sheet/public.test.ts +15 -12
  127. package/src/features/date-picker/adapters/inbound/DatePicker.native.tsx +46 -0
  128. package/src/features/date-picker/adapters/inbound/DatePicker.tsx +1 -356
  129. package/src/features/date-picker/adapters/inbound/DatePicker.web.tsx +48 -0
  130. package/src/features/date-picker/composition/DatePickerContent.tsx +256 -0
  131. package/src/features/date-picker/composition/DatePickerField.tsx +53 -0
  132. package/src/features/date-picker/composition/DatePickerTrigger.tsx +47 -0
  133. package/src/features/date-picker/public.test.ts +14 -4
  134. package/src/features/time-picker/adapters/inbound/TimePicker.native.tsx +47 -0
  135. package/src/features/time-picker/adapters/inbound/TimePicker.tsx +1 -258
  136. package/src/features/time-picker/adapters/inbound/TimePicker.web.tsx +49 -0
  137. package/src/features/time-picker/composition/TimePickerContent.tsx +164 -0
  138. package/src/features/time-picker/composition/TimePickerField.tsx +53 -0
  139. package/src/features/time-picker/composition/TimePickerTrigger.tsx +35 -0
  140. package/src/features/time-picker/public.test.ts +14 -3
  141. package/src/features/toast/adapters/inbound/Toast.tsx +13 -0
  142. package/src/features/toast/adapters/inbound/ToastProvider.tsx +9 -0
  143. package/src/features/toast/public.test.ts +19 -0
  144. package/src/features/toast/public.ts +10 -0
  145. package/src/features/toast/toastMeta.ts +10 -0
  146. package/src/features/toast/toastProviderMeta.ts +10 -0
  147. package/src/index.ts +8 -2
  148. package/src/metadata/componentMeta.ts +2 -1
  149. package/src/patterns/selection/SelectionProvider.tsx +4 -6
  150. package/src/registry.ts +1 -1
  151. package/src/theme/ZoraProvider.tsx +11 -13
  152. package/src/theme/ZoraRuntimeCapabilities.native.tsx +19 -0
  153. package/src/theme/ZoraRuntimeCapabilities.tsx +1 -0
  154. package/src/theme/ZoraRuntimeCapabilities.web.tsx +10 -0
  155. package/src/theme/ZoraThemeRuntimeContext.tsx +3 -2
  156. package/src/theme/ZoraThemeScope.tsx +14 -12
  157. package/src/theme/index.ts +1 -1
  158. package/src/theme/themeConfigScope.test.ts +4 -1
  159. package/src/theme/themeScopeStructure.test.ts +24 -16
  160. package/src/types/provider.ts +22 -0
  161. package/src/{components/toast/types.ts → types/toast.ts} +5 -7
  162. package/dist/components/toast/Toast.d.ts +0 -9
  163. package/dist/components/toast/Toast.d.ts.map +0 -1
  164. package/dist/components/toast/Toast.js +0 -13
  165. package/dist/components/toast/Toast.js.map +0 -1
  166. package/dist/components/toast/ToastProvider.d.ts +0 -9
  167. package/dist/components/toast/ToastProvider.d.ts.map +0 -1
  168. package/dist/components/toast/ToastProvider.js +0 -14
  169. package/dist/components/toast/ToastProvider.js.map +0 -1
  170. package/dist/components/toast/index.d.ts +0 -5
  171. package/dist/components/toast/index.d.ts.map +0 -1
  172. package/dist/components/toast/index.js +0 -4
  173. package/dist/components/toast/index.js.map +0 -1
  174. package/dist/components/toast/meta.d.ts +0 -17
  175. package/dist/components/toast/meta.d.ts.map +0 -1
  176. package/dist/components/toast/meta.js +0 -17
  177. package/dist/components/toast/meta.js.map +0 -1
  178. package/dist/components/toast/types.d.ts +0 -12
  179. package/dist/components/toast/types.d.ts.map +0 -1
  180. package/dist/components/toast/types.js +0 -2
  181. package/dist/components/toast/types.js.map +0 -1
  182. package/src/components/toast/Toast.tsx +0 -16
  183. package/src/components/toast/ToastProvider.tsx +0 -17
  184. package/src/components/toast/index.ts +0 -4
  185. package/src/components/toast/meta.ts +0 -19
@@ -0,0 +1,49 @@
1
+ import { Popover } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { withZoraThemeScope } from '../../../../theme/withZoraThemeScope';
5
+ import type { TimePickerProps } from '../../../../types/time-picker';
6
+ import { Surface } from '../../../surface/public';
7
+ import { TimePickerContent } from '../../composition/TimePickerContent';
8
+ import { TimePickerField } from '../../composition/TimePickerField';
9
+ import { TimePickerTrigger } from '../../composition/TimePickerTrigger';
10
+
11
+ /*** Renders TimePicker with anchored web Popover presentation. */
12
+ export const TimePicker = withZoraThemeScope(TimePickerInner);
13
+
14
+ /*** Connects the shared TimePicker field and content to the web Popover host. */
15
+ function TimePickerInner({ themeId: _themeId, mode: _mode, ...props }: TimePickerProps) {
16
+ const [open, setOpen] = React.useState(false);
17
+
18
+ return (
19
+ <TimePickerField {...props}>
20
+ <Popover
21
+ anchor={({ toggle }) => <TimePickerTrigger {...props} onPress={toggle} />}
22
+ interactionPolicy={props.interactionPolicy}
23
+ onOpenChange={setOpen}
24
+ open={open}
25
+ placement="bottom-start"
26
+ testID={props.testID ? `${props.testID}-popover` : undefined}
27
+ >
28
+ <Surface variant="raised">
29
+ <TimePickerContent
30
+ description={props.description}
31
+ formatTime={props.formatTime}
32
+ interactionPolicy={props.interactionPolicy}
33
+ label={props.label}
34
+ maxTime={props.maxTime}
35
+ minTime={props.minTime}
36
+ onDismiss={() => setOpen(false)}
37
+ onSelect={(time) => {
38
+ props.onValueChange?.(time);
39
+ setOpen(false);
40
+ }}
41
+ stepMinutes={props.stepMinutes}
42
+ testID={props.testID}
43
+ value={props.value}
44
+ />
45
+ </Surface>
46
+ </Popover>
47
+ </TimePickerField>
48
+ );
49
+ }
@@ -0,0 +1,164 @@
1
+ import React from 'react';
2
+ import { ScrollView, StyleSheet } from 'react-native';
3
+
4
+ import type { TimePickerProps } from '../../../types/time-picker';
5
+ import { Button } from '../../button/public';
6
+ import { Stack } from '../../layout/public';
7
+ import { Text } from '../../typography/public';
8
+
9
+ const MINUTES_PER_DAY = 24 * 60;
10
+ const DEFAULT_STEP_MINUTES = 30;
11
+
12
+ /*** Renders shared time options for native BottomSheet and web Popover hosts. */
13
+ export function TimePickerContent({
14
+ description,
15
+ formatTime,
16
+ interactionPolicy,
17
+ label,
18
+ maxTime,
19
+ minTime,
20
+ onDismiss,
21
+ onSelect,
22
+ stepMinutes,
23
+ testID,
24
+ value,
25
+ }: TimePickerContentProps) {
26
+ const options = React.useMemo(
27
+ () => generateTimeOptions({ maxTime, minTime, stepMinutes }),
28
+ [maxTime, minTime, stepMinutes],
29
+ );
30
+
31
+ return (
32
+ <Stack gap="m" p="m" testID={testID ? `${testID}-content` : undefined}>
33
+ <PickerHeader description={description} title={label ?? 'Choose time'} />
34
+ <ScrollView style={styles.options}>
35
+ <Stack gap="xxs">
36
+ {options.map((option) => (
37
+ <TimePickerOption
38
+ formatTime={formatTime}
39
+ interactionPolicy={interactionPolicy}
40
+ key={option}
41
+ onSelect={onSelect}
42
+ option={option}
43
+ selected={option === value}
44
+ testID={testID}
45
+ />
46
+ ))}
47
+ </Stack>
48
+ </ScrollView>
49
+ <Button fullWidth interactionPolicy={interactionPolicy} onPress={onDismiss} variant="ghost">
50
+ Cancel
51
+ </Button>
52
+ </Stack>
53
+ );
54
+ }
55
+
56
+ interface TimePickerContentProps extends Pick<
57
+ TimePickerProps,
58
+ | 'description'
59
+ | 'formatTime'
60
+ | 'interactionPolicy'
61
+ | 'label'
62
+ | 'maxTime'
63
+ | 'minTime'
64
+ | 'stepMinutes'
65
+ | 'testID'
66
+ | 'value'
67
+ > {
68
+ onDismiss: () => void;
69
+ onSelect: (value: string) => void;
70
+ }
71
+
72
+ /*** Renders the shared title block used by picker overlays. */
73
+ function PickerHeader({
74
+ description,
75
+ title,
76
+ }: {
77
+ description: React.ReactNode | undefined;
78
+ title: React.ReactNode;
79
+ }) {
80
+ return (
81
+ <Stack gap="xxs">
82
+ <Text align="center" variant="label" weight="semiBold">
83
+ {title}
84
+ </Text>
85
+ {description ? (
86
+ <Text align="center" emphasis="muted" variant="bodySmall">
87
+ {description}
88
+ </Text>
89
+ ) : null}
90
+ </Stack>
91
+ );
92
+ }
93
+
94
+ /*** Renders one accessible selectable time option. */
95
+ function TimePickerOption({
96
+ formatTime,
97
+ interactionPolicy,
98
+ onSelect,
99
+ option,
100
+ selected,
101
+ testID,
102
+ }: Pick<TimePickerContentProps, 'formatTime' | 'interactionPolicy' | 'testID'> & {
103
+ onSelect: (value: string) => void;
104
+ option: string;
105
+ selected: boolean;
106
+ }) {
107
+ return (
108
+ <Button
109
+ accessibilityState={{ selected }}
110
+ fullWidth
111
+ interactionPolicy={interactionPolicy}
112
+ onPress={() => onSelect(option)}
113
+ testID={testID ? `${testID}-option-${option}` : undefined}
114
+ variant={selected ? 'soft' : 'ghost'}
115
+ >
116
+ {formatTime ? formatTime(option) : option}
117
+ </Button>
118
+ );
119
+ }
120
+
121
+ /*** Parses one HH:mm value to minutes since midnight. */
122
+ function parseTimeToMinutes(value: string | undefined): number | undefined {
123
+ if (!value) return undefined;
124
+ const match = /^(\d{2}):(\d{2})$/.exec(value);
125
+ if (!match) return undefined;
126
+
127
+ const hours = Number(match[1]);
128
+ const minutes = Number(match[2]);
129
+ if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return undefined;
130
+ return hours * 60 + minutes;
131
+ }
132
+
133
+ /*** Formats minutes since midnight as HH:mm. */
134
+ function formatMinutes(value: number): string {
135
+ const hours = Math.floor(value / 60);
136
+ const minutes = value % 60;
137
+ return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
138
+ }
139
+
140
+ /*** Normalizes the configured time step to a positive whole minute. */
141
+ function resolveStepMinutes(stepMinutes: number | undefined): number {
142
+ if (stepMinutes === undefined || !Number.isFinite(stepMinutes) || stepMinutes <= 0) {
143
+ return DEFAULT_STEP_MINUTES;
144
+ }
145
+ return Math.max(1, Math.floor(stepMinutes));
146
+ }
147
+
148
+ /*** Generates selectable HH:mm values within the configured range. */
149
+ function generateTimeOptions({
150
+ minTime,
151
+ maxTime,
152
+ stepMinutes,
153
+ }: Pick<TimePickerProps, 'maxTime' | 'minTime' | 'stepMinutes'>): string[] {
154
+ const step = resolveStepMinutes(stepMinutes);
155
+ const min = parseTimeToMinutes(minTime) ?? 0;
156
+ const max = parseTimeToMinutes(maxTime) ?? MINUTES_PER_DAY - 1;
157
+ return Array.from({ length: Math.ceil(MINUTES_PER_DAY / step) }, (_, index) => index * step)
158
+ .filter((minute) => minute >= min && minute <= max)
159
+ .map(formatMinutes);
160
+ }
161
+
162
+ const styles = StyleSheet.create({
163
+ options: { maxHeight: 360 },
164
+ });
@@ -0,0 +1,53 @@
1
+ import { Field } from '@ankhorage/surface';
2
+ import type React from 'react';
3
+
4
+ import type { TimePickerProps } from '../../../types/time-picker';
5
+ import { Stack } from '../../layout/public';
6
+ import { Text } from '../../typography/public';
7
+
8
+ /*** Renders the shared TimePicker field frame around a platform-specific trigger. */
9
+ export function TimePickerField({
10
+ children,
11
+ description,
12
+ disabled,
13
+ error,
14
+ label,
15
+ required,
16
+ testID,
17
+ }: TimePickerFieldProps) {
18
+ return (
19
+ <Field
20
+ disabled={disabled}
21
+ errorText={error}
22
+ invalid={Boolean(error)}
23
+ label={label ? renderLabel(label, description) : undefined}
24
+ required={required}
25
+ testID={testID}
26
+ >
27
+ {children}
28
+ </Field>
29
+ );
30
+ }
31
+
32
+ interface TimePickerFieldProps extends Pick<
33
+ TimePickerProps,
34
+ 'description' | 'disabled' | 'error' | 'label' | 'required' | 'testID'
35
+ > {
36
+ children: React.ReactNode;
37
+ }
38
+
39
+ /*** Renders the picker label and optional supporting description. */
40
+ function renderLabel(label: React.ReactNode, description: React.ReactNode | undefined) {
41
+ return (
42
+ <Stack gap="xs">
43
+ <Text variant="label" weight="semiBold">
44
+ {label}
45
+ </Text>
46
+ {description ? (
47
+ <Text emphasis="muted" variant="bodySmall">
48
+ {description}
49
+ </Text>
50
+ ) : null}
51
+ </Stack>
52
+ );
53
+ }
@@ -0,0 +1,35 @@
1
+ import type { TimePickerProps } from '../../../types/time-picker';
2
+ import { Button } from '../../button/public';
3
+
4
+ /*** Renders the shared TimePicker trigger button for native and web hosts. */
5
+ export function TimePickerTrigger({
6
+ disabled,
7
+ formatTime,
8
+ interactionPolicy,
9
+ onPress,
10
+ placeholder = 'Choose time',
11
+ testID,
12
+ value,
13
+ }: TimePickerTriggerProps) {
14
+ const displayValue = value ? (formatTime ? formatTime(value) : value) : placeholder;
15
+
16
+ return (
17
+ <Button
18
+ disabled={disabled}
19
+ interactionPolicy={interactionPolicy}
20
+ onPress={onPress}
21
+ testID={testID ? `${testID}-trigger` : undefined}
22
+ trailingIcon={{ name: 'chevron-down-outline' }}
23
+ variant="outline"
24
+ >
25
+ {displayValue}
26
+ </Button>
27
+ );
28
+ }
29
+
30
+ interface TimePickerTriggerProps extends Pick<
31
+ TimePickerProps,
32
+ 'disabled' | 'formatTime' | 'interactionPolicy' | 'placeholder' | 'testID' | 'value'
33
+ > {
34
+ onPress: () => void;
35
+ }
@@ -2,13 +2,24 @@ import { expect, test } from 'bun:test';
2
2
 
3
3
  import { ZORA_COMPONENT_META } from '../../metadata/componentMeta';
4
4
 
5
- test('TimePicker exposes a serializable manifest contract through the shared BottomSheet', async () => {
6
- const source = await Bun.file('src/features/time-picker/adapters/inbound/TimePicker.tsx').text();
5
+ test('TimePicker keeps one manifest contract across native and web presentation hosts', async () => {
6
+ const nativeSource = await Bun.file(
7
+ 'src/features/time-picker/adapters/inbound/TimePicker.native.tsx',
8
+ ).text();
9
+ const webSource = await Bun.file(
10
+ 'src/features/time-picker/adapters/inbound/TimePicker.web.tsx',
11
+ ).text();
12
+ const contentSource = await Bun.file(
13
+ 'src/features/time-picker/composition/TimePickerContent.tsx',
14
+ ).text();
7
15
  const meta = ZORA_COMPONENT_META.TimePicker;
8
16
 
9
17
  expect(meta.directManifestNode).toBe(true);
10
18
  expect(meta.props.value?.type).toBe('string');
11
19
  expect(meta.events?.valueChange?.eventType).toBe('timePicker.valueChange');
12
20
  expect(meta.bindings?.props?.value?.value.type).toBe('string');
13
- expect(source).toContain('useBottomSheet()');
21
+ expect(nativeSource).toContain('useBottomSheet()');
22
+ expect(webSource).toContain('<Popover');
23
+ expect(webSource).not.toContain('useBottomSheet');
24
+ expect(contentSource).toContain('TimePickerOption');
14
25
  });
@@ -0,0 +1,13 @@
1
+ import { Toast as SurfaceToast } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { withZoraThemeScope } from '../../../../theme/withZoraThemeScope';
5
+ import type { ToastProps } from '../../../../types/toast';
6
+
7
+ /*** Renders a toast notification message with ZORA theme scoping. */
8
+ export const Toast = withZoraThemeScope(ToastInner);
9
+
10
+ /*** Applies the ZORA theme scope before delegating toast presentation to Surface. */
11
+ function ToastInner({ mode: _mode, themeId: _themeId, ...props }: ToastProps) {
12
+ return <SurfaceToast {...props} />;
13
+ }
@@ -0,0 +1,9 @@
1
+ import { ToastProvider as SurfaceToastProvider } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import type { ToastProviderProps } from '../../../../types/toast';
5
+
6
+ /*** Provides toast state and rendering context through the Surface runtime host. */
7
+ export function ToastProvider(props: ToastProviderProps) {
8
+ return <SurfaceToastProvider {...props} />;
9
+ }
@@ -0,0 +1,19 @@
1
+ import { expect, test } from 'bun:test';
2
+
3
+ import { ZORA_COMPONENT_META } from '../../metadata/componentMeta';
4
+
5
+ test('Toast stays imperative and installs its host only when the ZoraProvider capability is enabled', async () => {
6
+ const [publicSource, providerSource, webCapabilities, nativeCapabilities] = await Promise.all([
7
+ Bun.file('src/features/toast/public.ts').text(),
8
+ Bun.file('src/theme/ZoraProvider.tsx').text(),
9
+ Bun.file('src/theme/ZoraRuntimeCapabilities.web.tsx').text(),
10
+ Bun.file('src/theme/ZoraRuntimeCapabilities.native.tsx').text(),
11
+ ]);
12
+
13
+ expect(ZORA_COMPONENT_META.Toast?.directManifestNode).toBe(false);
14
+ expect(ZORA_COMPONENT_META.ToastProvider?.directManifestNode).toBe(false);
15
+ expect(publicSource).toContain("useToast } from '@ankhorage/surface'");
16
+ expect(providerSource).toContain('toast = false');
17
+ expect(webCapabilities).toContain('if (!toast)');
18
+ expect(nativeCapabilities).toContain('toast ?');
19
+ });
@@ -0,0 +1,10 @@
1
+ export type {
2
+ ToastController,
3
+ ToastOptions,
4
+ ToastProps,
5
+ ToastProviderProps,
6
+ ToastStatus,
7
+ } from '../../types/toast';
8
+ export { Toast } from './adapters/inbound/Toast';
9
+ export { ToastProvider } from './adapters/inbound/ToastProvider';
10
+ export { useToast } from '@ankhorage/surface';
@@ -0,0 +1,10 @@
1
+ import type { ZoraComponentMeta } from '../../metadata';
2
+
3
+ export const toastMeta = {
4
+ name: 'Toast',
5
+ category: 'component',
6
+ directManifestNode: false,
7
+ allowedChildren: [],
8
+ note: 'Transient feedback component exposed through the imperative toast workflow, not as a manifest node.',
9
+ props: {},
10
+ } as const satisfies ZoraComponentMeta;
@@ -0,0 +1,10 @@
1
+ import type { ZoraComponentMeta } from '../../metadata';
2
+
3
+ export const toastProviderMeta = {
4
+ name: 'ToastProvider',
5
+ category: 'component',
6
+ directManifestNode: false,
7
+ allowedChildren: [],
8
+ note: 'Provider for imperative toast feedback; application roots should normally enable the ZoraProvider toast capability.',
9
+ props: {},
10
+ } as const satisfies ZoraComponentMeta;
package/src/index.ts CHANGED
@@ -16,8 +16,6 @@ export type { SelectOption, SelectProps } from './components/select';
16
16
  export { Select } from './components/select';
17
17
  export type { TabItem, TabsProps, TabsVariant } from './components/tabs';
18
18
  export { Tabs } from './components/tabs';
19
- export type { ToastOptions, ToastProps, ToastProviderProps, ToastStatus } from './components/toast';
20
- export { Toast, ToastProvider, useToast } from './components/toast';
21
19
  export type { ToolbarActionProps, ToolbarPosition, ToolbarProps } from './components/toolbar';
22
20
  export { Toolbar, ToolbarAction } from './components/toolbar';
23
21
  export { ZORA_CORE_PLUGIN } from './corePlugin';
@@ -247,6 +245,14 @@ export {
247
245
  type TimePickerProps,
248
246
  type TimePickerValue,
249
247
  } from './features/time-picker/public';
248
+ export type {
249
+ ToastController,
250
+ ToastOptions,
251
+ ToastProps,
252
+ ToastProviderProps,
253
+ ToastStatus,
254
+ } from './features/toast/public';
255
+ export { Toast, ToastProvider, useToast } from './features/toast/public';
250
256
  export type {
251
257
  HeadingAlign,
252
258
  HeadingColor,
@@ -7,7 +7,6 @@ import { ratingMeta } from '../components/rating/meta';
7
7
  import { searchBarMeta } from '../components/search-bar/meta';
8
8
  import { selectMeta } from '../components/select/meta';
9
9
  import { tabsMeta } from '../components/tabs/meta';
10
- import { toastMeta, toastProviderMeta } from '../components/toast/meta';
11
10
  import { toolbarActionMeta, toolbarMeta } from '../components/toolbar/meta';
12
11
  import { accordionItemMeta } from '../features/accordion/accordionItemMeta';
13
12
  import { accordionMeta } from '../features/accordion/accordionMeta';
@@ -65,6 +64,8 @@ import {
65
64
  } from '../features/skeleton/skeletonMeta';
66
65
  import { surfaceMeta } from '../features/surface/surfaceMeta';
67
66
  import { timePickerMeta } from '../features/time-picker/timePickerMeta';
67
+ import { toastMeta } from '../features/toast/toastMeta';
68
+ import { toastProviderMeta } from '../features/toast/toastProviderMeta';
68
69
  import { headingMeta } from '../features/typography/headingMeta';
69
70
  import { textMeta } from '../features/typography/textMeta';
70
71
  import { uploaderMeta } from '../features/uploader/uploaderMeta';
@@ -10,21 +10,21 @@ type SelectionContextValue = UseSelectionResult;
10
10
 
11
11
  const SelectionContext = React.createContext<SelectionContextValue | null>(null);
12
12
 
13
+ /*** Resolve the effective selection mode. */
13
14
  function resolveMode(mode: SelectionMode | undefined): SelectionMode {
14
15
  return mode ?? 'single';
15
16
  }
16
17
 
18
+ /*** Resolve the effective disabled state. */
17
19
  function resolveDisabled(disabled: boolean | undefined): boolean {
18
20
  return disabled ?? false;
19
21
  }
20
22
 
21
23
  /***
22
24
  * Accesses selection state provided by `SelectionProvider`.
23
- *
24
-
25
25
  */
26
26
  export function useSelection(): UseSelectionResult {
27
- const value = React.useContext(SelectionContext);
27
+ const value = React.use(SelectionContext);
28
28
  if (!value) {
29
29
  throw new Error(MISSING_CONTEXT_MESSAGE);
30
30
  }
@@ -34,8 +34,6 @@ export function useSelection(): UseSelectionResult {
34
34
 
35
35
  /***
36
36
  * Provides selection state for building selectable lists and grids.
37
- *
38
-
39
37
  */
40
38
  export function SelectionProvider({
41
39
  children,
@@ -110,5 +108,5 @@ export function SelectionProvider({
110
108
  };
111
109
  }, [clear, currentNormalizedIds, resolvedDisabled, resolvedMode, select, selectedIdSet, toggle]);
112
110
 
113
- return <SelectionContext.Provider value={value}>{children}</SelectionContext.Provider>;
111
+ return <SelectionContext value={value}>{children}</SelectionContext>;
114
112
  }
package/src/registry.ts CHANGED
@@ -10,7 +10,6 @@ import { Rating } from './components/rating';
10
10
  import { SearchBar } from './components/search-bar';
11
11
  import { Select } from './components/select';
12
12
  import { Tabs } from './components/tabs';
13
- import { Toast } from './components/toast';
14
13
  import { Toolbar, ToolbarAction } from './components/toolbar';
15
14
  import { Accordion, AccordionItem } from './features/accordion/public';
16
15
  import { ActivityIndicator } from './features/activity-indicator/public';
@@ -56,6 +55,7 @@ import { Progress, ProgressRing } from './features/progress/public';
56
55
  import { Skeleton, SkeletonCard, SkeletonList, SkeletonText } from './features/skeleton/public';
57
56
  import { Surface } from './features/surface/public';
58
57
  import { TimePicker } from './features/time-picker/public';
58
+ import { Toast } from './features/toast/public';
59
59
  import { Heading } from './features/typography/public';
60
60
  import { Text } from './features/typography/public';
61
61
  import { Uploader } from './features/uploader/public';
@@ -1,26 +1,22 @@
1
- import type { ThemeConfig } from '@ankhorage/contracts';
2
1
  import { ThemeProvider } from '@ankhorage/surface';
3
- import { BottomSheetProvider } from '@ankhorage/surface/bottom-sheet';
4
2
  import React from 'react';
5
3
 
4
+ import type { ZoraProviderProps } from '../types/provider';
6
5
  import { createZoraThemeConfig } from './createZoraThemeConfig';
7
- import type { ZoraTheme, ZoraThemeMode } from './types';
8
6
  import { zoraDefaultTheme } from './zoraDefaultTheme';
7
+ import { ZoraRuntimeCapabilities } from './ZoraRuntimeCapabilities';
9
8
  import { ZoraThemeRuntimeContext } from './ZoraThemeRuntimeContext';
10
9
 
11
- export interface ZoraProviderProps {
12
- children: React.ReactNode;
13
- theme?: ZoraTheme;
14
- themeConfig?: ThemeConfig;
15
- initialMode?: ZoraThemeMode;
16
- }
10
+ export type { ZoraProviderProps, ZoraToastCapability } from '../types/provider';
17
11
 
18
- /** Installs the ZORA theme runtime, Surface theme, and shared bottom-sheet controller. */
12
+ /*** Installs the core ZORA theme runtime and explicitly enabled optional capabilities. */
19
13
  export function ZoraProvider({
14
+ bottomSheet = false,
20
15
  children,
21
16
  theme = zoraDefaultTheme,
22
17
  themeConfig,
23
18
  initialMode = 'light',
19
+ toast = false,
24
20
  }: ZoraProviderProps) {
25
21
  const resolvedConfig = React.useMemo(
26
22
  () => themeConfig ?? createZoraThemeConfig(theme),
@@ -29,10 +25,12 @@ export function ZoraProvider({
29
25
  const runtimeValue = React.useMemo(() => ({ themeId: resolvedConfig.id }), [resolvedConfig.id]);
30
26
 
31
27
  return (
32
- <ZoraThemeRuntimeContext.Provider value={runtimeValue}>
28
+ <ZoraThemeRuntimeContext value={runtimeValue}>
33
29
  <ThemeProvider initialConfig={resolvedConfig} initialMode={initialMode}>
34
- <BottomSheetProvider>{children}</BottomSheetProvider>
30
+ <ZoraRuntimeCapabilities bottomSheet={bottomSheet} toast={toast}>
31
+ {children}
32
+ </ZoraRuntimeCapabilities>
35
33
  </ThemeProvider>
36
- </ZoraThemeRuntimeContext.Provider>
34
+ </ZoraThemeRuntimeContext>
37
35
  );
38
36
  }
@@ -0,0 +1,19 @@
1
+ import { ToastProvider as SurfaceToastProvider } from '@ankhorage/surface';
2
+ import { BottomSheetProvider } from '@ankhorage/surface/bottom-sheet';
3
+
4
+ import type { ZoraRuntimeCapabilitiesProps } from '../types/provider';
5
+
6
+ /*** Installs optional native runtime capabilities without forcing unused hosts. */
7
+ export function ZoraRuntimeCapabilities({
8
+ bottomSheet,
9
+ children,
10
+ toast,
11
+ }: ZoraRuntimeCapabilitiesProps) {
12
+ const toastContent = toast ? (
13
+ <SurfaceToastProvider {...(toast === true ? {} : toast)}>{children}</SurfaceToastProvider>
14
+ ) : (
15
+ children
16
+ );
17
+
18
+ return bottomSheet ? <BottomSheetProvider>{toastContent}</BottomSheetProvider> : toastContent;
19
+ }
@@ -0,0 +1 @@
1
+ export { ZoraRuntimeCapabilities } from './ZoraRuntimeCapabilities.web';
@@ -0,0 +1,10 @@
1
+ import { ToastProvider as SurfaceToastProvider } from '@ankhorage/surface';
2
+
3
+ import type { ZoraRuntimeCapabilitiesProps } from '../types/provider';
4
+
5
+ /*** Installs only web-relevant optional runtime capabilities. */
6
+ export function ZoraRuntimeCapabilities({ children, toast }: ZoraRuntimeCapabilitiesProps) {
7
+ if (!toast) return children;
8
+
9
+ return <SurfaceToastProvider {...(toast === true ? {} : toast)}>{children}</SurfaceToastProvider>;
10
+ }
@@ -1,4 +1,4 @@
1
- import { createContext, useContext } from 'react';
1
+ import { createContext, use } from 'react';
2
2
 
3
3
  import type { ZoraThemeId } from './types';
4
4
  import { zoraDefaultTheme } from './zoraDefaultTheme';
@@ -11,6 +11,7 @@ export const ZoraThemeRuntimeContext = createContext<ZoraThemeRuntime>({
11
11
  themeId: zoraDefaultTheme.id,
12
12
  });
13
13
 
14
+ /*** Returns the active ZORA theme runtime. */
14
15
  export function useZoraThemeRuntime(): ZoraThemeRuntime {
15
- return useContext(ZoraThemeRuntimeContext);
16
+ return use(ZoraThemeRuntimeContext);
16
17
  }
@@ -11,6 +11,17 @@ export interface ZoraThemeScopeProps {
11
11
  mode?: ZoraThemeMode;
12
12
  }
13
13
 
14
+ /*** Applies nested ZORA theme overrides without installing another Surface theme runtime. */
15
+ export function ZoraThemeScope({ children, themeId, mode }: ZoraThemeScopeProps) {
16
+ if (mode === undefined && themeId === undefined) return children;
17
+ return (
18
+ <ZoraThemeScopeInner mode={mode} themeId={themeId}>
19
+ {children}
20
+ </ZoraThemeScopeInner>
21
+ );
22
+ }
23
+
24
+ /*** Resolves and provides the scoped Surface and ZORA theme runtime values. */
14
25
  function ZoraThemeScopeInner({ children, themeId, mode }: ZoraThemeScopeProps) {
15
26
  const parentSurface = useTheme();
16
27
  const parentRuntime = useZoraThemeRuntime();
@@ -38,17 +49,8 @@ function ZoraThemeScopeInner({ children, themeId, mode }: ZoraThemeScopeProps) {
38
49
  const scopedRuntimeValue = useMemo(() => ({ themeId: scopedThemeId }), [scopedThemeId]);
39
50
 
40
51
  return (
41
- <ZoraThemeRuntimeContext.Provider value={scopedRuntimeValue}>
42
- <ThemeContext.Provider value={scopedSurfaceValue}>{children}</ThemeContext.Provider>
43
- </ZoraThemeRuntimeContext.Provider>
44
- );
45
- }
46
-
47
- export function ZoraThemeScope({ children, themeId, mode }: ZoraThemeScopeProps) {
48
- if (mode === undefined && themeId === undefined) return children;
49
- return (
50
- <ZoraThemeScopeInner mode={mode} themeId={themeId}>
51
- {children}
52
- </ZoraThemeScopeInner>
52
+ <ZoraThemeRuntimeContext value={scopedRuntimeValue}>
53
+ <ThemeContext value={scopedSurfaceValue}>{children}</ThemeContext>
54
+ </ZoraThemeRuntimeContext>
53
55
  );
54
56
  }
@@ -4,7 +4,7 @@ export type { ThemeModeToggleProps } from './ThemeModeToggleProps';
4
4
  export { useZoraTheme } from './useZoraTheme';
5
5
  export { withZoraThemeScope } from './withZoraThemeScope';
6
6
  export type { ZoraBaseProps } from './ZoraBaseProps';
7
- export type { ZoraProviderProps } from './ZoraProvider';
7
+ export type { ZoraProviderProps, ZoraToastCapability } from './ZoraProvider';
8
8
  export { ZoraProvider } from './ZoraProvider';
9
9
  export type { ZoraThemeScopeProps } from './ZoraThemeScope';
10
10
  export { ZoraThemeScope } from './ZoraThemeScope';