@ankhorage/zora 0.3.6 → 0.3.7
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 +6 -0
- package/README.md +0 -90
- package/package.json +5 -1
- package/src/components/badge/Badge.tsx +19 -0
- package/src/components/badge/index.ts +2 -0
- package/src/components/badge/types.ts +14 -0
- package/src/components/button/Button.tsx +13 -0
- package/src/components/button/index.ts +2 -0
- package/src/components/button/types.ts +16 -0
- package/src/components/card/Card.tsx +65 -0
- package/src/components/card/index.ts +2 -0
- package/src/components/card/types.ts +15 -0
- package/src/components/drawer/Drawer.tsx +27 -0
- package/src/components/drawer/index.ts +2 -0
- package/src/components/drawer/types.ts +12 -0
- package/src/components/icon/Icon.tsx +8 -0
- package/src/components/icon/index.ts +2 -0
- package/src/components/icon-button/IconButton.tsx +27 -0
- package/src/components/icon-button/index.ts +2 -0
- package/src/components/icon-button/types.ts +15 -0
- package/src/components/input/Input.tsx +38 -0
- package/src/components/input/index.ts +2 -0
- package/src/components/input/types.ts +12 -0
- package/src/components/modal/Modal.tsx +37 -0
- package/src/components/modal/index.ts +2 -0
- package/src/components/modal/types.ts +15 -0
- package/src/components/select/Select.tsx +49 -0
- package/src/components/select/index.ts +2 -0
- package/src/components/select/types.ts +14 -0
- package/src/components/tabs/Tabs.tsx +103 -0
- package/src/components/tabs/index.ts +2 -0
- package/src/components/tabs/types.ts +25 -0
- package/src/components/textarea/Textarea.tsx +38 -0
- package/src/components/textarea/index.ts +2 -0
- package/src/components/textarea/types.ts +12 -0
- package/src/components/toolbar/Toolbar.tsx +38 -0
- package/src/components/toolbar/ToolbarAction.tsx +15 -0
- package/src/components/toolbar/index.ts +3 -0
- package/src/components/toolbar/types.ts +21 -0
- package/src/index.ts +72 -0
- package/src/internal/deepMerge.ts +23 -0
- package/src/internal/recipes.test.ts +46 -0
- package/src/internal/recipes.ts +92 -0
- package/src/layout/app-shell/AppShell.tsx +15 -0
- package/src/layout/app-shell/index.ts +2 -0
- package/src/layout/app-shell/types.ts +7 -0
- package/src/layout/auth-layout/AuthLayout.tsx +29 -0
- package/src/layout/auth-layout/index.ts +2 -0
- package/src/layout/auth-layout/types.ts +10 -0
- package/src/layout/page/Page.tsx +17 -0
- package/src/layout/page/index.ts +2 -0
- package/src/layout/page/types.ts +11 -0
- package/src/layout/page-header/PageHeader.tsx +41 -0
- package/src/layout/page-header/index.ts +2 -0
- package/src/layout/page-header/types.ts +10 -0
- package/src/layout/page-section/PageSection.tsx +14 -0
- package/src/layout/page-section/index.ts +2 -0
- package/src/layout/page-section/types.ts +9 -0
- package/src/layout/settings-layout/SettingsLayout.tsx +26 -0
- package/src/layout/settings-layout/index.ts +2 -0
- package/src/layout/settings-layout/types.ts +10 -0
- package/src/layout/sidebar-layout/SidebarLayout.tsx +23 -0
- package/src/layout/sidebar-layout/index.ts +2 -0
- package/src/layout/sidebar-layout/types.ts +10 -0
- package/src/layout/topbar-layout/TopbarLayout.tsx +14 -0
- package/src/layout/topbar-layout/index.ts +2 -0
- package/src/layout/topbar-layout/types.ts +8 -0
- package/src/patterns/collection-editor/CollectionEditor.tsx +100 -0
- package/src/patterns/collection-editor/index.ts +2 -0
- package/src/patterns/collection-editor/types.ts +25 -0
- package/src/patterns/confirm-dialog/ConfirmDialog.tsx +46 -0
- package/src/patterns/confirm-dialog/index.ts +2 -0
- package/src/patterns/confirm-dialog/types.ts +19 -0
- package/src/patterns/disclosure-section/DisclosureSection.tsx +61 -0
- package/src/patterns/disclosure-section/index.ts +2 -0
- package/src/patterns/disclosure-section/types.ts +15 -0
- package/src/patterns/empty-state/EmptyState.tsx +53 -0
- package/src/patterns/empty-state/index.ts +2 -0
- package/src/patterns/empty-state/types.ts +20 -0
- package/src/patterns/form-field/FormField.tsx +27 -0
- package/src/patterns/form-field/index.ts +2 -0
- package/src/patterns/form-field/types.ts +11 -0
- package/src/patterns/inspector-field/InspectorField.tsx +16 -0
- package/src/patterns/inspector-field/index.ts +2 -0
- package/src/patterns/inspector-field/types.ts +15 -0
- package/src/patterns/notice/Notice.tsx +30 -0
- package/src/patterns/notice/index.ts +2 -0
- package/src/patterns/notice/types.ts +12 -0
- package/src/patterns/panel/Panel.tsx +8 -0
- package/src/patterns/panel/index.ts +2 -0
- package/src/patterns/panel/types.ts +15 -0
- package/src/patterns/responsive-panel/ResponsivePanel.tsx +70 -0
- package/src/patterns/responsive-panel/index.ts +2 -0
- package/src/patterns/responsive-panel/types.ts +20 -0
- package/src/patterns/section-header/SectionHeader.tsx +39 -0
- package/src/patterns/section-header/index.ts +2 -0
- package/src/patterns/section-header/types.ts +9 -0
- package/src/patterns/settings-row/SettingsRow.tsx +40 -0
- package/src/patterns/settings-row/index.ts +2 -0
- package/src/patterns/settings-row/types.ts +27 -0
- package/src/patterns/switch-field/SwitchField.tsx +24 -0
- package/src/patterns/switch-field/index.ts +2 -0
- package/src/patterns/switch-field/types.ts +10 -0
- package/src/patterns/tile-grid/PaletteItem.tsx +49 -0
- package/src/patterns/tile-grid/TileGrid.tsx +44 -0
- package/src/patterns/tile-grid/index.ts +3 -0
- package/src/patterns/tile-grid/types.ts +20 -0
- package/src/patterns/tree-view/TreeItem.tsx +86 -0
- package/src/patterns/tree-view/TreeView.tsx +50 -0
- package/src/patterns/tree-view/index.ts +3 -0
- package/src/patterns/tree-view/types.ts +31 -0
- package/src/theme/ZoraProvider.tsx +22 -0
- package/src/theme/createZoraTheme.test.ts +25 -0
- package/src/theme/createZoraTheme.ts +10 -0
- package/src/theme/index.ts +6 -0
- package/src/theme/useZoraTheme.ts +5 -0
- package/src/theme/zoraTheme.ts +16 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Stack } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { SectionHeader } from '../../patterns/section-header';
|
|
5
|
+
import type { PageSectionProps } from './types';
|
|
6
|
+
|
|
7
|
+
export function PageSection({ title, description, actions, children, testID }: PageSectionProps) {
|
|
8
|
+
return (
|
|
9
|
+
<Stack gap="m" testID={testID}>
|
|
10
|
+
{title ? <SectionHeader actions={actions} description={description} title={title} /> : null}
|
|
11
|
+
{children}
|
|
12
|
+
</Stack>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Page } from '../page';
|
|
4
|
+
import { PageHeader } from '../page-header';
|
|
5
|
+
import { SidebarLayout } from '../sidebar-layout';
|
|
6
|
+
import type { SettingsLayoutProps } from './types';
|
|
7
|
+
|
|
8
|
+
export function SettingsLayout({
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
sidebar,
|
|
12
|
+
children,
|
|
13
|
+
actions,
|
|
14
|
+
testID,
|
|
15
|
+
}: SettingsLayoutProps) {
|
|
16
|
+
return (
|
|
17
|
+
<Page
|
|
18
|
+
header={
|
|
19
|
+
title ? <PageHeader actions={actions} description={description} title={title} /> : null
|
|
20
|
+
}
|
|
21
|
+
testID={testID}
|
|
22
|
+
>
|
|
23
|
+
<SidebarLayout sidebar={sidebar}>{children}</SidebarLayout>
|
|
24
|
+
</Page>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Box, Stack } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import type { SidebarLayoutProps } from './types';
|
|
5
|
+
|
|
6
|
+
export function SidebarLayout({
|
|
7
|
+
sidebar,
|
|
8
|
+
children,
|
|
9
|
+
aside,
|
|
10
|
+
sidebarWidth = 280,
|
|
11
|
+
asideWidth = 280,
|
|
12
|
+
testID,
|
|
13
|
+
}: SidebarLayoutProps) {
|
|
14
|
+
return (
|
|
15
|
+
<Stack direction={{ base: 'column', lg: 'row' }} gap="l" testID={testID} align="flex-start">
|
|
16
|
+
<Box width={{ base: '100%', lg: sidebarWidth }}>{sidebar}</Box>
|
|
17
|
+
<Box flex={1} width="100%">
|
|
18
|
+
{children}
|
|
19
|
+
</Box>
|
|
20
|
+
{aside ? <Box width={{ base: '100%', lg: asideWidth }}>{aside}</Box> : null}
|
|
21
|
+
</Stack>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Box, Stack } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { SidebarLayout } from '../sidebar-layout';
|
|
5
|
+
import type { TopbarLayoutProps } from './types';
|
|
6
|
+
|
|
7
|
+
export function TopbarLayout({ topbar, children, sidebar, testID }: TopbarLayoutProps) {
|
|
8
|
+
return (
|
|
9
|
+
<Stack gap="l" testID={testID}>
|
|
10
|
+
<Box>{topbar}</Box>
|
|
11
|
+
{sidebar ? <SidebarLayout sidebar={sidebar}>{children}</SidebarLayout> : children}
|
|
12
|
+
</Stack>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Box, Stack, Text } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '../../components/button';
|
|
5
|
+
import { IconButton } from '../../components/icon-button';
|
|
6
|
+
import { Panel } from '../panel';
|
|
7
|
+
import type { CollectionEditorProps } from './types';
|
|
8
|
+
|
|
9
|
+
export function CollectionEditor<TItem>({
|
|
10
|
+
title,
|
|
11
|
+
description,
|
|
12
|
+
items,
|
|
13
|
+
renderItem,
|
|
14
|
+
onAdd,
|
|
15
|
+
onRemove,
|
|
16
|
+
onMove,
|
|
17
|
+
addLabel = 'Add Item',
|
|
18
|
+
emptyLabel = 'No items yet.',
|
|
19
|
+
disabled,
|
|
20
|
+
testID,
|
|
21
|
+
}: CollectionEditorProps<TItem>) {
|
|
22
|
+
const isEmpty = items.length === 0;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Panel
|
|
26
|
+
compact
|
|
27
|
+
description={description}
|
|
28
|
+
testID={testID}
|
|
29
|
+
title={title}
|
|
30
|
+
actions={
|
|
31
|
+
onAdd ? (
|
|
32
|
+
<Button emphasis="soft" size="s" disabled={disabled} onPress={onAdd}>
|
|
33
|
+
{addLabel}
|
|
34
|
+
</Button>
|
|
35
|
+
) : null
|
|
36
|
+
}
|
|
37
|
+
>
|
|
38
|
+
<Stack gap="s">
|
|
39
|
+
{isEmpty ? (
|
|
40
|
+
<Box py="m">
|
|
41
|
+
<Text align="center" tone="muted">
|
|
42
|
+
{emptyLabel}
|
|
43
|
+
</Text>
|
|
44
|
+
</Box>
|
|
45
|
+
) : (
|
|
46
|
+
items.map((item, index) => (
|
|
47
|
+
<Box key={index} bg="subtle" p="s" radius="m" borderColor="border" borderWidth={1}>
|
|
48
|
+
<Stack direction="row" gap="m" align="center">
|
|
49
|
+
<Box flex={1}>
|
|
50
|
+
{renderItem({
|
|
51
|
+
item,
|
|
52
|
+
index,
|
|
53
|
+
remove: () => onRemove?.(index),
|
|
54
|
+
moveUp: () => onMove?.(index, index - 1),
|
|
55
|
+
moveDown: () => onMove?.(index, index + 1),
|
|
56
|
+
canMoveUp: index > 0,
|
|
57
|
+
canMoveDown: index < items.length - 1,
|
|
58
|
+
})}
|
|
59
|
+
</Box>
|
|
60
|
+
<Stack direction="row" gap="xs">
|
|
61
|
+
{onMove ? (
|
|
62
|
+
<>
|
|
63
|
+
<IconButton
|
|
64
|
+
icon={{ name: 'arrow-up-outline' }}
|
|
65
|
+
label="Move Up"
|
|
66
|
+
disabled={disabled ?? index === 0}
|
|
67
|
+
onPress={() => onMove(index, index - 1)}
|
|
68
|
+
size="s"
|
|
69
|
+
emphasis="ghost"
|
|
70
|
+
/>
|
|
71
|
+
<IconButton
|
|
72
|
+
icon={{ name: 'arrow-down-outline' }}
|
|
73
|
+
label="Move Down"
|
|
74
|
+
disabled={disabled ?? index === items.length - 1}
|
|
75
|
+
onPress={() => onMove(index, index + 1)}
|
|
76
|
+
size="s"
|
|
77
|
+
emphasis="ghost"
|
|
78
|
+
/>
|
|
79
|
+
</>
|
|
80
|
+
) : null}
|
|
81
|
+
{onRemove ? (
|
|
82
|
+
<IconButton
|
|
83
|
+
icon={{ name: 'trash-outline' }}
|
|
84
|
+
label="Remove"
|
|
85
|
+
tone="danger"
|
|
86
|
+
disabled={disabled}
|
|
87
|
+
onPress={() => onRemove(index)}
|
|
88
|
+
size="s"
|
|
89
|
+
emphasis="ghost"
|
|
90
|
+
/>
|
|
91
|
+
) : null}
|
|
92
|
+
</Stack>
|
|
93
|
+
</Stack>
|
|
94
|
+
</Box>
|
|
95
|
+
))
|
|
96
|
+
)}
|
|
97
|
+
</Stack>
|
|
98
|
+
</Panel>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CollectionEditorRenderItemProps<TItem> {
|
|
4
|
+
item: TItem;
|
|
5
|
+
index: number;
|
|
6
|
+
remove: () => void;
|
|
7
|
+
moveUp: () => void;
|
|
8
|
+
moveDown: () => void;
|
|
9
|
+
canMoveUp: boolean;
|
|
10
|
+
canMoveDown: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CollectionEditorProps<TItem> {
|
|
14
|
+
title?: React.ReactNode;
|
|
15
|
+
description?: React.ReactNode;
|
|
16
|
+
items: readonly TItem[];
|
|
17
|
+
renderItem: (props: CollectionEditorRenderItemProps<TItem>) => React.ReactNode;
|
|
18
|
+
onAdd?: () => void;
|
|
19
|
+
onRemove?: (index: number) => void;
|
|
20
|
+
onMove?: (from: number, to: number) => void;
|
|
21
|
+
addLabel?: React.ReactNode;
|
|
22
|
+
emptyLabel?: React.ReactNode;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
testID?: string;
|
|
25
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Stack } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '../../components/button';
|
|
5
|
+
import { Modal } from '../../components/modal';
|
|
6
|
+
import type { ConfirmDialogProps } from './types';
|
|
7
|
+
|
|
8
|
+
export function ConfirmDialog({
|
|
9
|
+
visible,
|
|
10
|
+
title,
|
|
11
|
+
description,
|
|
12
|
+
children,
|
|
13
|
+
confirmLabel = 'Confirm',
|
|
14
|
+
cancelLabel = 'Cancel',
|
|
15
|
+
confirmTone = 'danger',
|
|
16
|
+
confirmEmphasis = 'solid',
|
|
17
|
+
busy = false,
|
|
18
|
+
closeOnBackdrop = true,
|
|
19
|
+
onConfirm,
|
|
20
|
+
onCancel,
|
|
21
|
+
testID,
|
|
22
|
+
}: ConfirmDialogProps) {
|
|
23
|
+
return (
|
|
24
|
+
<Modal
|
|
25
|
+
closeOnBackdrop={closeOnBackdrop}
|
|
26
|
+
description={description}
|
|
27
|
+
footer={
|
|
28
|
+
<Stack direction={{ base: 'column', md: 'row' }} gap="s" justify="flex-end">
|
|
29
|
+
<Button emphasis="soft" onPress={onCancel} tone="neutral">
|
|
30
|
+
{cancelLabel}
|
|
31
|
+
</Button>
|
|
32
|
+
<Button emphasis={confirmEmphasis} loading={busy} onPress={onConfirm} tone={confirmTone}>
|
|
33
|
+
{confirmLabel}
|
|
34
|
+
</Button>
|
|
35
|
+
</Stack>
|
|
36
|
+
}
|
|
37
|
+
onDismiss={onCancel}
|
|
38
|
+
testID={testID}
|
|
39
|
+
title={title}
|
|
40
|
+
visible={visible}
|
|
41
|
+
width="narrow"
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</Modal>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ZoraEmphasis, ZoraTone } from '../../internal/recipes';
|
|
4
|
+
|
|
5
|
+
export interface ConfirmDialogProps {
|
|
6
|
+
visible: boolean;
|
|
7
|
+
title: React.ReactNode;
|
|
8
|
+
description?: React.ReactNode;
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
confirmLabel?: React.ReactNode;
|
|
11
|
+
cancelLabel?: React.ReactNode;
|
|
12
|
+
confirmTone?: ZoraTone;
|
|
13
|
+
confirmEmphasis?: ZoraEmphasis;
|
|
14
|
+
busy?: boolean;
|
|
15
|
+
closeOnBackdrop?: boolean;
|
|
16
|
+
onConfirm?: () => void;
|
|
17
|
+
onCancel?: () => void;
|
|
18
|
+
testID?: string;
|
|
19
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Box, Stack } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { IconButton } from '../../components/icon-button';
|
|
5
|
+
import { Panel } from '../panel';
|
|
6
|
+
import type { DisclosureSectionProps } from './types';
|
|
7
|
+
|
|
8
|
+
export function DisclosureSection({
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
icon,
|
|
12
|
+
defaultOpen = true,
|
|
13
|
+
open: controlledOpen,
|
|
14
|
+
onOpenChange,
|
|
15
|
+
actions,
|
|
16
|
+
children,
|
|
17
|
+
disabled,
|
|
18
|
+
testID,
|
|
19
|
+
}: DisclosureSectionProps) {
|
|
20
|
+
const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
|
|
21
|
+
|
|
22
|
+
const isControlled = controlledOpen !== undefined;
|
|
23
|
+
const isOpen = isControlled ? controlledOpen : internalOpen;
|
|
24
|
+
|
|
25
|
+
const toggleOpen = () => {
|
|
26
|
+
if (disabled) return;
|
|
27
|
+
|
|
28
|
+
if (isControlled) {
|
|
29
|
+
onOpenChange?.(!controlledOpen);
|
|
30
|
+
} else {
|
|
31
|
+
setInternalOpen(!internalOpen);
|
|
32
|
+
onOpenChange?.(!internalOpen);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Panel
|
|
38
|
+
compact
|
|
39
|
+
description={description}
|
|
40
|
+
testID={testID}
|
|
41
|
+
title={title}
|
|
42
|
+
eyebrow={icon ? <Box pb="xs">{/* Surface icon spec would go here */}</Box> : null}
|
|
43
|
+
actions={
|
|
44
|
+
<Stack direction="row" gap="xs" align="center">
|
|
45
|
+
{actions}
|
|
46
|
+
<IconButton
|
|
47
|
+
icon={{ name: isOpen ? 'chevron-up-outline' : 'chevron-down-outline' }}
|
|
48
|
+
label={isOpen ? 'Collapse' : 'Expand'}
|
|
49
|
+
emphasis="ghost"
|
|
50
|
+
tone="neutral"
|
|
51
|
+
size="s"
|
|
52
|
+
disabled={disabled}
|
|
53
|
+
onPress={toggleOpen}
|
|
54
|
+
/>
|
|
55
|
+
</Stack>
|
|
56
|
+
}
|
|
57
|
+
>
|
|
58
|
+
{isOpen ? <Box pt="m">{children}</Box> : null}
|
|
59
|
+
</Panel>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ButtonIconSpec } from '@ankhorage/surface';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface DisclosureSectionProps {
|
|
5
|
+
title: ReactNode;
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
icon?: ButtonIconSpec;
|
|
8
|
+
defaultOpen?: boolean;
|
|
9
|
+
open?: boolean;
|
|
10
|
+
onOpenChange?: (open: boolean) => void;
|
|
11
|
+
actions?: ReactNode;
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
testID?: string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Stack } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '../../components/button';
|
|
5
|
+
import { Card } from '../../components/card';
|
|
6
|
+
import type { EmptyStateProps } from './types';
|
|
7
|
+
|
|
8
|
+
export function EmptyState({
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
eyebrow,
|
|
12
|
+
primaryAction,
|
|
13
|
+
secondaryAction,
|
|
14
|
+
footer,
|
|
15
|
+
testID,
|
|
16
|
+
}: EmptyStateProps) {
|
|
17
|
+
return (
|
|
18
|
+
<Card
|
|
19
|
+
compact
|
|
20
|
+
description={description}
|
|
21
|
+
eyebrow={eyebrow}
|
|
22
|
+
testID={testID}
|
|
23
|
+
title={title}
|
|
24
|
+
tone="subtle"
|
|
25
|
+
>
|
|
26
|
+
<Stack gap="m">
|
|
27
|
+
{primaryAction || secondaryAction ? (
|
|
28
|
+
<Stack direction={{ base: 'column', md: 'row' }} gap="s">
|
|
29
|
+
{primaryAction ? (
|
|
30
|
+
<Button
|
|
31
|
+
emphasis={primaryAction.emphasis}
|
|
32
|
+
onPress={primaryAction.onPress}
|
|
33
|
+
tone={primaryAction.tone}
|
|
34
|
+
>
|
|
35
|
+
{primaryAction.label}
|
|
36
|
+
</Button>
|
|
37
|
+
) : null}
|
|
38
|
+
{secondaryAction ? (
|
|
39
|
+
<Button
|
|
40
|
+
emphasis={secondaryAction.emphasis ?? 'soft'}
|
|
41
|
+
onPress={secondaryAction.onPress}
|
|
42
|
+
tone={secondaryAction.tone ?? 'neutral'}
|
|
43
|
+
>
|
|
44
|
+
{secondaryAction.label}
|
|
45
|
+
</Button>
|
|
46
|
+
) : null}
|
|
47
|
+
</Stack>
|
|
48
|
+
) : null}
|
|
49
|
+
{footer}
|
|
50
|
+
</Stack>
|
|
51
|
+
</Card>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ZoraEmphasis, ZoraTone } from '../../internal/recipes';
|
|
4
|
+
|
|
5
|
+
export interface EmptyStateAction {
|
|
6
|
+
label: React.ReactNode;
|
|
7
|
+
onPress: () => void;
|
|
8
|
+
tone?: ZoraTone;
|
|
9
|
+
emphasis?: ZoraEmphasis;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface EmptyStateProps {
|
|
13
|
+
title: React.ReactNode;
|
|
14
|
+
description?: React.ReactNode;
|
|
15
|
+
eyebrow?: React.ReactNode;
|
|
16
|
+
primaryAction?: EmptyStateAction;
|
|
17
|
+
secondaryAction?: EmptyStateAction;
|
|
18
|
+
footer?: React.ReactNode;
|
|
19
|
+
testID?: string;
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Field, Stack, Text } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import type { FormFieldProps } from './types';
|
|
5
|
+
|
|
6
|
+
export function FormField({ label, description, helperText, children, ...props }: FormFieldProps) {
|
|
7
|
+
return (
|
|
8
|
+
<Field
|
|
9
|
+
{...props}
|
|
10
|
+
helperText={helperText}
|
|
11
|
+
label={
|
|
12
|
+
<Stack gap="xs">
|
|
13
|
+
<Text variant="label" weight="semiBold">
|
|
14
|
+
{label}
|
|
15
|
+
</Text>
|
|
16
|
+
{description ? (
|
|
17
|
+
<Text tone="muted" variant="bodySmall">
|
|
18
|
+
{description}
|
|
19
|
+
</Text>
|
|
20
|
+
) : null}
|
|
21
|
+
</Stack>
|
|
22
|
+
}
|
|
23
|
+
>
|
|
24
|
+
{children}
|
|
25
|
+
</Field>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FieldProps as SurfaceFieldProps } from '@ankhorage/surface';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
|
|
4
|
+
export interface FormFieldProps extends Pick<
|
|
5
|
+
SurfaceFieldProps,
|
|
6
|
+
'children' | 'disabled' | 'errorText' | 'invalid' | 'readOnly' | 'required' | 'testID'
|
|
7
|
+
> {
|
|
8
|
+
label: React.ReactNode;
|
|
9
|
+
description?: React.ReactNode;
|
|
10
|
+
helperText?: React.ReactNode;
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Box, Stack } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { FormField } from '../form-field';
|
|
5
|
+
import type { InspectorFieldProps } from './types';
|
|
6
|
+
|
|
7
|
+
export function InspectorField({ label, control, children, ...props }: InspectorFieldProps) {
|
|
8
|
+
return (
|
|
9
|
+
<FormField {...props} label={label}>
|
|
10
|
+
<Stack direction="row" gap="s" align="center">
|
|
11
|
+
<Box flex={1}>{children}</Box>
|
|
12
|
+
{control ? <Box>{control}</Box> : null}
|
|
13
|
+
</Stack>
|
|
14
|
+
</FormField>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface InspectorFieldProps {
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
description?: ReactNode;
|
|
6
|
+
helperText?: ReactNode;
|
|
7
|
+
errorText?: ReactNode;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
invalid?: boolean;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
readOnly?: boolean;
|
|
12
|
+
control?: ReactNode;
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
testID?: string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Box, Stack } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Badge } from '../../components/badge';
|
|
5
|
+
import { Card } from '../../components/card';
|
|
6
|
+
import type { NoticeProps } from './types';
|
|
7
|
+
|
|
8
|
+
export function Notice({
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
children,
|
|
12
|
+
actions,
|
|
13
|
+
tone = 'primary',
|
|
14
|
+
testID,
|
|
15
|
+
}: NoticeProps) {
|
|
16
|
+
return (
|
|
17
|
+
<Card
|
|
18
|
+
description={description}
|
|
19
|
+
eyebrow={<Badge tone={tone}>{String(tone).toUpperCase()}</Badge>}
|
|
20
|
+
testID={testID}
|
|
21
|
+
title={title}
|
|
22
|
+
tone="subtle"
|
|
23
|
+
>
|
|
24
|
+
<Stack gap="m">
|
|
25
|
+
{children ? <Box>{children}</Box> : null}
|
|
26
|
+
{actions ? <Box>{actions}</Box> : null}
|
|
27
|
+
</Stack>
|
|
28
|
+
</Card>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ZoraTone } from '../../internal/recipes';
|
|
4
|
+
|
|
5
|
+
export interface NoticeProps {
|
|
6
|
+
title: React.ReactNode;
|
|
7
|
+
description?: React.ReactNode;
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
actions?: React.ReactNode;
|
|
10
|
+
tone?: ZoraTone;
|
|
11
|
+
testID?: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ZoraCardTone } from '../../internal/recipes';
|
|
4
|
+
|
|
5
|
+
export interface PanelProps {
|
|
6
|
+
title?: React.ReactNode;
|
|
7
|
+
description?: React.ReactNode;
|
|
8
|
+
eyebrow?: React.ReactNode;
|
|
9
|
+
actions?: React.ReactNode;
|
|
10
|
+
footer?: React.ReactNode;
|
|
11
|
+
children?: React.ReactNode;
|
|
12
|
+
tone?: ZoraCardTone;
|
|
13
|
+
compact?: boolean;
|
|
14
|
+
testID?: string;
|
|
15
|
+
}
|