@aphexcms/cms-core 0.1.1 → 0.1.3

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 (98) hide show
  1. package/package.json +22 -5
  2. package/src/api/assets.ts +0 -75
  3. package/src/api/client.ts +0 -150
  4. package/src/api/documents.ts +0 -102
  5. package/src/api/index.ts +0 -7
  6. package/src/api/organizations.ts +0 -154
  7. package/src/api/types.ts +0 -34
  8. package/src/app.d.ts +0 -19
  9. package/src/auth/MULTI_TENANCY_PLAN.md +0 -1183
  10. package/src/auth/auth-errors.ts +0 -23
  11. package/src/auth/auth-hooks.ts +0 -132
  12. package/src/auth/provider.ts +0 -25
  13. package/src/client/index.ts +0 -47
  14. package/src/components/AdminApp.svelte +0 -1078
  15. package/src/components/admin/AdminLayout.svelte +0 -115
  16. package/src/components/admin/DocumentEditor.svelte +0 -795
  17. package/src/components/admin/DocumentTypesList.svelte +0 -97
  18. package/src/components/admin/ObjectModal.svelte +0 -135
  19. package/src/components/admin/SchemaField.svelte +0 -171
  20. package/src/components/admin/fields/ArrayField.svelte +0 -266
  21. package/src/components/admin/fields/BooleanField.svelte +0 -35
  22. package/src/components/admin/fields/ImageField.svelte +0 -284
  23. package/src/components/admin/fields/NumberField.svelte +0 -82
  24. package/src/components/admin/fields/ReferenceField.svelte +0 -260
  25. package/src/components/admin/fields/SlugField.svelte +0 -74
  26. package/src/components/admin/fields/StringField.svelte +0 -40
  27. package/src/components/admin/fields/TextareaField.svelte +0 -40
  28. package/src/components/fields/index.ts +0 -9
  29. package/src/components/index.ts +0 -16
  30. package/src/components/layout/OrganizationSwitcher.svelte +0 -218
  31. package/src/components/layout/Sidebar.svelte +0 -88
  32. package/src/components/layout/sidebar/AppSidebar.svelte +0 -63
  33. package/src/components/layout/sidebar/NavMain.svelte +0 -95
  34. package/src/components/layout/sidebar/NavSecondary.svelte +0 -69
  35. package/src/components/layout/sidebar/NavUser.svelte +0 -85
  36. package/src/config.ts +0 -18
  37. package/src/db/adapters/index.ts +0 -3
  38. package/src/db/index.ts +0 -5
  39. package/src/db/interfaces/asset.ts +0 -61
  40. package/src/db/interfaces/document.ts +0 -53
  41. package/src/db/interfaces/index.ts +0 -98
  42. package/src/db/interfaces/organization.ts +0 -51
  43. package/src/db/interfaces/schema.ts +0 -13
  44. package/src/db/interfaces/user.ts +0 -16
  45. package/src/db/utils/reference-resolver.ts +0 -119
  46. package/src/define.ts +0 -7
  47. package/src/email/index.ts +0 -5
  48. package/src/email/interfaces/email.ts +0 -45
  49. package/src/engine.ts +0 -85
  50. package/src/field-validation/rule.ts +0 -287
  51. package/src/field-validation/utils.ts +0 -91
  52. package/src/hooks.ts +0 -142
  53. package/src/index.ts +0 -5
  54. package/src/lib/is-mobile.svelte.ts +0 -9
  55. package/src/lib/utils.ts +0 -13
  56. package/src/plugins/README.md +0 -154
  57. package/src/routes/assets-by-id.ts +0 -161
  58. package/src/routes/assets-cdn.ts +0 -185
  59. package/src/routes/assets.ts +0 -116
  60. package/src/routes/documents-by-id.ts +0 -188
  61. package/src/routes/documents-publish.ts +0 -211
  62. package/src/routes/documents.ts +0 -172
  63. package/src/routes/index.ts +0 -13
  64. package/src/routes/organizations-by-id.ts +0 -258
  65. package/src/routes/organizations-invitations.ts +0 -183
  66. package/src/routes/organizations-members.ts +0 -301
  67. package/src/routes/organizations-switch.ts +0 -74
  68. package/src/routes/organizations.ts +0 -146
  69. package/src/routes/schemas-by-type.ts +0 -35
  70. package/src/routes/schemas.ts +0 -19
  71. package/src/routes-exports.ts +0 -42
  72. package/src/schema-context.svelte.ts +0 -24
  73. package/src/schema-utils/cleanup.ts +0 -116
  74. package/src/schema-utils/index.ts +0 -4
  75. package/src/schema-utils/utils.ts +0 -47
  76. package/src/schema-utils/validator.ts +0 -58
  77. package/src/server/index.ts +0 -40
  78. package/src/services/asset-service.ts +0 -256
  79. package/src/services/index.ts +0 -6
  80. package/src/storage/adapters/index.ts +0 -2
  81. package/src/storage/adapters/local-storage-adapter.ts +0 -215
  82. package/src/storage/index.ts +0 -8
  83. package/src/storage/interfaces/index.ts +0 -2
  84. package/src/storage/interfaces/storage.ts +0 -114
  85. package/src/storage/providers/storage.ts +0 -83
  86. package/src/types/asset.ts +0 -81
  87. package/src/types/auth.ts +0 -80
  88. package/src/types/config.ts +0 -45
  89. package/src/types/document.ts +0 -38
  90. package/src/types/index.ts +0 -8
  91. package/src/types/organization.ts +0 -119
  92. package/src/types/schemas.ts +0 -151
  93. package/src/types/sidebar.ts +0 -37
  94. package/src/types/user.ts +0 -17
  95. package/src/utils/content-hash.ts +0 -75
  96. package/src/utils/image-url.ts +0 -204
  97. package/src/utils/index.ts +0 -12
  98. package/src/utils/slug.ts +0 -33
@@ -1,40 +0,0 @@
1
- <script lang="ts">
2
- import { Input } from '@aphexcms/ui/shadcn/input';
3
- import type { Field } from '../../../types/schemas.js';
4
-
5
- interface Props {
6
- field: Field;
7
- value: any;
8
- onUpdate: (value: any) => void;
9
- validationClasses?: string;
10
- onBlur?: (event: any) => void;
11
- onFocus?: (event: any) => void;
12
- readonly?: boolean;
13
- }
14
-
15
- let {
16
- field,
17
- value,
18
- onUpdate,
19
- validationClasses,
20
- onBlur,
21
- onFocus,
22
- readonly = false
23
- }: Props = $props();
24
-
25
- function handleInputChange(event: Event) {
26
- const target = event.target as HTMLInputElement;
27
- onUpdate(target.value);
28
- }
29
- </script>
30
-
31
- <Input
32
- id={field.name}
33
- value={value || ''}
34
- placeholder={field.title}
35
- oninput={handleInputChange}
36
- onblur={onBlur}
37
- onfocus={onFocus}
38
- class={validationClasses}
39
- disabled={readonly}
40
- />
@@ -1,40 +0,0 @@
1
- <script lang="ts">
2
- import { Textarea } from '@aphexcms/ui/shadcn/textarea';
3
- import type { Field } from '../../../types/schemas.js';
4
-
5
- interface Props {
6
- field: Field;
7
- value: any;
8
- onUpdate: (value: any) => void;
9
- validationClasses?: string;
10
- onBlur?: (event: any) => void;
11
- onFocus?: (event: any) => void;
12
- readonly?: boolean;
13
- }
14
-
15
- let {
16
- field,
17
- value,
18
- onUpdate,
19
- validationClasses,
20
- onBlur,
21
- onFocus,
22
- readonly = false
23
- }: Props = $props();
24
-
25
- function handleInputChange(event: Event) {
26
- const target = event.target as HTMLTextAreaElement;
27
- onUpdate(target.value);
28
- }
29
- </script>
30
-
31
- <Textarea
32
- id={field.name}
33
- value={value || ''}
34
- placeholder={field.title}
35
- oninput={handleInputChange}
36
- onblur={onBlur}
37
- onfocus={onFocus}
38
- class="min-h-[100px] {validationClasses}"
39
- disabled={readonly}
40
- />
@@ -1,9 +0,0 @@
1
- // Field components re-export
2
- export { default as StringField } from '../admin/fields/StringField.svelte';
3
- export { default as TextareaField } from '../admin/fields/TextareaField.svelte';
4
- export { default as NumberField } from '../admin/fields/NumberField.svelte';
5
- export { default as BooleanField } from '../admin/fields/BooleanField.svelte';
6
- export { default as SlugField } from '../admin/fields/SlugField.svelte';
7
- export { default as ImageField } from '../admin/fields/ImageField.svelte';
8
- export { default as ArrayField } from '../admin/fields/ArrayField.svelte';
9
- export { default as ReferenceField } from '../admin/fields/ReferenceField.svelte';
@@ -1,16 +0,0 @@
1
- // Aphex CMS Components
2
- // All admin interface components -- is this being used?
3
-
4
- // Main admin app
5
- export { default as AdminApp } from './AdminApp.svelte';
6
-
7
- // Sidebar
8
- export { default as Sidebar } from './layout/Sidebar.svelte';
9
-
10
- // Admin components (will be migrated from your current structure)
11
- export { default as DocumentEditor } from './admin/DocumentEditor.svelte';
12
- export { default as DocumentTypesList } from './admin/DocumentTypesList.svelte';
13
- export { default as SchemaField } from './admin/SchemaField.svelte';
14
-
15
- // Field components
16
- export * from './fields/index.js';
@@ -1,218 +0,0 @@
1
- <script lang="ts">
2
- import { organizations } from '../../api/index.js';
3
- import { invalidateAll, goto } from '$app/navigation';
4
- import { page } from '$app/state';
5
- import { SvelteURLSearchParams } from 'svelte/reactivity';
6
- import {
7
- DropdownMenu,
8
- DropdownMenuContent,
9
- DropdownMenuItem,
10
- DropdownMenuLabel,
11
- DropdownMenuSeparator,
12
- DropdownMenuTrigger
13
- } from '@aphexcms/ui/shadcn/dropdown-menu';
14
- import {
15
- SidebarMenu,
16
- SidebarMenuItem,
17
- SidebarMenuButton,
18
- useSidebar
19
- } from '@aphexcms/ui/shadcn/sidebar';
20
- import type { SidebarOrganization } from '../../types/sidebar.js';
21
-
22
- type Props = {
23
- organizations?: SidebarOrganization[];
24
- activeOrganization?: SidebarOrganization;
25
- canCreateOrganization?: boolean;
26
- onOpenChange?: (open: boolean) => void;
27
- };
28
-
29
- let {
30
- organizations: orgs = [],
31
- activeOrganization,
32
- canCreateOrganization = false,
33
- onOpenChange
34
- }: Props = $props();
35
-
36
- const sidebar = useSidebar();
37
-
38
- let isSwitching = $state(false);
39
-
40
- // Set initial orgId in URL if not present
41
- $effect(() => {
42
- if (activeOrganization && !page.url.searchParams.has('orgId')) {
43
- const params = new SvelteURLSearchParams(page.url.searchParams);
44
- params.set('orgId', activeOrganization.id);
45
- goto(`${page.url.pathname}?${params.toString()}`, { replaceState: true });
46
- }
47
- });
48
-
49
- async function handleSwitchOrganization(org: SidebarOrganization) {
50
- if (org.id === activeOrganization?.id) return;
51
-
52
- isSwitching = true;
53
- try {
54
- // Switch organization on the server
55
- await organizations.switch({ organizationId: org.id });
56
-
57
- // Update URL with new orgId and clear editor state
58
- const params = new SvelteURLSearchParams();
59
- params.set('orgId', org.id);
60
-
61
- // Preserve docType if present, but clear editor-specific params
62
- const currentDocType = page.url.searchParams.get('docType');
63
- if (currentDocType) {
64
- params.set('docType', currentDocType);
65
- }
66
-
67
- await goto(`${page.url.pathname}?${params.toString()}`, { replaceState: false });
68
-
69
- // Invalidate all data to refetch with new organization context
70
- await invalidateAll();
71
- } catch (error) {
72
- console.error('Failed to switch organization:', error);
73
- // TODO: Show error toast
74
- } finally {
75
- isSwitching = false;
76
- }
77
- }
78
-
79
- function getOrganizationInitials(name: string): string {
80
- return name
81
- .split(' ')
82
- .map((word) => word[0])
83
- .join('')
84
- .toUpperCase()
85
- .slice(0, 2);
86
- }
87
-
88
- function getRoleLabel(role: string): string {
89
- return role.charAt(0).toUpperCase() + role.slice(1);
90
- }
91
- </script>
92
-
93
- <SidebarMenu>
94
- <SidebarMenuItem>
95
- <DropdownMenu {onOpenChange}>
96
- <DropdownMenuTrigger>
97
- {#snippet child({ props })}
98
- <SidebarMenuButton
99
- {...props}
100
- size="lg"
101
- class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
102
- disabled={isSwitching}
103
- >
104
- {#if activeOrganization}
105
- <div
106
- class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg text-sm font-semibold"
107
- >
108
- {getOrganizationInitials(activeOrganization.name)}
109
- </div>
110
- <div class="grid flex-1 text-left text-sm leading-tight">
111
- <span class="truncate font-medium">
112
- {activeOrganization.name}
113
- </span>
114
- <span class="text-muted-foreground truncate text-xs">
115
- {getRoleLabel(activeOrganization.role)}
116
- </span>
117
- </div>
118
- {:else}
119
- <div
120
- class="bg-muted flex aspect-square size-8 items-center justify-center rounded-lg"
121
- >
122
- <span class="text-muted-foreground text-xs">?</span>
123
- </div>
124
- <span class="text-muted-foreground">No organization</span>
125
- {/if}
126
- <svg
127
- xmlns="http://www.w3.org/2000/svg"
128
- width="16"
129
- height="16"
130
- viewBox="0 0 24 24"
131
- fill="none"
132
- stroke="currentColor"
133
- stroke-width="2"
134
- stroke-linecap="round"
135
- stroke-linejoin="round"
136
- class="ml-auto"
137
- >
138
- <path d="m7 15 5 5 5-5" />
139
- <path d="m7 9 5-5 5 5" />
140
- </svg>
141
- </SidebarMenuButton>
142
- {/snippet}
143
- </DropdownMenuTrigger>
144
- <DropdownMenuContent
145
- class="w-[--bits-dropdown-menu-anchor-width] min-w-56 rounded-lg"
146
- align="start"
147
- side={sidebar.isMobile ? 'bottom' : 'right'}
148
- sideOffset={4}
149
- >
150
- <DropdownMenuLabel class="text-muted-foreground text-xs">Organizations</DropdownMenuLabel>
151
- {#each orgs as org, index (org.id)}
152
- <DropdownMenuItem
153
- onSelect={() => handleSwitchOrganization(org)}
154
- class="gap-2 p-2"
155
- disabled={isSwitching || org.id === activeOrganization?.id}
156
- >
157
- <div
158
- class="flex size-6 items-center justify-center rounded-md border text-xs font-semibold"
159
- >
160
- {getOrganizationInitials(org.name)}
161
- </div>
162
- <div class="flex flex-1 flex-col">
163
- <span class="text-sm">{org.name}</span>
164
- <span class="text-muted-foreground text-xs">{getRoleLabel(org.role)}</span>
165
- </div>
166
- {#if org.id === activeOrganization?.id}
167
- <svg
168
- xmlns="http://www.w3.org/2000/svg"
169
- width="16"
170
- height="16"
171
- viewBox="0 0 24 24"
172
- fill="none"
173
- stroke="currentColor"
174
- stroke-width="2"
175
- stroke-linecap="round"
176
- stroke-linejoin="round"
177
- class="text-primary"
178
- >
179
- <path d="M20 6 9 17l-5-5" />
180
- </svg>
181
- {/if}
182
- </DropdownMenuItem>
183
- {/each}
184
- <DropdownMenuSeparator />
185
- <DropdownMenuItem onSelect={() => goto('/admin/organizations')} class="gap-2 p-2">
186
- <button class="text-muted-foreground font-medium">View all organizations</button>
187
- </DropdownMenuItem>
188
- {#if canCreateOrganization}
189
- <DropdownMenuSeparator />
190
- <DropdownMenuItem class="gap-2 p-2">
191
- <div class="flex size-6 items-center justify-center rounded-md border bg-transparent">
192
- <svg
193
- xmlns="http://www.w3.org/2000/svg"
194
- width="16"
195
- height="16"
196
- viewBox="0 0 24 24"
197
- fill="none"
198
- stroke="currentColor"
199
- stroke-width="2"
200
- stroke-linecap="round"
201
- stroke-linejoin="round"
202
- >
203
- <path d="M5 12h14" />
204
- <path d="M12 5v14" />
205
- </svg>
206
- </div>
207
- <button
208
- class="text-muted-foreground font-medium"
209
- onclick={() => {
210
- goto('/admin/organizations?action=create');
211
- }}>Create organization</button
212
- >
213
- </DropdownMenuItem>
214
- {/if}
215
- </DropdownMenuContent>
216
- </DropdownMenu>
217
- </SidebarMenuItem>
218
- </SidebarMenu>
@@ -1,88 +0,0 @@
1
- <script lang="ts">
2
- import { SidebarProvider, SidebarInset, SidebarTrigger } from '@aphexcms/ui/shadcn/sidebar';
3
- import { Separator } from '@aphexcms/ui/shadcn/separator';
4
- import { Button } from '@aphexcms/ui/shadcn/button';
5
- import { ModeWatcher } from 'mode-watcher';
6
- import { Sun, Moon } from 'lucide-svelte';
7
- import { toggleMode } from 'mode-watcher';
8
- import { page } from '$app/state';
9
- import type { SidebarData } from '../../types/sidebar.js';
10
- import AppSidebar from './sidebar/AppSidebar.svelte';
11
- import { activeTabState } from '$lib/stores/activeTab.svelte.js';
12
-
13
- type Props = {
14
- data: SidebarData;
15
- onSignOut?: () => void | Promise<void>;
16
- children: any;
17
- enableGraphiQL?: boolean;
18
- };
19
-
20
- let { data, onSignOut, children, enableGraphiQL = false }: Props = $props();
21
-
22
- // Only show tabs on the main /admin page
23
- const showTabs = $derived(page.url.pathname === '/admin');
24
- </script>
25
-
26
- <ModeWatcher />
27
- <SidebarProvider class="h-screen">
28
- <AppSidebar {data} {onSignOut} />
29
- <SidebarInset class="flex h-full flex-col">
30
- <header
31
- class="flex h-16 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12"
32
- >
33
- <div class="flex w-full items-center px-4" class:justify-between={showTabs}>
34
- <!-- Left: Trigger and Separator -->
35
- <div class="flex items-center gap-2">
36
- <SidebarTrigger class="-ml-1" />
37
- <Separator orientation="vertical" class="mr-2 h-4" />
38
- </div>
39
-
40
- <!-- Center: Structure/Vision Tabs (only on /admin page) -->
41
- {#if showTabs && activeTabState}
42
- <div
43
- class="bg-muted text-muted-foreground mx-auto inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]"
44
- >
45
- <button
46
- onclick={() => {
47
- if (activeTabState) activeTabState.value = 'structure';
48
- }}
49
- class="{activeTabState.value === 'structure'
50
- ? 'bg-background text-foreground shadow'
51
- : 'text-muted-foreground'} ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
52
- >
53
- Structure
54
- </button>
55
- {#if enableGraphiQL}
56
- <button
57
- onclick={() => {
58
- if (activeTabState) activeTabState.value = 'vision';
59
- }}
60
- class="{activeTabState.value === 'vision'
61
- ? 'bg-background text-foreground shadow'
62
- : 'text-muted-foreground'} ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
63
- >
64
- Vision
65
- </button>
66
- {/if}
67
- </div>
68
- {/if}
69
-
70
- <!-- Right: Theme Toggle -->
71
- <div class:ml-auto={!showTabs}>
72
- <Button onclick={toggleMode} variant="outline" size="icon">
73
- <Sun
74
- class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
75
- />
76
- <Moon
77
- class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
78
- />
79
- <span class="sr-only">Toggle theme</span>
80
- </Button>
81
- </div>
82
- </div>
83
- </header>
84
- <main class="flex flex-1 flex-col overflow-hidden pt-0">
85
- {@render children()}
86
- </main>
87
- </SidebarInset>
88
- </SidebarProvider>
@@ -1,63 +0,0 @@
1
- <script lang="ts">
2
- import {
3
- Sidebar,
4
- SidebarContent,
5
- SidebarFooter,
6
- SidebarHeader,
7
- SidebarRail
8
- } from '@aphexcms/ui/shadcn/sidebar';
9
- import OrganizationSwitcher from '../OrganizationSwitcher.svelte';
10
- import NavMain from './NavMain.svelte';
11
- import NavUser from './NavUser.svelte';
12
- import type { SidebarData } from '../../../types/sidebar.js';
13
- import type { ComponentProps } from 'svelte';
14
-
15
- type Props = ComponentProps<typeof Sidebar> & {
16
- data: SidebarData;
17
- onSignOut?: () => void | Promise<void>;
18
- };
19
-
20
- let { data, onSignOut, ...restProps }: Props = $props();
21
-
22
- // Convert navItems to the format expected by NavMain
23
- const navMainItems = $derived(
24
- data?.navItems?.map((item) => ({
25
- title: item.label,
26
- url: item.href,
27
- icon: undefined, // We'll keep icons simple for now
28
- isActive: false
29
- })) || [
30
- {
31
- title: 'Content',
32
- url: '/admin',
33
- icon: undefined,
34
- isActive: false
35
- }
36
- ]
37
- );
38
- </script>
39
-
40
- <Sidebar collapsible="icon" {...restProps}>
41
- <SidebarHeader>
42
- <!-- Organization Switcher -->
43
- {#if data?.organizations && data.organizations.length > 0}
44
- <OrganizationSwitcher
45
- organizations={data.organizations}
46
- activeOrganization={data.activeOrganization}
47
- canCreateOrganization={data.user?.role === 'super_admin'}
48
- />
49
- {/if}
50
- </SidebarHeader>
51
-
52
- <SidebarContent>
53
- <NavMain items={navMainItems} />
54
- </SidebarContent>
55
-
56
- <SidebarFooter>
57
- {#if data?.user}
58
- <NavUser user={data.user} {onSignOut} />
59
- {/if}
60
- </SidebarFooter>
61
-
62
- <SidebarRail />
63
- </Sidebar>
@@ -1,95 +0,0 @@
1
- <script lang="ts">
2
- import { goto } from '$app/navigation';
3
- import { page } from '$app/state';
4
- import * as Collapsible from '@aphexcms/ui/shadcn/collapsible';
5
- import {
6
- SidebarGroup,
7
- SidebarGroupLabel,
8
- SidebarMenu,
9
- SidebarMenuItem,
10
- SidebarMenuButton,
11
- SidebarMenuSub,
12
- SidebarMenuSubItem,
13
- SidebarMenuSubButton
14
- } from '@aphexcms/ui/shadcn/sidebar';
15
- import { ChevronRight, type Icon as IconType } from 'lucide-svelte';
16
-
17
- type NavItem = {
18
- title: string;
19
- url: string;
20
- icon?: typeof IconType;
21
- isActive?: boolean;
22
- items?: {
23
- title: string;
24
- url: string;
25
- }[];
26
- };
27
-
28
- type Props = {
29
- items: NavItem[];
30
- label?: string;
31
- };
32
-
33
- let { items, label = 'Content' }: Props = $props();
34
-
35
- let currentPath = $derived(page.url.pathname);
36
- </script>
37
-
38
- <SidebarGroup>
39
- <SidebarGroupLabel>{label}</SidebarGroupLabel>
40
- <SidebarMenu>
41
- {#each items as item (item.title)}
42
- {#if item.items && item.items.length > 0}
43
- <Collapsible.Root open={item.isActive} class="group/collapsible">
44
- {#snippet child({ props })}
45
- <SidebarMenuItem {...props}>
46
- <Collapsible.Trigger>
47
- {#snippet child({ props })}
48
- <SidebarMenuButton {...props} tooltipContent={item.title}>
49
- {#if item.icon}
50
- {@const Icon = item.icon}
51
- <Icon class="h-4 w-4" />
52
- {/if}
53
- <span>{item.title}</span>
54
- <ChevronRight
55
- class="ml-auto h-4 w-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
56
- />
57
- </SidebarMenuButton>
58
- {/snippet}
59
- </Collapsible.Trigger>
60
- <Collapsible.Content>
61
- <SidebarMenuSub>
62
- {#each item.items as subItem (subItem.title)}
63
- <SidebarMenuSubItem>
64
- <SidebarMenuSubButton>
65
- {#snippet child({ props })}
66
- <a href={subItem.url} {...props}>
67
- <span>{subItem.title}</span>
68
- </a>
69
- {/snippet}
70
- </SidebarMenuSubButton>
71
- </SidebarMenuSubItem>
72
- {/each}
73
- </SidebarMenuSub>
74
- </Collapsible.Content>
75
- </SidebarMenuItem>
76
- {/snippet}
77
- </Collapsible.Root>
78
- {:else}
79
- <SidebarMenuItem>
80
- <SidebarMenuButton
81
- onclick={() => goto(item.url)}
82
- isActive={currentPath.startsWith(item.url)}
83
- tooltipContent={item.title}
84
- >
85
- {#if item.icon}
86
- {@const Icon = item.icon}
87
- <Icon class="h-4 w-4" />
88
- {/if}
89
- <span>{item.title}</span>
90
- </SidebarMenuButton>
91
- </SidebarMenuItem>
92
- {/if}
93
- {/each}
94
- </SidebarMenu>
95
- </SidebarGroup>
@@ -1,69 +0,0 @@
1
- <script lang="ts">
2
- import {
3
- SidebarGroup,
4
- SidebarGroupContent,
5
- SidebarMenu,
6
- SidebarMenuItem,
7
- SidebarMenuButton
8
- } from '@aphexcms/ui/shadcn/sidebar';
9
- import { Sun, Moon } from 'lucide-svelte';
10
- import { toggleMode } from 'mode-watcher';
11
-
12
- type Props = {
13
- enableGraphiQL?: boolean;
14
- activeTab?: 'structure' | 'vision';
15
- onTabChange?: (tab: 'structure' | 'vision') => void;
16
- };
17
-
18
- let { enableGraphiQL = false, activeTab = $bindable('structure'), onTabChange }: Props = $props();
19
-
20
- function handleTabClick(tab: 'structure' | 'vision') {
21
- activeTab = tab;
22
- if (onTabChange) {
23
- onTabChange(tab);
24
- }
25
- }
26
- </script>
27
-
28
- <SidebarGroup class="mt-auto">
29
- <SidebarGroupContent>
30
- <SidebarMenu>
31
- <!-- Structure Tab -->
32
- <SidebarMenuItem>
33
- <SidebarMenuButton
34
- onclick={() => handleTabClick('structure')}
35
- isActive={activeTab === 'structure'}
36
- tooltipContent="Structure"
37
- size="sm"
38
- >
39
- <span>Structure</span>
40
- </SidebarMenuButton>
41
- </SidebarMenuItem>
42
-
43
- <!-- Vision Tab (if GraphiQL is enabled) -->
44
- {#if enableGraphiQL}
45
- <SidebarMenuItem>
46
- <SidebarMenuButton
47
- onclick={() => handleTabClick('vision')}
48
- isActive={activeTab === 'vision'}
49
- tooltipContent="Vision"
50
- size="sm"
51
- >
52
- <span>Vision</span>
53
- </SidebarMenuButton>
54
- </SidebarMenuItem>
55
- {/if}
56
-
57
- <!-- Theme Toggle -->
58
- <SidebarMenuItem>
59
- <SidebarMenuButton onclick={toggleMode} tooltipContent="Toggle theme" size="sm">
60
- <Sun class="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
61
- <Moon
62
- class="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
63
- />
64
- <span>Theme</span>
65
- </SidebarMenuButton>
66
- </SidebarMenuItem>
67
- </SidebarMenu>
68
- </SidebarGroupContent>
69
- </SidebarGroup>