@svadmin/ui 0.37.0 → 0.38.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 (46) hide show
  1. package/dist/app.css +42 -88
  2. package/dist/components/AdminApp.svelte +18 -14
  3. package/dist/components/CanAccess.svelte +3 -2
  4. package/dist/components/CanAccess.svelte.d.ts +1 -0
  5. package/dist/components/CreateInferencer.svelte +13 -0
  6. package/dist/components/CreateInferencer.svelte.d.ts +7 -0
  7. package/dist/components/EditInferencer.svelte +21 -0
  8. package/dist/components/EditInferencer.svelte.d.ts +9 -0
  9. package/dist/components/Header.svelte +4 -4
  10. package/dist/components/Layout.svelte +17 -7
  11. package/dist/components/ListInferencer.svelte +14 -0
  12. package/dist/components/ListInferencer.svelte.d.ts +7 -0
  13. package/dist/components/ResourceInferencer.svelte +28 -0
  14. package/dist/components/ResourceInferencer.svelte.d.ts +7 -0
  15. package/dist/components/ResourceOperationsPage.svelte +168 -21
  16. package/dist/components/ResourceOperationsPage.svelte.d.ts +2 -0
  17. package/dist/components/SettingsPage.svelte +3 -2
  18. package/dist/components/ShowInferencer.svelte +21 -0
  19. package/dist/components/ShowInferencer.svelte.d.ts +9 -0
  20. package/dist/components/ShowPage.svelte +2 -2
  21. package/dist/components/ShowPage.svelte.d.ts +1 -0
  22. package/dist/components/Sidebar.svelte +101 -104
  23. package/dist/components/Sidebar.svelte.d.ts +0 -1
  24. package/dist/components/SidebarItem.svelte +114 -52
  25. package/dist/components/account/TeamMembersPage.svelte +9 -3
  26. package/dist/components/buttons/CloneButton.svelte +10 -2
  27. package/dist/components/buttons/CloneButton.svelte.d.ts +2 -4
  28. package/dist/components/buttons/CreateButton.svelte +9 -2
  29. package/dist/components/buttons/CreateButton.svelte.d.ts +2 -4
  30. package/dist/components/buttons/DeleteButton.svelte +10 -2
  31. package/dist/components/buttons/DeleteButton.svelte.d.ts +2 -4
  32. package/dist/components/buttons/EditButton.svelte +10 -2
  33. package/dist/components/buttons/EditButton.svelte.d.ts +2 -4
  34. package/dist/components/buttons/ExportButton.svelte +9 -2
  35. package/dist/components/buttons/ExportButton.svelte.d.ts +2 -4
  36. package/dist/components/buttons/ImportButton.svelte +9 -2
  37. package/dist/components/buttons/ImportButton.svelte.d.ts +2 -4
  38. package/dist/components/buttons/ShowButton.svelte +10 -2
  39. package/dist/components/buttons/ShowButton.svelte.d.ts +2 -4
  40. package/dist/components/buttons/access-control.d.ts +7 -0
  41. package/dist/components/buttons/access-control.js +5 -0
  42. package/dist/components/ui/sheet/sheet.svelte +3 -3
  43. package/dist/index.d.ts +5 -0
  44. package/dist/index.js +5 -0
  45. package/dist/router-state.svelte.js +15 -0
  46. 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(1 0 0 / 80%);
52
- --sidebar-foreground: oklch(0.13 0.02 265);
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.94 0.025 264);
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.16 0.015 265 / 80%);
93
- --sidebar-foreground: oklch(0.985 0 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 / 10%);
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"] .md\:ml-64 {
524
+ [dir="rtl"] .sidebar-content-expanded {
525
525
  margin-left: 0;
526
- margin-right: 16rem;
526
+ margin-right: 252px;
527
527
  }
528
- [dir="rtl"] .md\:ml-16 {
528
+ [dir="rtl"] .sidebar-content-collapsed {
529
529
  margin-left: 0;
530
- margin-right: 4rem;
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 [data-sidebar="sidebar"] {
716
- background: #ffffff !important;
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: 1px solid #f1f3f7 !important;
735
+ border-right-color: #f1f3f7 !important;
719
736
  box-shadow: none !important;
720
737
  }
721
- .dark.layout-clean-flat [data-sidebar="sidebar"],
722
- .dark .layout-clean-flat [data-sidebar="sidebar"] {
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: 1px solid #2b2b40 !important;
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 [data-sidebar="menu-button"] {
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 [data-sidebar="menu-button"]:hover {
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 [data-sidebar="menu-button"]:hover,
750
- .dark .layout-clean-flat [data-sidebar="menu-button"]:hover {
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 [data-sidebar="menu-button"]:hover > svg {
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 [data-sidebar="menu-button"][data-active="true"],
767
- .dark .layout-clean-flat [data-sidebar="menu-button"][data-active="true"] {
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 [data-sidebar="menu-button"][data-active="true"] svg {
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;
@@ -235,46 +235,50 @@
235
235
  <Layout {title} {menu} {siteUrl} routeMode={resolvedRouteMode}>
236
236
  {#key route + (params.resource ?? '') + (params.id ?? '') + (params.variant ?? '') + (params.columns ?? '')}
237
237
  <div class="svadmin-page-enter">
238
- {#if route === '/2fa'}
238
+ {#if route === '/2fa' || route === '/authentication/branded/2fa'}
239
239
  <TwoFactorAuthPage />
240
240
  {:else if route === '/public-profile'}
241
241
  <PublicProfilePage variant="default" initialTab="projects" />
242
242
  {:else if route === '/public-profile/projects/:columns'}
243
- <PublicProfilePage variant="default" initialTab="projects" columns={params.columns === '3' ? 3 : 2} />
243
+ <PublicProfilePage variant="default" initialTab="projects" columns={params.columns?.includes('3') ? 3 : 2} />
244
244
  {:else if route === '/public-profile/activity'}
245
245
  <PublicProfilePage variant="default" initialTab="activity" />
246
246
  {:else if route === '/public-profile/teams'}
247
247
  <PublicProfilePage variant="default" initialTab="teams" />
248
248
  {:else if route === '/public-profile/profiles/:variant'}
249
249
  <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'}
250
+ {:else if route === '/account/get-started' || route === '/account/home/get-started'}
251
251
  <GetStartedPage />
252
- {:else if route === '/account/company-profile'}
252
+ {:else if route === '/account/home/user-profile'}
253
+ <SettingsPage />
254
+ {:else if route === '/account/company-profile' || route === '/account/home/company-profile'}
253
255
  <CompanyProfilePage />
254
- {:else if route === '/account/settings-plain'}
256
+ {:else if route === '/account/settings-plain' || route === '/account/home/settings-plain'}
255
257
  <SettingsPlainPage />
256
- {:else if route === '/account/settings-sidebar'}
258
+ {:else if route === '/account/settings-sidebar' || route === '/account/home/settings-sidebar'}
257
259
  <SettingsSidebarPage />
258
- {:else if route === '/account/settings-enterprise'}
260
+ {:else if route === '/account/settings-enterprise' || route === '/account/home/settings-enterprise'}
259
261
  <SettingsEnterprisePage />
260
- {:else if route === '/account/import-members'}
262
+ {:else if route === '/account/:tab'}
263
+ <SettingsPage />
264
+ {:else if route === '/account/import-members' || route === '/account/members/import-members'}
261
265
  <ImportMembersPage />
262
- {:else if route === '/account/members-starter'}
266
+ {:else if route === '/account/members-starter' || route === '/account/members/members-starter'}
263
267
  <MembersStarterPage />
264
- {:else if route === '/account/team-members'}
268
+ {:else if route === '/account/team-members' || route === '/account/members/team-members'}
265
269
  <TeamMembersPage />
266
- {:else if route === '/account/security-log'}
270
+ {:else if route === '/account/security-log' || route === '/account/security/security-log'}
267
271
  <SecurityLogPage />
268
- {:else if route === '/network/user-cards'}
272
+ {:else if route === '/network/user-cards' || route === '/network/user-cards/nft'}
269
273
  <UserCardsNFTPage />
270
- {:else if route === '/network/team-crew'}
274
+ {:else if route === '/network/team-crew' || route === '/network/user-table/team-crew'}
271
275
  <TeamCrewTablePage />
272
276
  {:else if route.startsWith('/settings')}
273
277
  <SettingsPage />
274
278
  {:else if route === '/500' || (route === '/:resource' && params.resource === '500')}
275
279
  {@const ErrorComp = mergedComponents.ErrorPage || ErrorPage}
276
280
  <ErrorComp status="500" />
277
- {:else if route === '/' || route === ''}
281
+ {:else if route === '/' || route === '' || route === '/inventory-dashboard'}
278
282
  {#if dashboard}
279
283
  {@render dashboard()}
280
284
  {: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}
@@ -4,6 +4,7 @@ type $$ComponentProps = {
4
4
  resource: string;
5
5
  action: Action;
6
6
  params?: Record<string, unknown>;
7
+ meta?: Record<string, unknown>;
7
8
  children: Snippet;
8
9
  fallback?: Snippet;
9
10
  };
@@ -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,7 @@
1
+ interface Props {
2
+ resourceName: string;
3
+ class?: string;
4
+ }
5
+ declare const CreateInferencer: import("svelte").Component<Props, {}, "">;
6
+ type CreateInferencer = ReturnType<typeof CreateInferencer>;
7
+ export default CreateInferencer;
@@ -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-14 w-full shrink-0 items-center justify-between bg-card/60 backdrop-blur-md px-4 md:px-6">
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-8 px-3">
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">Search...</span>
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-64 bg-sidebar/80 p-4 space-y-4">
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-64={!collapsed}
142
- class:md:ml-16={collapsed}
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
- <TooltipButton tooltip={t('common.menu')} class="md:hidden" onclick={() => { mobileMenuOpen = true; }}>
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
- </TooltipButton>
163
+ <span class="text-xs">{t('common.menu')}</span>
164
+ </Button>
155
165
  {/snippet}
156
166
  {#snippet rightActions()}
157
167
  {#if taskProvider}
@@ -163,7 +173,7 @@
163
173
  </Header>
164
174
 
165
175
  <!-- Content area -->
166
- <main class="flex-1 overflow-y-auto p-6 md:p-8">
176
+ <main class="flex-1 overflow-y-auto p-5 md:p-7 bg-muted/30">
167
177
  {#key getPath()}
168
178
  <div in:fly={{ x: 20, duration: 150 }} out:fade={{ duration: 80 }}>
169
179
  {@render children()}
@@ -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,7 @@
1
+ interface Props {
2
+ resourceName: string;
3
+ class?: string;
4
+ }
5
+ declare const ListInferencer: import("svelte").Component<Props, {}, "">;
6
+ type ListInferencer = ReturnType<typeof ListInferencer>;
7
+ export default ListInferencer;
@@ -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}
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ resourceName: string;
3
+ class?: string;
4
+ }
5
+ declare const ResourceInferencer: import("svelte").Component<Props, {}, "">;
6
+ type ResourceInferencer = ReturnType<typeof ResourceInferencer>;
7
+ export default ResourceInferencer;