@ankhorage/surface 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +692 -47
- package/dist/components/action-sheet/ActionSheet.d.ts +1 -1
- package/dist/components/action-sheet/ActionSheet.d.ts.map +1 -1
- package/dist/components/action-sheet/ActionSheet.js +5 -4
- package/dist/components/action-sheet/ActionSheet.js.map +1 -1
- package/dist/components/action-sheet/ActionSheetItem.d.ts +1 -1
- package/dist/components/action-sheet/ActionSheetItem.d.ts.map +1 -1
- package/dist/components/action-sheet/ActionSheetItem.js +2 -2
- package/dist/components/action-sheet/ActionSheetItem.js.map +1 -1
- package/dist/components/action-sheet/types.d.ts +3 -0
- package/dist/components/action-sheet/types.d.ts.map +1 -1
- package/dist/components/action-sheet/types.js.map +1 -1
- package/dist/components/drawer/Drawer.d.ts +1 -1
- package/dist/components/drawer/Drawer.d.ts.map +1 -1
- package/dist/components/drawer/Drawer.js +4 -3
- package/dist/components/drawer/Drawer.js.map +1 -1
- package/dist/components/drawer/types.d.ts +2 -0
- package/dist/components/drawer/types.d.ts.map +1 -1
- package/dist/components/drawer/types.js.map +1 -1
- package/dist/components/menu/Menu.d.ts +1 -1
- package/dist/components/menu/Menu.d.ts.map +1 -1
- package/dist/components/menu/Menu.js +10 -6
- package/dist/components/menu/Menu.js.map +1 -1
- package/dist/components/menu/types.d.ts +2 -0
- package/dist/components/menu/types.d.ts.map +1 -1
- package/dist/components/menu/types.js.map +1 -1
- package/dist/components/modal/Modal.d.ts +1 -1
- package/dist/components/modal/Modal.d.ts.map +1 -1
- package/dist/components/modal/Modal.js +4 -3
- package/dist/components/modal/Modal.js.map +1 -1
- package/dist/components/modal/types.d.ts +2 -0
- package/dist/components/modal/types.d.ts.map +1 -1
- package/dist/components/modal/types.js.map +1 -1
- package/dist/components/navigation/DrawerNavigation.d.ts +1 -1
- package/dist/components/navigation/DrawerNavigation.d.ts.map +1 -1
- package/dist/components/navigation/DrawerNavigation.js +2 -2
- package/dist/components/navigation/DrawerNavigation.js.map +1 -1
- package/dist/components/navigation/DrawerNavigationItem.d.ts +1 -1
- package/dist/components/navigation/DrawerNavigationItem.d.ts.map +1 -1
- package/dist/components/navigation/DrawerNavigationItem.js +2 -2
- package/dist/components/navigation/DrawerNavigationItem.js.map +1 -1
- package/dist/components/navigation/NavigationItem.d.ts +1 -1
- package/dist/components/navigation/NavigationItem.d.ts.map +1 -1
- package/dist/components/navigation/NavigationItem.js +2 -2
- package/dist/components/navigation/NavigationItem.js.map +1 -1
- package/dist/components/navigation/NavigationList.d.ts +1 -1
- package/dist/components/navigation/NavigationList.d.ts.map +1 -1
- package/dist/components/navigation/NavigationList.js +2 -2
- package/dist/components/navigation/NavigationList.js.map +1 -1
- package/dist/components/navigation/TabBar.d.ts +1 -1
- package/dist/components/navigation/TabBar.d.ts.map +1 -1
- package/dist/components/navigation/TabBar.js +2 -2
- package/dist/components/navigation/TabBar.js.map +1 -1
- package/dist/components/navigation/TabBarItem.d.ts +1 -1
- package/dist/components/navigation/TabBarItem.d.ts.map +1 -1
- package/dist/components/navigation/TabBarItem.js +2 -2
- package/dist/components/navigation/TabBarItem.js.map +1 -1
- package/dist/components/navigation/types.d.ts +7 -0
- package/dist/components/navigation/types.d.ts.map +1 -1
- package/dist/components/navigation/types.js.map +1 -1
- package/dist/components/tabs/Tab.d.ts +1 -1
- package/dist/components/tabs/Tab.d.ts.map +1 -1
- package/dist/components/tabs/Tab.js +9 -6
- package/dist/components/tabs/Tab.js.map +1 -1
- package/dist/components/tabs/types.d.ts +2 -0
- package/dist/components/tabs/types.d.ts.map +1 -1
- package/dist/components/tabs/types.js.map +1 -1
- package/dist/components/text-input/TextInput.d.ts +1 -1
- package/dist/components/text-input/TextInput.d.ts.map +1 -1
- package/dist/components/text-input/TextInput.js +8 -2
- package/dist/components/text-input/TextInput.js.map +1 -1
- package/dist/components/text-input/types.d.ts +2 -0
- package/dist/components/text-input/types.d.ts.map +1 -1
- package/dist/components/text-input/types.js.map +1 -1
- package/dist/components/toast/Toast.d.ts +1 -1
- package/dist/components/toast/Toast.d.ts.map +1 -1
- package/dist/components/toast/Toast.js +3 -2
- package/dist/components/toast/Toast.js.map +1 -1
- package/dist/components/toast/types.d.ts +2 -0
- package/dist/components/toast/types.d.ts.map +1 -1
- package/dist/components/toast/types.js.map +1 -1
- package/dist/components/tooltip/Tooltip.d.ts +1 -1
- package/dist/components/tooltip/Tooltip.d.ts.map +1 -1
- package/dist/components/tooltip/Tooltip.js +4 -3
- package/dist/components/tooltip/Tooltip.js.map +1 -1
- package/dist/components/tooltip/types.d.ts +2 -0
- package/dist/components/tooltip/types.d.ts.map +1 -1
- package/dist/components/tooltip/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interactionPolicy.d.ts +5 -0
- package/dist/interactionPolicy.d.ts.map +1 -0
- package/dist/interactionPolicy.js +2 -0
- package/dist/interactionPolicy.js.map +1 -0
- package/dist/primitives/button-base/ButtonBase.d.ts +1 -1
- package/dist/primitives/button-base/ButtonBase.d.ts.map +1 -1
- package/dist/primitives/button-base/ButtonBase.js +3 -2
- package/dist/primitives/button-base/ButtonBase.js.map +1 -1
- package/dist/primitives/button-base/types.d.ts +2 -1
- package/dist/primitives/button-base/types.d.ts.map +1 -1
- package/dist/primitives/button-base/types.js.map +1 -1
- package/dist/theme/colorEngine.d.ts.map +1 -1
- package/dist/theme/colorEngine.js +4 -1
- package/dist/theme/colorEngine.js.map +1 -1
- package/dist/theme/types.d.ts +1 -0
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/theme/types.js.map +1 -1
- package/package.json +5 -2
- package/src/components/action-sheet/ActionSheet.tsx +5 -2
- package/src/components/action-sheet/ActionSheetItem.tsx +2 -0
- package/src/components/action-sheet/types.ts +3 -0
- package/src/components/drawer/Drawer.tsx +4 -2
- package/src/components/drawer/types.ts +3 -0
- package/src/components/menu/Menu.tsx +18 -6
- package/src/components/menu/types.ts +3 -0
- package/src/components/modal/Modal.tsx +4 -2
- package/src/components/modal/types.ts +3 -0
- package/src/components/navigation/DrawerNavigation.tsx +8 -1
- package/src/components/navigation/DrawerNavigationItem.tsx +7 -1
- package/src/components/navigation/NavigationItem.tsx +7 -1
- package/src/components/navigation/NavigationList.tsx +8 -1
- package/src/components/navigation/TabBar.tsx +8 -2
- package/src/components/navigation/TabBarItem.tsx +2 -1
- package/src/components/navigation/policy.test.ts +96 -0
- package/src/components/navigation/types.ts +7 -0
- package/src/components/tabs/Tab.tsx +17 -6
- package/src/components/tabs/types.ts +3 -0
- package/src/components/text-input/TextInput.tsx +10 -2
- package/src/components/text-input/types.ts +2 -0
- package/src/components/toast/Toast.tsx +10 -2
- package/src/components/toast/types.ts +2 -0
- package/src/components/tooltip/Tooltip.tsx +7 -5
- package/src/components/tooltip/types.ts +3 -0
- package/src/index.test.ts +1 -0
- package/src/index.ts +1 -0
- package/src/interactionPolicy.test.ts +86 -0
- package/src/interactionPolicy.ts +5 -0
- package/src/primitives/button-base/ButtonBase.tsx +4 -2
- package/src/primitives/button-base/types.ts +3 -1
- package/src/theme/colorEngine.test.ts +3 -2
- package/src/theme/colorEngine.ts +4 -2
- package/src/theme/inverseSemantics.test.ts +48 -0
- package/src/theme/types.ts +1 -0
|
@@ -14,10 +14,12 @@ export function Drawer({
|
|
|
14
14
|
position = 'right',
|
|
15
15
|
children,
|
|
16
16
|
closeOnBackdrop = true,
|
|
17
|
+
interactionPolicy = 'enabled',
|
|
17
18
|
testID,
|
|
18
19
|
}: DrawerProps) {
|
|
19
20
|
const { theme } = useTheme();
|
|
20
21
|
const animation = resolveOverlayAnimation('drawer');
|
|
22
|
+
const passive = interactionPolicy === 'passive';
|
|
21
23
|
|
|
22
24
|
if (!visible) {
|
|
23
25
|
return null;
|
|
@@ -36,7 +38,7 @@ export function Drawer({
|
|
|
36
38
|
}}
|
|
37
39
|
>
|
|
38
40
|
<Pressable
|
|
39
|
-
onPress={closeOnBackdrop ? onDismiss : undefined}
|
|
41
|
+
onPress={passive ? undefined : closeOnBackdrop ? onDismiss : undefined}
|
|
40
42
|
style={{
|
|
41
43
|
backgroundColor: theme.semantics.neutral.text,
|
|
42
44
|
bottom: 0,
|
|
@@ -50,7 +52,7 @@ export function Drawer({
|
|
|
50
52
|
/>
|
|
51
53
|
<FocusScope
|
|
52
54
|
active={visible}
|
|
53
|
-
onEscape={onDismiss}
|
|
55
|
+
onEscape={passive ? undefined : onDismiss}
|
|
54
56
|
testID={testID ? `${testID}-focus` : undefined}
|
|
55
57
|
>
|
|
56
58
|
<Box
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
|
|
3
|
+
import type { InteractionPolicy } from '../../interactionPolicy';
|
|
4
|
+
|
|
3
5
|
export interface DrawerProps {
|
|
4
6
|
visible: boolean;
|
|
5
7
|
onDismiss?: (() => void) | undefined;
|
|
6
8
|
position?: 'left' | 'right';
|
|
7
9
|
children?: React.ReactNode;
|
|
8
10
|
closeOnBackdrop?: boolean;
|
|
11
|
+
interactionPolicy?: InteractionPolicy;
|
|
9
12
|
testID?: string;
|
|
10
13
|
}
|
|
@@ -53,7 +53,14 @@ function renderActionContent(action: MenuAction, active: boolean) {
|
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
export function Menu({
|
|
56
|
+
export function Menu({
|
|
57
|
+
trigger,
|
|
58
|
+
actions,
|
|
59
|
+
dismiss,
|
|
60
|
+
closeOnSelect = true,
|
|
61
|
+
interactionPolicy = 'enabled',
|
|
62
|
+
testID,
|
|
63
|
+
}: MenuProps) {
|
|
57
64
|
const { theme } = useTheme();
|
|
58
65
|
const { bindKeydown } = useFocusManager();
|
|
59
66
|
const animation = resolveOverlayAnimation('menu');
|
|
@@ -61,6 +68,7 @@ export function Menu({ trigger, actions, dismiss, closeOnSelect = true, testID }
|
|
|
61
68
|
const [open, setOpen] = React.useState(false);
|
|
62
69
|
const [layout, setLayout] = React.useState<LayoutRectangle | null>(null);
|
|
63
70
|
const [activeIndex, setActiveIndex] = React.useState(0);
|
|
71
|
+
const passive = interactionPolicy === 'passive';
|
|
64
72
|
|
|
65
73
|
const closeMenu = React.useCallback(() => {
|
|
66
74
|
setOpen(false);
|
|
@@ -99,6 +107,10 @@ export function Menu({ trigger, actions, dismiss, closeOnSelect = true, testID }
|
|
|
99
107
|
}
|
|
100
108
|
|
|
101
109
|
return bindKeydown((event) => {
|
|
110
|
+
if (passive) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
102
114
|
const { key } = event;
|
|
103
115
|
if (key === 'ArrowDown' || key === 'ArrowUp' || key === 'Home' || key === 'End') {
|
|
104
116
|
event.preventDefault();
|
|
@@ -118,12 +130,12 @@ export function Menu({ trigger, actions, dismiss, closeOnSelect = true, testID }
|
|
|
118
130
|
closeMenu();
|
|
119
131
|
}
|
|
120
132
|
});
|
|
121
|
-
}, [activateAction, actions, activeIndex, bindKeydown, closeMenu, open]);
|
|
133
|
+
}, [activateAction, actions, activeIndex, bindKeydown, closeMenu, open, passive]);
|
|
122
134
|
|
|
123
135
|
return (
|
|
124
136
|
<View collapsable={false} ref={anchorRef}>
|
|
125
137
|
<ButtonBase
|
|
126
|
-
onPress={open ? closeMenu : openMenu}
|
|
138
|
+
onPress={passive ? undefined : open ? closeMenu : openMenu}
|
|
127
139
|
testID={testID ? `${testID}-trigger` : undefined}
|
|
128
140
|
>
|
|
129
141
|
{trigger}
|
|
@@ -140,7 +152,7 @@ export function Menu({ trigger, actions, dismiss, closeOnSelect = true, testID }
|
|
|
140
152
|
}}
|
|
141
153
|
>
|
|
142
154
|
<Pressable
|
|
143
|
-
onPress={closeMenu}
|
|
155
|
+
onPress={passive ? undefined : closeMenu}
|
|
144
156
|
style={{
|
|
145
157
|
bottom: 0,
|
|
146
158
|
left: 0,
|
|
@@ -149,7 +161,7 @@ export function Menu({ trigger, actions, dismiss, closeOnSelect = true, testID }
|
|
|
149
161
|
top: 0,
|
|
150
162
|
}}
|
|
151
163
|
/>
|
|
152
|
-
<FocusScope active={open} onEscape={closeMenu}>
|
|
164
|
+
<FocusScope active={open} onEscape={passive ? undefined : closeMenu}>
|
|
153
165
|
<View
|
|
154
166
|
style={{
|
|
155
167
|
left: layout?.x ?? 0,
|
|
@@ -179,7 +191,7 @@ export function Menu({ trigger, actions, dismiss, closeOnSelect = true, testID }
|
|
|
179
191
|
accessibilityState={{ disabled: action.disabled, selected }}
|
|
180
192
|
disabled={action.disabled}
|
|
181
193
|
key={action.id}
|
|
182
|
-
onPress={() => activateAction(action)}
|
|
194
|
+
onPress={passive ? undefined : () => activateAction(action)}
|
|
183
195
|
>
|
|
184
196
|
<Box
|
|
185
197
|
px="m"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
|
|
3
|
+
import type { InteractionPolicy } from '../../interactionPolicy';
|
|
4
|
+
|
|
3
5
|
export type MenuActionIntent = 'default' | 'danger';
|
|
4
6
|
|
|
5
7
|
export interface MenuAction {
|
|
@@ -19,5 +21,6 @@ export interface MenuProps {
|
|
|
19
21
|
actions: readonly MenuAction[];
|
|
20
22
|
dismiss?: () => void;
|
|
21
23
|
closeOnSelect?: boolean;
|
|
24
|
+
interactionPolicy?: InteractionPolicy;
|
|
22
25
|
testID?: string;
|
|
23
26
|
}
|
|
@@ -13,10 +13,12 @@ export function Modal({
|
|
|
13
13
|
onDismiss,
|
|
14
14
|
children,
|
|
15
15
|
closeOnBackdrop = true,
|
|
16
|
+
interactionPolicy = 'enabled',
|
|
16
17
|
testID,
|
|
17
18
|
}: ModalProps) {
|
|
18
19
|
const { theme } = useTheme();
|
|
19
20
|
const animation = resolveOverlayAnimation('modal');
|
|
21
|
+
const passive = interactionPolicy === 'passive';
|
|
20
22
|
|
|
21
23
|
if (!visible) {
|
|
22
24
|
return null;
|
|
@@ -35,7 +37,7 @@ export function Modal({
|
|
|
35
37
|
}}
|
|
36
38
|
>
|
|
37
39
|
<Pressable
|
|
38
|
-
onPress={closeOnBackdrop ? onDismiss : undefined}
|
|
40
|
+
onPress={passive ? undefined : closeOnBackdrop ? onDismiss : undefined}
|
|
39
41
|
style={{
|
|
40
42
|
backgroundColor: theme.semantics.neutral.text,
|
|
41
43
|
bottom: 0,
|
|
@@ -49,7 +51,7 @@ export function Modal({
|
|
|
49
51
|
/>
|
|
50
52
|
<FocusScope
|
|
51
53
|
active={visible}
|
|
52
|
-
onEscape={onDismiss}
|
|
54
|
+
onEscape={passive ? undefined : onDismiss}
|
|
53
55
|
testID={testID ? `${testID}-focus` : undefined}
|
|
54
56
|
>
|
|
55
57
|
<Center
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
|
|
3
|
+
import type { InteractionPolicy } from '../../interactionPolicy';
|
|
4
|
+
|
|
3
5
|
export interface ModalProps {
|
|
4
6
|
visible: boolean;
|
|
5
7
|
onDismiss?: (() => void) | undefined;
|
|
6
8
|
children?: React.ReactNode;
|
|
7
9
|
closeOnBackdrop?: boolean;
|
|
10
|
+
interactionPolicy?: InteractionPolicy;
|
|
8
11
|
testID?: string;
|
|
9
12
|
}
|
|
@@ -10,13 +10,20 @@ export function DrawerNavigation({
|
|
|
10
10
|
footer,
|
|
11
11
|
compact = false,
|
|
12
12
|
testID,
|
|
13
|
+
interactionPolicy,
|
|
13
14
|
}: DrawerNavigationProps) {
|
|
14
15
|
return (
|
|
15
16
|
<Stack gap={compact ? 's' : 'm'} testID={testID}>
|
|
16
17
|
{header}
|
|
17
18
|
<Stack gap={compact ? 'xs' : 's'}>
|
|
18
19
|
{items.map((item) => (
|
|
19
|
-
<DrawerNavigationItem
|
|
20
|
+
<DrawerNavigationItem
|
|
21
|
+
compact={compact}
|
|
22
|
+
interactionPolicy={interactionPolicy}
|
|
23
|
+
item={item}
|
|
24
|
+
key={item.id}
|
|
25
|
+
testID={testID}
|
|
26
|
+
/>
|
|
20
27
|
))}
|
|
21
28
|
</Stack>
|
|
22
29
|
{footer}
|
|
@@ -3,10 +3,16 @@ import React from 'react';
|
|
|
3
3
|
import { NavigationItem } from './NavigationItem';
|
|
4
4
|
import type { DrawerNavigationItemProps } from './types';
|
|
5
5
|
|
|
6
|
-
export function DrawerNavigationItem({
|
|
6
|
+
export function DrawerNavigationItem({
|
|
7
|
+
item,
|
|
8
|
+
compact = false,
|
|
9
|
+
testID,
|
|
10
|
+
interactionPolicy,
|
|
11
|
+
}: DrawerNavigationItemProps) {
|
|
7
12
|
return (
|
|
8
13
|
<NavigationItem
|
|
9
14
|
compact={compact}
|
|
15
|
+
interactionPolicy={interactionPolicy}
|
|
10
16
|
item={{
|
|
11
17
|
...item,
|
|
12
18
|
accessibilityRole: item.accessibilityRole ?? 'button',
|
|
@@ -9,7 +9,12 @@ import { useTheme } from '../../theme/ThemeContext';
|
|
|
9
9
|
import { resolveNavigationAccessibilityState, resolveNavigationItemPresentation } from './helpers';
|
|
10
10
|
import type { NavigationItemProps } from './types';
|
|
11
11
|
|
|
12
|
-
export function NavigationItem({
|
|
12
|
+
export function NavigationItem({
|
|
13
|
+
item,
|
|
14
|
+
compact = false,
|
|
15
|
+
testID,
|
|
16
|
+
interactionPolicy,
|
|
17
|
+
}: NavigationItemProps) {
|
|
13
18
|
const { theme } = useTheme();
|
|
14
19
|
const active = Boolean(item.active);
|
|
15
20
|
const disabled = Boolean(item.disabled);
|
|
@@ -26,6 +31,7 @@ export function NavigationItem({ item, compact = false, testID }: NavigationItem
|
|
|
26
31
|
accessibilityRole={item.accessibilityRole ?? 'button'}
|
|
27
32
|
accessibilityState={resolvedAccessibilityState}
|
|
28
33
|
disabled={disabled}
|
|
34
|
+
interactionPolicy={interactionPolicy}
|
|
29
35
|
onPress={item.onPress}
|
|
30
36
|
testID={resolvedTestID}
|
|
31
37
|
>
|
|
@@ -9,6 +9,7 @@ export function NavigationList({
|
|
|
9
9
|
orientation = 'vertical',
|
|
10
10
|
compact = false,
|
|
11
11
|
testID,
|
|
12
|
+
interactionPolicy,
|
|
12
13
|
}: NavigationListProps) {
|
|
13
14
|
return (
|
|
14
15
|
<Stack
|
|
@@ -17,7 +18,13 @@ export function NavigationList({
|
|
|
17
18
|
testID={testID}
|
|
18
19
|
>
|
|
19
20
|
{items.map((item) => (
|
|
20
|
-
<NavigationItem
|
|
21
|
+
<NavigationItem
|
|
22
|
+
compact={compact}
|
|
23
|
+
interactionPolicy={interactionPolicy}
|
|
24
|
+
item={item}
|
|
25
|
+
key={item.id}
|
|
26
|
+
testID={testID}
|
|
27
|
+
/>
|
|
21
28
|
))}
|
|
22
29
|
</Stack>
|
|
23
30
|
);
|
|
@@ -5,7 +5,7 @@ import { useTheme } from '../../theme/ThemeContext';
|
|
|
5
5
|
import { TabBarItem } from './TabBarItem';
|
|
6
6
|
import type { TabBarProps } from './types';
|
|
7
7
|
|
|
8
|
-
export function TabBar({ items, compact = false, testID }: TabBarProps) {
|
|
8
|
+
export function TabBar({ items, compact = false, testID, interactionPolicy }: TabBarProps) {
|
|
9
9
|
const { theme } = useTheme();
|
|
10
10
|
|
|
11
11
|
return (
|
|
@@ -18,7 +18,13 @@ export function TabBar({ items, compact = false, testID }: TabBarProps) {
|
|
|
18
18
|
testID={testID}
|
|
19
19
|
>
|
|
20
20
|
{items.map((item) => (
|
|
21
|
-
<TabBarItem
|
|
21
|
+
<TabBarItem
|
|
22
|
+
compact={compact}
|
|
23
|
+
interactionPolicy={interactionPolicy}
|
|
24
|
+
item={item}
|
|
25
|
+
key={item.id}
|
|
26
|
+
testID={testID}
|
|
27
|
+
/>
|
|
22
28
|
))}
|
|
23
29
|
</Box>
|
|
24
30
|
);
|
|
@@ -9,7 +9,7 @@ import { useTheme } from '../../theme/ThemeContext';
|
|
|
9
9
|
import { resolveNavigationAccessibilityState, resolveNavigationItemPresentation } from './helpers';
|
|
10
10
|
import type { TabBarItemProps } from './types';
|
|
11
11
|
|
|
12
|
-
export function TabBarItem({ item, compact = false, testID }: TabBarItemProps) {
|
|
12
|
+
export function TabBarItem({ item, compact = false, testID, interactionPolicy }: TabBarItemProps) {
|
|
13
13
|
const { theme } = useTheme();
|
|
14
14
|
const active = Boolean(item.active);
|
|
15
15
|
const disabled = Boolean(item.disabled);
|
|
@@ -26,6 +26,7 @@ export function TabBarItem({ item, compact = false, testID }: TabBarItemProps) {
|
|
|
26
26
|
accessibilityRole={item.accessibilityRole ?? 'tab'}
|
|
27
27
|
accessibilityState={resolvedAccessibilityState}
|
|
28
28
|
disabled={disabled}
|
|
29
|
+
interactionPolicy={interactionPolicy}
|
|
29
30
|
onPress={item.onPress}
|
|
30
31
|
style={{ flex: 1 }}
|
|
31
32
|
testID={resolvedTestID}
|
|
@@ -42,3 +42,99 @@ describe('navigation module boundaries', () => {
|
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
describe('navigation interaction policy', () => {
|
|
47
|
+
it('host component prop types include interactionPolicy', () => {
|
|
48
|
+
const sources = listNavigationSources().map((path) => ({
|
|
49
|
+
path,
|
|
50
|
+
content: readFileSync(path, 'utf8'),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
const hostComponents = [{ path: 'types.ts', prop: 'interactionPolicy?: InteractionPolicy' }];
|
|
54
|
+
|
|
55
|
+
sources.forEach(({ content, path }) => {
|
|
56
|
+
const component = hostComponents.find((host) => path.endsWith(host.path));
|
|
57
|
+
if (component) {
|
|
58
|
+
expect(content, path).toContain(component.prop);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('navigation components accept interactionPolicy prop', () => {
|
|
64
|
+
const sources = listNavigationSources().map((path) => ({
|
|
65
|
+
path,
|
|
66
|
+
content: readFileSync(path, 'utf8'),
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
const components = [
|
|
70
|
+
'NavigationItem.tsx',
|
|
71
|
+
'NavigationList.tsx',
|
|
72
|
+
'TabBar.tsx',
|
|
73
|
+
'TabBarItem.tsx',
|
|
74
|
+
'DrawerNavigation.tsx',
|
|
75
|
+
'DrawerNavigationItem.tsx',
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
sources.forEach(({ content, path }) => {
|
|
79
|
+
const component = components.find((c) => path.endsWith(c));
|
|
80
|
+
if (component) {
|
|
81
|
+
expect(content, path).toContain('interactionPolicy');
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('direct ButtonBase owners forward interactionPolicy unchanged', () => {
|
|
87
|
+
const sources = listNavigationSources().map((path) => ({
|
|
88
|
+
path,
|
|
89
|
+
content: readFileSync(path, 'utf8'),
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
const directButtonBaseOwners = ['NavigationItem.tsx', 'TabBarItem.tsx'];
|
|
93
|
+
|
|
94
|
+
sources.forEach(({ content, path }) => {
|
|
95
|
+
const component = directButtonBaseOwners.find((owner) => path.endsWith(owner));
|
|
96
|
+
if (component) {
|
|
97
|
+
expect(content, path).toContain('interactionPolicy={interactionPolicy}');
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('container components forward interactionPolicy to children', () => {
|
|
103
|
+
const sources = listNavigationSources().map((path) => ({
|
|
104
|
+
path,
|
|
105
|
+
content: readFileSync(path, 'utf8'),
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
const forwardingMap: Record<string, string> = {
|
|
109
|
+
'NavigationList.tsx': 'NavigationItem',
|
|
110
|
+
'TabBar.tsx': 'TabBarItem',
|
|
111
|
+
'DrawerNavigation.tsx': 'DrawerNavigationItem',
|
|
112
|
+
'DrawerNavigationItem.tsx': 'NavigationItem',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
sources.forEach(({ content, path }) => {
|
|
116
|
+
const filename = path.split('/').pop();
|
|
117
|
+
if (filename && forwardingMap[filename]) {
|
|
118
|
+
expect(content, path).toContain(`interactionPolicy={interactionPolicy}`);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('does not map passive interactionPolicy to disabled in navigation components', () => {
|
|
124
|
+
const sources = listNavigationSources().map((path) => ({
|
|
125
|
+
path,
|
|
126
|
+
content: readFileSync(path, 'utf8'),
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
const passiveToDisabledPatterns = [
|
|
130
|
+
/disabled=\{interactionPolicy === 'passive'\}/,
|
|
131
|
+
/disabled=\{passive/,
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
sources.forEach(({ content, path }) => {
|
|
135
|
+
passiveToDisabledPatterns.forEach((pattern) => {
|
|
136
|
+
expect(content, path).not.toMatch(pattern);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import type { AccessibilityRole, AccessibilityState } from 'react-native';
|
|
3
3
|
|
|
4
|
+
import type { InteractionPolicy } from '../../interactionPolicy';
|
|
4
5
|
import type { IconProps } from '../../primitives/icon';
|
|
5
6
|
|
|
6
7
|
export type NavigationItemIcon = Pick<IconProps, 'name' | 'provider'>;
|
|
@@ -23,6 +24,7 @@ export interface NavigationItemProps {
|
|
|
23
24
|
item: NavigationItemSpec;
|
|
24
25
|
compact?: boolean;
|
|
25
26
|
testID?: string;
|
|
27
|
+
interactionPolicy?: InteractionPolicy;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export interface NavigationListProps {
|
|
@@ -30,18 +32,21 @@ export interface NavigationListProps {
|
|
|
30
32
|
orientation?: 'vertical' | 'horizontal';
|
|
31
33
|
compact?: boolean;
|
|
32
34
|
testID?: string;
|
|
35
|
+
interactionPolicy?: InteractionPolicy;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export interface TabBarProps {
|
|
36
39
|
items: readonly NavigationItemSpec[];
|
|
37
40
|
compact?: boolean;
|
|
38
41
|
testID?: string;
|
|
42
|
+
interactionPolicy?: InteractionPolicy;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
export interface TabBarItemProps {
|
|
42
46
|
item: NavigationItemSpec;
|
|
43
47
|
compact?: boolean;
|
|
44
48
|
testID?: string;
|
|
49
|
+
interactionPolicy?: InteractionPolicy;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
export interface DrawerNavigationProps {
|
|
@@ -50,10 +55,12 @@ export interface DrawerNavigationProps {
|
|
|
50
55
|
footer?: React.ReactNode;
|
|
51
56
|
compact?: boolean;
|
|
52
57
|
testID?: string;
|
|
58
|
+
interactionPolicy?: InteractionPolicy;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
export interface DrawerNavigationItemProps {
|
|
56
62
|
item: NavigationItemSpec;
|
|
57
63
|
compact?: boolean;
|
|
58
64
|
testID?: string;
|
|
65
|
+
interactionPolicy?: InteractionPolicy;
|
|
59
66
|
}
|
|
@@ -7,7 +7,13 @@ import { useTheme } from '../../theme/ThemeContext';
|
|
|
7
7
|
import { useTabsContext } from './context';
|
|
8
8
|
import type { TabProps } from './types';
|
|
9
9
|
|
|
10
|
-
export function Tab({
|
|
10
|
+
export function Tab({
|
|
11
|
+
value,
|
|
12
|
+
children,
|
|
13
|
+
disabled = false,
|
|
14
|
+
interactionPolicy = 'enabled',
|
|
15
|
+
testID,
|
|
16
|
+
}: TabProps) {
|
|
11
17
|
const { theme } = useTheme();
|
|
12
18
|
const {
|
|
13
19
|
activeValue,
|
|
@@ -22,6 +28,7 @@ export function Tab({ value, children, disabled = false, testID }: TabProps) {
|
|
|
22
28
|
const selected = activeValue === value;
|
|
23
29
|
const tabId = getTabId(value);
|
|
24
30
|
const panelId = getPanelId(value);
|
|
31
|
+
const passive = interactionPolicy === 'passive';
|
|
25
32
|
|
|
26
33
|
React.useEffect(() => {
|
|
27
34
|
registerTab({
|
|
@@ -50,11 +57,15 @@ export function Tab({ value, children, disabled = false, testID }: TabProps) {
|
|
|
50
57
|
disabled={disabled}
|
|
51
58
|
onFocus={() => setFocusedValue(value)}
|
|
52
59
|
onBlur={() => setFocusedValue(undefined)}
|
|
53
|
-
onPress={
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
onPress={
|
|
61
|
+
passive
|
|
62
|
+
? undefined
|
|
63
|
+
: () => {
|
|
64
|
+
if (!disabled) {
|
|
65
|
+
setActiveValue(value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
58
69
|
ref={pressableRef}
|
|
59
70
|
testID={testID}
|
|
60
71
|
>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
|
|
3
|
+
import type { InteractionPolicy } from '../../interactionPolicy';
|
|
4
|
+
|
|
3
5
|
export interface TabsProps {
|
|
4
6
|
children?: React.ReactNode;
|
|
5
7
|
value?: string;
|
|
@@ -17,6 +19,7 @@ export interface TabProps {
|
|
|
17
19
|
value: string;
|
|
18
20
|
children?: React.ReactNode;
|
|
19
21
|
disabled?: boolean;
|
|
22
|
+
interactionPolicy?: InteractionPolicy;
|
|
20
23
|
testID?: string;
|
|
21
24
|
}
|
|
22
25
|
|
|
@@ -22,6 +22,7 @@ export function TextInput({
|
|
|
22
22
|
invalid = false,
|
|
23
23
|
leadingAccessory,
|
|
24
24
|
trailingAccessory,
|
|
25
|
+
interactionPolicy = 'enabled',
|
|
25
26
|
style,
|
|
26
27
|
testID,
|
|
27
28
|
onFocus,
|
|
@@ -52,6 +53,7 @@ export function TextInput({
|
|
|
52
53
|
const containerMinHeight = props.multiline
|
|
53
54
|
? inputMinHeight + controlSize.paddingVertical * 2
|
|
54
55
|
: controlSize.minHeight;
|
|
56
|
+
const passive = interactionPolicy === 'passive';
|
|
55
57
|
|
|
56
58
|
const handleFocus: NonNullable<TextInputProps['onFocus']> = (event) => {
|
|
57
59
|
setFocused(true);
|
|
@@ -87,10 +89,16 @@ export function TextInput({
|
|
|
87
89
|
<ReactNativeTextInput
|
|
88
90
|
{...props}
|
|
89
91
|
defaultValue={defaultValue}
|
|
90
|
-
editable={!disabled && !readOnly}
|
|
92
|
+
editable={passive ? false : !disabled && !readOnly}
|
|
91
93
|
numberOfLines={props.multiline ? props.numberOfLines : 1}
|
|
92
94
|
onBlur={handleBlur}
|
|
93
|
-
onChangeText={
|
|
95
|
+
onChangeText={(nextValue) => {
|
|
96
|
+
if (passive) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onChangeText?.(nextValue);
|
|
101
|
+
}}
|
|
94
102
|
onFocus={handleFocus}
|
|
95
103
|
placeholder={placeholder}
|
|
96
104
|
placeholderTextColor={colors.placeholderColor}
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
TextStyle,
|
|
6
6
|
} from 'react-native';
|
|
7
7
|
|
|
8
|
+
import type { InteractionPolicy } from '../../interactionPolicy';
|
|
8
9
|
import type { ControlSize } from '../../internal/resolvers/resolveControlSize';
|
|
9
10
|
|
|
10
11
|
export interface TextInputProps extends Omit<
|
|
@@ -27,6 +28,7 @@ export interface TextInputProps extends Omit<
|
|
|
27
28
|
invalid?: boolean;
|
|
28
29
|
leadingAccessory?: React.ReactNode;
|
|
29
30
|
trailingAccessory?: React.ReactNode;
|
|
31
|
+
interactionPolicy?: InteractionPolicy;
|
|
30
32
|
style?: StyleProp<TextStyle>;
|
|
31
33
|
testID?: string;
|
|
32
34
|
}
|
|
@@ -38,10 +38,18 @@ function resolveToastStatusTextColor(status: ToastProps['status']): SurfaceColor
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export function Toast({
|
|
41
|
+
export function Toast({
|
|
42
|
+
title,
|
|
43
|
+
description,
|
|
44
|
+
status = 'default',
|
|
45
|
+
onDismiss,
|
|
46
|
+
interactionPolicy = 'enabled',
|
|
47
|
+
testID,
|
|
48
|
+
}: ToastProps) {
|
|
42
49
|
const { theme } = useTheme();
|
|
43
50
|
const statusColor = resolveToastStatusColor(theme, status);
|
|
44
51
|
const statusTextColor = resolveToastStatusTextColor(status);
|
|
52
|
+
const passive = interactionPolicy === 'passive';
|
|
45
53
|
|
|
46
54
|
return (
|
|
47
55
|
<Surface
|
|
@@ -70,7 +78,7 @@ export function Toast({ title, description, status = 'default', onDismiss, testI
|
|
|
70
78
|
<Pressable
|
|
71
79
|
accessibilityLabel="Dismiss notification"
|
|
72
80
|
accessibilityRole="button"
|
|
73
|
-
onPress={onDismiss}
|
|
81
|
+
onPress={passive ? undefined : onDismiss}
|
|
74
82
|
testID={testID ? `${testID}-dismiss` : undefined}
|
|
75
83
|
>
|
|
76
84
|
<Text color={statusTextColor}>×</Text>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
|
|
3
|
+
import type { InteractionPolicy } from '../../interactionPolicy';
|
|
3
4
|
import type { SurfaceStatusColor } from '../../surfaceColor';
|
|
4
5
|
|
|
5
6
|
export type ToastStatus = 'default' | SurfaceStatusColor;
|
|
@@ -9,6 +10,7 @@ export interface ToastProps {
|
|
|
9
10
|
description?: React.ReactNode;
|
|
10
11
|
status?: ToastStatus;
|
|
11
12
|
onDismiss?: (() => void) | undefined;
|
|
13
|
+
interactionPolicy?: InteractionPolicy;
|
|
12
14
|
testID?: string;
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -25,6 +25,7 @@ export function Tooltip({
|
|
|
25
25
|
children,
|
|
26
26
|
content,
|
|
27
27
|
delay = 150,
|
|
28
|
+
interactionPolicy = 'enabled',
|
|
28
29
|
placement = 'top',
|
|
29
30
|
testID,
|
|
30
31
|
}: TooltipProps) {
|
|
@@ -34,6 +35,7 @@ export function Tooltip({
|
|
|
34
35
|
const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
35
36
|
const [visible, setVisible] = React.useState(false);
|
|
36
37
|
const [layout, setLayout] = React.useState<LayoutRectangle | null>(null);
|
|
38
|
+
const passive = interactionPolicy === 'passive';
|
|
37
39
|
|
|
38
40
|
const show = React.useCallback(() => {
|
|
39
41
|
if (timeoutRef.current) {
|
|
@@ -74,10 +76,10 @@ export function Tooltip({
|
|
|
74
76
|
return (
|
|
75
77
|
<View collapsable={false} ref={anchorRef} testID={testID ? `${testID}-anchor` : undefined}>
|
|
76
78
|
<Pressable
|
|
77
|
-
onBlur={hide}
|
|
78
|
-
onFocus={show}
|
|
79
|
-
onHoverIn={Platform.OS === 'web' ? show : undefined}
|
|
80
|
-
onHoverOut={Platform.OS === 'web' ? hide : undefined}
|
|
79
|
+
onBlur={passive ? undefined : hide}
|
|
80
|
+
onFocus={passive ? undefined : show}
|
|
81
|
+
onHoverIn={Platform.OS === 'web' && !passive ? show : undefined}
|
|
82
|
+
onHoverOut={Platform.OS === 'web' && !passive ? hide : undefined}
|
|
81
83
|
>
|
|
82
84
|
{children}
|
|
83
85
|
</Pressable>
|
|
@@ -93,7 +95,7 @@ export function Tooltip({
|
|
|
93
95
|
<Surface
|
|
94
96
|
p="s"
|
|
95
97
|
style={{
|
|
96
|
-
backgroundColor: theme.semantics.
|
|
98
|
+
backgroundColor: theme.semantics.surface.inverse,
|
|
97
99
|
}}
|
|
98
100
|
testID={testID}
|
|
99
101
|
variant="raised"
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
|
|
3
|
+
import type { InteractionPolicy } from '../../interactionPolicy';
|
|
4
|
+
|
|
3
5
|
export interface TooltipProps {
|
|
4
6
|
children?: React.ReactNode;
|
|
5
7
|
content?: React.ReactNode;
|
|
6
8
|
delay?: number;
|
|
9
|
+
interactionPolicy?: InteractionPolicy;
|
|
7
10
|
placement?: 'top' | 'bottom';
|
|
8
11
|
testID?: string;
|
|
9
12
|
}
|