@svadmin/ui 0.37.0 → 0.38.1
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 +42 -88
- package/dist/components/AdminApp.svelte +38 -35
- package/dist/components/CanAccess.svelte +3 -2
- package/dist/components/CanAccess.svelte.d.ts +1 -0
- package/dist/components/CreateInferencer.svelte +13 -0
- package/dist/components/CreateInferencer.svelte.d.ts +7 -0
- package/dist/components/EditInferencer.svelte +21 -0
- package/dist/components/EditInferencer.svelte.d.ts +9 -0
- package/dist/components/Header.svelte +4 -4
- package/dist/components/Layout.svelte +26 -13
- package/dist/components/ListInferencer.svelte +14 -0
- package/dist/components/ListInferencer.svelte.d.ts +7 -0
- package/dist/components/ResourceInferencer.svelte +28 -0
- package/dist/components/ResourceInferencer.svelte.d.ts +7 -0
- package/dist/components/ResourceOperationsPage.svelte +168 -21
- package/dist/components/ResourceOperationsPage.svelte.d.ts +2 -0
- package/dist/components/SettingsPage.svelte +3 -2
- package/dist/components/ShowInferencer.svelte +21 -0
- package/dist/components/ShowInferencer.svelte.d.ts +9 -0
- package/dist/components/ShowPage.svelte +2 -2
- package/dist/components/ShowPage.svelte.d.ts +1 -0
- package/dist/components/Sidebar.svelte +101 -104
- package/dist/components/Sidebar.svelte.d.ts +0 -1
- package/dist/components/SidebarItem.svelte +114 -52
- package/dist/components/StepsForm.svelte +2 -1
- package/dist/components/UndoableNotification.svelte +2 -1
- package/dist/components/account/TeamMembersPage.svelte +9 -3
- package/dist/components/buttons/CloneButton.svelte +10 -2
- package/dist/components/buttons/CloneButton.svelte.d.ts +2 -4
- package/dist/components/buttons/CreateButton.svelte +9 -2
- package/dist/components/buttons/CreateButton.svelte.d.ts +2 -4
- package/dist/components/buttons/DeleteButton.svelte +10 -2
- package/dist/components/buttons/DeleteButton.svelte.d.ts +2 -4
- package/dist/components/buttons/EditButton.svelte +10 -2
- package/dist/components/buttons/EditButton.svelte.d.ts +2 -4
- package/dist/components/buttons/ExportButton.svelte +9 -2
- package/dist/components/buttons/ExportButton.svelte.d.ts +2 -4
- package/dist/components/buttons/ImportButton.svelte +9 -2
- package/dist/components/buttons/ImportButton.svelte.d.ts +2 -4
- package/dist/components/buttons/ShowButton.svelte +10 -2
- package/dist/components/buttons/ShowButton.svelte.d.ts +2 -4
- package/dist/components/buttons/access-control.d.ts +7 -0
- package/dist/components/buttons/access-control.js +5 -0
- package/dist/components/ui/sheet/sheet.svelte +3 -3
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/router-state.svelte.js +15 -0
- package/package.json +2 -2
package/dist/app.css
CHANGED
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
--warning-foreground: oklch(0.3 0.08 60);
|
|
49
49
|
--info: oklch(0.6 0.15 250);
|
|
50
50
|
--info-foreground: oklch(0.985 0 0);
|
|
51
|
-
--sidebar: oklch(
|
|
52
|
-
--sidebar-foreground: oklch(0.
|
|
51
|
+
--sidebar: oklch(0.985 0.002 264);
|
|
52
|
+
--sidebar-foreground: oklch(0.23 0.02 265);
|
|
53
53
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
54
54
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
55
|
-
--sidebar-accent: oklch(0.
|
|
55
|
+
--sidebar-accent: oklch(0.955 0.012 264);
|
|
56
56
|
--sidebar-accent-foreground: oklch(0.33 0.17 264);
|
|
57
57
|
--sidebar-border: oklch(0.935 0.005 264);
|
|
58
58
|
--sidebar-ring: oklch(0.488 0.243 264.376);
|
|
@@ -89,11 +89,11 @@
|
|
|
89
89
|
--warning-foreground: oklch(0.25 0.05 60);
|
|
90
90
|
--info: oklch(0.65 0.15 250);
|
|
91
91
|
--info-foreground: oklch(0.985 0 0);
|
|
92
|
-
--sidebar: oklch(0.
|
|
93
|
-
--sidebar-foreground: oklch(0.
|
|
92
|
+
--sidebar: oklch(0.17 0.015 265);
|
|
93
|
+
--sidebar-foreground: oklch(0.92 0.005 264);
|
|
94
94
|
--sidebar-primary: oklch(0.585 0.233 264);
|
|
95
95
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
96
|
-
--sidebar-accent: oklch(0.488 0.243 264.376 /
|
|
96
|
+
--sidebar-accent: oklch(0.488 0.243 264.376 / 12%);
|
|
97
97
|
--sidebar-accent-foreground: oklch(0.7 0.15 264);
|
|
98
98
|
--sidebar-border: oklch(0.27 0.02 265);
|
|
99
99
|
--sidebar-ring: oklch(0.55 0.18 264);
|
|
@@ -521,13 +521,30 @@ main {
|
|
|
521
521
|
right: 0;
|
|
522
522
|
}
|
|
523
523
|
/* Mirror sidebar margins for RTL */
|
|
524
|
-
[dir="rtl"] .
|
|
524
|
+
[dir="rtl"] .sidebar-content-expanded {
|
|
525
525
|
margin-left: 0;
|
|
526
|
-
margin-right:
|
|
526
|
+
margin-right: 252px;
|
|
527
527
|
}
|
|
528
|
-
[dir="rtl"] .
|
|
528
|
+
[dir="rtl"] .sidebar-content-collapsed {
|
|
529
529
|
margin-left: 0;
|
|
530
|
-
margin-right:
|
|
530
|
+
margin-right: 70px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/* ── Sidebar Menu Item Styles ── */
|
|
534
|
+
.sidebar-menu-item {
|
|
535
|
+
position: relative;
|
|
536
|
+
cursor: pointer;
|
|
537
|
+
-webkit-tap-highlight-color: transparent;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.sidebar-menu-item-active {
|
|
541
|
+
position: relative;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.sidebar-scroll [data-radix-scroll-area-viewport] > div {
|
|
545
|
+
min-height: 100%;
|
|
546
|
+
display: flex;
|
|
547
|
+
flex-direction: column;
|
|
531
548
|
}
|
|
532
549
|
|
|
533
550
|
/* ⑰ Accessibility Minimum Touch Target — Apple HIG 44px minimum */
|
|
@@ -711,117 +728,54 @@ main {
|
|
|
711
728
|
background-color: rgba(43, 43, 64, 0.3) !important;
|
|
712
729
|
}
|
|
713
730
|
|
|
714
|
-
/* Sidebar layout adjustments */
|
|
715
|
-
.layout-clean-flat
|
|
716
|
-
background: #
|
|
731
|
+
/* Sidebar layout adjustments for clean-flat theme */
|
|
732
|
+
.layout-clean-flat aside {
|
|
733
|
+
background: #fafbfc !important;
|
|
717
734
|
color: #181c32 !important;
|
|
718
|
-
border-right:
|
|
735
|
+
border-right-color: #f1f3f7 !important;
|
|
719
736
|
box-shadow: none !important;
|
|
720
737
|
}
|
|
721
|
-
.dark.layout-clean-flat
|
|
722
|
-
.dark .layout-clean-flat
|
|
738
|
+
.dark.layout-clean-flat aside,
|
|
739
|
+
.dark .layout-clean-flat aside {
|
|
723
740
|
background: #1e1e2d !important;
|
|
724
741
|
color: #a1a5b7 !important;
|
|
725
|
-
border-right:
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
.layout-clean-flat [data-sidebar="group-label"] {
|
|
729
|
-
font-size: 0.9375rem !important;
|
|
730
|
-
font-weight: 650 !important;
|
|
731
|
-
text-transform: none;
|
|
732
|
-
letter-spacing: 0;
|
|
733
|
-
color: #a1a5b7 !important;
|
|
734
|
-
padding: 0.5rem 0.5rem 0.375rem !important;
|
|
742
|
+
border-right-color: #2b2b40 !important;
|
|
735
743
|
}
|
|
736
744
|
|
|
737
|
-
.layout-clean-flat
|
|
745
|
+
.layout-clean-flat .sidebar-menu-item {
|
|
738
746
|
color: #5e6278 !important;
|
|
739
747
|
border-radius: 0.5rem !important;
|
|
740
748
|
transition: color 0.15s ease, background-color 0.15s ease !important;
|
|
741
|
-
font-weight: 500 !important;
|
|
742
749
|
box-shadow: none !important;
|
|
743
750
|
}
|
|
744
751
|
|
|
745
|
-
.layout-clean-flat
|
|
752
|
+
.layout-clean-flat .sidebar-menu-item:hover {
|
|
746
753
|
background: transparent !important;
|
|
747
754
|
color: #181c32 !important;
|
|
748
755
|
}
|
|
749
|
-
.dark.layout-clean-flat
|
|
750
|
-
.dark .layout-clean-flat
|
|
756
|
+
.dark.layout-clean-flat .sidebar-menu-item:hover,
|
|
757
|
+
.dark .layout-clean-flat .sidebar-menu-item:hover {
|
|
751
758
|
background: transparent !important;
|
|
752
759
|
color: #f1f6ff !important;
|
|
753
760
|
}
|
|
754
761
|
|
|
755
|
-
.layout-clean-flat
|
|
756
|
-
opacity: 0.85 !important;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
.layout-clean-flat [data-sidebar="menu-button"][data-active="true"] {
|
|
762
|
+
.layout-clean-flat .sidebar-menu-item-active {
|
|
760
763
|
background: transparent !important;
|
|
761
764
|
color: #3e97ff !important;
|
|
762
765
|
font-weight: 600 !important;
|
|
763
766
|
box-shadow: none !important;
|
|
764
|
-
outline: none !important;
|
|
765
767
|
}
|
|
766
|
-
.dark.layout-clean-flat
|
|
767
|
-
.dark .layout-clean-flat
|
|
768
|
+
.dark.layout-clean-flat .sidebar-menu-item-active,
|
|
769
|
+
.dark .layout-clean-flat .sidebar-menu-item-active {
|
|
768
770
|
background: transparent !important;
|
|
769
771
|
color: #3e97ff !important;
|
|
770
772
|
}
|
|
771
773
|
|
|
772
|
-
.layout-clean-flat
|
|
774
|
+
.layout-clean-flat .sidebar-menu-item-active svg {
|
|
773
775
|
color: #3e97ff !important;
|
|
774
776
|
opacity: 1 !important;
|
|
775
777
|
}
|
|
776
778
|
|
|
777
|
-
.layout-clean-flat [data-sidebar="menu-button"] [data-sidebar="menu-icon"],
|
|
778
|
-
.layout-clean-flat [data-sidebar="menu-sub-button"] [data-sidebar="menu-icon"],
|
|
779
|
-
.layout-clean-flat [data-sidebar="menu-button"][data-active="true"] [data-sidebar="menu-icon"],
|
|
780
|
-
.layout-clean-flat [data-sidebar="menu-sub-button"][data-active="true"] [data-sidebar="menu-icon"] {
|
|
781
|
-
background: transparent !important;
|
|
782
|
-
color: inherit !important;
|
|
783
|
-
box-shadow: none !important;
|
|
784
|
-
outline: none !important;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
.layout-clean-flat [data-sidebar="menu-sub"] {
|
|
788
|
-
margin-left: 0 !important;
|
|
789
|
-
padding-left: 0 !important;
|
|
790
|
-
border-left: 0 !important;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
.layout-clean-flat [data-sidebar="menu-sub-button"] {
|
|
794
|
-
color: #5e6278 !important;
|
|
795
|
-
border-radius: 0.5rem !important;
|
|
796
|
-
font-size: 0.8125rem !important;
|
|
797
|
-
font-weight: 400 !important;
|
|
798
|
-
transition: color 0.15s ease, background-color 0.15s ease !important;
|
|
799
|
-
box-shadow: none !important;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
.layout-clean-flat [data-sidebar="menu-sub-button"]:hover {
|
|
803
|
-
background: transparent !important;
|
|
804
|
-
color: #181c32 !important;
|
|
805
|
-
}
|
|
806
|
-
.dark.layout-clean-flat [data-sidebar="menu-sub-button"]:hover,
|
|
807
|
-
.dark .layout-clean-flat [data-sidebar="menu-sub-button"]:hover {
|
|
808
|
-
background: transparent !important;
|
|
809
|
-
color: #f1f6ff !important;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
.layout-clean-flat [data-sidebar="menu-sub-button"][data-active="true"] {
|
|
813
|
-
background: transparent !important;
|
|
814
|
-
color: #3e97ff !important;
|
|
815
|
-
font-weight: 500 !important;
|
|
816
|
-
box-shadow: none !important;
|
|
817
|
-
outline: none !important;
|
|
818
|
-
}
|
|
819
|
-
.dark.layout-clean-flat [data-sidebar="menu-sub-button"][data-active="true"],
|
|
820
|
-
.dark .layout-clean-flat [data-sidebar="menu-sub-button"][data-active="true"] {
|
|
821
|
-
background: transparent !important;
|
|
822
|
-
color: #3e97ff !important;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
779
|
/* Button & input styles override under clean-flat layout */
|
|
826
780
|
.layout-clean-flat [data-slot="button"][data-variant="default"] {
|
|
827
781
|
background: #3e97ff !important;
|
|
@@ -117,27 +117,16 @@
|
|
|
117
117
|
Skeleton: Skeleton as unknown as ComponentRegistry['Skeleton'],
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
-
// Merge and keep a local reference — setContext is only visible to *children*,
|
|
121
|
-
// so we must use the local variable for lookups within this component.
|
|
122
|
-
const mergedComponents: ComponentRegistry = { ...defaultComponents, ...userComponents };
|
|
123
|
-
setComponentRegistry(mergedComponents);
|
|
124
|
-
|
|
125
120
|
// Resolve router provider (default to hash)
|
|
121
|
+
const mergedComponents = $derived.by((): ComponentRegistry => ({ ...defaultComponents, ...userComponents }));
|
|
126
122
|
const resolvedRouter = $derived(routerProvider ?? createHashRouterProvider());
|
|
127
123
|
const resolvedRouteMode = $derived(routeMode ?? (routerProvider ? 'auto' : 'hash'));
|
|
128
124
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (taskProvider) setTaskProvider(taskProvider);
|
|
133
|
-
setResources(resources);
|
|
134
|
-
setRouterProvider(resolvedRouter);
|
|
135
|
-
if (locale) setLocale(locale);
|
|
136
|
-
if (userThemeConfig) configureTheme(userThemeConfig);
|
|
137
|
-
if (defaultTheme) setTheme(defaultTheme);
|
|
125
|
+
function syncComponentRegistry() {
|
|
126
|
+
setComponentRegistry(mergedComponents);
|
|
127
|
+
}
|
|
138
128
|
|
|
139
|
-
|
|
140
|
-
$effect.pre(() => {
|
|
129
|
+
function syncAppContext() {
|
|
141
130
|
setDataProvider(dataProvider);
|
|
142
131
|
if (authProvider) setAuthProvider(authProvider);
|
|
143
132
|
if (taskProvider) setTaskProvider(taskProvider);
|
|
@@ -146,7 +135,15 @@
|
|
|
146
135
|
if (locale) setLocale(locale);
|
|
147
136
|
if (userThemeConfig) configureTheme(userThemeConfig);
|
|
148
137
|
if (defaultTheme) setTheme(defaultTheme);
|
|
149
|
-
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Set up initial context synchronously so children can access resources immediately during first render.
|
|
141
|
+
syncComponentRegistry();
|
|
142
|
+
syncAppContext();
|
|
143
|
+
|
|
144
|
+
// Set up context — use $effect so prop changes are tracked
|
|
145
|
+
$effect.pre(syncComponentRegistry);
|
|
146
|
+
$effect.pre(syncAppContext);
|
|
150
147
|
|
|
151
148
|
const queryClient = $derived(providedQueryClient ?? new QueryClient({
|
|
152
149
|
defaultOptions: {
|
|
@@ -155,11 +152,13 @@
|
|
|
155
152
|
},
|
|
156
153
|
}));
|
|
157
154
|
|
|
158
|
-
|
|
159
|
-
initRouter(resolvedRouter);
|
|
160
|
-
$effect.pre(() => {
|
|
155
|
+
function syncRouter() {
|
|
161
156
|
initRouter(resolvedRouter);
|
|
162
|
-
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Initialize router with provider synchronously
|
|
160
|
+
syncRouter();
|
|
161
|
+
$effect.pre(syncRouter);
|
|
163
162
|
|
|
164
163
|
// Reactive getters for route state
|
|
165
164
|
const route = $derived(getRoute());
|
|
@@ -235,46 +234,50 @@
|
|
|
235
234
|
<Layout {title} {menu} {siteUrl} routeMode={resolvedRouteMode}>
|
|
236
235
|
{#key route + (params.resource ?? '') + (params.id ?? '') + (params.variant ?? '') + (params.columns ?? '')}
|
|
237
236
|
<div class="svadmin-page-enter">
|
|
238
|
-
{#if route === '/2fa'}
|
|
237
|
+
{#if route === '/2fa' || route === '/authentication/branded/2fa'}
|
|
239
238
|
<TwoFactorAuthPage />
|
|
240
239
|
{:else if route === '/public-profile'}
|
|
241
240
|
<PublicProfilePage variant="default" initialTab="projects" />
|
|
242
241
|
{:else if route === '/public-profile/projects/:columns'}
|
|
243
|
-
<PublicProfilePage variant="default" initialTab="projects" columns={params.columns
|
|
242
|
+
<PublicProfilePage variant="default" initialTab="projects" columns={params.columns?.includes('3') ? 3 : 2} />
|
|
244
243
|
{:else if route === '/public-profile/activity'}
|
|
245
244
|
<PublicProfilePage variant="default" initialTab="activity" />
|
|
246
245
|
{:else if route === '/public-profile/teams'}
|
|
247
246
|
<PublicProfilePage variant="default" initialTab="teams" />
|
|
248
247
|
{:else if route === '/public-profile/profiles/:variant'}
|
|
249
248
|
<PublicProfilePage variant={(params.variant === 'company' || params.variant === 'gamer' || params.variant === 'default' ? params.variant : 'default') as 'company' | 'gamer' | 'default'} />
|
|
250
|
-
{:else if route === '/account/get-started'}
|
|
249
|
+
{:else if route === '/account/get-started' || route === '/account/home/get-started'}
|
|
251
250
|
<GetStartedPage />
|
|
252
|
-
{:else if route === '/account/
|
|
251
|
+
{:else if route === '/account/home/user-profile'}
|
|
252
|
+
<SettingsPage />
|
|
253
|
+
{:else if route === '/account/company-profile' || route === '/account/home/company-profile'}
|
|
253
254
|
<CompanyProfilePage />
|
|
254
|
-
{:else if route === '/account/settings-plain'}
|
|
255
|
+
{:else if route === '/account/settings-plain' || route === '/account/home/settings-plain'}
|
|
255
256
|
<SettingsPlainPage />
|
|
256
|
-
{:else if route === '/account/settings-sidebar'}
|
|
257
|
+
{:else if route === '/account/settings-sidebar' || route === '/account/home/settings-sidebar'}
|
|
257
258
|
<SettingsSidebarPage />
|
|
258
|
-
{:else if route === '/account/settings-enterprise'}
|
|
259
|
+
{:else if route === '/account/settings-enterprise' || route === '/account/home/settings-enterprise'}
|
|
259
260
|
<SettingsEnterprisePage />
|
|
260
|
-
{:else if route === '/account
|
|
261
|
+
{:else if route === '/account/:tab'}
|
|
262
|
+
<SettingsPage />
|
|
263
|
+
{:else if route === '/account/import-members' || route === '/account/members/import-members'}
|
|
261
264
|
<ImportMembersPage />
|
|
262
|
-
{:else if route === '/account/members-starter'}
|
|
265
|
+
{:else if route === '/account/members-starter' || route === '/account/members/members-starter'}
|
|
263
266
|
<MembersStarterPage />
|
|
264
|
-
{:else if route === '/account/team-members'}
|
|
267
|
+
{:else if route === '/account/team-members' || route === '/account/members/team-members'}
|
|
265
268
|
<TeamMembersPage />
|
|
266
|
-
{:else if route === '/account/security-log'}
|
|
269
|
+
{:else if route === '/account/security-log' || route === '/account/security/security-log'}
|
|
267
270
|
<SecurityLogPage />
|
|
268
|
-
{:else if route === '/network/user-cards'}
|
|
271
|
+
{:else if route === '/network/user-cards' || route === '/network/user-cards/nft'}
|
|
269
272
|
<UserCardsNFTPage />
|
|
270
|
-
{:else if route === '/network/team-crew'}
|
|
273
|
+
{:else if route === '/network/team-crew' || route === '/network/user-table/team-crew'}
|
|
271
274
|
<TeamCrewTablePage />
|
|
272
275
|
{:else if route.startsWith('/settings')}
|
|
273
276
|
<SettingsPage />
|
|
274
277
|
{:else if route === '/500' || (route === '/:resource' && params.resource === '500')}
|
|
275
278
|
{@const ErrorComp = mergedComponents.ErrorPage || ErrorPage}
|
|
276
279
|
<ErrorComp status="500" />
|
|
277
|
-
{:else if route === '/' || route === ''}
|
|
280
|
+
{:else if route === '/' || route === '' || route === '/inventory-dashboard'}
|
|
278
281
|
{#if dashboard}
|
|
279
282
|
{@render dashboard()}
|
|
280
283
|
{:else}
|
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
import { useCan } from '@svadmin/core';
|
|
4
4
|
import type { Action } from '@svadmin/core';
|
|
5
5
|
|
|
6
|
-
let { resource, action, params, children, fallback } = $props<{
|
|
6
|
+
let { resource, action, params, meta, children, fallback } = $props<{
|
|
7
7
|
resource: string;
|
|
8
8
|
action: Action;
|
|
9
9
|
params?: Record<string, unknown>;
|
|
10
|
+
meta?: Record<string, unknown>;
|
|
10
11
|
children: Snippet;
|
|
11
12
|
fallback?: Snippet;
|
|
12
13
|
}>();
|
|
13
14
|
|
|
14
|
-
const can = useCan(() => ({ resource, action, params }));
|
|
15
|
+
const can = useCan(() => ({ resource, action, params, meta }));
|
|
15
16
|
</script>
|
|
16
17
|
|
|
17
18
|
{#if can.isLoading}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// CreateInferencer — 基于资源定义零配置渲染创建表单
|
|
3
|
+
import CreatePage from './CreatePage.svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
resourceName: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { resourceName, class: className = '' }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<CreatePage {resourceName} class={className} />
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// EditInferencer — 基于资源定义零配置渲染编辑表单
|
|
3
|
+
// 从 URL 自动提取 :id 参数
|
|
4
|
+
import EditPage from './EditPage.svelte';
|
|
5
|
+
import { useParsed } from '@svadmin/core';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
resourceName: string;
|
|
9
|
+
/** 可选:显式指定 id,默认从 URL 解析 */
|
|
10
|
+
id?: string | number;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let { resourceName, id, class: className = '' }: Props = $props();
|
|
15
|
+
const parsed = useParsed();
|
|
16
|
+
const resolvedId = $derived(id ?? parsed.id ?? '');
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
{#if resolvedId}
|
|
20
|
+
<EditPage {resourceName} id={resolvedId} class={className} />
|
|
21
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
resourceName: string;
|
|
3
|
+
/** 可选:显式指定 id,默认从 URL 解析 */
|
|
4
|
+
id?: string | number;
|
|
5
|
+
class?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const EditInferencer: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type EditInferencer = ReturnType<typeof EditInferencer>;
|
|
9
|
+
export default EditInferencer;
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
const CustomNotificationPanel = registry.NotificationPanel;
|
|
36
36
|
</script>
|
|
37
37
|
|
|
38
|
-
<header class="sticky top-0 z-30 flex h-
|
|
38
|
+
<header class="sticky top-0 z-30 flex h-[70px] w-full shrink-0 items-center justify-between border-b border-border/40 bg-background/95 backdrop-blur-sm px-4 md:px-6">
|
|
39
39
|
<div class="flex items-center gap-4">
|
|
40
40
|
{#if children}
|
|
41
41
|
{@render children()}
|
|
@@ -64,10 +64,10 @@
|
|
|
64
64
|
{/if}
|
|
65
65
|
|
|
66
66
|
{#if showSearch && onSearchClick}
|
|
67
|
-
<Button variant="outline" size="sm" onclick={onSearchClick} class="gap-2 text-muted-foreground h-
|
|
67
|
+
<Button variant="outline" size="sm" onclick={onSearchClick} class="gap-2 text-muted-foreground h-9 px-3 rounded-lg border-border/60">
|
|
68
68
|
<Search class="h-3.5 w-3.5" />
|
|
69
|
-
<span class="hidden sm:inline text-xs">
|
|
70
|
-
<kbd class="hidden sm:inline-flex items-center gap-0.5 rounded border bg-muted px-1.5 py-0.5 text-[10px] font-medium font-mono text-muted-foreground">
|
|
69
|
+
<span class="hidden sm:inline text-xs">{t('common.search')}...</span>
|
|
70
|
+
<kbd class="hidden sm:inline-flex items-center gap-0.5 rounded border bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium font-mono text-muted-foreground">
|
|
71
71
|
<span class="text-xs">⌘</span>K
|
|
72
72
|
</kbd>
|
|
73
73
|
</Button>
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import CommandPalette from './CommandPalette.svelte';
|
|
8
8
|
import KeyboardShortcuts from './KeyboardShortcuts.svelte';
|
|
9
9
|
import ChatDialog from './ChatDialog.svelte';
|
|
10
|
-
import TooltipButton from './TooltipButton.svelte';
|
|
11
10
|
import { t } from '@svadmin/core/i18n';
|
|
12
11
|
import { getAuthProvider, getTaskProvider } from '@svadmin/core';
|
|
13
12
|
import type { Identity, MenuItem, TaskProvider, TaskRecord } from '@svadmin/core';
|
|
@@ -17,6 +16,7 @@
|
|
|
17
16
|
import * as Sheet from './ui/sheet/index.js';
|
|
18
17
|
import { Menu } from '@lucide/svelte';
|
|
19
18
|
import { getComponentRegistry } from '../component-registry.svelte.js';
|
|
19
|
+
import { Button } from './ui/button/index.js';
|
|
20
20
|
|
|
21
21
|
let commandOpen = $state(false);
|
|
22
22
|
let shortcutsOpen = $state(false);
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
/>
|
|
106
106
|
{#if loading}
|
|
107
107
|
<div class="flex h-screen" in:fade={{ duration: 150 }}>
|
|
108
|
-
<div class="hidden md:block w-
|
|
108
|
+
<div class="hidden md:block w-[252px] bg-sidebar/80 p-4 space-y-4">
|
|
109
109
|
<Skeleton class="h-8 w-32" />
|
|
110
110
|
<div class="space-y-2 mt-6">
|
|
111
111
|
{#each Array(5) as _, _i (_i)}
|
|
@@ -138,8 +138,10 @@
|
|
|
138
138
|
|
|
139
139
|
<div
|
|
140
140
|
class="flex-1 flex flex-col overflow-hidden transition-all duration-300"
|
|
141
|
-
class:md:ml-
|
|
142
|
-
class:
|
|
141
|
+
class:md:ml-[252px]={!collapsed}
|
|
142
|
+
class:sidebar-content-expanded={!collapsed}
|
|
143
|
+
class:md:ml-[70px]={collapsed}
|
|
144
|
+
class:sidebar-content-collapsed={collapsed}
|
|
143
145
|
>
|
|
144
146
|
<!-- Header with mobile hamburger -->
|
|
145
147
|
<Header
|
|
@@ -149,9 +151,17 @@
|
|
|
149
151
|
>
|
|
150
152
|
{#snippet children()}
|
|
151
153
|
<!-- Mobile hamburger -->
|
|
152
|
-
<
|
|
154
|
+
<Button
|
|
155
|
+
type="button"
|
|
156
|
+
variant="outline"
|
|
157
|
+
size="sm"
|
|
158
|
+
class="md:hidden gap-1.5 px-2.5"
|
|
159
|
+
aria-label={t('common.menu')}
|
|
160
|
+
onclick={() => { mobileMenuOpen = true; }}
|
|
161
|
+
>
|
|
153
162
|
<Menu class="h-5 w-5" />
|
|
154
|
-
|
|
163
|
+
<span class="text-xs">{t('common.menu')}</span>
|
|
164
|
+
</Button>
|
|
155
165
|
{/snippet}
|
|
156
166
|
{#snippet rightActions()}
|
|
157
167
|
{#if taskProvider}
|
|
@@ -162,13 +172,16 @@
|
|
|
162
172
|
{/snippet}
|
|
163
173
|
</Header>
|
|
164
174
|
|
|
165
|
-
<!-- Content area
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
<!-- Content area: responsive padding + centered max-width container
|
|
176
|
+
so wide screens don't stretch content indefinitely (avoids sparse layouts) -->
|
|
177
|
+
<main class="flex-1 overflow-y-auto bg-muted/30 px-4 py-5 sm:px-5 md:px-7.5 md:py-7">
|
|
178
|
+
<div class="mx-auto w-full max-w-[1600px]">
|
|
179
|
+
{#key getPath()}
|
|
180
|
+
<div in:fly={{ x: 20, duration: 150 }} out:fade={{ duration: 80 }}>
|
|
181
|
+
{@render children()}
|
|
182
|
+
</div>
|
|
183
|
+
{/key}
|
|
184
|
+
</div>
|
|
172
185
|
</main>
|
|
173
186
|
</div>
|
|
174
187
|
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// ListInferencer — 基于资源定义零配置渲染列表视图
|
|
3
|
+
// 直接复用 ListPage + AutoTable,不需要手写任何页面代码。
|
|
4
|
+
import ListPage from './ListPage.svelte';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
resourceName: string;
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { resourceName, class: className = '' }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<ListPage {resourceName} class={className} />
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// ResourceInferencer — 一站式零配置 CRUD 渲染器
|
|
3
|
+
// 根据 URL action(list/create/edit/show)自动分发到对应的 Inferencer 组件。
|
|
4
|
+
// 用法:<ResourceInferencer resourceName="posts" />
|
|
5
|
+
import { useParsed } from '@svadmin/core';
|
|
6
|
+
import ListInferencer from './ListInferencer.svelte';
|
|
7
|
+
import CreateInferencer from './CreateInferencer.svelte';
|
|
8
|
+
import EditInferencer from './EditInferencer.svelte';
|
|
9
|
+
import ShowInferencer from './ShowInferencer.svelte';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
resourceName: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { resourceName, class: className = '' }: Props = $props();
|
|
17
|
+
const parsed = useParsed();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
{#if parsed.action === 'create'}
|
|
21
|
+
<CreateInferencer {resourceName} class={className} />
|
|
22
|
+
{:else if parsed.action === 'edit'}
|
|
23
|
+
<EditInferencer {resourceName} class={className} />
|
|
24
|
+
{:else if parsed.action === 'show'}
|
|
25
|
+
<ShowInferencer {resourceName} class={className} />
|
|
26
|
+
{:else}
|
|
27
|
+
<ListInferencer {resourceName} class={className} />
|
|
28
|
+
{/if}
|