@ankhorage/zora 14.0.0 → 15.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 (249) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +176 -6
  3. package/dist/features/app-bar/adapters/inbound/AppBar.d.ts +5 -0
  4. package/dist/features/app-bar/adapters/inbound/AppBar.d.ts.map +1 -0
  5. package/dist/features/app-bar/adapters/inbound/AppBar.js +92 -0
  6. package/dist/features/app-bar/adapters/inbound/AppBar.js.map +1 -0
  7. package/dist/{components/app-bar/meta.d.ts → features/app-bar/appBarMeta.d.ts} +1 -1
  8. package/dist/features/app-bar/appBarMeta.d.ts.map +1 -0
  9. package/dist/{components/app-bar/meta.js → features/app-bar/appBarMeta.js} +1 -1
  10. package/dist/features/app-bar/appBarMeta.js.map +1 -0
  11. package/dist/features/app-bar/public.d.ts +3 -0
  12. package/dist/features/app-bar/public.d.ts.map +1 -0
  13. package/dist/features/app-bar/public.js +2 -0
  14. package/dist/features/app-bar/public.js.map +1 -0
  15. package/dist/features/bottom-sheet/public.d.ts +2 -2
  16. package/dist/features/bottom-sheet/public.d.ts.map +1 -1
  17. package/dist/features/bottom-sheet/public.js +1 -1
  18. package/dist/features/bottom-sheet/public.js.map +1 -1
  19. package/dist/features/breadcrumbs/adapters/inbound/Breadcrumbs.d.ts +5 -0
  20. package/dist/features/breadcrumbs/adapters/inbound/Breadcrumbs.d.ts.map +1 -0
  21. package/dist/features/breadcrumbs/adapters/inbound/Breadcrumbs.js +81 -0
  22. package/dist/features/breadcrumbs/adapters/inbound/Breadcrumbs.js.map +1 -0
  23. package/dist/features/breadcrumbs/breadcrumbsMeta.d.ts +119 -0
  24. package/dist/features/breadcrumbs/breadcrumbsMeta.d.ts.map +1 -0
  25. package/dist/features/breadcrumbs/breadcrumbsMeta.js +94 -0
  26. package/dist/features/breadcrumbs/breadcrumbsMeta.js.map +1 -0
  27. package/dist/features/breadcrumbs/public.d.ts +3 -0
  28. package/dist/features/breadcrumbs/public.d.ts.map +1 -0
  29. package/dist/features/breadcrumbs/public.js +2 -0
  30. package/dist/features/breadcrumbs/public.js.map +1 -0
  31. package/dist/features/button/utils/resolveButtonThemeRecipe.d.ts +1 -1
  32. package/dist/features/data-table/adapters/inbound/DataTable.d.ts.map +1 -1
  33. package/dist/features/data-table/adapters/inbound/DataTable.js +4 -2
  34. package/dist/features/data-table/adapters/inbound/DataTable.js.map +1 -1
  35. package/dist/features/form/formFieldMeta.d.ts +1 -1
  36. package/dist/features/form/formFieldMeta.js +1 -1
  37. package/dist/features/form/formFieldMeta.js.map +1 -1
  38. package/dist/features/form/radio/utils/resolveRadioGroupThemeRecipe.d.ts +1 -1
  39. package/dist/features/form/select/adapters/inbound/Select.d.ts +2 -0
  40. package/dist/features/form/select/adapters/inbound/Select.d.ts.map +1 -0
  41. package/dist/features/form/select/adapters/inbound/Select.js +2 -0
  42. package/dist/features/form/select/adapters/inbound/Select.js.map +1 -0
  43. package/dist/features/form/select/adapters/inbound/Select.native.d.ts +5 -0
  44. package/dist/features/form/select/adapters/inbound/Select.native.d.ts.map +1 -0
  45. package/dist/features/form/select/adapters/inbound/Select.native.js +42 -0
  46. package/dist/features/form/select/adapters/inbound/Select.native.js.map +1 -0
  47. package/dist/features/form/select/adapters/inbound/Select.web.d.ts +5 -0
  48. package/dist/features/form/select/adapters/inbound/Select.web.d.ts.map +1 -0
  49. package/dist/features/form/select/adapters/inbound/Select.web.js +31 -0
  50. package/dist/features/form/select/adapters/inbound/Select.web.js.map +1 -0
  51. package/dist/features/form/select/composition/SelectField.d.ts +10 -0
  52. package/dist/features/form/select/composition/SelectField.d.ts.map +1 -0
  53. package/dist/features/form/select/composition/SelectField.js +8 -0
  54. package/dist/features/form/select/composition/SelectField.js.map +1 -0
  55. package/dist/features/form/select/composition/SelectOptionRow.d.ts +12 -0
  56. package/dist/features/form/select/composition/SelectOptionRow.d.ts.map +1 -0
  57. package/dist/features/form/select/composition/SelectOptionRow.js +9 -0
  58. package/dist/features/form/select/composition/SelectOptionRow.js.map +1 -0
  59. package/dist/features/form/select/composition/SelectTrigger.d.ts +10 -0
  60. package/dist/features/form/select/composition/SelectTrigger.d.ts.map +1 -0
  61. package/dist/features/form/select/composition/SelectTrigger.js +9 -0
  62. package/dist/features/form/select/composition/SelectTrigger.js.map +1 -0
  63. package/dist/features/form/select/composition/useSelectController.d.ts +8 -0
  64. package/dist/features/form/select/composition/useSelectController.d.ts.map +1 -0
  65. package/dist/features/form/select/composition/useSelectController.js +15 -0
  66. package/dist/features/form/select/composition/useSelectController.js.map +1 -0
  67. package/dist/features/form/select/public.d.ts +3 -0
  68. package/dist/features/form/select/public.d.ts.map +1 -0
  69. package/dist/features/form/select/public.js +2 -0
  70. package/dist/features/form/select/public.js.map +1 -0
  71. package/dist/features/form/select/selectMeta.d.ts +195 -0
  72. package/dist/features/form/select/selectMeta.d.ts.map +1 -0
  73. package/dist/features/form/select/selectMeta.js +152 -0
  74. package/dist/features/form/select/selectMeta.js.map +1 -0
  75. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.d.ts +5 -0
  76. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.d.ts.map +1 -0
  77. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.js +43 -0
  78. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.js.map +1 -0
  79. package/dist/features/popover-menu/popoverMenuMeta.d.ts +9 -0
  80. package/dist/features/popover-menu/popoverMenuMeta.d.ts.map +1 -0
  81. package/dist/features/popover-menu/popoverMenuMeta.js +9 -0
  82. package/dist/features/popover-menu/popoverMenuMeta.js.map +1 -0
  83. package/dist/features/popover-menu/public.d.ts +3 -0
  84. package/dist/features/popover-menu/public.d.ts.map +1 -0
  85. package/dist/features/popover-menu/public.js +2 -0
  86. package/dist/features/popover-menu/public.js.map +1 -0
  87. package/dist/features/typography/utils/resolveHeadingThemeRecipe.d.ts +1 -1
  88. package/dist/features/typography/utils/resolveTextThemeRecipe.d.ts +1 -1
  89. package/dist/index.d.ts +5 -8
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +4 -4
  92. package/dist/index.js.map +1 -1
  93. package/dist/layout/settings-layout/SettingsLayout.d.ts +0 -2
  94. package/dist/layout/settings-layout/SettingsLayout.d.ts.map +1 -1
  95. package/dist/layout/settings-layout/SettingsLayout.js +1 -3
  96. package/dist/layout/settings-layout/SettingsLayout.js.map +1 -1
  97. package/dist/metadata/bindableComponentMeta.d.ts +1 -2
  98. package/dist/metadata/bindableComponentMeta.d.ts.map +1 -1
  99. package/dist/metadata/bindableComponentMeta.js +1 -2
  100. package/dist/metadata/bindableComponentMeta.js.map +1 -1
  101. package/dist/metadata/componentMeta.d.ts.map +1 -1
  102. package/dist/metadata/componentMeta.js +5 -6
  103. package/dist/metadata/componentMeta.js.map +1 -1
  104. package/dist/patterns/reader/ReaderSurface.js +1 -1
  105. package/dist/patterns/reader/ReaderSurface.js.map +1 -1
  106. package/dist/patterns/theme-composer/ThemeComposer.js +1 -1
  107. package/dist/patterns/theme-composer/ThemeComposer.js.map +1 -1
  108. package/dist/registry.d.ts.map +1 -1
  109. package/dist/registry.js +5 -6
  110. package/dist/registry.js.map +1 -1
  111. package/dist/{components/app-bar/types.d.ts → types/app-bar.d.ts} +8 -5
  112. package/dist/types/app-bar.d.ts.map +1 -0
  113. package/dist/types/app-bar.js +2 -0
  114. package/dist/types/app-bar.js.map +1 -0
  115. package/dist/{components/breadcrumbs/types.d.ts → types/breadcrumbs.d.ts} +8 -6
  116. package/dist/types/breadcrumbs.d.ts.map +1 -0
  117. package/dist/types/breadcrumbs.js +2 -0
  118. package/dist/types/breadcrumbs.js.map +1 -0
  119. package/dist/types/data-table.d.ts +2 -2
  120. package/dist/types/data-table.d.ts.map +1 -1
  121. package/dist/types/data-table.js.map +1 -1
  122. package/dist/types/popover-menu.d.ts +24 -0
  123. package/dist/types/popover-menu.d.ts.map +1 -0
  124. package/dist/types/popover-menu.js +2 -0
  125. package/dist/types/popover-menu.js.map +1 -0
  126. package/dist/types/select.d.ts +22 -0
  127. package/dist/types/select.d.ts.map +1 -0
  128. package/dist/types/select.js +2 -0
  129. package/dist/types/select.js.map +1 -0
  130. package/package.json +2 -4
  131. package/src/features/app-bar/adapters/inbound/AppBar.tsx +184 -0
  132. package/src/features/app-bar/public.ts +2 -0
  133. package/src/features/bottom-sheet/public.test.ts +10 -1
  134. package/src/features/bottom-sheet/public.ts +9 -1
  135. package/src/features/breadcrumbs/adapters/inbound/Breadcrumbs.tsx +179 -0
  136. package/src/features/breadcrumbs/breadcrumbsMeta.ts +95 -0
  137. package/src/features/breadcrumbs/public.test.ts +22 -0
  138. package/src/features/breadcrumbs/public.ts +6 -0
  139. package/src/features/data-table/adapters/inbound/DataTable.tsx +9 -5
  140. package/src/features/form/formFieldMeta.ts +1 -1
  141. package/src/features/form/select/adapters/inbound/Select.native.tsx +78 -0
  142. package/src/features/form/select/adapters/inbound/Select.tsx +1 -0
  143. package/src/features/form/select/adapters/inbound/Select.web.tsx +63 -0
  144. package/src/features/form/select/composition/SelectField.tsx +27 -0
  145. package/src/features/form/select/composition/SelectOptionRow.tsx +36 -0
  146. package/src/features/form/select/composition/SelectTrigger.tsx +32 -0
  147. package/src/features/form/select/composition/useSelectController.ts +21 -0
  148. package/src/features/form/select/public.test.ts +25 -0
  149. package/src/features/form/select/public.ts +2 -0
  150. package/src/features/form/select/selectMeta.ts +154 -0
  151. package/src/features/popover-menu/adapters/inbound/PopoverMenu.tsx +60 -0
  152. package/src/{components/breadcrumbs/meta.ts → features/popover-menu/popoverMenuMeta.ts} +3 -3
  153. package/src/features/popover-menu/public.test.ts +11 -0
  154. package/src/features/popover-menu/public.ts +6 -0
  155. package/src/index.ts +15 -8
  156. package/src/interactionPolicy.test.ts +24 -22
  157. package/src/layout/settings-layout/SettingsLayout.tsx +1 -3
  158. package/src/metadata/bindableComponentMeta.test.ts +8 -0
  159. package/src/metadata/bindableComponentMeta.ts +1 -2
  160. package/src/metadata/componentMeta.test.ts +3 -0
  161. package/src/metadata/componentMeta.ts +5 -6
  162. package/src/nativePeerContract.test.ts +5 -19
  163. package/src/patterns/reader/ReaderSurface.tsx +1 -1
  164. package/src/patterns/theme-composer/ThemeComposer.tsx +1 -1
  165. package/src/platformAcceptance.test.ts +1 -1
  166. package/src/registry.ts +5 -6
  167. package/src/theme/themeScopeStructure.test.ts +10 -5
  168. package/src/{components/app-bar/types.ts → types/app-bar.ts} +7 -4
  169. package/src/{components/breadcrumbs/types.ts → types/breadcrumbs.ts} +8 -5
  170. package/src/types/data-table.ts +2 -2
  171. package/src/types/popover-menu.ts +31 -0
  172. package/src/{components/select/types.ts → types/select.ts} +11 -3
  173. package/dist/components/app-bar/AppBar.d.ts +0 -7
  174. package/dist/components/app-bar/AppBar.d.ts.map +0 -1
  175. package/dist/components/app-bar/AppBar.js +0 -67
  176. package/dist/components/app-bar/AppBar.js.map +0 -1
  177. package/dist/components/app-bar/index.d.ts +0 -3
  178. package/dist/components/app-bar/index.d.ts.map +0 -1
  179. package/dist/components/app-bar/index.js +0 -3
  180. package/dist/components/app-bar/index.js.map +0 -1
  181. package/dist/components/app-bar/meta.d.ts.map +0 -1
  182. package/dist/components/app-bar/meta.js.map +0 -1
  183. package/dist/components/app-bar/types.d.ts.map +0 -1
  184. package/dist/components/app-bar/types.js +0 -2
  185. package/dist/components/app-bar/types.js.map +0 -1
  186. package/dist/components/breadcrumbs/Breadcrumbs.d.ts +0 -7
  187. package/dist/components/breadcrumbs/Breadcrumbs.d.ts.map +0 -1
  188. package/dist/components/breadcrumbs/Breadcrumbs.js +0 -80
  189. package/dist/components/breadcrumbs/Breadcrumbs.js.map +0 -1
  190. package/dist/components/breadcrumbs/index.d.ts +0 -3
  191. package/dist/components/breadcrumbs/index.d.ts.map +0 -1
  192. package/dist/components/breadcrumbs/index.js +0 -2
  193. package/dist/components/breadcrumbs/index.js.map +0 -1
  194. package/dist/components/breadcrumbs/meta.d.ts +0 -9
  195. package/dist/components/breadcrumbs/meta.d.ts.map +0 -1
  196. package/dist/components/breadcrumbs/meta.js +0 -9
  197. package/dist/components/breadcrumbs/meta.js.map +0 -1
  198. package/dist/components/breadcrumbs/types.d.ts.map +0 -1
  199. package/dist/components/breadcrumbs/types.js +0 -2
  200. package/dist/components/breadcrumbs/types.js.map +0 -1
  201. package/dist/components/menu/DropdownMenu.d.ts +0 -7
  202. package/dist/components/menu/DropdownMenu.d.ts.map +0 -1
  203. package/dist/components/menu/DropdownMenu.js +0 -9
  204. package/dist/components/menu/DropdownMenu.js.map +0 -1
  205. package/dist/components/menu/Menu.d.ts +0 -7
  206. package/dist/components/menu/Menu.d.ts.map +0 -1
  207. package/dist/components/menu/Menu.js +0 -46
  208. package/dist/components/menu/Menu.js.map +0 -1
  209. package/dist/components/menu/index.d.ts +0 -4
  210. package/dist/components/menu/index.d.ts.map +0 -1
  211. package/dist/components/menu/index.js +0 -3
  212. package/dist/components/menu/index.js.map +0 -1
  213. package/dist/components/menu/meta.d.ts +0 -17
  214. package/dist/components/menu/meta.d.ts.map +0 -1
  215. package/dist/components/menu/meta.js +0 -17
  216. package/dist/components/menu/meta.js.map +0 -1
  217. package/dist/components/menu/types.d.ts +0 -22
  218. package/dist/components/menu/types.d.ts.map +0 -1
  219. package/dist/components/menu/types.js +0 -2
  220. package/dist/components/menu/types.js.map +0 -1
  221. package/dist/components/select/Select.d.ts +0 -7
  222. package/dist/components/select/Select.d.ts.map +0 -1
  223. package/dist/components/select/Select.js +0 -32
  224. package/dist/components/select/Select.js.map +0 -1
  225. package/dist/components/select/index.d.ts +0 -3
  226. package/dist/components/select/index.d.ts.map +0 -1
  227. package/dist/components/select/index.js +0 -3
  228. package/dist/components/select/index.js.map +0 -1
  229. package/dist/components/select/meta.d.ts +0 -9
  230. package/dist/components/select/meta.d.ts.map +0 -1
  231. package/dist/components/select/meta.js +0 -9
  232. package/dist/components/select/meta.js.map +0 -1
  233. package/dist/components/select/types.d.ts +0 -14
  234. package/dist/components/select/types.d.ts.map +0 -1
  235. package/dist/components/select/types.js +0 -2
  236. package/dist/components/select/types.js.map +0 -1
  237. package/src/components/app-bar/AppBar.tsx +0 -140
  238. package/src/components/app-bar/index.ts +0 -2
  239. package/src/components/breadcrumbs/Breadcrumbs.tsx +0 -147
  240. package/src/components/breadcrumbs/index.ts +0 -2
  241. package/src/components/menu/DropdownMenu.tsx +0 -11
  242. package/src/components/menu/Menu.tsx +0 -59
  243. package/src/components/menu/index.ts +0 -3
  244. package/src/components/menu/meta.ts +0 -19
  245. package/src/components/menu/types.ts +0 -31
  246. package/src/components/select/Select.tsx +0 -67
  247. package/src/components/select/index.ts +0 -2
  248. package/src/components/select/meta.ts +0 -10
  249. /package/src/{components/app-bar/meta.ts → features/app-bar/appBarMeta.ts} +0 -0
@@ -2,7 +2,6 @@ import type { InteractionPolicy } from '@ankhorage/surface';
2
2
  import React from 'react';
3
3
  import { ScrollView, type ViewStyle } from 'react-native';
4
4
 
5
- import { DropdownMenu, type MenuAction } from '../../../../components/menu';
6
5
  import { Show } from '../../../../foundation';
7
6
  import { withZoraThemeScope } from '../../../../theme/withZoraThemeScope';
8
7
  import type {
@@ -19,6 +18,7 @@ import { IconButton } from '../../../button/public';
19
18
  import { Card } from '../../../card/public';
20
19
  import { EmptyState } from '../../../empty-state/public';
21
20
  import { Box, Stack } from '../../../layout/public';
21
+ import { PopoverMenu, type PopoverMenuAction } from '../../../popover-menu/public';
22
22
  import { SkeletonList } from '../../../skeleton/public';
23
23
  import { Text, type TextAlign } from '../../../typography/public';
24
24
  import { resolveDataTableRowKey } from '../../utils/resolveDataTableRowKey';
@@ -139,10 +139,11 @@ function resolveNextSortDirection(
139
139
  return current === 'asc' ? 'desc' : 'asc';
140
140
  }
141
141
 
142
+ /*** Maps row-scoped DataTable actions into the shared PopoverMenu action contract. */
142
143
  function mapRowActions<TRow extends object>(
143
144
  row: TRow,
144
145
  actions: readonly DataTableRowAction<TRow>[],
145
- ): readonly MenuAction[] {
146
+ ): readonly PopoverMenuAction[] {
146
147
  return actions.map((action) => ({
147
148
  description: action.description,
148
149
  disabled: action.disabled,
@@ -154,6 +155,7 @@ function mapRowActions<TRow extends object>(
154
155
  }));
155
156
  }
156
157
 
158
+ /*** Renders the row action trigger and anchored PopoverMenu when actions exist. */
157
159
  function renderRowActions<TRow extends object>({
158
160
  row,
159
161
  rowIndex,
@@ -174,18 +176,20 @@ function renderRowActions<TRow extends object>({
174
176
  }
175
177
 
176
178
  return (
177
- <DropdownMenu
179
+ <PopoverMenu
178
180
  actions={mapRowActions(row, actions)}
179
181
  interactionPolicy={interactionPolicy}
180
182
  testID={testID ? `${testID}-row-actions-${rowIndex}` : undefined}
181
- trigger={
183
+ trigger={({ toggle }) => (
182
184
  <IconButton
183
185
  icon={{ name: 'ellipsis-horizontal' }}
186
+ interactionPolicy={interactionPolicy}
184
187
  label="Row actions"
188
+ onPress={toggle}
185
189
  size="s"
186
190
  variant="ghost"
187
191
  />
188
- }
192
+ )}
189
193
  />
190
194
  );
191
195
  }
@@ -3,7 +3,7 @@ export const formFieldMeta = {
3
3
  name: 'FormField',
4
4
  category: 'component',
5
5
  directManifestNode: true,
6
- allowedChildren: ['Checkbox', 'CheckboxGroup', 'RadioGroup', 'TextInput'],
6
+ allowedChildren: ['Checkbox', 'CheckboxGroup', 'RadioGroup', 'Select', 'TextInput'],
7
7
  blueprint: { label: 'Form field', defaultProps: { label: 'Label' } },
8
8
  bindings: {
9
9
  props: {
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import { type ListRenderItemInfo, StyleSheet } from 'react-native';
3
+
4
+ import { withZoraThemeScope } from '../../../../../theme/withZoraThemeScope';
5
+ import type { SelectOption, SelectProps } from '../../../../../types/select';
6
+ import { BottomSheetFlatList, useBottomSheet } from '../../../../bottom-sheet/public';
7
+ import { SelectField } from '../../composition/SelectField';
8
+ import { SelectOptionRow } from '../../composition/SelectOptionRow';
9
+ import { SelectTrigger } from '../../composition/SelectTrigger';
10
+ import { useSelectController } from '../../composition/useSelectController';
11
+
12
+ const SELECT_SHEET_MAX_HEIGHT = 480;
13
+
14
+ /*** Renders Select with BottomSheetFlatList presentation on native platforms. */
15
+ export const Select = withZoraThemeScope(SelectInner);
16
+
17
+ /*** Connects shared Select state and controls to the native BottomSheet host. */
18
+ function SelectInner<TValue extends string = string>({
19
+ themeId: _themeId,
20
+ mode: _mode,
21
+ ...props
22
+ }: SelectProps<TValue>) {
23
+ const { dismiss, present } = useBottomSheet();
24
+ const { select, selectedOption, value } = useSelectController(props);
25
+ const passive = props.interactionPolicy === 'passive';
26
+ const { disabled, interactionPolicy, options, readOnly, testID } = props;
27
+ const listData = React.useMemo(() => [...options], [options]);
28
+
29
+ const handleSelect = React.useCallback(
30
+ (nextValue: TValue) => {
31
+ select(nextValue);
32
+ dismiss();
33
+ },
34
+ [dismiss, select],
35
+ );
36
+
37
+ const renderOption = React.useCallback(
38
+ ({ item }: ListRenderItemInfo<SelectOption<TValue>>) => (
39
+ <SelectOptionRow
40
+ interactionPolicy={interactionPolicy}
41
+ onSelect={handleSelect}
42
+ option={item}
43
+ selected={value === item.value}
44
+ testID={testID}
45
+ />
46
+ ),
47
+ [handleSelect, interactionPolicy, testID, value],
48
+ );
49
+
50
+ const openSelect = React.useCallback(() => {
51
+ if (disabled || readOnly || passive) return;
52
+
53
+ present({
54
+ contentMode: 'direct',
55
+ maxDynamicContentSize: SELECT_SHEET_MAX_HEIGHT,
56
+ content: (
57
+ <BottomSheetFlatList
58
+ contentContainerStyle={styles.listContent}
59
+ data={listData}
60
+ keyExtractor={(item) => item.value}
61
+ renderItem={renderOption}
62
+ />
63
+ ),
64
+ });
65
+ }, [disabled, listData, passive, present, readOnly, renderOption]);
66
+
67
+ return (
68
+ <SelectField props={props}>
69
+ <SelectTrigger displayLabel={selectedOption?.label} onPress={openSelect} props={props} />
70
+ </SelectField>
71
+ );
72
+ }
73
+
74
+ const styles = StyleSheet.create({
75
+ listContent: {
76
+ padding: 16,
77
+ },
78
+ });
@@ -0,0 +1 @@
1
+ export { Select } from './Select.web';
@@ -0,0 +1,63 @@
1
+ import { Popover } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { withZoraThemeScope } from '../../../../../theme/withZoraThemeScope';
5
+ import type { SelectProps } from '../../../../../types/select';
6
+ import { Stack } from '../../../../layout/public';
7
+ import { Surface } from '../../../../surface/public';
8
+ import { SelectField } from '../../composition/SelectField';
9
+ import { SelectOptionRow } from '../../composition/SelectOptionRow';
10
+ import { SelectTrigger } from '../../composition/SelectTrigger';
11
+ import { useSelectController } from '../../composition/useSelectController';
12
+
13
+ /*** Renders Select with anchored Popover presentation on web. */
14
+ export const Select = withZoraThemeScope(SelectInner);
15
+
16
+ /*** Connects shared Select state and controls to the web Popover host. */
17
+ function SelectInner<TValue extends string = string>({
18
+ themeId: _themeId,
19
+ mode: _mode,
20
+ ...props
21
+ }: SelectProps<TValue>) {
22
+ const [open, setOpen] = React.useState(false);
23
+ const { select, selectedOption, value } = useSelectController(props);
24
+ const { interactionPolicy, options, testID } = props;
25
+
26
+ const selectAndClose = React.useCallback(
27
+ (nextValue: TValue) => {
28
+ select(nextValue);
29
+ setOpen(false);
30
+ },
31
+ [select],
32
+ );
33
+
34
+ return (
35
+ <SelectField props={props}>
36
+ <Popover
37
+ anchor={({ toggle }) => (
38
+ <SelectTrigger displayLabel={selectedOption?.label} onPress={toggle} props={props} />
39
+ )}
40
+ interactionPolicy={props.interactionPolicy}
41
+ onOpenChange={setOpen}
42
+ open={open}
43
+ placement="bottom-start"
44
+ testID={testID ? `${testID}-popover` : undefined}
45
+ >
46
+ <Surface variant="raised">
47
+ <Stack gap="xs" p="xs">
48
+ {options.map((option) => (
49
+ <SelectOptionRow
50
+ interactionPolicy={interactionPolicy}
51
+ key={option.value}
52
+ onSelect={selectAndClose}
53
+ option={option}
54
+ selected={value === option.value}
55
+ testID={testID}
56
+ />
57
+ ))}
58
+ </Stack>
59
+ </Surface>
60
+ </Popover>
61
+ </SelectField>
62
+ );
63
+ }
@@ -0,0 +1,27 @@
1
+ import { Field } from '@ankhorage/surface';
2
+ import type React from 'react';
3
+
4
+ import type { SelectProps } from '../../../../types/select';
5
+
6
+ /*** Wraps Select presentation in the canonical Surface form-field semantics. */
7
+ export function SelectField<TValue extends string>({ children, props }: SelectFieldProps<TValue>) {
8
+ return (
9
+ <Field
10
+ disabled={props.disabled}
11
+ errorText={props.errorText}
12
+ helperText={props.helperText}
13
+ invalid={props.invalid}
14
+ label={props.label}
15
+ readOnly={props.readOnly}
16
+ required={props.required}
17
+ testID={props.testID ? `${props.testID}-field` : undefined}
18
+ >
19
+ {children}
20
+ </Field>
21
+ );
22
+ }
23
+
24
+ interface SelectFieldProps<TValue extends string> {
25
+ children: React.ReactNode;
26
+ props: SelectProps<TValue>;
27
+ }
@@ -0,0 +1,36 @@
1
+ import type { SelectOption, SelectProps } from '../../../../types/select';
2
+ import { Button } from '../../../button/public';
3
+
4
+ const SELECTED_ICON = { name: 'checkmark' } as const;
5
+
6
+ /*** Renders one Select option with shared web/native selection semantics. */
7
+ export function SelectOptionRow<TValue extends string>({
8
+ interactionPolicy,
9
+ onSelect,
10
+ option,
11
+ selected,
12
+ testID,
13
+ }: SelectOptionRowProps<TValue>) {
14
+ return (
15
+ <Button
16
+ color={selected ? 'primary' : 'neutral'}
17
+ disabled={option.disabled}
18
+ fullWidth
19
+ interactionPolicy={interactionPolicy}
20
+ onPress={() => onSelect(option.value)}
21
+ testID={testID ? `${testID}-option-${option.value}` : undefined}
22
+ trailingIcon={selected ? SELECTED_ICON : undefined}
23
+ variant={selected ? 'soft' : 'ghost'}
24
+ >
25
+ {option.label}
26
+ </Button>
27
+ );
28
+ }
29
+
30
+ interface SelectOptionRowProps<TValue extends string> {
31
+ interactionPolicy: SelectProps<TValue>['interactionPolicy'];
32
+ onSelect: (value: TValue) => void;
33
+ option: SelectOption<TValue>;
34
+ selected: boolean;
35
+ testID: string | undefined;
36
+ }
@@ -0,0 +1,32 @@
1
+ import type { SelectProps } from '../../../../types/select';
2
+ import { Button } from '../../../button/public';
3
+
4
+ const SELECT_CHEVRON_ICON = { name: 'chevron-down' } as const;
5
+
6
+ /*** Renders the shared Select trigger with the resolved option label or placeholder. */
7
+ export function SelectTrigger<TValue extends string>({
8
+ displayLabel,
9
+ onPress,
10
+ props,
11
+ }: SelectTriggerProps<TValue>) {
12
+ return (
13
+ <Button
14
+ color={props.invalid ? 'danger' : 'neutral'}
15
+ disabled={props.disabled}
16
+ fullWidth
17
+ interactionPolicy={props.interactionPolicy}
18
+ onPress={props.readOnly ? undefined : onPress}
19
+ testID={props.testID ? `${props.testID}-trigger` : undefined}
20
+ trailingIcon={SELECT_CHEVRON_ICON}
21
+ variant="outline"
22
+ >
23
+ {displayLabel ?? props.placeholder ?? 'Select'}
24
+ </Button>
25
+ );
26
+ }
27
+
28
+ interface SelectTriggerProps<TValue extends string> {
29
+ displayLabel: string | undefined;
30
+ onPress: () => void;
31
+ props: SelectProps<TValue>;
32
+ }
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+
3
+ import type { SelectProps } from '../../../../types/select';
4
+
5
+ /*** Resolves controlled or uncontrolled Select value state and the selected option. */
6
+ export function useSelectController<TValue extends string>(props: SelectProps<TValue>) {
7
+ const { defaultValue, onValueChange, options, value: controlledValue } = props;
8
+ const [localValue, setLocalValue] = React.useState<TValue | undefined>(defaultValue);
9
+ const value = controlledValue ?? localValue;
10
+ const selectedOption = options.find((option) => option.value === value);
11
+
12
+ const select = React.useCallback(
13
+ (nextValue: TValue) => {
14
+ if (controlledValue === undefined) setLocalValue(nextValue);
15
+ onValueChange?.(nextValue);
16
+ },
17
+ [controlledValue, onValueChange],
18
+ );
19
+
20
+ return { select, selectedOption, value } as const;
21
+ }
@@ -0,0 +1,25 @@
1
+ import { expect, test } from 'bun:test';
2
+
3
+ import { selectMeta } from './selectMeta';
4
+
5
+ test('Select is a direct manifest node with valueChange payload', () => {
6
+ expect(selectMeta.directManifestNode).toBe(true);
7
+ expect(selectMeta.events.valueChange.eventType).toBe('select.valueChange');
8
+ expect(selectMeta.events.valueChange.payloadFields).toEqual([
9
+ { path: 'value', type: 'string', label: 'Value' },
10
+ ]);
11
+ });
12
+
13
+ test('Select uses web Popover and native BottomSheetFlatList presentation', async () => {
14
+ const [neutral, web, native] = await Promise.all([
15
+ Bun.file('src/features/form/select/adapters/inbound/Select.tsx').text(),
16
+ Bun.file('src/features/form/select/adapters/inbound/Select.web.tsx').text(),
17
+ Bun.file('src/features/form/select/adapters/inbound/Select.native.tsx').text(),
18
+ ]);
19
+
20
+ expect(neutral).toContain("export { Select } from './Select.web';");
21
+ expect(web).toContain("import { Popover } from '@ankhorage/surface';");
22
+ expect(native).toContain('<BottomSheetFlatList');
23
+ expect(native).toContain("contentMode: 'direct'");
24
+ expect(`${neutral}\n${web}\n${native}`).not.toContain('@react-native-picker/picker');
25
+ });
@@ -0,0 +1,2 @@
1
+ export type { SelectOption, SelectProps } from '../../../types/select';
2
+ export { Select } from './adapters/inbound/Select';
@@ -0,0 +1,154 @@
1
+ import type { ZoraComponentMeta } from '../../../metadata';
2
+
3
+ export const selectMeta = {
4
+ name: 'Select',
5
+ category: 'component',
6
+ description:
7
+ 'Single-value form selection with Popover presentation on web and BottomSheet presentation on native.',
8
+ directManifestNode: true,
9
+ allowedChildren: [],
10
+ blueprint: {
11
+ label: 'Select',
12
+ defaultProps: {
13
+ label: 'Option',
14
+ placeholder: 'Select',
15
+ defaultValue: 'option-b',
16
+ options: [
17
+ { value: 'option-a', label: 'First option' },
18
+ { value: 'option-b', label: 'Second option' },
19
+ { value: 'option-c', label: 'Third option' },
20
+ ],
21
+ disabled: false,
22
+ readOnly: false,
23
+ invalid: false,
24
+ },
25
+ },
26
+ bindings: {
27
+ props: {
28
+ value: {
29
+ label: 'Value',
30
+ description: 'Currently selected option value.',
31
+ value: { type: 'string' },
32
+ acceptsFallback: true,
33
+ acceptsTransforms: true,
34
+ },
35
+ disabled: {
36
+ label: 'Disabled',
37
+ description: 'Whether the select is disabled.',
38
+ value: { type: 'boolean' },
39
+ acceptsFallback: true,
40
+ acceptsTransforms: true,
41
+ },
42
+ },
43
+ events: {
44
+ valueChange: {
45
+ label: 'Value change',
46
+ description: 'Runs when the selected option value changes.',
47
+ payload: {
48
+ eventType: 'select.valueChange',
49
+ fields: [{ path: 'value', type: 'string', label: 'Value' }],
50
+ },
51
+ },
52
+ },
53
+ },
54
+ events: {
55
+ valueChange: {
56
+ label: 'Value change',
57
+ eventType: 'select.valueChange',
58
+ description: 'Emitted when the selected option value changes.',
59
+ payloadFields: [{ path: 'value', type: 'string', label: 'Value' }],
60
+ },
61
+ },
62
+ props: {
63
+ defaultValue: {
64
+ type: 'string',
65
+ category: 'State',
66
+ label: 'Initial value',
67
+ authoring: { authority: 'instance' },
68
+ },
69
+ value: {
70
+ type: 'string',
71
+ category: 'State',
72
+ label: 'Value',
73
+ authoring: { authority: 'instance' },
74
+ },
75
+ options: {
76
+ type: 'array',
77
+ category: 'Content',
78
+ label: 'Options',
79
+ itemSchema: [
80
+ {
81
+ key: 'value',
82
+ schema: { type: 'string', category: 'Content', label: 'Value' },
83
+ },
84
+ {
85
+ key: 'label',
86
+ schema: { type: 'string', category: 'Content', label: 'Label' },
87
+ },
88
+ {
89
+ key: 'disabled',
90
+ schema: {
91
+ type: 'boolean',
92
+ category: 'State',
93
+ label: 'Disabled',
94
+ default: false,
95
+ },
96
+ },
97
+ ],
98
+ authoring: { authority: 'instance' },
99
+ },
100
+ label: {
101
+ type: 'string',
102
+ category: 'Content',
103
+ label: 'Label',
104
+ authoring: { authority: 'instance' },
105
+ },
106
+ helperText: {
107
+ type: 'string',
108
+ category: 'Content',
109
+ label: 'Helper text',
110
+ authoring: { authority: 'instance' },
111
+ },
112
+ errorText: {
113
+ type: 'string',
114
+ category: 'Content',
115
+ label: 'Error text',
116
+ authoring: { authority: 'instance' },
117
+ },
118
+ placeholder: {
119
+ type: 'string',
120
+ category: 'Content',
121
+ label: 'Placeholder',
122
+ default: 'Select',
123
+ authoring: { authority: 'instance' },
124
+ },
125
+ required: {
126
+ type: 'boolean',
127
+ category: 'State',
128
+ label: 'Required',
129
+ default: false,
130
+ authoring: { authority: 'instance' },
131
+ },
132
+ disabled: {
133
+ type: 'boolean',
134
+ category: 'State',
135
+ label: 'Disabled',
136
+ default: false,
137
+ authoring: { authority: 'instance' },
138
+ },
139
+ readOnly: {
140
+ type: 'boolean',
141
+ category: 'State',
142
+ label: 'Read only',
143
+ default: false,
144
+ authoring: { authority: 'instance' },
145
+ },
146
+ invalid: {
147
+ type: 'boolean',
148
+ category: 'State',
149
+ label: 'Invalid',
150
+ default: false,
151
+ authoring: { authority: 'instance' },
152
+ },
153
+ },
154
+ } as const satisfies ZoraComponentMeta;
@@ -0,0 +1,60 @@
1
+ import {
2
+ PopoverMenu as SurfacePopoverMenu,
3
+ type PopoverMenuAction as SurfacePopoverMenuAction,
4
+ } from '@ankhorage/surface';
5
+ import React from 'react';
6
+
7
+ import { resolveIconSize } from '../../../../internal/recipes';
8
+ import { useZoraTheme } from '../../../../theme/useZoraTheme';
9
+ import { withZoraThemeScope } from '../../../../theme/withZoraThemeScope';
10
+ import type { PopoverMenuAction, PopoverMenuProps } from '../../../../types/popover-menu';
11
+ import { Icon } from '../../../icon/public';
12
+ import { Box } from '../../../layout/public';
13
+
14
+ /*** Presents a ZORA-styled action list through the Surface PopoverMenu capability. */
15
+ export const PopoverMenu = withZoraThemeScope(PopoverMenuInner);
16
+
17
+ /*** Maps ZORA action presentation into the Surface action-menu boundary. */
18
+ function PopoverMenuInner({
19
+ themeId: _themeId,
20
+ mode: _mode,
21
+ actions,
22
+ onDismiss,
23
+ ...props
24
+ }: PopoverMenuProps) {
25
+ const { theme } = useZoraTheme();
26
+ const iconColor = theme.semantics.content.muted;
27
+ const surfaceActions = React.useMemo(
28
+ () => actions.map((action) => toSurfaceAction(action, iconColor)),
29
+ [actions, iconColor],
30
+ );
31
+
32
+ return <SurfacePopoverMenu {...props} actions={surfaceActions} dismiss={onDismiss} />;
33
+ }
34
+
35
+ /*** Maps one ZORA PopoverMenu action to the Surface action contract. */
36
+ function toSurfaceAction(action: PopoverMenuAction, iconColor: string): SurfacePopoverMenuAction {
37
+ return {
38
+ activate: action.onPress,
39
+ description: action.description,
40
+ disabled: action.disabled,
41
+ id: action.id,
42
+ intent: action.intent,
43
+ leading: renderActionLeading(action, iconColor),
44
+ selected: action.selected,
45
+ title: action.title,
46
+ trailing: renderActionTrailing(action),
47
+ };
48
+ }
49
+
50
+ /*** Resolves explicit leading content or the configured action icon. */
51
+ function renderActionLeading(action: PopoverMenuAction, iconColor: string): React.ReactNode {
52
+ if (action.leading !== undefined) return action.leading;
53
+ if (action.icon === undefined) return undefined;
54
+ return <Icon {...action.icon} color={iconColor} size={resolveIconSize('s')} />;
55
+ }
56
+
57
+ /*** Wraps optional trailing action content in the ZORA layout boundary. */
58
+ function renderActionTrailing(action: PopoverMenuAction): React.ReactNode {
59
+ return action.trailing === undefined ? undefined : <Box>{action.trailing}</Box>;
60
+ }
@@ -1,10 +1,10 @@
1
1
  import type { ZoraComponentMeta } from '../../metadata';
2
2
 
3
- export const breadcrumbsMeta = {
4
- name: 'Breadcrumbs',
3
+ export const popoverMenuMeta = {
4
+ name: 'PopoverMenu',
5
5
  category: 'component',
6
6
  directManifestNode: false,
7
7
  allowedChildren: [],
8
- note: 'Code-facing route hierarchy component; not represented as a manifest node in v1.',
8
+ note: 'Code-facing anchored action menu; the trigger is a render prop and is not serialized as a direct manifest node.',
9
9
  props: {},
10
10
  } as const satisfies ZoraComponentMeta;
@@ -0,0 +1,11 @@
1
+ import { expect, test } from 'bun:test';
2
+
3
+ test('PopoverMenu composes the released Surface PopoverMenu capability', async () => {
4
+ const source = await Bun.file(
5
+ 'src/features/popover-menu/adapters/inbound/PopoverMenu.tsx',
6
+ ).text();
7
+
8
+ expect(source).toContain('PopoverMenu as SurfacePopoverMenu');
9
+ expect(source).toContain('<SurfacePopoverMenu');
10
+ expect(source).not.toContain('DropdownMenu');
11
+ });
@@ -0,0 +1,6 @@
1
+ export type {
2
+ PopoverMenuAction,
3
+ PopoverMenuActionIntent,
4
+ PopoverMenuProps,
5
+ } from '../../types/popover-menu';
6
+ export { PopoverMenu } from './adapters/inbound/PopoverMenu';
package/src/index.ts CHANGED
@@ -1,9 +1,3 @@
1
- export type { AppBarMode, AppBarOverflowAction, AppBarProps } from './components/app-bar';
2
- export { AppBar } from './components/app-bar';
3
- export type { BreadcrumbItem, BreadcrumbsProps } from './components/breadcrumbs';
4
- export { Breadcrumbs } from './components/breadcrumbs';
5
- export type { DropdownMenuProps, MenuAction, MenuActionIntent, MenuProps } from './components/menu';
6
- export { DropdownMenu, Menu } from './components/menu';
7
1
  export type { ModalProps } from './components/modal';
8
2
  export { Modal } from './components/modal';
9
3
  export type { PaginationProps } from './components/pagination';
@@ -12,8 +6,6 @@ export type { RatingProps } from './components/rating';
12
6
  export { Rating } from './components/rating';
13
7
  export type { SearchBarProps } from './components/search-bar';
14
8
  export { SearchBar } from './components/search-bar';
15
- export type { SelectOption, SelectProps } from './components/select';
16
- export { Select } from './components/select';
17
9
  export type { TabItem, TabsProps, TabsVariant } from './components/tabs';
18
10
  export { Tabs } from './components/tabs';
19
11
  export type { ToolbarActionProps, ToolbarPosition, ToolbarProps } from './components/toolbar';
@@ -33,6 +25,8 @@ export {
33
25
  ActivityIndicator,
34
26
  type ActivityIndicatorProps,
35
27
  } from './features/activity-indicator/public';
28
+ export type { AppBarMode, AppBarOverflowMenu, AppBarProps } from './features/app-bar/public';
29
+ export { AppBar } from './features/app-bar/public';
36
30
  export type {
37
31
  AuthFormBaseProps,
38
32
  AuthIdentifierKind,
@@ -70,6 +64,12 @@ export type { BadgeProps } from './features/badge/public';
70
64
  export { Badge } from './features/badge/public';
71
65
  export type { BottomSheetProps } from './features/bottom-sheet/public';
72
66
  export { BottomSheet } from './features/bottom-sheet/public';
67
+ export {
68
+ type BreadcrumbItem,
69
+ type BreadcrumbPressEvent,
70
+ Breadcrumbs,
71
+ type BreadcrumbsProps,
72
+ } from './features/breadcrumbs/public';
73
73
  export type { ButtonProps } from './features/button/public';
74
74
  export type {
75
75
  ButtonGroupAlign,
@@ -161,6 +161,7 @@ export {
161
161
  type RadioGroupProps,
162
162
  type RadioProps,
163
163
  } from './features/form/radio/public';
164
+ export { Select, type SelectOption, type SelectProps } from './features/form/select/public';
164
165
  export {
165
166
  TextInput,
166
167
  type TextInputProps,
@@ -219,6 +220,12 @@ export type {
219
220
  } from './features/list/public';
220
221
  export { FlatList, SectionList } from './features/list/public';
221
222
  export { MissingElement, type MissingElementProps } from './features/missing-element/public';
223
+ export {
224
+ PopoverMenu,
225
+ type PopoverMenuAction,
226
+ type PopoverMenuActionIntent,
227
+ type PopoverMenuProps,
228
+ } from './features/popover-menu/public';
222
229
  export {
223
230
  Progress,
224
231
  type ProgressProps,