@svadmin/ui 0.56.0 → 0.58.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 +28 -0
- package/dist/app.theme.css +28 -0
- package/dist/components/AICommandBar.svelte +33 -10
- package/dist/components/AdminApp.svelte +16 -5
- package/dist/components/AutoForm.svelte +4 -4
- package/dist/components/AutoTable.svelte +866 -204
- package/dist/components/AutoTable.svelte.d.ts +1 -1
- package/dist/components/CommandPalette.svelte +39 -17
- package/dist/components/ConfirmDialog.svelte +4 -3
- package/dist/components/ConfirmDialog.svelte.d.ts +1 -0
- package/dist/components/CopilotPanel.svelte +3 -3
- package/dist/components/DraggableHeader.svelte +15 -36
- package/dist/components/DraggableHeader.svelte.d.ts +13 -4
- package/dist/components/EditPage.svelte +4 -4
- package/dist/components/EmptyState.svelte +1 -1
- package/dist/components/ErrorComponent.svelte +1 -1
- package/dist/components/FieldRenderer.svelte +2 -1
- package/dist/components/ListPage.svelte +4 -12
- package/dist/components/ListPage.svelte.d.ts +1 -1
- package/dist/components/RecordDetailDrawer.svelte +121 -0
- package/dist/components/RecordDetailDrawer.svelte.d.ts +9 -0
- package/dist/components/ResourceAccessGuard.svelte +68 -0
- package/dist/components/ResourceAccessGuard.svelte.d.ts +12 -0
- package/dist/components/ResourceOperationsPage.svelte +8 -3
- package/dist/components/ShowPage.svelte +5 -6
- package/dist/components/TaskQueueDrawer.svelte +1 -1
- package/dist/components/TenantSwitcher.svelte +1 -0
- package/dist/components/Toast.svelte +35 -1
- package/dist/components/UndoableNotification.svelte +12 -6
- package/dist/components/UndoableNotification.svelte.d.ts +2 -0
- package/dist/components/account/MembersStarterPage.svelte +1 -1
- package/dist/components/buttons/CloneButton.svelte +9 -2
- package/dist/components/buttons/CreateButton.svelte +9 -2
- package/dist/components/buttons/DeleteButton.svelte +9 -2
- package/dist/components/buttons/EditButton.svelte +9 -2
- package/dist/components/buttons/ListButton.svelte +1 -1
- package/dist/components/buttons/ShowButton.svelte +9 -2
- package/dist/components/content/DataState.svelte +1 -1
- package/dist/components/content/FilterToolbar.svelte +6 -2
- package/dist/components/content/MediaThumbnail.svelte +109 -0
- package/dist/components/content/MediaThumbnail.svelte.d.ts +22 -0
- package/dist/components/content/ProjectCard.svelte +2 -1
- package/dist/components/content/SystemErrorState.svelte +1 -1
- package/dist/components/content/index.d.ts +2 -0
- package/dist/components/content/index.js +1 -0
- package/dist/components/fields/ImageField.svelte +19 -4
- package/dist/components/fields/ImageField.svelte.d.ts +4 -0
- package/dist/components/saved-list-views.d.ts +35 -0
- package/dist/components/saved-list-views.js +168 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/package.json +2 -2
package/dist/app.css
CHANGED
|
@@ -263,6 +263,27 @@
|
|
|
263
263
|
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
@layer components {
|
|
267
|
+
/* Collapsed semantic containers must not retain surface chrome or spacing. */
|
|
268
|
+
details.svadmin-collapsible:not([open]),
|
|
269
|
+
[data-svadmin-collapsible]:not([data-open="true"]) {
|
|
270
|
+
margin: 0 !important;
|
|
271
|
+
padding: 0 !important;
|
|
272
|
+
border: 0 !important;
|
|
273
|
+
background: transparent !important;
|
|
274
|
+
box-shadow: none !important;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
[data-svadmin-filter-toolbar] .svadmin-filter-toolbar-advanced[hidden] {
|
|
278
|
+
display: none !important;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
[data-svadmin-filter-toolbar] .svadmin-filter-toolbar-advanced:not([hidden]) {
|
|
282
|
+
min-width: 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
}
|
|
286
|
+
|
|
266
287
|
body {
|
|
267
288
|
background: var(--background);
|
|
268
289
|
color: var(--foreground);
|
|
@@ -605,6 +626,13 @@ main {
|
|
|
605
626
|
font-weight: 600;
|
|
606
627
|
}
|
|
607
628
|
|
|
629
|
+
/* Collapsible details normalization stays scoped to svadmin-owned hooks. */
|
|
630
|
+
details[data-svadmin-filter]:not([open]) > :not(summary),
|
|
631
|
+
details.svadmin-collapsible-filter:not([open]) > :not(summary),
|
|
632
|
+
details.svadmin-collapsible:not([open]) > :not(summary) {
|
|
633
|
+
display: none !important;
|
|
634
|
+
}
|
|
635
|
+
|
|
608
636
|
/* --- Stripe-first layout preset (clean-flat) --- */
|
|
609
637
|
/* Keep this public class for layoutPreset API compatibility. The preset only
|
|
610
638
|
* adjusts density and composition; color, focus, and elevation stay semantic. */
|
package/dist/app.theme.css
CHANGED
|
@@ -263,6 +263,27 @@
|
|
|
263
263
|
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
@layer components {
|
|
267
|
+
/* Collapsed semantic containers must not retain surface chrome or spacing. */
|
|
268
|
+
details.svadmin-collapsible:not([open]),
|
|
269
|
+
[data-svadmin-collapsible]:not([data-open="true"]) {
|
|
270
|
+
margin: 0 !important;
|
|
271
|
+
padding: 0 !important;
|
|
272
|
+
border: 0 !important;
|
|
273
|
+
background: transparent !important;
|
|
274
|
+
box-shadow: none !important;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
[data-svadmin-filter-toolbar] .svadmin-filter-toolbar-advanced[hidden] {
|
|
278
|
+
display: none !important;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
[data-svadmin-filter-toolbar] .svadmin-filter-toolbar-advanced:not([hidden]) {
|
|
282
|
+
min-width: 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
}
|
|
286
|
+
|
|
266
287
|
body {
|
|
267
288
|
background: var(--background);
|
|
268
289
|
color: var(--foreground);
|
|
@@ -604,6 +625,13 @@ main {
|
|
|
604
625
|
font-weight: 600;
|
|
605
626
|
}
|
|
606
627
|
|
|
628
|
+
/* Collapsible details normalization stays scoped to svadmin-owned hooks. */
|
|
629
|
+
details[data-svadmin-filter]:not([open]) > :not(summary),
|
|
630
|
+
details.svadmin-collapsible-filter:not([open]) > :not(summary),
|
|
631
|
+
details.svadmin-collapsible:not([open]) > :not(summary) {
|
|
632
|
+
display: none !important;
|
|
633
|
+
}
|
|
634
|
+
|
|
607
635
|
/* --- Stripe-first layout preset (clean-flat) --- */
|
|
608
636
|
/* Keep this public class for layoutPreset API compatibility. The preset only
|
|
609
637
|
* adjusts density and composition; color, focus, and elevation stay semantic. */
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { captureAdminContext, toggleTheme } from '@svadmin/core';
|
|
2
|
+
import { captureAdminContext, toggleTheme, useNavigation } from '@svadmin/core';
|
|
3
3
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
4
|
import { Command } from './ui/command/index.js';
|
|
5
5
|
import * as Dialog from './ui/dialog/index.js';
|
|
6
6
|
import { Search, LayoutDashboard, Plus, Sun, FileText, Sparkles, Loader2 } from '@lucide/svelte';
|
|
7
7
|
import MarkdownRenderer from './MarkdownRenderer.svelte';
|
|
8
|
+
import CanAccess from './CanAccess.svelte';
|
|
8
9
|
|
|
9
10
|
const i18n = useTranslation();
|
|
10
11
|
|
|
11
12
|
let { open = $bindable(false) } = $props<{ open?: boolean }>();
|
|
12
13
|
const adminContext = captureAdminContext();
|
|
14
|
+
const navigation = useNavigation();
|
|
13
15
|
let searchValue = $state('');
|
|
14
16
|
|
|
15
17
|
const resources = $derived(adminContext.resources);
|
|
@@ -127,7 +129,8 @@
|
|
|
127
129
|
/>
|
|
128
130
|
{#if searchValue.trim() && !aiMode}
|
|
129
131
|
<button
|
|
130
|
-
|
|
132
|
+
type="button"
|
|
133
|
+
class="absolute right-3 flex items-center gap-1 text-[10px] font-medium text-warning bg-warning/10 hover:bg-warning/20 px-2 py-1 rounded transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
131
134
|
onclick={askAI}
|
|
132
135
|
>
|
|
133
136
|
<Sparkles class="h-3 w-3" />
|
|
@@ -149,10 +152,19 @@
|
|
|
149
152
|
{i18n.t('common.home')}
|
|
150
153
|
</Command.Item>
|
|
151
154
|
{#each resources as r, _i (_i)}
|
|
152
|
-
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
{#if adminContext.accessControlProvider}
|
|
156
|
+
<CanAccess resource={r.name} action="list">
|
|
157
|
+
<Command.Item value={r.name} onSelect={() => act(() => navigation.list(r.name))} class={itemClass}>
|
|
158
|
+
<FileText class="h-4 w-4 text-muted-foreground" />
|
|
159
|
+
{r.label}
|
|
160
|
+
</Command.Item>
|
|
161
|
+
</CanAccess>
|
|
162
|
+
{:else}
|
|
163
|
+
<Command.Item value={r.name} onSelect={() => act(() => navigation.list(r.name))} class={itemClass}>
|
|
164
|
+
<FileText class="h-4 w-4 text-muted-foreground" />
|
|
165
|
+
{r.label}
|
|
166
|
+
</Command.Item>
|
|
167
|
+
{/if}
|
|
156
168
|
{/each}
|
|
157
169
|
</Command.Group>
|
|
158
170
|
|
|
@@ -161,10 +173,21 @@
|
|
|
161
173
|
<!-- Actions -->
|
|
162
174
|
<Command.Group heading={i18n.t('common.actions')}>
|
|
163
175
|
{#each resources as r, _i (_i)}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
176
|
+
{#if r.canCreate !== false}
|
|
177
|
+
{#if adminContext.accessControlProvider}
|
|
178
|
+
<CanAccess resource={r.name} action="create">
|
|
179
|
+
<Command.Item value={`create-${r.name}`} onSelect={() => act(() => navigation.create(r.name))} class={itemClass}>
|
|
180
|
+
<Plus class="h-4 w-4 text-muted-foreground" />
|
|
181
|
+
{i18n.t('common.create')} {r.label}
|
|
182
|
+
</Command.Item>
|
|
183
|
+
</CanAccess>
|
|
184
|
+
{:else}
|
|
185
|
+
<Command.Item value={`create-${r.name}`} onSelect={() => act(() => navigation.create(r.name))} class={itemClass}>
|
|
186
|
+
<Plus class="h-4 w-4 text-muted-foreground" />
|
|
187
|
+
{i18n.t('common.create')} {r.label}
|
|
188
|
+
</Command.Item>
|
|
189
|
+
{/if}
|
|
190
|
+
{/if}
|
|
168
191
|
{/each}
|
|
169
192
|
<Command.Item value="toggle-theme" onSelect={() => act(() => toggleTheme())} class={itemClass}>
|
|
170
193
|
<Sun class="h-4 w-4 text-muted-foreground" />
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
import ErrorPage from './ErrorPage.svelte';
|
|
74
74
|
import SystemPageShell from './SystemPageShell.svelte';
|
|
75
75
|
import LazyPage from './LazyPage.svelte';
|
|
76
|
+
import ResourceAccessGuard from './ResourceAccessGuard.svelte';
|
|
76
77
|
import { Button } from './ui/button/index.js';
|
|
77
78
|
import { Input } from './ui/input/index.js';
|
|
78
79
|
import { Badge } from './ui/badge/index.js';
|
|
@@ -609,27 +610,37 @@
|
|
|
609
610
|
{:else if (renderedRoute === '/:resource' || renderedRoute === '/:parent/:parentId/:resource') && renderedHasRouteResource}
|
|
610
611
|
{#key renderedParams.resource}
|
|
611
612
|
{@const Comp = renderedResourcePages?.list ?? mergedComponents.AutoTable}
|
|
612
|
-
<
|
|
613
|
+
<ResourceAccessGuard resourceName={renderedParams.resource} action="list">
|
|
614
|
+
<Comp resourceName={renderedParams.resource} />
|
|
615
|
+
</ResourceAccessGuard>
|
|
613
616
|
{/key}
|
|
614
617
|
{:else if (renderedRoute === '/:resource/create' || renderedRoute === '/:parent/:parentId/:resource/create') && renderedHasRouteResource}
|
|
615
618
|
{#key renderedParams.resource}
|
|
616
619
|
{@const Comp = renderedResourcePages?.create ?? mergedComponents.AutoForm}
|
|
617
|
-
<
|
|
620
|
+
<ResourceAccessGuard resourceName={renderedParams.resource} action="create">
|
|
621
|
+
<Comp resourceName={renderedParams.resource} mode="create" />
|
|
622
|
+
</ResourceAccessGuard>
|
|
618
623
|
{/key}
|
|
619
624
|
{:else if (renderedRoute === '/:resource/edit/:id' || renderedRoute === '/:resource/:id/edit' || renderedRoute === '/:parent/:parentId/:resource/edit/:id') && renderedHasRouteResource}
|
|
620
625
|
{#key `${renderedParams.resource}-${renderedParams.id}`}
|
|
621
626
|
{@const Comp = renderedResourcePages?.edit ?? mergedComponents.AutoForm}
|
|
622
|
-
<
|
|
627
|
+
<ResourceAccessGuard resourceName={renderedParams.resource} action="edit" id={renderedParams.id}>
|
|
628
|
+
<Comp resourceName={renderedParams.resource} mode="edit" id={renderedParams.id} />
|
|
629
|
+
</ResourceAccessGuard>
|
|
623
630
|
{/key}
|
|
624
631
|
{:else if (renderedRoute === '/:resource/show/:id' || renderedRoute === '/:resource/:id' || renderedRoute === '/:parent/:parentId/:resource/show/:id') && renderedHasRouteResource}
|
|
625
632
|
{#key `${renderedParams.resource}-${renderedParams.id}`}
|
|
626
633
|
{@const Comp = renderedResourcePages?.show ?? mergedComponents.ShowPage}
|
|
627
|
-
<
|
|
634
|
+
<ResourceAccessGuard resourceName={renderedParams.resource} action="show" id={renderedParams.id}>
|
|
635
|
+
<Comp resourceName={renderedParams.resource} id={renderedParams.id} />
|
|
636
|
+
</ResourceAccessGuard>
|
|
628
637
|
{/key}
|
|
629
638
|
{:else if (renderedRoute === '/:resource/clone/:id' || renderedRoute === '/:parent/:parentId/:resource/clone/:id') && renderedHasRouteResource}
|
|
630
639
|
{#key `${renderedParams.resource}-clone-${renderedParams.id}`}
|
|
631
640
|
{@const Comp = renderedResourcePages?.clone ?? mergedComponents.AutoForm}
|
|
632
|
-
<
|
|
641
|
+
<ResourceAccessGuard resourceName={renderedParams.resource} action="create" id={renderedParams.id} requireSourceRead>
|
|
642
|
+
<Comp resourceName={renderedParams.resource} mode="clone" id={renderedParams.id} />
|
|
643
|
+
</ResourceAccessGuard>
|
|
633
644
|
{/key}
|
|
634
645
|
{:else}
|
|
635
646
|
{@const ErrorComp = mergedComponents.ErrorPage || ErrorPage}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { useForm, getResource, deriveValidator, useNavigation } from '@svadmin/core';
|
|
3
3
|
import { slide } from 'svelte/transition';
|
|
4
4
|
import type { FieldDefinition } from '@svadmin/core';
|
|
5
5
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
onSuccess,
|
|
46
46
|
onNavigationGuardReady,
|
|
47
47
|
}: Props = $props();
|
|
48
|
-
const
|
|
48
|
+
const navigation = useNavigation();
|
|
49
49
|
const isReadonly = $derived(mode === 'show');
|
|
50
50
|
const isCompact = $derived(density === 'compact');
|
|
51
51
|
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
<div class="flex items-center gap-4">
|
|
197
197
|
<TooltipButton
|
|
198
198
|
tooltip={i18n.t('common.back')}
|
|
199
|
-
onclick={() => guardNavigate(() =>
|
|
199
|
+
onclick={() => guardNavigate(() => navigation.list(resourceName))}
|
|
200
200
|
>
|
|
201
201
|
<ArrowLeft class="h-5 w-5" />
|
|
202
202
|
</TooltipButton>
|
|
@@ -311,7 +311,7 @@
|
|
|
311
311
|
type="button"
|
|
312
312
|
variant="outline"
|
|
313
313
|
size={isCompact ? 'sm' : 'default'}
|
|
314
|
-
onclick={() => guardNavigate(() =>
|
|
314
|
+
onclick={() => guardNavigate(() => navigation.list(resourceName))}
|
|
315
315
|
>
|
|
316
316
|
{i18n.t('common.cancel')}
|
|
317
317
|
</Button>
|