@valentinkolb/cloud 0.3.1 → 0.5.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 (194) hide show
  1. package/package.json +18 -8
  2. package/scripts/preload.ts +78 -23
  3. package/src/_internal/define-app.ts +119 -47
  4. package/src/_internal/runtime-context.ts +1 -0
  5. package/src/api/accounts-entities.ts +4 -0
  6. package/src/api/admin-core-settings.ts +98 -0
  7. package/src/api/announcements.ts +131 -0
  8. package/src/api/auth/schemas.ts +24 -0
  9. package/src/api/auth.ts +113 -10
  10. package/src/api/index.ts +15 -25
  11. package/src/api/me.ts +203 -14
  12. package/src/api/search/schemas.ts +1 -0
  13. package/src/api/search.ts +62 -8
  14. package/src/config/ssr.ts +2 -9
  15. package/src/contracts/announcements.test.ts +37 -0
  16. package/src/contracts/announcements.ts +121 -0
  17. package/src/contracts/app.ts +4 -0
  18. package/src/contracts/index.ts +3 -2
  19. package/src/contracts/registry.ts +4 -0
  20. package/src/contracts/shared.ts +108 -1
  21. package/src/desktop/index.ts +704 -0
  22. package/src/desktop/solid.tsx +938 -0
  23. package/src/server/api/index.ts +1 -1
  24. package/src/server/api/respond.ts +50 -10
  25. package/src/server/index.ts +44 -38
  26. package/src/server/middleware/auth.ts +98 -9
  27. package/src/server/middleware/index.ts +2 -1
  28. package/src/server/middleware/settings.ts +26 -0
  29. package/src/server/services/access.test.ts +197 -0
  30. package/src/server/services/access.ts +254 -6
  31. package/src/server/services/index.ts +14 -11
  32. package/src/server/services/pagination.ts +22 -0
  33. package/src/server/time.ts +45 -0
  34. package/src/services/account-lifecycle/index.ts +142 -18
  35. package/src/services/accounts/app.ts +658 -170
  36. package/src/services/accounts/authz.test.ts +77 -0
  37. package/src/services/accounts/authz.ts +22 -0
  38. package/src/services/accounts/entities.ts +84 -5
  39. package/src/services/accounts/groups.ts +30 -24
  40. package/src/services/accounts/model.test.ts +30 -0
  41. package/src/services/accounts/switching.test.ts +14 -0
  42. package/src/services/accounts/switching.ts +15 -6
  43. package/src/services/accounts/users.ts +75 -52
  44. package/src/services/announcements/index.test.ts +32 -0
  45. package/src/services/announcements/index.ts +224 -0
  46. package/src/services/audit/index.test.ts +84 -0
  47. package/src/services/audit/index.ts +431 -0
  48. package/src/services/auth-flows/index.ts +9 -2
  49. package/src/services/auth-flows/ipa.ts +0 -2
  50. package/src/services/auth-flows/magic-link.ts +3 -2
  51. package/src/services/auth-flows/password-reset.ts +284 -0
  52. package/src/services/auth-flows/proxy-return.test.ts +24 -0
  53. package/src/services/auth-flows/proxy-return.ts +49 -0
  54. package/src/services/gateway.ts +162 -0
  55. package/src/services/index.ts +44 -2
  56. package/src/services/ipa/effective-groups.test.ts +33 -0
  57. package/src/services/ipa/effective-groups.ts +70 -0
  58. package/src/services/ipa/profile.ts +45 -3
  59. package/src/services/ipa/search.ts +3 -5
  60. package/src/services/ipa/service-account.ts +15 -0
  61. package/src/services/ipa/sync-planning.test.ts +32 -0
  62. package/src/services/ipa/sync-planning.ts +22 -0
  63. package/src/services/ipa/sync.ts +110 -38
  64. package/src/services/oauth-tokens.ts +104 -0
  65. package/src/services/postgres.ts +21 -6
  66. package/src/services/providers/local/auth.test.ts +22 -0
  67. package/src/services/providers/local/auth.ts +46 -3
  68. package/src/services/secrets.ts +10 -0
  69. package/src/services/service-account-credentials.test.ts +210 -0
  70. package/src/services/service-account-credentials.ts +715 -0
  71. package/src/services/service-accounts.ts +188 -0
  72. package/src/services/session/index.ts +7 -8
  73. package/src/services/settings/app.ts +4 -20
  74. package/src/services/settings/defaults.ts +64 -22
  75. package/src/services/settings/store.ts +47 -0
  76. package/src/services/weather/forecast.ts +40 -7
  77. package/src/services/webauthn.test.ts +36 -0
  78. package/src/services/webauthn.ts +384 -0
  79. package/src/shared/icons.ts +391 -100
  80. package/src/shared/index.ts +7 -0
  81. package/src/shared/markdown/extensions/code.ts +38 -1
  82. package/src/shared/markdown/extensions/images.ts +39 -3
  83. package/src/shared/markdown/extensions/info-blocks.ts +5 -5
  84. package/src/shared/markdown/extensions/mark.ts +48 -0
  85. package/src/shared/markdown/extensions/sub-sup.ts +60 -0
  86. package/src/shared/markdown/extensions/tables.ts +79 -58
  87. package/src/shared/markdown/formula.test.ts +1089 -0
  88. package/src/shared/markdown/formula.ts +1187 -0
  89. package/src/shared/markdown/index.ts +76 -2
  90. package/src/shared/mock-cover.ts +130 -0
  91. package/src/shared/redirect.test.ts +49 -0
  92. package/src/shared/redirect.ts +52 -0
  93. package/src/shared/theme.test.ts +24 -0
  94. package/src/shared/theme.ts +68 -0
  95. package/src/shared/time.ts +13 -0
  96. package/src/ssr/AdminLayout.tsx +7 -3
  97. package/src/ssr/AdminSidebar.tsx +115 -49
  98. package/src/ssr/AppLaunchpad.island.tsx +176 -0
  99. package/src/ssr/Footer.island.tsx +3 -8
  100. package/src/ssr/GlobalAnnouncements.island.tsx +141 -0
  101. package/src/ssr/GlobalSearchDialog.tsx +545 -117
  102. package/src/ssr/HotkeysHelpRail.island.tsx +3 -70
  103. package/src/ssr/Layout.tsx +74 -66
  104. package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
  105. package/src/ssr/LayoutHelp.tsx +266 -0
  106. package/src/ssr/NavMenu.island.tsx +0 -39
  107. package/src/ssr/ThemeToggleRail.island.tsx +3 -3
  108. package/src/ssr/TimezoneCookie.island.tsx +23 -0
  109. package/src/ssr/islands/index.ts +13 -0
  110. package/src/styles/base-popover.css +5 -2
  111. package/src/styles/effects.css +87 -6
  112. package/src/styles/global.css +146 -9
  113. package/src/styles/input.css +3 -1
  114. package/src/styles/utilities-buttons.css +133 -27
  115. package/src/styles/utilities-code-display.css +67 -0
  116. package/src/styles/utilities-completion.css +223 -0
  117. package/src/styles/utilities-detail.css +73 -0
  118. package/src/styles/utilities-feedback.css +16 -15
  119. package/src/styles/utilities-layout.css +42 -2
  120. package/src/styles/utilities-markdown-editor.css +472 -0
  121. package/src/styles/utilities-navigation.css +63 -8
  122. package/src/styles/utilities-script.css +84 -0
  123. package/src/styles/utilities-table-tile.css +229 -0
  124. package/src/types/ambient.d.ts +9 -0
  125. package/src/ui/completion/behaviors.test.ts +95 -0
  126. package/src/ui/completion/behaviors.ts +205 -0
  127. package/src/ui/completion/engine.ts +368 -0
  128. package/src/ui/completion/index.ts +40 -0
  129. package/src/ui/completion/overlay.ts +92 -0
  130. package/src/ui/dialog-core.ts +173 -45
  131. package/src/ui/filter/FilterChip.tsx +42 -40
  132. package/src/ui/index.ts +11 -12
  133. package/src/ui/input/AutocompleteEditor.tsx +656 -0
  134. package/src/ui/input/CheckboxCard.tsx +91 -0
  135. package/src/ui/input/Combobox.tsx +375 -0
  136. package/src/ui/input/DatePicker.tsx +846 -0
  137. package/src/ui/input/DateTimeInput.tsx +29 -4
  138. package/src/ui/input/FileDropzone.tsx +116 -0
  139. package/src/ui/input/IconInput.tsx +116 -0
  140. package/src/ui/input/ImageInput.tsx +19 -2
  141. package/src/ui/input/MultiSelectInput.tsx +448 -0
  142. package/src/ui/input/NumberInput.tsx +417 -61
  143. package/src/ui/input/SegmentedControl.tsx +2 -2
  144. package/src/ui/input/Select.tsx +172 -10
  145. package/src/ui/input/Slider.tsx +3 -4
  146. package/src/ui/input/Switch.tsx +3 -2
  147. package/src/ui/input/TemplateEditor.tsx +212 -0
  148. package/src/ui/input/TextInput.tsx +144 -13
  149. package/src/ui/input/index.ts +53 -8
  150. package/src/ui/input/markdown/MarkdownEditor.tsx +774 -0
  151. package/src/ui/input/markdown/Toolbar.tsx +90 -0
  152. package/src/ui/input/markdown/actions.ts +233 -0
  153. package/src/ui/input/markdown/active-formats.ts +94 -0
  154. package/src/ui/input/markdown/behaviors.ts +193 -0
  155. package/src/ui/input/markdown/code-zone.ts +23 -0
  156. package/src/ui/input/markdown/highlight.ts +316 -0
  157. package/src/ui/layout.ts +22 -0
  158. package/src/ui/misc/AppOverview.tsx +105 -0
  159. package/src/ui/misc/AppWorkspace.tsx +607 -0
  160. package/src/ui/misc/Calendar.tsx +1291 -0
  161. package/src/ui/misc/Chart.tsx +162 -0
  162. package/src/ui/misc/CodeDisplay.tsx +54 -0
  163. package/src/ui/misc/ContextMenu.tsx +2 -2
  164. package/src/ui/misc/DataTable.tsx +269 -0
  165. package/src/ui/misc/DockWorkspace.tsx +425 -0
  166. package/src/ui/misc/Docs.tsx +153 -0
  167. package/src/ui/misc/Dropdown.tsx +2 -2
  168. package/src/ui/misc/EntitySearch.tsx +260 -129
  169. package/src/ui/misc/LinkCard.tsx +14 -2
  170. package/src/ui/misc/LogEntriesTable.tsx +34 -31
  171. package/src/ui/misc/Pagination.tsx +31 -12
  172. package/src/ui/misc/PanelDialog.tsx +109 -0
  173. package/src/ui/misc/Panes.tsx +873 -0
  174. package/src/ui/misc/PermissionEditor.tsx +358 -262
  175. package/src/ui/misc/Placeholder.tsx +40 -0
  176. package/src/ui/misc/ProgressBar.tsx +1 -1
  177. package/src/ui/misc/ResourceApiKeys.tsx +260 -0
  178. package/src/ui/misc/SettingsModal.tsx +150 -0
  179. package/src/ui/misc/StatCell.tsx +182 -40
  180. package/src/ui/misc/StatGrid.tsx +149 -0
  181. package/src/ui/misc/StructuredDataPreview.tsx +107 -0
  182. package/src/ui/misc/code-highlight.ts +213 -0
  183. package/src/ui/misc/index.ts +93 -12
  184. package/src/ui/prompts.tsx +362 -312
  185. package/src/ui/toast.ts +384 -0
  186. package/src/ui/widgets/Widget.tsx +12 -4
  187. package/src/ssr/MoreAppsDropdown.island.tsx +0 -61
  188. package/src/ui/ipa/GroupView.tsx +0 -36
  189. package/src/ui/ipa/LoginBtn.tsx +0 -16
  190. package/src/ui/ipa/UserView.tsx +0 -58
  191. package/src/ui/ipa/index.ts +0 -4
  192. package/src/ui/navigation.ts +0 -32
  193. package/src/ui/sidebar.tsx +0 -468
  194. /package/src/ui/{ipa → misc}/Avatar.tsx +0 -0
@@ -1,468 +0,0 @@
1
- import { For, Show, createMemo, type JSX } from "solid-js";
2
-
3
- export type SidebarRow = {
4
- id: string;
5
- label?: string;
6
- href?: string;
7
- icon?: string;
8
- labelIcon?: string;
9
- meta?: string;
10
- active?: boolean;
11
- class?: string;
12
- content?: JSX.Element;
13
- actionIcon?: string;
14
- actionLabel?: string;
15
- onActionClick?: (event: MouseEvent) => void;
16
- };
17
-
18
- export type SidebarSection = {
19
- title?: string;
20
- rows: SidebarRow[];
21
- };
22
-
23
- export type SidebarTreeNode = {
24
- id: string;
25
- label: string;
26
- icon?: string;
27
- labelIcon?: string;
28
- meta?: string;
29
- active?: boolean;
30
- href?: string;
31
- actionIcon?: string;
32
- actionLabel?: string;
33
- onActionClick?: (event: MouseEvent, nodeId: string) => void;
34
- children?: SidebarTreeNode[];
35
- };
36
-
37
- export type SidebarTreeSpec = {
38
- title?: string;
39
- nodes: SidebarTreeNode[];
40
- selectedId?: string;
41
- expandedIds?: string[];
42
- onToggle?: (nodeId: string) => void;
43
- onSelect?: (nodeId: string) => void;
44
- };
45
-
46
- export type SidebarSpec = {
47
- header: {
48
- title: string;
49
- subtitle?: string;
50
- icon?: string | JSX.Element;
51
- settingsHref?: string;
52
- };
53
- actions?: SidebarSection[] | SidebarRow[];
54
- nav?: SidebarSection[] | SidebarRow[];
55
- tree?: SidebarTreeSpec;
56
- controls?: JSX.Element;
57
- footer?: SidebarSection[] | SidebarRow[];
58
- mobile?: {
59
- mode?: "auto" | "hidden";
60
- defaultOpen?: boolean;
61
- toggleIcon?: "chevron" | "eye";
62
- include?: Array<"settings" | "actions" | "nav" | "tree" | "controls" | "footer">;
63
- };
64
- };
65
-
66
- type SidebarLayoutProps = {
67
- render?: "both" | "mobile" | "desktop";
68
- mobile?: {
69
- header: JSX.Element;
70
- items?: JSX.Element;
71
- body?: JSX.Element;
72
- defaultOpen?: boolean;
73
- bodyClass?: string;
74
- toggleIcon?: "chevron" | "eye";
75
- };
76
- desktop: {
77
- class?: string;
78
- header: JSX.Element;
79
- actions?: JSX.Element;
80
- body?: JSX.Element;
81
- footer?: JSX.Element;
82
- };
83
- };
84
-
85
- type SidebarFromSpecProps = {
86
- spec: SidebarSpec;
87
- render?: "both" | "mobile" | "desktop";
88
- desktopClass?: string;
89
- };
90
-
91
- const normalizeSections = (sections?: SidebarSection[] | SidebarRow[], title?: string): SidebarSection[] => {
92
- if (!sections || sections.length === 0) return [];
93
- if ("rows" in sections[0]!) {
94
- return sections as SidebarSection[];
95
- }
96
- return [{ title, rows: sections as SidebarRow[] }];
97
- };
98
-
99
- function SidebarRowItem(props: { row: SidebarRow; mobile?: boolean }) {
100
- if (props.row.content) return <>{props.row.content}</>;
101
-
102
- const sharedLabel = (
103
- <>
104
- <Show when={props.row.icon}>
105
- <i class={`ti ${props.row.icon} text-sm`} />
106
- </Show>
107
- <div class="min-w-0 flex-1 text-left">
108
- <span class="block truncate">{props.row.label}</span>
109
- <Show when={props.row.meta}>
110
- <span class="sidebar-item-meta block truncate">{props.row.meta}</span>
111
- </Show>
112
- </div>
113
- <Show when={props.row.labelIcon}>
114
- <i class={`ti ${props.row.labelIcon} text-xs text-dimmed`} />
115
- </Show>
116
- <Show when={props.row.actionIcon && !props.mobile}>
117
- <button
118
- type="button"
119
- class="sidebar-item-action"
120
- aria-label={props.row.actionLabel ?? "Row action"}
121
- onClick={(event) => {
122
- event.preventDefault();
123
- event.stopPropagation();
124
- props.row.onActionClick?.(event);
125
- }}
126
- >
127
- <i class={`ti ${props.row.actionIcon}`} />
128
- </button>
129
- </Show>
130
- </>
131
- );
132
-
133
- if (props.mobile) {
134
- if (props.row.href) {
135
- return (
136
- <a
137
- href={props.row.href}
138
- class={`btn-input btn-input-sm ${props.row.active ? "border-blue-500/35 bg-blue-50/70 text-blue-700 dark:border-blue-400/40 dark:bg-blue-950/40 dark:text-blue-200" : ""} ${props.row.class ?? ""}`}
139
- data-row={props.row.id}
140
- >
141
- <Show when={props.row.icon}>
142
- <i class={`ti ${props.row.icon}`} />
143
- </Show>
144
- <span>{props.row.label}</span>
145
- </a>
146
- );
147
- }
148
- return (
149
- <button
150
- type="button"
151
- class={`btn-input btn-input-sm ${props.row.active ? "border-blue-500/35 bg-blue-50/70 text-blue-700 dark:border-blue-400/40 dark:bg-blue-950/40 dark:text-blue-200" : ""} ${props.row.class ?? ""}`}
152
- data-row={props.row.id}
153
- >
154
- <Show when={props.row.icon}>
155
- <i class={`ti ${props.row.icon}`} />
156
- </Show>
157
- <span>{props.row.label}</span>
158
- </button>
159
- );
160
- }
161
-
162
- if (props.row.href) {
163
- return (
164
- <a href={props.row.href} class={`sidebar-item ${props.row.active ? "sidebar-item-active" : ""} ${props.row.class ?? ""}`} data-row={props.row.id}>
165
- {sharedLabel}
166
- </a>
167
- );
168
- }
169
-
170
- return (
171
- <button
172
- type="button"
173
- class={`sidebar-item ${props.row.active ? "sidebar-item-active" : ""} ${props.row.class ?? ""}`}
174
- data-row={props.row.id}
175
- >
176
- {sharedLabel}
177
- </button>
178
- );
179
- }
180
-
181
- function SidebarTree(props: { tree: SidebarTreeSpec; level?: number }) {
182
- const level = props.level ?? 0;
183
- const expanded = createMemo(() => new Set(props.tree.expandedIds ?? []));
184
-
185
- return (
186
- <div class="sidebar-tree" role={level === 0 ? "tree" : undefined}>
187
- <For each={props.tree.nodes}>
188
- {(node) => {
189
- const hasChildren = () => (node.children?.length ?? 0) > 0;
190
- const isExpanded = () => expanded().has(node.id);
191
- const isSelected = () => node.active || props.tree.selectedId === node.id;
192
- const showLeafIcon = () => !hasChildren() && !!node.icon;
193
-
194
- return (
195
- <div class="sidebar-tree-item" role="treeitem" aria-level={level + 1} aria-expanded={hasChildren() ? isExpanded() : undefined}>
196
- <div class={`sidebar-tree-row ${isSelected() ? "sidebar-item-active" : ""}`} style={`--sidebar-level:${level}`}>
197
- <button
198
- type="button"
199
- class="sidebar-tree-toggle"
200
- onClick={() => {
201
- if (!hasChildren()) return;
202
- props.tree.onToggle?.(node.id);
203
- }}
204
- aria-label={hasChildren() ? (isExpanded() ? "Collapse" : "Expand") : undefined}
205
- >
206
- <Show when={hasChildren()} fallback={showLeafIcon() ? <i class={`ti ${node.icon} text-xs`} /> : <span class="h-2 w-2 rounded-full bg-zinc-300 dark:bg-zinc-600" />}>
207
- <i class={`ti ${isExpanded() ? "ti-chevron-down" : "ti-chevron-right"} text-[10px]`} />
208
- </Show>
209
- </button>
210
- <Show when={hasChildren() && node.icon}>
211
- <i class={`ti ${node.icon} text-xs text-dimmed`} />
212
- </Show>
213
- <Show
214
- when={node.href}
215
- fallback={
216
- <button type="button" class="min-w-0 flex-1 truncate text-left" onClick={() => props.tree.onSelect?.(node.id)}>
217
- {node.label}
218
- </button>
219
- }
220
- >
221
- <a href={node.href!} class="min-w-0 flex-1 truncate">
222
- {node.label}
223
- </a>
224
- </Show>
225
- <Show when={node.labelIcon}>
226
- <i class={`ti ${node.labelIcon} text-xs text-dimmed`} />
227
- </Show>
228
- <Show when={node.actionIcon}>
229
- <button
230
- type="button"
231
- class="sidebar-item-action"
232
- aria-label={node.actionLabel ?? "Row action"}
233
- onClick={(event) => {
234
- event.preventDefault();
235
- event.stopPropagation();
236
- node.onActionClick?.(event, node.id);
237
- }}
238
- >
239
- <i class={`ti ${node.actionIcon}`} />
240
- </button>
241
- </Show>
242
- </div>
243
- <Show when={hasChildren() && isExpanded()}>
244
- <div class="sidebar-tree-children">
245
- <SidebarTree tree={{ ...props.tree, nodes: node.children ?? [] }} level={level + 1} />
246
- </div>
247
- </Show>
248
- </div>
249
- );
250
- }}
251
- </For>
252
- </div>
253
- );
254
- }
255
-
256
- export function SidebarFromSpec(props: SidebarFromSpecProps) {
257
- const include = createMemo(() => new Set(props.spec.mobile?.include ?? ["settings", "actions", "nav", "tree", "controls", "footer"]));
258
- const mobileMode = props.spec.mobile?.mode ?? "auto";
259
-
260
- const actionSections = createMemo(() => normalizeSections(props.spec.actions, "Actions"));
261
- const navSections = createMemo(() => normalizeSections(props.spec.nav, "Navigation"));
262
- const footerSections = createMemo(() => normalizeSections(props.spec.footer));
263
-
264
- const desktopHeader = (
265
- <>
266
- <Show when={typeof props.spec.header.icon === "string"}>
267
- <div class="w-6 h-6 rounded bg-blue-500 flex items-center justify-center text-white shrink-0">
268
- <i class={`ti ${(props.spec.header.icon as string) || "ti-layout-sidebar"} text-xs`} />
269
- </div>
270
- </Show>
271
- <Show when={typeof props.spec.header.icon !== "string" && props.spec.header.icon}>{props.spec.header.icon as JSX.Element}</Show>
272
- <div class="min-w-0 flex-1">
273
- <p class="truncate text-sm font-semibold text-primary">{props.spec.header.title}</p>
274
- <Show when={props.spec.header.subtitle}>
275
- <p class="text-xs text-dimmed truncate">{props.spec.header.subtitle}</p>
276
- </Show>
277
- </div>
278
- <Show when={props.spec.header.settingsHref}>
279
- <a href={props.spec.header.settingsHref!} class="p-0.5 text-dimmed hover:text-primary transition-colors shrink-0" title="Settings">
280
- <i class="ti ti-settings text-xs" />
281
- </a>
282
- </Show>
283
- </>
284
- );
285
-
286
- const desktopActions = (
287
- <>
288
- <For each={actionSections()}>
289
- {(section) => (
290
- <section class="sidebar-section">
291
- <Show when={section.title}>
292
- <p class="sidebar-section-title">{section.title}</p>
293
- </Show>
294
- <div class="flex flex-col gap-1">
295
- <For each={section.rows}>{(row) => <SidebarRowItem row={row} />}</For>
296
- </div>
297
- </section>
298
- )}
299
- </For>
300
- <For each={navSections()}>
301
- {(section) => (
302
- <section class="sidebar-section">
303
- <Show when={section.title}>
304
- <p class="sidebar-section-title">{section.title}</p>
305
- </Show>
306
- <div class="flex flex-col gap-1">
307
- <For each={section.rows}>{(row) => <SidebarRowItem row={row} />}</For>
308
- </div>
309
- </section>
310
- )}
311
- </For>
312
- </>
313
- );
314
-
315
- const desktopBody = (
316
- <>
317
- <Show when={props.spec.tree}>
318
- <section class="sidebar-section">
319
- <Show when={props.spec.tree?.title}>
320
- <p class="sidebar-section-title">{props.spec.tree?.title}</p>
321
- </Show>
322
- <SidebarTree tree={props.spec.tree!} />
323
- </section>
324
- </Show>
325
- <Show when={props.spec.controls}>
326
- <section class="sidebar-section">{props.spec.controls}</section>
327
- </Show>
328
- </>
329
- );
330
-
331
- const desktopFooter = (
332
- <For each={footerSections()}>
333
- {(section) => (
334
- <section class="sidebar-section">
335
- <Show when={section.title}>
336
- <p class="sidebar-section-title">{section.title}</p>
337
- </Show>
338
- <div class="flex flex-col gap-1">
339
- <For each={section.rows}>{(row) => <SidebarRowItem row={row} />}</For>
340
- </div>
341
- </section>
342
- )}
343
- </For>
344
- );
345
-
346
- const mobileHeader = (
347
- <>
348
- <Show when={typeof props.spec.header.icon === "string"}>
349
- <div class="w-8 h-8 rounded-lg bg-blue-500 flex items-center justify-center text-white shrink-0">
350
- <i class={`ti ${(props.spec.header.icon as string) || "ti-layout-sidebar"} text-sm`} />
351
- </div>
352
- </Show>
353
- <Show when={typeof props.spec.header.icon !== "string" && props.spec.header.icon}>{props.spec.header.icon as JSX.Element}</Show>
354
- <span class="font-semibold truncate flex-1">{props.spec.header.title}</span>
355
- </>
356
- );
357
-
358
- const mobileItems = (
359
- <>
360
- <Show when={include().has("settings") && props.spec.header.settingsHref}>
361
- <a href={props.spec.header.settingsHref!} class="btn-input btn-input-sm">
362
- <i class="ti ti-settings" />
363
- Settings
364
- </a>
365
- </Show>
366
- <Show when={include().has("actions")}>
367
- <For each={actionSections()}>
368
- {(section) => <For each={section.rows}>{(row) => <SidebarRowItem row={row} mobile />}</For>}
369
- </For>
370
- </Show>
371
- <Show when={include().has("nav")}>
372
- <For each={navSections()}>
373
- {(section) => <For each={section.rows}>{(row) => <SidebarRowItem row={row} mobile />}</For>}
374
- </For>
375
- </Show>
376
- <Show when={include().has("footer")}>
377
- <For each={footerSections()}>
378
- {(section) => <For each={section.rows}>{(row) => <SidebarRowItem row={row} mobile />}</For>}
379
- </For>
380
- </Show>
381
- </>
382
- );
383
-
384
- const mobileBody = (
385
- <>
386
- <Show when={include().has("tree") && props.spec.tree}>
387
- <section class="sidebar-section">
388
- <Show when={props.spec.tree?.title}>
389
- <p class="sidebar-section-title">{props.spec.tree?.title}</p>
390
- </Show>
391
- <SidebarTree tree={props.spec.tree!} />
392
- </section>
393
- </Show>
394
- <Show when={include().has("controls") && props.spec.controls}>
395
- <section class="sidebar-section">{props.spec.controls}</section>
396
- </Show>
397
- </>
398
- );
399
-
400
- return (
401
- <SidebarLayout
402
- render={props.render}
403
- desktop={{
404
- class: props.desktopClass,
405
- header: desktopHeader,
406
- actions: desktopActions,
407
- body: desktopBody,
408
- footer: desktopFooter,
409
- }}
410
- mobile={
411
- mobileMode === "hidden"
412
- ? undefined
413
- : {
414
- defaultOpen: props.spec.mobile?.defaultOpen,
415
- toggleIcon: props.spec.mobile?.toggleIcon,
416
- header: mobileHeader,
417
- items: mobileItems,
418
- body: mobileBody,
419
- }
420
- }
421
- />
422
- );
423
- }
424
-
425
- export function SidebarLayout(props: SidebarLayoutProps) {
426
- const renderMode = props.render ?? "both";
427
- const mobileOpenProps = props.mobile?.defaultOpen ? { open: true } : {};
428
-
429
- return (
430
- <>
431
- <Show when={(renderMode === "both" || renderMode === "mobile") && props.mobile}>
432
- <nav class="lg:hidden flex flex-col gap-3">
433
- <details class="group" {...mobileOpenProps}>
434
- <summary class="sidebar-header cursor-pointer select-none list-none">
435
- {props.mobile!.header}
436
- <span class="ml-auto inline-flex h-7 w-7 items-center justify-center rounded-md text-dimmed transition-transform group-open:rotate-180">
437
- <i class={`ti ${props.mobile?.toggleIcon === "eye" ? "ti-eye" : "ti-chevron-down"} text-sm`} />
438
- </span>
439
- </summary>
440
- <Show when={props.mobile?.items}>
441
- <div class="mt-2 flex flex-wrap gap-2">{props.mobile?.items}</div>
442
- </Show>
443
- <Show when={props.mobile?.body}>
444
- <div class={`mt-2 ${props.mobile?.bodyClass ?? "max-h-64 overflow-y-auto p-2"}`}>{props.mobile?.body}</div>
445
- </Show>
446
- </details>
447
- </nav>
448
- </Show>
449
-
450
- <Show when={renderMode === "both" || renderMode === "desktop"}>
451
- <aside class={`hidden lg:flex flex-col min-h-0 overflow-y-auto ${props.desktop.class ?? ""}`}>
452
- <div class="sidebar-header">{props.desktop.header}</div>
453
- <Show when={props.desktop.actions}>
454
- <div class="flex flex-col gap-3">{props.desktop.actions}</div>
455
- </Show>
456
- <Show when={props.desktop.body}>
457
- <div class={`sidebar-body ${props.desktop.actions ? "mt-2" : ""}`}>{props.desktop.body}</div>
458
- </Show>
459
- <Show when={props.desktop.footer}>
460
- <div class="sidebar-footer">{props.desktop.footer}</div>
461
- </Show>
462
- </aside>
463
- </Show>
464
- </>
465
- );
466
- }
467
-
468
- export default SidebarLayout;
File without changes