@ankhorage/surface 2.0.0 → 2.0.2
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/dist/components/action-sheet/ActionSheet.d.ts +4 -0
- package/dist/components/action-sheet/ActionSheet.d.ts.map +1 -0
- package/dist/components/action-sheet/ActionSheet.js +71 -0
- package/dist/components/action-sheet/ActionSheet.js.map +1 -0
- package/dist/components/action-sheet/ActionSheetItem.d.ts +4 -0
- package/dist/components/action-sheet/ActionSheetItem.d.ts.map +1 -0
- package/dist/components/action-sheet/ActionSheetItem.js +27 -0
- package/dist/components/action-sheet/ActionSheetItem.js.map +1 -0
- package/dist/components/action-sheet/index.d.ts +4 -0
- package/dist/components/action-sheet/index.d.ts.map +1 -0
- package/dist/components/action-sheet/index.js +3 -0
- package/dist/components/action-sheet/index.js.map +1 -0
- package/dist/components/action-sheet/types.d.ts +24 -0
- package/dist/components/action-sheet/types.d.ts.map +1 -0
- package/dist/components/action-sheet/types.js +2 -0
- package/dist/components/action-sheet/types.js.map +1 -0
- 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 +50 -32
- package/dist/components/menu/Menu.js.map +1 -1
- package/dist/components/menu/index.d.ts +1 -1
- package/dist/components/menu/index.d.ts.map +1 -1
- package/dist/components/menu/index.js.map +1 -1
- package/dist/components/menu/navigation.d.ts +1 -1
- package/dist/components/menu/navigation.d.ts.map +1 -1
- package/dist/components/menu/navigation.js.map +1 -1
- package/dist/components/menu/types.d.ts +11 -5
- package/dist/components/menu/types.d.ts.map +1 -1
- package/dist/components/menu/types.js.map +1 -1
- package/dist/examples/DocsExamples.d.ts +3 -2
- package/dist/examples/DocsExamples.d.ts.map +1 -1
- package/dist/examples/DocsExamples.js +61 -37
- package/dist/examples/DocsExamples.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/action-sheet/ActionSheet.tsx +121 -0
- package/src/components/action-sheet/ActionSheetItem.tsx +57 -0
- package/src/components/action-sheet/index.ts +3 -0
- package/src/components/action-sheet/types.ts +26 -0
- package/src/components/menu/Menu.tsx +71 -36
- package/src/components/menu/index.ts +1 -1
- package/src/components/menu/navigation.ts +1 -1
- package/src/components/menu/types.ts +12 -5
- package/src/examples/DocsExamples.tsx +74 -60
- package/src/index.ts +3 -1
|
@@ -5,12 +5,12 @@ import { FocusScope } from '../../internal/focus/FocusScope';
|
|
|
5
5
|
import { useFocusManager } from '../../internal/focus/useFocusManager';
|
|
6
6
|
import { Portal } from '../../internal/overlay/Portal';
|
|
7
7
|
import { resolveOverlayAnimation } from '../../internal/resolvers';
|
|
8
|
-
import { Box, Surface } from '../../layout';
|
|
8
|
+
import { Box, Inline, Stack, Surface } from '../../layout';
|
|
9
9
|
import { ButtonBase } from '../../primitives/button-base';
|
|
10
10
|
import { Text } from '../../primitives/text';
|
|
11
11
|
import { useTheme } from '../../theme/ThemeContext';
|
|
12
12
|
import { resolveNextMenuIndex } from './navigation';
|
|
13
|
-
import type { MenuProps } from './types';
|
|
13
|
+
import type { MenuAction, MenuProps } from './types';
|
|
14
14
|
|
|
15
15
|
interface MeasurableNode {
|
|
16
16
|
measureInWindow?: (
|
|
@@ -25,7 +25,35 @@ function measureNode(node: unknown, callback: (layout: LayoutRectangle) => void)
|
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
function renderActionContent(action: MenuAction, active: boolean) {
|
|
29
|
+
const titleColor =
|
|
30
|
+
action.intent === 'danger' ? 'danger' : active || action.selected ? 'neutral' : undefined;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Inline align="center" gap="s" justify="space-between" wrap="nowrap">
|
|
34
|
+
{action.leading ? <Box>{action.leading}</Box> : null}
|
|
35
|
+
<Box flex={1}>
|
|
36
|
+
<Stack gap="xxs">
|
|
37
|
+
<Text
|
|
38
|
+
color={titleColor}
|
|
39
|
+
variant="bodySmall"
|
|
40
|
+
weight={action.selected ? 'semiBold' : 'medium'}
|
|
41
|
+
>
|
|
42
|
+
{action.title}
|
|
43
|
+
</Text>
|
|
44
|
+
{action.description ? (
|
|
45
|
+
<Text emphasis="muted" variant="caption">
|
|
46
|
+
{action.description}
|
|
47
|
+
</Text>
|
|
48
|
+
) : null}
|
|
49
|
+
</Stack>
|
|
50
|
+
</Box>
|
|
51
|
+
{action.trailing ? <Box>{action.trailing}</Box> : null}
|
|
52
|
+
</Inline>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function Menu({ trigger, actions, dismiss, closeOnSelect = true, testID }: MenuProps) {
|
|
29
57
|
const { theme } = useTheme();
|
|
30
58
|
const { bindKeydown } = useFocusManager();
|
|
31
59
|
const animation = resolveOverlayAnimation('menu');
|
|
@@ -36,15 +64,34 @@ export function Menu({ trigger, items, onDismiss, closeOnSelect = true, testID }
|
|
|
36
64
|
|
|
37
65
|
const closeMenu = React.useCallback(() => {
|
|
38
66
|
setOpen(false);
|
|
39
|
-
|
|
40
|
-
|
|
67
|
+
if (dismiss) {
|
|
68
|
+
dismiss();
|
|
69
|
+
}
|
|
70
|
+
}, [dismiss]);
|
|
71
|
+
|
|
72
|
+
const activateAction = React.useCallback(
|
|
73
|
+
(action: MenuAction) => {
|
|
74
|
+
if (action.disabled) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (action.activate) {
|
|
79
|
+
action.activate();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (closeOnSelect) {
|
|
83
|
+
closeMenu();
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
[closeMenu, closeOnSelect],
|
|
87
|
+
);
|
|
41
88
|
|
|
42
89
|
const openMenu = React.useCallback(() => {
|
|
43
90
|
measureNode(anchorRef.current, setLayout);
|
|
44
|
-
const firstEnabledIndex =
|
|
91
|
+
const firstEnabledIndex = actions.findIndex((action) => !action.disabled);
|
|
45
92
|
setActiveIndex(firstEnabledIndex === -1 ? 0 : firstEnabledIndex);
|
|
46
93
|
setOpen(true);
|
|
47
|
-
}, [
|
|
94
|
+
}, [actions]);
|
|
48
95
|
|
|
49
96
|
React.useEffect(() => {
|
|
50
97
|
if (!open) {
|
|
@@ -55,17 +102,14 @@ export function Menu({ trigger, items, onDismiss, closeOnSelect = true, testID }
|
|
|
55
102
|
const { key } = event;
|
|
56
103
|
if (key === 'ArrowDown' || key === 'ArrowUp' || key === 'Home' || key === 'End') {
|
|
57
104
|
event.preventDefault();
|
|
58
|
-
setActiveIndex((current) => resolveNextMenuIndex(
|
|
105
|
+
setActiveIndex((current) => resolveNextMenuIndex(actions, current, key));
|
|
59
106
|
}
|
|
60
107
|
|
|
61
108
|
if (event.key === 'Enter') {
|
|
62
109
|
event.preventDefault();
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
if (closeOnSelect) {
|
|
67
|
-
closeMenu();
|
|
68
|
-
}
|
|
110
|
+
const activeAction = actions[activeIndex];
|
|
111
|
+
if (activeAction) {
|
|
112
|
+
activateAction(activeAction);
|
|
69
113
|
}
|
|
70
114
|
}
|
|
71
115
|
|
|
@@ -74,7 +118,7 @@ export function Menu({ trigger, items, onDismiss, closeOnSelect = true, testID }
|
|
|
74
118
|
closeMenu();
|
|
75
119
|
}
|
|
76
120
|
});
|
|
77
|
-
}, [
|
|
121
|
+
}, [activateAction, actions, activeIndex, bindKeydown, closeMenu, open]);
|
|
78
122
|
|
|
79
123
|
return (
|
|
80
124
|
<View collapsable={false} ref={anchorRef}>
|
|
@@ -117,7 +161,7 @@ export function Menu({ trigger, items, onDismiss, closeOnSelect = true, testID }
|
|
|
117
161
|
accessibilityRole="menu"
|
|
118
162
|
p="xs"
|
|
119
163
|
style={{
|
|
120
|
-
minWidth: Math.max(layout?.width ?? 0,
|
|
164
|
+
minWidth: Math.max(layout?.width ?? 0, 220),
|
|
121
165
|
shadowOpacity: 0.12,
|
|
122
166
|
shadowRadius: 12,
|
|
123
167
|
shadowOffset: { width: 0, height: 6 },
|
|
@@ -125,40 +169,31 @@ export function Menu({ trigger, items, onDismiss, closeOnSelect = true, testID }
|
|
|
125
169
|
testID={testID}
|
|
126
170
|
variant="raised"
|
|
127
171
|
>
|
|
128
|
-
{
|
|
129
|
-
const
|
|
172
|
+
{actions.map((action, index) => {
|
|
173
|
+
const active = index === activeIndex;
|
|
174
|
+
const selected = action.selected ?? active;
|
|
130
175
|
|
|
131
176
|
return (
|
|
132
177
|
<Pressable
|
|
133
178
|
accessibilityRole="menuitem"
|
|
134
|
-
accessibilityState={{ disabled:
|
|
135
|
-
disabled={
|
|
136
|
-
key={
|
|
137
|
-
onPress={() =>
|
|
138
|
-
if (item.disabled) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
item.onPress?.();
|
|
143
|
-
if (closeOnSelect) {
|
|
144
|
-
closeMenu();
|
|
145
|
-
}
|
|
146
|
-
}}
|
|
179
|
+
accessibilityState={{ disabled: action.disabled, selected }}
|
|
180
|
+
disabled={action.disabled}
|
|
181
|
+
key={action.id}
|
|
182
|
+
onPress={() => activateAction(action)}
|
|
147
183
|
>
|
|
148
184
|
<Box
|
|
149
185
|
px="m"
|
|
150
186
|
py="s"
|
|
187
|
+
radius="s"
|
|
151
188
|
style={{
|
|
152
189
|
backgroundColor: selected
|
|
153
190
|
? theme.semantics.action.neutral.softBg
|
|
154
191
|
: 'transparent',
|
|
155
|
-
opacity:
|
|
192
|
+
opacity: action.disabled ? 0.56 : 1,
|
|
156
193
|
}}
|
|
157
|
-
testID={testID ? `${testID}-item-${
|
|
194
|
+
testID={testID ? `${testID}-item-${action.id}` : undefined}
|
|
158
195
|
>
|
|
159
|
-
|
|
160
|
-
{item.label}
|
|
161
|
-
</Text>
|
|
196
|
+
{renderActionContent(action, active)}
|
|
162
197
|
</Box>
|
|
163
198
|
</Pressable>
|
|
164
199
|
);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Menu } from './Menu';
|
|
2
|
-
export type { MenuItem, MenuProps } from './types';
|
|
2
|
+
export type { MenuAction, MenuActionIntent, MenuAction as MenuItem, MenuProps } from './types';
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export type MenuActionIntent = 'default' | 'danger';
|
|
4
|
+
|
|
5
|
+
export interface MenuAction {
|
|
4
6
|
id: string;
|
|
5
|
-
|
|
7
|
+
title: React.ReactNode;
|
|
8
|
+
description?: React.ReactNode;
|
|
9
|
+
leading?: React.ReactNode;
|
|
10
|
+
trailing?: React.ReactNode;
|
|
11
|
+
intent?: MenuActionIntent;
|
|
6
12
|
disabled?: boolean;
|
|
7
|
-
|
|
13
|
+
selected?: boolean;
|
|
14
|
+
activate?: () => void;
|
|
8
15
|
}
|
|
9
16
|
|
|
10
17
|
export interface MenuProps {
|
|
11
18
|
trigger?: React.ReactNode;
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
actions: readonly MenuAction[];
|
|
20
|
+
dismiss?: () => void;
|
|
14
21
|
closeOnSelect?: boolean;
|
|
15
22
|
testID?: string;
|
|
16
23
|
}
|
|
@@ -1,77 +1,91 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
TabPanel,
|
|
20
|
-
Tabs,
|
|
21
|
-
Text,
|
|
22
|
-
Textarea,
|
|
23
|
-
TextInput,
|
|
24
|
-
ThemeProvider,
|
|
25
|
-
} from '../index';
|
|
3
|
+
import { Button } from '../components/button';
|
|
4
|
+
import { Card } from '../components/card';
|
|
5
|
+
import { Checkbox } from '../components/checkbox';
|
|
6
|
+
import { Drawer } from '../components/drawer';
|
|
7
|
+
import { Field } from '../components/field';
|
|
8
|
+
import { HelperText } from '../components/helper-text';
|
|
9
|
+
import { Menu } from '../components/menu';
|
|
10
|
+
import { Modal } from '../components/modal';
|
|
11
|
+
import { Tab, TabList, TabPanel, Tabs } from '../components/tabs';
|
|
12
|
+
import { TextInput } from '../components/text-input';
|
|
13
|
+
import { ToastProvider, useToast } from '../components/toast';
|
|
14
|
+
import { Tooltip } from '../components/tooltip';
|
|
15
|
+
import { Box, Stack } from '../layout';
|
|
16
|
+
import { Text } from '../primitives/text';
|
|
17
|
+
import { ThemeProvider } from '../theme';
|
|
18
|
+
import type { ThemeConfig } from '../theme/types';
|
|
26
19
|
|
|
27
|
-
const docsThemeConfig = {
|
|
28
|
-
id: 'docs
|
|
29
|
-
name: 'Docs
|
|
30
|
-
light: {
|
|
31
|
-
|
|
32
|
-
primaryColor: '#2563eb',
|
|
33
|
-
},
|
|
34
|
-
dark: {
|
|
35
|
-
harmony: 'monochromatic' as const,
|
|
36
|
-
primaryColor: '#2563eb',
|
|
37
|
-
},
|
|
20
|
+
const docsThemeConfig: ThemeConfig = {
|
|
21
|
+
id: 'docs',
|
|
22
|
+
name: 'Docs',
|
|
23
|
+
light: { primaryColor: '#2563EB', harmony: 'analogous' },
|
|
24
|
+
dark: { primaryColor: '#60A5FA', harmony: 'analogous' },
|
|
38
25
|
};
|
|
39
26
|
|
|
40
|
-
export function
|
|
27
|
+
export function FormExample() {
|
|
41
28
|
return (
|
|
42
29
|
<ThemeProvider initialConfig={docsThemeConfig}>
|
|
43
30
|
<Stack gap="m" p="l">
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
<Field helperText="Use your work email." label="Email" required>
|
|
32
|
+
<TextInput placeholder="you@example.com" />
|
|
33
|
+
</Field>
|
|
34
|
+
<Checkbox checked>Accept terms</Checkbox>
|
|
35
|
+
<HelperText emphasis="muted">Looks good.</HelperText>
|
|
49
36
|
</Stack>
|
|
50
37
|
</ThemeProvider>
|
|
51
38
|
);
|
|
52
39
|
}
|
|
53
40
|
|
|
54
|
-
|
|
41
|
+
function ToastButton() {
|
|
42
|
+
const { showToast } = useToast();
|
|
43
|
+
return (
|
|
44
|
+
<Button
|
|
45
|
+
onPress={() =>
|
|
46
|
+
showToast({ description: 'Your changes were saved.', status: 'success', title: 'Saved' })
|
|
47
|
+
}
|
|
48
|
+
>
|
|
49
|
+
Show toast
|
|
50
|
+
</Button>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function FeedbackExample() {
|
|
55
55
|
return (
|
|
56
56
|
<ThemeProvider initialConfig={docsThemeConfig}>
|
|
57
|
-
<
|
|
58
|
-
<
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<
|
|
63
|
-
</Field>
|
|
64
|
-
<Stack gap="s">
|
|
65
|
-
<Label required>Preferences</Label>
|
|
66
|
-
<Checkbox defaultChecked>Weekly updates</Checkbox>
|
|
67
|
-
<Radio>Product announcements</Radio>
|
|
68
|
-
<Switch readOnly>Read-only setting</Switch>
|
|
69
|
-
<HelperText color="error">Invalid fields should reuse the same error color.</HelperText>
|
|
57
|
+
<ToastProvider>
|
|
58
|
+
<Stack gap="m" p="l">
|
|
59
|
+
<Tooltip content="Helpful contextual information">
|
|
60
|
+
<Text>Hover for tooltip</Text>
|
|
61
|
+
</Tooltip>
|
|
62
|
+
<ToastButton />
|
|
70
63
|
</Stack>
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
</ToastProvider>
|
|
65
|
+
</ThemeProvider>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function OverlayExample() {
|
|
70
|
+
const [modalVisible, setModalVisible] = React.useState(false);
|
|
71
|
+
const [drawerVisible, setDrawerVisible] = React.useState(false);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<ThemeProvider initialConfig={docsThemeConfig}>
|
|
75
|
+
<Stack gap="m" p="l">
|
|
76
|
+
<Button onPress={() => setModalVisible(true)}>Open modal</Button>
|
|
77
|
+
<Button onPress={() => setDrawerVisible(true)}>Open drawer</Button>
|
|
78
|
+
<Modal onDismiss={() => setModalVisible(false)} visible={modalVisible}>
|
|
79
|
+
<Card>
|
|
80
|
+
<Stack gap="s">
|
|
81
|
+
<Text variant="label" weight="semiBold">
|
|
82
|
+
Modal title
|
|
83
|
+
</Text>
|
|
84
|
+
<Text>Modal content</Text>
|
|
85
|
+
</Stack>
|
|
86
|
+
</Card>
|
|
73
87
|
</Modal>
|
|
74
|
-
<Drawer
|
|
88
|
+
<Drawer onDismiss={() => setDrawerVisible(false)} visible={drawerVisible}>
|
|
75
89
|
<Text>Drawer content</Text>
|
|
76
90
|
</Drawer>
|
|
77
91
|
</Stack>
|
|
@@ -99,9 +113,9 @@ export function NavigationExample() {
|
|
|
99
113
|
</TabPanel>
|
|
100
114
|
</Tabs>
|
|
101
115
|
<Menu
|
|
102
|
-
|
|
103
|
-
{ id: 'edit',
|
|
104
|
-
{ disabled: true, id: 'archive',
|
|
116
|
+
actions={[
|
|
117
|
+
{ id: 'edit', title: 'Edit' },
|
|
118
|
+
{ disabled: true, id: 'archive', title: 'Archive' },
|
|
105
119
|
]}
|
|
106
120
|
trigger={<Text>Open menu</Text>}
|
|
107
121
|
/>
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type { ActionSheetItemProps, ActionSheetProps } from './components/action-sheet';
|
|
2
|
+
export { ActionSheet, ActionSheetItem } from './components/action-sheet';
|
|
1
3
|
export type { BadgeProps } from './components/badge';
|
|
2
4
|
export { Badge } from './components/badge';
|
|
3
5
|
export type { ButtonIconSpec, ButtonProps } from './components/button';
|
|
@@ -18,7 +20,7 @@ export type { LabelProps } from './components/label';
|
|
|
18
20
|
export { Label } from './components/label';
|
|
19
21
|
export type { ListItemProps } from './components/list-item';
|
|
20
22
|
export { ListItem } from './components/list-item';
|
|
21
|
-
export type { MenuItem, MenuProps } from './components/menu';
|
|
23
|
+
export type { MenuAction, MenuActionIntent, MenuItem, MenuProps } from './components/menu';
|
|
22
24
|
export { Menu } from './components/menu';
|
|
23
25
|
export type { ModalProps } from './components/modal';
|
|
24
26
|
export { Modal } from './components/modal';
|