@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
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import PageHeader from './PageHeader.svelte';
|
|
10
10
|
import AutoTable from './AutoTable.svelte';
|
|
11
11
|
import type { BatchSelection } from './table-contract';
|
|
12
|
+
import type { SavedListViewProvider } from './saved-list-views.js';
|
|
12
13
|
import CreateButton from './buttons/CreateButton.svelte';
|
|
13
14
|
|
|
14
15
|
interface Props {
|
|
@@ -16,10 +17,10 @@
|
|
|
16
17
|
rendering?: ResourceRendering | undefined;
|
|
17
18
|
title?: string;
|
|
18
19
|
canCreate?: boolean;
|
|
19
|
-
density?: 'compact' | 'comfortable';
|
|
20
20
|
/** Passthrough: enable row selection checkboxes */
|
|
21
21
|
selectable?: boolean;
|
|
22
22
|
allowSelectAllMatching?: boolean;
|
|
23
|
+
savedViewProvider?: SavedListViewProvider;
|
|
23
24
|
/** Passthrough: custom header actions */
|
|
24
25
|
headerActions?: Snippet;
|
|
25
26
|
/** Optional status tabs slot above filter/table */
|
|
@@ -44,9 +45,9 @@
|
|
|
44
45
|
rendering,
|
|
45
46
|
title,
|
|
46
47
|
canCreate,
|
|
47
|
-
density = 'comfortable',
|
|
48
48
|
selectable,
|
|
49
49
|
allowSelectAllMatching,
|
|
50
|
+
savedViewProvider,
|
|
50
51
|
headerActions,
|
|
51
52
|
statusTabs,
|
|
52
53
|
filterToolbar,
|
|
@@ -62,8 +63,8 @@
|
|
|
62
63
|
const showCreate = $derived(canCreate ?? resource.canCreate !== false);
|
|
63
64
|
</script>
|
|
64
65
|
|
|
65
|
-
<div class="
|
|
66
|
-
<PageHeader title={pageTitle}
|
|
66
|
+
<div class="svadmin-u-3e7ce58d64fa {className}" data-svadmin-list-page={resourceName}>
|
|
67
|
+
<PageHeader title={pageTitle}>
|
|
67
68
|
{#snippet actions()}
|
|
68
69
|
{#if showCreate}
|
|
69
70
|
<CreateButton resource={resourceName} />
|
|
@@ -75,27 +76,29 @@
|
|
|
75
76
|
</PageHeader>
|
|
76
77
|
|
|
77
78
|
{#if statusTabs}
|
|
78
|
-
<div>
|
|
79
|
+
<div data-svadmin-list-page-status-tabs>
|
|
79
80
|
{@render statusTabs()}
|
|
80
81
|
</div>
|
|
81
82
|
{/if}
|
|
82
83
|
|
|
83
84
|
{#if filterToolbar}
|
|
84
|
-
<div>
|
|
85
|
+
<div data-svadmin-list-page-filter-toolbar>
|
|
85
86
|
{@render filterToolbar()}
|
|
86
87
|
</div>
|
|
87
88
|
{/if}
|
|
88
89
|
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
<div data-svadmin-list-page-table>
|
|
91
|
+
<AutoTable
|
|
92
|
+
{rendering}
|
|
93
|
+
{resourceName}
|
|
94
|
+
showHeader={false}
|
|
95
|
+
{...definedOptions({ "selectable": selectable })}
|
|
96
|
+
{...definedOptions({ allowSelectAllMatching })}
|
|
97
|
+
{...definedOptions({ savedViewProvider })}
|
|
98
|
+
{...definedOptions({ batchActions })}
|
|
99
|
+
{...definedOptions({ "defaultCellRenderer": cellRenderer })}
|
|
100
|
+
{...definedOptions({ "rowActions": rowActions })}
|
|
101
|
+
{...definedOptions({ emptyState, expandedRowRender })}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
101
104
|
</div>
|
|
@@ -2,15 +2,16 @@ import type { ResourceRendering } from '../rendering/index.js';
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { FieldDefinition } from '@svadmin/core';
|
|
4
4
|
import type { BatchSelection } from './table-contract';
|
|
5
|
+
import type { SavedListViewProvider } from './saved-list-views.js';
|
|
5
6
|
interface Props {
|
|
6
7
|
resourceName: string;
|
|
7
8
|
rendering?: ResourceRendering | undefined;
|
|
8
9
|
title?: string;
|
|
9
10
|
canCreate?: boolean;
|
|
10
|
-
density?: 'compact' | 'comfortable';
|
|
11
11
|
/** Passthrough: enable row selection checkboxes */
|
|
12
12
|
selectable?: boolean;
|
|
13
13
|
allowSelectAllMatching?: boolean;
|
|
14
|
+
savedViewProvider?: SavedListViewProvider;
|
|
14
15
|
/** Passthrough: custom header actions */
|
|
15
16
|
headerActions?: Snippet;
|
|
16
17
|
/** Optional status tabs slot above filter/table */
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
defaultIdentifier = '',
|
|
26
26
|
defaultPassword = '',
|
|
27
27
|
loginHint,
|
|
28
|
+
identifierLabel,
|
|
29
|
+
identifierPlaceholder,
|
|
30
|
+
passwordLabel,
|
|
31
|
+
loginButtonLabel,
|
|
28
32
|
} = $props<{
|
|
29
33
|
title?: string;
|
|
30
34
|
onSuccess?: () => void;
|
|
@@ -32,6 +36,10 @@
|
|
|
32
36
|
defaultIdentifier?: string;
|
|
33
37
|
defaultPassword?: string;
|
|
34
38
|
loginHint?: string;
|
|
39
|
+
identifierLabel?: string;
|
|
40
|
+
identifierPlaceholder?: string;
|
|
41
|
+
passwordLabel?: string;
|
|
42
|
+
loginButtonLabel?: string;
|
|
35
43
|
}>();
|
|
36
44
|
|
|
37
45
|
const adminContext = captureAdminContext();
|
|
@@ -72,6 +80,7 @@
|
|
|
72
80
|
{/if}
|
|
73
81
|
{/snippet}
|
|
74
82
|
|
|
83
|
+
<div data-svadmin-login-page>
|
|
75
84
|
<AuthPageShell brand={title} title={i18n.t('auth.welcomeBack')} description={i18n.t('auth.welcomeMessage')} {footer}>
|
|
76
85
|
{#if loginHint || defaultIdentifier || defaultPassword}
|
|
77
86
|
<aside data-svadmin-demo-credentials class="svadmin-u-fb88ccaacf5b svadmin-u-421ac2be5045 svadmin-u-ca6bcd4b6f3f svadmin-u-18049387f0af svadmin-u-60ed9c3ef326 svadmin-u-eb6e8b881acd">
|
|
@@ -95,9 +104,9 @@
|
|
|
95
104
|
</aside>
|
|
96
105
|
{/if}
|
|
97
106
|
|
|
98
|
-
<form onsubmit={handleSubmit} class="svadmin-u-b43b4c086d9a">
|
|
107
|
+
<form onsubmit={handleSubmit} class="svadmin-u-b43b4c086d9a" data-svadmin-login-form>
|
|
99
108
|
{#if error}
|
|
100
|
-
<Alert.Root variant="destructive">
|
|
109
|
+
<Alert.Root variant="destructive" id="login-error" data-svadmin-login-error aria-live="assertive">
|
|
101
110
|
<AlertCircle class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
102
111
|
<Alert.Description class="svadmin-u-fc7473ca09eb svadmin-u-2689f3958069">{error}</Alert.Description>
|
|
103
112
|
</Alert.Root>
|
|
@@ -105,17 +114,20 @@
|
|
|
105
114
|
|
|
106
115
|
<div class="svadmin-u-6f7e013d6499">
|
|
107
116
|
<Label for="login-identifier">
|
|
108
|
-
{i18n.t('auth.usernameOrEmail')}
|
|
117
|
+
{identifierLabel ?? i18n.t('auth.usernameOrEmail')}
|
|
109
118
|
</Label>
|
|
110
|
-
<div class="svadmin-
|
|
111
|
-
<User class="svadmin-
|
|
119
|
+
<div class="svadmin-auth-input-field" data-svadmin-login-identifier-field>
|
|
120
|
+
<User class="svadmin-auth-input-icon" data-svadmin-auth-input-icon aria-hidden="true" />
|
|
112
121
|
<Input
|
|
113
122
|
id="login-identifier"
|
|
123
|
+
data-svadmin-login-identifier
|
|
114
124
|
type="text"
|
|
115
|
-
placeholder={i18n.t('auth.identifierPlaceholder')}
|
|
125
|
+
placeholder={identifierPlaceholder ?? i18n.t('auth.identifierPlaceholder')}
|
|
116
126
|
bind:value={identifier}
|
|
117
|
-
class="svadmin-
|
|
127
|
+
class="svadmin-auth-input-control"
|
|
118
128
|
autocomplete="username"
|
|
129
|
+
aria-invalid={error ? 'true' : undefined}
|
|
130
|
+
aria-describedby={error ? 'login-error' : undefined}
|
|
119
131
|
/>
|
|
120
132
|
</div>
|
|
121
133
|
</div>
|
|
@@ -123,7 +135,7 @@
|
|
|
123
135
|
<div class="svadmin-u-6f7e013d6499">
|
|
124
136
|
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc">
|
|
125
137
|
<Label for="login-password">
|
|
126
|
-
{i18n.t('auth.password')}
|
|
138
|
+
{passwordLabel ?? i18n.t('auth.password')}
|
|
127
139
|
</Label>
|
|
128
140
|
{#if authProvider?.forgotPassword}
|
|
129
141
|
<Button variant="link" class="svadmin-u-b8f0a08ece1e svadmin-u-8a539c7fe216 svadmin-u-359090c2d529 svadmin-u-2689f3958069" onclick={() => adminContext.navigate('/forgot-password')}>
|
|
@@ -133,17 +145,20 @@
|
|
|
133
145
|
</div>
|
|
134
146
|
<PasswordInput
|
|
135
147
|
id="login-password"
|
|
136
|
-
label=
|
|
148
|
+
label={passwordLabel ?? i18n.t('auth.password')}
|
|
149
|
+
showLabel={false}
|
|
137
150
|
bind:value={password}
|
|
138
151
|
autocomplete="current-password"
|
|
152
|
+
ariaInvalid={error ? 'true' : undefined}
|
|
153
|
+
ariaDescribedby={error ? 'login-error' : undefined}
|
|
139
154
|
/>
|
|
140
155
|
</div>
|
|
141
156
|
|
|
142
|
-
<Button type="submit" class="svadmin-u-426b8b75185b svadmin-u-6da6a3c3f741" disabled={login.isLoading}>
|
|
157
|
+
<Button type="submit" data-svadmin-login-submit class="svadmin-u-426b8b75185b svadmin-u-6da6a3c3f741" disabled={login.isLoading} aria-busy={login.isLoading}>
|
|
143
158
|
{#if login.isLoading}
|
|
144
159
|
<Loader2 class="svadmin-u-cd0d9c512cdc svadmin-u-72470489ff4e svadmin-u-afbdd13a380e svadmin-u-d2347e8497a9" />
|
|
145
160
|
{/if}
|
|
146
|
-
{i18n.t('auth.loginButton')}
|
|
161
|
+
{loginButtonLabel ?? i18n.t('auth.loginButton')}
|
|
147
162
|
</Button>
|
|
148
163
|
</form>
|
|
149
164
|
|
|
@@ -171,3 +186,4 @@
|
|
|
171
186
|
</div>
|
|
172
187
|
{/if}
|
|
173
188
|
</AuthPageShell>
|
|
189
|
+
</div>
|
|
@@ -10,6 +10,10 @@ type $$ComponentProps = {
|
|
|
10
10
|
defaultIdentifier?: string;
|
|
11
11
|
defaultPassword?: string;
|
|
12
12
|
loginHint?: string;
|
|
13
|
+
identifierLabel?: string;
|
|
14
|
+
identifierPlaceholder?: string;
|
|
15
|
+
passwordLabel?: string;
|
|
16
|
+
loginButtonLabel?: string;
|
|
13
17
|
};
|
|
14
18
|
declare const LoginPage: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
15
19
|
type LoginPage = ReturnType<typeof LoginPage>;
|
|
@@ -33,12 +33,13 @@
|
|
|
33
33
|
let loading = $state(false);
|
|
34
34
|
let saving = $state(false);
|
|
35
35
|
let loaded = $state(false);
|
|
36
|
-
let
|
|
36
|
+
let loadError = $state('');
|
|
37
|
+
let saveError = $state('');
|
|
37
38
|
let epoch = 0;
|
|
38
39
|
async function load() {
|
|
39
40
|
const provider = preferencesProvider;
|
|
40
41
|
const request = ++epoch;
|
|
41
|
-
|
|
42
|
+
loadError = ''; saveError = ''; loaded = false; saving = false;
|
|
42
43
|
loading = Boolean(provider);
|
|
43
44
|
if (!provider) return;
|
|
44
45
|
try {
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
emailAlerts = { ...result.email }; pushAlerts = { ...result.push }; smsAlerts = { ...result.sms };
|
|
48
49
|
loaded = true;
|
|
49
50
|
} catch (caught) {
|
|
50
|
-
if (request === epoch)
|
|
51
|
+
if (request === epoch) loadError = caught instanceof Error ? caught.message : String(caught);
|
|
51
52
|
} finally {
|
|
52
53
|
if (request === epoch) loading = false;
|
|
53
54
|
}
|
|
@@ -61,12 +62,12 @@
|
|
|
61
62
|
const provider = preferencesProvider;
|
|
62
63
|
if (!provider || !loaded || saving) return;
|
|
63
64
|
const request = epoch;
|
|
64
|
-
saving = true;
|
|
65
|
+
saving = true; saveError = '';
|
|
65
66
|
try {
|
|
66
67
|
await provider.save({ email: { ...emailAlerts }, push: { ...pushAlerts }, sms: { ...smsAlerts } });
|
|
67
68
|
if (request === epoch) notification.success(i18n.t('common.updateSuccess'), 3000);
|
|
68
69
|
} catch (caught) {
|
|
69
|
-
if (request === epoch)
|
|
70
|
+
if (request === epoch) saveError = caught instanceof Error ? caught.message : String(caught);
|
|
70
71
|
} finally {
|
|
71
72
|
if (request === epoch) saving = false;
|
|
72
73
|
}
|
|
@@ -85,9 +86,15 @@
|
|
|
85
86
|
{#if !preferencesProvider}
|
|
86
87
|
<FeedbackNotice tone="warning" message={i18n.locale === 'zh-CN' ? '未配置通知偏好存储服务,设置不可保存。' : 'Notification preference storage is not configured. Settings cannot be saved.'} />
|
|
87
88
|
{/if}
|
|
88
|
-
{#if loading}
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
{#if loading}
|
|
90
|
+
<DataState state="loading" />
|
|
91
|
+
{:else if loadError}
|
|
92
|
+
<DataState state="error" description={loadError} retry={load} />
|
|
93
|
+
{:else if saveError}
|
|
94
|
+
<FeedbackNotice tone="danger" message={saveError} />
|
|
95
|
+
{/if}
|
|
96
|
+
{#if loaded || !preferencesProvider}
|
|
97
|
+
<fieldset disabled={!preferencesProvider || saving} class="svadmin-u-b3542e058833">
|
|
91
98
|
<SettingsGroup title={i18n.t('notifications.emailTitle')} description={i18n.t('notifications.emailDescription')} headingLevel={headingLevel === 'h1' ? 'h2' : 'h3'}>
|
|
92
99
|
<div class="svadmin-u-fa6acbf81d74 svadmin-u-e783642739e3">
|
|
93
100
|
<SettingsFieldRow controlId={`${controlId}-email-security`} label={i18n.t('notifications.securityAlerts')} description={i18n.t('notifications.securityAlertsDesc')}>{#snippet control()}<Switch id={`${controlId}-email-security`} bind:checked={emailAlerts.security} />{/snippet}</SettingsFieldRow>
|
|
@@ -106,4 +113,5 @@
|
|
|
106
113
|
<SettingsFieldRow controlId={`${controlId}-sms-security`} label={i18n.t('notifications.criticalSecurity')} description={i18n.t('notifications.criticalSecurityDesc')}>{#snippet control()}<Switch id={`${controlId}-sms-security`} bind:checked={smsAlerts.security} />{/snippet}</SettingsFieldRow>
|
|
107
114
|
</SettingsGroup>
|
|
108
115
|
</fieldset>
|
|
116
|
+
{/if}
|
|
109
117
|
</div>
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
onBack?: () => void;
|
|
16
16
|
backLabel?: string;
|
|
17
17
|
showBreadcrumbs?: boolean;
|
|
18
|
-
density?: 'compact' | 'comfortable';
|
|
19
18
|
class?: string;
|
|
20
19
|
}
|
|
21
20
|
|
|
@@ -29,13 +28,12 @@
|
|
|
29
28
|
onBack,
|
|
30
29
|
backLabel = 'Back',
|
|
31
30
|
showBreadcrumbs = true,
|
|
32
|
-
density = 'comfortable',
|
|
33
31
|
class: className = '',
|
|
34
32
|
}: Props = $props();
|
|
35
33
|
|
|
36
34
|
</script>
|
|
37
35
|
|
|
38
|
-
<div class={cn('svadmin-page-header', className)}
|
|
36
|
+
<div class={cn('svadmin-page-header', className)}>
|
|
39
37
|
{#if showBreadcrumbs}
|
|
40
38
|
<Breadcrumbs class="svadmin-page-header__breadcrumbs" />
|
|
41
39
|
{/if}
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
import { definedOptions } from '@svadmin/core/options';
|
|
4
4
|
import ContentPageShell from './content/ContentPageShell.svelte';
|
|
5
5
|
import WorkspaceLayout from './content/WorkspaceLayout.svelte';
|
|
6
|
+
import DataState from './content/DataState.svelte';
|
|
6
7
|
|
|
7
8
|
export type PagePatternKind = 'list' | 'form' | 'detail' | 'workspace' | 'dashboard';
|
|
9
|
+
export type PagePatternState = 'ready' | 'loading' | 'empty' | 'filtered-empty' | 'error' | 'forbidden';
|
|
8
10
|
|
|
9
11
|
interface Props {
|
|
10
12
|
kind: PagePatternKind;
|
|
@@ -18,8 +20,14 @@
|
|
|
18
20
|
children: Snippet;
|
|
19
21
|
footer?: Snippet;
|
|
20
22
|
width?: 'narrow' | 'default' | 'wide';
|
|
23
|
+
/** @deprecated Accepted for compatibility; the product baseline is shared. */
|
|
21
24
|
density?: 'compact' | 'comfortable';
|
|
22
25
|
pageId?: string;
|
|
26
|
+
state?: PagePatternState;
|
|
27
|
+
stateTitle?: string;
|
|
28
|
+
stateDescription?: string;
|
|
29
|
+
retry?: () => void;
|
|
30
|
+
stateAction?: Snippet;
|
|
23
31
|
class?: string;
|
|
24
32
|
}
|
|
25
33
|
|
|
@@ -35,24 +43,41 @@
|
|
|
35
43
|
children,
|
|
36
44
|
footer,
|
|
37
45
|
width,
|
|
38
|
-
density,
|
|
46
|
+
density = 'comfortable',
|
|
39
47
|
pageId,
|
|
48
|
+
state = 'ready',
|
|
49
|
+
stateTitle,
|
|
50
|
+
stateDescription,
|
|
51
|
+
retry,
|
|
52
|
+
stateAction,
|
|
40
53
|
class: className = '',
|
|
41
54
|
}: Props = $props();
|
|
42
55
|
|
|
43
56
|
const resolvedPageId = $derived(pageId ?? `pattern-${kind}`);
|
|
44
57
|
const resolvedWidth = $derived(width ?? (kind === 'form' ? 'narrow' : kind === 'detail' ? 'default' : 'wide'));
|
|
45
|
-
const
|
|
58
|
+
const showState = $derived(state !== 'ready');
|
|
46
59
|
</script>
|
|
47
60
|
|
|
48
61
|
<ContentPageShell
|
|
49
62
|
{title}
|
|
50
63
|
{...definedOptions({ description, eyebrow, actions })}
|
|
51
|
-
density={resolvedDensity}
|
|
52
64
|
width={resolvedWidth}
|
|
65
|
+
{density}
|
|
53
66
|
pageId={resolvedPageId}
|
|
54
67
|
class={className}
|
|
55
68
|
>
|
|
69
|
+
<div data-page-pattern-kind={kind} data-page-pattern-state={state}>
|
|
70
|
+
{#if showState}
|
|
71
|
+
{#if state === 'loading' || state === 'empty' || state === 'filtered-empty' || state === 'error' || state === 'forbidden'}
|
|
72
|
+
<DataState
|
|
73
|
+
{state}
|
|
74
|
+
{...definedOptions({ title: stateTitle })}
|
|
75
|
+
{...definedOptions({ description: stateDescription })}
|
|
76
|
+
{...definedOptions({ retry: state === 'error' ? retry : undefined })}
|
|
77
|
+
{...definedOptions({ action: stateAction })}
|
|
78
|
+
/>
|
|
79
|
+
{/if}
|
|
80
|
+
{:else}
|
|
56
81
|
{#if metrics}
|
|
57
82
|
<div class="page-pattern__metrics" data-page-pattern-metrics>
|
|
58
83
|
{@render metrics()}
|
|
@@ -68,7 +93,7 @@
|
|
|
68
93
|
{#if secondary}
|
|
69
94
|
<WorkspaceLayout primary={children} {...definedOptions({ secondary })} />
|
|
70
95
|
{:else}
|
|
71
|
-
<div class="page-pattern__content" data-page-pattern-content={kind}
|
|
96
|
+
<div class="page-pattern__content" data-page-pattern-content={kind}>
|
|
72
97
|
{@render children()}
|
|
73
98
|
</div>
|
|
74
99
|
{/if}
|
|
@@ -78,13 +103,15 @@
|
|
|
78
103
|
{@render footer()}
|
|
79
104
|
</footer>
|
|
80
105
|
{/if}
|
|
106
|
+
{/if}
|
|
107
|
+
</div>
|
|
81
108
|
</ContentPageShell>
|
|
82
109
|
|
|
83
110
|
<style>
|
|
84
111
|
.page-pattern__metrics {
|
|
85
112
|
display: grid;
|
|
86
113
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
|
|
87
|
-
gap:
|
|
114
|
+
gap: var(--svadmin-space-4);
|
|
88
115
|
}
|
|
89
116
|
|
|
90
117
|
.page-pattern__toolbar,
|
|
@@ -94,11 +121,7 @@
|
|
|
94
121
|
|
|
95
122
|
.page-pattern__content {
|
|
96
123
|
display: grid;
|
|
97
|
-
gap:
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.page-pattern__content[data-density='compact'] {
|
|
101
|
-
gap: 16px;
|
|
124
|
+
gap: var(--svadmin-space-5);
|
|
102
125
|
}
|
|
103
126
|
|
|
104
127
|
.page-pattern__footer {
|
|
@@ -106,9 +129,9 @@
|
|
|
106
129
|
flex-wrap: wrap;
|
|
107
130
|
align-items: center;
|
|
108
131
|
justify-content: flex-end;
|
|
109
|
-
gap:
|
|
110
|
-
margin-top:
|
|
111
|
-
padding-top:
|
|
132
|
+
gap: var(--svadmin-space-2);
|
|
133
|
+
margin-top: var(--svadmin-space-5);
|
|
134
|
+
padding-top: var(--svadmin-space-3);
|
|
112
135
|
border-top: 1px solid var(--border);
|
|
113
136
|
}
|
|
114
137
|
</style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
export type PagePatternKind = 'list' | 'form' | 'detail' | 'workspace' | 'dashboard';
|
|
3
|
+
export type PagePatternState = 'ready' | 'loading' | 'empty' | 'filtered-empty' | 'error' | 'forbidden';
|
|
3
4
|
interface Props {
|
|
4
5
|
kind: PagePatternKind;
|
|
5
6
|
title: string;
|
|
@@ -12,8 +13,14 @@ interface Props {
|
|
|
12
13
|
children: Snippet;
|
|
13
14
|
footer?: Snippet;
|
|
14
15
|
width?: 'narrow' | 'default' | 'wide';
|
|
16
|
+
/** @deprecated Accepted for compatibility; the product baseline is shared. */
|
|
15
17
|
density?: 'compact' | 'comfortable';
|
|
16
18
|
pageId?: string;
|
|
19
|
+
state?: PagePatternState;
|
|
20
|
+
stateTitle?: string;
|
|
21
|
+
stateDescription?: string;
|
|
22
|
+
retry?: () => void;
|
|
23
|
+
stateAction?: Snippet;
|
|
17
24
|
class?: string;
|
|
18
25
|
}
|
|
19
26
|
declare const PagePattern: import("svelte").Component<Props, {}, "">;
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
autocomplete?: FullAutoFill;
|
|
17
17
|
showStrength?: boolean;
|
|
18
18
|
disabled?: boolean;
|
|
19
|
+
showLabel?: boolean;
|
|
19
20
|
class?: string;
|
|
21
|
+
ariaInvalid?: 'true' | 'false' | undefined;
|
|
22
|
+
ariaDescribedby?: string | undefined;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
let {
|
|
@@ -27,7 +30,10 @@
|
|
|
27
30
|
autocomplete = 'current-password',
|
|
28
31
|
showStrength = false,
|
|
29
32
|
disabled = false,
|
|
33
|
+
showLabel = true,
|
|
30
34
|
class: className = '',
|
|
35
|
+
ariaInvalid,
|
|
36
|
+
ariaDescribedby,
|
|
31
37
|
}: Props = $props();
|
|
32
38
|
|
|
33
39
|
let showPassword = $state(false);
|
|
@@ -44,10 +50,10 @@
|
|
|
44
50
|
return Math.min(score, 100);
|
|
45
51
|
});
|
|
46
52
|
|
|
47
|
-
const
|
|
48
|
-
strength < 30 ? '
|
|
49
|
-
strength < 60 ? '
|
|
50
|
-
strength < 80 ? '
|
|
53
|
+
const strengthTone = $derived(
|
|
54
|
+
strength < 30 ? 'danger' :
|
|
55
|
+
strength < 60 ? 'warning' :
|
|
56
|
+
strength < 80 ? 'info' : 'success'
|
|
51
57
|
);
|
|
52
58
|
|
|
53
59
|
const strengthLabel = $derived(
|
|
@@ -57,45 +63,58 @@
|
|
|
57
63
|
);
|
|
58
64
|
</script>
|
|
59
65
|
|
|
60
|
-
<div
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
<div
|
|
67
|
+
class="svadmin-password-input {className}"
|
|
68
|
+
data-svadmin-password-input
|
|
69
|
+
data-svadmin-login-password
|
|
70
|
+
data-password-visible={showPassword ? 'true' : 'false'}
|
|
71
|
+
>
|
|
72
|
+
{#if showLabel}
|
|
73
|
+
<Label for={id} class="svadmin-password-input__label" data-svadmin-password-label>{label}</Label>
|
|
74
|
+
{/if}
|
|
75
|
+
<div class="svadmin-password-input__field" data-svadmin-password-field>
|
|
76
|
+
<Lock class="svadmin-password-input__icon" data-svadmin-password-icon aria-hidden="true" />
|
|
64
77
|
<Input
|
|
65
78
|
{id}
|
|
66
79
|
type={showPassword ? 'text' : 'password'}
|
|
67
80
|
{placeholder}
|
|
68
81
|
bind:value
|
|
69
|
-
class="svadmin-
|
|
82
|
+
class="svadmin-password-input__control"
|
|
83
|
+
data-svadmin-password-control
|
|
70
84
|
{autocomplete}
|
|
71
85
|
{disabled}
|
|
86
|
+
aria-invalid={ariaInvalid}
|
|
87
|
+
aria-describedby={ariaDescribedby}
|
|
72
88
|
/>
|
|
73
89
|
<TooltipButton
|
|
74
90
|
tooltip={showPassword ? i18n.t('common.hidePassword') : i18n.t('common.showPassword')}
|
|
75
91
|
variant="ghost"
|
|
76
92
|
size="icon"
|
|
77
93
|
type="button"
|
|
78
|
-
class="svadmin-
|
|
94
|
+
class="svadmin-password-input__toggle"
|
|
95
|
+
data-svadmin-password-toggle
|
|
96
|
+
aria-controls={id}
|
|
97
|
+
aria-pressed={showPassword}
|
|
79
98
|
onclick={(e) => { e.preventDefault(); showPassword = !showPassword; }}
|
|
80
99
|
{disabled}
|
|
81
|
-
tabindex={-1}
|
|
82
100
|
>
|
|
83
101
|
{#if showPassword}
|
|
84
|
-
<EyeOff class="svadmin-
|
|
102
|
+
<EyeOff class="svadmin-password-input__glyph" />
|
|
85
103
|
{:else}
|
|
86
|
-
<Eye class="svadmin-
|
|
104
|
+
<Eye class="svadmin-password-input__glyph" />
|
|
87
105
|
{/if}
|
|
88
106
|
</TooltipButton>
|
|
89
107
|
</div>
|
|
90
108
|
{#if showStrength && value}
|
|
91
|
-
<div class="svadmin-
|
|
92
|
-
<div class="svadmin-
|
|
109
|
+
<div class="svadmin-password-input__strength" data-svadmin-password-strength>
|
|
110
|
+
<div class="svadmin-password-input__strength-track">
|
|
93
111
|
<div
|
|
94
|
-
class="svadmin-
|
|
112
|
+
class="svadmin-password-input__strength-fill"
|
|
113
|
+
data-tone={strengthTone}
|
|
95
114
|
style="width: {strength}%"
|
|
96
115
|
></div>
|
|
97
116
|
</div>
|
|
98
|
-
<span class="svadmin-
|
|
117
|
+
<span class="svadmin-password-input__strength-label">{strengthLabel}</span>
|
|
99
118
|
</div>
|
|
100
119
|
{/if}
|
|
101
120
|
</div>
|
|
@@ -7,7 +7,10 @@ interface Props {
|
|
|
7
7
|
autocomplete?: FullAutoFill;
|
|
8
8
|
showStrength?: boolean;
|
|
9
9
|
disabled?: boolean;
|
|
10
|
+
showLabel?: boolean;
|
|
10
11
|
class?: string;
|
|
12
|
+
ariaInvalid?: 'true' | 'false' | undefined;
|
|
13
|
+
ariaDescribedby?: string | undefined;
|
|
11
14
|
}
|
|
12
15
|
declare const PasswordInput: import("svelte").Component<Props, {}, "value">;
|
|
13
16
|
type PasswordInput = ReturnType<typeof PasswordInput>;
|