@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,96 +0,0 @@
1
- import React from 'react';
2
- import { Pressable, View } from 'react-native';
3
-
4
- import { Surface } from '../../features/surface/public';
5
- import { FocusScope } from '../../internal/focus/FocusScope';
6
- import { Portal } from '../../internal/overlay/Portal';
7
- import { resolvePointerEvents } from '../../internal/resolvePointerEvents';
8
- import { resolveOverlayAnimation } from '../../internal/resolvers';
9
- import { Center } from '../../layout';
10
- import { useTheme } from '../../theme/ThemeContext';
11
- import type { ModalProps } from './types';
12
-
13
- const boxNonePointerEvents = resolvePointerEvents('box-none');
14
-
15
- export function Modal({
16
- visible,
17
- onDismiss,
18
- children,
19
- closeOnBackdrop = true,
20
- interactionPolicy = 'enabled',
21
- testID,
22
- }: ModalProps) {
23
- const { theme } = useTheme();
24
- const animation = resolveOverlayAnimation('modal');
25
- const passive = interactionPolicy === 'passive';
26
-
27
- if (!visible) {
28
- return null;
29
- }
30
-
31
- return (
32
- <Portal layer="modal" visible={visible}>
33
- <View
34
- {...boxNonePointerEvents.props}
35
- style={[
36
- boxNonePointerEvents.style,
37
- {
38
- bottom: 0,
39
- left: 0,
40
- position: 'absolute',
41
- right: 0,
42
- top: 0,
43
- },
44
- ]}
45
- >
46
- <Pressable
47
- onPress={passive ? undefined : closeOnBackdrop ? onDismiss : undefined}
48
- style={{
49
- backgroundColor: theme.semantics.neutral.text,
50
- bottom: 0,
51
- left: 0,
52
- opacity: animation.backdropOpacity,
53
- position: 'absolute',
54
- right: 0,
55
- top: 0,
56
- }}
57
- testID={testID ? `${testID}-backdrop` : undefined}
58
- />
59
- <FocusScope
60
- active={visible}
61
- onEscape={passive ? undefined : onDismiss}
62
- testID={testID ? `${testID}-focus` : undefined}
63
- >
64
- <Center
65
- p="l"
66
- style={{
67
- flex: 1,
68
- }}
69
- >
70
- <View
71
- accessible
72
- accessibilityViewIsModal
73
- style={{
74
- maxWidth: 560,
75
- width: '100%',
76
- }}
77
- >
78
- <Surface
79
- p="l"
80
- style={{
81
- shadowOpacity: 0.14,
82
- shadowRadius: 18,
83
- shadowOffset: { width: 0, height: 8 },
84
- }}
85
- testID={testID}
86
- variant="raised"
87
- >
88
- {children}
89
- </Surface>
90
- </View>
91
- </Center>
92
- </FocusScope>
93
- </View>
94
- </Portal>
95
- );
96
- }
@@ -1,2 +0,0 @@
1
- export { Modal } from './Modal';
2
- export type { ModalProps } from './types';
@@ -1,89 +0,0 @@
1
- import React from 'react';
2
- import { Pressable } from 'react-native';
3
-
4
- import { Box } from '../../features/layout/public';
5
- import { Text } from '../../features/typography/public';
6
- import { useTheme } from '../../theme/ThemeContext';
7
- import { useTabsContext } from './context';
8
- import type { TabProps } from './types';
9
-
10
- export function Tab({
11
- value,
12
- children,
13
- disabled = false,
14
- interactionPolicy = 'enabled',
15
- testID,
16
- }: TabProps) {
17
- const { theme } = useTheme();
18
- const {
19
- activeValue,
20
- getPanelId,
21
- getTabId,
22
- registerTab,
23
- setActiveValue,
24
- setFocusedValue,
25
- unregisterTab,
26
- } = useTabsContext();
27
- const pressableRef = React.useRef<React.ElementRef<typeof Pressable> | null>(null);
28
- const selected = activeValue === value;
29
- const tabId = getTabId(value);
30
- const panelId = getPanelId(value);
31
- const passive = interactionPolicy === 'passive';
32
-
33
- React.useEffect(() => {
34
- registerTab({
35
- disabled,
36
- focus: () => {
37
- const focusable = pressableRef.current as unknown as {
38
- focus?: (() => void) | undefined;
39
- } | null;
40
- focusable?.focus?.();
41
- },
42
- value,
43
- });
44
-
45
- return () => {
46
- unregisterTab(value);
47
- };
48
- }, [disabled, registerTab, unregisterTab, value]);
49
-
50
- return (
51
- <Pressable
52
- accessibilityLabel={undefined}
53
- accessibilityRole="tab"
54
- accessibilityState={{ disabled, selected }}
55
- nativeID={tabId}
56
- aria-controls={panelId}
57
- disabled={disabled}
58
- onFocus={() => setFocusedValue(value)}
59
- onBlur={() => setFocusedValue(undefined)}
60
- onPress={
61
- passive
62
- ? undefined
63
- : () => {
64
- if (!disabled) {
65
- setActiveValue(value);
66
- }
67
- }
68
- }
69
- ref={pressableRef}
70
- testID={testID}
71
- >
72
- <Box
73
- px="m"
74
- py="s"
75
- style={{
76
- borderBottomColor: selected
77
- ? theme.semantics.action.primary.base
78
- : theme.semantics.border.default,
79
- borderBottomWidth: 2,
80
- opacity: disabled ? 0.64 : 1,
81
- }}
82
- >
83
- <Text color={selected ? 'primary' : undefined} variant="label" weight="medium">
84
- {children}
85
- </Text>
86
- </Box>
87
- </Pressable>
88
- );
89
- }
@@ -1,51 +0,0 @@
1
- import React from 'react';
2
-
3
- import { Box } from '../../features/layout/public';
4
- import { useFocusManager } from '../../internal/focus/useFocusManager';
5
- import { useTabsContext } from './context';
6
- import { resolveNextTabValue } from './navigation';
7
- import type { TabListProps } from './types';
8
-
9
- export function TabList({ children, testID }: TabListProps) {
10
- const { bindKeydown } = useFocusManager();
11
- const { focusedValue, setActiveValue, tabs } = useTabsContext();
12
-
13
- React.useEffect(() => {
14
- if (!focusedValue) {
15
- return undefined;
16
- }
17
-
18
- return bindKeydown((event) => {
19
- const nextValue =
20
- event.key === 'ArrowLeft' ||
21
- event.key === 'ArrowRight' ||
22
- event.key === 'ArrowUp' ||
23
- event.key === 'ArrowDown' ||
24
- event.key === 'Home' ||
25
- event.key === 'End'
26
- ? resolveNextTabValue(tabs, focusedValue, event.key)
27
- : undefined;
28
-
29
- if (!nextValue) {
30
- return;
31
- }
32
-
33
- event.preventDefault();
34
- const nextTab = tabs.find((tab) => tab.value === nextValue);
35
- nextTab?.focus();
36
- setActiveValue(nextValue);
37
- });
38
- }, [bindKeydown, focusedValue, setActiveValue, tabs]);
39
-
40
- return (
41
- <Box
42
- accessibilityRole="tablist"
43
- style={{
44
- flexDirection: 'row',
45
- }}
46
- testID={testID}
47
- >
48
- {children}
49
- </Box>
50
- );
51
- }
@@ -1,67 +0,0 @@
1
- import React from 'react';
2
-
3
- import { Box } from '../../features/layout/public';
4
- import { useControllableState } from '../../internal/useControllableState';
5
- import { getTabId as resolveTabId, getTabPanelId as resolveTabPanelId } from './a11y';
6
- import { type TabRegistration, TabsContext } from './context';
7
- import type { TabsProps } from './types';
8
-
9
- export function Tabs({ children, value, defaultValue, onValueChange, testID }: TabsProps) {
10
- const [activeValue, setActiveValue] = useControllableState<string | undefined>({
11
- value,
12
- defaultValue,
13
- onChange: (nextValue) => {
14
- if (nextValue !== undefined) {
15
- onValueChange?.(nextValue);
16
- }
17
- },
18
- });
19
- const [focusedValue, setFocusedValue] = React.useState<string | undefined>(undefined);
20
- const [tabs, setTabs] = React.useState<TabRegistration[]>([]);
21
-
22
- const registerTab = React.useCallback((tab: TabRegistration) => {
23
- setTabs((current) => {
24
- return current.some((entry) => entry.value === tab.value)
25
- ? current.map((entry) => (entry.value === tab.value ? tab : entry))
26
- : [...current, tab];
27
- });
28
- }, []);
29
-
30
- const unregisterTab = React.useCallback((valueToRemove: string) => {
31
- setTabs((current) => current.filter((entry) => entry.value !== valueToRemove));
32
- }, []);
33
-
34
- React.useEffect(() => {
35
- if (activeValue !== undefined) {
36
- return;
37
- }
38
-
39
- const firstEnabledTab = tabs.find((entry) => !entry.disabled);
40
- if (firstEnabledTab) {
41
- setActiveValue(firstEnabledTab.value);
42
- }
43
- }, [activeValue, setActiveValue, tabs]);
44
-
45
- const contextValue = React.useMemo(
46
- () => ({
47
- activeValue,
48
- focusedValue,
49
- getPanelId: (tabValue: string) => resolveTabPanelId(testID, tabValue),
50
- getTabId: (tabValue: string) => resolveTabId(testID, tabValue),
51
- registerTab,
52
- setActiveValue: (nextValue: string) => {
53
- setActiveValue(nextValue);
54
- },
55
- setFocusedValue,
56
- tabs,
57
- unregisterTab,
58
- }),
59
- [activeValue, focusedValue, registerTab, setActiveValue, tabs, testID, unregisterTab],
60
- );
61
-
62
- return (
63
- <TabsContext value={contextValue}>
64
- <Box testID={testID}>{children}</Box>
65
- </TabsContext>
66
- );
67
- }
@@ -1,15 +0,0 @@
1
- import { describe, expect, it } from 'bun:test';
2
-
3
- import { getTabId, getTabPanelId } from './a11y';
4
-
5
- describe('tabs accessibility ids', () => {
6
- it('creates stable ids from tab values', () => {
7
- expect(getTabId(undefined, 'Account Settings')).toBe('tabs-tab-Account-Settings');
8
- expect(getTabPanelId(undefined, 'Account Settings')).toBe('tabs-panel-Account-Settings');
9
- });
10
-
11
- it('namespaces ids by tabs testID when provided', () => {
12
- expect(getTabId('settings', 'billing')).toBe('settings-tabs-tab-billing');
13
- expect(getTabPanelId('settings', 'billing')).toBe('settings-tabs-panel-billing');
14
- });
15
- });
@@ -1,15 +0,0 @@
1
- function sanitizeTabValue(value: string): string {
2
- return value.trim().replace(/[^a-zA-Z0-9_-]+/g, '-');
3
- }
4
-
5
- function createTabsIdBase(testID: string | undefined): string {
6
- return testID ? `${testID}-tabs` : 'tabs';
7
- }
8
-
9
- export function getTabId(testID: string | undefined, value: string): string {
10
- return `${createTabsIdBase(testID)}-tab-${sanitizeTabValue(value)}`;
11
- }
12
-
13
- export function getTabPanelId(testID: string | undefined, value: string): string {
14
- return `${createTabsIdBase(testID)}-panel-${sanitizeTabValue(value)}`;
15
- }
@@ -1,31 +0,0 @@
1
- import React from 'react';
2
-
3
- export interface TabRegistration {
4
- disabled: boolean;
5
- focus: () => void;
6
- value: string;
7
- }
8
-
9
- export interface TabsContextValue {
10
- activeValue: string | undefined;
11
- focusedValue: string | undefined;
12
- getPanelId: (value: string) => string;
13
- getTabId: (value: string) => string;
14
- registerTab: (tab: TabRegistration) => void;
15
- setActiveValue: (value: string) => void;
16
- setFocusedValue: (value: string | undefined) => void;
17
- tabs: TabRegistration[];
18
- unregisterTab: (value: string) => void;
19
- }
20
-
21
- export const TabsContext = React.createContext<TabsContextValue | null>(null);
22
-
23
- export function useTabsContext(): TabsContextValue {
24
- const value = React.use(TabsContext);
25
-
26
- if (!value) {
27
- throw new Error('Tabs components must be used within <Tabs>.');
28
- }
29
-
30
- return value;
31
- }
@@ -1,5 +0,0 @@
1
- export { Tab } from './Tab';
2
- export { TabList } from './TabList';
3
- export { TabPanel } from './TabPanel';
4
- export { Tabs } from './Tabs';
5
- export type { TabListProps, TabPanelProps, TabProps, TabsProps } from './types';
@@ -1,32 +0,0 @@
1
- import type { TabRegistration } from './context';
2
-
3
- export function resolveNextTabValue(
4
- tabs: TabRegistration[],
5
- currentValue: string | undefined,
6
- key: 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'ArrowDown' | 'Home' | 'End',
7
- ): string | undefined {
8
- const enabledTabs = tabs.filter((tab) => !tab.disabled);
9
- if (enabledTabs.length === 0) {
10
- return undefined;
11
- }
12
-
13
- if (key === 'Home') {
14
- return enabledTabs[0]?.value;
15
- }
16
-
17
- if (key === 'End') {
18
- return enabledTabs[enabledTabs.length - 1]?.value;
19
- }
20
-
21
- const currentIndex = enabledTabs.findIndex((tab) => tab.value === currentValue);
22
- if (currentIndex === -1) {
23
- return enabledTabs[0]?.value;
24
- }
25
-
26
- const nextIndex =
27
- key === 'ArrowLeft' || key === 'ArrowUp'
28
- ? (currentIndex - 1 + enabledTabs.length) % enabledTabs.length
29
- : (currentIndex + 1) % enabledTabs.length;
30
-
31
- return enabledTabs[nextIndex]?.value;
32
- }
@@ -1,30 +0,0 @@
1
- import type React from 'react';
2
-
3
- import type { InteractionPolicy } from '../../interactionPolicy';
4
-
5
- export interface TabsProps {
6
- children?: React.ReactNode;
7
- value?: string;
8
- defaultValue?: string;
9
- onValueChange?: ((value: string) => void) | undefined;
10
- testID?: string;
11
- }
12
-
13
- export interface TabListProps {
14
- children?: React.ReactNode;
15
- testID?: string;
16
- }
17
-
18
- export interface TabProps {
19
- value: string;
20
- children?: React.ReactNode;
21
- disabled?: boolean;
22
- interactionPolicy?: InteractionPolicy;
23
- testID?: string;
24
- }
25
-
26
- export interface TabPanelProps {
27
- value: string;
28
- children?: React.ReactNode;
29
- testID?: string;
30
- }
@@ -1,117 +0,0 @@
1
- import React from 'react';
2
- import { type LayoutRectangle, Platform, Pressable, View } from 'react-native';
3
-
4
- import { Surface } from '../../features/surface/public';
5
- import { Text } from '../../features/typography/public';
6
- import { Portal } from '../../internal/overlay/Portal';
7
- import { resolvePointerEvents } from '../../internal/resolvePointerEvents';
8
- import { resolveOverlayAnimation } from '../../internal/resolvers';
9
- import { useTheme } from '../../theme/ThemeContext';
10
- import type { TooltipProps } from './types';
11
-
12
- const noPointerEvents = resolvePointerEvents('none');
13
-
14
- interface MeasurableNode {
15
- measureInWindow?: (
16
- callback: (x: number, y: number, width: number, height: number) => void,
17
- ) => void;
18
- }
19
-
20
- function measureNode(node: unknown, callback: (layout: LayoutRectangle) => void) {
21
- const measurableNode = node as MeasurableNode | null;
22
- measurableNode?.measureInWindow?.((x, y, width, height) => {
23
- callback({ height, width, x, y });
24
- });
25
- }
26
-
27
- export function Tooltip({
28
- children,
29
- content,
30
- delay = 150,
31
- interactionPolicy = 'enabled',
32
- placement = 'top',
33
- testID,
34
- }: TooltipProps) {
35
- const { theme } = useTheme();
36
- const animation = resolveOverlayAnimation('tooltip');
37
- const anchorRef = React.useRef<View | null>(null);
38
- const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
39
- const [visible, setVisible] = React.useState(false);
40
- const [layout, setLayout] = React.useState<LayoutRectangle | null>(null);
41
- const passive = interactionPolicy === 'passive';
42
-
43
- const show = React.useCallback(() => {
44
- if (timeoutRef.current) {
45
- clearTimeout(timeoutRef.current);
46
- }
47
-
48
- timeoutRef.current = setTimeout(() => {
49
- measureNode(anchorRef.current, setLayout);
50
- setVisible(true);
51
- }, delay);
52
- }, [delay]);
53
-
54
- const hide = React.useCallback(() => {
55
- if (timeoutRef.current) {
56
- clearTimeout(timeoutRef.current);
57
- timeoutRef.current = null;
58
- }
59
- setVisible(false);
60
- }, []);
61
-
62
- React.useEffect(
63
- () => () => {
64
- if (timeoutRef.current) {
65
- clearTimeout(timeoutRef.current);
66
- }
67
- },
68
- [],
69
- );
70
-
71
- const tooltipLeft = layout ? layout.x : 0;
72
- const tooltipTop =
73
- layout && placement === 'top'
74
- ? layout.y - animation.offset
75
- : layout
76
- ? layout.y + layout.height + animation.offset
77
- : 0;
78
-
79
- return (
80
- <View collapsable={false} ref={anchorRef} testID={testID ? `${testID}-anchor` : undefined}>
81
- <Pressable
82
- onBlur={passive ? undefined : hide}
83
- onFocus={passive ? undefined : show}
84
- onHoverIn={Platform.OS === 'web' && !passive ? show : undefined}
85
- onHoverOut={Platform.OS === 'web' && !passive ? hide : undefined}
86
- >
87
- {children}
88
- </Pressable>
89
- <Portal layer="tooltip" visible={visible && Boolean(layout)}>
90
- <View
91
- {...noPointerEvents.props}
92
- style={[
93
- noPointerEvents.style,
94
- {
95
- left: tooltipLeft,
96
- position: 'absolute',
97
- top: tooltipTop,
98
- },
99
- ]}
100
- >
101
- <Surface
102
- p="s"
103
- style={{
104
- backgroundColor: theme.semantics.surface.inverse,
105
- }}
106
- testID={testID}
107
- variant="raised"
108
- >
109
- <Text emphasis="inverse" variant="caption">
110
- {content}
111
- </Text>
112
- </Surface>
113
- </View>
114
- </Portal>
115
- </View>
116
- );
117
- }
@@ -1,2 +0,0 @@
1
- export { Tooltip } from './Tooltip';
2
- export type { TooltipProps } from './types';