@ankhorage/zora 2.4.3 → 2.4.5
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 +155 -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 +8 -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 +14 -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/meta.d.ts +17 -0
- package/dist/components/action-sheet/meta.d.ts.map +1 -0
- package/dist/components/action-sheet/meta.js +17 -0
- package/dist/components/action-sheet/meta.js.map +1 -0
- package/dist/components/action-sheet/types.d.ts +13 -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/data-table/DataTable.d.ts +4 -0
- package/dist/components/data-table/DataTable.d.ts.map +1 -0
- package/dist/components/data-table/DataTable.js +207 -0
- package/dist/components/data-table/DataTable.js.map +1 -0
- package/dist/components/data-table/index.d.ts +3 -0
- package/dist/components/data-table/index.d.ts.map +1 -0
- package/dist/components/data-table/index.js +2 -0
- package/dist/components/data-table/index.js.map +1 -0
- package/dist/components/data-table/meta.d.ts +9 -0
- package/dist/components/data-table/meta.d.ts.map +1 -0
- package/dist/components/data-table/meta.js +9 -0
- package/dist/components/data-table/meta.js.map +1 -0
- package/dist/components/data-table/types.d.ts +51 -0
- package/dist/components/data-table/types.d.ts.map +1 -0
- package/dist/components/data-table/types.js +2 -0
- package/dist/components/data-table/types.js.map +1 -0
- package/dist/components/menu/DropdownMenu.d.ts +4 -0
- package/dist/components/menu/DropdownMenu.d.ts.map +1 -0
- package/dist/components/menu/DropdownMenu.js +6 -0
- package/dist/components/menu/DropdownMenu.js.map +1 -0
- package/dist/components/menu/Menu.d.ts +4 -0
- package/dist/components/menu/Menu.d.ts.map +1 -0
- package/dist/components/menu/Menu.js +43 -0
- package/dist/components/menu/Menu.js.map +1 -0
- package/dist/components/menu/index.d.ts +4 -0
- package/dist/components/menu/index.d.ts.map +1 -0
- package/dist/components/menu/index.js +3 -0
- package/dist/components/menu/index.js.map +1 -0
- package/dist/components/menu/meta.d.ts +17 -0
- package/dist/components/menu/meta.d.ts.map +1 -0
- package/dist/components/menu/meta.js +17 -0
- package/dist/components/menu/meta.js.map +1 -0
- package/dist/components/menu/types.d.ts +22 -0
- package/dist/components/menu/types.d.ts.map +1 -0
- package/dist/components/menu/types.js +2 -0
- package/dist/components/menu/types.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/metadata/componentMeta.d.ts.map +1 -1
- package/dist/metadata/componentMeta.js +8 -0
- package/dist/metadata/componentMeta.js.map +1 -1
- package/package.json +2 -2
- package/src/components/action-sheet/ActionSheet.tsx +11 -0
- package/src/components/action-sheet/ActionSheetItem.tsx +32 -0
- package/src/components/action-sheet/index.ts +3 -0
- package/src/components/action-sheet/meta.ts +19 -0
- package/src/components/action-sheet/types.ts +23 -0
- package/src/components/data-table/DataTable.tsx +388 -0
- package/src/components/data-table/index.ts +11 -0
- package/src/components/data-table/meta.ts +10 -0
- package/src/components/data-table/types.ts +57 -0
- package/src/components/menu/DropdownMenu.tsx +8 -0
- package/src/components/menu/Menu.tsx +63 -0
- package/src/components/menu/index.ts +3 -0
- package/src/components/menu/meta.ts +19 -0
- package/src/components/menu/types.ts +31 -0
- package/src/index.ts +15 -0
- package/src/metadata/componentMeta.ts +8 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ActionSheetItem as SurfaceActionSheetItem } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { resolveIconSize } from '../../internal/recipes';
|
|
5
|
+
import { useZoraTheme } from '../../theme/useZoraTheme';
|
|
6
|
+
import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
|
|
7
|
+
import { Icon } from '../icon';
|
|
8
|
+
import type { ActionSheetItemProps } from './types';
|
|
9
|
+
|
|
10
|
+
function ActionSheetItemInner({
|
|
11
|
+
themeId: _themeId,
|
|
12
|
+
mode: _mode,
|
|
13
|
+
icon,
|
|
14
|
+
leading,
|
|
15
|
+
...props
|
|
16
|
+
}: ActionSheetItemProps) {
|
|
17
|
+
const { theme } = useZoraTheme();
|
|
18
|
+
const resolvedLeading =
|
|
19
|
+
leading ??
|
|
20
|
+
(icon ? (
|
|
21
|
+
<Icon
|
|
22
|
+
color={theme.semantics.content.muted}
|
|
23
|
+
name={icon.name}
|
|
24
|
+
provider={icon.provider}
|
|
25
|
+
size={resolveIconSize('s')}
|
|
26
|
+
/>
|
|
27
|
+
) : undefined);
|
|
28
|
+
|
|
29
|
+
return <SurfaceActionSheetItem {...props} leading={resolvedLeading} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const ActionSheetItem = withZoraThemeScope(ActionSheetItemInner);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ZoraComponentMeta } from '../../metadata';
|
|
2
|
+
|
|
3
|
+
export const actionSheetMeta = {
|
|
4
|
+
name: 'ActionSheet',
|
|
5
|
+
category: 'component',
|
|
6
|
+
directManifestNode: false,
|
|
7
|
+
allowedChildren: [],
|
|
8
|
+
note: 'Surface-backed contextual action sheet; not represented as a manifest node in v1.',
|
|
9
|
+
props: {},
|
|
10
|
+
} as const satisfies ZoraComponentMeta;
|
|
11
|
+
|
|
12
|
+
export const actionSheetItemMeta = {
|
|
13
|
+
name: 'ActionSheetItem',
|
|
14
|
+
category: 'component',
|
|
15
|
+
directManifestNode: false,
|
|
16
|
+
allowedChildren: [],
|
|
17
|
+
note: 'Surface-backed contextual action sheet row; not represented as a manifest node in v1.',
|
|
18
|
+
props: {},
|
|
19
|
+
} as const satisfies ZoraComponentMeta;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActionSheetItemProps as SurfaceActionSheetItemProps,
|
|
3
|
+
ActionSheetProps as SurfaceActionSheetProps,
|
|
4
|
+
ButtonIconSpec,
|
|
5
|
+
} from '@ankhorage/surface';
|
|
6
|
+
import type React from 'react';
|
|
7
|
+
|
|
8
|
+
import type { ZoraColor } from '../../internal/recipes';
|
|
9
|
+
import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
|
|
10
|
+
|
|
11
|
+
export interface ActionSheetProps
|
|
12
|
+
extends ZoraBaseProps, Omit<SurfaceActionSheetProps, 'children' | 'mode' | 'themeId'> {
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ActionSheetItemProps
|
|
17
|
+
extends
|
|
18
|
+
ZoraBaseProps,
|
|
19
|
+
Omit<SurfaceActionSheetItemProps, 'color' | 'leading' | 'mode' | 'themeId'> {
|
|
20
|
+
color?: ZoraColor;
|
|
21
|
+
icon?: ButtonIconSpec;
|
|
22
|
+
leading?: React.ReactNode;
|
|
23
|
+
}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ScrollView, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { Box, Show, Stack } from '../../foundation';
|
|
5
|
+
import { EmptyState } from '../../patterns/empty-state';
|
|
6
|
+
import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
|
|
7
|
+
import { Button } from '../button';
|
|
8
|
+
import { Card } from '../card';
|
|
9
|
+
import { IconButton } from '../icon-button';
|
|
10
|
+
import { DropdownMenu, type MenuAction } from '../menu';
|
|
11
|
+
import { SkeletonList } from '../skeleton';
|
|
12
|
+
import { Text, type TextAlign } from '../text';
|
|
13
|
+
import type {
|
|
14
|
+
DataTableCellContext,
|
|
15
|
+
DataTableColumn,
|
|
16
|
+
DataTableColumnAlign,
|
|
17
|
+
DataTableDensity,
|
|
18
|
+
DataTableProps,
|
|
19
|
+
DataTableRowAction,
|
|
20
|
+
DataTableSortDirection,
|
|
21
|
+
} from './types';
|
|
22
|
+
|
|
23
|
+
function resolveTextAlign(align: DataTableColumnAlign | undefined): TextAlign {
|
|
24
|
+
switch (align) {
|
|
25
|
+
case 'center':
|
|
26
|
+
return 'center';
|
|
27
|
+
case 'end':
|
|
28
|
+
return 'right';
|
|
29
|
+
case 'start':
|
|
30
|
+
default:
|
|
31
|
+
return 'left';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveCellJustify(align: DataTableColumnAlign | undefined): ViewStyle['alignItems'] {
|
|
36
|
+
switch (align) {
|
|
37
|
+
case 'center':
|
|
38
|
+
return 'center';
|
|
39
|
+
case 'end':
|
|
40
|
+
return 'flex-end';
|
|
41
|
+
case 'start':
|
|
42
|
+
default:
|
|
43
|
+
return 'flex-start';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveCellStyle<TRow extends object>(column: DataTableColumn<TRow>): ViewStyle {
|
|
48
|
+
return {
|
|
49
|
+
alignItems: resolveCellJustify(column.align),
|
|
50
|
+
flexGrow: column.width === undefined ? 1 : 0,
|
|
51
|
+
flexShrink: 0,
|
|
52
|
+
minWidth: column.minWidth ?? 140,
|
|
53
|
+
width: column.width,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveRowPadding(density: DataTableDensity) {
|
|
58
|
+
return density === 'compact'
|
|
59
|
+
? { px: 'm' as const, py: 's' as const }
|
|
60
|
+
: { px: 'm' as const, py: 'm' as const };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveAccessorValue<TRow extends object>(row: TRow, column: DataTableColumn<TRow>) {
|
|
64
|
+
if (column.accessor === undefined) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return row[column.accessor];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function renderDefaultCell(value: unknown): React.ReactNode {
|
|
72
|
+
if (value === null || value === undefined) {
|
|
73
|
+
return '—';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
77
|
+
return String(value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof value === 'bigint') {
|
|
81
|
+
return value.toString();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (value instanceof Date) {
|
|
85
|
+
return value.toLocaleDateString();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return '—';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createCellContext<TRow extends object>(
|
|
92
|
+
column: DataTableColumn<TRow>,
|
|
93
|
+
row: TRow,
|
|
94
|
+
rowIndex: number,
|
|
95
|
+
): DataTableCellContext<TRow> {
|
|
96
|
+
return {
|
|
97
|
+
column,
|
|
98
|
+
row,
|
|
99
|
+
rowIndex,
|
|
100
|
+
value: resolveAccessorValue(row, column),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function renderCell<TRow extends object>(
|
|
105
|
+
column: DataTableColumn<TRow>,
|
|
106
|
+
row: TRow,
|
|
107
|
+
rowIndex: number,
|
|
108
|
+
) {
|
|
109
|
+
const context = createCellContext(column, row, rowIndex);
|
|
110
|
+
return column.renderCell ? column.renderCell(context) : renderDefaultCell(context.value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function renderTableCell<TRow extends object>(
|
|
114
|
+
column: DataTableColumn<TRow>,
|
|
115
|
+
row: TRow,
|
|
116
|
+
rowIndex: number,
|
|
117
|
+
) {
|
|
118
|
+
if (column.renderCell) {
|
|
119
|
+
return column.renderCell(createCellContext(column, row, rowIndex));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Text align={resolveTextAlign(column.align)} variant="bodySmall">
|
|
124
|
+
{renderDefaultCell(resolveAccessorValue(row, column))}
|
|
125
|
+
</Text>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function resolveNextSortDirection(
|
|
130
|
+
current: DataTableSortDirection | undefined,
|
|
131
|
+
): DataTableSortDirection {
|
|
132
|
+
return current === 'asc' ? 'desc' : 'asc';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function mapRowActions<TRow extends object>(
|
|
136
|
+
row: TRow,
|
|
137
|
+
actions: readonly DataTableRowAction<TRow>[],
|
|
138
|
+
): readonly MenuAction[] {
|
|
139
|
+
return actions.map((action) => ({
|
|
140
|
+
description: action.description,
|
|
141
|
+
disabled: action.disabled,
|
|
142
|
+
icon: action.icon,
|
|
143
|
+
id: action.id,
|
|
144
|
+
intent: action.intent,
|
|
145
|
+
onPress: action.onPress ? () => action.onPress?.(row) : undefined,
|
|
146
|
+
title: action.title,
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function renderRowActions<TRow extends object>({
|
|
151
|
+
row,
|
|
152
|
+
rowIndex,
|
|
153
|
+
rowActions,
|
|
154
|
+
testID,
|
|
155
|
+
}: {
|
|
156
|
+
row: TRow;
|
|
157
|
+
rowIndex: number;
|
|
158
|
+
rowActions: DataTableProps<TRow>['rowActions'];
|
|
159
|
+
testID?: string;
|
|
160
|
+
}) {
|
|
161
|
+
const actions = rowActions?.(row, rowIndex) ?? [];
|
|
162
|
+
|
|
163
|
+
if (actions.length === 0) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<DropdownMenu
|
|
169
|
+
actions={mapRowActions(row, actions)}
|
|
170
|
+
trigger={
|
|
171
|
+
<IconButton
|
|
172
|
+
icon={{ name: 'ellipsis-horizontal' }}
|
|
173
|
+
label="Row actions"
|
|
174
|
+
size="s"
|
|
175
|
+
variant="ghost"
|
|
176
|
+
/>
|
|
177
|
+
}
|
|
178
|
+
testID={testID ? `${testID}-row-actions-${rowIndex}` : undefined}
|
|
179
|
+
/>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function DataTableHeader<TRow extends object>({
|
|
184
|
+
columns,
|
|
185
|
+
sort,
|
|
186
|
+
onSortChange,
|
|
187
|
+
density,
|
|
188
|
+
}: Pick<DataTableProps<TRow>, 'columns' | 'density' | 'onSortChange' | 'sort'>) {
|
|
189
|
+
const padding = resolveRowPadding(density ?? 'comfortable');
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<Box bg="subtle" borderColor="border" borderWidth={1} radius="m">
|
|
193
|
+
<Stack direction="row" align="center">
|
|
194
|
+
{columns.map((column) => {
|
|
195
|
+
const currentDirection = sort?.columnId === column.id ? sort.direction : undefined;
|
|
196
|
+
const sortable = Boolean(column.sortable && onSortChange);
|
|
197
|
+
const directionLabel =
|
|
198
|
+
currentDirection === undefined ? '' : currentDirection === 'asc' ? ' ↑' : ' ↓';
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<Box key={column.id} px={padding.px} py={padding.py} style={resolveCellStyle(column)}>
|
|
202
|
+
{sortable ? (
|
|
203
|
+
<Button
|
|
204
|
+
color="primary"
|
|
205
|
+
onPress={() =>
|
|
206
|
+
onSortChange?.({
|
|
207
|
+
columnId: column.id,
|
|
208
|
+
direction: resolveNextSortDirection(currentDirection),
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
size="s"
|
|
212
|
+
variant="ghost"
|
|
213
|
+
>
|
|
214
|
+
{column.header}
|
|
215
|
+
{directionLabel}
|
|
216
|
+
</Button>
|
|
217
|
+
) : (
|
|
218
|
+
<Text
|
|
219
|
+
align={resolveTextAlign(column.align)}
|
|
220
|
+
emphasis="muted"
|
|
221
|
+
variant="caption"
|
|
222
|
+
weight="semiBold"
|
|
223
|
+
>
|
|
224
|
+
{column.header}
|
|
225
|
+
</Text>
|
|
226
|
+
)}
|
|
227
|
+
</Box>
|
|
228
|
+
);
|
|
229
|
+
})}
|
|
230
|
+
<Box px={padding.px} py={padding.py} style={{ minWidth: 56, width: 56 }}>
|
|
231
|
+
<Text align="right" emphasis="muted" variant="caption" weight="semiBold">
|
|
232
|
+
Actions
|
|
233
|
+
</Text>
|
|
234
|
+
</Box>
|
|
235
|
+
</Stack>
|
|
236
|
+
</Box>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function DataTableDesktop<TRow extends object>({
|
|
241
|
+
columns,
|
|
242
|
+
rows,
|
|
243
|
+
rowActions,
|
|
244
|
+
rowId,
|
|
245
|
+
sort,
|
|
246
|
+
onSortChange,
|
|
247
|
+
density,
|
|
248
|
+
testID,
|
|
249
|
+
}: DataTableProps<TRow>) {
|
|
250
|
+
const padding = resolveRowPadding(density ?? 'comfortable');
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
254
|
+
<Box minWidth={720} style={{ width: '100%' }}>
|
|
255
|
+
<Stack gap="xs">
|
|
256
|
+
<DataTableHeader
|
|
257
|
+
columns={columns}
|
|
258
|
+
density={density}
|
|
259
|
+
onSortChange={onSortChange}
|
|
260
|
+
sort={sort}
|
|
261
|
+
/>
|
|
262
|
+
<Stack gap="xs">
|
|
263
|
+
{rows.map((row, rowIndex) => (
|
|
264
|
+
<Box
|
|
265
|
+
bg="surface"
|
|
266
|
+
borderColor="border"
|
|
267
|
+
borderWidth={1}
|
|
268
|
+
key={rowId(row, rowIndex)}
|
|
269
|
+
radius="m"
|
|
270
|
+
testID={testID ? `${testID}-row-${rowIndex}` : undefined}
|
|
271
|
+
>
|
|
272
|
+
<Stack direction="row" align="center">
|
|
273
|
+
{columns.map((column) => (
|
|
274
|
+
<Box
|
|
275
|
+
key={column.id}
|
|
276
|
+
px={padding.px}
|
|
277
|
+
py={padding.py}
|
|
278
|
+
style={resolveCellStyle(column)}
|
|
279
|
+
>
|
|
280
|
+
{renderTableCell(column, row, rowIndex)}
|
|
281
|
+
</Box>
|
|
282
|
+
))}
|
|
283
|
+
<Box
|
|
284
|
+
px={padding.px}
|
|
285
|
+
py={padding.py}
|
|
286
|
+
style={{ alignItems: 'flex-end', minWidth: 56, width: 56 }}
|
|
287
|
+
>
|
|
288
|
+
{renderRowActions({ row, rowActions, rowIndex, testID })}
|
|
289
|
+
</Box>
|
|
290
|
+
</Stack>
|
|
291
|
+
</Box>
|
|
292
|
+
))}
|
|
293
|
+
</Stack>
|
|
294
|
+
</Stack>
|
|
295
|
+
</Box>
|
|
296
|
+
</ScrollView>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function DataTableMobile<TRow extends object>({
|
|
301
|
+
columns,
|
|
302
|
+
rows,
|
|
303
|
+
rowActions,
|
|
304
|
+
rowId,
|
|
305
|
+
testID,
|
|
306
|
+
}: DataTableProps<TRow>) {
|
|
307
|
+
const [primaryColumn, ...detailColumns] = columns;
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<Stack gap="s">
|
|
311
|
+
{rows.map((row, rowIndex) => {
|
|
312
|
+
const title = primaryColumn
|
|
313
|
+
? renderCell(primaryColumn, row, rowIndex)
|
|
314
|
+
: `Row ${rowIndex + 1}`;
|
|
315
|
+
const actions = renderRowActions({ row, rowActions, rowIndex, testID });
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<Card
|
|
319
|
+
actions={actions}
|
|
320
|
+
compact
|
|
321
|
+
key={rowId(row, rowIndex)}
|
|
322
|
+
testID={testID ? `${testID}-card-${rowIndex}` : undefined}
|
|
323
|
+
title={title}
|
|
324
|
+
>
|
|
325
|
+
<Stack gap="s">
|
|
326
|
+
{detailColumns.map((column) => (
|
|
327
|
+
<Stack gap="xxs" key={column.id}>
|
|
328
|
+
<Text emphasis="muted" variant="caption" weight="semiBold">
|
|
329
|
+
{column.header}
|
|
330
|
+
</Text>
|
|
331
|
+
{column.renderCell ? (
|
|
332
|
+
renderCell(column, row, rowIndex)
|
|
333
|
+
) : (
|
|
334
|
+
<Text variant="bodySmall">
|
|
335
|
+
{renderDefaultCell(resolveAccessorValue(row, column))}
|
|
336
|
+
</Text>
|
|
337
|
+
)}
|
|
338
|
+
</Stack>
|
|
339
|
+
))}
|
|
340
|
+
</Stack>
|
|
341
|
+
</Card>
|
|
342
|
+
);
|
|
343
|
+
})}
|
|
344
|
+
</Stack>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function DataTableInner<TRow extends object>({
|
|
349
|
+
themeId: _themeId,
|
|
350
|
+
mode: _mode,
|
|
351
|
+
rows,
|
|
352
|
+
loading = false,
|
|
353
|
+
loadingRows = 5,
|
|
354
|
+
emptyTitle = 'No data',
|
|
355
|
+
emptyDescription = 'There are no rows to display.',
|
|
356
|
+
testID,
|
|
357
|
+
density = 'comfortable',
|
|
358
|
+
...props
|
|
359
|
+
}: DataTableProps<TRow>) {
|
|
360
|
+
if (loading) {
|
|
361
|
+
return <SkeletonList rows={loadingRows} variant="card" testID={testID} />;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (rows.length === 0) {
|
|
365
|
+
return <EmptyState title={emptyTitle} description={emptyDescription} />;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const tableProps: DataTableProps<TRow> = {
|
|
369
|
+
...props,
|
|
370
|
+
density,
|
|
371
|
+
emptyDescription,
|
|
372
|
+
emptyTitle,
|
|
373
|
+
loading,
|
|
374
|
+
loadingRows,
|
|
375
|
+
rows,
|
|
376
|
+
testID,
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
return (
|
|
380
|
+
<Box testID={testID}>
|
|
381
|
+
<Show when={{ base: false, md: true }} fallback={<DataTableMobile {...tableProps} />}>
|
|
382
|
+
<DataTableDesktop {...tableProps} />
|
|
383
|
+
</Show>
|
|
384
|
+
</Box>
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export const DataTable = withZoraThemeScope(DataTableInner);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ZoraComponentMeta } from '../../metadata';
|
|
2
|
+
|
|
3
|
+
export const dataTableMeta = {
|
|
4
|
+
name: 'DataTable',
|
|
5
|
+
category: 'component',
|
|
6
|
+
directManifestNode: false,
|
|
7
|
+
allowedChildren: [],
|
|
8
|
+
note: 'Code-facing typed data table component; not represented as a manifest node in v1.',
|
|
9
|
+
props: {},
|
|
10
|
+
} as const satisfies ZoraComponentMeta;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ButtonIconSpec } from '@ankhorage/surface';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
|
|
4
|
+
import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
|
|
5
|
+
import type { MenuActionIntent } from '../menu';
|
|
6
|
+
|
|
7
|
+
export type DataTableColumnAlign = 'start' | 'center' | 'end';
|
|
8
|
+
export type DataTableDensity = 'comfortable' | 'compact';
|
|
9
|
+
export type DataTableSortDirection = 'asc' | 'desc';
|
|
10
|
+
|
|
11
|
+
export interface DataTableSortState {
|
|
12
|
+
columnId: string;
|
|
13
|
+
direction: DataTableSortDirection;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DataTableCellContext<TRow extends object> {
|
|
17
|
+
row: TRow;
|
|
18
|
+
rowIndex: number;
|
|
19
|
+
column: DataTableColumn<TRow>;
|
|
20
|
+
value: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DataTableColumn<TRow extends object> {
|
|
24
|
+
id: string;
|
|
25
|
+
header: React.ReactNode;
|
|
26
|
+
accessor?: keyof TRow;
|
|
27
|
+
align?: DataTableColumnAlign;
|
|
28
|
+
width?: number;
|
|
29
|
+
minWidth?: number;
|
|
30
|
+
sortable?: boolean;
|
|
31
|
+
renderCell?: (context: DataTableCellContext<TRow>) => React.ReactNode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DataTableRowAction<TRow extends object> {
|
|
35
|
+
id: string;
|
|
36
|
+
title: React.ReactNode;
|
|
37
|
+
description?: React.ReactNode;
|
|
38
|
+
icon?: ButtonIconSpec;
|
|
39
|
+
intent?: MenuActionIntent;
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
onPress?: (row: TRow) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DataTableProps<TRow extends object> extends ZoraBaseProps {
|
|
45
|
+
columns: readonly DataTableColumn<TRow>[];
|
|
46
|
+
rows: readonly TRow[];
|
|
47
|
+
rowId: (row: TRow, index: number) => string;
|
|
48
|
+
rowActions?: (row: TRow, index: number) => readonly DataTableRowAction<TRow>[];
|
|
49
|
+
sort?: DataTableSortState;
|
|
50
|
+
onSortChange?: (sort: DataTableSortState) => void;
|
|
51
|
+
loading?: boolean;
|
|
52
|
+
loadingRows?: number;
|
|
53
|
+
emptyTitle?: React.ReactNode;
|
|
54
|
+
emptyDescription?: React.ReactNode;
|
|
55
|
+
density?: DataTableDensity;
|
|
56
|
+
testID?: string;
|
|
57
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Menu as SurfaceMenu, type MenuAction as SurfaceMenuAction } from '@ankhorage/surface';
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Box } from '../../foundation';
|
|
5
|
+
import { resolveIconSize } from '../../internal/recipes';
|
|
6
|
+
import { useZoraTheme } from '../../theme/useZoraTheme';
|
|
7
|
+
import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
|
|
8
|
+
import { Icon } from '../icon';
|
|
9
|
+
import type { MenuAction, MenuProps } from './types';
|
|
10
|
+
|
|
11
|
+
function renderActionLeading(action: MenuAction, iconColor: string) {
|
|
12
|
+
if (action.leading) {
|
|
13
|
+
return action.leading;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!action.icon) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Icon
|
|
22
|
+
color={iconColor}
|
|
23
|
+
name={action.icon.name}
|
|
24
|
+
provider={action.icon.provider}
|
|
25
|
+
size={resolveIconSize('s')}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function renderActionTrailing(action: MenuAction) {
|
|
31
|
+
if (!action.trailing) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return <Box>{action.trailing}</Box>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function toSurfaceAction(action: MenuAction, iconColor: string): SurfaceMenuAction {
|
|
39
|
+
return {
|
|
40
|
+
activate: action.onPress,
|
|
41
|
+
description: action.description,
|
|
42
|
+
disabled: action.disabled,
|
|
43
|
+
id: action.id,
|
|
44
|
+
intent: action.intent,
|
|
45
|
+
leading: renderActionLeading(action, iconColor),
|
|
46
|
+
selected: action.selected,
|
|
47
|
+
title: action.title,
|
|
48
|
+
trailing: renderActionTrailing(action),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function MenuInner({ themeId: _themeId, mode: _mode, actions, onDismiss, ...props }: MenuProps) {
|
|
53
|
+
const { theme } = useZoraTheme();
|
|
54
|
+
const iconColor = theme.semantics.content.muted;
|
|
55
|
+
const surfaceActions = useMemo(
|
|
56
|
+
() => actions.map((action) => toSurfaceAction(action, iconColor)),
|
|
57
|
+
[actions, iconColor],
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return <SurfaceMenu {...props} actions={surfaceActions} dismiss={onDismiss} />;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const Menu = withZoraThemeScope(MenuInner);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ZoraComponentMeta } from '../../metadata';
|
|
2
|
+
|
|
3
|
+
export const menuMeta = {
|
|
4
|
+
name: 'Menu',
|
|
5
|
+
category: 'component',
|
|
6
|
+
directManifestNode: false,
|
|
7
|
+
allowedChildren: [],
|
|
8
|
+
note: 'Surface-backed action menu; not represented as a manifest node in v1.',
|
|
9
|
+
props: {},
|
|
10
|
+
} as const satisfies ZoraComponentMeta;
|
|
11
|
+
|
|
12
|
+
export const dropdownMenuMeta = {
|
|
13
|
+
name: 'DropdownMenu',
|
|
14
|
+
category: 'component',
|
|
15
|
+
directManifestNode: false,
|
|
16
|
+
allowedChildren: [],
|
|
17
|
+
note: 'Product-facing alias for Menu trigger/action composition; not represented as a manifest node in v1.',
|
|
18
|
+
props: {},
|
|
19
|
+
} as const satisfies ZoraComponentMeta;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ButtonIconSpec,
|
|
3
|
+
MenuActionIntent as SurfaceMenuActionIntent,
|
|
4
|
+
MenuProps as SurfaceMenuProps,
|
|
5
|
+
} from '@ankhorage/surface';
|
|
6
|
+
import type React from 'react';
|
|
7
|
+
|
|
8
|
+
import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
|
|
9
|
+
|
|
10
|
+
export type MenuActionIntent = SurfaceMenuActionIntent;
|
|
11
|
+
|
|
12
|
+
export interface MenuAction {
|
|
13
|
+
id: string;
|
|
14
|
+
title: React.ReactNode;
|
|
15
|
+
description?: React.ReactNode;
|
|
16
|
+
icon?: ButtonIconSpec;
|
|
17
|
+
leading?: React.ReactNode;
|
|
18
|
+
trailing?: React.ReactNode;
|
|
19
|
+
intent?: MenuActionIntent;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
selected?: boolean;
|
|
22
|
+
onPress?: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface MenuProps
|
|
26
|
+
extends ZoraBaseProps, Omit<SurfaceMenuProps, 'actions' | 'dismiss' | 'mode' | 'themeId'> {
|
|
27
|
+
actions: readonly MenuAction[];
|
|
28
|
+
onDismiss?: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type DropdownMenuProps = MenuProps;
|