@svadmin/ui 0.48.0 → 0.49.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.
@@ -75,6 +75,7 @@
75
75
  interface Props {
76
76
  resourceName: string;
77
77
  selectable?: boolean;
78
+ density?: 'compact' | 'comfortable';
78
79
  /** Custom header actions (right side) */
79
80
  headerActions?: Snippet;
80
81
  /** Custom cell renderers by field key */
@@ -98,6 +99,7 @@
98
99
  let {
99
100
  resourceName,
100
101
  selectable = true,
102
+ density = 'comfortable',
101
103
  headerActions,
102
104
  columns: customColumns,
103
105
  defaultCellRenderer,
@@ -109,6 +111,8 @@
109
111
  sorters: externalSorters,
110
112
  }: Props = $props();
111
113
  const adminContext = captureAdminContext();
114
+ const headerDensityClass = $derived(density === 'compact' ? 'h-8 py-1.5 text-[0.6875rem]' : 'h-9 py-2.5 text-xs');
115
+ const cellDensityClass = $derived(density === 'compact' ? 'h-8 px-2 py-1 text-xs' : 'h-10 p-2 text-sm');
112
116
 
113
117
  const resource = $derived(getResource(resourceName));
114
118
  const primaryKey = $derived(resource.primaryKey ?? 'id');
@@ -682,7 +686,7 @@
682
686
  </div>
683
687
 
684
688
  <!-- Table (TanStack-powered) -->
685
- <div class="rounded-[24px] bg-card/80 backdrop-blur-3xl shadow-[0_8px_30px_rgb(0,0,0,0.04)] overflow-hidden ring-1 ring-border/30" role="region" aria-label="{resource.label} {i18n.t('common.list')}">
689
+ <div class="overflow-hidden rounded-lg border border-border bg-card shadow-sm" role="region" aria-label="{resource.label} {i18n.t('common.list')}" data-table-density={density}>
686
690
  {#if query.isLoading}
687
691
  <div class="p-4 space-y-3">
688
692
  <div class="flex gap-4 mb-2">
@@ -719,7 +723,7 @@
719
723
  {@const header = col.header as typeof headerGroup.headers[0]}
720
724
  <Table.Head
721
725
  {...dragProps}
722
- class={cn("bg-transparent hover:bg-muted/10 border-b border-border/20 uppercase tracking-[0.12em] text-[10px] text-muted-foreground/70 font-semibold py-4", dragProps.class)}
726
+ class={cn('border-b border-border bg-muted/25 font-medium tracking-normal text-muted-foreground hover:bg-muted/40', headerDensityClass, dragProps.class)}
723
727
  style={header_getSize(header) != null && header_getSize(header) !== 150 ? `width:${header_getSize(header)}px` : undefined}
724
728
  >
725
729
  {#if header.id === '_select'}
@@ -764,7 +768,7 @@
764
768
  {#snippet child({ props })}
765
769
  <Table.Row {...props} class="transition-all duration-300 border-b border-border/10 {row_getIsSelected(row) ? 'bg-primary/5' : 'hover:bg-muted/20'}">
766
770
  {#each visibleCells as cell, _i (_i)}
767
- <Table.Cell>
771
+ <Table.Cell class={cellDensityClass}>
768
772
  {#if cell.column.id === '_select'}
769
773
  <Checkbox
770
774
  checked={row_getIsSelected(row)}
@@ -3,6 +3,7 @@ import type { Snippet } from 'svelte';
3
3
  interface Props {
4
4
  resourceName: string;
5
5
  selectable?: boolean;
6
+ density?: 'compact' | 'comfortable';
6
7
  /** Custom header actions (right side) */
7
8
  headerActions?: Snippet;
8
9
  /** Custom cell renderers by field key */
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { cn } from '../../utils.js';
4
+
5
+ interface Props {
6
+ title: string;
7
+ description?: string;
8
+ status?: string;
9
+ primaryAction: Snippet;
10
+ secondaryActions?: Snippet;
11
+ class?: string;
12
+ }
13
+
14
+ let { title, description, status, primaryAction, secondaryActions, class: className = '' }: Props = $props();
15
+ </script>
16
+
17
+ <section
18
+ class={cn('flex flex-col gap-3 border-y border-border bg-card px-4 py-3 sm:flex-row sm:items-center sm:justify-between', className)}
19
+ aria-label={title}
20
+ data-svadmin-workspace-action-bar
21
+ >
22
+ <div class="min-w-0">
23
+ <div class="flex flex-wrap items-center gap-2">
24
+ {#if status}<span class="rounded-sm bg-warning/15 px-2 py-0.5 text-xs font-medium text-warning-foreground">{status}</span>{/if}
25
+ <h2 class="text-sm font-semibold text-foreground">{title}</h2>
26
+ </div>
27
+ {#if description}<p class="mt-1 text-xs leading-5 text-muted-foreground">{description}</p>{/if}
28
+ </div>
29
+ <div class="flex shrink-0 flex-wrap items-center gap-2">
30
+ {#if secondaryActions}{@render secondaryActions()}{/if}
31
+ {@render primaryAction()}
32
+ </div>
33
+ </section>
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ title: string;
4
+ description?: string;
5
+ status?: string;
6
+ primaryAction: Snippet;
7
+ secondaryActions?: Snippet;
8
+ class?: string;
9
+ }
10
+ declare const WorkspaceActionBar: import("svelte").Component<Props, {}, "">;
11
+ type WorkspaceActionBar = ReturnType<typeof WorkspaceActionBar>;
12
+ export default WorkspaceActionBar;
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { PanelRightClose, PanelRightOpen } from '@lucide/svelte';
4
+ import { Button } from '../ui/button/index.js';
5
+ import { cn } from '../../utils.js';
6
+
7
+ interface Props {
8
+ title: string;
9
+ description?: string;
10
+ open?: boolean;
11
+ ontoggle?: (open: boolean) => void;
12
+ children: Snippet;
13
+ footer?: Snippet;
14
+ openLabel?: string;
15
+ closeLabel?: string;
16
+ class?: string;
17
+ }
18
+
19
+ let {
20
+ title,
21
+ description,
22
+ open = true,
23
+ ontoggle,
24
+ children,
25
+ footer,
26
+ openLabel = 'Open inspector',
27
+ closeLabel = 'Close inspector',
28
+ class: className = '',
29
+ }: Props = $props();
30
+
31
+ function toggle(): void {
32
+ ontoggle?.(!open);
33
+ }
34
+ </script>
35
+
36
+ <aside class={cn('overflow-hidden rounded-lg border border-border bg-card', className)} data-svadmin-workspace-inspector data-open={open}>
37
+ {#if open}
38
+ <header class="flex items-start justify-between gap-3 border-b border-border px-4 py-3">
39
+ <div class="min-w-0">
40
+ <h2 class="text-sm font-semibold text-foreground">{title}</h2>
41
+ {#if description}<p class="mt-1 text-xs leading-5 text-muted-foreground">{description}</p>{/if}
42
+ </div>
43
+ <Button variant="ghost" size="icon-sm" aria-label={closeLabel} title={closeLabel} onclick={toggle}>
44
+ <PanelRightClose class="size-4" />
45
+ </Button>
46
+ </header>
47
+ <div class="max-h-[calc(100vh-15rem)] overflow-y-auto p-4">{@render children()}</div>
48
+ {#if footer}<footer class="border-t border-border p-3">{@render footer()}</footer>{/if}
49
+ {:else}
50
+ <div class="flex min-h-64 flex-col items-center gap-3 py-3">
51
+ <Button variant="ghost" size="icon-sm" aria-label={openLabel} title={openLabel} onclick={toggle}>
52
+ <PanelRightOpen class="size-4" />
53
+ </Button>
54
+ <span class="[writing-mode:vertical-rl] text-xs font-medium text-muted-foreground">{title}</span>
55
+ </div>
56
+ {/if}
57
+ </aside>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ title: string;
4
+ description?: string;
5
+ open?: boolean;
6
+ ontoggle?: (open: boolean) => void;
7
+ children: Snippet;
8
+ footer?: Snippet;
9
+ openLabel?: string;
10
+ closeLabel?: string;
11
+ class?: string;
12
+ }
13
+ declare const WorkspaceInspector: import("svelte").Component<Props, {}, "">;
14
+ type WorkspaceInspector = ReturnType<typeof WorkspaceInspector>;
15
+ export default WorkspaceInspector;
@@ -7,6 +7,8 @@
7
7
  secondary?: Snippet;
8
8
  summary?: Snippet;
9
9
  secondaryWidth?: string;
10
+ secondaryCollapsed?: boolean;
11
+ secondaryCollapsedWidth?: string;
10
12
  mobileOrder?: 'primary-first' | 'secondary-first';
11
13
  class?: string;
12
14
  }
@@ -16,12 +18,21 @@
16
18
  secondary,
17
19
  summary,
18
20
  secondaryWidth = '22rem',
21
+ secondaryCollapsed = false,
22
+ secondaryCollapsedWidth = '3rem',
19
23
  mobileOrder = 'primary-first',
20
24
  class: className = '',
21
25
  }: Props = $props();
26
+
27
+ const resolvedSecondaryWidth = $derived(secondaryCollapsed ? secondaryCollapsedWidth : secondaryWidth);
22
28
  </script>
23
29
 
24
- <div class={cn('flex flex-col gap-6', className)} data-svadmin-workspace-layout style:--workspace-secondary-width={secondaryWidth}>
30
+ <div
31
+ class={cn('flex flex-col gap-6', className)}
32
+ data-svadmin-workspace-layout
33
+ data-secondary-collapsed={secondaryCollapsed}
34
+ style:--workspace-secondary-width={resolvedSecondaryWidth}
35
+ >
25
36
  {#if summary}<div data-svadmin-workspace-summary>{@render summary()}</div>{/if}
26
37
  <div class={cn('grid min-w-0 items-start gap-6 xl:grid-cols-[minmax(0,1fr)_var(--workspace-secondary-width)]', mobileOrder === 'secondary-first' && 'grid-rows-[auto_auto]')}>
27
38
  {#if mobileOrder === 'secondary-first' && secondary}<aside class="order-1 min-w-0 xl:order-2">{@render secondary()}</aside>{/if}
@@ -4,6 +4,8 @@ interface Props {
4
4
  secondary?: Snippet;
5
5
  summary?: Snippet;
6
6
  secondaryWidth?: string;
7
+ secondaryCollapsed?: boolean;
8
+ secondaryCollapsedWidth?: string;
7
9
  mobileOrder?: 'primary-first' | 'secondary-first';
8
10
  class?: string;
9
11
  }
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { cn } from '../../utils.js';
4
+
5
+ interface Props {
6
+ primary: Snippet;
7
+ secondary: Snippet;
8
+ secondaryWidth?: string;
9
+ reverse?: boolean;
10
+ class?: string;
11
+ }
12
+
13
+ let {
14
+ primary,
15
+ secondary,
16
+ secondaryWidth = '42%',
17
+ reverse = false,
18
+ class: className = '',
19
+ }: Props = $props();
20
+ </script>
21
+
22
+ <div
23
+ class={cn(
24
+ 'grid min-w-0 overflow-hidden rounded-lg border border-border bg-card',
25
+ reverse ? 'lg:grid-cols-[var(--workspace-split-width)_minmax(0,1fr)]' : 'lg:grid-cols-[minmax(0,1fr)_var(--workspace-split-width)]',
26
+ className,
27
+ )}
28
+ style:--workspace-split-width={secondaryWidth}
29
+ data-svadmin-workspace-split-pane
30
+ data-reverse={reverse}
31
+ >
32
+ <section class={cn('min-w-0 p-4', reverse && 'lg:order-2')} data-svadmin-split-primary>{@render primary()}</section>
33
+ <section class={cn('min-w-0 border-t border-border bg-muted/15 p-4 lg:border-l lg:border-t-0', reverse && 'lg:order-1 lg:border-l-0 lg:border-r')} data-svadmin-split-secondary>{@render secondary()}</section>
34
+ </div>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ primary: Snippet;
4
+ secondary: Snippet;
5
+ secondaryWidth?: string;
6
+ reverse?: boolean;
7
+ class?: string;
8
+ }
9
+ declare const WorkspaceSplitPane: import("svelte").Component<Props, {}, "">;
10
+ type WorkspaceSplitPane = ReturnType<typeof WorkspaceSplitPane>;
11
+ export default WorkspaceSplitPane;
@@ -0,0 +1,71 @@
1
+ <script module lang="ts">
2
+ export type WorkspaceStageStatus = 'complete' | 'current' | 'blocked' | 'pending';
3
+
4
+ export interface WorkspaceStage {
5
+ id: string;
6
+ label: string;
7
+ description?: string;
8
+ status: WorkspaceStageStatus;
9
+ }
10
+ </script>
11
+
12
+ <script lang="ts">
13
+ import { AlertTriangle, Check, Circle } from '@lucide/svelte';
14
+ import { cn } from '../../utils.js';
15
+
16
+ interface Props {
17
+ stages: WorkspaceStage[];
18
+ activeId: string;
19
+ onselect?: (stage: WorkspaceStage) => void;
20
+ ariaLabel?: string;
21
+ class?: string;
22
+ }
23
+
24
+ let {
25
+ stages,
26
+ activeId,
27
+ onselect,
28
+ ariaLabel = 'Workflow stages',
29
+ class: className = '',
30
+ }: Props = $props();
31
+
32
+ const markerClass: Record<WorkspaceStageStatus, string> = {
33
+ complete: 'border-success/30 bg-success/10 text-success',
34
+ current: 'border-primary bg-primary text-primary-foreground',
35
+ blocked: 'border-warning/40 bg-warning/15 text-warning-foreground',
36
+ pending: 'border-border bg-background text-muted-foreground',
37
+ };
38
+ </script>
39
+
40
+ <nav aria-label={ariaLabel} class={cn('overflow-x-auto', className)} data-svadmin-workspace-stages>
41
+ <ol class="flex min-w-max items-start gap-0 px-0.5 py-1">
42
+ {#each stages as stage, index (stage.id)}
43
+ <li class="flex items-start">
44
+ <button
45
+ type="button"
46
+ class="group grid min-w-28 grid-cols-[auto_minmax(0,1fr)] gap-x-2 rounded-md px-2 py-1.5 text-left outline-none transition-colors hover:bg-muted/50 focus-visible:ring-2 focus-visible:ring-ring sm:min-w-36"
47
+ aria-current={stage.id === activeId ? 'step' : undefined}
48
+ aria-label={`${stage.label}: ${stage.status}`}
49
+ onclick={() => onselect?.(stage)}
50
+ >
51
+ <span class={cn('flex size-7 items-center justify-center rounded-full border', markerClass[stage.status])}>
52
+ {#if stage.status === 'complete'}
53
+ <Check class="size-3.5" />
54
+ {:else if stage.status === 'blocked'}
55
+ <AlertTriangle class="size-3.5" />
56
+ {:else}
57
+ <Circle class={cn('size-2.5', stage.status === 'current' && 'fill-current')} />
58
+ {/if}
59
+ </span>
60
+ <span class="min-w-0 pt-0.5">
61
+ <span class={cn('block text-xs font-medium', stage.id === activeId ? 'text-foreground' : 'text-muted-foreground')}>{stage.label}</span>
62
+ {#if stage.description}<span class="mt-0.5 block max-w-32 truncate text-[0.6875rem] text-muted-foreground">{stage.description}</span>{/if}
63
+ </span>
64
+ </button>
65
+ {#if index < stages.length - 1}
66
+ <span class={cn('mt-5 h-px w-4 bg-border sm:w-7', stage.status === 'complete' && 'bg-success/50')} aria-hidden="true"></span>
67
+ {/if}
68
+ </li>
69
+ {/each}
70
+ </ol>
71
+ </nav>
@@ -0,0 +1,17 @@
1
+ export type WorkspaceStageStatus = 'complete' | 'current' | 'blocked' | 'pending';
2
+ export interface WorkspaceStage {
3
+ id: string;
4
+ label: string;
5
+ description?: string;
6
+ status: WorkspaceStageStatus;
7
+ }
8
+ interface Props {
9
+ stages: WorkspaceStage[];
10
+ activeId: string;
11
+ onselect?: (stage: WorkspaceStage) => void;
12
+ ariaLabel?: string;
13
+ class?: string;
14
+ }
15
+ declare const WorkspaceStageStepper: import("svelte").Component<Props, {}, "">;
16
+ type WorkspaceStageStepper = ReturnType<typeof WorkspaceStageStepper>;
17
+ export default WorkspaceStageStepper;
@@ -3,6 +3,10 @@ export { default as ContentPageHeader } from './ContentPageHeader.svelte';
3
3
  export { default as SectionHeader } from './SectionHeader.svelte';
4
4
  export { default as PageToolbar } from './PageToolbar.svelte';
5
5
  export { default as WorkspaceLayout } from './WorkspaceLayout.svelte';
6
+ export { default as WorkspaceStageStepper } from './WorkspaceStageStepper.svelte';
7
+ export { default as WorkspaceActionBar } from './WorkspaceActionBar.svelte';
8
+ export { default as WorkspaceInspector } from './WorkspaceInspector.svelte';
9
+ export { default as WorkspaceSplitPane } from './WorkspaceSplitPane.svelte';
6
10
  export { default as SettingsGroup } from './SettingsGroup.svelte';
7
11
  export { default as SettingsFieldRow } from './SettingsFieldRow.svelte';
8
12
  export { default as MetricBlock } from './MetricBlock.svelte';
@@ -3,6 +3,10 @@ export { default as ContentPageHeader } from './ContentPageHeader.svelte';
3
3
  export { default as SectionHeader } from './SectionHeader.svelte';
4
4
  export { default as PageToolbar } from './PageToolbar.svelte';
5
5
  export { default as WorkspaceLayout } from './WorkspaceLayout.svelte';
6
+ export { default as WorkspaceStageStepper } from './WorkspaceStageStepper.svelte';
7
+ export { default as WorkspaceActionBar } from './WorkspaceActionBar.svelte';
8
+ export { default as WorkspaceInspector } from './WorkspaceInspector.svelte';
9
+ export { default as WorkspaceSplitPane } from './WorkspaceSplitPane.svelte';
6
10
  export { default as SettingsGroup } from './SettingsGroup.svelte';
7
11
  export { default as SettingsFieldRow } from './SettingsFieldRow.svelte';
8
12
  export { default as MetricBlock } from './MetricBlock.svelte';
package/dist/index.d.ts CHANGED
@@ -159,6 +159,11 @@ export { default as ContentPageHeader } from './components/content/ContentPageHe
159
159
  export { default as SectionHeader } from './components/content/SectionHeader.svelte';
160
160
  export { default as PageToolbar } from './components/content/PageToolbar.svelte';
161
161
  export { default as WorkspaceLayout } from './components/content/WorkspaceLayout.svelte';
162
+ export { default as WorkspaceStageStepper } from './components/content/WorkspaceStageStepper.svelte';
163
+ export type { WorkspaceStage, WorkspaceStageStatus } from './components/content/WorkspaceStageStepper.svelte';
164
+ export { default as WorkspaceActionBar } from './components/content/WorkspaceActionBar.svelte';
165
+ export { default as WorkspaceInspector } from './components/content/WorkspaceInspector.svelte';
166
+ export { default as WorkspaceSplitPane } from './components/content/WorkspaceSplitPane.svelte';
162
167
  export { default as SettingsGroup } from './components/content/SettingsGroup.svelte';
163
168
  export { default as SettingsFieldRow } from './components/content/SettingsFieldRow.svelte';
164
169
  export { default as MetricBlock } from './components/content/MetricBlock.svelte';
package/dist/index.js CHANGED
@@ -174,6 +174,10 @@ export { default as ContentPageHeader } from './components/content/ContentPageHe
174
174
  export { default as SectionHeader } from './components/content/SectionHeader.svelte';
175
175
  export { default as PageToolbar } from './components/content/PageToolbar.svelte';
176
176
  export { default as WorkspaceLayout } from './components/content/WorkspaceLayout.svelte';
177
+ export { default as WorkspaceStageStepper } from './components/content/WorkspaceStageStepper.svelte';
178
+ export { default as WorkspaceActionBar } from './components/content/WorkspaceActionBar.svelte';
179
+ export { default as WorkspaceInspector } from './components/content/WorkspaceInspector.svelte';
180
+ export { default as WorkspaceSplitPane } from './components/content/WorkspaceSplitPane.svelte';
177
181
  export { default as SettingsGroup } from './components/content/SettingsGroup.svelte';
178
182
  export { default as SettingsFieldRow } from './components/content/SettingsFieldRow.svelte';
179
183
  export { default as MetricBlock } from './components/content/MetricBlock.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [