@lobb-js/studio 0.33.0 → 0.35.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 (119) hide show
  1. package/dist/applyUITheme.d.ts +2 -0
  2. package/dist/{applyStudioTheme.js → applyUITheme.js} +4 -4
  3. package/dist/components/LlmButton.svelte +4 -2
  4. package/dist/components/LlmButton.svelte.d.ts +1 -0
  5. package/dist/components/Studio.svelte +15 -7
  6. package/dist/components/codeEditor.svelte +1 -1
  7. package/dist/components/createManyButton.svelte +2 -2
  8. package/dist/components/dataTable/dataTable.svelte +1 -1
  9. package/dist/components/dataTable/dataTableTabs.svelte +1 -1
  10. package/dist/components/dataTable/filter.svelte +3 -2
  11. package/dist/components/dataTable/filterButton.svelte +1 -1
  12. package/dist/components/dataTable/footer.svelte +1 -1
  13. package/dist/components/dataTable/header.svelte +14 -21
  14. package/dist/components/dataTable/listViewChildren.svelte +1 -1
  15. package/dist/components/dataTable/sort.svelte +1 -1
  16. package/dist/components/dataTable/sortButton.svelte +1 -1
  17. package/dist/components/dataTable/table.svelte +8 -8
  18. package/dist/components/dataTable/utils.js +2 -1
  19. package/dist/components/dataTablePopup/dataTablePopup.svelte +1 -1
  20. package/dist/components/detailView/create/children.svelte +1 -1
  21. package/dist/components/detailView/create/createDetailView.svelte +2 -2
  22. package/dist/components/detailView/create/createDetailViewButton.svelte +2 -0
  23. package/dist/components/detailView/create/createDetailViewButton.svelte.d.ts +1 -0
  24. package/dist/components/detailView/create/createDetailViewChildren.svelte +4 -4
  25. package/dist/components/detailView/create/createManyView.svelte +4 -4
  26. package/dist/components/detailView/detailView.svelte +2 -1
  27. package/dist/components/detailView/fieldInput.svelte +10 -10
  28. package/dist/components/detailView/fieldInputReplacement.svelte +7 -7
  29. package/dist/components/detailView/passwordInput.svelte +1 -1
  30. package/dist/components/detailView/update/updateDetailView.svelte +2 -2
  31. package/dist/components/detailView/update/updateDetailViewButton.svelte +2 -0
  32. package/dist/components/detailView/update/updateDetailViewButton.svelte.d.ts +1 -0
  33. package/dist/components/detailView/update/updateDetailViewChildren.svelte +4 -4
  34. package/dist/components/diffViewer.svelte +1 -1
  35. package/dist/components/drawer.svelte +2 -2
  36. package/dist/components/foreingKeyInput.svelte +2 -2
  37. package/dist/components/horizontalNav.svelte +85 -0
  38. package/dist/components/horizontalNav.svelte.d.ts +3 -0
  39. package/dist/components/importButton.svelte +7 -7
  40. package/dist/components/mainNav.svelte +15 -0
  41. package/dist/components/mainNav.svelte.d.ts +6 -0
  42. package/dist/components/mainNavShared.d.ts +10 -0
  43. package/dist/components/mainNavShared.js +62 -0
  44. package/dist/components/polymorphicInput.svelte +1 -1
  45. package/dist/components/rangeCalendarButton.svelte +11 -12
  46. package/dist/components/richTextEditor.svelte +1 -1
  47. package/dist/components/routes/extensions/publicExtension.svelte +1 -1
  48. package/dist/components/routes/home.svelte +1 -1
  49. package/dist/components/routes/workflows/workflows.svelte +1 -1
  50. package/dist/components/setServerPage.svelte +1 -1
  51. package/dist/components/sidebar/sidebar.svelte +3 -3
  52. package/dist/components/sidebar/sidebarElements.svelte +4 -4
  53. package/dist/components/singletone.svelte +2 -2
  54. package/dist/components/ui/skeleton/skeleton.svelte +1 -1
  55. package/dist/components/ui/tooltip/tooltip-content.svelte +1 -1
  56. package/dist/components/verticalNav.svelte +174 -0
  57. package/dist/components/verticalNav.svelte.d.ts +3 -0
  58. package/dist/components/workflowEditor.svelte +2 -2
  59. package/dist/store.types.d.ts +4 -3
  60. package/dist/utils.d.ts +1 -0
  61. package/dist/utils.js +12 -0
  62. package/package.json +2 -2
  63. package/src/app.css +50 -76
  64. package/src/lib/{applyStudioTheme.ts → applyUITheme.ts} +5 -5
  65. package/src/lib/components/LlmButton.svelte +4 -2
  66. package/src/lib/components/Studio.svelte +15 -7
  67. package/src/lib/components/codeEditor.svelte +1 -1
  68. package/src/lib/components/createManyButton.svelte +2 -2
  69. package/src/lib/components/dataTable/dataTable.svelte +1 -1
  70. package/src/lib/components/dataTable/dataTableTabs.svelte +1 -1
  71. package/src/lib/components/dataTable/filter.svelte +3 -2
  72. package/src/lib/components/dataTable/filterButton.svelte +1 -1
  73. package/src/lib/components/dataTable/footer.svelte +1 -1
  74. package/src/lib/components/dataTable/header.svelte +14 -21
  75. package/src/lib/components/dataTable/listViewChildren.svelte +1 -1
  76. package/src/lib/components/dataTable/sort.svelte +1 -1
  77. package/src/lib/components/dataTable/sortButton.svelte +1 -1
  78. package/src/lib/components/dataTable/table.svelte +8 -8
  79. package/src/lib/components/dataTable/utils.ts +2 -1
  80. package/src/lib/components/dataTablePopup/dataTablePopup.svelte +1 -1
  81. package/src/lib/components/detailView/create/children.svelte +1 -1
  82. package/src/lib/components/detailView/create/createDetailView.svelte +2 -2
  83. package/src/lib/components/detailView/create/createDetailViewButton.svelte +2 -0
  84. package/src/lib/components/detailView/create/createDetailViewChildren.svelte +4 -4
  85. package/src/lib/components/detailView/create/createManyView.svelte +4 -4
  86. package/src/lib/components/detailView/detailView.svelte +2 -1
  87. package/src/lib/components/detailView/fieldInput.svelte +10 -10
  88. package/src/lib/components/detailView/fieldInputReplacement.svelte +7 -7
  89. package/src/lib/components/detailView/passwordInput.svelte +1 -1
  90. package/src/lib/components/detailView/update/updateDetailView.svelte +2 -2
  91. package/src/lib/components/detailView/update/updateDetailViewButton.svelte +2 -0
  92. package/src/lib/components/detailView/update/updateDetailViewChildren.svelte +4 -4
  93. package/src/lib/components/diffViewer.svelte +1 -1
  94. package/src/lib/components/drawer.svelte +2 -2
  95. package/src/lib/components/foreingKeyInput.svelte +2 -2
  96. package/src/lib/components/horizontalNav.svelte +85 -0
  97. package/src/lib/components/importButton.svelte +7 -7
  98. package/src/lib/components/mainNav.svelte +15 -0
  99. package/src/lib/components/mainNavShared.ts +67 -0
  100. package/src/lib/components/polymorphicInput.svelte +1 -1
  101. package/src/lib/components/rangeCalendarButton.svelte +11 -12
  102. package/src/lib/components/richTextEditor.svelte +1 -1
  103. package/src/lib/components/routes/extensions/publicExtension.svelte +1 -1
  104. package/src/lib/components/routes/home.svelte +1 -1
  105. package/src/lib/components/routes/workflows/workflows.svelte +1 -1
  106. package/src/lib/components/setServerPage.svelte +1 -1
  107. package/src/lib/components/sidebar/sidebar.svelte +3 -3
  108. package/src/lib/components/sidebar/sidebarElements.svelte +4 -4
  109. package/src/lib/components/singletone.svelte +2 -2
  110. package/src/lib/components/ui/skeleton/skeleton.svelte +1 -1
  111. package/src/lib/components/ui/tooltip/tooltip-content.svelte +1 -1
  112. package/src/lib/components/verticalNav.svelte +174 -0
  113. package/src/lib/components/workflowEditor.svelte +2 -2
  114. package/src/lib/store.types.ts +2 -2
  115. package/src/lib/utils.ts +12 -0
  116. package/dist/applyStudioTheme.d.ts +0 -2
  117. package/dist/components/miniSidebar.svelte +0 -300
  118. package/dist/components/miniSidebar.svelte.d.ts +0 -5
  119. package/src/lib/components/miniSidebar.svelte +0 -300
@@ -27,14 +27,14 @@ interface Collection {
27
27
  }
28
28
  type Collections = Record<string, Collection>;
29
29
 
30
- export interface StudioTheme {
30
+ export interface UITheme {
31
31
  light?: Record<string, string>;
32
32
  dark?: Record<string, string>;
33
33
  }
34
34
 
35
35
  interface Meta {
36
36
  version: string;
37
- studio?: { theme?: StudioTheme };
37
+ ui?: { theme?: UITheme; horizontalNav?: boolean };
38
38
  relations: Array<any>;
39
39
  collections: Collections;
40
40
  extensions: Record<string, any>;
package/src/lib/utils.ts CHANGED
@@ -24,6 +24,18 @@ export const mediaQueries = {
24
24
  '2xl': new MediaQuery('min-width: 1536px'),
25
25
  }
26
26
 
27
+ // User-facing label for a field type. "string"/"text" are jargon to
28
+ // non-technical users, so we surface "Text"/"Long text" instead. Types
29
+ // not in the map fall through to the raw identifier — extend as needed.
30
+ const FIELD_TYPE_LABELS: Record<string, string> = {
31
+ string: "Text",
32
+ text: "Long text",
33
+ };
34
+ export function getFieldTypeLabel(type: string | undefined | null): string {
35
+ if (!type) return "";
36
+ return FIELD_TYPE_LABELS[type] ?? type;
37
+ }
38
+
27
39
 
28
40
  export function calculateDrawerWidth() {
29
41
  const backgroundDrawerButtons = document.querySelectorAll(".backgroundDrawerButton");
@@ -1,2 +0,0 @@
1
- import type { StudioTheme } from "./store.types";
2
- export declare function applyStudioTheme(theme: StudioTheme | undefined): void;
@@ -1,300 +0,0 @@
1
- <script lang="ts" module>
2
- let isSmallScreen = $derived(!mediaQueries.sm.current);
3
- let isCollapsed = $derived(isSmallScreen);
4
-
5
- export let collapseMiniSideBar = () => {
6
- isCollapsed = true;
7
- };
8
- export let expandMiniSideBar = () => {
9
- isCollapsed = false;
10
- };
11
- </script>
12
-
13
- <script lang="ts">
14
- import { House, Layers, Library, Workflow, X } from "lucide-svelte";
15
- import Button from "./ui/button/button.svelte";
16
- import Separator from "./ui/separator/separator.svelte";
17
- import * as Tooltip from "./ui/tooltip";
18
- import * as Accordion from "./ui/accordion/index.js";
19
- import { onMount } from "svelte";
20
-
21
- import { getStudioContext } from "../context";
22
- import { getDashboardNavs } from "../extensions/extensionUtils";
23
- import { emitEvent } from "../eventSystem";
24
-
25
- const { lobb, ctx } = getStudioContext();
26
- import { mediaQueries } from "../utils";
27
- import * as Popover from "./ui/popover";
28
- import { page } from "$app/state";
29
- import { goto } from "$app/navigation";
30
-
31
- const rawSections: any[][] = [
32
- [
33
- {
34
- label: "Home",
35
- href: "/studio",
36
- icon: House,
37
- },
38
- {
39
- label: "Collections",
40
- href: "/studio/collections",
41
- icon: Library,
42
- },
43
- {
44
- label: "Data Model",
45
- href: "/studio/datamodel",
46
- icon: Layers,
47
- represents: "core_data_model",
48
- },
49
- {
50
- label: "Workflows",
51
- href: "/studio/workflows",
52
- icon: Workflow,
53
- represents: "core_workflows",
54
- },
55
- ],
56
- [],
57
- [],
58
- ];
59
-
60
- const navs = getDashboardNavs(ctx);
61
-
62
- if (navs.top) {
63
- rawSections[0] = [...rawSections[0], ...navs.top];
64
- }
65
- if (navs.middle) {
66
- rawSections[1] = [...rawSections[1], ...navs.middle];
67
- }
68
- if (navs.bottom) {
69
- rawSections[2] = [...rawSections[2], ...navs.bottom];
70
- }
71
-
72
- // Items without a `represents` are always visible. Items with one are
73
- // gated by emitting auth.canAccess — the auth extension (or any
74
- // drop-in replacement) decides based on the current user's session.
75
- // Start empty so nothing flashes before the answers come back.
76
- let sections: any[][] = $state([[], [], []]);
77
-
78
- async function isItemVisible(item: any): Promise<boolean> {
79
- if (!item.represents) return true;
80
- const res = await emitEvent(
81
- { lobb, ctx },
82
- "auth.canAccess",
83
- { collection: item.represents, action: "read" },
84
- );
85
- return res === true;
86
- }
87
-
88
- // Highlight the nav item matching the current URL. "/studio" requires an
89
- // exact match (otherwise it would light up on every sub-route since it's a
90
- // prefix of everything); other items use startsWith so sub-paths
91
- // (e.g. /studio/collections/risks) still highlight their parent.
92
- // Popover items with children are active when any of their children match.
93
- // SvelteKit's pathname can come back with a trailing slash (`/studio/`)
94
- // depending on config, so we normalize it before comparing.
95
- const currentPath = $derived(page.url.pathname.replace(/\/$/, "") || "/");
96
- function isItemActive(item: any): boolean {
97
- if (item.navs) return item.navs.some((c: any) => isItemActive(c));
98
- if (!item.href) return false;
99
- const itemHref = item.href.replace(/\/$/, "") || "/";
100
- if (itemHref === "/studio") return currentPath === "/studio";
101
- return currentPath === itemHref || currentPath.startsWith(itemHref + "/");
102
- }
103
-
104
- // onMount is enough — Studio gets remounted on login/logout (see
105
- // remountStudio in @lobb-js/studio), so by the time this component
106
- // mounts, ctx.extensions.auth.user / permissions are already populated.
107
- onMount(async () => {
108
- const result: any[][] = [[], [], []];
109
- for (let i = 0; i < rawSections.length; i++) {
110
- for (const item of rawSections[i]) {
111
- if (item.navs) {
112
- const visibleChildren: any[] = [];
113
- for (const child of item.navs) {
114
- if (await isItemVisible(child)) visibleChildren.push(child);
115
- }
116
- if (visibleChildren.length && (await isItemVisible(item))) {
117
- result[i].push({ ...item, navs: visibleChildren });
118
- }
119
- } else if (await isItemVisible(item)) {
120
- result[i].push(item);
121
- }
122
- }
123
- }
124
- sections = result;
125
- });
126
- </script>
127
-
128
- {#snippet section(section: any)}
129
- <div class="flex flex-col {isSmallScreen ? 'gap-0' : 'gap-2'}">
130
- {#each section as item}
131
- {@const active = isItemActive(item)}
132
- {#if isSmallScreen}
133
- {#if !item.navs}
134
- <Button
135
- onclick={() => {
136
- if (item.onclick) {
137
- item.onclick();
138
- } else {
139
- goto(item.href);
140
- }
141
- isCollapsed = true;
142
- }}
143
- class="flex items-center justify-start flex-nowrap text-nowrap h-10 w-full {active
144
- ? 'bg-accent text-accent-foreground'
145
- : 'text-muted-foreground'}"
146
- variant="ghost"
147
- size="icon"
148
- Icon={item.icon}
149
- >
150
- {item.label}
151
- </Button>
152
- {:else}
153
- <Accordion.Root type="single">
154
- <Accordion.Item class="border-b-0">
155
- <Accordion.Trigger class="justify-between p-0 h-10">
156
- <div
157
- class="flex items-center gap-2 {active
158
- ? 'text-accent-foreground'
159
- : 'text-muted-foreground'}"
160
- >
161
- <item.icon size="18" />
162
- <div class="text-nowrap">{item.label}</div>
163
- </div>
164
- </Accordion.Trigger>
165
- <Accordion.Content class="pl-2 border-l">
166
- {#each item.navs as childItem}
167
- {@const childActive = isItemActive(childItem)}
168
- <Button
169
- onclick={() => {
170
- if (childItem.onclick) {
171
- childItem.onclick();
172
- } else {
173
- goto(item.href);
174
- }
175
- isCollapsed = true;
176
- }}
177
- class="flex items-center justify-start flex-nowrap text-nowrap h-8 w-full {childActive
178
- ? 'bg-accent text-accent-foreground'
179
- : 'text-muted-foreground'}"
180
- variant="ghost"
181
- size="icon"
182
- Icon={childItem.icon}
183
- >
184
- {childItem.label}
185
- </Button>
186
- {/each}
187
- </Accordion.Content>
188
- </Accordion.Item>
189
- </Accordion.Root>
190
- {/if}
191
- {:else}
192
- <Tooltip.Root>
193
- <Tooltip.Trigger>
194
- {#if !item.navs}
195
- <Button
196
- onclick={() => {
197
- if (item.onclick) {
198
- item.onclick();
199
- }
200
- isCollapsed = true;
201
- }}
202
- href={item.href}
203
- class={active
204
- ? 'bg-accent text-accent-foreground'
205
- : 'text-muted-foreground'}
206
- variant="ghost"
207
- size="icon"
208
- Icon={item.icon}
209
- ></Button>
210
- {:else}
211
- <Popover.Root>
212
- <Popover.Trigger>
213
- <Button
214
- class={active
215
- ? 'bg-accent text-accent-foreground'
216
- : 'text-muted-foreground'}
217
- variant="ghost"
218
- size="icon"
219
- Icon={item.icon}
220
- ></Button>
221
- </Popover.Trigger>
222
- <Popover.Content
223
- sideOffset={17.5}
224
- side="right"
225
- class="popover_content w-60 mb-4 p-0"
226
- >
227
- <div class="py-1">
228
- {#each item.navs as childItem}
229
- {@const childActive = isItemActive(childItem)}
230
- <div
231
- class="px-1 text-xs text-muted-foreground"
232
- >
233
- <Button
234
- variant="ghost"
235
- class="flex h-7 w-full justify-start p-2 text-xs font-normal {childActive
236
- ? 'bg-accent text-accent-foreground'
237
- : 'text-muted-foreground'}"
238
- Icon={childItem.icon}
239
- onclick={() => {
240
- if (childItem.onclick) {
241
- childItem.onclick();
242
- }
243
- }}
244
- href={childItem.href}
245
- >
246
- {childItem.label}
247
- </Button>
248
- </div>
249
- {/each}
250
- </div>
251
- </Popover.Content>
252
- </Popover.Root>
253
- {/if}
254
- </Tooltip.Trigger>
255
- <Tooltip.Content side="right" sideOffset={15}>
256
- {item.label}
257
- </Tooltip.Content>
258
- </Tooltip.Root>
259
- {/if}
260
- {/each}
261
- </div>
262
- {/snippet}
263
-
264
- <div
265
- class="
266
- {isSmallScreen ? 'fixed top-0 left-0 h-full z-50' : 'relative'}
267
- border-r bg-background w-screen
268
- {isCollapsed
269
- ? 'max-w-0 p-0'
270
- : `max-w-14 ${isSmallScreen ? 'px-3 pb-3' : 'p-2'}`}"
271
- style="transition: max-width 150ms, padding 150ms; {isSmallScreen &&
272
- !isCollapsed
273
- ? 'max-width: 100vw'
274
- : ''}"
275
- >
276
- {#if isSmallScreen}
277
- {#if !isCollapsed}
278
- <X
279
- class="absolute h-10 text-muted-foreground hover:bg-transparent cursor-pointer hover:text-foreground"
280
- style="left: 0.6rem; top: 0rem;"
281
- size="18"
282
- onclick={() => (isCollapsed = !isCollapsed)}
283
- />
284
- {/if}
285
- {/if}
286
- <div
287
- class="flex h-full flex-col justify-between gap-2 w-full overflow-hidden"
288
- >
289
- <!-- upper part -->
290
- <div class="flex flex-col gap-2 {isSmallScreen ? 'pt-8' : ''}">
291
- {@render section(sections[0])}
292
- <Separator />
293
- {@render section(sections[1])}
294
- </div>
295
- <div class="flex flex-col gap-2">
296
- <Separator />
297
- {@render section(sections[2])}
298
- </div>
299
- </div>
300
- </div>
@@ -1,5 +0,0 @@
1
- export declare let collapseMiniSideBar: () => void;
2
- export declare let expandMiniSideBar: () => void;
3
- declare const MiniSidebar: import("svelte").Component<Record<string, never>, {}, "">;
4
- type MiniSidebar = ReturnType<typeof MiniSidebar>;
5
- export default MiniSidebar;
@@ -1,300 +0,0 @@
1
- <script lang="ts" module>
2
- let isSmallScreen = $derived(!mediaQueries.sm.current);
3
- let isCollapsed = $derived(isSmallScreen);
4
-
5
- export let collapseMiniSideBar = () => {
6
- isCollapsed = true;
7
- };
8
- export let expandMiniSideBar = () => {
9
- isCollapsed = false;
10
- };
11
- </script>
12
-
13
- <script lang="ts">
14
- import { House, Layers, Library, Workflow, X } from "lucide-svelte";
15
- import Button from "./ui/button/button.svelte";
16
- import Separator from "./ui/separator/separator.svelte";
17
- import * as Tooltip from "./ui/tooltip";
18
- import * as Accordion from "./ui/accordion/index.js";
19
- import { onMount } from "svelte";
20
-
21
- import { getStudioContext } from "../context";
22
- import { getDashboardNavs } from "../extensions/extensionUtils";
23
- import { emitEvent } from "../eventSystem";
24
-
25
- const { lobb, ctx } = getStudioContext();
26
- import { mediaQueries } from "../utils";
27
- import * as Popover from "./ui/popover";
28
- import { page } from "$app/state";
29
- import { goto } from "$app/navigation";
30
-
31
- const rawSections: any[][] = [
32
- [
33
- {
34
- label: "Home",
35
- href: "/studio",
36
- icon: House,
37
- },
38
- {
39
- label: "Collections",
40
- href: "/studio/collections",
41
- icon: Library,
42
- },
43
- {
44
- label: "Data Model",
45
- href: "/studio/datamodel",
46
- icon: Layers,
47
- represents: "core_data_model",
48
- },
49
- {
50
- label: "Workflows",
51
- href: "/studio/workflows",
52
- icon: Workflow,
53
- represents: "core_workflows",
54
- },
55
- ],
56
- [],
57
- [],
58
- ];
59
-
60
- const navs = getDashboardNavs(ctx);
61
-
62
- if (navs.top) {
63
- rawSections[0] = [...rawSections[0], ...navs.top];
64
- }
65
- if (navs.middle) {
66
- rawSections[1] = [...rawSections[1], ...navs.middle];
67
- }
68
- if (navs.bottom) {
69
- rawSections[2] = [...rawSections[2], ...navs.bottom];
70
- }
71
-
72
- // Items without a `represents` are always visible. Items with one are
73
- // gated by emitting auth.canAccess — the auth extension (or any
74
- // drop-in replacement) decides based on the current user's session.
75
- // Start empty so nothing flashes before the answers come back.
76
- let sections: any[][] = $state([[], [], []]);
77
-
78
- async function isItemVisible(item: any): Promise<boolean> {
79
- if (!item.represents) return true;
80
- const res = await emitEvent(
81
- { lobb, ctx },
82
- "auth.canAccess",
83
- { collection: item.represents, action: "read" },
84
- );
85
- return res === true;
86
- }
87
-
88
- // Highlight the nav item matching the current URL. "/studio" requires an
89
- // exact match (otherwise it would light up on every sub-route since it's a
90
- // prefix of everything); other items use startsWith so sub-paths
91
- // (e.g. /studio/collections/risks) still highlight their parent.
92
- // Popover items with children are active when any of their children match.
93
- // SvelteKit's pathname can come back with a trailing slash (`/studio/`)
94
- // depending on config, so we normalize it before comparing.
95
- const currentPath = $derived(page.url.pathname.replace(/\/$/, "") || "/");
96
- function isItemActive(item: any): boolean {
97
- if (item.navs) return item.navs.some((c: any) => isItemActive(c));
98
- if (!item.href) return false;
99
- const itemHref = item.href.replace(/\/$/, "") || "/";
100
- if (itemHref === "/studio") return currentPath === "/studio";
101
- return currentPath === itemHref || currentPath.startsWith(itemHref + "/");
102
- }
103
-
104
- // onMount is enough — Studio gets remounted on login/logout (see
105
- // remountStudio in @lobb-js/studio), so by the time this component
106
- // mounts, ctx.extensions.auth.user / permissions are already populated.
107
- onMount(async () => {
108
- const result: any[][] = [[], [], []];
109
- for (let i = 0; i < rawSections.length; i++) {
110
- for (const item of rawSections[i]) {
111
- if (item.navs) {
112
- const visibleChildren: any[] = [];
113
- for (const child of item.navs) {
114
- if (await isItemVisible(child)) visibleChildren.push(child);
115
- }
116
- if (visibleChildren.length && (await isItemVisible(item))) {
117
- result[i].push({ ...item, navs: visibleChildren });
118
- }
119
- } else if (await isItemVisible(item)) {
120
- result[i].push(item);
121
- }
122
- }
123
- }
124
- sections = result;
125
- });
126
- </script>
127
-
128
- {#snippet section(section: any)}
129
- <div class="flex flex-col {isSmallScreen ? 'gap-0' : 'gap-2'}">
130
- {#each section as item}
131
- {@const active = isItemActive(item)}
132
- {#if isSmallScreen}
133
- {#if !item.navs}
134
- <Button
135
- onclick={() => {
136
- if (item.onclick) {
137
- item.onclick();
138
- } else {
139
- goto(item.href);
140
- }
141
- isCollapsed = true;
142
- }}
143
- class="flex items-center justify-start flex-nowrap text-nowrap h-10 w-full {active
144
- ? 'bg-accent text-accent-foreground'
145
- : 'text-muted-foreground'}"
146
- variant="ghost"
147
- size="icon"
148
- Icon={item.icon}
149
- >
150
- {item.label}
151
- </Button>
152
- {:else}
153
- <Accordion.Root type="single">
154
- <Accordion.Item class="border-b-0">
155
- <Accordion.Trigger class="justify-between p-0 h-10">
156
- <div
157
- class="flex items-center gap-2 {active
158
- ? 'text-accent-foreground'
159
- : 'text-muted-foreground'}"
160
- >
161
- <item.icon size="18" />
162
- <div class="text-nowrap">{item.label}</div>
163
- </div>
164
- </Accordion.Trigger>
165
- <Accordion.Content class="pl-2 border-l">
166
- {#each item.navs as childItem}
167
- {@const childActive = isItemActive(childItem)}
168
- <Button
169
- onclick={() => {
170
- if (childItem.onclick) {
171
- childItem.onclick();
172
- } else {
173
- goto(item.href);
174
- }
175
- isCollapsed = true;
176
- }}
177
- class="flex items-center justify-start flex-nowrap text-nowrap h-8 w-full {childActive
178
- ? 'bg-accent text-accent-foreground'
179
- : 'text-muted-foreground'}"
180
- variant="ghost"
181
- size="icon"
182
- Icon={childItem.icon}
183
- >
184
- {childItem.label}
185
- </Button>
186
- {/each}
187
- </Accordion.Content>
188
- </Accordion.Item>
189
- </Accordion.Root>
190
- {/if}
191
- {:else}
192
- <Tooltip.Root>
193
- <Tooltip.Trigger>
194
- {#if !item.navs}
195
- <Button
196
- onclick={() => {
197
- if (item.onclick) {
198
- item.onclick();
199
- }
200
- isCollapsed = true;
201
- }}
202
- href={item.href}
203
- class={active
204
- ? 'bg-accent text-accent-foreground'
205
- : 'text-muted-foreground'}
206
- variant="ghost"
207
- size="icon"
208
- Icon={item.icon}
209
- ></Button>
210
- {:else}
211
- <Popover.Root>
212
- <Popover.Trigger>
213
- <Button
214
- class={active
215
- ? 'bg-accent text-accent-foreground'
216
- : 'text-muted-foreground'}
217
- variant="ghost"
218
- size="icon"
219
- Icon={item.icon}
220
- ></Button>
221
- </Popover.Trigger>
222
- <Popover.Content
223
- sideOffset={17.5}
224
- side="right"
225
- class="popover_content w-60 mb-4 p-0"
226
- >
227
- <div class="py-1">
228
- {#each item.navs as childItem}
229
- {@const childActive = isItemActive(childItem)}
230
- <div
231
- class="px-1 text-xs text-muted-foreground"
232
- >
233
- <Button
234
- variant="ghost"
235
- class="flex h-7 w-full justify-start p-2 text-xs font-normal {childActive
236
- ? 'bg-accent text-accent-foreground'
237
- : 'text-muted-foreground'}"
238
- Icon={childItem.icon}
239
- onclick={() => {
240
- if (childItem.onclick) {
241
- childItem.onclick();
242
- }
243
- }}
244
- href={childItem.href}
245
- >
246
- {childItem.label}
247
- </Button>
248
- </div>
249
- {/each}
250
- </div>
251
- </Popover.Content>
252
- </Popover.Root>
253
- {/if}
254
- </Tooltip.Trigger>
255
- <Tooltip.Content side="right" sideOffset={15}>
256
- {item.label}
257
- </Tooltip.Content>
258
- </Tooltip.Root>
259
- {/if}
260
- {/each}
261
- </div>
262
- {/snippet}
263
-
264
- <div
265
- class="
266
- {isSmallScreen ? 'fixed top-0 left-0 h-full z-50' : 'relative'}
267
- border-r bg-background w-screen
268
- {isCollapsed
269
- ? 'max-w-0 p-0'
270
- : `max-w-14 ${isSmallScreen ? 'px-3 pb-3' : 'p-2'}`}"
271
- style="transition: max-width 150ms, padding 150ms; {isSmallScreen &&
272
- !isCollapsed
273
- ? 'max-width: 100vw'
274
- : ''}"
275
- >
276
- {#if isSmallScreen}
277
- {#if !isCollapsed}
278
- <X
279
- class="absolute h-10 text-muted-foreground hover:bg-transparent cursor-pointer hover:text-foreground"
280
- style="left: 0.6rem; top: 0rem;"
281
- size="18"
282
- onclick={() => (isCollapsed = !isCollapsed)}
283
- />
284
- {/if}
285
- {/if}
286
- <div
287
- class="flex h-full flex-col justify-between gap-2 w-full overflow-hidden"
288
- >
289
- <!-- upper part -->
290
- <div class="flex flex-col gap-2 {isSmallScreen ? 'pt-8' : ''}">
291
- {@render section(sections[0])}
292
- <Separator />
293
- {@render section(sections[1])}
294
- </div>
295
- <div class="flex flex-col gap-2">
296
- <Separator />
297
- {@render section(sections[2])}
298
- </div>
299
- </div>
300
- </div>