@svadmin/ui 0.85.0 → 0.88.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/README.md +31 -0
- package/dist/app-shell.css +216 -0
- package/dist/app.css +659 -165
- package/dist/app.theme.css +659 -165
- package/dist/component-registry.d.ts +7292 -529
- package/dist/component-registry.js +32 -3
- package/dist/components/AppShellPage.svelte +172 -0
- package/dist/components/AppShellPage.svelte.d.ts +30 -0
- package/dist/components/ApprovalActionCard.svelte +54 -31
- package/dist/components/ApprovalCenter.svelte +73 -31
- package/dist/components/AuthPageShell.svelte +13 -2
- package/dist/components/AutoForm.svelte +7 -12
- package/dist/components/AutoForm.svelte.d.ts +0 -1
- package/dist/components/AutoTable.svelte +15 -3
- package/dist/components/AutoTable.svelte.d.ts +2 -0
- package/dist/components/ConfigurationPage.svelte +0 -3
- package/dist/components/ConfigurationPage.svelte.d.ts +0 -1
- package/dist/components/CreatePage.svelte +22 -19
- package/dist/components/CreatePage.svelte.d.ts +4 -1
- package/dist/components/EditPage.svelte +23 -20
- package/dist/components/EditPage.svelte.d.ts +4 -1
- package/dist/components/FieldRenderer.svelte +2 -10
- package/dist/components/FieldRenderer.svelte.d.ts +0 -1
- package/dist/components/ListPage.svelte +21 -18
- package/dist/components/ListPage.svelte.d.ts +2 -1
- package/dist/components/LoginPage.svelte +27 -11
- package/dist/components/LoginPage.svelte.d.ts +4 -0
- package/dist/components/NotificationsSettings.svelte +16 -8
- package/dist/components/PageHeader.svelte +1 -3
- package/dist/components/PageHeader.svelte.d.ts +0 -1
- package/dist/components/PagePattern.svelte +36 -13
- package/dist/components/PagePattern.svelte.d.ts +7 -0
- package/dist/components/PasswordInput.svelte +36 -17
- package/dist/components/PasswordInput.svelte.d.ts +3 -0
- package/dist/components/QuickEditDrawer.svelte +0 -1
- package/dist/components/ShowPage.svelte +24 -29
- package/dist/components/ShowPage.svelte.d.ts +0 -1
- package/dist/components/SvarAutoTable.svelte +2 -1
- package/dist/components/SvarDataGrid.svelte +8 -4
- package/dist/components/SvarDataGrid.svelte.d.ts +3 -0
- package/dist/components/SvarResourceTable.svelte +0 -1
- package/dist/components/SvarResourceTable.svelte.d.ts +0 -1
- package/dist/components/SvarResourceTableView.svelte +2 -2
- package/dist/components/account/MemberDirectory.svelte +17 -4
- package/dist/components/account/SecurityLogPage.svelte +8 -1
- package/dist/components/auto-table-grid.d.ts +1 -0
- package/dist/components/content/AuditTimeline.svelte +32 -4
- package/dist/components/content/AuditTimeline.svelte.d.ts +9 -0
- package/dist/components/content/ContentPageHeader.svelte +2 -1
- package/dist/components/content/ContentPageHeader.svelte.d.ts +1 -0
- package/dist/components/content/ContentPageShell.svelte +1 -1
- package/dist/components/content/ContentPageShell.svelte.d.ts +1 -0
- package/dist/components/content/DataPanel.svelte +58 -0
- package/dist/components/content/DataPanel.svelte.d.ts +16 -0
- package/dist/components/content/DataState.svelte +5 -4
- package/dist/components/content/DataState.svelte.d.ts +1 -1
- package/dist/components/content/DescriptionList.svelte +5 -8
- package/dist/components/content/DescriptionList.svelte.d.ts +0 -1
- package/dist/components/content/FilterToolbar.svelte +9 -6
- package/dist/components/content/FilterToolbar.svelte.d.ts +0 -1
- package/dist/components/content/NetworkTable.svelte +3 -3
- package/dist/components/content/NetworkTable.svelte.d.ts +0 -1
- package/dist/components/content/PageSection.svelte +1 -1
- package/dist/components/content/StatusTabs.svelte +2 -7
- package/dist/components/content/StatusTabs.svelte.d.ts +0 -1
- package/dist/components/content/WorkspaceLayout.svelte +3 -1
- package/dist/components/content/WorkspaceLayout.svelte.d.ts +1 -0
- package/dist/components/content/index.d.ts +1 -0
- package/dist/components/content/index.js +1 -0
- package/dist/components/network/TeamCrewTablePage.svelte +8 -1
- package/dist/components/network/UserCardsNFTPage.svelte +3 -1
- package/dist/components/profile/ProjectsGrid.svelte +1 -1
- package/dist/components/profile/TeamsShowcase.svelte +1 -1
- package/dist/components/ui/table/index.d.ts +0 -1
- package/dist/components/ui/table/table.svelte +1 -0
- package/dist/components/ui/table/table.svelte.d.ts +1 -0
- package/dist/components.css +1 -11
- package/dist/data-panel.css +88 -0
- package/dist/design-contract.d.ts +11 -0
- package/dist/design-contract.js +8 -0
- package/dist/design-system.d.ts +3 -2
- package/dist/design-system.js +3 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/page-chrome.css +42 -64
- package/dist/recipes.d.ts +12 -66
- package/dist/recipes.js +52 -50
- package/dist/styles/recipes.css +15 -48
- package/package.json +4 -4
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import CloneButton from './buttons/CloneButton.svelte';
|
|
14
14
|
import { Button } from './ui/button/index.js';
|
|
15
15
|
import { RefreshCw } from '@lucide/svelte';
|
|
16
|
+
import DataState from './content/DataState.svelte';
|
|
16
17
|
|
|
17
18
|
const i18n = useTranslation();
|
|
18
19
|
|
|
@@ -20,7 +21,6 @@
|
|
|
20
21
|
resourceName: string;
|
|
21
22
|
rendering?: ResourceRendering | undefined;
|
|
22
23
|
id: string | number;
|
|
23
|
-
density?: 'compact' | 'comfortable';
|
|
24
24
|
layout?: 'list' | 'grid';
|
|
25
25
|
columns?: 1 | 2 | 3 | 4;
|
|
26
26
|
bordered?: boolean;
|
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
resourceName,
|
|
37
37
|
rendering,
|
|
38
38
|
id,
|
|
39
|
-
density = 'comfortable',
|
|
40
39
|
layout = 'list',
|
|
41
40
|
columns = 2,
|
|
42
41
|
bordered = false,
|
|
@@ -58,7 +57,6 @@
|
|
|
58
57
|
}
|
|
59
58
|
navigation.list(resourceName);
|
|
60
59
|
}
|
|
61
|
-
const isCompact = $derived(density === 'compact');
|
|
62
60
|
|
|
63
61
|
const detail = useRecordDetail(() => ({ resourceName, id, rendering }));
|
|
64
62
|
const activeRendering = $derived(detail.rendering);
|
|
@@ -76,13 +74,13 @@
|
|
|
76
74
|
});
|
|
77
75
|
</script>
|
|
78
76
|
|
|
79
|
-
<div data-svadmin-rendering-resource={activeRendering?.resource.name} data-svadmin-rendering-kind={activeRendering ? 'show' : undefined}
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
<div data-svadmin-rendering-resource={activeRendering?.resource.name} data-svadmin-rendering-kind={activeRendering ? 'show' : undefined} data-svadmin-crud-page="show" data-svadmin-resource={resourceName} data-svadmin-record-id={id} class="svadmin-u-3e7ce58d64fa {className}">
|
|
78
|
+
<div data-svadmin-crud-page-header>
|
|
79
|
+
<PageHeader
|
|
80
|
+
title="{resource.label} {i18n.t('common.detail')} #{id}"
|
|
81
|
+
onBack={goBack}
|
|
82
|
+
backLabel={i18n.t('common.back')}
|
|
83
|
+
>
|
|
86
84
|
{#snippet actions()}
|
|
87
85
|
{#if detail.canRead && query.isSuccess && resource.canEdit !== false}
|
|
88
86
|
<EditButton resource={resourceName} recordItemId={id} hideText />
|
|
@@ -102,13 +100,15 @@
|
|
|
102
100
|
{@render headerActions()}
|
|
103
101
|
{/if}
|
|
104
102
|
{/snippet}
|
|
105
|
-
|
|
103
|
+
</PageHeader>
|
|
104
|
+
</div>
|
|
106
105
|
|
|
106
|
+
<div data-svadmin-crud-page-content data-svadmin-detail-state={detail.checkingPermission || query.isLoading ? 'loading' : !detail.canRead ? 'forbidden' : query.isError ? 'error' : query.isSuccess ? 'ready' : 'empty'}>
|
|
107
107
|
{#if detail.checkingPermission || query.isLoading}
|
|
108
108
|
<Card.Root class="svadmin-u-2cd02d11d1af svadmin-u-6ee2d41e2d2d svadmin-u-438b2237b8d6">
|
|
109
109
|
<Card.Content class="svadmin-u-8a539c7fe216">
|
|
110
110
|
{#each showFields.slice(0, 6) as _, i (i)}
|
|
111
|
-
<div class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-020ba687fa12
|
|
111
|
+
<div class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-020ba687fa12 svadmin-u-f0faeb26d656 svadmin-u-7a9aabfcd059 svadmin-u-1b2d54a3fd12 svadmin-u-b7daff9b9ddd {i % 2 === 1 ? 'svadmin-u-967d113a1451' : ''}">
|
|
112
112
|
<Skeleton class="svadmin-u-11e59c6d5f6b svadmin-u-b7ce0d2f6c04 svadmin-u-5a14cc9f9ed4" />
|
|
113
113
|
<Skeleton class="svadmin-u-11e59c6d5f6b svadmin-u-1d9f2d98d149 svadmin-u-d2ffb24f3e1d svadmin-u-b6b02c0ebef6 svadmin-u-dfe9bd9e7001 svadmin-u-77533d414653" />
|
|
114
114
|
</div>
|
|
@@ -116,15 +116,11 @@
|
|
|
116
116
|
</Card.Content>
|
|
117
117
|
</Card.Root>
|
|
118
118
|
{:else if !detail.canRead}
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
<div role="alert">
|
|
122
|
-
<p>{i18n.t('common.operationFailed')}</p>
|
|
123
|
-
<Button type="button" variant="ghost" size="icon" title={i18n.t('common.retry')}
|
|
124
|
-
aria-label={i18n.t('common.retry')} disabled={query.isFetching} onclick={detail.refresh}>
|
|
125
|
-
<RefreshCw class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
126
|
-
</Button>
|
|
119
|
+
<div data-svadmin-access-denied>
|
|
120
|
+
<DataState state="forbidden" title={i18n.t('common.accessDenied')} />
|
|
127
121
|
</div>
|
|
122
|
+
{:else if query.isError}
|
|
123
|
+
<DataState state="error" title={i18n.t('common.operationFailed')} retry={detail.refresh} />
|
|
128
124
|
{:else if query.isSuccess}
|
|
129
125
|
{@const record = activeRendering ? activeRendering.record(query.data.data) : query.data.data}
|
|
130
126
|
{#if layout === 'grid'}
|
|
@@ -153,9 +149,9 @@
|
|
|
153
149
|
<Card.Content class="svadmin-u-8a539c7fe216 svadmin-u-fa6acbf81d74 svadmin-u-3533cbce9e93">
|
|
154
150
|
{#each showFields as field, i (i)}
|
|
155
151
|
{@const value = record[field.key]}
|
|
156
|
-
<div class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-020ba687fa12
|
|
157
|
-
<div class="svadmin-u-b03f3c253900
|
|
158
|
-
<div class="svadmin-u-6b3526212209
|
|
152
|
+
<div class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-020ba687fa12 svadmin-u-f0faeb26d656 svadmin-u-7a9aabfcd059 svadmin-u-1b2d54a3fd12 svadmin-u-4701b613ff95 {i % 2 === 1 ? 'svadmin-u-8a25a995eb8e' : ''}">
|
|
153
|
+
<div class="svadmin-u-b03f3c253900 svadmin-u-359090c2d529 svadmin-u-e2327d142859 svadmin-u-2689f3958069 svadmin-u-bfa603190748 svadmin-u-65281709dacf svadmin-u-84bdd56b0e81">{field.label}</div>
|
|
154
|
+
<div class="svadmin-u-6b3526212209 svadmin-u-fc7473ca09eb svadmin-u-d4108abe6359">
|
|
159
155
|
<FieldDisplay
|
|
160
156
|
type={field.type}
|
|
161
157
|
{value}
|
|
@@ -169,13 +165,12 @@
|
|
|
169
165
|
</Card.Root>
|
|
170
166
|
{/if}
|
|
171
167
|
{#if children}
|
|
172
|
-
|
|
168
|
+
<div data-svadmin-crud-page-extension>
|
|
169
|
+
{@render children()}
|
|
170
|
+
</div>
|
|
173
171
|
{/if}
|
|
174
172
|
{:else}
|
|
175
|
-
<
|
|
176
|
-
<Card.Content class="svadmin-u-845f53365c8d svadmin-u-ca6bf63030aa">
|
|
177
|
-
<p class="svadmin-u-bfa603190748">{i18n.t('common.noData')}</p>
|
|
178
|
-
</Card.Content>
|
|
179
|
-
</Card.Root>
|
|
173
|
+
<DataState state="empty" description={i18n.t('common.noData')} />
|
|
180
174
|
{/if}
|
|
175
|
+
</div>
|
|
181
176
|
</div>
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
{#snippet gridBody(state)}
|
|
27
27
|
<details><summary>{state.locale.startsWith('zh') ? '表格选择与列顺序' : 'Selection and column order'}</summary>{@render state.controls()}</details>
|
|
28
28
|
<SvarDataGrid {Grid} {Theme} items={state.records} columns={state.columns} primaryKey={state.primaryKey}
|
|
29
|
+
density={state.density}
|
|
29
30
|
{height} freezeLeft={Math.min(freezeLeft, state.columns.length - Math.min(freezeRight, state.columns.length - 1))}
|
|
30
|
-
freezeRight={Math.min(freezeRight, state.columns.length - 1)}
|
|
31
|
+
freezeRight={Math.min(freezeRight, state.columns.length - 1)}
|
|
31
32
|
queryMode="server" sorters={state.sorters} onSortChange={state.onSortChange}
|
|
32
33
|
disabled={state.disabled} scopeKey={state.scopeKey} locale={state.locale} label={state.label}>
|
|
33
34
|
{#snippet cellContent({ id, field })}{@render state.cell({ id, field })}{/snippet}
|
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
primaryKey?: string;
|
|
19
19
|
childrenKey?: string;
|
|
20
20
|
height?: number;
|
|
21
|
+
/** @deprecated Retained for compatibility; comfortable is the shared default. */
|
|
21
22
|
density?: 'compact' | 'comfortable';
|
|
23
|
+
rowHeight?: number;
|
|
24
|
+
headerHeight?: number;
|
|
22
25
|
freezeLeft?: number;
|
|
23
26
|
/** 独立右侧面板,不使用上游 split.right。 */
|
|
24
27
|
freezeRight?: number;
|
|
@@ -52,14 +55,15 @@
|
|
|
52
55
|
disabledLabel?: string;
|
|
53
56
|
fallbackLabel?: string;
|
|
54
57
|
}
|
|
55
|
-
let { Grid, Theme, items = [], columns, primaryKey = 'id', childrenKey, height = 420, density = 'comfortable', freezeLeft = 0, freezeRight = 0, queryMode = 'local',
|
|
58
|
+
let { Grid, Theme, items = [], columns, primaryKey = 'id', childrenKey, height = 420, density = 'comfortable', rowHeight: rowHeightProp, headerHeight: headerHeightProp, freezeLeft = 0, freezeRight = 0, queryMode = 'local',
|
|
56
59
|
sorters = [], filters = [], onSortChange, onFilterChange, onCellEdit, selectable = false, selectedIds = [], onSelectionChange, onRecordsChange, cellContent,
|
|
57
60
|
windowSource, loadChildren, hasChildrenKey = 'hasChildren', onLoadMore, hasMore = false, scopeKey = 0,
|
|
58
61
|
loading = false, error, disabled = false, locale = 'en-US', label = 'Data grid', emptyLabel = 'No records', loadingLabel = 'Loading',
|
|
59
62
|
disabledLabel = 'Grid is disabled', fallbackLabel = 'Static preview (up to 20 rows)',
|
|
60
63
|
}: SvarDataGridProps = $props();
|
|
61
64
|
const treeKey = $derived(loadChildren ? childrenKey ?? 'children' : childrenKey);
|
|
62
|
-
const rowHeight = $derived(density === 'compact' ? 32 : 44);
|
|
65
|
+
const rowHeight = $derived(rowHeightProp ?? (density === 'compact' ? 32 : 44));
|
|
66
|
+
const headerHeight = $derived(headerHeightProp ?? (density === 'compact' ? 32 : 36));
|
|
63
67
|
// 普通追加不重建引擎,否则无限加载会把滚动位置重置。懒树根替换必须清理分支缓存。
|
|
64
68
|
const dataOwner = $derived({ scopeKey, items: loadChildren ? items : undefined, source: windowSource,
|
|
65
69
|
loadChildren, onLoadMore, primaryKey, treeKey, hasChildrenKey, freezeRight, Grid });
|
|
@@ -304,12 +308,12 @@
|
|
|
304
308
|
{#key dataOwner}<div class="grid-panes" inert={disabled}>
|
|
305
309
|
<div class="grid-viewport center-pane" data-svar-pane="center" style:height="{height}px" role="region" aria-label="Scrollable columns" onkeydowncapture={event => navigatePane(event, 0)}>
|
|
306
310
|
<Grid columns={model.center} data={activeError || isLoading ? [] : records.rows} tree={treeKey !== undefined}
|
|
307
|
-
split={{ left: freezeLeft }} sizes={{ rowHeight, headerHeight
|
|
311
|
+
split={{ left: freezeLeft }} sizes={{ rowHeight, headerHeight, columnWidth: 160 }} select={selectable} multiselect={selectable} {selectedRows}
|
|
308
312
|
reorder={false} draggableRows={false} undo={false} {...queryProps} {...dynamicProps} init={api => init(api, 0)} />
|
|
309
313
|
</div>
|
|
310
314
|
{#if freezeRight > 0}<div class="grid-viewport right-pane" data-svar-pane="right" style:height="{height}px" style:width="{model.rightWidth}px" role="region" aria-label="Frozen right columns" onkeydowncapture={event => navigatePane(event, 1)}>
|
|
311
315
|
<Grid columns={model.right} data={activeError || isLoading ? [] : rightRows} tree={treeKey !== undefined}
|
|
312
|
-
split={{ left: 0 }} sizes={{ rowHeight, headerHeight
|
|
316
|
+
split={{ left: 0 }} sizes={{ rowHeight, headerHeight, columnWidth: 160 }} select={selectable} multiselect={selectable} {selectedRows}
|
|
313
317
|
reorder={false} draggableRows={false} undo={false} {...queryProps} {...dynamicProps} init={api => init(api, 1)} />
|
|
314
318
|
</div>{/if}
|
|
315
319
|
</div>{/key}
|
|
@@ -16,7 +16,10 @@ export interface SvarDataGridProps {
|
|
|
16
16
|
primaryKey?: string;
|
|
17
17
|
childrenKey?: string;
|
|
18
18
|
height?: number;
|
|
19
|
+
/** @deprecated Retained for compatibility; comfortable is the shared default. */
|
|
19
20
|
density?: 'compact' | 'comfortable';
|
|
21
|
+
rowHeight?: number;
|
|
22
|
+
headerHeight?: number;
|
|
20
23
|
freezeLeft?: number;
|
|
21
24
|
/** 独立右侧面板,不使用上游 split.right。 */
|
|
22
25
|
freezeRight?: number;
|
|
@@ -16,7 +16,6 @@ export interface SvarResourceTableProps {
|
|
|
16
16
|
/** 高级读取仍使用资源契约、Provider 和核心查询缓存。 */
|
|
17
17
|
loadingMode?: 'page' | 'window' | 'infinite';
|
|
18
18
|
lazyTree?: SvarLazyTree;
|
|
19
|
-
density?: 'compact' | 'comfortable';
|
|
20
19
|
childrenKey?: string;
|
|
21
20
|
dataScopeKey?: string | number;
|
|
22
21
|
disabled?: boolean;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import type { SvarResourceTableProps } from './SvarResourceTable.svelte';
|
|
16
16
|
|
|
17
17
|
let { Grid, Theme, resourceName, pageSize = 25, filters = [], initialSorters = [], height = 420,
|
|
18
|
-
freezeLeft = 0, freezeRight = 0, loadingMode = 'page', lazyTree,
|
|
18
|
+
freezeLeft = 0, freezeRight = 0, loadingMode = 'page', lazyTree, childrenKey: providedChildrenKey, dataScopeKey = 0, disabled = false,
|
|
19
19
|
editable = false, selectable = false, batchUpdate = false, batchDelete = false, exportable = false,
|
|
20
20
|
savedViews = false, preferenceScopeKey, migrateAutoTableViews = false, deleteVariables, formats = {},
|
|
21
21
|
}: SvarResourceTableProps = $props();
|
|
@@ -333,7 +333,7 @@
|
|
|
333
333
|
</div>{/if}
|
|
334
334
|
{#if feedback}<p role="alert">{feedback}</p>{/if}
|
|
335
335
|
{#if result}<p role="status">{zh ? '成功 / 失败 / 未执行' : 'Succeeded / failed / skipped'}: {result.succeeded.length} / {result.failed.length} / {result.skipped.length}</p>{/if}
|
|
336
|
-
<SvarDataGrid {Grid} {Theme} items={gridItems} {columns} {primaryKey} {height} freezeRight={Math.min(freezeRight, Math.max(0, columns.length - 1))} freezeLeft={Math.min(freezeLeft, columns.length - Math.min(freezeRight, Math.max(0, columns.length - 1)))}
|
|
336
|
+
<SvarDataGrid {Grid} {Theme} items={gridItems} {columns} {primaryKey} {height} freezeRight={Math.min(freezeRight, Math.max(0, columns.length - 1))} freezeLeft={Math.min(freezeLeft, columns.length - Math.min(freezeRight, Math.max(0, columns.length - 1)))}
|
|
337
337
|
queryMode="server" {sorters} filters={textFilters} onSortChange={changeSort} onFilterChange={changeFilter}
|
|
338
338
|
selectable={selectable || batchUpdate || batchDelete} {selectedIds} onSelectionChange={selection} {...advanced ? { onRecordsChange: loaded } : {}} scopeKey={gridScope}
|
|
339
339
|
{...windowSource ? { windowSource } : {}}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { Input } from '../ui/input/index.js';
|
|
21
21
|
import DataState from '../content/DataState.svelte';
|
|
22
22
|
import FeedbackNotice from '../content/FeedbackNotice.svelte';
|
|
23
|
-
import { Copy, Send } from '@lucide/svelte';
|
|
23
|
+
import { Copy, Send, X } from '@lucide/svelte';
|
|
24
24
|
|
|
25
25
|
let { provider }: { provider?: MemberDirectoryProvider | undefined } = $props();
|
|
26
26
|
const context = captureAdminContext();
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
const controlId = $props.id();
|
|
30
30
|
let rows = $state<DirectoryMember[]>([]);
|
|
31
31
|
let query = $state('');
|
|
32
|
+
let searchInput = $state<HTMLInputElement | null>(null);
|
|
32
33
|
let email = $state('');
|
|
33
34
|
let loading = $state(false);
|
|
34
35
|
let sending = $state(false);
|
|
@@ -37,6 +38,10 @@
|
|
|
37
38
|
let status = $state('');
|
|
38
39
|
let epoch = 0;
|
|
39
40
|
const filtered = $derived(rows.filter(row => `${row.name} ${row.email}`.toLowerCase().includes(query.toLowerCase())));
|
|
41
|
+
function clearSearch(): void {
|
|
42
|
+
query = '';
|
|
43
|
+
searchInput?.focus();
|
|
44
|
+
}
|
|
40
45
|
async function load() {
|
|
41
46
|
const current = provider;
|
|
42
47
|
const request = ++epoch;
|
|
@@ -117,10 +122,18 @@
|
|
|
117
122
|
{:else}
|
|
118
123
|
<div class="w-full max-w-sm space-y-2">
|
|
119
124
|
<label for={`${controlId}-search`} class="block text-sm font-medium text-foreground">{isZh ? '搜索成员' : 'Search members'}</label>
|
|
120
|
-
<Input id={`${controlId}-search`} type="search" bind:value={query} />
|
|
125
|
+
<Input id={`${controlId}-search`} type="search" bind:value={query} bind:ref={searchInput} />
|
|
121
126
|
</div>
|
|
122
|
-
{#if
|
|
123
|
-
<DataState state="empty" title={isZh ? '
|
|
127
|
+
{#if rows.length === 0}
|
|
128
|
+
<DataState state="empty" title={isZh ? '暂无成员' : 'No members yet'} />
|
|
129
|
+
{:else if filtered.length === 0}
|
|
130
|
+
<DataState state="filtered-empty" title={isZh ? '没有匹配的成员' : 'No matching members'} description={isZh ? '请清除或调整搜索条件。' : 'Clear or adjust the search.'}>
|
|
131
|
+
{#snippet action()}
|
|
132
|
+
<Button type="button" variant="outline" size="sm" onclick={clearSearch}>
|
|
133
|
+
<X size={16} aria-hidden="true" />{isZh ? '清除搜索' : 'Clear search'}
|
|
134
|
+
</Button>
|
|
135
|
+
{/snippet}
|
|
136
|
+
</DataState>
|
|
124
137
|
{:else}
|
|
125
138
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex (横向滚动区域需支持键盘聚焦与滚动) -->
|
|
126
139
|
<div class="member-directory-scroll w-full min-w-0 overflow-x-auto" role="region" aria-label={isZh ? '成员目录' : 'Member directory'} tabindex="0">
|
|
@@ -23,5 +23,12 @@
|
|
|
23
23
|
<ContentPageHeader title={i18n.t('account.securityLog')} description={i18n.t('account.securityLogDescription')} {...definedOptions({ "actions": undefined })} />
|
|
24
24
|
<div class="svadmin-u-60fbb7713999 svadmin-u-77c08e015d14"><Button variant="outline" size="sm" onclick={() => downloadRows('sample-security-events.json', filtered)}><Download class="svadmin-u-783b0d9d1e2c" />{i18n.t('common.export')}</Button></div>
|
|
25
25
|
<FilterToolbar bind:query placeholder={i18n.t('common.search')} />
|
|
26
|
-
<SecurityEventTable
|
|
26
|
+
<SecurityEventTable
|
|
27
|
+
events={filtered}
|
|
28
|
+
{...definedOptions({
|
|
29
|
+
state: query.trim() && filtered.length === 0 ? 'filtered-empty' : undefined,
|
|
30
|
+
stateTitle: query.trim() && filtered.length === 0 ? (i18n.locale === 'zh-CN' ? '没有匹配的安全事件' : 'No matching security events') : undefined,
|
|
31
|
+
stateDescription: query.trim() && filtered.length === 0 ? (i18n.locale === 'zh-CN' ? '请清除或调整搜索条件。' : 'Clear or adjust the search.') : undefined,
|
|
32
|
+
})}
|
|
33
|
+
/>
|
|
27
34
|
</ContentPageShell>
|
|
@@ -11,6 +11,7 @@ export interface AutoTableGridState {
|
|
|
11
11
|
}[];
|
|
12
12
|
readonly primaryKey: string;
|
|
13
13
|
readonly sorters: readonly Sort[];
|
|
14
|
+
/** @deprecated Retained for custom grid renderers during the baseline transition. */
|
|
14
15
|
readonly density: 'compact' | 'comfortable';
|
|
15
16
|
readonly locale: string;
|
|
16
17
|
readonly label: string;
|
|
@@ -14,14 +14,24 @@
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<script lang="ts">
|
|
17
|
+
import type { Snippet } from 'svelte';
|
|
18
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
17
19
|
import { cn } from '../../utils.js';
|
|
18
20
|
import StatusBadge from './StatusBadge.svelte';
|
|
19
21
|
import DataState from './DataState.svelte';
|
|
22
|
+
import type { DataStateKind } from './DataState.svelte';
|
|
20
23
|
|
|
21
24
|
interface Props {
|
|
22
25
|
items?: TimelineItem[];
|
|
23
26
|
emptyTitle?: string;
|
|
24
27
|
emptyDescription?: string;
|
|
28
|
+
state?: DataStateKind;
|
|
29
|
+
stateTitle?: string;
|
|
30
|
+
stateDescription?: string;
|
|
31
|
+
retry?: () => void;
|
|
32
|
+
retryLabel?: string;
|
|
33
|
+
loadingLabel?: string;
|
|
34
|
+
action?: Snippet;
|
|
25
35
|
class?: string;
|
|
26
36
|
}
|
|
27
37
|
|
|
@@ -29,6 +39,13 @@
|
|
|
29
39
|
items = [],
|
|
30
40
|
emptyTitle = 'No timeline events',
|
|
31
41
|
emptyDescription = 'Activity events and status transitions will appear here.',
|
|
42
|
+
state,
|
|
43
|
+
stateTitle,
|
|
44
|
+
stateDescription,
|
|
45
|
+
retry,
|
|
46
|
+
retryLabel,
|
|
47
|
+
loadingLabel,
|
|
48
|
+
action,
|
|
32
49
|
class: className = '',
|
|
33
50
|
}: Props = $props();
|
|
34
51
|
|
|
@@ -39,13 +56,24 @@
|
|
|
39
56
|
info: 'svadmin-u-75b1bec3ea0e svadmin-u-05f954a846d6 svadmin-u-20aaf08a7ed1',
|
|
40
57
|
neutral: 'svadmin-u-43ed7af3fc8a svadmin-u-18049387f0af svadmin-u-bfa603190748',
|
|
41
58
|
};
|
|
59
|
+
const resolvedState = $derived(state ?? (items.length === 0 ? 'empty' : undefined));
|
|
42
60
|
</script>
|
|
43
61
|
|
|
44
|
-
<div class={cn('svadmin-u-3e7ce58d64fa', className)}>
|
|
45
|
-
{#if
|
|
46
|
-
<DataState
|
|
62
|
+
<div class={cn('svadmin-u-3e7ce58d64fa', className)} data-svadmin-audit-timeline data-svadmin-audit-state={resolvedState ?? 'ready'}>
|
|
63
|
+
{#if resolvedState}
|
|
64
|
+
<DataState
|
|
65
|
+
state={resolvedState}
|
|
66
|
+
{...definedOptions({
|
|
67
|
+
title: stateTitle ?? (resolvedState === 'empty' ? emptyTitle : undefined),
|
|
68
|
+
description: stateDescription ?? (resolvedState === 'empty' ? emptyDescription : undefined),
|
|
69
|
+
retry,
|
|
70
|
+
retryLabel,
|
|
71
|
+
loadingLabel,
|
|
72
|
+
action,
|
|
73
|
+
})}
|
|
74
|
+
/>
|
|
47
75
|
{:else}
|
|
48
|
-
<div class="svadmin-u-d89972fe17d6 svadmin-u-9079b62ef1cb svadmin-u-b3542e058833 svadmin-u-884dada40b17 svadmin-u-699f6fc4b3e0 svadmin-u-37f34e63818d svadmin-u-f76c7493f919 svadmin-u-8f848a57f066 svadmin-u-3242dfaf010c">
|
|
76
|
+
<div class="svadmin-u-d89972fe17d6 svadmin-u-9079b62ef1cb svadmin-u-b3542e058833 svadmin-u-884dada40b17 svadmin-u-699f6fc4b3e0 svadmin-u-37f34e63818d svadmin-u-f76c7493f919 svadmin-u-8f848a57f066 svadmin-u-3242dfaf010c" data-svadmin-audit-events>
|
|
49
77
|
{#each items as item (item.id)}
|
|
50
78
|
{@const statusTone = item.status ?? 'info'}
|
|
51
79
|
<div class="svadmin-u-d89972fe17d6 svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-44ee8ba0a421 svadmin-u-fc7473ca09eb group">
|
|
@@ -9,10 +9,19 @@ export interface TimelineItem {
|
|
|
9
9
|
tag?: string;
|
|
10
10
|
meta?: Record<string, unknown>;
|
|
11
11
|
}
|
|
12
|
+
import type { Snippet } from 'svelte';
|
|
13
|
+
import type { DataStateKind } from './DataState.svelte';
|
|
12
14
|
interface Props {
|
|
13
15
|
items?: TimelineItem[];
|
|
14
16
|
emptyTitle?: string;
|
|
15
17
|
emptyDescription?: string;
|
|
18
|
+
state?: DataStateKind;
|
|
19
|
+
stateTitle?: string;
|
|
20
|
+
stateDescription?: string;
|
|
21
|
+
retry?: () => void;
|
|
22
|
+
retryLabel?: string;
|
|
23
|
+
loadingLabel?: string;
|
|
24
|
+
action?: Snippet;
|
|
16
25
|
class?: string;
|
|
17
26
|
}
|
|
18
27
|
declare const AuditTimeline: import("svelte").Component<Props, {}, "">;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
description?: string;
|
|
9
9
|
breadcrumbs?: string[];
|
|
10
10
|
actions?: Snippet;
|
|
11
|
+
/** @deprecated Accepted for compatibility; the product baseline is shared. */
|
|
11
12
|
density?: 'compact' | 'comfortable';
|
|
12
13
|
headingLevel?: 'h1' | 'h2';
|
|
13
14
|
class?: string;
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
headingLevel = 'h1',
|
|
23
24
|
class: className = '',
|
|
24
25
|
}: Props = $props();
|
|
25
|
-
const styles = $derived(contentHeader(
|
|
26
|
+
const styles = $derived(contentHeader());
|
|
26
27
|
</script>
|
|
27
28
|
|
|
28
29
|
<header
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
children: Snippet;
|
|
13
13
|
pageId?: string;
|
|
14
14
|
width?: 'narrow' | 'default' | 'wide';
|
|
15
|
+
/** @deprecated Accepted for compatibility; the product baseline is shared. */
|
|
15
16
|
density?: 'compact' | 'comfortable';
|
|
16
17
|
class?: string;
|
|
17
18
|
}
|
|
@@ -32,7 +33,6 @@
|
|
|
32
33
|
const resolvedWidth = $derived(width === 'narrow' || width === 'wide' ? width : 'default');
|
|
33
34
|
const styles = $derived(contentPage({
|
|
34
35
|
width: resolvedWidth,
|
|
35
|
-
density,
|
|
36
36
|
}));
|
|
37
37
|
</script>
|
|
38
38
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Panel heading. Omit it for an untitled surface. */
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
actions?: Snippet;
|
|
10
|
+
children: Snippet;
|
|
11
|
+
footer?: Snippet;
|
|
12
|
+
/** `none` removes body padding so a table or list can meet the panel edge. */
|
|
13
|
+
padding?: 'default' | 'compact' | 'none';
|
|
14
|
+
headingLevel?: 'h2' | 'h3';
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
title,
|
|
20
|
+
description,
|
|
21
|
+
actions,
|
|
22
|
+
children,
|
|
23
|
+
footer,
|
|
24
|
+
padding = 'default',
|
|
25
|
+
headingLevel = 'h2',
|
|
26
|
+
class: className = '',
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
const uid = $props.id();
|
|
30
|
+
const headingId = `svadmin-data-panel-${uid}`;
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<section
|
|
34
|
+
class={cn('svadmin-data-panel', className)}
|
|
35
|
+
data-svadmin-data-panel
|
|
36
|
+
data-padding={padding}
|
|
37
|
+
aria-labelledby={title ? headingId : undefined}
|
|
38
|
+
>
|
|
39
|
+
{#if title}
|
|
40
|
+
<header class="svadmin-data-panel__header" data-slot="data-panel-header">
|
|
41
|
+
<div class="svadmin-data-panel__heading">
|
|
42
|
+
<svelte:element this={headingLevel} id={headingId} class="svadmin-data-panel__title">
|
|
43
|
+
{title}
|
|
44
|
+
</svelte:element>
|
|
45
|
+
{#if description}
|
|
46
|
+
<p class="svadmin-data-panel__description">{description}</p>
|
|
47
|
+
{/if}
|
|
48
|
+
</div>
|
|
49
|
+
{#if actions}
|
|
50
|
+
<div class="svadmin-data-panel__actions">{@render actions()}</div>
|
|
51
|
+
{/if}
|
|
52
|
+
</header>
|
|
53
|
+
{/if}
|
|
54
|
+
<div class="svadmin-data-panel__body" data-slot="data-panel-body">{@render children()}</div>
|
|
55
|
+
{#if footer}
|
|
56
|
+
<footer class="svadmin-data-panel__footer" data-slot="data-panel-footer">{@render footer()}</footer>
|
|
57
|
+
{/if}
|
|
58
|
+
</section>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Panel heading. Omit it for an untitled surface. */
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
actions?: Snippet;
|
|
7
|
+
children: Snippet;
|
|
8
|
+
footer?: Snippet;
|
|
9
|
+
/** `none` removes body padding so a table or list can meet the panel edge. */
|
|
10
|
+
padding?: 'default' | 'compact' | 'none';
|
|
11
|
+
headingLevel?: 'h2' | 'h3';
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
declare const DataPanel: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type DataPanel = ReturnType<typeof DataPanel>;
|
|
16
|
+
export default DataPanel;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Button } from '../ui/button/index.js';
|
|
6
6
|
import * as Alert from '../ui/alert/index.js';
|
|
7
7
|
import { Skeleton } from '../ui/skeleton/index.js';
|
|
8
|
-
export type DataStateKind = 'loading' | 'empty' | 'error' | 'forbidden';
|
|
8
|
+
export type DataStateKind = 'loading' | 'empty' | 'filtered-empty' | 'error' | 'forbidden';
|
|
9
9
|
interface Props {
|
|
10
10
|
state: DataStateKind;
|
|
11
11
|
title?: string;
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
const defaults = $derived({
|
|
32
32
|
loading: [i18n.t('common.loading'), i18n.t('common.loading')],
|
|
33
33
|
empty: [i18n.t('empty.title'), i18n.t('empty.description')],
|
|
34
|
+
'filtered-empty': [isZh ? '没有匹配结果' : 'No matching results', isZh ? '请调整或清除筛选条件。' : 'Adjust or clear your filters.'],
|
|
34
35
|
error: [i18n.t('common.error'), i18n.t('common.retry')],
|
|
35
36
|
forbidden: [isZh ? '无权访问' : 'Access restricted', isZh ? '你没有查看此内容的权限。' : 'You do not have permission to view this content.'],
|
|
36
37
|
} as const);
|
|
@@ -41,11 +42,11 @@
|
|
|
41
42
|
</script>
|
|
42
43
|
|
|
43
44
|
{#if state === 'loading'}
|
|
44
|
-
<div class={'svadmin-u-6ed543e2fbbb svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 svadmin-u-c07e54fd1439 ' + className} role="status" aria-live="polite" aria-label={resolvedLoadingLabel} aria-busy="true">
|
|
45
|
+
<div data-svadmin-data-state={state} class={'svadmin-u-6ed543e2fbbb svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 svadmin-u-c07e54fd1439 ' + className} role="status" aria-live="polite" aria-label={resolvedLoadingLabel} aria-busy="true">
|
|
45
46
|
<Skeleton class="svadmin-u-cd0d9c512cdc svadmin-u-84789e8a20cd" /><Skeleton class="svadmin-u-11e59c6d5f6b svadmin-u-6da6a3c3f741 svadmin-u-9794ab45094d" /><Skeleton class="svadmin-u-0a769880db93 svadmin-u-6da6a3c3f741" />
|
|
46
47
|
</div>
|
|
47
48
|
{:else if state === 'error' || state === 'forbidden'}
|
|
48
|
-
<Alert.Root variant={state === 'error' ? 'destructive' : 'warning'} class={className}>
|
|
49
|
+
<Alert.Root data-svadmin-data-state={state} variant={state === 'error' ? 'destructive' : 'warning'} class={className}>
|
|
49
50
|
{#if state === 'error'}<AlertTriangle class="svadmin-u-f7b5fa971871" aria-hidden="true" />{:else}<LockKeyhole class="svadmin-u-f7b5fa971871" aria-hidden="true" />{/if}
|
|
50
51
|
<Alert.Title>{resolvedTitle}</Alert.Title>
|
|
51
52
|
<Alert.Description>{resolvedDescription}</Alert.Description>
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
{#if action}<div class="svadmin-u-eccd13ef4f2f">{@render action()}</div>{/if}
|
|
54
55
|
</Alert.Root>
|
|
55
56
|
{:else}
|
|
56
|
-
<div class={'empty-state ' + className} role="status" aria-live="polite">
|
|
57
|
+
<div data-svadmin-data-state={state} class={'empty-state ' + className} role="status" aria-live="polite">
|
|
57
58
|
<span class="empty-state__icon" aria-hidden="true">
|
|
58
59
|
<Inbox size={16} />
|
|
59
60
|
</span>
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
columns?: 1 | 2 | 3 | 4;
|
|
15
15
|
layout?: 'vertical' | 'horizontal';
|
|
16
16
|
bordered?: boolean;
|
|
17
|
-
density?: 'compact' | 'comfortable';
|
|
18
17
|
class?: string;
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -23,11 +22,9 @@
|
|
|
23
22
|
columns = 1,
|
|
24
23
|
layout = 'vertical',
|
|
25
24
|
bordered = false,
|
|
26
|
-
density = 'comfortable',
|
|
27
25
|
class: className = '',
|
|
28
26
|
}: Props = $props();
|
|
29
27
|
|
|
30
|
-
const isCompact = $derived(density === 'compact');
|
|
31
28
|
const isHorizontal = $derived(layout === 'horizontal');
|
|
32
29
|
|
|
33
30
|
function spanClass(span: number | undefined): string {
|
|
@@ -65,7 +62,7 @@
|
|
|
65
62
|
<dt
|
|
66
63
|
class={cn(
|
|
67
64
|
'svadmin-u-2859c861d7de svadmin-u-2689f3958069 svadmin-u-bfa603190748 svadmin-u-012fbd121f37 svadmin-u-65fdbade2025 svadmin-u-a968a4123359 svadmin-u-27165ed73e86 svadmin-u-cdf6e054e5e0',
|
|
68
|
-
|
|
65
|
+
'svadmin-u-f0faeb26d656 svadmin-u-e7ee55ac7ffe svadmin-u-359090c2d529 svadmin-u-e2327d142859',
|
|
69
66
|
isHorizontal ? 'svadmin-u-b03f3c253900' : '',
|
|
70
67
|
)}
|
|
71
68
|
>
|
|
@@ -74,7 +71,7 @@
|
|
|
74
71
|
<dd
|
|
75
72
|
class={cn(
|
|
76
73
|
'svadmin-u-170cee3ff4e4 svadmin-u-2689f3958069 svadmin-u-d4108abe6359 svadmin-u-cd0ad9a56558 svadmin-u-36e579c0b41c svadmin-u-7e0b7cdf1a94',
|
|
77
|
-
|
|
74
|
+
'svadmin-u-f0faeb26d656 svadmin-u-e7ee55ac7ffe svadmin-u-359090c2d529 svadmin-u-e2327d142859',
|
|
78
75
|
)}
|
|
79
76
|
>
|
|
80
77
|
{#if item.href}
|
|
@@ -95,7 +92,7 @@
|
|
|
95
92
|
class={cn(
|
|
96
93
|
'svadmin-u-f3c543ad5fe9',
|
|
97
94
|
columnClass,
|
|
98
|
-
|
|
95
|
+
'svadmin-u-706c16d853e6 svadmin-u-375f36f67650',
|
|
99
96
|
className,
|
|
100
97
|
)}
|
|
101
98
|
>
|
|
@@ -110,7 +107,7 @@
|
|
|
110
107
|
<dt
|
|
111
108
|
class={cn(
|
|
112
109
|
'svadmin-u-2689f3958069 svadmin-u-bfa603190748',
|
|
113
|
-
|
|
110
|
+
'svadmin-u-359090c2d529',
|
|
114
111
|
isHorizontal ? 'svadmin-u-012fbd121f37 svadmin-u-2e7a6d18a20d' : '',
|
|
115
112
|
)}
|
|
116
113
|
>
|
|
@@ -119,7 +116,7 @@
|
|
|
119
116
|
<dd
|
|
120
117
|
class={cn(
|
|
121
118
|
'svadmin-u-170cee3ff4e4 svadmin-u-2689f3958069 svadmin-u-d4108abe6359 svadmin-u-7e0b7cdf1a94 svadmin-u-36e579c0b41c',
|
|
122
|
-
isHorizontal ? 'svadmin-u-359090c2d529 svadmin-u-e2327d142859' :
|
|
119
|
+
isHorizontal ? 'svadmin-u-359090c2d529 svadmin-u-e2327d142859' : 'svadmin-u-b6b02c0ebef6 svadmin-u-fc7473ca09eb',
|
|
123
120
|
)}
|
|
124
121
|
>
|
|
125
122
|
{#if item.href}
|