@nexxtmove/ui 1.3.1 → 1.3.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.
@@ -12,6 +12,12 @@ interface NexxtDropdownButtonGroup {
12
12
  }
13
13
  type NexxtDropdownButtonBase = {
14
14
  variant?: 'primary' | 'secondary';
15
+ /**
16
+ * Accessible name for the trigger button. Supply this whenever the button
17
+ * has no visible text, which is every `iconOnly` case. Defaults to the
18
+ * visible label when there is one, and otherwise to a generic Dutch label.
19
+ */
20
+ ariaLabel?: string;
15
21
  options: NexxtDropdownButton[] | NexxtDropdownButtonGroup[];
16
22
  placement?: 'left' | 'right';
17
23
  triggerIcon?: InstanceType<typeof NexxtIcon>['name'];
@@ -17,7 +17,10 @@ declare var __VLS_6: {};
17
17
  type __VLS_Slots = {} & {
18
18
  right?: (props: typeof __VLS_6) => any;
19
19
  };
20
- declare const __VLS_base: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
20
+ declare const __VLS_base: import('vue').DefineComponent<__VLS_PublicProps, {
21
+ focus: () => void | undefined;
22
+ inputEl: Readonly<import('vue').ShallowRef<HTMLInputElement | null>>;
23
+ }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
21
24
  "update:modelValue": (value: string | number | undefined) => any;
22
25
  }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
23
26
  "onUpdate:modelValue"?: ((value: string | number | undefined) => any) | undefined;
package/dist/index.js CHANGED
@@ -1615,19 +1615,24 @@ var ut = { funda: { render: lt } }, Q = /* @__PURE__ */ p({
1615
1615
  modelModifiers: {}
1616
1616
  }),
1617
1617
  emits: ["update:modelValue"],
1618
- setup(e) {
1619
- let t = "input-field-" + Math.random().toString(36).slice(2, 10), r = M(e, "modelValue"), a = i(() => m()?.attrs.type === "number"), l = () => {
1620
- let e = Number(r.value || 0);
1621
- r.value = e + 1;
1622
- }, u = () => {
1623
- let e = Number(r.value || 0);
1624
- r.value = e - 1;
1618
+ setup(e, { expose: t }) {
1619
+ let r = "input-field-" + Math.random().toString(36).slice(2, 10), a = M(e, "modelValue"), l = N("inputEl");
1620
+ t({
1621
+ focus: () => l.value?.focus(),
1622
+ inputEl: l
1623
+ });
1624
+ let u = i(() => m()?.attrs.type === "number"), p = () => {
1625
+ let e = Number(a.value || 0);
1626
+ a.value = e + 1;
1627
+ }, h = () => {
1628
+ let e = Number(a.value || 0);
1629
+ a.value = e - 1;
1625
1630
  };
1626
- return (i, p) => (w(), s("div", vn, [
1631
+ return (t, i) => (w(), s("div", vn, [
1627
1632
  e.label ? (w(), s("label", {
1628
1633
  key: 0,
1629
1634
  class: "text-color-gray-700 justify-start body-normal text-sm",
1630
- for: t
1635
+ for: r
1631
1636
  }, [d(k(e.label) + " ", 1), e.required ? (w(), s("span", yn, "*")) : o("", !0)])) : o("", !0),
1632
1637
  c("div", { class: y(["group flex items-center gap-2 overflow-hidden rounded-lg ring transition-colors ease-out focus-within:ring-cornflower-blue-500", [e.error ? "ring-brick-400" : "ring-gray-200", {
1633
1638
  "pl-3": e.leftIcon,
@@ -1635,18 +1640,20 @@ var ut = { funda: { render: lt } }, Q = /* @__PURE__ */ p({
1635
1640
  }]]) }, [
1636
1641
  e.leftIcon ? (w(), s("span", bn, [f(Q, { name: e.leftIcon }, null, 8, ["name"])])) : o("", !0),
1637
1642
  z(c("input", _({
1638
- id: t,
1639
- "onUpdate:modelValue": p[0] ||= (e) => r.value = e,
1643
+ ref_key: "inputEl",
1644
+ ref: l,
1645
+ id: r,
1646
+ "onUpdate:modelValue": i[0] ||= (e) => a.value = e,
1640
1647
  placeholder: e.placeholder
1641
- }, i.$attrs, { class: ["bg-transparant h-10 w-full min-w-60 text-sm text-gray-900 placeholder-gray-400 focus:outline-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none", {
1648
+ }, t.$attrs, { class: ["bg-transparant h-10 w-full min-w-60 text-sm text-gray-900 placeholder-gray-400 focus:outline-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none", {
1642
1649
  "pl-3": !e.leftIcon,
1643
1650
  "pr-3": !e.rightIcon
1644
- }] }), null, 16, xn), [[F, r.value]]),
1645
- D(i.$slots, "right", {}, () => [a.value ? (w(), s("div", Sn, [c("button", {
1651
+ }] }), null, 16, xn), [[F, a.value]]),
1652
+ D(t.$slots, "right", {}, () => [u.value ? (w(), s("div", Sn, [c("button", {
1646
1653
  type: "button",
1647
1654
  class: "flex h-5 w-8 cursor-pointer items-center justify-center pt-2 text-gray-400 transition-colors group-focus-within:text-gray-950 hover:text-cornflower-blue-600 focus:text-cornflower-blue-600 focus:outline-none",
1648
1655
  "aria-label": "Verhogen",
1649
- onClick: l
1656
+ onClick: p
1650
1657
  }, [f(Q, {
1651
1658
  name: "chevron-up",
1652
1659
  class: "text-[10px]",
@@ -1655,7 +1662,7 @@ var ut = { funda: { render: lt } }, Q = /* @__PURE__ */ p({
1655
1662
  type: "button",
1656
1663
  class: "flex h-5 w-8 cursor-pointer items-center justify-center pb-2 text-gray-400 transition-colors group-focus-within:text-gray-950 hover:text-cornflower-blue-600 focus:text-cornflower-blue-600 focus:outline-none",
1657
1664
  "aria-label": "Verlagen",
1658
- onClick: u
1665
+ onClick: h
1659
1666
  }, [f(Q, {
1660
1667
  name: "chevron-down",
1661
1668
  class: "text-[10px]",
@@ -3149,6 +3156,7 @@ var ar = /* @__PURE__ */ p({
3149
3156
  __name: "DropdownButton",
3150
3157
  props: {
3151
3158
  variant: { default: "primary" },
3159
+ ariaLabel: {},
3152
3160
  options: {},
3153
3161
  placement: { default: "right" },
3154
3162
  triggerIcon: { default: "chevron-down" },
@@ -3162,26 +3170,30 @@ var ar = /* @__PURE__ */ p({
3162
3170
  },
3163
3171
  emits: ["navigate", "checkboxChange"],
3164
3172
  setup(t, { emit: n }) {
3165
- let r = n, l = T(null), u = i(() => t.placement === "right" ? "bottom-end" : "bottom-start"), p = i(() => t.options.length > 0 && "items" in t.options[0] ? t.options.flatMap((e) => e.items) : t.options), m = T({}), h = i(() => Object.fromEntries(p.value.filter((e) => e.checkbox).map((e) => [e.label, e.label in m.value ? m.value[e.label] : e.selected ?? !1]))), g = (e) => {
3166
- typeof e == "function" ? e() : r("navigate", e);
3167
- }, _ = (e) => {
3173
+ let r = i(() => t.ariaLabel ?? (t.label ? `${t.label} menu openen` : "Menu openen")), l = n, u = T(null), p = i(() => t.placement === "right" ? "bottom-end" : "bottom-start"), m = i(() => t.options.length > 0 && "items" in t.options[0] ? t.options.flatMap((e) => e.items) : t.options), h = T({});
3174
+ I(() => m.value.filter((e) => e.checkbox).map((e) => e.selected ?? !1), () => {
3175
+ h.value = {};
3176
+ }, { deep: !0 });
3177
+ let g = i(() => Object.fromEntries(m.value.filter((e) => e.checkbox).map((e) => [e.label, e.label in h.value ? h.value[e.label] : e.selected ?? !1]))), _ = (e) => {
3178
+ typeof e == "function" ? e() : l("navigate", e);
3179
+ }, v = (e) => {
3168
3180
  if (e.checkbox) {
3169
- let t = !h.value[e.label];
3170
- m.value[e.label] = t, r("checkboxChange", e.label, t);
3171
- } else l.value?.close();
3172
- g(e.action);
3173
- }, v = (e) => e.length > 0 && "items" in e[0], b = i(() => v(t.options) ? t.options : [{
3181
+ let t = !g.value[e.label];
3182
+ h.value[e.label] = t, l("checkboxChange", e.label, t);
3183
+ } else u.value?.close();
3184
+ _(e.action);
3185
+ }, b = (e) => e.length > 0 && "items" in e[0], x = i(() => b(t.options) ? t.options : [{
3174
3186
  title: "",
3175
3187
  items: t.options
3176
- }]), x = i(() => t.variant === "primary" ? "bg-button-bg-primary-default text-button-text-primary-default" : t.variant === "secondary" ? "bg-button-bg-secondary-default text-button-text-secondary-default border-button-border-secondary-default border border-solid hover:border-button-border-secondary-hover" : ""), S = i(() => t.variant === "primary" ? "bg-button-bg-primary-default hover:bg-button-bg-primary-hover focus-visible:bg-button-bg-primary-focussed focus-visible:outline-button-border-primary-focussed focus-visible:outline focus-visible:outline-solid active:bg-button-bg-primary-selected" : t.variant === "secondary" ? "bg-button-bg-secondary-default hover:bg-button-bg-secondary-hover focus-visible:bg-button-bg-secondary-focussed focus-visible:outline-button-border-secondary-focussed focus-visible:outline focus-visible:outline-solid active:bg-button-bg-secondary-selected" : "");
3177
- return (n, r) => (w(), a($, {
3188
+ }]), S = i(() => t.variant === "primary" ? "bg-button-bg-primary-default text-button-text-primary-default" : t.variant === "secondary" ? "bg-button-bg-secondary-default text-button-text-secondary-default border-button-border-secondary-default border border-solid hover:border-button-border-secondary-hover" : ""), C = i(() => t.variant === "primary" ? "bg-button-bg-primary-default hover:bg-button-bg-primary-hover focus-visible:bg-button-bg-primary-focussed focus-visible:outline-button-border-primary-focussed focus-visible:outline focus-visible:outline-solid active:bg-button-bg-primary-selected" : t.variant === "secondary" ? "bg-button-bg-secondary-default hover:bg-button-bg-secondary-hover focus-visible:bg-button-bg-secondary-focussed focus-visible:outline-button-border-secondary-focussed focus-visible:outline focus-visible:outline-solid active:bg-button-bg-secondary-selected" : "");
3189
+ return (n, i) => (w(), a($, {
3178
3190
  ref_key: "panelRef",
3179
- ref: l,
3180
- placement: u.value
3191
+ ref: u,
3192
+ placement: p.value
3181
3193
  }, {
3182
- trigger: R(({ isOpen: n, toggle: r }) => [c("div", { class: y(["inline-flex items-center overflow-hidden rounded-lg", x.value]) }, [t.iconOnly ? o("", !0) : (w(), s(e, { key: 0 }, [c("button", {
3183
- class: y(["flex cursor-pointer items-center gap-2 px-4 py-2 transition-colors duration-200", S.value]),
3184
- onClick: (e) => t.action ? g(t.action) : r()
3194
+ trigger: R(({ isOpen: n, toggle: i }) => [c("div", { class: y(["inline-flex items-center overflow-hidden rounded-lg", S.value]) }, [t.iconOnly ? o("", !0) : (w(), s(e, { key: 0 }, [c("button", {
3195
+ class: y(["flex cursor-pointer items-center gap-2 px-4 py-2 transition-colors duration-200", C.value]),
3196
+ onClick: (e) => t.action ? _(t.action) : i()
3185
3197
  }, [t.icon ? (w(), a(Q, {
3186
3198
  key: 0,
3187
3199
  name: t.icon,
@@ -3190,28 +3202,28 @@ var ar = /* @__PURE__ */ p({
3190
3202
  class: y(["h-7 w-px rounded-sm", t.variant === "primary" ? "bg-white" : "bg-gray-200"]),
3191
3203
  "aria-hidden": "true"
3192
3204
  }, null, 2)], 64)), c("button", {
3193
- class: y(["w-10 shrink-0 cursor-pointer transition-colors duration-200", [S.value, t.iconOnly ? "p-2" : "px-2 py-2"]]),
3205
+ class: y(["w-10 shrink-0 cursor-pointer transition-colors duration-200", [C.value, t.iconOnly ? "p-2" : "px-2 py-2"]]),
3194
3206
  "aria-haspopup": "menu",
3195
3207
  "aria-expanded": n,
3196
- "aria-label": `Open ${t.label} menu`,
3197
- onClick: r
3208
+ "aria-label": r.value,
3209
+ onClick: i
3198
3210
  }, [f(Q, {
3199
3211
  name: t.triggerIcon,
3200
3212
  class: y(["transition-transform duration-200", { "rotate-180": n && t.triggerIcon === "chevron-down" }]),
3201
3213
  "aria-hidden": "true"
3202
3214
  }, null, 8, ["name", "class"])], 10, br)], 2)]),
3203
- content: R(() => [c("div", xr, [(w(!0), s(e, null, E(b.value, (t, n) => (w(), s(e, { key: t.title || n }, [t.title ? (w(), s("div", {
3215
+ content: R(() => [c("div", xr, [(w(!0), s(e, null, E(x.value, (t, n) => (w(), s(e, { key: t.title || n }, [t.title ? (w(), s("div", {
3204
3216
  key: 0,
3205
3217
  class: y(["border-b border-gray-100 px-6 py-3 extra-small-normal text-gray-600", n > 0 ? "border-t pt-2" : ""])
3206
3218
  }, k(t.title), 3)) : o("", !0), (w(!0), s(e, null, E(t.items, (e) => (w(), s("button", {
3207
3219
  key: e.label,
3208
3220
  role: e.checkbox ? "menuitemcheckbox" : "menuitem",
3209
- "aria-checked": e.checkbox ? h.value[e.label] ?? !1 : void 0,
3221
+ "aria-checked": e.checkbox ? g.value[e.label] ?? !1 : void 0,
3210
3222
  class: y(["flex w-full min-w-42 cursor-pointer items-center gap-2 px-6 py-3 text-left text-sm whitespace-nowrap text-gray-700 transition-colors duration-200 hover:bg-gray-100 hover:text-purple-600 focus:bg-gray-100 focus:outline-none", !e.checkbox && e.selected ? "bg-cornflower-blue-100" : ""]),
3211
- onClick: (t) => _(e)
3223
+ onClick: (t) => v(e)
3212
3224
  }, [e.checkbox ? (w(), a(tn, {
3213
3225
  key: 0,
3214
- "model-value": h.value[e.label] ?? !1,
3226
+ "model-value": g.value[e.label] ?? !1,
3215
3227
  class: "pointer-events-none"
3216
3228
  }, null, 8, ["model-value"])) : e.icon ? (w(), a(Q, {
3217
3229
  key: 1,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nexxtmove/ui",
3
3
  "type": "module",
4
- "version": "1.3.1",
4
+ "version": "1.3.3",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" setup>
2
- import { ref, computed } from 'vue'
2
+ import { ref, computed, watch } from 'vue'
3
3
  import type { Placement } from '@floating-ui/vue'
4
4
  import NexxtIcon from '../Icon/Icon.vue'
5
5
  import NexxtCheckbox from '../Checkbox/Checkbox.vue'
@@ -20,6 +20,12 @@ interface NexxtDropdownButtonGroup {
20
20
 
21
21
  type NexxtDropdownButtonBase = {
22
22
  variant?: 'primary' | 'secondary'
23
+ /**
24
+ * Accessible name for the trigger button. Supply this whenever the button
25
+ * has no visible text, which is every `iconOnly` case. Defaults to the
26
+ * visible label when there is one, and otherwise to a generic Dutch label.
27
+ */
28
+ ariaLabel?: string
23
29
  options: NexxtDropdownButton[] | NexxtDropdownButtonGroup[]
24
30
  placement?: 'left' | 'right'
25
31
  triggerIcon?: InstanceType<typeof NexxtIcon>['name']
@@ -39,8 +45,16 @@ const {
39
45
  placement = 'right',
40
46
  iconOnly = false,
41
47
  triggerIcon = 'chevron-down',
48
+ ariaLabel,
42
49
  } = defineProps<NexxtDropdownButtonProps>()
43
50
 
51
+ // `label` is optional in the `iconOnly` branch of the props union, so
52
+ // interpolating it produced a literal "Open undefined menu" on every icon-only
53
+ // trigger. The fallback chain never yields `undefined`.
54
+ const triggerAriaLabel = computed(
55
+ () => ariaLabel ?? (label ? `${label} menu openen` : 'Menu openen'),
56
+ )
57
+
44
58
  const emit = defineEmits<{
45
59
  navigate: [url: string]
46
60
  checkboxChange: [label: string, checked: boolean]
@@ -58,8 +72,23 @@ const flatItems = computed<NexxtDropdownButton[]>(() =>
58
72
  : (options as NexxtDropdownButton[]),
59
73
  )
60
74
 
75
+ // Local echo of a click, so a checkbox responds instantly instead of waiting
76
+ // for the parent to feed `selected` back in.
61
77
  const toggledItems = ref<Record<string, boolean>>({})
62
78
 
79
+ // The echo must not outlive the prop it shadows. Once an item was toggled its
80
+ // label stayed in `toggledItems` forever, so `selected` was permanently
81
+ // ignored and a parent could never reset a filter menu. Clearing the echo
82
+ // whenever the incoming `selected` values change hands control back to the
83
+ // parent while keeping the component usable uncontrolled.
84
+ watch(
85
+ () => flatItems.value.filter((item) => item.checkbox).map((item) => item.selected ?? false),
86
+ () => {
87
+ toggledItems.value = {}
88
+ },
89
+ { deep: true },
90
+ )
91
+
63
92
  const checkedItems = computed(() =>
64
93
  Object.fromEntries(
65
94
  flatItems.value
@@ -150,7 +179,7 @@ const buttonClasses = computed(() => {
150
179
  :class="[buttonClasses, iconOnly ? 'p-2' : 'px-2 py-2']"
151
180
  aria-haspopup="menu"
152
181
  :aria-expanded="isOpen"
153
- :aria-label="`Open ${label} menu`"
182
+ :aria-label="triggerAriaLabel"
154
183
  @click="toggle"
155
184
  >
156
185
  <NexxtIcon
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, getCurrentInstance } from 'vue'
2
+ import { computed, getCurrentInstance, useTemplateRef } from 'vue'
3
3
  import Icon from '../Icon/Icon.vue'
4
4
 
5
5
  const INPUT_ID = 'input-field-' + Math.random().toString(36).slice(2, 10)
@@ -22,6 +22,17 @@ defineProps<NexxtInputFieldProps>()
22
22
 
23
23
  const modelValue = defineModel<string | number>()
24
24
 
25
+ const inputEl = useTemplateRef<HTMLInputElement>('inputEl')
26
+
27
+ /**
28
+ * Focus the underlying input. Exposed so callers do not have to reach through
29
+ * the component with `$el.querySelector('input')`, which breaks the moment the
30
+ * internal markup changes.
31
+ */
32
+ const focus = () => inputEl.value?.focus()
33
+
34
+ defineExpose({ focus, inputEl })
35
+
25
36
  const isNumber = computed(() => {
26
37
  const instance = getCurrentInstance()
27
38
  return instance?.attrs.type === 'number'
@@ -58,6 +69,7 @@ const decrement = () => {
58
69
  </span>
59
70
 
60
71
  <input
72
+ ref="inputEl"
61
73
  :id="INPUT_ID"
62
74
  v-model="modelValue"
63
75
  :placeholder="placeholder"
@@ -113,6 +113,17 @@
113
113
  --color-brands-google: oklch(63.039% 0.18003 259.95533);
114
114
  --color-brands-tiktok: oklch(63.451% 0.25395 16.82869);
115
115
  --color-brands-whatsapp: oklch(76.095% 0.20146 149.74008);
116
+ --color-yellow-50: oklch(98.368% 0.01059 100.83239);
117
+ --color-yellow-100: oklch(95.814% 0.02653 99.92039);
118
+ --color-yellow-200: oklch(93.256% 0.06003 102.33209);
119
+ --color-yellow-300: oklch(90.398% 0.09432 101.47911);
120
+ --color-yellow-400: oklch(87.635% 0.13397 101.26883);
121
+ --color-yellow-500: oklch(84.92% 0.17048 101.62037);
122
+ --color-yellow-600: oklch(70.556% 0.14179 101.46368);
123
+ --color-yellow-700: oklch(56.751% 0.11269 101.29896);
124
+ --color-yellow-800: oklch(42.725% 0.08495 101.41972);
125
+ --color-yellow-900: oklch(28.975% 0.0576 102.22885);
126
+ --color-yellow-950: oklch(21.884% 0.04426 103.48365);
116
127
  --color-button-bg-primary-default: var(--color-cornflower-blue-500);
117
128
  --color-button-bg-primary-hover: var(--color-cornflower-blue-400);
118
129
  --color-button-bg-primary-focussed: var(--color-cornflower-blue-500);