@octabits-io/nuxt-ui-kit 0.8.0 → 0.9.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/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.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",
@@ -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,10 @@ 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
+ * - State-changing decision actions (confirm, publish, cancel, …) render
19
+ * labeled via `show-label`; generic reversible tools (edit, preview,
20
+ * download, add) stay icon-only with tooltips.
21
+ * - Max 3 inline actions visible per state in `#actions`; more go in `overflowItems`.
19
22
  * - Destructive actions are ALWAYS placed inside the overflow menu, never inline.
20
23
  * - Header height, spacing, and tooltip behavior are normalized via `PageAction`.
21
24
  * - Utility triggers (Help, AI history, …) live in `#utility` (default =