@svadmin/ui 0.42.2 → 0.43.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 (78) hide show
  1. package/dist/app.css +482 -840
  2. package/dist/app.theme.css +482 -840
  3. package/dist/components/ErrorPage.svelte +4 -56
  4. package/dist/components/Layout.svelte +1 -1
  5. package/dist/components/Sidebar.svelte +1 -0
  6. package/dist/components/TwoFactorAuthPage.svelte +44 -232
  7. package/dist/components/account/CompanyProfilePage.svelte +21 -142
  8. package/dist/components/account/GetStartedPage.svelte +33 -115
  9. package/dist/components/account/GetStartedPage.svelte.d.ts +1 -2
  10. package/dist/components/account/SecurityLogPage.svelte +16 -131
  11. package/dist/components/account/SettingsEnterprisePage.svelte +15 -275
  12. package/dist/components/account/SettingsPlainPage.svelte +10 -175
  13. package/dist/components/account/SettingsSidebarPage.svelte +11 -304
  14. package/dist/components/account/TeamMembersPage.svelte +18 -187
  15. package/dist/components/account/UserProfilePage.svelte +19 -196
  16. package/dist/components/account/UserProfilePage.svelte.d.ts +1 -2
  17. package/dist/components/content/ApiKeyList.svelte +13 -0
  18. package/dist/components/content/ApiKeyList.svelte.d.ts +16 -0
  19. package/dist/components/content/ContentPageHeader.svelte +31 -0
  20. package/dist/components/content/ContentPageHeader.svelte.d.ts +12 -0
  21. package/dist/components/content/ContentPageShell.svelte +39 -0
  22. package/dist/components/content/ContentPageShell.svelte.d.ts +14 -0
  23. package/dist/components/content/DataState.svelte +44 -0
  24. package/dist/components/content/DataState.svelte.d.ts +13 -0
  25. package/dist/components/content/DescriptionList.svelte +27 -0
  26. package/dist/components/content/DescriptionList.svelte.d.ts +14 -0
  27. package/dist/components/content/FileList.svelte +13 -0
  28. package/dist/components/content/FileList.svelte.d.ts +16 -0
  29. package/dist/components/content/FilterToolbar.svelte +24 -0
  30. package/dist/components/content/FilterToolbar.svelte.d.ts +11 -0
  31. package/dist/components/content/ImportDropzone.svelte +19 -0
  32. package/dist/components/content/ImportDropzone.svelte.d.ts +10 -0
  33. package/dist/components/content/IntegrationCard.svelte +16 -0
  34. package/dist/components/content/IntegrationCard.svelte.d.ts +15 -0
  35. package/dist/components/content/MemberList.svelte +16 -0
  36. package/dist/components/content/MemberList.svelte.d.ts +17 -0
  37. package/dist/components/content/MetricBlock.svelte +27 -0
  38. package/dist/components/content/MetricBlock.svelte.d.ts +13 -0
  39. package/dist/components/content/NetworkTable.svelte +18 -0
  40. package/dist/components/content/NetworkTable.svelte.d.ts +31 -0
  41. package/dist/components/content/NetworkTable.types.d.ts +4 -0
  42. package/dist/components/content/NetworkTable.types.js +1 -0
  43. package/dist/components/content/NetworkUserCard.svelte +17 -0
  44. package/dist/components/content/NetworkUserCard.svelte.d.ts +17 -0
  45. package/dist/components/content/OtpInput.svelte +22 -0
  46. package/dist/components/content/OtpInput.svelte.d.ts +10 -0
  47. package/dist/components/content/PageToolbar.svelte +18 -0
  48. package/dist/components/content/PageToolbar.svelte.d.ts +10 -0
  49. package/dist/components/content/ProjectCard.svelte +39 -0
  50. package/dist/components/content/ProjectCard.svelte.d.ts +20 -0
  51. package/dist/components/content/SectionHeader.svelte +18 -0
  52. package/dist/components/content/SectionHeader.svelte.d.ts +10 -0
  53. package/dist/components/content/SecurityEventTable.svelte +18 -0
  54. package/dist/components/content/SecurityEventTable.svelte.d.ts +15 -0
  55. package/dist/components/content/StatusBadge.svelte +19 -0
  56. package/dist/components/content/StatusBadge.svelte.d.ts +9 -0
  57. package/dist/components/content/SystemErrorState.svelte +18 -0
  58. package/dist/components/content/SystemErrorState.svelte.d.ts +10 -0
  59. package/dist/components/content/TeamCard.svelte +26 -0
  60. package/dist/components/content/TeamCard.svelte.d.ts +23 -0
  61. package/dist/components/content/TwoFactorStepper.svelte +11 -0
  62. package/dist/components/content/TwoFactorStepper.svelte.d.ts +8 -0
  63. package/dist/components/content/index.d.ts +32 -0
  64. package/dist/components/content/index.js +22 -0
  65. package/dist/components/network/TeamCrewTablePage.svelte +29 -181
  66. package/dist/components/network/UserCardsNFTPage.svelte +24 -137
  67. package/dist/components/profile/ActivityTimeline.svelte +17 -91
  68. package/dist/components/profile/ProfileCard.svelte +4 -4
  69. package/dist/components/profile/ProjectsGrid.svelte +28 -106
  70. package/dist/components/profile/ProjectsGrid.svelte.d.ts +2 -12
  71. package/dist/components/profile/PublicProfilePage.svelte +31 -181
  72. package/dist/components/profile/TeamsShowcase.svelte +12 -114
  73. package/dist/components/profile/TeamsShowcase.svelte.d.ts +2 -12
  74. package/dist/index.d.ts +36 -0
  75. package/dist/index.js +24 -0
  76. package/dist/reference-data.d.ts +171 -0
  77. package/dist/reference-data.js +39 -0
  78. package/package.json +1 -1
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ export type DataStateKind = 'loading' | 'empty' | 'error' | 'forbidden';
3
+ interface Props {
4
+ state: DataStateKind;
5
+ title?: string;
6
+ description?: string;
7
+ retry?: () => void;
8
+ action?: Snippet;
9
+ class?: string;
10
+ }
11
+ declare const DataState: import("svelte").Component<Props, {}, "">;
12
+ type DataState = ReturnType<typeof DataState>;
13
+ export default DataState;
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ export interface DescriptionItem {
3
+ label: string;
4
+ value: string | number;
5
+ description?: string;
6
+ href?: string;
7
+ }
8
+ interface Props {
9
+ items?: DescriptionItem[];
10
+ columns?: 1 | 2;
11
+ class?: string;
12
+ }
13
+ let { items = [], columns = 1, class: className = '' }: Props = $props();
14
+ const columnClass = $derived(columns === 2 ? 'sm:grid-cols-2' : 'grid-cols-1');
15
+ </script>
16
+
17
+ <dl class={'grid gap-x-6 gap-y-5 ' + columnClass + ' ' + className}>
18
+ {#each items as item (item.label)}
19
+ <div class="min-w-0">
20
+ <dt class="text-xs font-medium text-muted-foreground">{item.label}</dt>
21
+ <dd class="mt-1 break-words text-sm font-medium text-foreground">
22
+ {#if item.href}<a class="text-primary underline-offset-4 hover:underline" href={item.href}>{item.value}</a>{:else}{item.value}{/if}
23
+ </dd>
24
+ {#if item.description}<p class="mt-1 text-xs text-muted-foreground">{item.description}</p>{/if}
25
+ </div>
26
+ {/each}
27
+ </dl>
@@ -0,0 +1,14 @@
1
+ export interface DescriptionItem {
2
+ label: string;
3
+ value: string | number;
4
+ description?: string;
5
+ href?: string;
6
+ }
7
+ interface Props {
8
+ items?: DescriptionItem[];
9
+ columns?: 1 | 2;
10
+ class?: string;
11
+ }
12
+ declare const DescriptionList: import("svelte").Component<Props, {}, "">;
13
+ type DescriptionList = ReturnType<typeof DescriptionList>;
14
+ export default DescriptionList;
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import { Download, File, Trash2 } from '@lucide/svelte';
3
+ import { Button } from '../ui/button/index.js';
4
+ export interface FileItem { id?: string; name: string; size?: string; type?: string; updatedAt?: string; }
5
+ interface Props { files?: FileItem[]; ondownload?: (file: FileItem) => void; onremove?: (file: FileItem) => void; class?: string; }
6
+ let { files = [], ondownload, onremove, class: className = '' }: Props = $props();
7
+ </script>
8
+ <div class={'divide-y divide-border overflow-hidden rounded-lg border border-border bg-card ' + className}>
9
+ {#each files as file, index (file.id ?? file.name + '-' + index)}
10
+ <div class="flex items-center gap-3 px-4 py-3"><span class="flex size-9 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground"><File class="size-4" /></span><div class="min-w-0 flex-1"><p class="truncate text-sm font-medium text-foreground">{file.name}</p><p class="text-xs text-muted-foreground">{file.size ?? file.type ?? 'File'}{file.updatedAt ? ' / ' + file.updatedAt : ''}</p></div>{#if ondownload}<Button variant="ghost" size="icon-sm" aria-label={'Download ' + file.name} onclick={() => ondownload?.(file)}><Download class="size-4" /></Button>{/if}{#if onremove}<Button variant="ghost" size="icon-sm" aria-label={'Remove ' + file.name} onclick={() => onremove?.(file)}><Trash2 class="size-4" /></Button>{/if}</div>
11
+ {/each}
12
+ {#if files.length === 0}<div class="p-6 text-center text-sm text-muted-foreground">No files</div>{/if}
13
+ </div>
@@ -0,0 +1,16 @@
1
+ export interface FileItem {
2
+ id?: string;
3
+ name: string;
4
+ size?: string;
5
+ type?: string;
6
+ updatedAt?: string;
7
+ }
8
+ interface Props {
9
+ files?: FileItem[];
10
+ ondownload?: (file: FileItem) => void;
11
+ onremove?: (file: FileItem) => void;
12
+ class?: string;
13
+ }
14
+ declare const FileList: import("svelte").Component<Props, {}, "">;
15
+ type FileList = ReturnType<typeof FileList>;
16
+ export default FileList;
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { Search, X } from '@lucide/svelte';
4
+ import { Button } from '../ui/button/index.js';
5
+ import { Input } from '../ui/input/index.js';
6
+ interface Props {
7
+ query?: string;
8
+ placeholder?: string;
9
+ filters?: Snippet;
10
+ actions?: Snippet;
11
+ class?: string;
12
+ }
13
+ let { query = $bindable(''), placeholder = 'Search', filters, actions, class: className = '' }: Props = $props();
14
+ </script>
15
+
16
+ <div class={'flex flex-col gap-2 sm:flex-row sm:items-center ' + className}>
17
+ <div class="relative min-w-0 flex-1">
18
+ <Search class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
19
+ <Input bind:value={query} {placeholder} class="pl-9 pr-9" aria-label={placeholder} />
20
+ {#if query}<Button variant="ghost" size="icon-xs" class="absolute right-1 top-1/2 -translate-y-1/2" aria-label="Clear search" onclick={() => query = ''}><X class="size-3.5" /></Button>{/if}
21
+ </div>
22
+ {#if filters}<div class="flex flex-wrap items-center gap-2">{@render filters()}</div>{/if}
23
+ {#if actions}<div class="flex shrink-0 flex-wrap items-center gap-2">{@render actions()}</div>{/if}
24
+ </div>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ query?: string;
4
+ placeholder?: string;
5
+ filters?: Snippet;
6
+ actions?: Snippet;
7
+ class?: string;
8
+ }
9
+ declare const FilterToolbar: import("svelte").Component<Props, {}, "query">;
10
+ type FilterToolbar = ReturnType<typeof FilterToolbar>;
11
+ export default FilterToolbar;
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import { UploadCloud } from '@lucide/svelte';
3
+ import { Button } from '../ui/button/index.js';
4
+ import { Input } from '../ui/input/index.js';
5
+ interface Props { accept?: string; loading?: boolean; status?: string; onimport?: (files: FileList | null) => void; class?: string; }
6
+ let { accept = '.csv,.xlsx', loading = false, status = '', onimport, class: className = '' }: Props = $props();
7
+ let files = $state<FileList | null>(null);
8
+ function choose(event: Event) {
9
+ files = (event.currentTarget as HTMLInputElement).files;
10
+ if (files?.length) onimport?.(files);
11
+ }
12
+ </script>
13
+ <div class={'rounded-lg border border-dashed border-border bg-card p-6 text-center ' + className} data-svadmin-import-dropzone>
14
+ <UploadCloud class="mx-auto size-8 text-muted-foreground" /><p class="mt-3 text-sm font-medium text-foreground">Import records</p><p class="mt-1 text-xs text-muted-foreground">CSV or spreadsheet files are accepted.</p>
15
+ <Input class="mx-auto mt-4 max-w-sm" type="file" {accept} onchange={choose} disabled={loading} />
16
+ {#if loading}<p class="mt-3 text-xs text-muted-foreground" aria-live="polite">Importing...</p>{/if}
17
+ {#if status}<p class="mt-3 text-xs text-success" role="status">{status}</p>{/if}
18
+ {#if !loading && files?.length}<Button class="mt-4" size="sm" onclick={() => onimport?.(files)}>Import {files.length} file{files.length === 1 ? '' : 's'}</Button>{/if}
19
+ </div>
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ accept?: string;
3
+ loading?: boolean;
4
+ status?: string;
5
+ onimport?: (files: FileList | null) => void;
6
+ class?: string;
7
+ }
8
+ declare const ImportDropzone: import("svelte").Component<Props, {}, "">;
9
+ type ImportDropzone = ReturnType<typeof ImportDropzone>;
10
+ export default ImportDropzone;
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import { Check, ExternalLink, PlugZap } from '@lucide/svelte';
3
+ import { Button } from '../ui/button/index.js';
4
+ import { Badge } from '../ui/badge/index.js';
5
+ import * as Card from '../ui/card/index.js';
6
+ export interface IntegrationSummary { id: string; name: string; description?: string; account?: string; connected?: boolean; }
7
+ interface Props { integration: IntegrationSummary; onconnect?: () => void; class?: string; }
8
+ let { integration, onconnect, class: className = '' }: Props = $props();
9
+ </script>
10
+ <Card.Card class={className}>
11
+ <Card.CardContent class="flex items-start gap-3 p-4">
12
+ <span class="flex size-9 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground"><PlugZap class="size-4" /></span>
13
+ <div class="min-w-0 flex-1"><div class="flex flex-wrap items-center gap-2"><h3 class="text-sm font-semibold text-foreground">{integration.name}</h3>{#if integration.connected}<Badge variant="outline" class="border-success/30 bg-success/10 text-success"><Check class="size-3" />Connected</Badge>{/if}</div><p class="mt-1 text-sm text-muted-foreground">{integration.description ?? 'Connect this provider to unlock synchronized workflows.'}</p>{#if integration.account}<p class="mt-2 text-xs text-muted-foreground">{integration.account}</p>{/if}</div>
14
+ {#if onconnect}<Button variant={integration.connected ? 'outline' : 'default'} size="sm" onclick={onconnect}>{integration.connected ? 'Manage' : 'Connect'}{#if !integration.connected}<ExternalLink class="size-3.5" />{/if}</Button>{/if}
15
+ </Card.CardContent>
16
+ </Card.Card>
@@ -0,0 +1,15 @@
1
+ export interface IntegrationSummary {
2
+ id: string;
3
+ name: string;
4
+ description?: string;
5
+ account?: string;
6
+ connected?: boolean;
7
+ }
8
+ interface Props {
9
+ integration: IntegrationSummary;
10
+ onconnect?: () => void;
11
+ class?: string;
12
+ }
13
+ declare const IntegrationCard: import("svelte").Component<Props, {}, "">;
14
+ type IntegrationCard = ReturnType<typeof IntegrationCard>;
15
+ export default IntegrationCard;
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import { MoreHorizontal, UserRound } from '@lucide/svelte';
3
+ import { Avatar } from '../ui/avatar/index.js';
4
+ import { Button } from '../ui/button/index.js';
5
+ import StatusBadge from './StatusBadge.svelte';
6
+ export interface MemberSummary { id: string; name: string; email?: string; role?: string; department?: string; status?: 'success' | 'warning' | 'danger' | 'info' | 'neutral'; avatar?: string; }
7
+ interface Props { members?: MemberSummary[]; onaction?: (member: MemberSummary) => void; class?: string; }
8
+ let { members = [], onaction, class: className = '' }: Props = $props();
9
+ const initials = (name: string) => name.split(' ').map((part) => part[0]).join('').slice(0, 2).toUpperCase();
10
+ </script>
11
+ <div class={'divide-y divide-border overflow-hidden rounded-lg border border-border bg-card ' + className}>
12
+ {#each members as member (member.id)}
13
+ <div class="flex items-center gap-3 px-4 py-3"><Avatar src={member.avatar} alt={member.name} fallback={initials(member.name)} size="sm" /><div class="min-w-0 flex-1"><p class="truncate text-sm font-medium text-foreground">{member.name}</p><p class="truncate text-xs text-muted-foreground">{member.email ?? member.department ?? 'Team member'}</p></div>{#if member.role}<span class="hidden text-xs text-muted-foreground sm:inline">{member.role}</span>{/if}{#if member.status}<StatusBadge status={member.status} label={member.status} />{/if}{#if onaction}<Button variant="ghost" size="icon-sm" aria-label={'Actions for ' + member.name} onclick={() => onaction?.(member)}><MoreHorizontal class="size-4" /></Button>{/if}</div>
14
+ {/each}
15
+ {#if members.length === 0}<div class="flex flex-col items-center gap-2 p-6 text-center text-sm text-muted-foreground"><UserRound class="size-6" />No members</div>{/if}
16
+ </div>
@@ -0,0 +1,17 @@
1
+ export interface MemberSummary {
2
+ id: string;
3
+ name: string;
4
+ email?: string;
5
+ role?: string;
6
+ department?: string;
7
+ status?: 'success' | 'warning' | 'danger' | 'info' | 'neutral';
8
+ avatar?: string;
9
+ }
10
+ interface Props {
11
+ members?: MemberSummary[];
12
+ onaction?: (member: MemberSummary) => void;
13
+ class?: string;
14
+ }
15
+ declare const MemberList: import("svelte").Component<Props, {}, "">;
16
+ type MemberList = ReturnType<typeof MemberList>;
17
+ export default MemberList;
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { Skeleton } from '../ui/skeleton/index.js';
4
+ interface Props {
5
+ label: string;
6
+ value: string | number;
7
+ detail?: string;
8
+ trend?: string;
9
+ icon?: Snippet;
10
+ loading?: boolean;
11
+ class?: string;
12
+ }
13
+ let { label, value, detail, trend, icon, loading = false, class: className = '' }: Props = $props();
14
+ </script>
15
+
16
+ <div data-svadmin-metric-card class={'min-w-0 rounded-lg border border-border bg-card p-4 shadow-sm ' + className}>
17
+ <div class="flex items-start justify-between gap-3">
18
+ <p class="text-sm text-muted-foreground">{label}</p>
19
+ {#if icon}<span class="text-muted-foreground">{@render icon()}</span>{/if}
20
+ </div>
21
+ {#if loading}
22
+ <Skeleton class="mt-3 h-8 w-24" />
23
+ {:else}
24
+ <p class="mt-2 truncate text-2xl font-semibold tracking-normal text-foreground">{value}</p>
25
+ {#if detail || trend}<div class="mt-2 flex flex-wrap gap-2 text-xs">{#if trend}<span class="font-medium text-success">{trend}</span>{/if}{#if detail}<span class="text-muted-foreground">{detail}</span>{/if}</div>{/if}
26
+ {/if}
27
+ </div>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ label: string;
4
+ value: string | number;
5
+ detail?: string;
6
+ trend?: string;
7
+ icon?: Snippet;
8
+ loading?: boolean;
9
+ class?: string;
10
+ }
11
+ declare const MetricBlock: import("svelte").Component<Props, {}, "">;
12
+ type MetricBlock = ReturnType<typeof MetricBlock>;
13
+ export default MetricBlock;
@@ -0,0 +1,18 @@
1
+ <script lang="ts" generics="T extends object">
2
+ import type { Snippet } from 'svelte';
3
+ import * as Table from '../ui/table/index.js';
4
+ import type { NetworkColumn } from './NetworkTable.types.js';
5
+ interface Props { rows?: T[]; columns: NetworkColumn<T>[]; row?: Snippet<[T]>; class?: string; }
6
+ let { rows = [], columns, row, class: className = '' }: Props = $props();
7
+ </script>
8
+ <div class={'overflow-x-auto rounded-lg border border-border bg-card ' + className}>
9
+ <Table.Root data-svadmin-datatable>
10
+ <Table.Header data-svadmin-table-head><Table.Row>{#each columns as column (String(column.key))}<Table.Head>{column.label}</Table.Head>{/each}</Table.Row></Table.Header>
11
+ <Table.Body>
12
+ {#each rows as item, index (String('id' in item ? item.id : index))}
13
+ {#if row}{@render row(item)}{:else}<Table.Row data-svadmin-table-row>{#each columns as column (String(column.key))}<Table.Cell>{String(item[column.key] ?? '')}</Table.Cell>{/each}</Table.Row>{/if}
14
+ {/each}
15
+ </Table.Body>
16
+ </Table.Root>
17
+ {#if rows.length === 0}<div class="p-6 text-center text-sm text-muted-foreground">No records</div>{/if}
18
+ </div>
@@ -0,0 +1,31 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { NetworkColumn } from './NetworkTable.types.js';
3
+ declare function $$render<T extends object>(): {
4
+ props: {
5
+ rows?: T[];
6
+ columns: NetworkColumn<T>[];
7
+ row?: Snippet<[T]>;
8
+ class?: string;
9
+ };
10
+ exports: {};
11
+ bindings: "";
12
+ slots: {};
13
+ events: {};
14
+ };
15
+ declare class __sveltets_Render<T extends object> {
16
+ props(): ReturnType<typeof $$render<T>>['props'];
17
+ events(): ReturnType<typeof $$render<T>>['events'];
18
+ slots(): ReturnType<typeof $$render<T>>['slots'];
19
+ bindings(): "";
20
+ exports(): {};
21
+ }
22
+ interface $$IsomorphicComponent {
23
+ new <T extends object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
24
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
25
+ } & ReturnType<__sveltets_Render<T>['exports']>;
26
+ <T extends object>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
27
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
28
+ }
29
+ declare const NetworkTable: $$IsomorphicComponent;
30
+ type NetworkTable<T extends object> = InstanceType<typeof NetworkTable<T>>;
31
+ export default NetworkTable;
@@ -0,0 +1,4 @@
1
+ export interface NetworkColumn<T extends object> {
2
+ key: keyof T;
3
+ label: string;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import { MessageSquare, UserPlus } from '@lucide/svelte';
3
+ import { Avatar } from '../ui/avatar/index.js';
4
+ import { Button } from '../ui/button/index.js';
5
+ import * as Card from '../ui/card/index.js';
6
+ export interface NetworkUser { id: string; name: string; handle?: string; role?: string; avatar?: string; summary?: string; }
7
+ interface Props { user: NetworkUser; onconnect?: () => void; onmessage?: () => void; class?: string; }
8
+ let { user, onconnect, onmessage, class: className = '' }: Props = $props();
9
+ const initials = $derived(user.name.split(' ').map((part) => part[0]).join('').slice(0, 2).toUpperCase());
10
+ </script>
11
+ <Card.Card class={className}>
12
+ <Card.CardContent class="flex items-start gap-3 p-4">
13
+ <Avatar src={user.avatar} alt={user.name} fallback={initials} size="lg" />
14
+ <div class="min-w-0 flex-1"><h3 class="truncate text-sm font-semibold text-foreground">{user.name}</h3>{#if user.handle}<p class="truncate text-xs text-muted-foreground">{user.handle}</p>{/if}{#if user.role}<p class="mt-2 text-xs font-medium text-foreground">{user.role}</p>{/if}{#if user.summary}<p class="mt-1 line-clamp-2 text-xs text-muted-foreground">{user.summary}</p>{/if}</div>
15
+ <div class="flex shrink-0 gap-1">{#if onmessage}<Button variant="ghost" size="icon-sm" aria-label={'Message ' + user.name} onclick={onmessage}><MessageSquare class="size-4" /></Button>{/if}{#if onconnect}<Button variant="outline" size="icon-sm" aria-label={'Connect with ' + user.name} onclick={onconnect}><UserPlus class="size-4" /></Button>{/if}</div>
16
+ </Card.CardContent>
17
+ </Card.Card>
@@ -0,0 +1,17 @@
1
+ export interface NetworkUser {
2
+ id: string;
3
+ name: string;
4
+ handle?: string;
5
+ role?: string;
6
+ avatar?: string;
7
+ summary?: string;
8
+ }
9
+ interface Props {
10
+ user: NetworkUser;
11
+ onconnect?: () => void;
12
+ onmessage?: () => void;
13
+ class?: string;
14
+ }
15
+ declare const NetworkUserCard: import("svelte").Component<Props, {}, "">;
16
+ type NetworkUserCard = ReturnType<typeof NetworkUserCard>;
17
+ export default NetworkUserCard;
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import { Input } from '../ui/input/index.js';
3
+ interface Props { value?: string[]; length?: number; disabled?: boolean; 'aria-label'?: string; class?: string; }
4
+ let { value = $bindable<string[]>([]), length = 6, disabled = false, 'aria-label': ariaLabel = 'One-time password', class: className = '' }: Props = $props();
5
+ const slots = $derived(Array.from({ length }, (_, index) => index));
6
+ function updateDigit(event: Event, index: number) {
7
+ const input = event.currentTarget as HTMLInputElement;
8
+ const digit = input.value.replace(/\D/g, '').slice(-1);
9
+ const next = [...value];
10
+ next[index] = digit;
11
+ value = next;
12
+ if (digit && index < length - 1) {
13
+ const sibling = input.parentElement?.querySelectorAll('input')[index + 1] as HTMLInputElement | undefined;
14
+ sibling?.focus();
15
+ }
16
+ }
17
+ </script>
18
+ <div class={'flex justify-center gap-2 ' + className} role="group" aria-label={ariaLabel}>
19
+ {#each slots as index (index)}
20
+ <Input class="size-12 text-center text-lg font-semibold" value={value[index] ?? ''} maxlength={1} inputmode="numeric" pattern="[0-9]*" aria-label={ariaLabel + ' digit ' + (index + 1)} {disabled} oninput={(event) => updateDigit(event, index)} />
21
+ {/each}
22
+ </div>
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ value?: string[];
3
+ length?: number;
4
+ disabled?: boolean;
5
+ 'aria-label'?: string;
6
+ class?: string;
7
+ }
8
+ declare const OtpInput: import("svelte").Component<Props, {}, "value">;
9
+ type OtpInput = ReturnType<typeof OtpInput>;
10
+ export default OtpInput;
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ interface Props {
4
+ leading?: Snippet;
5
+ trailing?: Snippet;
6
+ children?: Snippet;
7
+ class?: string;
8
+ }
9
+ let { leading, trailing, children, class: className = '' }: Props = $props();
10
+ </script>
11
+
12
+ <div data-svadmin-table-toolbar class={'flex flex-col gap-3 rounded-lg border border-border bg-card p-3 shadow-sm sm:flex-row sm:items-center sm:justify-between ' + className}>
13
+ <div class="flex min-w-0 flex-1 flex-wrap items-center gap-2">
14
+ {#if leading}{@render leading()}{/if}
15
+ {#if children}{@render children()}{/if}
16
+ </div>
17
+ {#if trailing}<div class="flex shrink-0 flex-wrap items-center gap-2">{@render trailing()}</div>{/if}
18
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ leading?: Snippet;
4
+ trailing?: Snippet;
5
+ children?: Snippet;
6
+ class?: string;
7
+ }
8
+ declare const PageToolbar: import("svelte").Component<Props, {}, "">;
9
+ type PageToolbar = ReturnType<typeof PageToolbar>;
10
+ export default PageToolbar;
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ import { ArrowRight, CheckCircle2, FolderKanban, Users } from '@lucide/svelte';
3
+ import { Button } from '../ui/button/index.js';
4
+ import * as Card from '../ui/card/index.js';
5
+ import { Badge } from '../ui/badge/index.js';
6
+ import Progress from '../ui/progress/progress.svelte';
7
+ import StatusBadge from './StatusBadge.svelte';
8
+ export type ProjectStatus = 'active' | 'completed' | 'on-hold' | 'draft';
9
+ export interface ProjectSummary {
10
+ id: string;
11
+ name: string;
12
+ description?: string;
13
+ status: ProjectStatus;
14
+ members?: number;
15
+ tasks?: number;
16
+ progress?: number;
17
+ tags?: string[];
18
+ image?: string;
19
+ }
20
+ interface Props { project: ProjectSummary; onclick?: () => void; class?: string; }
21
+ let { project, onclick, class: className = '' }: Props = $props();
22
+ const progress = $derived(project.progress ?? (project.status === 'completed' ? 100 : project.status === 'active' ? 72 : 0));
23
+ </script>
24
+ <Card.Card data-interactive={onclick ? 'true' : undefined} class={'h-full ' + className}>
25
+ {#if project.image}<img src={project.image} alt={project.name} class="h-32 w-full object-cover" />{/if}
26
+ <Card.CardContent class="flex h-full flex-col gap-4 p-4">
27
+ <div class="flex items-start justify-between gap-3">
28
+ <div class="flex min-w-0 items-center gap-3"><span class="flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-muted/50 text-muted-foreground"><FolderKanban class="size-4" /></span><h3 class="truncate text-sm font-semibold text-foreground">{project.name}</h3></div>
29
+ <StatusBadge status={project.status === 'active' ? 'success' : project.status === 'completed' ? 'info' : project.status === 'on-hold' ? 'warning' : 'neutral'} label={project.status} />
30
+ </div>
31
+ {#if project.description}<p class="line-clamp-2 text-sm text-muted-foreground">{project.description}</p>{/if}
32
+ {#if project.tags?.length}<div class="flex flex-wrap gap-1.5">{#each project.tags as tag (tag)}<Badge variant="outline">{tag}</Badge>{/each}</div>{/if}
33
+ <div class="mt-auto space-y-2">
34
+ <div class="flex items-center justify-between text-xs text-muted-foreground"><span>Progress</span><span class="font-medium text-foreground">{progress}%</span></div>
35
+ <Progress value={progress} class="h-1.5" />
36
+ <div class="flex items-center justify-between border-t border-border pt-3 text-xs text-muted-foreground"><span class="flex items-center gap-1"><Users class="size-3.5" />{project.members ?? 0}</span><span class="flex items-center gap-1"><CheckCircle2 class="size-3.5" />{project.tasks ?? 0}</span>{#if onclick}<Button variant="ghost" size="icon-sm" aria-label={project.name} onclick={onclick}><ArrowRight class="size-3.5" /></Button>{/if}</div>
37
+ </div>
38
+ </Card.CardContent>
39
+ </Card.Card>
@@ -0,0 +1,20 @@
1
+ export type ProjectStatus = 'active' | 'completed' | 'on-hold' | 'draft';
2
+ export interface ProjectSummary {
3
+ id: string;
4
+ name: string;
5
+ description?: string;
6
+ status: ProjectStatus;
7
+ members?: number;
8
+ tasks?: number;
9
+ progress?: number;
10
+ tags?: string[];
11
+ image?: string;
12
+ }
13
+ interface Props {
14
+ project: ProjectSummary;
15
+ onclick?: () => void;
16
+ class?: string;
17
+ }
18
+ declare const ProjectCard: import("svelte").Component<Props, {}, "">;
19
+ type ProjectCard = ReturnType<typeof ProjectCard>;
20
+ export default ProjectCard;
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ interface Props {
4
+ title: string;
5
+ description?: string;
6
+ actions?: Snippet;
7
+ class?: string;
8
+ }
9
+ let { title, description, actions, class: className = '' }: Props = $props();
10
+ </script>
11
+
12
+ <div class={'flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between ' + className}>
13
+ <div class="min-w-0 space-y-1">
14
+ <h2 class="text-base font-semibold text-foreground">{title}</h2>
15
+ {#if description}<p class="text-sm text-muted-foreground">{description}</p>{/if}
16
+ </div>
17
+ {#if actions}<div class="flex shrink-0 flex-wrap items-center gap-2">{@render actions()}</div>{/if}
18
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ title: string;
4
+ description?: string;
5
+ actions?: Snippet;
6
+ class?: string;
7
+ }
8
+ declare const SectionHeader: import("svelte").Component<Props, {}, "">;
9
+ type SectionHeader = ReturnType<typeof SectionHeader>;
10
+ export default SectionHeader;
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ import * as Table from '../ui/table/index.js';
3
+ import StatusBadge from './StatusBadge.svelte';
4
+ export interface SecurityEvent { id: string; event: string; actor?: string; location?: string; createdAt: string; severity: 'info' | 'warning' | 'danger'; }
5
+ interface Props { events?: SecurityEvent[]; class?: string; }
6
+ let { events = [], class: className = '' }: Props = $props();
7
+ </script>
8
+ <div class={'overflow-x-auto rounded-lg border border-border bg-card ' + className}>
9
+ <Table.Root data-svadmin-datatable>
10
+ <Table.Header data-svadmin-table-head><Table.Row><Table.Head>Event</Table.Head><Table.Head>Actor</Table.Head><Table.Head>Location</Table.Head><Table.Head>Time</Table.Head><Table.Head>Risk</Table.Head></Table.Row></Table.Header>
11
+ <Table.Body>
12
+ {#each events as event (event.id)}
13
+ <Table.Row data-svadmin-table-row><Table.Cell class="font-medium">{event.event}</Table.Cell><Table.Cell>{event.actor ?? 'System'}</Table.Cell><Table.Cell>{event.location ?? 'Unknown'}</Table.Cell><Table.Cell class="whitespace-nowrap">{event.createdAt}</Table.Cell><Table.Cell><StatusBadge status={event.severity} label={event.severity} /></Table.Cell></Table.Row>
14
+ {/each}
15
+ </Table.Body>
16
+ </Table.Root>
17
+ {#if events.length === 0}<div class="p-6 text-center text-sm text-muted-foreground">No security events</div>{/if}
18
+ </div>
@@ -0,0 +1,15 @@
1
+ export interface SecurityEvent {
2
+ id: string;
3
+ event: string;
4
+ actor?: string;
5
+ location?: string;
6
+ createdAt: string;
7
+ severity: 'info' | 'warning' | 'danger';
8
+ }
9
+ interface Props {
10
+ events?: SecurityEvent[];
11
+ class?: string;
12
+ }
13
+ declare const SecurityEventTable: import("svelte").Component<Props, {}, "">;
14
+ type SecurityEventTable = ReturnType<typeof SecurityEventTable>;
15
+ export default SecurityEventTable;
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import { Badge } from '../ui/badge/index.js';
3
+ export type Status = 'success' | 'warning' | 'danger' | 'info' | 'neutral';
4
+ interface Props {
5
+ status: Status;
6
+ label?: string;
7
+ class?: string;
8
+ }
9
+ let { status, label = status, class: className = '' }: Props = $props();
10
+ const toneClass: Record<Status, string> = {
11
+ success: 'border-success/30 bg-success/10 text-success',
12
+ warning: 'border-warning/30 bg-warning/10 text-warning-foreground',
13
+ danger: 'border-destructive/30 bg-destructive/10 text-destructive',
14
+ info: 'border-primary/30 bg-primary/10 text-primary',
15
+ neutral: 'border-border bg-muted text-muted-foreground',
16
+ };
17
+ </script>
18
+
19
+ <Badge variant="outline" class={'font-medium ' + toneClass[status] + ' ' + className}>{label}</Badge>
@@ -0,0 +1,9 @@
1
+ export type Status = 'success' | 'warning' | 'danger' | 'info' | 'neutral';
2
+ interface Props {
3
+ status: Status;
4
+ label?: string;
5
+ class?: string;
6
+ }
7
+ declare const StatusBadge: import("svelte").Component<Props, {}, "">;
8
+ type StatusBadge = ReturnType<typeof StatusBadge>;
9
+ export default StatusBadge;
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { AlertTriangle, ArrowLeft, FileQuestion, Home } from '@lucide/svelte';
4
+ import { captureAdminContext } from '@svadmin/core';
5
+ import { useTranslation } from '@svadmin/core/i18n';
6
+ import { Button } from '../ui/button/index.js';
7
+ import * as Card from '../ui/card/index.js';
8
+ interface Props { status?: '404' | '500'; title?: string; description?: string; action?: Snippet; }
9
+ let { status = '404', title, description, action }: Props = $props();
10
+ const adminContext = captureAdminContext();
11
+ const i18n = useTranslation();
12
+ const isNotFound = $derived(status === '404');
13
+ const resolvedTitle = $derived(title ?? (isNotFound ? i18n.t('common.pageNotFound') : i18n.t('common.error')));
14
+ const resolvedDescription = $derived(description ?? (isNotFound ? i18n.t('error.pageNotFoundDescription') : i18n.t('error.internalServerErrorDescription')));
15
+ </script>
16
+ <div class="flex min-h-[24rem] items-center justify-center p-4" data-svadmin-system-error>
17
+ <Card.Card class="w-full max-w-md"><Card.CardContent class="space-y-5 p-8 text-center"><div class="mx-auto flex size-14 items-center justify-center rounded-lg bg-muted text-muted-foreground">{#if isNotFound}<FileQuestion class="size-7" />{:else}<AlertTriangle class="size-7 text-destructive" />{/if}</div><div class="space-y-2"><p class="text-xs font-medium text-muted-foreground">{status}</p><h2 class="text-xl font-semibold text-foreground">{resolvedTitle}</h2><p class="text-sm leading-6 text-muted-foreground">{resolvedDescription}</p></div>{#if action}<div>{@render action()}</div>{/if}<div class="flex flex-wrap justify-center gap-2"><Button variant="outline" onclick={() => adminContext.back()}><ArrowLeft class="size-4" />{i18n.t('common.back')}</Button><Button onclick={() => adminContext.navigate('/')}><Home class="size-4" />{i18n.t('common.returnHome')}</Button></div></Card.CardContent></Card.Card>
18
+ </div>