@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
package/dist/actions.d.ts CHANGED
@@ -20,6 +20,10 @@ export interface OpenDataTablePopupProps {
20
20
  showHeader?: boolean;
21
21
  showFooter?: boolean;
22
22
  tabs?: CollectionTab[];
23
+ view?: {
24
+ id: string;
25
+ [key: string]: any;
26
+ };
23
27
  }
24
28
  export declare function showDialog(title: string, description: string): Promise<boolean>;
25
29
  export declare function openCreateDetailView(studioContext: StudioContext, props: CreateDetailViewProp): void;
@@ -0,0 +1,2 @@
1
+ import type { UITheme } from "./store.types";
2
+ export declare function applyUITheme(theme: UITheme | undefined): void;
@@ -0,0 +1,36 @@
1
+ // UI theme injection. Writes the configured CSS-variable overrides into
2
+ // a single <style> tag — light overrides under `:root`, dark under
3
+ // `.dark` — so each mode picks up its own variant. Idempotent: re-runs
4
+ // replace the previous block.
5
+ const STYLE_ID = "lobb-ui-theme";
6
+ function buildDeclarations(vars) {
7
+ if (!vars)
8
+ return "";
9
+ const out = [];
10
+ for (const [key, value] of Object.entries(vars)) {
11
+ if (!key.startsWith("--") || !value)
12
+ continue;
13
+ out.push(`${key}: ${value};`);
14
+ }
15
+ return out.join(" ");
16
+ }
17
+ export function applyUITheme(theme) {
18
+ if (typeof document === "undefined")
19
+ return;
20
+ document.getElementById(STYLE_ID)?.remove();
21
+ if (!theme)
22
+ return;
23
+ const light = buildDeclarations(theme.light);
24
+ const dark = buildDeclarations(theme.dark);
25
+ if (!light && !dark)
26
+ return;
27
+ const rules = [];
28
+ if (light)
29
+ rules.push(`:root { ${light} }`);
30
+ if (dark)
31
+ rules.push(`.dark { ${dark} }`);
32
+ const style = document.createElement("style");
33
+ style.id = STYLE_ID;
34
+ style.textContent = rules.join(" ");
35
+ document.head.appendChild(style);
36
+ }
@@ -16,6 +16,7 @@
16
16
  format?: any;
17
17
  messages?: any[];
18
18
  variant?: ButtonProps["variant"];
19
+ size?: ButtonProps["size"];
19
20
  class?: ButtonProps["class"];
20
21
  Icon?: ButtonProps["Icon"];
21
22
  children?: ButtonProps["children"];
@@ -28,6 +29,7 @@
28
29
  description,
29
30
  placeholder = "write prompt description",
30
31
  variant = "default",
32
+ size,
31
33
  Icon = Brain,
32
34
  onApiResponseComplete,
33
35
  messages,
@@ -98,7 +100,7 @@
98
100
  {#if ctx.meta.extensions.llm && ctx.meta.collections.llm_chat}
99
101
  <Popover.Root bind:open={popoverOpen}>
100
102
  <Popover.Trigger>
101
- <Button {variant} class={props.class}>
103
+ <Button {variant} {size} class={props.class}>
102
104
  {#if loading}
103
105
  <LoaderIcon class="animate-spin" />
104
106
  {:else}
@@ -129,7 +131,7 @@
129
131
  <Button
130
132
  type="submit"
131
133
  Icon={Send}
132
- class="h-7 px-3 text-xs font-normal">Submit</Button
134
+ size="sm">Submit</Button
133
135
  >
134
136
  </form>
135
137
  </Popover.Content>
@@ -7,6 +7,7 @@ interface LocalProp {
7
7
  format?: any;
8
8
  messages?: any[];
9
9
  variant?: ButtonProps["variant"];
10
+ size?: ButtonProps["size"];
10
11
  class?: ButtonProps["class"];
11
12
  Icon?: ButtonProps["Icon"];
12
13
  children?: ButtonProps["children"];
@@ -5,7 +5,7 @@
5
5
  import { createLobb } from "../store.svelte";
6
6
  import { setStudioContext } from "../context";
7
7
  import { LoaderCircle, ServerOff } from "lucide-svelte";
8
- import MiniSidebar from "./miniSidebar.svelte";
8
+ import MainNav from "./mainNav.svelte";
9
9
  import * as Tooltip from "./ui/tooltip";
10
10
  import { page } from "$app/state";
11
11
  import { afterNavigate } from "$app/navigation";
@@ -17,6 +17,7 @@
17
17
  } from "../extensions/extensionUtils";
18
18
  import extensionMap from 'virtual:lobb-studio-extensions';
19
19
  import { mediaQueries } from "../utils";
20
+ import { applyUITheme } from "../applyUITheme";
20
21
  import Home from "./routes/home.svelte";
21
22
  import DataModel from "./routes/data_model/dataModel.svelte";
22
23
  import Collections from "./routes/collections/collections.svelte";
@@ -43,6 +44,12 @@
43
44
 
44
45
  let status: "loading" | "error" | "ready" = $state("loading");
45
46
  let isSmallScreen = $derived(!mediaQueries.sm.current);
47
+ // Horizontal nav forces the layout to row mode (top bar instead of
48
+ // left rail). Falls back to vertical when small-screen mode is on,
49
+ // since there's no useful horizontal layout below 640px.
50
+ const horizontalNav = $derived(
51
+ (ctx.meta as any)?.ui?.horizontalNav === true && !isSmallScreen,
52
+ );
46
53
 
47
54
  onMount(async () => {
48
55
  // Remove the static loading screen defined in app.html — it shows instantly
@@ -50,6 +57,7 @@
50
57
  document.getElementById("app-loading")?.remove();
51
58
  try {
52
59
  ctx.meta = await lobb.getMeta();
60
+ applyUITheme(ctx.meta.ui?.theme);
53
61
  ctx.extensions = await loadExtensions(lobb, ctx, extensionMap);
54
62
  await executeExtensionsOnStartup(lobb, ctx);
55
63
  loadExtensionWorkflows(ctx as any);
@@ -102,11 +110,13 @@
102
110
  {:else}
103
111
  <Tooltip.Provider delayDuration={0} disableHoverableContent={true}>
104
112
  <main
105
- class="bg-muted h-screen w-screen"
106
- style="display: grid; grid-template-columns: {isSmallScreen ? '1fr' : '3.5rem 1fr'};"
113
+ class="bg-muted h-screen w-screen overflow-hidden"
114
+ style={horizontalNav
115
+ ? "display: grid; grid-template-rows: 3rem 1fr;"
116
+ : `display: grid; grid-template-columns: ${isSmallScreen ? '1fr' : '3.5rem 1fr'};`}
107
117
  >
108
- <MiniSidebar />
109
- <div class="min-h-0 h-screen overflow-hidden">
118
+ <MainNav orientation={horizontalNav ? "horizontal" : "vertical"} />
119
+ <div class="min-h-0 h-full overflow-hidden">
110
120
  {#if page.url.pathname.replace(/\/$/, "") === "/studio"}
111
121
  <Home />
112
122
  {:else if page.url.pathname.startsWith("/studio/collections")}
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ // Declarative permission gate. Replaces the recurring pattern of
3
+ // `let canX = $state(false); onMount(async () => canX = await emitEvent
4
+ // ("auth.canAccess", { collection, action }) === true);` plus an `{#if canX}`.
5
+ //
6
+ // Usage:
7
+ // <CanAccess collection="risks" action="update">
8
+ // <EditButton ... />
9
+ // </CanAccess>
10
+ //
11
+ // Optional `fallback` snippet renders when the user is NOT allowed
12
+ // (defaults to nothing). The brief in-flight window before the answer
13
+ // is known renders nothing so we don't flash unauthorized content.
14
+ import type { Snippet } from "svelte";
15
+ import { onMount } from "svelte";
16
+ import { getStudioContext } from "../context";
17
+ import { emitEvent } from "../eventSystem";
18
+
19
+ interface Props {
20
+ collection: string;
21
+ action: "read" | "create" | "update" | "delete" | string;
22
+ children: Snippet;
23
+ fallback?: Snippet;
24
+ }
25
+
26
+ const { collection, action, children, fallback }: Props = $props();
27
+ const { lobb, ctx } = getStudioContext();
28
+
29
+ // null = "haven't checked yet" — distinguished from false so the fallback
30
+ // doesn't flash during the async resolution.
31
+ let allowed = $state<boolean | null>(null);
32
+
33
+ onMount(async () => {
34
+ try {
35
+ const result = await emitEvent({ lobb, ctx }, "auth.canAccess", {
36
+ collection,
37
+ action,
38
+ });
39
+ allowed = result === true;
40
+ } catch {
41
+ // No handler registered (auth extension not loaded), or the
42
+ // handler threw — fail closed.
43
+ allowed = false;
44
+ }
45
+ });
46
+ </script>
47
+
48
+ {#if allowed === true}
49
+ {@render children()}
50
+ {:else if allowed === false && fallback}
51
+ {@render fallback()}
52
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ collection: string;
4
+ action: "read" | "create" | "update" | "delete" | string;
5
+ children: Snippet;
6
+ fallback?: Snippet;
7
+ }
8
+ declare const CanAccess: import("svelte").Component<Props, {}, "">;
9
+ type CanAccess = ReturnType<typeof CanAccess>;
10
+ export default CanAccess;
@@ -90,14 +90,14 @@
90
90
  <Button
91
91
  variant="outline"
92
92
  onclick={() => hideDrawer()}
93
- class="h-7 px-3 text-xs font-normal"
93
+ size="sm"
94
94
  Icon={X}
95
95
  >
96
96
  Cancel
97
97
  </Button>
98
98
  <Button
99
99
  variant="default"
100
- class="h-7 px-3 text-xs font-normal"
100
+ size="sm"
101
101
  Icon={Plus}
102
102
  onclick={handleCreateMany}
103
103
  >
@@ -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
@@ -22,6 +22,10 @@ interface Props {
22
22
  tableProps?: Partial<TableProps>;
23
23
  tabs?: CollectionTab[];
24
24
  headerLeft?: Snippet<[]>;
25
+ view?: {
26
+ id: string;
27
+ [key: string]: any;
28
+ };
25
29
  }
26
30
  declare const DataTable: import("svelte").Component<Props, {}, "">;
27
31
  type DataTable = ReturnType<typeof DataTable>;
@@ -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>
@@ -9,7 +9,6 @@ interface Props {
9
9
  onLink?: (record: any) => void;
10
10
  onCreate?: (changes: Changes) => void;
11
11
  showImport?: boolean;
12
- showCreate?: boolean;
13
12
  left?: Snippet<[]>;
14
13
  }
15
14
  declare const Header: import("svelte").Component<Props, {}, "selectedRecords" | "params">;
@@ -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>