@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.
@@ -41,6 +41,7 @@ interface KitMessages {
41
41
  back: string;
42
42
  moreActions: string;
43
43
  help: string;
44
+ ai: string;
44
45
  };
45
46
  }
46
47
  declare const kitMessagesEn: KitMessages;
@@ -29,7 +29,8 @@ const kitMessagesEn = {
29
29
  pageChrome: {
30
30
  back: "Back",
31
31
  moreActions: "More actions",
32
- help: "Help"
32
+ help: "Help",
33
+ ai: "AI"
33
34
  }
34
35
  };
35
36
  //#endregion
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.10.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 inlineItems = computed(() => props.items.filter(item =>
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
- ? props.items.filter(item => (item.visibility ?? 'auto') === 'auto')
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 props.items) {
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
  /**