@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.
Files changed (89) hide show
  1. package/README.md +31 -0
  2. package/dist/app-shell.css +216 -0
  3. package/dist/app.css +659 -165
  4. package/dist/app.theme.css +659 -165
  5. package/dist/component-registry.d.ts +7292 -529
  6. package/dist/component-registry.js +32 -3
  7. package/dist/components/AppShellPage.svelte +172 -0
  8. package/dist/components/AppShellPage.svelte.d.ts +30 -0
  9. package/dist/components/ApprovalActionCard.svelte +54 -31
  10. package/dist/components/ApprovalCenter.svelte +73 -31
  11. package/dist/components/AuthPageShell.svelte +13 -2
  12. package/dist/components/AutoForm.svelte +7 -12
  13. package/dist/components/AutoForm.svelte.d.ts +0 -1
  14. package/dist/components/AutoTable.svelte +15 -3
  15. package/dist/components/AutoTable.svelte.d.ts +2 -0
  16. package/dist/components/ConfigurationPage.svelte +0 -3
  17. package/dist/components/ConfigurationPage.svelte.d.ts +0 -1
  18. package/dist/components/CreatePage.svelte +22 -19
  19. package/dist/components/CreatePage.svelte.d.ts +4 -1
  20. package/dist/components/EditPage.svelte +23 -20
  21. package/dist/components/EditPage.svelte.d.ts +4 -1
  22. package/dist/components/FieldRenderer.svelte +2 -10
  23. package/dist/components/FieldRenderer.svelte.d.ts +0 -1
  24. package/dist/components/ListPage.svelte +21 -18
  25. package/dist/components/ListPage.svelte.d.ts +2 -1
  26. package/dist/components/LoginPage.svelte +27 -11
  27. package/dist/components/LoginPage.svelte.d.ts +4 -0
  28. package/dist/components/NotificationsSettings.svelte +16 -8
  29. package/dist/components/PageHeader.svelte +1 -3
  30. package/dist/components/PageHeader.svelte.d.ts +0 -1
  31. package/dist/components/PagePattern.svelte +36 -13
  32. package/dist/components/PagePattern.svelte.d.ts +7 -0
  33. package/dist/components/PasswordInput.svelte +36 -17
  34. package/dist/components/PasswordInput.svelte.d.ts +3 -0
  35. package/dist/components/QuickEditDrawer.svelte +0 -1
  36. package/dist/components/ShowPage.svelte +24 -29
  37. package/dist/components/ShowPage.svelte.d.ts +0 -1
  38. package/dist/components/SvarAutoTable.svelte +2 -1
  39. package/dist/components/SvarDataGrid.svelte +8 -4
  40. package/dist/components/SvarDataGrid.svelte.d.ts +3 -0
  41. package/dist/components/SvarResourceTable.svelte +0 -1
  42. package/dist/components/SvarResourceTable.svelte.d.ts +0 -1
  43. package/dist/components/SvarResourceTableView.svelte +2 -2
  44. package/dist/components/account/MemberDirectory.svelte +17 -4
  45. package/dist/components/account/SecurityLogPage.svelte +8 -1
  46. package/dist/components/auto-table-grid.d.ts +1 -0
  47. package/dist/components/content/AuditTimeline.svelte +32 -4
  48. package/dist/components/content/AuditTimeline.svelte.d.ts +9 -0
  49. package/dist/components/content/ContentPageHeader.svelte +2 -1
  50. package/dist/components/content/ContentPageHeader.svelte.d.ts +1 -0
  51. package/dist/components/content/ContentPageShell.svelte +1 -1
  52. package/dist/components/content/ContentPageShell.svelte.d.ts +1 -0
  53. package/dist/components/content/DataPanel.svelte +58 -0
  54. package/dist/components/content/DataPanel.svelte.d.ts +16 -0
  55. package/dist/components/content/DataState.svelte +5 -4
  56. package/dist/components/content/DataState.svelte.d.ts +1 -1
  57. package/dist/components/content/DescriptionList.svelte +5 -8
  58. package/dist/components/content/DescriptionList.svelte.d.ts +0 -1
  59. package/dist/components/content/FilterToolbar.svelte +9 -6
  60. package/dist/components/content/FilterToolbar.svelte.d.ts +0 -1
  61. package/dist/components/content/NetworkTable.svelte +3 -3
  62. package/dist/components/content/NetworkTable.svelte.d.ts +0 -1
  63. package/dist/components/content/PageSection.svelte +1 -1
  64. package/dist/components/content/StatusTabs.svelte +2 -7
  65. package/dist/components/content/StatusTabs.svelte.d.ts +0 -1
  66. package/dist/components/content/WorkspaceLayout.svelte +3 -1
  67. package/dist/components/content/WorkspaceLayout.svelte.d.ts +1 -0
  68. package/dist/components/content/index.d.ts +1 -0
  69. package/dist/components/content/index.js +1 -0
  70. package/dist/components/network/TeamCrewTablePage.svelte +8 -1
  71. package/dist/components/network/UserCardsNFTPage.svelte +3 -1
  72. package/dist/components/profile/ProjectsGrid.svelte +1 -1
  73. package/dist/components/profile/TeamsShowcase.svelte +1 -1
  74. package/dist/components/ui/table/index.d.ts +0 -1
  75. package/dist/components/ui/table/table.svelte +1 -0
  76. package/dist/components/ui/table/table.svelte.d.ts +1 -0
  77. package/dist/components.css +1 -11
  78. package/dist/data-panel.css +88 -0
  79. package/dist/design-contract.d.ts +11 -0
  80. package/dist/design-contract.js +8 -0
  81. package/dist/design-system.d.ts +3 -2
  82. package/dist/design-system.js +3 -2
  83. package/dist/index.d.ts +4 -2
  84. package/dist/index.js +2 -0
  85. package/dist/page-chrome.css +42 -64
  86. package/dist/recipes.d.ts +12 -66
  87. package/dist/recipes.js +52 -50
  88. package/dist/styles/recipes.css +15 -48
  89. package/package.json +4 -4
@@ -8,7 +8,6 @@ type $$ComponentProps = {
8
8
  disabled?: boolean;
9
9
  invalid?: boolean;
10
10
  errorId?: string;
11
- density?: 'compact' | 'comfortable';
12
11
  children?: Snippet;
13
12
  };
14
13
  declare const FieldRenderer: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -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="{density === 'compact' ? 'svadmin-u-6ed543e2fbbb' : 'svadmin-u-3e7ce58d64fa'} {className}">
66
- <PageHeader title={pageTitle} {density}>
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
- <AutoTable
90
- {rendering}
91
- {resourceName}
92
- showHeader={false}
93
- {density}
94
- {...definedOptions({ "selectable": selectable })}
95
- {...definedOptions({ allowSelectAllMatching })}
96
- {...definedOptions({ batchActions })}
97
- {...definedOptions({ "defaultCellRenderer": cellRenderer })}
98
- {...definedOptions({ "rowActions": rowActions })}
99
- {...definedOptions({ emptyState, expandedRowRender })}
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-u-d89972fe17d6">
111
- <User class="svadmin-u-a4326536b8f5 svadmin-u-da4dbfbc4fdc svadmin-u-22e59b722111 svadmin-u-d694ba66e322 svadmin-u-536a7530a44f svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 svadmin-u-36b381be4df3 svadmin-u-bfa603190748" />
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-u-9e83b2412bc9"
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 error = $state('');
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
- error = ''; loaded = false; saving = false;
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) error = caught instanceof Error ? caught.message : String(caught);
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; error = '';
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) error = caught instanceof Error ? caught.message : String(caught);
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}<DataState state="loading" />{/if}
89
- {#if error}<p role="alert">{error}</p>{#if !loaded}<Button onclick={load}>{i18n.t('common.retry')}</Button>{/if}{/if}
90
- <fieldset disabled={!loaded || loading || saving} class="svadmin-u-b3542e058833">
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)} data-density={density}>
36
+ <div class={cn('svadmin-page-header', className)}>
39
37
  {#if showBreadcrumbs}
40
38
  <Breadcrumbs class="svadmin-page-header__breadcrumbs" />
41
39
  {/if}
@@ -9,7 +9,6 @@ interface Props {
9
9
  onBack?: () => void;
10
10
  backLabel?: string;
11
11
  showBreadcrumbs?: boolean;
12
- density?: 'compact' | 'comfortable';
13
12
  class?: string;
14
13
  }
15
14
  declare const PageHeader: import("svelte").Component<Props, {}, "">;
@@ -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 resolvedDensity = $derived(density ?? 'comfortable');
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} data-density={resolvedDensity}>
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: 16px;
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: 24px;
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: 8px;
110
- margin-top: 24px;
111
- padding-top: 12px;
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 strengthColor = $derived(
48
- strength < 30 ? 'bg-destructive' :
49
- strength < 60 ? 'bg-warning' :
50
- strength < 80 ? 'bg-info' : 'bg-success'
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 class="svadmin-u-6f7e013d6499 {className}">
61
- <Label for={id}>{label}</Label>
62
- <div class="svadmin-u-d89972fe17d6">
63
- <Lock class="svadmin-u-da4dbfbc4fdc svadmin-u-22e59b722111 svadmin-u-d694ba66e322 svadmin-u-36b381be4df3 svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 svadmin-u-bfa603190748 svadmin-u-a4326536b8f5 svadmin-u-536a7530a44f" />
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-u-9e83b2412bc9 svadmin-u-1b1df78e4d0a"
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-u-da4dbfbc4fdc svadmin-u-68d3fc190004 svadmin-u-d694ba66e322 svadmin-u-36b381be4df3 svadmin-u-d0a52b312f7d svadmin-u-cbbf90f9a828 svadmin-u-536a7530a44f"
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-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
102
+ <EyeOff class="svadmin-password-input__glyph" />
85
103
  {:else}
86
- <Eye class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
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-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4">
92
- <div class="svadmin-u-36e579c0b41c svadmin-u-095acb275581 svadmin-u-ac204c108886 svadmin-u-2ef11f1cb219 svadmin-u-2cd02d11d1af">
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-u-668b21aa5409 svadmin-u-ac204c108886 svadmin-u-0fe7d7d814d0 svadmin-u-7890552ecd63 {strengthColor}"
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-u-359090c2d529 svadmin-u-bfa603190748 svadmin-u-e7e371071bc5 svadmin-u-308fc069e46e">{strengthLabel}</span>
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>;
@@ -32,7 +32,6 @@
32
32
  {resourceName}
33
33
  id={recordId}
34
34
  mode="edit"
35
- density="compact"
36
35
  showHeader={false}
37
36
  redirect={false}
38
37
  onSuccess={onClose}