@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
@@ -0,0 +1,174 @@
1
+ <script lang="ts">
2
+ import { X } from "lucide-svelte";
3
+ import { onMount } from "svelte";
4
+ import { page } from "$app/state";
5
+ import { goto } from "$app/navigation";
6
+ import Button from "./ui/button/button.svelte";
7
+ import Separator from "./ui/separator/separator.svelte";
8
+ import * as Tooltip from "./ui/tooltip";
9
+ import * as Accordion from "./ui/accordion/index.js";
10
+ import * as Popover from "./ui/popover";
11
+ import { getStudioContext } from "../context";
12
+ import { mediaQueries } from "../utils";
13
+ import { buildNavSections, isItemActive, type NavItem } from "./mainNavShared";
14
+
15
+ const { lobb, ctx } = getStudioContext();
16
+
17
+ let isSmallScreen = $derived(!mediaQueries.sm.current);
18
+ let isCollapsed = $state(true);
19
+ $effect(() => {
20
+ isCollapsed = isSmallScreen;
21
+ });
22
+
23
+ let sections: NavItem[][] = $state([[], [], []]);
24
+ onMount(async () => {
25
+ sections = await buildNavSections(lobb, ctx);
26
+ });
27
+
28
+ const currentPath = $derived(page.url.pathname.replace(/\/$/, "") || "/");
29
+ </script>
30
+
31
+ {#snippet section(section: NavItem[])}
32
+ <div class="flex flex-col {isSmallScreen ? 'gap-0' : 'gap-2'}">
33
+ {#each section as item}
34
+ {@const active = isItemActive(currentPath, item)}
35
+ {#if isSmallScreen}
36
+ {#if !item.navs}
37
+ <Button
38
+ onclick={() => {
39
+ if (item.onclick) item.onclick();
40
+ else if (item.href) goto(item.href);
41
+ isCollapsed = true;
42
+ }}
43
+ class="flex items-center justify-start flex-nowrap text-nowrap h-10 w-full {active
44
+ ? 'bg-accent text-foreground'
45
+ : 'text-foreground/70 hover:bg-accent/50 hover:text-foreground'}"
46
+ variant="ghost"
47
+ size="icon"
48
+ Icon={item.icon}
49
+ >
50
+ {item.label}
51
+ </Button>
52
+ {:else}
53
+ <Accordion.Root type="single">
54
+ <Accordion.Item class="border-b-0">
55
+ <Accordion.Trigger class="justify-between p-0 h-10">
56
+ <div
57
+ class="flex items-center gap-2 {active
58
+ ? 'text-accent-foreground'
59
+ : 'text-foreground/70'}"
60
+ >
61
+ <item.icon size="18" />
62
+ <div class="text-nowrap">{item.label}</div>
63
+ </div>
64
+ </Accordion.Trigger>
65
+ <Accordion.Content class="pl-2 border-l border-border">
66
+ {#each item.navs as childItem}
67
+ {@const childActive = isItemActive(currentPath, childItem)}
68
+ <Button
69
+ onclick={() => {
70
+ if (childItem.onclick) childItem.onclick();
71
+ else if (childItem.href) goto(childItem.href);
72
+ isCollapsed = true;
73
+ }}
74
+ class="flex items-center justify-start flex-nowrap text-nowrap h-8 w-full {childActive
75
+ ? 'bg-accent text-foreground'
76
+ : 'text-foreground/70 hover:bg-accent/50 hover:text-foreground'}"
77
+ variant="ghost"
78
+ size="icon"
79
+ Icon={childItem.icon}
80
+ >
81
+ {childItem.label}
82
+ </Button>
83
+ {/each}
84
+ </Accordion.Content>
85
+ </Accordion.Item>
86
+ </Accordion.Root>
87
+ {/if}
88
+ {:else}
89
+ <Tooltip.Root>
90
+ <Tooltip.Trigger>
91
+ {#if !item.navs}
92
+ <Button
93
+ onclick={() => { if (item.onclick) item.onclick(); }}
94
+ href={item.href}
95
+ class={active
96
+ ? 'bg-accent text-foreground'
97
+ : 'text-foreground/70 hover:bg-accent/50 hover:text-foreground'}
98
+ variant="ghost"
99
+ size="icon"
100
+ Icon={item.icon}
101
+ ></Button>
102
+ {:else}
103
+ <Popover.Root>
104
+ <Popover.Trigger>
105
+ <Button
106
+ class={active
107
+ ? 'bg-accent text-foreground'
108
+ : 'text-foreground/70 hover:bg-accent/50 hover:text-foreground'}
109
+ variant="ghost"
110
+ size="icon"
111
+ Icon={item.icon}
112
+ ></Button>
113
+ </Popover.Trigger>
114
+ <Popover.Content sideOffset={17.5} side="right" class="popover_content w-60 mb-4 p-0">
115
+ <div class="py-1">
116
+ {#each item.navs as childItem}
117
+ {@const childActive = isItemActive(currentPath, childItem)}
118
+ <div class="px-1 text-xs text-muted-foreground">
119
+ <Button
120
+ variant="ghost"
121
+ class="flex h-7 w-full justify-start p-2 text-xs font-normal {childActive
122
+ ? 'bg-accent text-accent-foreground'
123
+ : 'text-muted-foreground'}"
124
+ Icon={childItem.icon}
125
+ onclick={() => { if (childItem.onclick) childItem.onclick(); }}
126
+ href={childItem.href}
127
+ >
128
+ {childItem.label}
129
+ </Button>
130
+ </div>
131
+ {/each}
132
+ </div>
133
+ </Popover.Content>
134
+ </Popover.Root>
135
+ {/if}
136
+ </Tooltip.Trigger>
137
+ <Tooltip.Content side="right" sideOffset={15}>
138
+ {item.label}
139
+ </Tooltip.Content>
140
+ </Tooltip.Root>
141
+ {/if}
142
+ {/each}
143
+ </div>
144
+ {/snippet}
145
+
146
+ <div
147
+ class="
148
+ {isSmallScreen ? 'fixed top-0 left-0 h-full z-50' : 'relative'}
149
+ border-r border-border bg-card text-foreground w-screen
150
+ {isCollapsed
151
+ ? 'max-w-0 p-0'
152
+ : `max-w-14 ${isSmallScreen ? 'px-3 pb-3' : 'p-2'}`}"
153
+ style="transition: max-width 150ms, padding 150ms; {isSmallScreen && !isCollapsed ? 'max-width: 100vw' : ''}"
154
+ >
155
+ {#if isSmallScreen && !isCollapsed}
156
+ <X
157
+ class="absolute h-10 text-foreground/70 hover:bg-transparent cursor-pointer hover:text-foreground"
158
+ style="left: 0.6rem; top: 0rem;"
159
+ size="18"
160
+ onclick={() => (isCollapsed = !isCollapsed)}
161
+ />
162
+ {/if}
163
+ <div class="flex h-full flex-col justify-between gap-2 w-full overflow-hidden">
164
+ <div class="flex flex-col gap-2 {isSmallScreen ? 'pt-8' : ''}">
165
+ {@render section(sections[0])}
166
+ <Separator class="bg-border" />
167
+ {@render section(sections[1])}
168
+ </div>
169
+ <div class="flex flex-col gap-2">
170
+ <Separator class="bg-border" />
171
+ {@render section(sections[2])}
172
+ </div>
173
+ </div>
174
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const VerticalNav: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type VerticalNav = ReturnType<typeof VerticalNav>;
3
+ export default VerticalNav;
@@ -157,7 +157,7 @@
157
157
  <div>
158
158
  {#if workflow.id}
159
159
  <Button
160
- class="h-7 px-3 text-xs font-normal w-full"
160
+ size="sm" class="w-full"
161
161
  variant="default"
162
162
  onclick={handleUpdateWorkflow}
163
163
  Icon={Edit}
@@ -167,7 +167,7 @@
167
167
  </Button>
168
168
  {:else}
169
169
  <Button
170
- class="h-7 px-3 text-xs font-normal w-full"
170
+ size="sm" class="w-full"
171
171
  variant="default"
172
172
  onclick={handleCreateWorkflow}
173
173
  Icon={Plus}
package/dist/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export { default as SidebarTrigger } from "./components/sidebar/sidebarTrigger.s
12
12
  export { default as CreateDetailViewButton } from "./components/detailView/create/createDetailViewButton.svelte";
13
13
  export { default as UpdateDetailViewButton } from "./components/detailView/update/updateDetailViewButton.svelte";
14
14
  export { default as DetailView } from "./components/detailView/detailView.svelte";
15
+ export { default as CanAccess } from "./components/canAccess.svelte";
16
+ export { default as ExtensionsComponents } from "./components/extensionsComponents.svelte";
15
17
  export * as Tooltip from "./components/ui/tooltip";
16
18
  export * as Breadcrumb from "./components/ui/breadcrumb";
17
19
  export { ContextMenu } from "bits-ui";
package/dist/index.js CHANGED
@@ -11,6 +11,8 @@ export { default as SidebarTrigger } from "./components/sidebar/sidebarTrigger.s
11
11
  export { default as CreateDetailViewButton } from "./components/detailView/create/createDetailViewButton.svelte";
12
12
  export { default as UpdateDetailViewButton } from "./components/detailView/update/updateDetailViewButton.svelte";
13
13
  export { default as DetailView } from "./components/detailView/detailView.svelte";
14
+ export { default as CanAccess } from "./components/canAccess.svelte";
15
+ export { default as ExtensionsComponents } from "./components/extensionsComponents.svelte";
14
16
  export * as Tooltip from "./components/ui/tooltip";
15
17
  export * as Breadcrumb from "./components/ui/breadcrumb";
16
18
  export { ContextMenu } from "bits-ui";
@@ -30,8 +30,16 @@ interface Collection {
30
30
  };
31
31
  }
32
32
  type Collections = Record<string, Collection>;
33
+ export interface UITheme {
34
+ light?: Record<string, string>;
35
+ dark?: Record<string, string>;
36
+ }
33
37
  interface Meta {
34
38
  version: string;
39
+ ui?: {
40
+ theme?: UITheme;
41
+ horizontalNav?: boolean;
42
+ };
35
43
  relations: Array<any>;
36
44
  collections: Collections;
37
45
  extensions: Record<string, any>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lobb-js/studio",
3
3
  "license": "UNLICENSED",
4
- "version": "0.32.0",
4
+ "version": "0.34.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -42,7 +42,7 @@
42
42
  "postpublish": "./scripts/postpublish.sh"
43
43
  },
44
44
  "devDependencies": {
45
- "@lobb-js/core": "^0.33.0",
45
+ "@lobb-js/core": "^0.35.0",
46
46
  "@chromatic-com/storybook": "^4.1.2",
47
47
  "@storybook/addon-a11y": "^10.0.1",
48
48
  "@storybook/addon-docs": "^10.0.1",
package/src/app.css CHANGED
@@ -7,74 +7,64 @@
7
7
  @custom-variant dark (&:is(.dark *));
8
8
 
9
9
  :root {
10
- --radius: 0.625rem;
10
+ --radius: 0.5rem;
11
+
11
12
  --background: oklch(1 0 0);
12
- --foreground: oklch(0.129 0.042 264.695);
13
+ --foreground: oklch(0.20 0.012 138);
14
+
13
15
  --card: oklch(1 0 0);
14
- --card-foreground: oklch(0.129 0.042 264.695);
16
+ --card-foreground: oklch(0.20 0.012 138);
15
17
  --popover: oklch(1 0 0);
16
- --popover-foreground: oklch(0.129 0.042 264.695);
17
- --primary: oklch(0.208 0.042 265.755);
18
- --primary-foreground: oklch(0.984 0.003 247.858);
19
- --secondary: oklch(0.968 0.007 247.896);
20
- --secondary-foreground: oklch(0.208 0.042 265.755);
21
- --muted: oklch(0.968 0.007 247.896);
22
- --muted-foreground: oklch(0.554 0.046 257.417);
23
- --muted-soft: oklch(0.9904 0.0021 247.896);
24
- --accent: oklch(0.968 0.007 247.896);
25
- --accent-foreground: oklch(0.208 0.042 265.755);
26
- --destructive: oklch(0.577 0.245 27.325);
27
- --border: oklch(0.929 0.013 255.508);
28
- --input: oklch(0.929 0.013 255.508);
29
- --ring: oklch(0.704 0.04 256.788);
30
- --chart-1: oklch(0.646 0.222 41.116);
31
- --chart-2: oklch(0.6 0.118 184.704);
32
- --chart-3: oklch(0.398 0.07 227.392);
33
- --chart-4: oklch(0.828 0.189 84.429);
34
- --chart-5: oklch(0.769 0.188 70.08);
35
- --sidebar: oklch(0.984 0.003 247.858);
36
- --sidebar-foreground: oklch(0.129 0.042 264.695);
37
- --sidebar-primary: oklch(0.208 0.042 265.755);
38
- --sidebar-primary-foreground: oklch(0.984 0.003 247.858);
39
- --sidebar-accent: oklch(0.968 0.007 247.896);
40
- --sidebar-accent-foreground: oklch(0.208 0.042 265.755);
41
- --sidebar-border: oklch(0.929 0.013 255.508);
42
- --sidebar-ring: oklch(0.704 0.04 256.788);
18
+ --popover-foreground: oklch(0.20 0.012 138);
19
+
20
+ /* Brand green — used for primary buttons, focus rings, active accents. */
21
+ --primary: oklch(0.62 0.18 138);
22
+ --primary-foreground: oklch(1 0 0);
23
+
24
+ --secondary: oklch(0.97 0.001 138);
25
+ --secondary-foreground: oklch(0.20 0.012 138);
26
+
27
+ --muted: oklch(0.97 0.001 138);
28
+ --muted-foreground: oklch(0.55 0.012 138);
29
+ --muted-soft: oklch(0.985 0.001 138);
30
+
31
+ --accent: oklch(0.95 0.001 138);
32
+ --accent-foreground: oklch(0.20 0.012 138);
33
+
34
+ --destructive: oklch(0.62 0.24 27);
35
+
36
+ --border: oklch(0.91 0.001 138);
37
+ --input: oklch(0.91 0.001 138);
38
+ --ring: oklch(0.62 0.18 138);
43
39
  }
44
40
 
45
41
  .dark {
46
- --background: oklch(0.129 0.042 264.695);
47
- --foreground: oklch(0.984 0.003 247.858);
48
- --card: oklch(0.208 0.042 265.755);
49
- --card-foreground: oklch(0.984 0.003 247.858);
50
- --popover: oklch(0.208 0.042 265.755);
51
- --popover-foreground: oklch(0.984 0.003 247.858);
52
- --primary: oklch(0.929 0.013 255.508);
53
- --primary-foreground: oklch(0.208 0.042 265.755);
54
- --secondary: oklch(0.279 0.041 260.031);
55
- --secondary-foreground: oklch(0.984 0.003 247.858);
56
- --muted: oklch(0.279 0.041 260.031);
57
- --muted-foreground: oklch(0.704 0.04 256.788);
58
- --muted-soft: oklch(0.174 0.042 263.3);
59
- --accent: oklch(0.279 0.041 260.031);
60
- --accent-foreground: oklch(0.984 0.003 247.858);
61
- --destructive: oklch(0.704 0.191 22.216);
62
- --border: oklch(1 0 0 / 10%);
63
- --input: oklch(1 0 0 / 15%);
64
- --ring: oklch(0.551 0.027 264.364);
65
- --chart-1: oklch(0.488 0.243 264.376);
66
- --chart-2: oklch(0.696 0.17 162.48);
67
- --chart-3: oklch(0.769 0.188 70.08);
68
- --chart-4: oklch(0.627 0.265 303.9);
69
- --chart-5: oklch(0.645 0.246 16.439);
70
- --sidebar: oklch(0.208 0.042 265.755);
71
- --sidebar-foreground: oklch(0.984 0.003 247.858);
72
- --sidebar-primary: oklch(0.488 0.243 264.376);
73
- --sidebar-primary-foreground: oklch(0.984 0.003 247.858);
74
- --sidebar-accent: oklch(0.279 0.041 260.031);
75
- --sidebar-accent-foreground: oklch(0.984 0.003 247.858);
76
- --sidebar-border: oklch(1 0 0 / 10%);
77
- --sidebar-ring: oklch(0.551 0.027 264.364);
42
+ --background: oklch(0.16 0.012 138);
43
+ --foreground: oklch(0.94 0.012 138);
44
+
45
+ --card: oklch(0.18 0.012 138);
46
+ --card-foreground: oklch(0.94 0.012 138);
47
+ --popover: oklch(0.18 0.012 138);
48
+ --popover-foreground: oklch(0.94 0.012 138);
49
+
50
+ --primary: oklch(0.72 0.20 140);
51
+ --primary-foreground: oklch(0.16 0.012 138);
52
+
53
+ --secondary: oklch(0.24 0.012 138);
54
+ --secondary-foreground: oklch(0.94 0.012 138);
55
+
56
+ --muted: oklch(0.24 0.012 138);
57
+ --muted-foreground: oklch(0.65 0.012 138);
58
+ --muted-soft: oklch(0.20 0.012 138);
59
+
60
+ --accent: oklch(0.27 0.012 138);
61
+ --accent-foreground: oklch(0.94 0.012 138);
62
+
63
+ --destructive: oklch(0.70 0.22 27);
64
+
65
+ --border: oklch(1 0 0 / 8%);
66
+ --input: oklch(1 0 0 / 12%);
67
+ --ring: oklch(0.72 0.20 140);
78
68
  }
79
69
 
80
70
  @theme inline {
@@ -101,19 +91,6 @@
101
91
  --color-border: var(--border);
102
92
  --color-input: var(--input);
103
93
  --color-ring: var(--ring);
104
- --color-chart-1: var(--chart-1);
105
- --color-chart-2: var(--chart-2);
106
- --color-chart-3: var(--chart-3);
107
- --color-chart-4: var(--chart-4);
108
- --color-chart-5: var(--chart-5);
109
- --color-sidebar: var(--sidebar);
110
- --color-sidebar-foreground: var(--sidebar-foreground);
111
- --color-sidebar-primary: var(--sidebar-primary);
112
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
113
- --color-sidebar-accent: var(--sidebar-accent);
114
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
115
- --color-sidebar-border: var(--sidebar-border);
116
- --color-sidebar-ring: var(--sidebar-ring);
117
94
  }
118
95
 
119
96
  @layer base {
@@ -30,6 +30,7 @@ export interface OpenDataTablePopupProps {
30
30
  showHeader?: boolean;
31
31
  showFooter?: boolean;
32
32
  tabs?: CollectionTab[];
33
+ view?: { id: string; [key: string]: any };
33
34
  }
34
35
 
35
36
  export function showDialog(title: string, description: string): Promise<boolean> {
@@ -0,0 +1,38 @@
1
+ import type { UITheme } from "./store.types";
2
+
3
+ // UI theme injection. Writes the configured CSS-variable overrides into
4
+ // a single <style> tag — light overrides under `:root`, dark under
5
+ // `.dark` — so each mode picks up its own variant. Idempotent: re-runs
6
+ // replace the previous block.
7
+
8
+ const STYLE_ID = "lobb-ui-theme";
9
+
10
+ function buildDeclarations(vars: Record<string, string> | undefined): string {
11
+ if (!vars) return "";
12
+ const out: string[] = [];
13
+ for (const [key, value] of Object.entries(vars)) {
14
+ if (!key.startsWith("--") || !value) continue;
15
+ out.push(`${key}: ${value};`);
16
+ }
17
+ return out.join(" ");
18
+ }
19
+
20
+ export function applyUITheme(theme: UITheme | undefined): void {
21
+ if (typeof document === "undefined") return;
22
+
23
+ document.getElementById(STYLE_ID)?.remove();
24
+ if (!theme) return;
25
+
26
+ const light = buildDeclarations(theme.light);
27
+ const dark = buildDeclarations(theme.dark);
28
+ if (!light && !dark) return;
29
+
30
+ const rules: string[] = [];
31
+ if (light) rules.push(`:root { ${light} }`);
32
+ if (dark) rules.push(`.dark { ${dark} }`);
33
+
34
+ const style = document.createElement("style");
35
+ style.id = STYLE_ID;
36
+ style.textContent = rules.join(" ");
37
+ document.head.appendChild(style);
38
+ }
@@ -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>
@@ -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}
@@ -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
  >