@octabits-io/nuxt-ui-kit 0.8.0 → 0.9.1

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.8.0",
3
+ "version": "0.9.1",
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",
@@ -30,25 +30,38 @@ const props = withDefaults(defineProps<{
30
30
  showLabel?: boolean
31
31
  /** Whether the tooltip should respect global disabled state. */
32
32
  tooltipDisabled?: boolean
33
+ /**
34
+ * Why the action is currently unavailable. When set, the button renders
35
+ * disabled and the tooltip shows "label — reason", keeping the action's
36
+ * purpose visible alongside the blocker. Hover on the disabled button works
37
+ * via an internal span wrapper (disabled buttons don't dispatch pointer
38
+ * events).
39
+ */
40
+ disabledReason?: string | null
33
41
  }>(), {
34
42
  tone: 'neutral',
35
43
  loading: false,
36
44
  disabled: false,
37
45
  showLabel: false,
38
46
  tooltipDisabled: false,
47
+ disabledReason: null,
39
48
  })
40
49
 
41
50
  const slots = useSlots()
42
51
 
43
52
  const hasLabelSlot = computed(() => Boolean(slots.default))
44
53
  const isIconOnly = computed(() => !hasLabelSlot.value && !props.showLabel)
54
+ const isBlocked = computed(() => Boolean(props.disabledReason))
55
+ const tooltipText = computed(() =>
56
+ props.disabledReason ? `${props.label} — ${props.disabledReason}` : props.label,
57
+ )
45
58
 
46
59
  const buttonProps = computed<Partial<ButtonProps>>(() => {
47
60
  const base: Partial<ButtonProps> = {
48
61
  icon: props.icon,
49
62
  size: 'sm',
50
63
  loading: props.loading,
51
- disabled: props.disabled || props.loading,
64
+ disabled: props.disabled || props.loading || isBlocked.value,
52
65
  to: props.to,
53
66
  target: props.target,
54
67
  }
@@ -72,8 +85,25 @@ if (import.meta.dev && !props.label) {
72
85
  </script>
73
86
 
74
87
  <template>
75
- <UTooltip v-if="isIconOnly" :text="label" :disabled="tooltipDisabled">
88
+ <UTooltip
89
+ v-if="isIconOnly || isBlocked"
90
+ :text="tooltipText"
91
+ :disabled="!isBlocked && tooltipDisabled"
92
+ >
93
+ <!-- Disabled buttons don't dispatch pointer events: when blocked, the span
94
+ is the hover target and the button opts out of pointer events. -->
95
+ <span v-if="isBlocked" class="inline-flex">
96
+ <UButton
97
+ v-bind="buttonProps"
98
+ class="pointer-events-none"
99
+ :aria-label="tooltipText"
100
+ :label="!isIconOnly && showLabel ? label : undefined"
101
+ >
102
+ <slot v-if="!isIconOnly" />
103
+ </UButton>
104
+ </span>
76
105
  <UButton
106
+ v-else
77
107
  v-bind="buttonProps"
78
108
  :aria-label="label"
79
109
  />
@@ -15,7 +15,14 @@ import PageUtilityActions from './PageUtilityActions.vue'
15
15
  * Standard page header.
16
16
  *
17
17
  * Conventions enforced by this component and its siblings:
18
- * - Max 3 neutral icon buttons inline in `#actions`; more go in `overflowItems`.
18
+ * - EVERY inline `#actions` button is labeled (`show-label`) — no icon-only
19
+ * buttons in the header. Hierarchy comes from tone alone: at most ONE
20
+ * `tone="primary"` (solid) per state — the state's main next step — ghost
21
+ * neutral for the rest.
22
+ * - Actions that are destructive only in *some* states (e.g. cancel) render
23
+ * inline while they are the state's decision counterpart, and move to the
24
+ * overflow menu (red) once they become destructive maintenance.
25
+ * - Max 3 inline actions visible per state in `#actions`; more go in `overflowItems`.
19
26
  * - Destructive actions are ALWAYS placed inside the overflow menu, never inline.
20
27
  * - Header height, spacing, and tooltip behavior are normalized via `PageAction`.
21
28
  * - Utility triggers (Help, AI history, …) live in `#utility` (default =
@@ -96,7 +103,9 @@ const titleClass = computed(() => props.density === 'compact' ? 'font-display te
96
103
  <slot name="badges" />
97
104
  </div>
98
105
 
99
- <div class="ml-auto flex items-center gap-1">
106
+ <!-- flex-wrap: labeled actions overflow narrow (mobile) viewports otherwise —
107
+ the outer header wraps rows, but this cluster must wrap internally too. -->
108
+ <div class="ml-auto flex flex-wrap items-center gap-1">
100
109
  <slot name="actions" />
101
110
  <PageActionMenu
102
111
  v-if="overflowItems.length"