@lobb-js/studio 0.32.0 → 0.34.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 (102) hide show
  1. package/dist/actions.d.ts +4 -0
  2. package/dist/applyUITheme.d.ts +2 -0
  3. package/dist/applyUITheme.js +36 -0
  4. package/dist/components/LlmButton.svelte +4 -2
  5. package/dist/components/LlmButton.svelte.d.ts +1 -0
  6. package/dist/components/Studio.svelte +15 -5
  7. package/dist/components/canAccess.svelte +52 -0
  8. package/dist/components/canAccess.svelte.d.ts +10 -0
  9. package/dist/components/createManyButton.svelte +2 -2
  10. package/dist/components/dataTable/dataTable.svelte +47 -28
  11. package/dist/components/dataTable/dataTable.svelte.d.ts +4 -0
  12. package/dist/components/dataTable/dataTableTabs.svelte +1 -1
  13. package/dist/components/dataTable/filter.svelte +3 -2
  14. package/dist/components/dataTable/filterButton.svelte +1 -1
  15. package/dist/components/dataTable/footer.svelte +1 -1
  16. package/dist/components/dataTable/header.svelte +20 -26
  17. package/dist/components/dataTable/header.svelte.d.ts +0 -1
  18. package/dist/components/dataTable/listViewChildren.svelte +1 -1
  19. package/dist/components/dataTable/sort.svelte +1 -1
  20. package/dist/components/dataTable/sortButton.svelte +1 -1
  21. package/dist/components/dataTable/table.svelte +4 -4
  22. package/dist/components/dataTablePopup/dataTablePopup.svelte +4 -1
  23. package/dist/components/dataTablePopup/dataTablePopup.svelte.d.ts +4 -0
  24. package/dist/components/detailView/create/createDetailView.svelte +2 -2
  25. package/dist/components/detailView/create/createDetailViewButton.svelte +2 -0
  26. package/dist/components/detailView/create/createDetailViewButton.svelte.d.ts +1 -0
  27. package/dist/components/detailView/create/createDetailViewChildren.svelte +3 -3
  28. package/dist/components/detailView/create/createManyView.svelte +2 -2
  29. package/dist/components/detailView/update/updateDetailView.svelte +2 -2
  30. package/dist/components/detailView/update/updateDetailViewButton.svelte +2 -0
  31. package/dist/components/detailView/update/updateDetailViewButton.svelte.d.ts +1 -0
  32. package/dist/components/detailView/update/updateDetailViewChildren.svelte +3 -3
  33. package/dist/components/drawer.svelte +2 -2
  34. package/dist/components/horizontalNav.svelte +85 -0
  35. package/dist/components/horizontalNav.svelte.d.ts +3 -0
  36. package/dist/components/importButton.svelte +6 -6
  37. package/dist/components/mainNav.svelte +15 -0
  38. package/dist/components/mainNav.svelte.d.ts +6 -0
  39. package/dist/components/mainNavShared.d.ts +10 -0
  40. package/dist/components/mainNavShared.js +62 -0
  41. package/dist/components/rangeCalendarButton.svelte +1 -2
  42. package/dist/components/routes/home.svelte +1 -1
  43. package/dist/components/routes/workflows/workflows.svelte +1 -1
  44. package/dist/components/setServerPage.svelte +1 -1
  45. package/dist/components/sidebar/sidebar.svelte +2 -2
  46. package/dist/components/sidebar/sidebarElements.svelte +3 -3
  47. package/dist/components/singletone.svelte +2 -2
  48. package/dist/components/ui/skeleton/skeleton.svelte +1 -1
  49. package/dist/components/ui/tooltip/tooltip-content.svelte +1 -1
  50. package/dist/components/verticalNav.svelte +174 -0
  51. package/dist/components/verticalNav.svelte.d.ts +3 -0
  52. package/dist/components/workflowEditor.svelte +2 -2
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.js +2 -0
  55. package/dist/store.types.d.ts +8 -0
  56. package/package.json +2 -2
  57. package/src/app.css +52 -75
  58. package/src/lib/actions.ts +1 -0
  59. package/src/lib/applyUITheme.ts +38 -0
  60. package/src/lib/components/LlmButton.svelte +4 -2
  61. package/src/lib/components/Studio.svelte +15 -5
  62. package/src/lib/components/canAccess.svelte +52 -0
  63. package/src/lib/components/createManyButton.svelte +2 -2
  64. package/src/lib/components/dataTable/dataTable.svelte +47 -28
  65. package/src/lib/components/dataTable/dataTableTabs.svelte +1 -1
  66. package/src/lib/components/dataTable/filter.svelte +3 -2
  67. package/src/lib/components/dataTable/filterButton.svelte +1 -1
  68. package/src/lib/components/dataTable/footer.svelte +1 -1
  69. package/src/lib/components/dataTable/header.svelte +20 -26
  70. package/src/lib/components/dataTable/listViewChildren.svelte +1 -1
  71. package/src/lib/components/dataTable/sort.svelte +1 -1
  72. package/src/lib/components/dataTable/sortButton.svelte +1 -1
  73. package/src/lib/components/dataTable/table.svelte +4 -4
  74. package/src/lib/components/dataTablePopup/dataTablePopup.svelte +4 -1
  75. package/src/lib/components/detailView/create/createDetailView.svelte +2 -2
  76. package/src/lib/components/detailView/create/createDetailViewButton.svelte +2 -0
  77. package/src/lib/components/detailView/create/createDetailViewChildren.svelte +3 -3
  78. package/src/lib/components/detailView/create/createManyView.svelte +2 -2
  79. package/src/lib/components/detailView/update/updateDetailView.svelte +2 -2
  80. package/src/lib/components/detailView/update/updateDetailViewButton.svelte +2 -0
  81. package/src/lib/components/detailView/update/updateDetailViewChildren.svelte +3 -3
  82. package/src/lib/components/drawer.svelte +2 -2
  83. package/src/lib/components/horizontalNav.svelte +85 -0
  84. package/src/lib/components/importButton.svelte +6 -6
  85. package/src/lib/components/mainNav.svelte +15 -0
  86. package/src/lib/components/mainNavShared.ts +67 -0
  87. package/src/lib/components/rangeCalendarButton.svelte +1 -2
  88. package/src/lib/components/routes/home.svelte +1 -1
  89. package/src/lib/components/routes/workflows/workflows.svelte +1 -1
  90. package/src/lib/components/setServerPage.svelte +1 -1
  91. package/src/lib/components/sidebar/sidebar.svelte +2 -2
  92. package/src/lib/components/sidebar/sidebarElements.svelte +3 -3
  93. package/src/lib/components/singletone.svelte +2 -2
  94. package/src/lib/components/ui/skeleton/skeleton.svelte +1 -1
  95. package/src/lib/components/ui/tooltip/tooltip-content.svelte +1 -1
  96. package/src/lib/components/verticalNav.svelte +174 -0
  97. package/src/lib/components/workflowEditor.svelte +2 -2
  98. package/src/lib/index.ts +2 -0
  99. package/src/lib/store.types.ts +6 -0
  100. package/dist/components/miniSidebar.svelte +0 -300
  101. package/dist/components/miniSidebar.svelte.d.ts +0 -5
  102. package/src/lib/components/miniSidebar.svelte +0 -300
@@ -13,6 +13,7 @@
13
13
  import Header from "./header.svelte";
14
14
  import Table, { type TableProps } from "./table.svelte";
15
15
  import { getCollectionColumns, getCollectionParamsFields } from "./utils";
16
+ import CanAccess from "../canAccess.svelte";
16
17
  import { Pencil, Trash, Unlink } from "lucide-svelte";
17
18
  import ListViewChildren from "./listViewChildren.svelte";
18
19
  import FieldCell from "./fieldCell.svelte";
@@ -24,8 +25,7 @@
24
25
  import type { ChildrenChanges } from "../detailView/utils";
25
26
  import ExtensionsComponents from "../extensionsComponents.svelte";
26
27
  import { getExtensionUtils, loadExtensionComponents } from "../../extensions/extensionUtils";
27
- import { emitEvent } from "../../eventSystem";
28
- import { onMount, untrack } from "svelte";
28
+ import { untrack } from "svelte";
29
29
  import Tabs from "./dataTableTabs.svelte";
30
30
  import { fade } from "svelte/transition";
31
31
  import type { CollectionTab } from "../../store.types";
@@ -48,6 +48,7 @@
48
48
  tableProps?: Partial<TableProps>;
49
49
  tabs?: CollectionTab[];
50
50
  headerLeft?: Snippet<[]>;
51
+ view?: { id: string; [key: string]: any };
51
52
  }
52
53
 
53
54
  let {
@@ -66,6 +67,7 @@
66
67
  tableProps,
67
68
  tabs,
68
69
  headerLeft,
70
+ view,
69
71
  }: Props = $props();
70
72
 
71
73
  const isRecordingMode = onChanges !== undefined;
@@ -73,19 +75,6 @@
73
75
  untrack(() => changes) ?? { created: [], updated: [], deleted: [], linked: [], unlinked: [] }
74
76
  );
75
77
 
76
- // Gate row/header buttons by the current user's permissions:
77
- // - showUpdate → per-row edit button
78
- // - showCreate → header's Create + Import buttons (passed to Header)
79
- let showUpdate = $state(false);
80
- let showCreate = $state(false);
81
- onMount(async () => {
82
- const [update, create] = await Promise.all([
83
- emitEvent({ lobb, ctx }, "auth.canAccess", { collection: collectionName, action: "update" }),
84
- emitEvent({ lobb, ctx }, "auth.canAccess", { collection: collectionName, action: "create" }),
85
- ]);
86
- showUpdate = update === true;
87
- showCreate = create === true;
88
- });
89
78
 
90
79
  // Derives the displayed rows by applying localChanges on top of server data.
91
80
  const data = $derived.by(() => {
@@ -122,6 +111,23 @@
122
111
 
123
112
  let activeTabFilter = $state<any>(undefined);
124
113
 
114
+ // Named-view lookup: when a `view` prop is supplied, resolve the
115
+ // matching `dataTable.view.<view.id>` registration. Exact key match,
116
+ // no `when` predicate — the caller already picked the view they want.
117
+ const customViewComponent = $derived.by(() => {
118
+ if (!view?.id) return null;
119
+ const key = `dataTable.view.${view.id}`;
120
+ for (const ext of Object.values(ctx.extensions ?? {})) {
121
+ const components = (ext as any)?.components ?? {};
122
+ const entry = components[key];
123
+ if (!entry) continue;
124
+ return entry && typeof entry === "object" && "component" in entry
125
+ ? entry.component
126
+ : entry;
127
+ }
128
+ return null;
129
+ });
130
+
125
131
  // Canonicalize the incoming filter so values like `{ status: "Open" }`
126
132
  // become `{ status: { $eq: "Open" } }`. The Filter UI and the server
127
133
  // both expect operator objects, so doing this once at the boundary
@@ -273,7 +279,7 @@
273
279
 
274
280
  <div
275
281
  bind:clientWidth={dataTableContainerWidth}
276
- class="flex flex-col overflow-auto h-full w-full"
282
+ class="flex flex-col overflow-auto h-full w-full bg-card"
277
283
  >
278
284
  {#snippet rowActionsSnippet(entry: Record<string, any>)}
279
285
  <ExtensionsComponents
@@ -291,7 +297,6 @@
291
297
  {collectionName}
292
298
  bind:selectedRecords
293
299
  {showImport}
294
- {showCreate}
295
300
  {parentContext}
296
301
  onLink={isRecordingMode ? handleLink : undefined}
297
302
  onCreate={isRecordingMode ? handleCreate : undefined}
@@ -311,6 +316,18 @@
311
316
  <Skeleton class="h-8 w-[80%]" />
312
317
  <Skeleton class="h-8 w-[60%]" />
313
318
  </div>
319
+ {:else if customViewComponent}
320
+ {@const CustomView = customViewComponent}
321
+ <CustomView
322
+ {collectionName}
323
+ {data}
324
+ {columns}
325
+ bind:params
326
+ {loading}
327
+ refresh={() => { params = { ...params }; }}
328
+ {view}
329
+ utils={getExtensionUtils(lobb, ctx)}
330
+ />
314
331
  {:else}
315
332
  <Table
316
333
  {data}
@@ -325,17 +342,19 @@
325
342
  {...tableProps}
326
343
  rowActions={hasRowActions ? rowActionsSnippet : undefined}>
327
344
  {#snippet tools(entry)}
328
- {#if showUpdate && showEdit}
329
- <UpdateDetailViewButton
330
- {collectionName}
331
- recordId={entry.id}
332
- variant="ghost"
333
- class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
334
- Icon={Pencil}
335
- changes={isRecordingMode ? localChanges.updated.find((u) => String(u.id) === String(entry.id))?.changes : undefined}
336
- onChanges={isRecordingMode ? (c) => handleUpdate(String(entry.id), c) : undefined}
337
- onSuccessfullSave={!isRecordingMode ? async () => { params = { ...params }; } : undefined}
338
- ></UpdateDetailViewButton>
345
+ {#if showEdit}
346
+ <CanAccess collection={collectionName} action="update">
347
+ <UpdateDetailViewButton
348
+ {collectionName}
349
+ recordId={entry.id}
350
+ variant="ghost"
351
+ class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
352
+ Icon={Pencil}
353
+ changes={isRecordingMode ? localChanges.updated.find((u) => String(u.id) === String(entry.id))?.changes : undefined}
354
+ onChanges={isRecordingMode ? (c) => handleUpdate(String(entry.id), c) : undefined}
355
+ onSuccessfullSave={!isRecordingMode ? async () => { params = { ...params }; } : undefined}
356
+ ></UpdateDetailViewButton>
357
+ </CanAccess>
339
358
  {/if}
340
359
  {#if parentContext}
341
360
  <Button
@@ -44,7 +44,7 @@
44
44
  </script>
45
45
 
46
46
  {#if tabs}
47
- <div class="flex items-center gap-1 px-3 py-1.5 border-b shrink-0 bg-background">
47
+ <div class="flex items-center gap-1 px-3 py-1.5 border-b shrink-0 bg-card">
48
48
  {#each tabs as tab}
49
49
  {@const key = tab.id ?? tab.label}
50
50
  {@const isActive = activeTab ? activeTab === key : (tab.default ?? tabs[0] === tab)}
@@ -71,7 +71,8 @@
71
71
  <Popover.Trigger
72
72
  class={buttonVariants({
73
73
  variant: "ghost",
74
- class: "h-7 px-2 text-xs font-normal text-muted-foreground",
74
+ size: "sm",
75
+ class: "text-muted-foreground",
75
76
  })}
76
77
  >
77
78
  <Plus />
@@ -184,7 +185,7 @@
184
185
  <Popover.Trigger
185
186
  class={buttonVariants({
186
187
  variant: "ghost",
187
- class: "h-7 px-2 text-xs font-normal",
188
+ size: "sm",
188
189
  })}
189
190
  >
190
191
  <Plus />
@@ -21,7 +21,7 @@
21
21
  <Popover.Trigger
22
22
  class={buttonVariants({
23
23
  variant: "ghost",
24
- class: "h-7 px-3 text-xs font-normal",
24
+ size: "sm",
25
25
  })}
26
26
  >
27
27
  <ListFilter />
@@ -27,7 +27,7 @@
27
27
 
28
28
  <div
29
29
  bind:clientWidth={footerWidth}
30
- class="flex justify-between box-content border-t bg-background px-2 h-10"
30
+ class="flex justify-between box-content border-t bg-card px-2 h-10"
31
31
  >
32
32
  <div class="flex items-center gap-2">
33
33
  <Button
@@ -2,8 +2,8 @@
2
2
  import { getStudioContext } from "../../context";
3
3
  import type { Changes } from "../detailView/utils";
4
4
  import type { ParentContext } from "./dataTable.svelte";
5
+ import CanAccess from "../canAccess.svelte";
5
6
  import { Download, ListRestart, Plus, Trash, Link } from "lucide-svelte";
6
- import * as Tooltip from "../ui/tooltip";
7
7
  import LlmButton from "../LlmButton.svelte";
8
8
  import FilterButton from "./filterButton.svelte";
9
9
  import SortButton from "./sortButton.svelte";
@@ -26,7 +26,6 @@
26
26
  onLink?: (record: any) => void;
27
27
  onCreate?: (changes: Changes) => void;
28
28
  showImport?: boolean;
29
- showCreate?: boolean;
30
29
  left?: Snippet<[]>;
31
30
  }
32
31
 
@@ -38,7 +37,6 @@
38
37
  onLink,
39
38
  onCreate,
40
39
  showImport = true,
41
- showCreate = false,
42
40
  left
43
41
  }: Props = $props();
44
42
 
@@ -106,7 +104,7 @@
106
104
  </script>
107
105
 
108
106
  <div
109
- class="flex justify-between items-center gap-2 p-2 border-b bg-background h-10"
107
+ class="flex justify-between items-center gap-2 p-2 border-b bg-card h-12"
110
108
  bind:clientWidth={headerWidth}
111
109
  >
112
110
  <div class="flex items-center gap-1">
@@ -116,7 +114,7 @@
116
114
  Icon={Trash}
117
115
  onclick={handleDeleteButton}
118
116
  variant="outline"
119
- class="h-7 px-3 text-xs font-normal"
117
+ size="sm"
120
118
  >
121
119
  Delete {selectedRecords.length}
122
120
  {selectedRecords.length > 1 ? "records" : "record"}
@@ -136,7 +134,7 @@
136
134
  variant="outline"
137
135
  title="Filter table with AI"
138
136
  description="Tell the AI how do you want to filter the table"
139
- class="h-7 px-3 text-xs font-normal"
137
+ size="sm"
140
138
  format={{
141
139
  type: "json_object",
142
140
  }}
@@ -162,27 +160,23 @@
162
160
  <div>
163
161
  <Button
164
162
  variant="ghost"
165
- class="h-7 px-2 font-normal text-muted-foreground"
163
+ size="sm"
164
+ class="text-muted-foreground"
166
165
  Icon={ListRestart}
167
166
  onclick={() => (params = { ...params })}
168
167
  >
169
168
  {headerIsSmall ? "" : "Refresh"}
170
169
  </Button>
171
- {#if showImport && showCreate}
172
- <Tooltip.Provider delayDuration={0}>
173
- <Tooltip.Root>
174
- <Tooltip.Trigger>
175
- <ImportButton
176
- {collectionName}
177
- variant="outline"
178
- class="h-7 px-2 text-xs font-normal"
179
- Icon={Download}
180
- onSuccessfullSave={() => (params = { ...params })}
181
- />
182
- </Tooltip.Trigger>
183
- <Tooltip.Content>Import</Tooltip.Content>
184
- </Tooltip.Root>
185
- </Tooltip.Provider>
170
+ {#if showImport}
171
+ <CanAccess collection={collectionName} action="create">
172
+ <ImportButton
173
+ {collectionName}
174
+ variant="outline"
175
+ size="sm"
176
+ Icon={Download}
177
+ onSuccessfullSave={() => (params = { ...params })}
178
+ />
179
+ </CanAccess>
186
180
  {/if}
187
181
  <ExtensionsComponents
188
182
  name="listView.header.actions"
@@ -194,24 +188,24 @@
194
188
  <SelectRecord
195
189
  {collectionName}
196
190
  variant="outline"
197
- class="h-7 px-3 text-xs font-normal"
191
+ size="sm"
198
192
  Icon={Link}
199
193
  onSelect={handleLink}
200
194
  >
201
195
  {headerIsSmall ? "" : "Link"}
202
196
  </SelectRecord>
203
197
  {/if}
204
- {#if showCreate}
198
+ <CanAccess collection={collectionName} action="create">
205
199
  <CreateDetailViewButton
206
200
  {collectionName}
207
201
  variant="default"
208
- class="h-7 px-3 text-xs font-normal"
202
+ size="sm"
209
203
  Icon={Plus}
210
204
  onChanges={onCreate ? handleCreate : undefined}
211
205
  onSuccessfullSave={onCreate ? undefined : handleCreate}
212
206
  >
213
207
  {headerIsSmall ? "" : "Create"}
214
208
  </CreateDetailViewButton>
215
- {/if}
209
+ </CanAccess>
216
210
  </div>
217
211
  </div>
@@ -61,7 +61,7 @@
61
61
  <CreateDetailViewButton
62
62
  collectionName={child.collection}
63
63
  variant="ghost"
64
- class="h-7 px-3 text-xs font-normal"
64
+ size="sm"
65
65
  Icon={Plus}
66
66
  values={{ [child.field]: recordId }}
67
67
  onSuccessfullSave={async () => { refreshDataTable = !refreshDataTable; }}
@@ -143,7 +143,7 @@
143
143
  <Popover.Trigger
144
144
  class={buttonVariants({
145
145
  variant: "ghost",
146
- class: "h-7 px-3 text-xs font-normal",
146
+ size: "sm",
147
147
  })}
148
148
  >
149
149
  <Plus />
@@ -17,7 +17,7 @@
17
17
  <Popover.Trigger
18
18
  class={buttonVariants({
19
19
  variant: "ghost",
20
- class: "h-7 px-3 text-xs font-normal",
20
+ size: "sm",
21
21
  })}
22
22
  >
23
23
  <ArrowDownWideNarrow />
@@ -249,7 +249,7 @@
249
249
  class="
250
250
  sticky left-0
251
251
  flex items-center p-2.5 text-xs h-10
252
- bg-background
252
+ bg-card
253
253
  border-r gap-2
254
254
  "
255
255
  >
@@ -295,8 +295,8 @@
295
295
  }}
296
296
  class="
297
297
  flex items-center p-2.5 text-xs h-10 text-nowrap overflow-clip
298
- {select ? 'cursor-pointer' : ''}
299
- bg-background
298
+ {select ? 'cursor-pointer hover:bg-accent' : ''}
299
+ bg-card
300
300
  {lastColumn && !showLastColumnBorder ? '' : 'border-r'}
301
301
  "
302
302
  >
@@ -313,7 +313,7 @@
313
313
  sticky right-0 z-10
314
314
  flex items-center p-2.5 text-xs h-10
315
315
  border-l gap-2
316
- bg-background
316
+ bg-card
317
317
  "
318
318
  >
319
319
  {@render rowActions?.(entry, index)}
@@ -19,6 +19,7 @@
19
19
  showFooter?: boolean;
20
20
  tableProps?: Partial<TableProps>;
21
21
  tabs?: CollectionTab[];
22
+ view?: { id: string; [key: string]: any };
22
23
  onClose?: () => void;
23
24
  }
24
25
 
@@ -32,6 +33,7 @@
32
33
  showFooter = true,
33
34
  tableProps,
34
35
  tabs,
36
+ view,
35
37
  onClose,
36
38
  }: Props = $props();
37
39
 
@@ -57,7 +59,7 @@
57
59
 
58
60
  <div
59
61
  transition:scale={{ duration: 200, easing: cubicOut, start: 0.95 }}
60
- class="fixed left-1/2 top-1/2 z-40 flex h-[85vh] w-[95vw] max-w-7xl -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-lg border bg-background shadow-2xl"
62
+ class="fixed left-1/2 top-1/2 z-40 flex h-[85vh] w-[95vw] max-w-7xl -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-lg border bg-card shadow-2xl"
61
63
  >
62
64
  <div class="flex h-12 shrink-0 items-center justify-between gap-4 border-b px-4">
63
65
  <div class="text-sm font-medium">{title ?? collectionName}</div>
@@ -78,6 +80,7 @@
78
80
  {showFooter}
79
81
  {tableProps}
80
82
  {tabs}
83
+ {view}
81
84
  />
82
85
  </div>
83
86
  </div>
@@ -143,14 +143,14 @@
143
143
  <Button
144
144
  variant="outline"
145
145
  onclick={handleCancel}
146
- class="h-7 px-3 text-xs font-normal"
146
+ size="sm"
147
147
  Icon={X}
148
148
  >
149
149
  Cancel
150
150
  </Button>
151
151
  <Button
152
152
  variant="default"
153
- class="h-7 px-3 text-xs font-normal"
153
+ size="sm"
154
154
  Icon={submitButton?.icon ? submitButton.icon : Plus}
155
155
  onclick={handleSave}
156
156
  >
@@ -6,6 +6,7 @@
6
6
 
7
7
  interface LocalProp extends CreateDetailViewProp {
8
8
  variant?: ButtonProps["variant"];
9
+ size?: ButtonProps["size"];
9
10
  class?: ButtonProps["class"];
10
11
  Icon?: ButtonProps["Icon"];
11
12
  children?: ButtonProps["children"];
@@ -17,6 +18,7 @@
17
18
 
18
19
  <Button
19
20
  variant={props.variant}
21
+ size={props.size}
20
22
  class={props.class}
21
23
  Icon={props.Icon}
22
24
  onclick={() => { open = true; }}
@@ -67,7 +67,7 @@
67
67
  <SelectRecord
68
68
  collectionName={child.collection}
69
69
  variant="outline"
70
- class="h-7 px-3 text-xs font-normal"
70
+ size="sm"
71
71
  Icon={Link}
72
72
  onSelect={(r) => handleEmptyLink(child.collection, r)}
73
73
  >
@@ -76,7 +76,7 @@
76
76
  <CreateDetailViewButton
77
77
  collectionName={child.collection}
78
78
  variant="default"
79
- class="h-7 px-3 text-xs font-normal"
79
+ size="sm"
80
80
  Icon={Plus}
81
81
  onChanges={(c) => handleEmptyCreate(child.collection, c)}
82
82
  >
@@ -86,7 +86,7 @@
86
86
  </div>
87
87
  </div>
88
88
  {:else}
89
- <div class="rounded-lg border bg-background overflow-hidden flex flex-col max-h-96">
89
+ <div class="rounded-lg border bg-card overflow-hidden flex flex-col max-h-96">
90
90
  <DataTable
91
91
  collectionName={child.collection}
92
92
  searchParams={{ children_of: collectionName, parent_id: -1 }}
@@ -137,12 +137,12 @@
137
137
  text="Select existing"
138
138
  onSelect={onRecordSelect}
139
139
  filter={selectRecordFilter}
140
- class="h-7 px-2 font-normal text-xs"
140
+ size="sm"
141
141
  variant="ghost"
142
142
  />
143
143
  <CreateDetailViewButton
144
144
  variant="ghost"
145
- class="h-7 px-2 font-normal text-xs"
145
+ size="sm"
146
146
  Icon={Plus}
147
147
  {collectionName}
148
148
  onChanges={(updated) => { addChanges = updated; }}
@@ -180,14 +180,14 @@
180
180
  <Button
181
181
  variant="outline"
182
182
  onclick={handleCancel}
183
- class="h-7 px-3 text-xs font-normal"
183
+ size="sm"
184
184
  Icon={X}
185
185
  >
186
186
  Cancel
187
187
  </Button>
188
188
  <Button
189
189
  variant="default"
190
- class="h-7 px-3 text-xs font-normal"
190
+ size="sm"
191
191
  Icon={submitButton?.icon ? submitButton.icon : Pencil}
192
192
  onclick={handleSave}
193
193
  disabled={!hasChanges}
@@ -7,6 +7,7 @@
7
7
 
8
8
  interface LocalProp extends UpdateDetailViewProp {
9
9
  variant?: ButtonProps["variant"];
10
+ size?: ButtonProps["size"];
10
11
  class?: ButtonProps["class"];
11
12
  Icon?: ButtonProps["Icon"];
12
13
  children?: ButtonProps["children"];
@@ -33,6 +34,7 @@
33
34
 
34
35
  <Button
35
36
  variant={props.variant}
37
+ size={props.size}
36
38
  class={props.class}
37
39
  Icon={props.Icon}
38
40
  onclick={openView}
@@ -75,7 +75,7 @@
75
75
  <SelectRecord
76
76
  collectionName={child.collection}
77
77
  variant="outline"
78
- class="h-7 px-3 text-xs font-normal"
78
+ size="sm"
79
79
  Icon={Link}
80
80
  onSelect={(r) => handleEmptyLink(child.collection, r)}
81
81
  >
@@ -84,7 +84,7 @@
84
84
  <CreateDetailViewButton
85
85
  collectionName={child.collection}
86
86
  variant="default"
87
- class="h-7 px-3 text-xs font-normal"
87
+ size="sm"
88
88
  Icon={Plus}
89
89
  onChanges={(c) => handleEmptyCreate(child.collection, c)}
90
90
  >
@@ -94,7 +94,7 @@
94
94
  </div>
95
95
  </div>
96
96
  {:else}
97
- <div class="rounded-lg border bg-background overflow-hidden flex flex-col max-h-96">
97
+ <div class="rounded-lg border bg-card overflow-hidden flex flex-col max-h-96">
98
98
  <DataTable
99
99
  collectionName={child.collection}
100
100
  searchParams={{ children_of: collectionName, parent_id: entry.id }}
@@ -38,8 +38,8 @@
38
38
  <div
39
39
  transition:slide={{ axis: position === "bottom" ? "y" : "x" }}
40
40
  class={position === "bottom"
41
- ? "fixed bottom-0 left-0 z-40 flex h-[60vh] w-full flex-col border-t bg-background"
42
- : "fixed right-0 top-0 z-40 flex h-full w-full flex-col border-l bg-background"}
41
+ ? "fixed bottom-0 left-0 z-40 flex h-[60vh] w-full flex-col border-t bg-card"
42
+ : "fixed right-0 top-0 z-40 flex h-full w-full flex-col border-l bg-card"}
43
43
  style={position === "side" ? `max-width: ${calculateDrawerWidth()}px;` : ""}
44
44
  >
45
45
  {@render children?.()}
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import { page } from "$app/state";
4
+ import Button from "./ui/button/button.svelte";
5
+ import Separator from "./ui/separator/separator.svelte";
6
+ import * as Popover from "./ui/popover";
7
+ import { getStudioContext } from "../context";
8
+ import { buildNavSections, isItemActive, type NavItem } from "./mainNavShared";
9
+
10
+ const { lobb, ctx } = getStudioContext();
11
+
12
+ let sections: NavItem[][] = $state([[], [], []]);
13
+ onMount(async () => {
14
+ sections = await buildNavSections(lobb, ctx);
15
+ });
16
+
17
+ const currentPath = $derived(page.url.pathname.replace(/\/$/, "") || "/");
18
+ </script>
19
+
20
+ {#snippet section(section: NavItem[])}
21
+ <div class="flex flex-row items-center gap-1">
22
+ {#each section as item}
23
+ {@const active = isItemActive(currentPath, item)}
24
+ {#if !item.navs}
25
+ <Button
26
+ onclick={() => { if (item.onclick) item.onclick(); }}
27
+ href={item.href}
28
+ class="h-8 px-2.5 text-xs font-normal {active
29
+ ? 'bg-accent text-foreground'
30
+ : 'text-foreground/70 hover:bg-accent/50 hover:text-foreground'}"
31
+ variant="ghost"
32
+ Icon={item.icon}
33
+ >
34
+ {item.label}
35
+ </Button>
36
+ {:else}
37
+ <Popover.Root>
38
+ <Popover.Trigger>
39
+ <Button
40
+ class="h-8 px-2.5 text-xs font-normal {active
41
+ ? 'bg-accent text-accent-foreground'
42
+ : 'text-muted-foreground'}"
43
+ variant="ghost"
44
+ Icon={item.icon}
45
+ >
46
+ {item.label}
47
+ </Button>
48
+ </Popover.Trigger>
49
+ <Popover.Content sideOffset={6} side="bottom" align="start" class="w-60 p-0">
50
+ <div class="py-1">
51
+ {#each item.navs as childItem}
52
+ {@const childActive = isItemActive(currentPath, childItem)}
53
+ <div class="px-1 text-xs text-muted-foreground">
54
+ <Button
55
+ variant="ghost"
56
+ class="flex h-7 w-full justify-start p-2 text-xs font-normal {childActive
57
+ ? 'bg-accent text-accent-foreground'
58
+ : 'text-muted-foreground'}"
59
+ Icon={childItem.icon}
60
+ onclick={() => { if (childItem.onclick) childItem.onclick(); }}
61
+ href={childItem.href}
62
+ >
63
+ {childItem.label}
64
+ </Button>
65
+ </div>
66
+ {/each}
67
+ </div>
68
+ </Popover.Content>
69
+ </Popover.Root>
70
+ {/if}
71
+ {/each}
72
+ </div>
73
+ {/snippet}
74
+
75
+ <div class="relative border-b border-border bg-card text-foreground h-12 w-full px-3 flex items-center justify-between gap-2">
76
+ <div class="flex flex-row items-center gap-2">
77
+ {@render section(sections[0])}
78
+ {#if sections[1].length > 0}
79
+ <Separator orientation="vertical" class="h-6 bg-border" />
80
+ {@render section(sections[1])}
81
+ {/if}
82
+ </div>
83
+
84
+ {@render section(sections[2])}
85
+ </div>