@octabits-io/nuxt-ui-kit 0.10.0 → 0.12.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.
package/dist/i18n/index.d.ts
CHANGED
package/dist/i18n/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -129,6 +129,14 @@ interface PageActionsItem {
|
|
|
129
129
|
key: string;
|
|
130
130
|
icon: string;
|
|
131
131
|
label: string;
|
|
132
|
+
/**
|
|
133
|
+
* 'ai' renders the item in the AI cluster: sparkles + primary-soft (AiButton
|
|
134
|
+
* styling). One inline AI item → verb-labeled button; several → a labeled
|
|
135
|
+
* "AI ∨" dropdown. Collapsed AI items form their own menu group.
|
|
136
|
+
*/
|
|
137
|
+
kind?: 'action' | 'ai';
|
|
138
|
+
/** Menu-only helper text (shown in the AI dropdown / overflow rows). */
|
|
139
|
+
description?: string;
|
|
132
140
|
/** Inline button tone. At most ONE 'primary' item should be visible per state. */
|
|
133
141
|
tone?: 'primary' | 'neutral';
|
|
134
142
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octabits-io/nuxt-ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Frontend kit for Nuxt/Vue admin SPAs: OIDC session harness (oidc-client-ts), Eden Treaty client factory, 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",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Shipped as source: the consumer's Vite compiles this SFC. All imports are
|
|
3
|
+
// explicit — no reliance on the consumer's auto-import configuration.
|
|
4
|
+
import UButton from '@nuxt/ui/components/Button.vue';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* THE visual token for AI triggers: sparkles + primary-soft + verb label.
|
|
8
|
+
* Every button that launches AI work (workflow, generation, suggestion) renders
|
|
9
|
+
* through this primitive so "AI will do something" always looks the same.
|
|
10
|
+
* Convention: `i-lucide-sparkles` is reserved exclusively for AI triggers;
|
|
11
|
+
* soft/solid primary = AI acts on data, ghost = AI conversation.
|
|
12
|
+
*/
|
|
13
|
+
withDefaults(defineProps<{
|
|
14
|
+
label: string
|
|
15
|
+
/** Leading icon. Keep the sparkles default unless a workflow icon is clearer. */
|
|
16
|
+
icon?: string
|
|
17
|
+
/** e.g. 'i-lucide-chevron-down' for dropdown triggers. */
|
|
18
|
+
trailingIcon?: string
|
|
19
|
+
size?: 'xs' | 'sm' | 'md'
|
|
20
|
+
variant?: 'soft' | 'solid' | 'subtle' | 'outline' | 'ghost'
|
|
21
|
+
loading?: boolean
|
|
22
|
+
disabled?: boolean
|
|
23
|
+
}>(), {
|
|
24
|
+
icon: 'i-lucide-sparkles',
|
|
25
|
+
trailingIcon: undefined,
|
|
26
|
+
size: 'sm',
|
|
27
|
+
variant: 'soft',
|
|
28
|
+
loading: false,
|
|
29
|
+
disabled: false,
|
|
30
|
+
});
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<UButton
|
|
35
|
+
:label="label"
|
|
36
|
+
:icon="icon"
|
|
37
|
+
:trailing-icon="trailingIcon"
|
|
38
|
+
color="primary"
|
|
39
|
+
:variant="variant"
|
|
40
|
+
:size="size"
|
|
41
|
+
:loading="loading"
|
|
42
|
+
:disabled="disabled"
|
|
43
|
+
/>
|
|
44
|
+
</template>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
// Shipped as source: the consumer's Vite compiles this SFC. All imports are
|
|
3
3
|
// explicit — no reliance on the consumer's auto-import configuration.
|
|
4
|
-
// i18n key contract: pageChrome.help (+ PageActionMenu's pageChrome.moreActions).
|
|
4
|
+
// i18n key contract: pageChrome.help, pageChrome.ai (+ PageActionMenu's pageChrome.moreActions).
|
|
5
5
|
import { computed, inject } from 'vue';
|
|
6
6
|
import { useI18n } from 'vue-i18n';
|
|
7
7
|
import USeparator from '@nuxt/ui/components/Separator.vue';
|
|
8
|
+
import UDropdownMenu from '@nuxt/ui/components/DropdownMenu.vue';
|
|
9
|
+
import UIcon from '@nuxt/ui/components/Icon.vue';
|
|
8
10
|
import type { DropdownMenuItem } from '@nuxt/ui';
|
|
11
|
+
import AiButton from './AiButton.vue';
|
|
9
12
|
import PageAction from './PageAction.vue';
|
|
10
13
|
import PageActionMenu from './PageActionMenu.vue';
|
|
11
14
|
// Package-name import (not ../composables): only src/components is packed, and
|
|
@@ -32,9 +35,14 @@ const props = withDefaults(defineProps<{
|
|
|
32
35
|
utilityItems?: PageActionsItem[]
|
|
33
36
|
/** Header width (px) below which 'auto'/utility items collapse into the menu. */
|
|
34
37
|
collapseBelow?: number
|
|
38
|
+
/** Render the built-in Help trigger (when a help registry with actions is
|
|
39
|
+
* provided). Disable in nested/panel headers where the page-level header
|
|
40
|
+
* already owns Help. */
|
|
41
|
+
help?: boolean
|
|
35
42
|
}>(), {
|
|
36
43
|
utilityItems: () => [],
|
|
37
44
|
collapseBelow: PAGE_ACTIONS_COLLAPSE_BELOW,
|
|
45
|
+
help: true,
|
|
38
46
|
});
|
|
39
47
|
|
|
40
48
|
const { t } = useI18n();
|
|
@@ -48,15 +56,35 @@ const collapsed = computed(() => {
|
|
|
48
56
|
return width != null && width < props.collapseBelow;
|
|
49
57
|
});
|
|
50
58
|
|
|
51
|
-
const showHelp = computed(() => Boolean(helpPanel?.hasActions.value));
|
|
59
|
+
const showHelp = computed(() => props.help && Boolean(helpPanel?.hasActions.value));
|
|
52
60
|
|
|
53
|
-
const
|
|
61
|
+
const actionItems = computed(() => props.items.filter(item => (item.kind ?? 'action') !== 'ai'));
|
|
62
|
+
const aiItems = computed(() => props.items.filter(item => item.kind === 'ai'));
|
|
63
|
+
|
|
64
|
+
const isInlineBound = (item: PageActionsItem) =>
|
|
54
65
|
(item.visibility ?? 'auto') === 'always'
|
|
55
|
-
|| ((item.visibility ?? 'auto') === 'auto' && !collapsed.value)
|
|
56
|
-
|
|
66
|
+
|| ((item.visibility ?? 'auto') === 'auto' && !collapsed.value);
|
|
67
|
+
|
|
68
|
+
const inlineItems = computed(() => actionItems.value.filter(isInlineBound));
|
|
69
|
+
|
|
70
|
+
// AI cluster: one inline item renders as its own verb-labeled AiButton; several
|
|
71
|
+
// share a labeled "AI ∨" dropdown (icons + descriptions per row).
|
|
72
|
+
const inlineAiItems = computed(() => aiItems.value.filter(isInlineBound));
|
|
73
|
+
const aiDropdownItems = computed<DropdownMenuItem[]>(() =>
|
|
74
|
+
inlineAiItems.value.map(item => ({
|
|
75
|
+
label: item.label,
|
|
76
|
+
description: item.description,
|
|
77
|
+
icon: item.icon,
|
|
78
|
+
disabled: item.disabled || Boolean(item.disabledReason),
|
|
79
|
+
loading: item.loading,
|
|
80
|
+
onSelect: item.onSelect,
|
|
81
|
+
})),
|
|
82
|
+
);
|
|
57
83
|
|
|
58
84
|
const inlineUtilityItems = computed(() => collapsed.value ? [] : props.utilityItems);
|
|
59
85
|
|
|
86
|
+
// Descriptions render only in the dedicated AI dropdown (which has wrap/width
|
|
87
|
+
// styling) — the compact ⋯ overflow stays label-only.
|
|
60
88
|
function toMenuItem(item: PageActionsItem): DropdownMenuItem {
|
|
61
89
|
return {
|
|
62
90
|
label: item.label,
|
|
@@ -72,18 +100,25 @@ function toMenuItem(item: PageActionsItem): DropdownMenuItem {
|
|
|
72
100
|
|
|
73
101
|
const menuGroups = computed<DropdownMenuItem[][]>(() => {
|
|
74
102
|
const collapsedAutos = collapsed.value
|
|
75
|
-
?
|
|
103
|
+
? actionItems.value.filter(item => (item.visibility ?? 'auto') === 'auto')
|
|
76
104
|
: [];
|
|
77
105
|
|
|
78
106
|
// Menu-only items grouped by section, in first-appearance order.
|
|
79
107
|
const sections = new Map<string, PageActionsItem[]>();
|
|
80
|
-
for (const item of
|
|
108
|
+
for (const item of actionItems.value) {
|
|
81
109
|
if ((item.visibility ?? 'auto') !== 'menu') continue;
|
|
82
110
|
const section = item.section ?? 'default';
|
|
83
111
|
if (!sections.has(section)) sections.set(section, []);
|
|
84
112
|
sections.get(section)!.push(item);
|
|
85
113
|
}
|
|
86
114
|
|
|
115
|
+
// AI items bound to the menu (explicit 'menu', or 'auto' while collapsed)
|
|
116
|
+
// form their own group between the action sections and the utilities.
|
|
117
|
+
const aiGroup = aiItems.value.filter(item =>
|
|
118
|
+
(item.visibility ?? 'auto') === 'menu'
|
|
119
|
+
|| ((item.visibility ?? 'auto') === 'auto' && collapsed.value),
|
|
120
|
+
);
|
|
121
|
+
|
|
87
122
|
const utilityGroup: DropdownMenuItem[] = collapsed.value
|
|
88
123
|
? [
|
|
89
124
|
...props.utilityItems.map(toMenuItem),
|
|
@@ -96,6 +131,7 @@ const menuGroups = computed<DropdownMenuItem[][]>(() => {
|
|
|
96
131
|
return [
|
|
97
132
|
collapsedAutos.map(toMenuItem),
|
|
98
133
|
...[...sections.values()].map(group => group.map(toMenuItem)),
|
|
134
|
+
aiGroup.map(toMenuItem),
|
|
99
135
|
utilityGroup,
|
|
100
136
|
].filter(group => group.length > 0);
|
|
101
137
|
});
|
|
@@ -120,6 +156,35 @@ const hasUtilityRegion = computed(() =>
|
|
|
120
156
|
:target="item.target"
|
|
121
157
|
@click="item.onSelect?.()"
|
|
122
158
|
/>
|
|
159
|
+
<!-- AI cluster: soft-primary sparkles = "AI acts on data". One item → its
|
|
160
|
+
verb label; several → the shared labeled dropdown. -->
|
|
161
|
+
<AiButton
|
|
162
|
+
v-if="inlineAiItems.length === 1"
|
|
163
|
+
:label="inlineAiItems[0]!.label"
|
|
164
|
+
:loading="inlineAiItems[0]!.loading"
|
|
165
|
+
:disabled="inlineAiItems[0]!.disabled || Boolean(inlineAiItems[0]!.disabledReason)"
|
|
166
|
+
@click="inlineAiItems[0]!.onSelect?.()"
|
|
167
|
+
/>
|
|
168
|
+
<UDropdownMenu
|
|
169
|
+
v-else-if="inlineAiItems.length > 1"
|
|
170
|
+
:items="aiDropdownItems"
|
|
171
|
+
:content="{ align: 'end' }"
|
|
172
|
+
:ui="{
|
|
173
|
+
content: 'w-72',
|
|
174
|
+
item: 'gap-2.5 p-2',
|
|
175
|
+
itemLabel: 'font-medium text-highlighted',
|
|
176
|
+
itemDescription: 'mt-0.5 whitespace-normal text-xs/4',
|
|
177
|
+
}"
|
|
178
|
+
>
|
|
179
|
+
<AiButton :label="t('pageChrome.ai')" trailing-icon="i-lucide-chevron-down" />
|
|
180
|
+
<template #item-leading="{ item }">
|
|
181
|
+
<span
|
|
182
|
+
class="flex size-8 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-primary/20 to-primary/5 text-primary ring-1 ring-primary/20 ring-inset"
|
|
183
|
+
>
|
|
184
|
+
<UIcon :name="(item.icon as string) ?? 'i-lucide-sparkles'" class="size-4" />
|
|
185
|
+
</span>
|
|
186
|
+
</template>
|
|
187
|
+
</UDropdownMenu>
|
|
123
188
|
<PageActionMenu :items="menuGroups" />
|
|
124
189
|
<template v-if="hasUtilityRegion">
|
|
125
190
|
<USeparator orientation="vertical" class="h-5 mx-1" />
|
|
@@ -11,6 +11,14 @@ export interface PageActionsItem {
|
|
|
11
11
|
key: string;
|
|
12
12
|
icon: string;
|
|
13
13
|
label: string;
|
|
14
|
+
/**
|
|
15
|
+
* 'ai' renders the item in the AI cluster: sparkles + primary-soft (AiButton
|
|
16
|
+
* styling). One inline AI item → verb-labeled button; several → a labeled
|
|
17
|
+
* "AI ∨" dropdown. Collapsed AI items form their own menu group.
|
|
18
|
+
*/
|
|
19
|
+
kind?: 'action' | 'ai';
|
|
20
|
+
/** Menu-only helper text (shown in the AI dropdown / overflow rows). */
|
|
21
|
+
description?: string;
|
|
14
22
|
/** Inline button tone. At most ONE 'primary' item should be visible per state. */
|
|
15
23
|
tone?: 'primary' | 'neutral';
|
|
16
24
|
/**
|