@svadmin/ui 0.77.0 → 0.78.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/dist/app.css +99 -79
- package/dist/app.theme.css +99 -79
- package/dist/components/AboutSettings.svelte +4 -3
- package/dist/components/AboutSettings.svelte.d.ts +1 -0
- package/dist/components/AdminApp.svelte +30 -15
- package/dist/components/ApiSettings.svelte +39 -2
- package/dist/components/AppearanceSettings.svelte +35 -9
- package/dist/components/AppearanceSettings.svelte.d.ts +4 -1
- package/dist/components/AutoTable.svelte +10 -6
- package/dist/components/AutoTable.svelte.d.ts +2 -0
- package/dist/components/IntegrationsSettings.svelte +5 -3
- package/dist/components/IntegrationsSettings.svelte.d.ts +1 -0
- package/dist/components/NotificationsSettings.svelte +18 -12
- package/dist/components/NotificationsSettings.svelte.d.ts +1 -0
- package/dist/components/ProfilePage.svelte +3 -3
- package/dist/components/ProfilePage.svelte.d.ts +4 -1
- package/dist/components/ResourceOverview.svelte +36 -0
- package/dist/components/ResourceOverview.svelte.d.ts +6 -0
- package/dist/components/ResourceOverviewCard.svelte +50 -0
- package/dist/components/ResourceOverviewCard.svelte.d.ts +7 -0
- package/dist/components/SecuritySettings.svelte +9 -6
- package/dist/components/SecuritySettings.svelte.d.ts +4 -1
- package/dist/components/account/SettingsEnterprisePage.svelte +19 -19
- package/dist/components/account/SettingsPlainPage.svelte +7 -3
- package/dist/components/account/SettingsSidebarPage.svelte +3 -3
- package/dist/components/content/ContentPageHeader.svelte +3 -1
- package/dist/components/content/ContentPageHeader.svelte.d.ts +1 -0
- package/dist/components/content/DashboardPage.svelte +35 -0
- package/dist/components/content/DashboardPage.svelte.d.ts +13 -0
- package/dist/components/content/FeedbackNotice.svelte +1 -1
- package/dist/components/content/PageSection.svelte +28 -0
- package/dist/components/content/PageSection.svelte.d.ts +10 -0
- package/dist/components/content/SettingsFieldRow.svelte +10 -5
- package/dist/components/content/SettingsFieldRow.svelte.d.ts +1 -0
- package/dist/components/content/SettingsGroup.svelte +3 -3
- package/dist/components/content/SettingsGroup.svelte.d.ts +1 -0
- package/dist/components/content/index.d.ts +2 -0
- package/dist/components/content/index.js +2 -0
- package/dist/components/ui/button/button.svelte +17 -1
- package/dist/components/ui/button/button.svelte.d.ts +1 -0
- package/dist/components/ui/sheet/sheet.svelte +3 -0
- package/dist/default-theme.d.ts +3 -0
- package/dist/default-theme.js +4 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/page-chrome.css +4 -4
- package/package.json +1 -1
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
import DataState from './content/DataState.svelte';
|
|
12
12
|
import FeedbackNotice from './content/FeedbackNotice.svelte';
|
|
13
13
|
import ConfirmDialog from './ConfirmDialog.svelte';
|
|
14
|
+
import ContentPageHeader from './content/ContentPageHeader.svelte';
|
|
14
15
|
|
|
16
|
+
let { headingLevel = 'h1' }: { headingLevel?: 'h1' | 'h2' } = $props();
|
|
17
|
+
const controlId = $props.id();
|
|
15
18
|
const i18n = useTranslation();
|
|
16
19
|
const adminContext = captureAdminContext();
|
|
17
20
|
const authProvider = $derived(adminContext.authProvider);
|
|
@@ -179,16 +182,16 @@
|
|
|
179
182
|
</script>
|
|
180
183
|
|
|
181
184
|
<div class="svadmin-u-b3542e058833">
|
|
182
|
-
<
|
|
185
|
+
<ContentPageHeader title={i18n.t('settings.security')} description={i18n.t('settings.securityDescription')} {headingLevel} />
|
|
183
186
|
{#if !sessionProvider}
|
|
184
187
|
<FeedbackNotice tone="warning" message={isZh ? '未配置 SessionProvider。会话和 MFA 设置不会使用模拟数据。' : 'SessionProvider is not configured. Session and MFA controls do not use simulated data.'} />
|
|
185
188
|
{/if}
|
|
186
|
-
<SettingsGroup title={i18n.t('security.twoFactorAuth')} description={i18n.t('security.twoFactorDescription')}>
|
|
187
|
-
<SettingsFieldRow label={i18n.t('security.enable2fa')} description={is2faEnabled ? i18n.t('security.twoFactorActive') : i18n.t('security.twoFactorInactive')}>
|
|
188
|
-
{#snippet control()}<Switch bind:checked={() => is2faEnabled, (enabled) => { void handleMfaChange(enabled); }} disabled={!sessionProvider?.setMfaEnabled || updatingMfa} />{/snippet}
|
|
189
|
+
<SettingsGroup title={i18n.t('security.twoFactorAuth')} description={i18n.t('security.twoFactorDescription')} headingLevel={headingLevel === 'h1' ? 'h2' : 'h3'}>
|
|
190
|
+
<SettingsFieldRow controlId={`${controlId}-mfa`} label={i18n.t('security.enable2fa')} description={is2faEnabled ? i18n.t('security.twoFactorActive') : i18n.t('security.twoFactorInactive')}>
|
|
191
|
+
{#snippet control()}<Switch id={`${controlId}-mfa`} bind:checked={() => is2faEnabled, (enabled) => { void handleMfaChange(enabled); }} disabled={!sessionProvider?.setMfaEnabled || updatingMfa} />{/snippet}
|
|
189
192
|
</SettingsFieldRow>
|
|
190
193
|
</SettingsGroup>
|
|
191
|
-
<SettingsGroup title={i18n.t('profile.changePassword')} description={isZh ? '密码修改由 AuthProvider 持久化。' : 'Password changes are persisted by AuthProvider.'}>
|
|
194
|
+
<SettingsGroup title={i18n.t('profile.changePassword')} description={isZh ? '密码修改由 AuthProvider 持久化。' : 'Password changes are persisted by AuthProvider.'} headingLevel={headingLevel === 'h1' ? 'h2' : 'h3'}>
|
|
192
195
|
{#if !authProvider?.updatePassword}<FeedbackNotice tone="warning" message={isZh ? '当前 AuthProvider 不支持修改密码。' : 'The current AuthProvider does not support password updates.'} />{/if}
|
|
193
196
|
<form onsubmit={handlePasswordChange} class="svadmin-u-6199866f612f svadmin-u-3e7ce58d64fa">
|
|
194
197
|
{#if passwordErrorMessage}<Alert.Root variant="destructive"><AlertTriangle class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /><Alert.Description>{passwordErrorMessage}</Alert.Description></Alert.Root>{/if}
|
|
@@ -198,7 +201,7 @@
|
|
|
198
201
|
<Button type="submit" disabled={!authProvider?.updatePassword || changingPassword}>{changingPassword ? (isZh ? '更新中...' : 'Updating...') : i18n.t('profile.updatePassword')}</Button>
|
|
199
202
|
</form>
|
|
200
203
|
</SettingsGroup>
|
|
201
|
-
<SettingsGroup title={i18n.t('security.activeSessions')} description={i18n.t('security.sessionsDescription')}>
|
|
204
|
+
<SettingsGroup title={i18n.t('security.activeSessions')} description={i18n.t('security.sessionsDescription')} headingLevel={headingLevel === 'h1' ? 'h2' : 'h3'}>
|
|
202
205
|
{#snippet actions()}{#if sessions.some((session) => !session.current)}<Button variant="outline" size="sm" disabled={revoking !== null} onclick={() => revokeAllOthers()}>{i18n.t('security.revokeOthers')}</Button>{/if}{/snippet}
|
|
203
206
|
{#if loadingSessions}
|
|
204
207
|
<DataState state="loading" />
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
headingLevel?: 'h1' | 'h2';
|
|
3
|
+
};
|
|
4
|
+
declare const SecuritySettings: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
2
5
|
type SecuritySettings = ReturnType<typeof SecuritySettings>;
|
|
3
6
|
export default SecuritySettings;
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import FeedbackNotice from '../content/FeedbackNotice.svelte';
|
|
26
26
|
|
|
27
27
|
const i18n = useTranslation();
|
|
28
|
+
const controlId = $props.id();
|
|
28
29
|
const adminContext = captureAdminContext();
|
|
29
30
|
const identityProvider = $derived(adminContext.identityGovernanceProvider);
|
|
30
31
|
const organizationProvider = $derived(adminContext.organizationProvider);
|
|
@@ -50,16 +51,12 @@
|
|
|
50
51
|
&& organizationName.trim()
|
|
51
52
|
&& organizationName.trim() !== organization.name,
|
|
52
53
|
));
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
&& Number.isSafeInteger(auditRetention)
|
|
60
|
-
&& sessionTimeout > 0
|
|
61
|
-
&& auditRetention > 0;
|
|
62
|
-
});
|
|
54
|
+
function isPositiveInteger(value: string): boolean {
|
|
55
|
+
return /^\d+$/.test(value) && Number.isSafeInteger(Number(value)) && Number(value) > 0;
|
|
56
|
+
}
|
|
57
|
+
const sessionTimeoutValid = $derived(isPositiveInteger(sessionTimeoutValue));
|
|
58
|
+
const auditRetentionValid = $derived(isPositiveInteger(auditRetentionValue));
|
|
59
|
+
const policyNumbersValid = $derived(sessionTimeoutValid && auditRetentionValid);
|
|
63
60
|
|
|
64
61
|
function message(error: unknown): string {
|
|
65
62
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -202,11 +199,11 @@
|
|
|
202
199
|
</script>
|
|
203
200
|
|
|
204
201
|
{#snippet auditControl()}
|
|
205
|
-
<Switch checked={policy?.auditLoggingEnabled ?? false} disabled={!policy || saving}
|
|
202
|
+
<Switch id={`${controlId}-audit`} checked={policy?.auditLoggingEnabled ?? false} disabled={!policy || saving} onCheckedChange={(checked) => { if (policy) policy = { ...policy, auditLoggingEnabled: checked }; }} />
|
|
206
203
|
{/snippet}
|
|
207
204
|
|
|
208
205
|
{#snippet ssoControl()}
|
|
209
|
-
<Switch checked={policy?.requireSso ?? false} disabled={!policy || saving}
|
|
206
|
+
<Switch id={`${controlId}-sso`} checked={policy?.requireSso ?? false} disabled={!policy || saving} onCheckedChange={(checked) => { if (policy) policy = { ...policy, requireSso: checked }; }} />
|
|
210
207
|
{/snippet}
|
|
211
208
|
|
|
212
209
|
<ContentPageShell pageId="account-settings-enterprise" width="wide">
|
|
@@ -222,19 +219,22 @@
|
|
|
222
219
|
<div class="svadmin-u-f3c543ad5fe9 svadmin-u-60541e1e26f8 svadmin-u-0c3bc98565dd svadmin-u-2f27a80ed92b">
|
|
223
220
|
<SettingsGroup title={i18n.t('account.securityPolicy')} description={isZh ? '配置组织级身份、会话与审计策略。' : 'Configure organization-wide identity, session, and audit controls.'} bodyClass="space-y-4">
|
|
224
221
|
{#if organizationProvider}
|
|
225
|
-
<div class="svadmin-u-6f7e013d6499"><Label for=
|
|
222
|
+
<div class="svadmin-u-6f7e013d6499"><Label for={`${controlId}-organization-name`}>{isZh ? '组织名称' : 'Organization name'}</Label><div class="svadmin-u-60fbb7713999 svadmin-u-77a2a20e90d4"><Input id={`${controlId}-organization-name`} bind:value={organizationName} disabled={!organizationProvider.updateCurrentOrganization || savingOrganization} /><Button variant="outline" size="sm" disabled={!organizationChanged || savingOrganization} onclick={saveOrganization}>{savingOrganization ? (isZh ? '保存中...' : 'Saving...') : i18n.t('common.save')}</Button></div></div>
|
|
226
223
|
{/if}
|
|
227
224
|
<div class="svadmin-u-f3c543ad5fe9 svadmin-u-0c3bc98565dd svadmin-u-e00ad81645a2">
|
|
228
|
-
<div class="svadmin-u-6f7e013d6499"><Label for=
|
|
229
|
-
<div class="svadmin-u-6f7e013d6499"><Label for=
|
|
225
|
+
<div class="svadmin-u-6f7e013d6499"><Label for={`${controlId}-session-timeout`}>{i18n.t('account.sessionTimeout')}</Label><Input id={`${controlId}-session-timeout`} type="number" min="1" step="1" bind:value={sessionTimeoutValue} disabled={!policy || saving} aria-invalid={policy ? !sessionTimeoutValid : undefined} aria-describedby={policy && !sessionTimeoutValid ? `${controlId}-policy-error` : undefined} /></div>
|
|
226
|
+
<div class="svadmin-u-6f7e013d6499"><Label for={`${controlId}-retention`}>{i18n.t('account.dataRetention')}</Label><Input id={`${controlId}-retention`} type="number" min="1" step="1" bind:value={auditRetentionValue} disabled={!policy || saving} aria-invalid={policy ? !auditRetentionValid : undefined} aria-describedby={policy && !auditRetentionValid ? `${controlId}-policy-error` : undefined} /></div>
|
|
230
227
|
</div>
|
|
231
|
-
{#if policy && !policyNumbersValid}<FeedbackNotice tone="warning" message={isZh ? '会话超时和审计保留天数必须为正整数。' : 'Session timeout and audit retention must be positive integers.'}
|
|
232
|
-
<SettingsFieldRow label={i18n.t('account.auditLogging')} description={isZh ? '由后端持久化组织级审计策略。' : 'Persist the organization audit policy through the backend provider.'} control={auditControl} separated />
|
|
228
|
+
{#if policy && !policyNumbersValid}<div id={`${controlId}-policy-error`}><FeedbackNotice tone="warning" message={isZh ? '会话超时和审计保留天数必须为正整数。' : 'Session timeout and audit retention must be positive integers.'} /></div>{/if}
|
|
229
|
+
<SettingsFieldRow label={i18n.t('account.auditLogging')} controlId={`${controlId}-audit`} description={isZh ? '由后端持久化组织级审计策略。' : 'Persist the organization audit policy through the backend provider.'} control={auditControl} separated />
|
|
233
230
|
<Button size="sm" disabled={!identityProvider || !policy || !policyNumbersValid || saving} onclick={saveSettings}>{saving ? (isZh ? '保存中...' : 'Saving...') : i18n.t('common.save')}</Button>
|
|
234
231
|
</SettingsGroup>
|
|
235
232
|
<SettingsGroup title={i18n.t('account.singleSignOn')} description={i18n.t('account.ssoDescription')} bodyClass="space-y-4">
|
|
236
|
-
<SettingsFieldRow label={i18n.t('account.enableSso')} description={isZh ? '启用后由已配置身份源执行组织登录策略。' : 'Require the configured identity provider for organization members.'} control={ssoControl} />
|
|
237
|
-
<
|
|
233
|
+
<SettingsFieldRow label={i18n.t('account.enableSso')} controlId={`${controlId}-sso`} description={isZh ? '启用后由已配置身份源执行组织登录策略。' : 'Require the configured identity provider for organization members.'} control={ssoControl} />
|
|
234
|
+
<div class="svadmin-u-6f7e013d6499">
|
|
235
|
+
<Label for={`${controlId}-metadata-url`}>{i18n.t('account.ssoProvider')}</Label>
|
|
236
|
+
<Input id={`${controlId}-metadata-url`} type="url" bind:value={metadataUrl} placeholder="https://idp.example.com/.well-known/openid-configuration" disabled={!identityProvider || testing} />
|
|
237
|
+
</div>
|
|
238
238
|
<Button variant="outline" size="sm" disabled={!identityProvider || testing} onclick={() => testConnection()}>{testing ? (isZh ? '测试中...' : 'Testing...') : i18n.t('common.test')}</Button>
|
|
239
239
|
</SettingsGroup>
|
|
240
240
|
</div>
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
import NotificationsSettings from '../NotificationsSettings.svelte';
|
|
4
4
|
import SecuritySettings from '../SecuritySettings.svelte';
|
|
5
5
|
import ContentPageShell from '../content/ContentPageShell.svelte';
|
|
6
|
+
import ContentPageHeader from '../content/ContentPageHeader.svelte';
|
|
7
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
8
|
+
const i18n = useTranslation();
|
|
6
9
|
let { preferencesProvider }: { preferencesProvider?: import('../NotificationsSettings.svelte').NotificationPreferencesProvider } = $props();
|
|
7
10
|
</script>
|
|
8
11
|
|
|
9
12
|
<ContentPageShell pageId="account-settings-plain" width="narrow">
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
+
<ContentPageHeader title={i18n.t('account.settingsPlain')} />
|
|
14
|
+
<ProfilePage headingLevel="h2" />
|
|
15
|
+
<NotificationsSettings headingLevel="h2" {...preferencesProvider ? { preferencesProvider } : {}} />
|
|
16
|
+
<SecuritySettings headingLevel="h2" />
|
|
13
17
|
</ContentPageShell>
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
</nav>
|
|
28
28
|
<div class="svadmin-u-7e0b7cdf1a94">
|
|
29
29
|
{#if active === 'profile'}
|
|
30
|
-
<ProfilePage />
|
|
30
|
+
<ProfilePage headingLevel="h2" />
|
|
31
31
|
{:else if active === 'signin'}
|
|
32
|
-
<IntegrationsSettings />
|
|
32
|
+
<IntegrationsSettings headingLevel="h2" />
|
|
33
33
|
{:else if active === 'preferences'}
|
|
34
|
-
<AppearanceSettings />
|
|
34
|
+
<AppearanceSettings headingLevel="h2" />
|
|
35
35
|
{:else}
|
|
36
36
|
<SettingsGroup title={i18n.t('account.manageApi')} description={isZh ? '使用有作用域的真实凭据,并在权限变更时轮换。' : 'Use scoped credentials and rotate them when access changes.'} bodyClass="space-y-3">
|
|
37
37
|
{#if credentialProvider}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
breadcrumbs?: string[];
|
|
10
10
|
actions?: Snippet;
|
|
11
11
|
density?: 'compact' | 'comfortable';
|
|
12
|
+
headingLevel?: 'h1' | 'h2';
|
|
12
13
|
class?: string;
|
|
13
14
|
}
|
|
14
15
|
let {
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
breadcrumbs = [],
|
|
19
20
|
actions,
|
|
20
21
|
density = 'comfortable',
|
|
22
|
+
headingLevel = 'h1',
|
|
21
23
|
class: className = '',
|
|
22
24
|
}: Props = $props();
|
|
23
25
|
const styles = $derived(contentHeader({ density }));
|
|
@@ -40,7 +42,7 @@
|
|
|
40
42
|
<div class={styles.row}>
|
|
41
43
|
<div class={styles.heading}>
|
|
42
44
|
{#if eyebrow}<p class={styles.eyebrow}>{eyebrow}</p>{/if}
|
|
43
|
-
<
|
|
45
|
+
<svelte:element this={headingLevel} class={styles.title}>{title}</svelte:element>
|
|
44
46
|
{#if description}<p class={styles.description}>{description}</p>{/if}
|
|
45
47
|
</div>
|
|
46
48
|
{#if actions}<div class={styles.actions}>{@render actions()}</div>{/if}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
4
|
+
import ContentPageShell from './ContentPageShell.svelte';
|
|
5
|
+
import WorkspaceLayout from './WorkspaceLayout.svelte';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
title, description, actions, metrics, secondary, children,
|
|
9
|
+
pageId = 'dashboard',
|
|
10
|
+
}: {
|
|
11
|
+
title: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
actions?: Snippet;
|
|
14
|
+
metrics?: Snippet;
|
|
15
|
+
secondary?: Snippet;
|
|
16
|
+
children: Snippet;
|
|
17
|
+
pageId?: string;
|
|
18
|
+
} = $props();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<ContentPageShell {title} {pageId} width="wide" {...definedOptions({ description, actions })}>
|
|
22
|
+
{#if metrics}
|
|
23
|
+
<div class="svadmin-dashboard-metrics" data-svadmin-dashboard-metrics>{@render metrics()}</div>
|
|
24
|
+
{/if}
|
|
25
|
+
<WorkspaceLayout primary={children} {...definedOptions({ secondary })} />
|
|
26
|
+
</ContentPageShell>
|
|
27
|
+
|
|
28
|
+
<style>
|
|
29
|
+
.svadmin-dashboard-metrics {
|
|
30
|
+
display: grid;
|
|
31
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
|
|
32
|
+
gap: 1rem;
|
|
33
|
+
min-width: 0;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
actions?: Snippet;
|
|
6
|
+
metrics?: Snippet;
|
|
7
|
+
secondary?: Snippet;
|
|
8
|
+
children: Snippet;
|
|
9
|
+
pageId?: string;
|
|
10
|
+
};
|
|
11
|
+
declare const DashboardPage: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type DashboardPage = ReturnType<typeof DashboardPage>;
|
|
13
|
+
export default DashboardPage;
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
tone === 'danger'
|
|
28
28
|
? 'svadmin-u-26e4f7bdbd56 svadmin-u-7a0854fdbc30 svadmin-u-811148b13d1e'
|
|
29
29
|
: tone === 'warning'
|
|
30
|
-
? 'svadmin-u-d008dee27eaa svadmin-u-283481e780bb svadmin-u-
|
|
30
|
+
? 'svadmin-u-d008dee27eaa svadmin-u-283481e780bb svadmin-u-d4108abe6359'
|
|
31
31
|
: 'svadmin-u-18049387f0af svadmin-u-b00f43c30c2b svadmin-u-d4108abe6359',
|
|
32
32
|
);
|
|
33
33
|
</script>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
4
|
+
import SectionHeader from './SectionHeader.svelte';
|
|
5
|
+
|
|
6
|
+
let { title, description, actions, children }: {
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
actions?: Snippet;
|
|
10
|
+
children: Snippet;
|
|
11
|
+
} = $props();
|
|
12
|
+
const id = $props.id();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<section class="svadmin-page-section" aria-labelledby={id} data-svadmin-page-section>
|
|
16
|
+
<SectionHeader {id} {title} {...definedOptions({ description, actions })} />
|
|
17
|
+
{@render children()}
|
|
18
|
+
</section>
|
|
19
|
+
|
|
20
|
+
<style>
|
|
21
|
+
.svadmin-page-section {
|
|
22
|
+
display: grid;
|
|
23
|
+
grid-template-columns: minmax(0, 1fr);
|
|
24
|
+
align-content: start;
|
|
25
|
+
gap: 1rem;
|
|
26
|
+
min-width: 0;
|
|
27
|
+
}
|
|
28
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
actions?: Snippet;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const PageSection: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type PageSection = ReturnType<typeof PageSection>;
|
|
10
|
+
export default PageSection;
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { productSettingsRowRecipe as productSettingsRow } from '../../recipes.js';
|
|
4
|
-
interface Props { label: string; description?: string; control: Snippet; separated?: boolean; class?: string; }
|
|
5
|
-
let { label, description, control, separated = false, class: className = '' }: Props = $props();
|
|
4
|
+
interface Props { label: string; description?: string; control: Snippet; controlId?: string; separated?: boolean; class?: string; }
|
|
5
|
+
let { label, description, control, controlId, separated = false, class: className = '' }: Props = $props();
|
|
6
|
+
const labelId = $props.id();
|
|
6
7
|
const styles = $derived(productSettingsRow({ separated }));
|
|
7
8
|
</script>
|
|
8
9
|
<div class={styles.root + ' ' + className} data-svadmin-settings-field-row>
|
|
9
10
|
<div class={styles.heading}>
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
{#if controlId}
|
|
12
|
+
<label id={labelId} for={controlId} class={styles.label}>{label}</label>
|
|
13
|
+
{:else}
|
|
14
|
+
<p id={labelId} class={styles.label}>{label}</p>
|
|
15
|
+
{/if}
|
|
16
|
+
{#if description}<p id={`${labelId}-description`} class={styles.description}>{description}</p>{/if}
|
|
12
17
|
</div>
|
|
13
|
-
<div class={styles.control}>{@render control()}</div>
|
|
18
|
+
<div class={styles.control} role="group" aria-labelledby={labelId} aria-describedby={description ? `${labelId}-description` : undefined}>{@render control()}</div>
|
|
14
19
|
</div>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { productSettingsRecipe as productSettings } from '../../recipes.js';
|
|
4
|
-
interface Props { title: string; description?: string; actions?: Snippet; children: Snippet; class?: string; bodyClass?: string; }
|
|
5
|
-
let { title, description, actions, children, class: className = '', bodyClass = '' }: Props = $props();
|
|
4
|
+
interface Props { title: string; description?: string; actions?: Snippet; children: Snippet; headingLevel?: 'h2' | 'h3'; class?: string; bodyClass?: string; }
|
|
5
|
+
let { title, description, actions, children, headingLevel = 'h2', class: className = '', bodyClass = '' }: Props = $props();
|
|
6
6
|
const styles = productSettings();
|
|
7
7
|
</script>
|
|
8
8
|
<section class={styles.root + ' ' + className} data-svadmin-settings-group>
|
|
9
9
|
<header class={styles.header}>
|
|
10
10
|
<div class={styles.heading}>
|
|
11
|
-
<
|
|
11
|
+
<svelte:element this={headingLevel} class={styles.title}>{title}</svelte:element>
|
|
12
12
|
{#if description}<p class={styles.description}>{description}</p>{/if}
|
|
13
13
|
</div>
|
|
14
14
|
{#if actions}<div class={styles.actions}>{@render actions()}</div>{/if}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { default as ContentPageShell } from './ContentPageShell.svelte';
|
|
2
|
+
export { default as DashboardPage } from './DashboardPage.svelte';
|
|
3
|
+
export { default as PageSection } from './PageSection.svelte';
|
|
2
4
|
export { default as ContentPageHeader } from './ContentPageHeader.svelte';
|
|
3
5
|
export { default as SectionHeader } from './SectionHeader.svelte';
|
|
4
6
|
export { default as PageToolbar } from './PageToolbar.svelte';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { default as ContentPageShell } from './ContentPageShell.svelte';
|
|
2
|
+
export { default as DashboardPage } from './DashboardPage.svelte';
|
|
3
|
+
export { default as PageSection } from './PageSection.svelte';
|
|
2
4
|
export { default as ContentPageHeader } from './ContentPageHeader.svelte';
|
|
3
5
|
export { default as SectionHeader } from './SectionHeader.svelte';
|
|
4
6
|
export { default as PageToolbar } from './PageToolbar.svelte';
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
variant?: ButtonVariantValue;
|
|
17
17
|
size?: ButtonSizeValue;
|
|
18
18
|
disabledReason?: string;
|
|
19
|
+
disabledReasonDisplay?: "tooltip" | "inline";
|
|
19
20
|
restrictionClass?: string;
|
|
20
21
|
restrictionStyle?: string;
|
|
21
22
|
};
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
type = "button",
|
|
35
36
|
disabled,
|
|
36
37
|
disabledReason = "",
|
|
38
|
+
disabledReasonDisplay = "tooltip",
|
|
37
39
|
restrictionClass,
|
|
38
40
|
restrictionStyle,
|
|
39
41
|
children,
|
|
@@ -100,7 +102,21 @@
|
|
|
100
102
|
{/if}
|
|
101
103
|
{/snippet}
|
|
102
104
|
|
|
103
|
-
{#if restriction}
|
|
105
|
+
{#if restriction && disabledReasonDisplay === "inline"}
|
|
106
|
+
<span
|
|
107
|
+
data-slot="button-restriction"
|
|
108
|
+
data-disabled-reason={restriction}
|
|
109
|
+
role="group"
|
|
110
|
+
aria-label={restProps["aria-label"]}
|
|
111
|
+
aria-labelledby={restProps["aria-labelledby"]}
|
|
112
|
+
aria-describedby={describedBy}
|
|
113
|
+
class={restrictionClass}
|
|
114
|
+
style={`display: inline-flex; flex-direction: column; align-items: flex-start; gap: .375rem; min-width: 0; max-width: 100%; ${restrictionStyle ?? ""}`}
|
|
115
|
+
>
|
|
116
|
+
{@render control()}
|
|
117
|
+
<span id={restrictionId} data-slot="button-restriction-description" style="font-size: .875rem; line-height: 1.4; overflow-wrap: anywhere;">{restriction}</span>
|
|
118
|
+
</span>
|
|
119
|
+
{:else if restriction}
|
|
104
120
|
<Tooltip.Root>
|
|
105
121
|
<Tooltip.Trigger>
|
|
106
122
|
{#snippet child({ props })}
|
|
@@ -6,6 +6,7 @@ export type ButtonProps = WithElementRef<HTMLButtonAttributes & HTMLAnchorAttrib
|
|
|
6
6
|
variant?: ButtonVariantValue;
|
|
7
7
|
size?: ButtonSizeValue;
|
|
8
8
|
disabledReason?: string;
|
|
9
|
+
disabledReasonDisplay?: "tooltip" | "inline";
|
|
9
10
|
restrictionClass?: string;
|
|
10
11
|
restrictionStyle?: string;
|
|
11
12
|
};
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
<script lang="ts">
|
|
34
34
|
/* eslint-disable svelte/no-unused-svelte-ignore */
|
|
35
35
|
import { tick } from "svelte";
|
|
36
|
+
import { Portal } from "bits-ui";
|
|
36
37
|
import { cn, type WithElementRef } from "../../../utils.js";
|
|
37
38
|
import type { HTMLAttributes } from "svelte/elements";
|
|
38
39
|
|
|
@@ -159,6 +160,7 @@
|
|
|
159
160
|
<svelte:document onkeydown={handleKeydown} />
|
|
160
161
|
|
|
161
162
|
{#if open}
|
|
163
|
+
<Portal>
|
|
162
164
|
<!-- Overlay -->
|
|
163
165
|
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
|
164
166
|
<div
|
|
@@ -191,4 +193,5 @@
|
|
|
191
193
|
<span class="svadmin-sr-only">{closeLabel}</span>
|
|
192
194
|
</button>
|
|
193
195
|
</div>
|
|
196
|
+
</Portal>
|
|
194
197
|
{/if}
|
package/dist/index.d.ts
CHANGED
|
@@ -98,6 +98,7 @@ export { default as ApprovalActionCard } from './components/ApprovalActionCard.s
|
|
|
98
98
|
export type { ApprovalStatus } from './components/ApprovalActionCard.svelte';
|
|
99
99
|
export { default as ApprovalCenter } from './components/ApprovalCenter.svelte';
|
|
100
100
|
export { default as DashboardView } from './components/DashboardView.svelte';
|
|
101
|
+
export { default as ResourceOverview } from './components/ResourceOverview.svelte';
|
|
101
102
|
export { default as StepForm } from './components/StepForm.svelte';
|
|
102
103
|
export type { FormStep } from './components/StepForm.svelte';
|
|
103
104
|
export { default as ModalForm } from './components/ModalForm.svelte';
|
|
@@ -271,6 +272,8 @@ export { default as UserCardsNFTPage } from './components/network/UserCardsNFTPa
|
|
|
271
272
|
export { default as TeamCrewTablePage } from './components/network/TeamCrewTablePage.svelte';
|
|
272
273
|
export { default as TwoFactorAuthPage } from './components/TwoFactorAuthPage.svelte';
|
|
273
274
|
export { default as ContentPageShell } from './components/content/ContentPageShell.svelte';
|
|
275
|
+
export { default as DashboardPage } from './components/content/DashboardPage.svelte';
|
|
276
|
+
export { default as PageSection } from './components/content/PageSection.svelte';
|
|
274
277
|
export { default as ContentPageHeader } from './components/content/ContentPageHeader.svelte';
|
|
275
278
|
export { default as SectionHeader } from './components/content/SectionHeader.svelte';
|
|
276
279
|
export { default as PageToolbar } from './components/content/PageToolbar.svelte';
|
package/dist/index.js
CHANGED
|
@@ -82,6 +82,7 @@ export { default as SensitiveDataMask } from './components/SensitiveDataMask.sve
|
|
|
82
82
|
export { default as ApprovalActionCard } from './components/ApprovalActionCard.svelte';
|
|
83
83
|
export { default as ApprovalCenter } from './components/ApprovalCenter.svelte';
|
|
84
84
|
export { default as DashboardView } from './components/DashboardView.svelte';
|
|
85
|
+
export { default as ResourceOverview } from './components/ResourceOverview.svelte';
|
|
85
86
|
export { default as StepForm } from './components/StepForm.svelte';
|
|
86
87
|
export { default as ModalForm } from './components/ModalForm.svelte';
|
|
87
88
|
export { default as DrawerForm } from './components/DrawerForm.svelte';
|
|
@@ -241,6 +242,8 @@ export { default as TeamCrewTablePage } from './components/network/TeamCrewTable
|
|
|
241
242
|
export { default as TwoFactorAuthPage } from './components/TwoFactorAuthPage.svelte';
|
|
242
243
|
// Admin UI content page and domain primitives
|
|
243
244
|
export { default as ContentPageShell } from './components/content/ContentPageShell.svelte';
|
|
245
|
+
export { default as DashboardPage } from './components/content/DashboardPage.svelte';
|
|
246
|
+
export { default as PageSection } from './components/content/PageSection.svelte';
|
|
244
247
|
export { default as ContentPageHeader } from './components/content/ContentPageHeader.svelte';
|
|
245
248
|
export { default as SectionHeader } from './components/content/SectionHeader.svelte';
|
|
246
249
|
export { default as PageToolbar } from './components/content/PageToolbar.svelte';
|
package/dist/page-chrome.css
CHANGED
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
.svadmin-filter-toolbar__search-icon {
|
|
107
107
|
position: absolute;
|
|
108
108
|
top: 50%;
|
|
109
|
-
|
|
109
|
+
inset-inline-start: 0.75rem;
|
|
110
110
|
width: var(--svadmin-filter-search-icon-size);
|
|
111
111
|
height: var(--svadmin-filter-search-icon-size);
|
|
112
112
|
color: var(--muted-foreground);
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
.svadmin-filter-toolbar[data-density] .svadmin-filter-toolbar__clear {
|
|
123
123
|
position: absolute;
|
|
124
124
|
top: 50%;
|
|
125
|
-
|
|
125
|
+
inset-inline-end: 0.25rem;
|
|
126
126
|
transform: translateY(-50%);
|
|
127
127
|
}
|
|
128
128
|
.svadmin-filter-toolbar[data-density] .svadmin-filter-toolbar__toggle {
|
|
@@ -144,14 +144,14 @@
|
|
|
144
144
|
.svadmin-filter-toolbar .svadmin-filter-toolbar__chevron {
|
|
145
145
|
width: 0.75rem;
|
|
146
146
|
height: 0.75rem;
|
|
147
|
-
margin-
|
|
147
|
+
margin-inline-start: 0.125rem;
|
|
148
148
|
color: var(--muted-foreground);
|
|
149
149
|
}
|
|
150
150
|
.svadmin-filter-toolbar[data-density] .svadmin-filter-toolbar__count {
|
|
151
151
|
height: 1rem;
|
|
152
152
|
min-height: 1rem;
|
|
153
153
|
min-width: 1rem;
|
|
154
|
-
margin-
|
|
154
|
+
margin-inline-start: 0.125rem;
|
|
155
155
|
padding: 0 0.25rem;
|
|
156
156
|
font-size: 0.625rem;
|
|
157
157
|
font-weight: 600;
|