@octabits-io/nuxt-ui-kit 0.17.2 → 0.17.3
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octabits-io/nuxt-ui-kit",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3",
|
|
4
4
|
"description": "Frontend kit for Nuxt/Vue admin SPAs: OIDC session harness (oidc-client-ts), API-client seams (base URL + OIDC bearer), auth/org store cores, and a route-guard builder — factory-style seams the app wires into its own plugins, stores, and middleware",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
foldInlineActions,
|
|
22
22
|
groupIsPrimary,
|
|
23
23
|
isInlineBound as isItemInlineBound,
|
|
24
|
+
buildMenuActionGroups,
|
|
24
25
|
resolveCollapseStages,
|
|
25
26
|
type PageActionsItem,
|
|
26
27
|
} from './pageActions.ts';
|
|
@@ -137,26 +138,6 @@ function toMenuItem(item: PageActionsItem): DropdownMenuItem {
|
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
const menuGroups = computed<DropdownMenuItem[][]>(() => {
|
|
140
|
-
const collapsedAutos = collapsed.value
|
|
141
|
-
? actionItems.value.filter(item => (item.visibility ?? 'auto') === 'auto')
|
|
142
|
-
: [];
|
|
143
|
-
|
|
144
|
-
// Menu-only items grouped by section, in first-appearance order.
|
|
145
|
-
const sections = new Map<string, PageActionsItem[]>();
|
|
146
|
-
for (const item of actionItems.value) {
|
|
147
|
-
if ((item.visibility ?? 'auto') !== 'menu') continue;
|
|
148
|
-
const section = item.section ?? item.group?.id ?? 'default';
|
|
149
|
-
if (!sections.has(section)) sections.set(section, []);
|
|
150
|
-
sections.get(section)!.push(item);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// AI items bound to the menu (explicit 'menu', or 'auto' while collapsed)
|
|
154
|
-
// form their own group between the action sections and the utilities.
|
|
155
|
-
const aiGroup = aiItems.value.filter(item =>
|
|
156
|
-
(item.visibility ?? 'auto') === 'menu'
|
|
157
|
-
|| ((item.visibility ?? 'auto') === 'auto' && collapsed.value),
|
|
158
|
-
);
|
|
159
|
-
|
|
160
141
|
const utilityGroup: DropdownMenuItem[] = utilitiesCollapsed.value
|
|
161
142
|
? [
|
|
162
143
|
...props.utilityItems.map(toMenuItem),
|
|
@@ -166,10 +147,11 @@ const menuGroups = computed<DropdownMenuItem[][]>(() => {
|
|
|
166
147
|
]
|
|
167
148
|
: [];
|
|
168
149
|
|
|
150
|
+
// Action groups (incl. their ORDER — see buildMenuActionGroups) are pure and
|
|
151
|
+
// live in the module; the utilities need i18n and the help registry.
|
|
169
152
|
return [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
aiGroup.map(toMenuItem),
|
|
153
|
+
...buildMenuActionGroups(actionItems.value, aiItems.value, collapsed.value)
|
|
154
|
+
.map(group => group.map(toMenuItem)),
|
|
173
155
|
utilityGroup,
|
|
174
156
|
].filter(group => group.length > 0);
|
|
175
157
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import {
|
|
3
|
+
buildMenuActionGroups,
|
|
3
4
|
foldInlineActions,
|
|
4
5
|
groupIsPrimary,
|
|
5
6
|
isInlineBound,
|
|
@@ -156,3 +157,53 @@ describe('resolveCollapseStages', () => {
|
|
|
156
157
|
}
|
|
157
158
|
});
|
|
158
159
|
});
|
|
160
|
+
|
|
161
|
+
describe('buildMenuActionGroups', () => {
|
|
162
|
+
const item = (over: Partial<PageActionsItem>): PageActionsItem =>
|
|
163
|
+
({ key: over.key ?? 'k', icon: 'i', label: over.label ?? 'l', ...over });
|
|
164
|
+
|
|
165
|
+
const keys = (groups: PageActionsItem[][]) => groups.map(g => g.map(i => i.key));
|
|
166
|
+
|
|
167
|
+
it('keeps an AI row above the destructive section', () => {
|
|
168
|
+
// The regression this exists for: an AI item appended AFTER the sections
|
|
169
|
+
// rendered under "Delete", where a "Generate page content" row reads as an
|
|
170
|
+
// afterthought to the deletion.
|
|
171
|
+
const actions = [
|
|
172
|
+
item({ key: 'export', visibility: 'menu' }),
|
|
173
|
+
item({ key: 'delete', visibility: 'menu', section: 'destructive', color: 'error' }),
|
|
174
|
+
];
|
|
175
|
+
const ai = [item({ key: 'generate', kind: 'ai' })];
|
|
176
|
+
|
|
177
|
+
expect(keys(buildMenuActionGroups(actions, ai, true)))
|
|
178
|
+
.toEqual([['generate'], ['export'], ['delete']]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('sits with the other collapsed actions, not behind them', () => {
|
|
182
|
+
const actions = [item({ key: 'edit' }), item({ key: 'delete', visibility: 'menu' })];
|
|
183
|
+
const ai = [item({ key: 'generate', kind: 'ai' })];
|
|
184
|
+
|
|
185
|
+
// 'edit' collapses out of the bar; the AI row is its own group beside it.
|
|
186
|
+
expect(keys(buildMenuActionGroups(actions, ai, true)))
|
|
187
|
+
.toEqual([['edit'], ['generate'], ['delete']]);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('leaves inline-bound items out of the menu entirely', () => {
|
|
191
|
+
const actions = [item({ key: 'publish', visibility: 'always' }), item({ key: 'edit' })];
|
|
192
|
+
const ai = [item({ key: 'generate', kind: 'ai', visibility: 'always' })];
|
|
193
|
+
|
|
194
|
+
// Wide: nothing collapses. Narrow: 'always' items still render inline, so
|
|
195
|
+
// the menu holds only 'edit'.
|
|
196
|
+
expect(keys(buildMenuActionGroups(actions, ai, false))).toEqual([]);
|
|
197
|
+
expect(keys(buildMenuActionGroups(actions, ai, true))).toEqual([['edit']]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('orders menu sections by first appearance, so the caller decides', () => {
|
|
201
|
+
const actions = [
|
|
202
|
+
item({ key: 'a', visibility: 'menu', section: 'one' }),
|
|
203
|
+
item({ key: 'b', visibility: 'menu', section: 'two' }),
|
|
204
|
+
item({ key: 'c', visibility: 'menu', section: 'one' }),
|
|
205
|
+
];
|
|
206
|
+
expect(keys(buildMenuActionGroups(actions, [], false)))
|
|
207
|
+
.toEqual([['a', 'c'], ['b']]);
|
|
208
|
+
});
|
|
209
|
+
})
|
|
@@ -193,3 +193,46 @@ export function resolveCollapseStages(
|
|
|
193
193
|
utilitiesCollapsed: collapsed || width < (utilityCollapseBelow ?? collapseBelow),
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The overflow menu's ACTION groups, in render order — everything above the
|
|
199
|
+
* utility group, which the component builds itself (it needs i18n and the help
|
|
200
|
+
* registry).
|
|
201
|
+
*
|
|
202
|
+
* The order is the whole point, and it is a convention the type cannot express:
|
|
203
|
+
* destructive rows are the last-declared menu section, so anything appended
|
|
204
|
+
* after the sections lands under "Delete". AI items used to, which read as an
|
|
205
|
+
* afterthought to the deletion rather than as a thing you might do to the
|
|
206
|
+
* record. They belong with the other collapsed actions instead.
|
|
207
|
+
*
|
|
208
|
+
* Groups render separated, and empty ones are dropped by the caller.
|
|
209
|
+
*/
|
|
210
|
+
export function buildMenuActionGroups(
|
|
211
|
+
actionItems: PageActionsItem[],
|
|
212
|
+
aiItems: PageActionsItem[],
|
|
213
|
+
collapsed: boolean,
|
|
214
|
+
): PageActionsItem[][] {
|
|
215
|
+
const visibilityOf = (item: PageActionsItem) => item.visibility ?? 'auto';
|
|
216
|
+
|
|
217
|
+
// 'auto' actions the current width pushed out of the bar.
|
|
218
|
+
const collapsedAutos = collapsed
|
|
219
|
+
? actionItems.filter(item => visibilityOf(item) === 'auto')
|
|
220
|
+
: [];
|
|
221
|
+
|
|
222
|
+
// Menu-only items, grouped by section in first-appearance order — so a
|
|
223
|
+
// caller's declaration order decides, and 'destructive' stays last by
|
|
224
|
+
// being declared last.
|
|
225
|
+
const sections = new Map<string, PageActionsItem[]>();
|
|
226
|
+
for (const item of actionItems) {
|
|
227
|
+
if (visibilityOf(item) !== 'menu') continue;
|
|
228
|
+
const section = item.section ?? item.group?.id ?? 'default';
|
|
229
|
+
if (!sections.has(section)) sections.set(section, []);
|
|
230
|
+
sections.get(section)!.push(item);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const aiGroup = aiItems.filter(item =>
|
|
234
|
+
visibilityOf(item) === 'menu' || (visibilityOf(item) === 'auto' && collapsed),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
return [collapsedAutos, aiGroup, ...sections.values()].filter(group => group.length > 0);
|
|
238
|
+
}
|