@ankhorage/surface 4.4.1 → 5.0.1

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 (235) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +2 -2
  3. package/dist/features/bottom-sheet/BottomSheetProvider.js +8 -6
  4. package/dist/features/bottom-sheet/BottomSheetProvider.js.map +1 -1
  5. package/dist/features/bottom-sheet/public.d.ts +2 -1
  6. package/dist/features/bottom-sheet/public.d.ts.map +1 -1
  7. package/dist/features/bottom-sheet/public.js +1 -0
  8. package/dist/features/bottom-sheet/public.js.map +1 -1
  9. package/dist/{components/modal → features/modal/adapters/inbound}/Modal.d.ts +2 -1
  10. package/dist/features/modal/adapters/inbound/Modal.d.ts.map +1 -0
  11. package/dist/features/modal/adapters/inbound/Modal.js +51 -0
  12. package/dist/features/modal/adapters/inbound/Modal.js.map +1 -0
  13. package/dist/features/modal/public.d.ts +3 -0
  14. package/dist/features/modal/public.d.ts.map +1 -0
  15. package/dist/features/modal/public.js +2 -0
  16. package/dist/features/modal/public.js.map +1 -0
  17. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.d.ts +5 -0
  18. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.d.ts.map +1 -0
  19. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.js +85 -0
  20. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.js.map +1 -0
  21. package/dist/features/popover-menu/composition/usePopoverMenuController.d.ts +19 -0
  22. package/dist/features/popover-menu/composition/usePopoverMenuController.d.ts.map +1 -0
  23. package/dist/features/popover-menu/composition/usePopoverMenuController.js +35 -0
  24. package/dist/features/popover-menu/composition/usePopoverMenuController.js.map +1 -0
  25. package/dist/features/popover-menu/public.d.ts +3 -0
  26. package/dist/features/popover-menu/public.d.ts.map +1 -0
  27. package/dist/features/popover-menu/public.js +2 -0
  28. package/dist/features/popover-menu/public.js.map +1 -0
  29. package/dist/features/popover-menu/utils/resolveNextMenuIndex.d.ts +5 -0
  30. package/dist/features/popover-menu/utils/resolveNextMenuIndex.d.ts.map +1 -0
  31. package/dist/features/popover-menu/utils/resolveNextMenuIndex.js +18 -0
  32. package/dist/features/popover-menu/utils/resolveNextMenuIndex.js.map +1 -0
  33. package/dist/features/tabs/adapters/inbound/Tab.d.ts +5 -0
  34. package/dist/features/tabs/adapters/inbound/Tab.d.ts.map +1 -0
  35. package/dist/features/tabs/adapters/inbound/Tab.js +33 -0
  36. package/dist/features/tabs/adapters/inbound/Tab.js.map +1 -0
  37. package/dist/features/tabs/adapters/inbound/TabList.d.ts +5 -0
  38. package/dist/features/tabs/adapters/inbound/TabList.d.ts.map +1 -0
  39. package/dist/features/tabs/adapters/inbound/TabList.js +42 -0
  40. package/dist/features/tabs/adapters/inbound/TabList.js.map +1 -0
  41. package/dist/features/tabs/adapters/inbound/TabPanel.d.ts +5 -0
  42. package/dist/features/tabs/adapters/inbound/TabPanel.d.ts.map +1 -0
  43. package/dist/features/tabs/adapters/inbound/TabPanel.js +14 -0
  44. package/dist/features/tabs/adapters/inbound/TabPanel.js.map +1 -0
  45. package/dist/features/tabs/adapters/inbound/Tabs.d.ts +5 -0
  46. package/dist/features/tabs/adapters/inbound/Tabs.d.ts.map +1 -0
  47. package/dist/features/tabs/adapters/inbound/Tabs.js +12 -0
  48. package/dist/features/tabs/adapters/inbound/Tabs.js.map +1 -0
  49. package/dist/features/tabs/composition/TabsContext.d.ts +4 -0
  50. package/dist/features/tabs/composition/TabsContext.d.ts.map +1 -0
  51. package/dist/features/tabs/composition/TabsContext.js +3 -0
  52. package/dist/features/tabs/composition/TabsContext.js.map +1 -0
  53. package/dist/features/tabs/composition/useTabRegistration.d.ts +7 -0
  54. package/dist/features/tabs/composition/useTabRegistration.d.ts.map +1 -0
  55. package/dist/features/tabs/composition/useTabRegistration.js +20 -0
  56. package/dist/features/tabs/composition/useTabRegistration.js.map +1 -0
  57. package/dist/features/tabs/composition/useTabsContext.d.ts +4 -0
  58. package/dist/features/tabs/composition/useTabsContext.d.ts.map +1 -0
  59. package/dist/{components/tabs/context.js → features/tabs/composition/useTabsContext.js} +4 -4
  60. package/dist/features/tabs/composition/useTabsContext.js.map +1 -0
  61. package/dist/features/tabs/composition/useTabsController.d.ts +4 -0
  62. package/dist/features/tabs/composition/useTabsController.d.ts.map +1 -0
  63. package/dist/features/tabs/composition/useTabsController.js +43 -0
  64. package/dist/features/tabs/composition/useTabsController.js.map +1 -0
  65. package/dist/features/tabs/public.d.ts +6 -0
  66. package/dist/features/tabs/public.d.ts.map +1 -0
  67. package/dist/features/tabs/public.js +5 -0
  68. package/dist/features/tabs/public.js.map +1 -0
  69. package/dist/features/tabs/utils/resolveNextTabValue.d.ts +4 -0
  70. package/dist/features/tabs/utils/resolveNextTabValue.d.ts.map +1 -0
  71. package/dist/{components/tabs/navigation.js → features/tabs/utils/resolveNextTabValue.js} +10 -13
  72. package/dist/features/tabs/utils/resolveNextTabValue.js.map +1 -0
  73. package/dist/features/tabs/utils/resolveTabElementId.d.ts +3 -0
  74. package/dist/features/tabs/utils/resolveTabElementId.d.ts.map +1 -0
  75. package/dist/features/tabs/utils/resolveTabElementId.js +10 -0
  76. package/dist/features/tabs/utils/resolveTabElementId.js.map +1 -0
  77. package/dist/features/tooltip/adapters/inbound/Tooltip.d.ts +5 -0
  78. package/dist/features/tooltip/adapters/inbound/Tooltip.d.ts.map +1 -0
  79. package/dist/features/tooltip/adapters/inbound/Tooltip.js +25 -0
  80. package/dist/features/tooltip/adapters/inbound/Tooltip.js.map +1 -0
  81. package/dist/features/tooltip/composition/useTooltipVisibility.d.ts +9 -0
  82. package/dist/features/tooltip/composition/useTooltipVisibility.d.ts.map +1 -0
  83. package/dist/features/tooltip/composition/useTooltipVisibility.js +25 -0
  84. package/dist/features/tooltip/composition/useTooltipVisibility.js.map +1 -0
  85. package/dist/features/tooltip/public.d.ts +3 -0
  86. package/dist/features/tooltip/public.d.ts.map +1 -0
  87. package/dist/features/tooltip/public.js +2 -0
  88. package/dist/features/tooltip/public.js.map +1 -0
  89. package/dist/index.d.ts +8 -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/types/bottomSheet.d.ts +3 -0
  94. package/dist/types/bottomSheet.d.ts.map +1 -1
  95. package/dist/types/bottomSheet.js.map +1 -1
  96. package/dist/{components/modal/types.d.ts → types/modal.d.ts} +2 -2
  97. package/dist/types/modal.d.ts.map +1 -0
  98. package/dist/types/modal.js +2 -0
  99. package/dist/types/modal.js.map +1 -0
  100. package/dist/types/popoverMenu.d.ts +24 -0
  101. package/dist/types/popoverMenu.d.ts.map +1 -0
  102. package/dist/types/popoverMenu.js +2 -0
  103. package/dist/types/popoverMenu.js.map +1 -0
  104. package/dist/types/tabs.d.ts +43 -0
  105. package/dist/types/tabs.d.ts.map +1 -0
  106. package/dist/types/tabs.js +2 -0
  107. package/dist/types/tabs.js.map +1 -0
  108. package/dist/{components/tooltip/types.d.ts → types/tooltip.d.ts} +2 -2
  109. package/dist/types/tooltip.d.ts.map +1 -0
  110. package/dist/types/tooltip.js +2 -0
  111. package/dist/types/tooltip.js.map +1 -0
  112. package/package.json +1 -1
  113. package/src/features/bottom-sheet/BottomSheetProvider.tsx +13 -8
  114. package/src/features/bottom-sheet/public.test.ts +23 -0
  115. package/src/features/bottom-sheet/public.ts +12 -1
  116. package/src/features/modal/adapters/inbound/Modal.tsx +74 -0
  117. package/src/features/modal/public.ts +2 -0
  118. package/src/features/popover-menu/adapters/inbound/PopoverMenu.tsx +201 -0
  119. package/src/features/popover-menu/composition/usePopoverMenuController.ts +55 -0
  120. package/src/features/popover-menu/public.test.ts +13 -0
  121. package/src/features/popover-menu/public.ts +6 -0
  122. package/src/{components/menu/navigation.test.ts → features/popover-menu/utils/resolveNextMenuIndex.test.ts} +4 -4
  123. package/src/features/popover-menu/utils/resolveNextMenuIndex.ts +21 -0
  124. package/src/features/tabs/adapters/inbound/Tab.tsx +67 -0
  125. package/src/features/tabs/adapters/inbound/TabList.tsx +50 -0
  126. package/src/{components/tabs → features/tabs/adapters/inbound}/TabPanel.tsx +6 -10
  127. package/src/features/tabs/adapters/inbound/Tabs.tsx +17 -0
  128. package/src/features/tabs/composition/TabsContext.tsx +5 -0
  129. package/src/features/tabs/composition/useTabRegistration.ts +27 -0
  130. package/src/features/tabs/composition/useTabsContext.ts +13 -0
  131. package/src/features/tabs/composition/useTabsController.ts +55 -0
  132. package/src/features/tabs/public.ts +5 -0
  133. package/src/{components/tabs/navigation.test.ts → features/tabs/utils/resolveNextTabValue.test.ts} +2 -2
  134. package/src/features/tabs/utils/resolveNextTabValue.ts +24 -0
  135. package/src/features/tabs/utils/resolveTabElementId.test.ts +19 -0
  136. package/src/features/tabs/utils/resolveTabElementId.ts +14 -0
  137. package/src/features/tooltip/adapters/inbound/Tooltip.tsx +56 -0
  138. package/src/features/tooltip/composition/useTooltipVisibility.ts +29 -0
  139. package/src/features/tooltip/public.test.ts +11 -0
  140. package/src/features/tooltip/public.ts +2 -0
  141. package/src/index.test.ts +12 -5
  142. package/src/index.ts +12 -8
  143. package/src/types/bottomSheet.ts +4 -0
  144. package/src/{components/modal/types.ts → types/modal.ts} +1 -1
  145. package/src/types/popoverMenu.ts +27 -0
  146. package/src/types/tabs.ts +51 -0
  147. package/src/{components/tooltip/types.ts → types/tooltip.ts} +1 -1
  148. package/dist/components/menu/Menu.d.ts +0 -4
  149. package/dist/components/menu/Menu.d.ts.map +0 -1
  150. package/dist/components/menu/Menu.js +0 -152
  151. package/dist/components/menu/Menu.js.map +0 -1
  152. package/dist/components/menu/index.d.ts +0 -3
  153. package/dist/components/menu/index.d.ts.map +0 -1
  154. package/dist/components/menu/index.js +0 -2
  155. package/dist/components/menu/index.js.map +0 -1
  156. package/dist/components/menu/navigation.d.ts +0 -4
  157. package/dist/components/menu/navigation.d.ts.map +0 -1
  158. package/dist/components/menu/navigation.js +0 -25
  159. package/dist/components/menu/navigation.js.map +0 -1
  160. package/dist/components/menu/types.d.ts +0 -23
  161. package/dist/components/menu/types.d.ts.map +0 -1
  162. package/dist/components/menu/types.js +0 -2
  163. package/dist/components/menu/types.js.map +0 -1
  164. package/dist/components/modal/Modal.d.ts.map +0 -1
  165. package/dist/components/modal/Modal.js +0 -59
  166. package/dist/components/modal/Modal.js.map +0 -1
  167. package/dist/components/modal/index.d.ts +0 -3
  168. package/dist/components/modal/index.d.ts.map +0 -1
  169. package/dist/components/modal/index.js +0 -2
  170. package/dist/components/modal/index.js.map +0 -1
  171. package/dist/components/modal/types.d.ts.map +0 -1
  172. package/dist/components/modal/types.js +0 -2
  173. package/dist/components/modal/types.js.map +0 -1
  174. package/dist/components/tabs/Tab.d.ts +0 -4
  175. package/dist/components/tabs/Tab.d.ts.map +0 -1
  176. package/dist/components/tabs/Tab.js +0 -48
  177. package/dist/components/tabs/Tab.js.map +0 -1
  178. package/dist/components/tabs/TabList.d.ts +0 -4
  179. package/dist/components/tabs/TabList.d.ts.map +0 -1
  180. package/dist/components/tabs/TabList.js +0 -37
  181. package/dist/components/tabs/TabList.js.map +0 -1
  182. package/dist/components/tabs/TabPanel.d.ts +0 -4
  183. package/dist/components/tabs/TabPanel.d.ts.map +0 -1
  184. package/dist/components/tabs/TabPanel.js +0 -16
  185. package/dist/components/tabs/TabPanel.js.map +0 -1
  186. package/dist/components/tabs/Tabs.d.ts +0 -4
  187. package/dist/components/tabs/Tabs.d.ts.map +0 -1
  188. package/dist/components/tabs/Tabs.js +0 -54
  189. package/dist/components/tabs/Tabs.js.map +0 -1
  190. package/dist/components/tabs/a11y.d.ts +0 -3
  191. package/dist/components/tabs/a11y.d.ts.map +0 -1
  192. package/dist/components/tabs/a11y.js +0 -13
  193. package/dist/components/tabs/a11y.js.map +0 -1
  194. package/dist/components/tabs/context.d.ts +0 -20
  195. package/dist/components/tabs/context.d.ts.map +0 -1
  196. package/dist/components/tabs/context.js.map +0 -1
  197. package/dist/components/tabs/index.d.ts +0 -6
  198. package/dist/components/tabs/index.d.ts.map +0 -1
  199. package/dist/components/tabs/index.js +0 -5
  200. package/dist/components/tabs/index.js.map +0 -1
  201. package/dist/components/tabs/navigation.d.ts +0 -3
  202. package/dist/components/tabs/navigation.d.ts.map +0 -1
  203. package/dist/components/tabs/navigation.js.map +0 -1
  204. package/dist/components/tabs/types.d.ts +0 -26
  205. package/dist/components/tabs/types.d.ts.map +0 -1
  206. package/dist/components/tabs/types.js +0 -2
  207. package/dist/components/tabs/types.js.map +0 -1
  208. package/dist/components/tooltip/Tooltip.d.ts +0 -4
  209. package/dist/components/tooltip/Tooltip.d.ts.map +0 -1
  210. package/dist/components/tooltip/Tooltip.js +0 -75
  211. package/dist/components/tooltip/Tooltip.js.map +0 -1
  212. package/dist/components/tooltip/index.d.ts +0 -3
  213. package/dist/components/tooltip/index.d.ts.map +0 -1
  214. package/dist/components/tooltip/index.js +0 -2
  215. package/dist/components/tooltip/index.js.map +0 -1
  216. package/dist/components/tooltip/types.d.ts.map +0 -1
  217. package/dist/components/tooltip/types.js +0 -2
  218. package/dist/components/tooltip/types.js.map +0 -1
  219. package/src/components/menu/Menu.tsx +0 -228
  220. package/src/components/menu/index.ts +0 -2
  221. package/src/components/menu/navigation.ts +0 -34
  222. package/src/components/menu/types.ts +0 -26
  223. package/src/components/modal/Modal.tsx +0 -96
  224. package/src/components/modal/index.ts +0 -2
  225. package/src/components/tabs/Tab.tsx +0 -89
  226. package/src/components/tabs/TabList.tsx +0 -51
  227. package/src/components/tabs/Tabs.tsx +0 -67
  228. package/src/components/tabs/a11y.test.ts +0 -15
  229. package/src/components/tabs/a11y.ts +0 -15
  230. package/src/components/tabs/context.tsx +0 -31
  231. package/src/components/tabs/index.ts +0 -5
  232. package/src/components/tabs/navigation.ts +0 -32
  233. package/src/components/tabs/types.ts +0 -30
  234. package/src/components/tooltip/Tooltip.tsx +0 -117
  235. package/src/components/tooltip/index.ts +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"bottomSheet.d.ts","sourceRoot":"","sources":["../../src/types/bottomSheet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,UAAU,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,gBAAgB,CAAC,EAAE,QAAQ,GAAG,YAAY,GAAG,aAAa,CAAC;IAC3D,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACtD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB"}
1
+ {"version":3,"file":"bottomSheet.d.ts","sourceRoot":"","sources":["../../src/types/bottomSheet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvD,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC,UAAU,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,gBAAgB,CAAC,EAAE,QAAQ,GAAG,YAAY,GAAG,aAAa,CAAC;IAC3D,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACtD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB"}
@@ -1 +1 @@
1
- {"version":3,"file":"bottomSheet.js","sourceRoot":"","sources":["../../src/types/bottomSheet.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nexport interface BottomSheetPresentOptions {\n content: React.ReactNode;\n snapPoints?: readonly (number | string)[];\n initialIndex?: number;\n enableDynamicSizing?: boolean;\n enablePanDownToClose?: boolean;\n dismissOnBackdropPress?: boolean;\n keyboardBehavior?: 'extend' | 'fillParent' | 'interactive';\n keyboardBlurBehavior?: 'none' | 'restore';\n onIndexChange?: (index: number) => void;\n onDismiss?: () => void;\n}\n\nexport interface BottomSheetController {\n present: (options: BottomSheetPresentOptions) => void;\n dismiss: () => void;\n}\n"]}
1
+ {"version":3,"file":"bottomSheet.js","sourceRoot":"","sources":["../../src/types/bottomSheet.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nexport type BottomSheetContentMode = 'view' | 'direct';\n\nexport interface BottomSheetPresentOptions {\n content: React.ReactNode;\n contentMode?: BottomSheetContentMode;\n snapPoints?: readonly (number | string)[];\n initialIndex?: number;\n enableDynamicSizing?: boolean;\n maxDynamicContentSize?: number;\n enablePanDownToClose?: boolean;\n dismissOnBackdropPress?: boolean;\n keyboardBehavior?: 'extend' | 'fillParent' | 'interactive';\n keyboardBlurBehavior?: 'none' | 'restore';\n onIndexChange?: (index: number) => void;\n onDismiss?: () => void;\n}\n\nexport interface BottomSheetController {\n present: (options: BottomSheetPresentOptions) => void;\n dismiss: () => void;\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type React from 'react';
2
- import type { InteractionPolicy } from '../../interactionPolicy';
2
+ import type { InteractionPolicy } from '../interactionPolicy';
3
3
  export interface ModalProps {
4
4
  visible: boolean;
5
5
  onDismiss?: (() => void) | undefined;
@@ -8,4 +8,4 @@ export interface ModalProps {
8
8
  interactionPolicy?: InteractionPolicy;
9
9
  testID?: string;
10
10
  }
11
- //# sourceMappingURL=types.d.ts.map
11
+ //# sourceMappingURL=modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../../src/types/modal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=modal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal.js","sourceRoot":"","sources":["../../src/types/modal.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { InteractionPolicy } from '../interactionPolicy';\n\nexport interface ModalProps {\n visible: boolean;\n onDismiss?: (() => void) | undefined;\n children?: React.ReactNode;\n closeOnBackdrop?: boolean;\n interactionPolicy?: InteractionPolicy;\n testID?: string;\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type React from 'react';
2
+ import type { InteractionPolicy } from '../interactionPolicy';
3
+ import type { PopoverAnchorRenderProps } from './popover';
4
+ export type PopoverMenuActionIntent = 'default' | 'danger';
5
+ export interface PopoverMenuAction {
6
+ id: string;
7
+ title: React.ReactNode;
8
+ description?: React.ReactNode;
9
+ leading?: React.ReactNode;
10
+ trailing?: React.ReactNode;
11
+ intent?: PopoverMenuActionIntent;
12
+ disabled?: boolean;
13
+ selected?: boolean;
14
+ activate?: () => void;
15
+ }
16
+ export interface PopoverMenuProps {
17
+ trigger: (controls: PopoverAnchorRenderProps) => React.ReactNode;
18
+ actions: readonly PopoverMenuAction[];
19
+ dismiss?: () => void;
20
+ closeOnSelect?: boolean;
21
+ interactionPolicy?: InteractionPolicy;
22
+ testID?: string;
23
+ }
24
+ //# sourceMappingURL=popoverMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popoverMenu.d.ts","sourceRoot":"","sources":["../../src/types/popoverMenu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAE1D,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,KAAK,CAAC,SAAS,CAAC;IACjE,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=popoverMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popoverMenu.js","sourceRoot":"","sources":["../../src/types/popoverMenu.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { InteractionPolicy } from '../interactionPolicy';\nimport type { PopoverAnchorRenderProps } from './popover';\n\nexport type PopoverMenuActionIntent = 'default' | 'danger';\n\nexport interface PopoverMenuAction {\n id: string;\n title: React.ReactNode;\n description?: React.ReactNode;\n leading?: React.ReactNode;\n trailing?: React.ReactNode;\n intent?: PopoverMenuActionIntent;\n disabled?: boolean;\n selected?: boolean;\n activate?: () => void;\n}\n\nexport interface PopoverMenuProps {\n trigger: (controls: PopoverAnchorRenderProps) => React.ReactNode;\n actions: readonly PopoverMenuAction[];\n dismiss?: () => void;\n closeOnSelect?: boolean;\n interactionPolicy?: InteractionPolicy;\n testID?: string;\n}\n"]}
@@ -0,0 +1,43 @@
1
+ import type React from 'react';
2
+ import type { InteractionPolicy } from '../interactionPolicy';
3
+ export interface TabsProps {
4
+ children?: React.ReactNode;
5
+ value?: string;
6
+ defaultValue?: string;
7
+ onValueChange?: ((value: string) => void) | undefined;
8
+ testID?: string;
9
+ }
10
+ export interface TabListProps {
11
+ children?: React.ReactNode;
12
+ testID?: string;
13
+ }
14
+ export interface TabProps {
15
+ value: string;
16
+ children?: React.ReactNode;
17
+ disabled?: boolean;
18
+ interactionPolicy?: InteractionPolicy;
19
+ testID?: string;
20
+ }
21
+ export interface TabPanelProps {
22
+ value: string;
23
+ children?: React.ReactNode;
24
+ testID?: string;
25
+ }
26
+ export interface TabRegistration {
27
+ disabled: boolean;
28
+ focus: () => void;
29
+ value: string;
30
+ }
31
+ export interface TabsContextValue {
32
+ activeValue: string | undefined;
33
+ focusedValue: string | undefined;
34
+ getPanelId: (value: string) => string;
35
+ getTabId: (value: string) => string;
36
+ registerTab: (tab: TabRegistration) => void;
37
+ setActiveValue: (value: string) => void;
38
+ setFocusedValue: (value: string | undefined) => void;
39
+ tabs: readonly TabRegistration[];
40
+ unregisterTab: (value: string) => void;
41
+ }
42
+ export type TabNavigationKey = 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'ArrowDown' | 'Home' | 'End';
43
+ //# sourceMappingURL=tabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../src/types/tabs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,WAAW,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAC;IAC5C,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACrD,IAAI,EAAE,SAAS,eAAe,EAAE,CAAC;IACjC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,MAAM,gBAAgB,GAC1B,WAAW,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,KAAK,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabs.js","sourceRoot":"","sources":["../../src/types/tabs.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { InteractionPolicy } from '../interactionPolicy';\n\nexport interface TabsProps {\n children?: React.ReactNode;\n value?: string;\n defaultValue?: string;\n onValueChange?: ((value: string) => void) | undefined;\n testID?: string;\n}\n\nexport interface TabListProps {\n children?: React.ReactNode;\n testID?: string;\n}\n\nexport interface TabProps {\n value: string;\n children?: React.ReactNode;\n disabled?: boolean;\n interactionPolicy?: InteractionPolicy;\n testID?: string;\n}\n\nexport interface TabPanelProps {\n value: string;\n children?: React.ReactNode;\n testID?: string;\n}\n\nexport interface TabRegistration {\n disabled: boolean;\n focus: () => void;\n value: string;\n}\n\nexport interface TabsContextValue {\n activeValue: string | undefined;\n focusedValue: string | undefined;\n getPanelId: (value: string) => string;\n getTabId: (value: string) => string;\n registerTab: (tab: TabRegistration) => void;\n setActiveValue: (value: string) => void;\n setFocusedValue: (value: string | undefined) => void;\n tabs: readonly TabRegistration[];\n unregisterTab: (value: string) => void;\n}\n\nexport type TabNavigationKey =\n 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'ArrowDown' | 'Home' | 'End';\n"]}
@@ -1,5 +1,5 @@
1
1
  import type React from 'react';
2
- import type { InteractionPolicy } from '../../interactionPolicy';
2
+ import type { InteractionPolicy } from '../interactionPolicy';
3
3
  export interface TooltipProps {
4
4
  children?: React.ReactNode;
5
5
  content?: React.ReactNode;
@@ -8,4 +8,4 @@ export interface TooltipProps {
8
8
  placement?: 'top' | 'bottom';
9
9
  testID?: string;
10
10
  }
11
- //# sourceMappingURL=types.d.ts.map
11
+ //# sourceMappingURL=tooltip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../src/types/tooltip.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tooltip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip.js","sourceRoot":"","sources":["../../src/types/tooltip.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { InteractionPolicy } from '../interactionPolicy';\n\nexport interface TooltipProps {\n children?: React.ReactNode;\n content?: React.ReactNode;\n delay?: number;\n interactionPolicy?: InteractionPolicy;\n placement?: 'top' | 'bottom';\n testID?: string;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/surface",
3
3
  "type": "module",
4
- "version": "4.4.1",
4
+ "version": "5.0.1",
5
5
  "description": "Stable UI foundation for React Native and React Native Web.",
6
6
  "homepage": "https://github.com/ankhorage/surface#readme",
7
7
  "bugs": {
@@ -77,10 +77,7 @@ function BottomSheetHost({
77
77
  presentedRef,
78
78
  }: BottomSheetHostProps) {
79
79
  React.useEffect(() => {
80
- if (!activeRequest || presentedRef.current) {
81
- return;
82
- }
83
-
80
+ if (!activeRequest || presentedRef.current) return;
84
81
  presentedRef.current = true;
85
82
  modalRef.current?.present();
86
83
  }, [activeRequest, modalRef, presentedRef]);
@@ -91,9 +88,7 @@ function BottomSheetHost({
91
88
  [activeRequest?.snapPoints],
92
89
  );
93
90
 
94
- if (!activeRequest) {
95
- return null;
96
- }
91
+ if (!activeRequest) return null;
97
92
 
98
93
  return (
99
94
  <BottomSheetModal
@@ -104,15 +99,25 @@ function BottomSheetHost({
104
99
  index={activeRequest.initialIndex ?? 0}
105
100
  keyboardBehavior={activeRequest.keyboardBehavior ?? 'interactive'}
106
101
  keyboardBlurBehavior={activeRequest.keyboardBlurBehavior ?? 'restore'}
102
+ maxDynamicContentSize={activeRequest.maxDynamicContentSize}
107
103
  onChange={activeRequest.onIndexChange}
108
104
  onDismiss={handleDismiss}
109
105
  snapPoints={snapPoints}
110
106
  >
111
- <BottomSheetView>{activeRequest.content}</BottomSheetView>
107
+ {renderBottomSheetContent(activeRequest)}
112
108
  </BottomSheetModal>
113
109
  );
114
110
  }
115
111
 
112
+ /*** Renders default content in BottomSheetView and direct content without an extra wrapper. */
113
+ function renderBottomSheetContent(activeRequest: BottomSheetPresentOptions): React.ReactNode {
114
+ return activeRequest.contentMode === 'direct' ? (
115
+ activeRequest.content
116
+ ) : (
117
+ <BottomSheetView>{activeRequest.content}</BottomSheetView>
118
+ );
119
+ }
120
+
116
121
  /*** Creates the backdrop renderer for the active bottom-sheet request. */
117
122
  function useBottomSheetBackdrop(dismissOnBackdropPress: boolean | undefined) {
118
123
  return React.useCallback(
@@ -0,0 +1,23 @@
1
+ import { expect, test } from 'bun:test';
2
+
3
+ test('bottom-sheet boundary exposes Gorhom integrated scrollables and direct content mode', async () => {
4
+ const [publicSource, providerSource, typeSource] = await Promise.all([
5
+ Bun.file('src/features/bottom-sheet/public.ts').text(),
6
+ Bun.file('src/features/bottom-sheet/BottomSheetProvider.tsx').text(),
7
+ Bun.file('src/types/bottomSheet.ts').text(),
8
+ ]);
9
+
10
+ for (const name of [
11
+ 'BottomSheetFlatList',
12
+ 'BottomSheetScrollView',
13
+ 'BottomSheetSectionList',
14
+ 'BottomSheetView',
15
+ 'BottomSheetVirtualizedList',
16
+ ]) {
17
+ expect(publicSource).toContain(name);
18
+ }
19
+
20
+ expect(typeSource).toContain("export type BottomSheetContentMode = 'view' | 'direct';");
21
+ expect(providerSource).toContain("activeRequest.contentMode === 'direct'");
22
+ expect(providerSource).toContain('maxDynamicContentSize={activeRequest.maxDynamicContentSize}');
23
+ });
@@ -1,3 +1,14 @@
1
- export type { BottomSheetController, BottomSheetPresentOptions } from '../../types/bottomSheet';
1
+ export type {
2
+ BottomSheetContentMode,
3
+ BottomSheetController,
4
+ BottomSheetPresentOptions,
5
+ } from '../../types/bottomSheet';
2
6
  export { BottomSheetProvider } from './BottomSheetProvider';
3
7
  export { useBottomSheet } from './useBottomSheet';
8
+ export {
9
+ BottomSheetFlatList,
10
+ BottomSheetScrollView,
11
+ BottomSheetSectionList,
12
+ BottomSheetView,
13
+ BottomSheetVirtualizedList,
14
+ } from '@gorhom/bottom-sheet';
@@ -0,0 +1,74 @@
1
+ import React from 'react';
2
+ import { Pressable, StyleSheet, View, type ViewStyle } from 'react-native';
3
+
4
+ import { FocusScope } from '../../../../internal/focus/FocusScope';
5
+ import { Portal } from '../../../../internal/overlay/Portal';
6
+ import { resolvePointerEvents } from '../../../../internal/resolvePointerEvents';
7
+ import { resolveOverlayAnimation } from '../../../../internal/resolvers';
8
+ import { Center } from '../../../../layout';
9
+ import { useTheme } from '../../../../theme/ThemeContext';
10
+ import type { ModalProps } from '../../../../types/modal';
11
+ import { Surface } from '../../../surface/public';
12
+
13
+ const boxNonePointerEvents = resolvePointerEvents('box-none');
14
+
15
+ /*** Renders the generic Surface modal overlay and focus boundary. */
16
+ export function Modal({
17
+ visible,
18
+ onDismiss,
19
+ children,
20
+ closeOnBackdrop = true,
21
+ interactionPolicy = 'enabled',
22
+ testID,
23
+ }: ModalProps) {
24
+ const { theme } = useTheme();
25
+ const animation = resolveOverlayAnimation('modal');
26
+ const passive = interactionPolicy === 'passive';
27
+ const backdropPress = passive || !closeOnBackdrop ? undefined : onDismiss;
28
+
29
+ if (!visible) return null;
30
+
31
+ return (
32
+ <Portal layer="modal" visible={visible}>
33
+ <View {...boxNonePointerEvents.props} style={[boxNonePointerEvents.style, styles.fill]}>
34
+ <Pressable
35
+ onPress={backdropPress}
36
+ style={[
37
+ styles.fill,
38
+ resolveBackdropStyle(theme.semantics.neutral.text, animation.backdropOpacity),
39
+ ]}
40
+ testID={testID ? `${testID}-backdrop` : undefined}
41
+ />
42
+ <FocusScope
43
+ active={visible}
44
+ onEscape={passive ? undefined : onDismiss}
45
+ testID={testID ? `${testID}-focus` : undefined}
46
+ >
47
+ <Center p="l" style={styles.center}>
48
+ <View accessible accessibilityViewIsModal style={styles.content}>
49
+ <Surface p="l" style={styles.surface} testID={testID} variant="raised">
50
+ {children}
51
+ </Surface>
52
+ </View>
53
+ </Center>
54
+ </FocusScope>
55
+ </View>
56
+ </Portal>
57
+ );
58
+ }
59
+
60
+ /*** Resolves the runtime backdrop color and opacity without inline JSX styles. */
61
+ function resolveBackdropStyle(backgroundColor: string, opacity: number): ViewStyle {
62
+ return { backgroundColor, opacity };
63
+ }
64
+
65
+ const styles = StyleSheet.create({
66
+ center: { flex: 1 },
67
+ content: { maxWidth: 560, width: '100%' },
68
+ fill: { bottom: 0, left: 0, position: 'absolute', right: 0, top: 0 },
69
+ surface: {
70
+ shadowOffset: { height: 8, width: 0 },
71
+ shadowOpacity: 0.14,
72
+ shadowRadius: 18,
73
+ },
74
+ });
@@ -0,0 +1,2 @@
1
+ export type { ModalProps } from '../../types/modal';
2
+ export { Modal } from './adapters/inbound/Modal';
@@ -0,0 +1,201 @@
1
+ import React from 'react';
2
+ import { Pressable } from 'react-native';
3
+
4
+ import { FocusScope } from '../../../../internal/focus/FocusScope';
5
+ import { useFocusManager } from '../../../../internal/focus/useFocusManager';
6
+ import { Inline } from '../../../../layout';
7
+ import { useTheme } from '../../../../theme/ThemeContext';
8
+ import type { PopoverMenuAction, PopoverMenuProps } from '../../../../types/popoverMenu';
9
+ import { Box, Stack } from '../../../layout/public';
10
+ import { Popover } from '../../../popover/public';
11
+ import { Surface } from '../../../surface/public';
12
+ import { Text } from '../../../typography/public';
13
+ import { usePopoverMenuController } from '../../composition/usePopoverMenuController';
14
+ import { resolveNextMenuIndex } from '../../utils/resolveNextMenuIndex';
15
+
16
+ /*** Presents an anchored action menu using the shared Popover capability. */
17
+ export function PopoverMenu({
18
+ trigger,
19
+ actions,
20
+ dismiss,
21
+ closeOnSelect = true,
22
+ interactionPolicy = 'enabled',
23
+ testID,
24
+ }: PopoverMenuProps) {
25
+ const passive = interactionPolicy === 'passive';
26
+ const controller = usePopoverMenuController({ actions, closeOnSelect, dismiss, passive });
27
+
28
+ return (
29
+ <Popover
30
+ anchor={trigger}
31
+ closeOnOutsidePress
32
+ interactionPolicy={interactionPolicy}
33
+ onOpenChange={controller.handleOpenChange}
34
+ open={controller.open}
35
+ placement="bottom-start"
36
+ testID={testID}
37
+ >
38
+ <PopoverMenuContent
39
+ actions={actions}
40
+ activeIndex={controller.activeIndex}
41
+ activateAction={controller.activateAction}
42
+ close={controller.close}
43
+ passive={passive}
44
+ setActiveIndex={controller.setActiveIndex}
45
+ testID={testID}
46
+ />
47
+ </Popover>
48
+ );
49
+ }
50
+
51
+ interface PopoverMenuContentProps {
52
+ actions: readonly PopoverMenuAction[];
53
+ activeIndex: number;
54
+ activateAction: (action: PopoverMenuAction) => void;
55
+ close: () => void;
56
+ passive: boolean;
57
+ setActiveIndex: React.Dispatch<React.SetStateAction<number>>;
58
+ testID?: string;
59
+ }
60
+
61
+ /*** Renders menu semantics and keyboard navigation inside an open Popover. */
62
+ function PopoverMenuContent({
63
+ actions,
64
+ activeIndex,
65
+ activateAction,
66
+ close,
67
+ passive,
68
+ setActiveIndex,
69
+ testID,
70
+ }: PopoverMenuContentProps) {
71
+ usePopoverMenuKeyboard({ actions, activeIndex, activateAction, close, passive, setActiveIndex });
72
+
73
+ return (
74
+ <FocusScope active onEscape={passive ? undefined : close}>
75
+ <Surface accessibilityRole="menu" p="xs" testID={testID} variant="raised">
76
+ {actions.map((action, index) => (
77
+ <PopoverMenuItem
78
+ action={action}
79
+ active={index === activeIndex}
80
+ activateAction={activateAction}
81
+ key={action.id}
82
+ passive={passive}
83
+ testID={testID}
84
+ />
85
+ ))}
86
+ </Surface>
87
+ </FocusScope>
88
+ );
89
+ }
90
+
91
+ /*** Subscribes an open PopoverMenu to standard menu keyboard controls. */
92
+ function usePopoverMenuKeyboard({
93
+ actions,
94
+ activeIndex,
95
+ activateAction,
96
+ close,
97
+ passive,
98
+ setActiveIndex,
99
+ }: Omit<PopoverMenuContentProps, 'testID'>) {
100
+ const { bindKeydown } = useFocusManager();
101
+
102
+ React.useEffect(
103
+ () =>
104
+ bindKeydown((event) => {
105
+ if (passive) return;
106
+
107
+ const { key } = event;
108
+ if (key === undefined) return;
109
+
110
+ if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(key)) {
111
+ event.preventDefault();
112
+ setActiveIndex((current) => resolveNextMenuIndex(actions, current, key));
113
+ return;
114
+ }
115
+ if (key === 'Enter') {
116
+ event.preventDefault();
117
+ const action = actions.at(activeIndex);
118
+ if (action) activateAction(action);
119
+ return;
120
+ }
121
+ if (key === 'Escape') {
122
+ event.preventDefault();
123
+ close();
124
+ }
125
+ }),
126
+ [actions, activeIndex, activateAction, bindKeydown, close, passive, setActiveIndex],
127
+ );
128
+ }
129
+
130
+ /*** Renders one accessible PopoverMenu action row. */
131
+ function PopoverMenuItem({
132
+ action,
133
+ active,
134
+ activateAction,
135
+ passive,
136
+ testID,
137
+ }: {
138
+ action: PopoverMenuAction;
139
+ active: boolean;
140
+ activateAction: (action: PopoverMenuAction) => void;
141
+ passive: boolean;
142
+ testID?: string;
143
+ }) {
144
+ const { theme } = useTheme();
145
+ const selected = action.selected ?? active;
146
+
147
+ return (
148
+ <Pressable
149
+ accessibilityRole="menuitem"
150
+ accessibilityState={{ disabled: action.disabled, selected }}
151
+ disabled={action.disabled}
152
+ onPress={passive ? undefined : () => activateAction(action)}
153
+ testID={testID ? `${testID}-item-${action.id}` : undefined}
154
+ >
155
+ <Box
156
+ bg={selected ? theme.semantics.action.neutral.softBg : 'transparent'}
157
+ opacity={action.disabled ? 0.56 : 1}
158
+ px="m"
159
+ py="s"
160
+ radius="s"
161
+ >
162
+ <PopoverMenuItemContent action={action} active={active} />
163
+ </Box>
164
+ </Pressable>
165
+ );
166
+ }
167
+
168
+ /*** Renders the leading, textual, and trailing content of one menu action. */
169
+ function PopoverMenuItemContent({
170
+ action,
171
+ active,
172
+ }: {
173
+ action: PopoverMenuAction;
174
+ active: boolean;
175
+ }) {
176
+ const titleColor =
177
+ action.intent === 'danger' ? 'danger' : active || action.selected ? 'neutral' : undefined;
178
+
179
+ return (
180
+ <Inline align="center" gap="s" justify="space-between" wrap="nowrap">
181
+ {action.leading ? <Box>{action.leading}</Box> : null}
182
+ <Box flex={1}>
183
+ <Stack gap="xxs">
184
+ <Text
185
+ color={titleColor}
186
+ variant="bodySmall"
187
+ weight={action.selected ? 'semiBold' : 'medium'}
188
+ >
189
+ {action.title}
190
+ </Text>
191
+ {action.description ? (
192
+ <Text emphasis="muted" variant="caption">
193
+ {action.description}
194
+ </Text>
195
+ ) : null}
196
+ </Stack>
197
+ </Box>
198
+ {action.trailing ? <Box>{action.trailing}</Box> : null}
199
+ </Inline>
200
+ );
201
+ }
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+
3
+ import type { PopoverMenuAction } from '../../../types/popoverMenu';
4
+
5
+ interface PopoverMenuControllerOptions {
6
+ actions: readonly PopoverMenuAction[];
7
+ closeOnSelect: boolean;
8
+ dismiss?: () => void;
9
+ passive: boolean;
10
+ }
11
+
12
+ /*** Owns PopoverMenu visibility, active action state, and selection behavior. */
13
+ export function usePopoverMenuController({
14
+ actions,
15
+ closeOnSelect,
16
+ dismiss,
17
+ passive,
18
+ }: PopoverMenuControllerOptions) {
19
+ const [open, setOpen] = React.useState(false);
20
+ const [activeIndex, setActiveIndex] = React.useState(0);
21
+
22
+ const handleOpenChange = React.useCallback(
23
+ (nextOpen: boolean) => {
24
+ if (nextOpen) setActiveIndex(resolveInitialIndex(actions));
25
+ if (open && !nextOpen) dismiss?.();
26
+ setOpen(nextOpen);
27
+ },
28
+ [actions, dismiss, open],
29
+ );
30
+
31
+ const close = React.useCallback(() => handleOpenChange(false), [handleOpenChange]);
32
+ const activateAction = React.useCallback(
33
+ (action: PopoverMenuAction) => {
34
+ if (action.disabled || passive) return;
35
+ action.activate?.();
36
+ if (closeOnSelect) close();
37
+ },
38
+ [close, closeOnSelect, passive],
39
+ );
40
+
41
+ return {
42
+ activeIndex,
43
+ activateAction,
44
+ close,
45
+ handleOpenChange,
46
+ open,
47
+ setActiveIndex,
48
+ } as const;
49
+ }
50
+
51
+ /*** Returns the first enabled action index for a newly opened menu. */
52
+ function resolveInitialIndex(actions: readonly PopoverMenuAction[]): number {
53
+ const index = actions.findIndex((action) => !action.disabled);
54
+ return index === -1 ? 0 : index;
55
+ }
@@ -0,0 +1,13 @@
1
+ import { expect, test } from 'bun:test';
2
+
3
+ test('PopoverMenu composes Popover instead of owning anchor and portal mechanics', async () => {
4
+ const source = await Bun.file(
5
+ 'src/features/popover-menu/adapters/inbound/PopoverMenu.tsx',
6
+ ).text();
7
+
8
+ expect(source).toContain("import { Popover } from '../../../popover/public';");
9
+ expect(source).toContain('<Popover');
10
+ expect(source).not.toContain('measureInWindow');
11
+ expect(source).not.toContain('internal/overlay');
12
+ expect(source).not.toContain('<Portal');
13
+ });
@@ -0,0 +1,6 @@
1
+ export type {
2
+ PopoverMenuAction,
3
+ PopoverMenuActionIntent,
4
+ PopoverMenuProps,
5
+ } from '../../types/popoverMenu';
6
+ export { PopoverMenu } from './adapters/inbound/PopoverMenu';
@@ -1,11 +1,11 @@
1
1
  import { describe, expect, it } from 'bun:test';
2
2
 
3
- import { resolveNextMenuIndex } from './navigation';
3
+ import { resolveNextMenuIndex } from './resolveNextMenuIndex';
4
4
 
5
5
  describe('resolveNextMenuIndex', () => {
6
- const items = [{}, { disabled: true }, {}, {}];
6
+ const items = [{}, { disabled: true }, {}, {}] as const;
7
7
 
8
- it('cycles down and up through the menu items', () => {
8
+ it('cycles down and up through enabled actions', () => {
9
9
  expect(resolveNextMenuIndex(items, 0, 'ArrowDown')).toBe(2);
10
10
  expect(resolveNextMenuIndex(items, 0, 'ArrowUp')).toBe(3);
11
11
  });
@@ -15,7 +15,7 @@ describe('resolveNextMenuIndex', () => {
15
15
  expect(resolveNextMenuIndex(items, 0, 'End')).toBe(3);
16
16
  });
17
17
 
18
- it('returns -1 when all items are disabled', () => {
18
+ it('returns -1 when all actions are disabled', () => {
19
19
  expect(resolveNextMenuIndex([{ disabled: true }], 0, 'ArrowDown')).toBe(-1);
20
20
  });
21
21
  });