@close-by/clay 0.2.5 → 0.2.6
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.
- package/package.json +5 -5
- package/src/pages.ts +79 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@close-by/clay",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "NiceGUI-like ui.* facade for Clay apps",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@close-by/clay-compiler": "0.2.
|
|
31
|
-
"@close-by/clay-core": "0.2.
|
|
32
|
-
"@close-by/clay-components": "0.2.
|
|
33
|
-
"@close-by/clay-server": "0.2.
|
|
30
|
+
"@close-by/clay-compiler": "0.2.6",
|
|
31
|
+
"@close-by/clay-core": "0.2.6",
|
|
32
|
+
"@close-by/clay-components": "0.2.6",
|
|
33
|
+
"@close-by/clay-server": "0.2.6",
|
|
34
34
|
"@tailwindcss/cli": "^4.3.3",
|
|
35
35
|
"tailwindcss": "^4.3.3"
|
|
36
36
|
}
|
package/src/pages.ts
CHANGED
|
@@ -58,6 +58,10 @@ export type PageMeta = {
|
|
|
58
58
|
* UX only — still call `requireRole` in the page builder.
|
|
59
59
|
*/
|
|
60
60
|
roles?: string[];
|
|
61
|
+
/** Nest this page under a collapsible sidebar group with the given label. */
|
|
62
|
+
group?: string;
|
|
63
|
+
/** Lucide icon for the group parent row (first page in the group with this set wins). */
|
|
64
|
+
groupIcon?: string;
|
|
61
65
|
};
|
|
62
66
|
|
|
63
67
|
const pageMetaByPath = new Map<string, PageMeta>();
|
|
@@ -140,8 +144,76 @@ export type NavFromPagesOptions = {
|
|
|
140
144
|
role?: string;
|
|
141
145
|
/** Viewer roles — page is shown if it has no `roles` meta, or any overlap. */
|
|
142
146
|
roles?: string[];
|
|
147
|
+
/**
|
|
148
|
+
* When true, nest `/examples/*` routes (except `/examples/auth`) under an
|
|
149
|
+
* "Examples" group. Individual pages can override with `pageMeta.group`.
|
|
150
|
+
*/
|
|
151
|
+
groupExamples?: boolean;
|
|
152
|
+
/** Label for the auto examples group. Default `"Examples"`. */
|
|
153
|
+
groupExamplesLabel?: string;
|
|
154
|
+
/** Icon for the auto examples group. Default `"layout-grid"`. */
|
|
155
|
+
groupExamplesIcon?: string;
|
|
143
156
|
};
|
|
144
157
|
|
|
158
|
+
type RankedNavPage = {
|
|
159
|
+
href: string;
|
|
160
|
+
label: string;
|
|
161
|
+
icon: string;
|
|
162
|
+
order: number;
|
|
163
|
+
group?: string;
|
|
164
|
+
groupIcon?: string;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function resolvePageGroup(
|
|
168
|
+
href: string,
|
|
169
|
+
meta: PageMeta,
|
|
170
|
+
opts?: NavFromPagesOptions,
|
|
171
|
+
): { group?: string; groupIcon?: string } {
|
|
172
|
+
if (meta.group !== undefined) {
|
|
173
|
+
return meta.group ? { group: meta.group, groupIcon: meta.groupIcon } : {};
|
|
174
|
+
}
|
|
175
|
+
if (opts?.groupExamples && href.startsWith('/examples/') && href !== '/examples/auth') {
|
|
176
|
+
return {
|
|
177
|
+
group: opts.groupExamplesLabel ?? 'Examples',
|
|
178
|
+
groupIcon: opts.groupExamplesIcon ?? 'layout-grid',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return {};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function buildNavFromRanked(ranked: RankedNavPage[]): AppNavItem[] {
|
|
185
|
+
const top: Array<{ order: number; node: AppNavItem }> = [];
|
|
186
|
+
const groups = new Map<string, AppNavItem>();
|
|
187
|
+
|
|
188
|
+
for (const row of ranked) {
|
|
189
|
+
const leaf: AppNavItem = { label: row.label, href: row.href, icon: row.icon };
|
|
190
|
+
if (!row.group) {
|
|
191
|
+
top.push({ order: row.order, node: leaf });
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let parent = groups.get(row.group);
|
|
196
|
+
if (!parent) {
|
|
197
|
+
parent = {
|
|
198
|
+
label: row.group,
|
|
199
|
+
href: row.href,
|
|
200
|
+
icon: row.groupIcon ?? 'folder',
|
|
201
|
+
items: [],
|
|
202
|
+
};
|
|
203
|
+
groups.set(row.group, parent);
|
|
204
|
+
top.push({ order: row.order, node: parent });
|
|
205
|
+
} else {
|
|
206
|
+
const entry = top.find((t) => t.node === parent);
|
|
207
|
+
if (entry) entry.order = Math.min(entry.order, row.order);
|
|
208
|
+
if (row.groupIcon && parent.icon === 'folder') parent.icon = row.groupIcon;
|
|
209
|
+
}
|
|
210
|
+
parent.items!.push(leaf);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
top.sort((a, b) => a.order - b.order || a.node.label.localeCompare(b.node.label));
|
|
214
|
+
return top.map((t) => t.node);
|
|
215
|
+
}
|
|
216
|
+
|
|
145
217
|
/**
|
|
146
218
|
* Build primary sidebar nav from registered pages + collected `pageMeta`.
|
|
147
219
|
* Filters by `nav: false` and optional role overlap; sorts by `order` then path.
|
|
@@ -160,10 +232,11 @@ export function navFromPages(opts?: NavFromPagesOptions): AppNavItem[] {
|
|
|
160
232
|
}
|
|
161
233
|
|
|
162
234
|
const paths = getRegisteredPaths();
|
|
163
|
-
const ranked = paths
|
|
235
|
+
const ranked: RankedNavPage[] = paths
|
|
164
236
|
.map((href) => {
|
|
165
237
|
const meta = pageMetaByPath.get(href) ?? {};
|
|
166
238
|
const order = href === '/' ? 0 : (meta.order ?? 100);
|
|
239
|
+
const { group, groupIcon } = resolvePageGroup(href, meta, opts);
|
|
167
240
|
return {
|
|
168
241
|
href,
|
|
169
242
|
label: meta.label ?? labelFromPath(href),
|
|
@@ -171,6 +244,8 @@ export function navFromPages(opts?: NavFromPagesOptions): AppNavItem[] {
|
|
|
171
244
|
order,
|
|
172
245
|
nav: meta.nav !== false,
|
|
173
246
|
roles: meta.roles,
|
|
247
|
+
group,
|
|
248
|
+
groupIcon,
|
|
174
249
|
};
|
|
175
250
|
})
|
|
176
251
|
.filter((item) => {
|
|
@@ -178,9 +253,10 @@ export function navFromPages(opts?: NavFromPagesOptions): AppNavItem[] {
|
|
|
178
253
|
if (!item.roles || item.roles.length === 0) return true;
|
|
179
254
|
if (viewer.size === 0) return false;
|
|
180
255
|
return item.roles.some((r) => viewer.has(r));
|
|
181
|
-
})
|
|
256
|
+
})
|
|
257
|
+
.map(({ nav: _nav, roles: _roles, ...rest }) => rest);
|
|
182
258
|
ranked.sort((a, b) => a.order - b.order || a.href.localeCompare(b.href));
|
|
183
|
-
return ranked
|
|
259
|
+
return buildNavFromRanked(ranked);
|
|
184
260
|
}
|
|
185
261
|
|
|
186
262
|
/** Test helper: clear page registry + meta map. */
|