@nexxtmove/ui 1.13.0 → 1.14.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.
@@ -1,16 +1,54 @@
1
1
  export type NexxtTemplateCardContentFeature = 'references' | 'valuation' | 'reviews' | 'packages';
2
2
  export interface NexxtTemplateCardProps {
3
+ /** Preview image of the template. Falls back to a placeholder of the same height when omitted. */
3
4
  image?: string;
5
+ /** Template name, shown as the card heading. Truncated on one line. */
4
6
  title: string;
7
+ /** Secondary line under the title, e.g. when the template was last changed. Truncated on one line. */
5
8
  subtitle?: string;
9
+ /** Content blocks the template contains; each renders an icon with a tooltip. */
6
10
  contentFeatures?: NexxtTemplateCardContentFeature[];
11
+ /** Overrides the default Dutch tooltip text per content feature. */
7
12
  messages?: Partial<Record<NexxtTemplateCardContentFeature, string>>;
13
+ /** Lays a full-card button over the content that emits `select`. Off keeps the card inert, so a read-only grid adds no empty tabstop per card. */
14
+ selectable?: boolean;
15
+ /** Whether this card is currently chosen: draws the blue border and, with `checkmark`, the badge. Only does something with `selectable`. A prop plus the `select` emit rather than a `defineModel`, because the card owns no state: the parent grid decides which single card may be chosen. */
16
+ selected?: boolean;
17
+ /** Shows a check badge in the top-right corner while selected. Only does something with `selectable`. */
18
+ checkmark?: boolean;
19
+ /** Disables the select button, dims the card content and drops the overlay. Only does something with `selectable`. */
20
+ disabled?: boolean;
8
21
  }
9
- declare const __VLS_export: import('vue').DefineComponent<NexxtTemplateCardProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<NexxtTemplateCardProps> & Readonly<{}>, {
22
+ export type NexxtTemplateCardSlots = {
23
+ /** Covers the whole card and fades in on hover or keyboard focus; the content underneath dims. Not rendered while the card is selectable and disabled. On a coarse pointer it shrinks to the image band, without veil or blur, so the title stays readable and the feature icons stay tappable. The layer is click-through, but `*:pointer-events-auto` only reaches its direct children: an own `absolute inset-0` wrapper inside the slot swallows the select click. */
24
+ overlay?(props: {
25
+ selected: boolean;
26
+ }): unknown;
27
+ /** Top-right corner of the card, for a preview or menu button. Stays interactive while `disabled`, which makes it the place for e.g. a preview button: the overlay is not rendered then. */
28
+ actions?(props: {
29
+ selected: boolean;
30
+ }): unknown;
31
+ };
32
+ type __VLS_Slots = NexxtTemplateCardSlots;
33
+ declare const __VLS_base: import('vue').DefineComponent<NexxtTemplateCardProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
34
+ select: () => any;
35
+ }, string, import('vue').PublicProps, Readonly<NexxtTemplateCardProps> & Readonly<{
36
+ onSelect?: (() => any) | undefined;
37
+ }>, {
10
38
  image: string;
11
39
  messages: Partial<Record<NexxtTemplateCardContentFeature, string>>;
40
+ selected: boolean;
41
+ disabled: boolean;
12
42
  subtitle: string;
13
43
  contentFeatures: NexxtTemplateCardContentFeature[];
44
+ selectable: boolean;
45
+ checkmark: boolean;
14
46
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
47
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
15
48
  declare const _default: typeof __VLS_export;
16
49
  export default _default;
50
+ type __VLS_WithSlots<T, S> = T & {
51
+ new (): {
52
+ $slots: S;
53
+ };
54
+ };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export type { EnergyLabelLetter } from './components/atoms/EnergyLabel/EnergyLab
5
5
  export type { IconType, NexxtIconName } from './components/atoms/Icon/Icon.vue';
6
6
  export type { CustomIconName } from './components/atoms/Icon/customIcons';
7
7
  export type { NexxtTabItem } from './components/molecules/Tabs/Tabs.vue';
8
+ export type { NexxtTemplateCardContentFeature, NexxtTemplateCardProps, NexxtTemplateCardSlots, } from './components/molecules/TemplateCard/TemplateCard.vue';
8
9
  export { default as NexxtAnimatedNumber } from './components/atoms/AnimatedNumber/AnimatedNumber.vue';
9
10
  export { default as NexxtAnnouncement } from './components/molecules/Announcement/Announcement.vue';
10
11
  export { default as NexxtAvatar } from './components/atoms/Avatar/Avatar.vue';
package/dist/index.js CHANGED
@@ -5283,16 +5283,29 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5283
5283
  _: 3
5284
5284
  }));
5285
5285
  }
5286
- }), Po = { class: "w-full max-w-105 rounded-xl border border-gray-200" }, Fo = { class: "h-38 w-full" }, Io = ["src", "alt"], Lo = {
5286
+ }), Po = { class: "block h-38 w-full" }, Fo = ["src"], Io = {
5287
5287
  key: 1,
5288
5288
  class: "flex h-38 w-full items-center justify-center rounded-t-xl bg-gray-100"
5289
- }, Ro = { class: "rounded-b-xl px-8 pt-3.5 pb-4" }, zo = { class: "truncate heading-4 text-gray-900" }, Bo = {
5289
+ }, Lo = { class: "rounded-b-xl px-8 pt-3.5 pb-4" }, Ro = { class: "truncate heading-4 text-gray-900" }, zo = {
5290
5290
  key: 0,
5291
5291
  class: "truncate body-normal text-gray-700"
5292
- }, Vo = {
5292
+ }, Bo = {
5293
5293
  key: 1,
5294
5294
  class: "mt-3 flex flex-wrap gap-2"
5295
- }, Ho = { class: "small-normal text-gray-700" }, Uo = /* @__PURE__ */ p({
5295
+ }, Vo = { class: "sr-only" }, Ho = { class: "small-normal text-gray-700" }, Uo = [
5296
+ "aria-label",
5297
+ "aria-pressed",
5298
+ "disabled"
5299
+ ], Wo = {
5300
+ key: 1,
5301
+ class: "absolute top-4 right-4 z-20"
5302
+ }, Go = {
5303
+ key: 2,
5304
+ class: "pointer-events-none absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 rounded-xl bg-white/60 opacity-0 backdrop-blur-xs transition-opacity duration-200 ease-out *:pointer-events-auto group-focus-within:opacity-100 group-hover:opacity-100 pointer-coarse:inset-x-0 pointer-coarse:top-0 pointer-coarse:bottom-auto pointer-coarse:h-38 pointer-coarse:justify-end pointer-coarse:bg-transparent pointer-coarse:p-4 pointer-coarse:opacity-100 pointer-coarse:backdrop-blur-none"
5305
+ }, Ko = {
5306
+ key: 3,
5307
+ class: "absolute -top-2 -right-2 z-30 flex size-6 items-center justify-center rounded-full bg-cornflower-blue-500 text-white shadow-sm"
5308
+ }, qo = /* @__PURE__ */ p({
5296
5309
  name: "NexxtTemplateCard",
5297
5310
  __name: "TemplateCard",
5298
5311
  props: {
@@ -5300,10 +5313,27 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5300
5313
  title: {},
5301
5314
  subtitle: { default: void 0 },
5302
5315
  contentFeatures: { default: () => [] },
5303
- messages: { default: () => ({}) }
5316
+ messages: { default: () => ({}) },
5317
+ selectable: {
5318
+ type: Boolean,
5319
+ default: !1
5320
+ },
5321
+ selected: {
5322
+ type: Boolean,
5323
+ default: !1
5324
+ },
5325
+ checkmark: {
5326
+ type: Boolean,
5327
+ default: !1
5328
+ },
5329
+ disabled: {
5330
+ type: Boolean,
5331
+ default: !1
5332
+ }
5304
5333
  },
5305
- setup(t) {
5306
- let n = {
5334
+ emits: ["select"],
5335
+ setup(t, { emit: n }) {
5336
+ let r = {
5307
5337
  references: {
5308
5338
  icon: "house-circle-check",
5309
5339
  textColor: "text-cornflower-blue-500",
@@ -5324,41 +5354,68 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5324
5354
  textColor: "text-green-500",
5325
5355
  backgroundColor: "bg-green-50"
5326
5356
  }
5327
- }, r = {
5357
+ }, a = {
5328
5358
  references: "Referenties",
5329
5359
  valuation: "Waardebepaling",
5330
5360
  reviews: "Reviews",
5331
5361
  packages: "Courtage"
5332
- }, a = t, l = i(() => ({
5333
- ...r,
5334
- ...a.messages
5335
- }));
5336
- return (r, i) => (w(), s("div", Po, [c("picture", Fo, [t.image ? (w(), s("img", {
5337
- key: 0,
5338
- src: t.image,
5339
- alt: t.title,
5340
- loading: "lazy",
5341
- class: "w-full rounded-t-xl object-cover"
5342
- }, null, 8, Io)) : (w(), s("div", Lo, [f(Q, {
5343
- name: "image",
5344
- class: "text-4xl text-gray-400"
5345
- })]))]), c("div", Ro, [
5346
- c("h3", zo, k(t.title), 1),
5347
- t.subtitle ? (w(), s("p", Bo, k(t.subtitle), 1)) : o("", !0),
5348
- t.contentFeatures.length ? (w(), s("ul", Vo, [(w(!0), s(e, null, E(t.contentFeatures, (e) => (w(), s("li", { key: e }, [f(No, null, {
5349
- tooltip: B(() => [c("span", Ho, k(l.value[e]), 1)]),
5350
- default: B(() => [c("div", { class: y([[n[e].backgroundColor, n[e].textColor], "flex size-8 items-center justify-center rounded-full"]) }, [f(Q, { name: n[e].icon }, null, 8, ["name"])], 2)]),
5351
- _: 2
5352
- }, 1024)]))), 128))])) : o("", !0)
5353
- ])]));
5362
+ }, l = t, u = n, d = i(() => ({
5363
+ ...a,
5364
+ ...l.messages
5365
+ })), p = () => {
5366
+ l.selectable && !l.disabled && u("select");
5367
+ };
5368
+ return (n, i) => (w(), s("div", { class: y(["group relative size-full max-w-105 rounded-xl border border-gray-200 transition-colors duration-200 ease-out", [t.selectable && t.selected && "border-cornflower-blue-500", t.selectable && !t.selected && !t.disabled && "hover:border-gray-300"]]) }, [
5369
+ c("div", { class: y(["relative z-10 transition-opacity duration-200 ease-out", [
5370
+ t.selectable && "pointer-events-none",
5371
+ t.selectable && t.disabled && "opacity-50",
5372
+ n.$slots.overlay && !(t.selectable && t.disabled) && "group-focus-within:opacity-40 group-hover:opacity-40 pointer-coarse:opacity-100!"
5373
+ ]]) }, [c("picture", Po, [t.image ? (w(), s("img", {
5374
+ key: 0,
5375
+ src: t.image,
5376
+ alt: "",
5377
+ loading: "lazy",
5378
+ class: "h-38 w-full rounded-t-xl object-cover"
5379
+ }, null, 8, Fo)) : (w(), s("div", Io, [f(Q, {
5380
+ name: "image",
5381
+ class: "text-4xl text-gray-400"
5382
+ })]))]), c("div", Lo, [
5383
+ c("h3", Ro, k(t.title), 1),
5384
+ t.subtitle ? (w(), s("p", zo, k(t.subtitle), 1)) : o("", !0),
5385
+ t.contentFeatures.length ? (w(), s("ul", Bo, [(w(!0), s(e, null, E(t.contentFeatures, (e) => (w(), s("li", { key: e }, [f(No, null, {
5386
+ tooltip: B(() => [c("span", Ho, k(d.value[e]), 1)]),
5387
+ default: B(() => [c("div", {
5388
+ class: y([[r[e].backgroundColor, r[e].textColor], "pointer-events-auto flex size-8 items-center justify-center rounded-full"]),
5389
+ onClick: p
5390
+ }, [f(Q, { name: r[e].icon }, null, 8, ["name"]), c("span", Vo, k(d.value[e]), 1)], 2)]),
5391
+ _: 2
5392
+ }, 1024)]))), 128))])) : o("", !0)
5393
+ ])], 2),
5394
+ t.selectable ? (w(), s("button", {
5395
+ key: 0,
5396
+ type: "button",
5397
+ "aria-label": t.title,
5398
+ "aria-pressed": t.selected,
5399
+ disabled: t.disabled || void 0,
5400
+ class: "absolute inset-0 cursor-pointer rounded-xl focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-cornflower-blue-500 disabled:cursor-not-allowed",
5401
+ onClick: p
5402
+ }, null, 8, Uo)) : o("", !0),
5403
+ n.$slots.actions ? (w(), s("div", Wo, [D(n.$slots, "actions", { selected: t.selected })])) : o("", !0),
5404
+ n.$slots.overlay && !(t.selectable && t.disabled) ? (w(), s("div", Go, [D(n.$slots, "overlay", { selected: t.selected })])) : o("", !0),
5405
+ t.selectable && t.selected && t.checkmark ? (w(), s("span", Ko, [f(Q, {
5406
+ name: "check",
5407
+ type: "solid",
5408
+ class: "text-xs"
5409
+ })])) : o("", !0)
5410
+ ], 2));
5354
5411
  }
5355
- }), Wo = {
5412
+ }), Jo = {
5356
5413
  key: 0,
5357
5414
  class: "text-brick-500"
5358
- }, Go = ["rows", "placeholder"], Ko = {
5415
+ }, Yo = ["rows", "placeholder"], Xo = {
5359
5416
  key: 1,
5360
5417
  class: "small-normal text-brick-400"
5361
- }, qo = /* @__PURE__ */ p({
5418
+ }, Zo = /* @__PURE__ */ p({
5362
5419
  name: "NexxtTextarea",
5363
5420
  inheritAttrs: !1,
5364
5421
  __name: "Textarea",
@@ -5414,7 +5471,7 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5414
5471
  key: 0,
5415
5472
  class: "justify-start small-normal text-gray-700",
5416
5473
  for: n
5417
- }, [d(k(e.label) + " ", 1), e.required ? (w(), s("span", Wo, "*")) : o("", !0)])) : o("", !0),
5474
+ }, [d(k(e.label) + " ", 1), e.required ? (w(), s("span", Jo, "*")) : o("", !0)])) : o("", !0),
5418
5475
  V(c("textarea", _({
5419
5476
  id: n,
5420
5477
  ref_key: "textareaEl",
@@ -5425,21 +5482,21 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5425
5482
  placeholder: e.placeholder,
5426
5483
  class: ["min-h-10 w-full rounded-lg border px-3 py-2 small-normal text-gray-900 transition-colors outline-none placeholder:text-gray-400 focus:border-cornflower-blue-500", [e.error ? "border-brick-400" : "border-gray-200", e.autosize || e.resize === "none" ? "resize-none" : "resize-y"]],
5427
5484
  onInput: p
5428
- }), null, 16, Go), [[L, r.value]]),
5429
- e.error && e.errorMessage ? (w(), s("p", Ko, k(e.errorMessage), 1)) : o("", !0)
5485
+ }), null, 16, Yo), [[L, r.value]]),
5486
+ e.error && e.errorMessage ? (w(), s("p", Xo, k(e.errorMessage), 1)) : o("", !0)
5430
5487
  ], 6));
5431
5488
  }
5432
- }), Jo = [
5489
+ }), Qo = [
5433
5490
  "data-event-source",
5434
5491
  "data-event-date",
5435
5492
  "date-event-type"
5436
- ], Yo = { class: "heading-3" }, Xo = ["href"], Zo = { class: "truncate group-hover:underline" }, Qo = {
5493
+ ], $o = { class: "heading-3" }, es = ["href"], ts = { class: "truncate group-hover:underline" }, ns = {
5437
5494
  key: 1,
5438
5495
  class: "mt-1 text-sm text-gray-500"
5439
- }, $o = {
5496
+ }, rs = {
5440
5497
  key: 2,
5441
5498
  class: "mt-1 text-sm text-gray-500"
5442
- }, es = /* @__PURE__ */ p({
5499
+ }, is = /* @__PURE__ */ p({
5443
5500
  name: "NexxtTimelineEvent",
5444
5501
  __name: "TimelineEvent",
5445
5502
  props: {
@@ -5481,20 +5538,20 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5481
5538
  name: n.value.icon,
5482
5539
  class: "flex size-6 text-xl text-white"
5483
5540
  }, null, 8, ["name"])], 2), c("div", { class: y(["flex w-full flex-col gap-2 rounded-xl border border-gray-200 p-8", { "max-w-lg": e.splitView }]) }, [
5484
- c("span", Yo, k(n.value.label), 1),
5541
+ c("span", $o, k(n.value.label), 1),
5485
5542
  e.event.url ? (w(), s("a", {
5486
5543
  key: 0,
5487
5544
  href: e.event.url,
5488
5545
  target: "_blank",
5489
5546
  class: "group mt-1 flex justify-between gap-2 text-sm text-cornflower-blue-500 hover:text-cornflower-blue-600"
5490
- }, [c("span", Zo, k(e.event.description || e.event.url), 1), f(Q, {
5547
+ }, [c("span", ts, k(e.event.description || e.event.url), 1), f(Q, {
5491
5548
  name: "external-link",
5492
5549
  class: "ml-1 size-4 text-gray-950"
5493
- })], 8, Xo)) : e.event.description ? (w(), s("span", Qo, k(e.event.description), 1)) : o("", !0),
5494
- e.event.request_reason ? (w(), s("span", $o, " Reden: " + k(e.event.request_reason), 1)) : o("", !0)
5495
- ], 2)], 2)], 10, Jo));
5550
+ })], 8, es)) : e.event.description ? (w(), s("span", ns, k(e.event.description), 1)) : o("", !0),
5551
+ e.event.request_reason ? (w(), s("span", rs, " Reden: " + k(e.event.request_reason), 1)) : o("", !0)
5552
+ ], 2)], 2)], 10, Qo));
5496
5553
  }
5497
- }), ts = { class: "relative z-10" }, ns = { class: "flex items-center justify-between px-4 py-2" }, rs = { class: "flex gap-1" }, is = { class: "text-sm" }, as = { class: "flex gap-1" }, os = { class: "text-sm" }, ss = /* @__PURE__ */ p({
5554
+ }), as = { class: "relative z-10" }, os = { class: "flex items-center justify-between px-4 py-2" }, ss = { class: "flex gap-1" }, cs = { class: "text-sm" }, ls = { class: "flex gap-1" }, us = { class: "text-sm" }, ds = /* @__PURE__ */ p({
5498
5555
  name: "NexxtTimelineLanes",
5499
5556
  __name: "TimelineLanes",
5500
5557
  props: {
@@ -5504,15 +5561,15 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5504
5561
  rightTooltip: { default: "Vanuit relatie" }
5505
5562
  },
5506
5563
  setup(e) {
5507
- return (t, n) => (w(), s("div", ts, [c("div", ns, [c("span", rs, [d(k(e.leftLabel) + " ", 1), e.leftTooltip ? (w(), a(No, { key: 0 }, {
5508
- tooltip: B(() => [c("span", is, k(e.leftTooltip), 1)]),
5564
+ return (t, n) => (w(), s("div", as, [c("div", os, [c("span", ss, [d(k(e.leftLabel) + " ", 1), e.leftTooltip ? (w(), a(No, { key: 0 }, {
5565
+ tooltip: B(() => [c("span", cs, k(e.leftTooltip), 1)]),
5509
5566
  default: B(() => [f(Q, {
5510
5567
  name: "circle-info",
5511
5568
  class: "size-4 text-xs text-gray-600"
5512
5569
  })]),
5513
5570
  _: 1
5514
- })) : o("", !0)]), c("span", as, [d(k(e.rightLabel) + " ", 1), e.rightTooltip ? (w(), a(No, { key: 0 }, {
5515
- tooltip: B(() => [c("span", os, k(e.rightTooltip), 1)]),
5571
+ })) : o("", !0)]), c("span", ls, [d(k(e.rightLabel) + " ", 1), e.rightTooltip ? (w(), a(No, { key: 0 }, {
5572
+ tooltip: B(() => [c("span", us, k(e.rightTooltip), 1)]),
5516
5573
  default: B(() => [f(Q, {
5517
5574
  name: "circle-info",
5518
5575
  class: "size-4 text-xs text-gray-600"
@@ -5520,10 +5577,10 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5520
5577
  _: 1
5521
5578
  })) : o("", !0)])])]));
5522
5579
  }
5523
- }), cs = ["data-phase"], ls = { class: "text-sm font-semibold tracking-wider uppercase" }, us = { class: "mt-2 font-medium text-xs text-gray-500" }, ds = {
5580
+ }), fs = ["data-phase"], ps = { class: "text-sm font-semibold tracking-wider uppercase" }, ms = { class: "mt-2 font-medium text-xs text-gray-500" }, hs = {
5524
5581
  key: 1,
5525
5582
  class: "h-1"
5526
- }, fs = /* @__PURE__ */ p({
5583
+ }, gs = /* @__PURE__ */ p({
5527
5584
  name: "NexxtTimelinePhaseblock",
5528
5585
  __name: "TimelinePhaseblock",
5529
5586
  props: {
@@ -5559,13 +5616,13 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5559
5616
  class: "relative z-10 flex flex-col gap-16",
5560
5617
  "data-phase": t.phase.phase.value
5561
5618
  }, [
5562
- t.index === 0 && t.isSplitView ? (w(), a(ss, { key: 0 })) : o("", !0),
5619
+ t.index === 0 && t.isSplitView ? (w(), a(ds, { key: 0 })) : o("", !0),
5563
5620
  c("div", { class: y(["absolute bottom-0 left-5 w-0 border-l", [A(Z)[t.phase.phase.value].color, t.index === 0 && t.isSplitView ? "top-10" : "top-0"]]) }, null, 2),
5564
5621
  t.isSplitView ? (w(), s("div", {
5565
5622
  key: 1,
5566
5623
  class: y(["absolute right-5 bottom-0 w-0 border-l", [A(Z)[t.phase.phase.value].color, t.index === 0 ? "top-10" : "top-0"]])
5567
5624
  }, null, 2)) : o("", !0),
5568
- (w(!0), s(e, null, E(t.phase.events, (e) => (w(), a(es, {
5625
+ (w(!0), s(e, null, E(t.phase.events, (e) => (w(), a(is, {
5569
5626
  key: e.id,
5570
5627
  event: e,
5571
5628
  "split-view": t.isSplitView
@@ -5574,26 +5631,26 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5574
5631
  key: 2,
5575
5632
  class: y(["relative flex flex-col items-center pt-8 pb-24 text-center", { "pl-16": !t.isSplitView }])
5576
5633
  }, [
5577
- c("div", { class: y(["flex items-center gap-2 rounded-full px-12 py-4", [A(Z)[t.phase.phase.value].bgColor, A(Z)[t.phase.phase.value].textColor]]) }, [c("span", ls, k(A(Z)[t.phase.phase.value].label), 1), d() ? (w(), a(Q, {
5634
+ c("div", { class: y(["flex items-center gap-2 rounded-full px-12 py-4", [A(Z)[t.phase.phase.value].bgColor, A(Z)[t.phase.phase.value].textColor]]) }, [c("span", ps, k(A(Z)[t.phase.phase.value].label), 1), d() ? (w(), a(Q, {
5578
5635
  key: 0,
5579
5636
  name: d() === "up" ? "arrow-up" : "arrow-down",
5580
5637
  class: "size-4 text-white"
5581
5638
  }, null, 8, ["name"])) : o("", !0)], 2),
5582
- c("span", us, k(u()), 1),
5639
+ c("span", ms, k(u()), 1),
5583
5640
  t.isSplitView ? (w(), s("div", {
5584
5641
  key: 0,
5585
5642
  class: y(["mt-4 h-px w-[75%]", A(Z)[t.phase.phase.value].bgColor])
5586
5643
  }, null, 2)) : o("", !0),
5587
- t.isLast ? o("", !0) : (w(), s("div", ds)),
5644
+ t.isLast ? o("", !0) : (w(), s("div", hs)),
5588
5645
  c("div", { class: y(["absolute inset-y-0 left-5 w-px bg-linear-to-b", r.value]) }, null, 2),
5589
5646
  t.isSplitView ? (w(), s("div", {
5590
5647
  key: 2,
5591
5648
  class: y(["absolute inset-y-0 right-5 w-px bg-linear-to-b", r.value])
5592
5649
  }, null, 2)) : o("", !0)
5593
5650
  ], 2))
5594
- ], 8, cs));
5651
+ ], 8, fs));
5595
5652
  }
5596
- }), ps = { class: "relative z-10 flex flex-col gap-16" }, ms = /* @__PURE__ */ p({
5653
+ }), _s = { class: "relative z-10 flex flex-col gap-16" }, vs = /* @__PURE__ */ p({
5597
5654
  name: "NexxtTimelineTrack",
5598
5655
  __name: "TimelineTrack",
5599
5656
  props: {
@@ -5614,8 +5671,8 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5614
5671
  },
5615
5672
  setup(t) {
5616
5673
  let n = t, r = i(() => n.splitView && n.showLanes);
5617
- return (n, i) => (w(), s("div", ps, [
5618
- r.value ? (w(), a(ss, {
5674
+ return (n, i) => (w(), s("div", _s, [
5675
+ r.value ? (w(), a(ds, {
5619
5676
  key: 0,
5620
5677
  "left-label": t.leftLabel,
5621
5678
  "right-label": t.rightLabel,
@@ -5632,24 +5689,24 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5632
5689
  key: 1,
5633
5690
  class: y(["absolute right-5 bottom-0 w-0 border-l", [t.color, r.value ? "top-10" : "top-0"]])
5634
5691
  }, null, 2)) : o("", !0),
5635
- D(n.$slots, "default", {}, () => [(w(!0), s(e, null, E(t.events, (e) => (w(), a(es, {
5692
+ D(n.$slots, "default", {}, () => [(w(!0), s(e, null, E(t.events, (e) => (w(), a(is, {
5636
5693
  key: e.id,
5637
5694
  event: e,
5638
5695
  "split-view": t.splitView
5639
5696
  }, null, 8, ["event", "split-view"]))), 128))])
5640
5697
  ]));
5641
5698
  }
5642
- }), hs = ["role", "aria-live"], gs = { class: "flex min-w-0 flex-1 flex-col gap-0.5 pt-0.5" }, _s = {
5699
+ }), ys = ["role", "aria-live"], bs = { class: "flex min-w-0 flex-1 flex-col gap-0.5 pt-0.5" }, xs = {
5643
5700
  key: 1,
5644
5701
  class: "body-normal"
5645
- }, vs = ["aria-label"], ys = {
5702
+ }, Ss = ["aria-label"], Cs = {
5646
5703
  key: 0,
5647
5704
  class: "absolute inset-0 -rotate-90 text-gray-400",
5648
5705
  viewBox: "0 0 24 24",
5649
5706
  width: "32",
5650
5707
  height: "32",
5651
5708
  "aria-hidden": "true"
5652
- }, bs = 56.55, xs = /*#__PURE__*/ Un(/* @__PURE__ */ p({
5709
+ }, ws = 56.55, Ts = /*#__PURE__*/ Un(/* @__PURE__ */ p({
5653
5710
  name: "NexxtToast",
5654
5711
  __name: "Toast",
5655
5712
  props: {
@@ -5664,7 +5721,7 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5664
5721
  },
5665
5722
  emits: ["dismiss"],
5666
5723
  setup(e, { emit: t }) {
5667
- M((e) => ({ fa0d20de: bs }));
5724
+ M((e) => ({ fa0d20de: ws }));
5668
5725
  let n = {
5669
5726
  info: {
5670
5727
  icon: "info",
@@ -5717,12 +5774,12 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5717
5774
  name: u.value.icon,
5718
5775
  class: "text-xl"
5719
5776
  }, null, 8, ["name"])], 2),
5720
- c("div", gs, [
5777
+ c("div", bs, [
5721
5778
  e.title ? (w(), s("p", {
5722
5779
  key: 0,
5723
5780
  class: y(["heading-4", u.value.titleColor])
5724
5781
  }, k(e.title), 3)) : o("", !0),
5725
- e.message ? (w(), s("p", _s, k(e.message), 1)) : o("", !0),
5782
+ e.message ? (w(), s("p", xs, k(e.message), 1)) : o("", !0),
5726
5783
  D(t.$slots, "default", {}, void 0, !0)
5727
5784
  ]),
5728
5785
  e.dismissible ? (w(), s("button", {
@@ -5731,7 +5788,7 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5731
5788
  class: "relative flex size-8 shrink-0 cursor-pointer items-center justify-center focus:outline-none",
5732
5789
  "aria-label": e.autoClose ? `Sluiten (verdwijnt automatisch na ${e.autoClose / 1e3} seconden)` : "Sluiten",
5733
5790
  onClick: n[0] ||= (e) => a("dismiss")
5734
- }, [e.autoClose ? (w(), s("svg", ys, [c("circle", {
5791
+ }, [e.autoClose ? (w(), s("svg", Cs, [c("circle", {
5735
5792
  cx: "12",
5736
5793
  cy: "12",
5737
5794
  r: "9",
@@ -5739,7 +5796,7 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5739
5796
  stroke: "currentColor",
5740
5797
  "stroke-width": "1.2",
5741
5798
  "stroke-linecap": "round",
5742
- "stroke-dasharray": bs,
5799
+ "stroke-dasharray": ws,
5743
5800
  class: "timer-circle",
5744
5801
  style: x({
5745
5802
  animationDuration: `${e.autoClose}ms`,
@@ -5748,10 +5805,10 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5748
5805
  }, null, 4)])) : o("", !0), f(Q, {
5749
5806
  name: "xmark",
5750
5807
  class: "text-sm"
5751
- })], 8, vs)) : o("", !0)
5752
- ], 42, hs));
5808
+ })], 8, Ss)) : o("", !0)
5809
+ ], 42, ys));
5753
5810
  }
5754
- }), [["__scopeId", "data-v-7e3c5bda"]]), Ss = /* @__PURE__ */ p({
5811
+ }), [["__scopeId", "data-v-7e3c5bda"]]), Es = /* @__PURE__ */ p({
5755
5812
  name: "NexxtToaster",
5756
5813
  __name: "Toaster",
5757
5814
  props: {
@@ -5784,7 +5841,7 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5784
5841
  default: B(() => [(w(!0), s(e, null, E(n.toasts, (e) => (w(), s("div", {
5785
5842
  key: e.id,
5786
5843
  class: "pointer-events-auto"
5787
- }, [f(xs, _({ ref_for: !0 }, e, { onDismiss: (t) => o("dismiss", e.id) }), null, 16, ["onDismiss"])]))), 128))]),
5844
+ }, [f(Ts, _({ ref_for: !0 }, e, { onDismiss: (t) => o("dismiss", e.id) }), null, 16, ["onDismiss"])]))), 128))]),
5788
5845
  _: 1
5789
5846
  }, 8, [
5790
5847
  "class",
@@ -5792,19 +5849,19 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5792
5849
  "leave-to-class"
5793
5850
  ])], 2)]));
5794
5851
  }
5795
- }), Cs = { class: "flex flex-col gap-1" }, ws = {
5852
+ }), Ds = { class: "flex flex-col gap-1" }, Os = {
5796
5853
  key: 0,
5797
5854
  class: "justify-start small-normal text-gray-700"
5798
- }, Ts = ["onClick"], Es = {
5855
+ }, ks = ["onClick"], As = {
5799
5856
  key: 1,
5800
5857
  class: "extra-small-semibold"
5801
- }, Ds = ["onClick"], Os = {
5858
+ }, js = ["onClick"], Ms = {
5802
5859
  key: 1,
5803
5860
  class: "extra-small-semibold"
5804
- }, ks = {
5861
+ }, Ns = {
5805
5862
  key: 1,
5806
5863
  class: "extra-small-semibold"
5807
- }, As = /* @__PURE__ */ p({
5864
+ }, Ps = /* @__PURE__ */ p({
5808
5865
  name: "NexxtToggleGroup",
5809
5866
  __name: "ToggleGroup",
5810
5867
  props: /*@__PURE__*/ g({
@@ -5835,7 +5892,7 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5835
5892
  };
5836
5893
  return R(r, async () => await m()), ot(i, () => m()), S(async () => {
5837
5894
  await document.fonts.ready, await m(), await v(), p.value = !0;
5838
- }), (n, m) => (w(), s("div", Cs, [t.label ? (w(), s("label", ws, k(t.label), 1)) : o("", !0), c("div", {
5895
+ }), (n, m) => (w(), s("div", Ds, [t.label ? (w(), s("label", Os, k(t.label), 1)) : o("", !0), c("div", {
5839
5896
  ref_key: "containerRef",
5840
5897
  ref: i,
5841
5898
  class: y(["relative isolate flex h-10 gap-1 rounded-xl bg-gray-100 p-1", { "flex-wrap": !t.equalWidth }])
@@ -5859,7 +5916,7 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5859
5916
  }, [n.icon ? (w(), a(Q, {
5860
5917
  key: 0,
5861
5918
  name: n.icon
5862
- }, null, 8, ["name"])) : o("", !0), n.label ? (w(), s("span", Es, k(n.label), 1)) : o("", !0)], 10, Ts)]),
5919
+ }, null, 8, ["name"])) : o("", !0), n.label ? (w(), s("span", As, k(n.label), 1)) : o("", !0)], 10, ks)]),
5863
5920
  _: 2
5864
5921
  }, 1024)) : (w(), s("button", {
5865
5922
  key: 1,
@@ -5871,7 +5928,7 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5871
5928
  }, [n.icon ? (w(), a(Q, {
5872
5929
  key: 0,
5873
5930
  name: n.icon
5874
- }, null, 8, ["name"])) : o("", !0), n.label ? (w(), s("span", Os, k(n.label), 1)) : o("", !0)], 10, Ds))], 64))), 128)),
5931
+ }, null, 8, ["name"])) : o("", !0), n.label ? (w(), s("span", Ms, k(n.label), 1)) : o("", !0)], 10, js))], 64))), 128)),
5875
5932
  c("div", {
5876
5933
  "aria-hidden": "true",
5877
5934
  class: y(["pointer-events-none absolute inset-0 z-20 flex gap-1 p-1", [p.value ? "transition-[clip-path] duration-500 ease-[cubic-bezier(0.34,1.2,0.64,1)]" : "", { "flex-wrap": !t.equalWidth }]]),
@@ -5882,29 +5939,29 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5882
5939
  }, [e.icon ? (w(), a(Q, {
5883
5940
  key: 0,
5884
5941
  name: e.icon
5885
- }, null, 8, ["name"])) : o("", !0), e.label ? (w(), s("span", ks, k(e.label), 1)) : o("", !0)], 2))), 128))], 6)
5942
+ }, null, 8, ["name"])) : o("", !0), e.label ? (w(), s("span", Ns, k(e.label), 1)) : o("", !0)], 2))), 128))], 6)
5886
5943
  ], 2)]));
5887
5944
  }
5888
- }), js = [
5945
+ }), Fs = [
5889
5946
  "src",
5890
5947
  "loop",
5891
5948
  "aria-label"
5892
- ], Ms = {
5949
+ ], Is = {
5893
5950
  key: 0,
5894
5951
  role: "alert",
5895
5952
  class: "absolute inset-0 flex flex-col items-center justify-center gap-3 rounded-lg bg-gray-900 text-white"
5896
- }, Ns = { class: "heading-4" }, Ps = { class: "max-w-xs truncate extra-small-normal text-white/50" }, Fs = {
5953
+ }, Ls = { class: "heading-4" }, Rs = { class: "max-w-xs truncate extra-small-normal text-white/50" }, zs = {
5897
5954
  key: 0,
5898
5955
  class: "pointer-events-none absolute inset-0 flex items-center justify-center"
5899
- }, Is = {
5956
+ }, Bs = {
5900
5957
  class: "flex size-16 items-center justify-center rounded-full bg-black/50 text-white",
5901
5958
  "aria-hidden": "true"
5902
- }, Ls = [
5959
+ }, Vs = [
5903
5960
  "aria-label",
5904
5961
  "aria-valuemax",
5905
5962
  "aria-valuenow",
5906
5963
  "aria-valuetext"
5907
- ], Rs = { class: "flex items-center gap-2" }, zs = ["aria-label"], Bs = { class: "extra-small-normal text-white/70 tabular-nums" }, Vs = ["aria-label"], Hs = ["aria-label"], Us = /* @__PURE__ */ p({
5964
+ ], Hs = { class: "flex items-center gap-2" }, Us = ["aria-label"], Ws = { class: "extra-small-normal text-white/70 tabular-nums" }, Gs = ["aria-label"], Ks = ["aria-label"], qs = /* @__PURE__ */ p({
5908
5965
  __name: "VideoPlayer",
5909
5966
  props: {
5910
5967
  src: {},
@@ -5985,15 +6042,15 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
5985
6042
  "aria-label": a.value.video,
5986
6043
  onError: r[0] ||= (e) => E.value = !0,
5987
6044
  onClick: r[1] ||= (t) => e.showControls === !1 ? void 0 : I()
5988
- }, null, 42, js),
5989
- E.value ? (w(), s("div", Ms, [
6045
+ }, null, 42, Fs),
6046
+ E.value ? (w(), s("div", Is, [
5990
6047
  f(Q, {
5991
6048
  name: "circle-exclamation",
5992
6049
  class: "text-3xl text-red-400",
5993
6050
  "aria-hidden": "true"
5994
6051
  }),
5995
- c("p", Ns, k(a.value.error), 1),
5996
- c("p", Ps, k(e.src), 1)
6052
+ c("p", Ls, k(a.value.error), 1),
6053
+ c("p", Rs, k(e.src), 1)
5997
6054
  ])) : o("", !0),
5998
6055
  f(n, {
5999
6056
  "enter-active-class": "transition-opacity duration-100 ease-out",
@@ -6001,7 +6058,7 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
6001
6058
  "enter-from-class": "opacity-0",
6002
6059
  "leave-to-class": "opacity-0"
6003
6060
  }, {
6004
- default: B(() => [e.showControls !== !1 && D.value && !E.value ? (w(), s("div", Fs, [c("div", Is, [f(Q, {
6061
+ default: B(() => [e.showControls !== !1 && D.value && !E.value ? (w(), s("div", zs, [c("div", Bs, [f(Q, {
6005
6062
  name: D.value,
6006
6063
  class: "text-2xl"
6007
6064
  }, null, 8, ["name"])])])) : o("", !0)]),
@@ -6044,7 +6101,7 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
6044
6101
  class: "absolute top-1/2 size-3 -translate-1/2 rounded-full bg-sapphire-100 opacity-0 shadow-sm transition-opacity group-hover/player:opacity-100",
6045
6102
  style: x({ left: `${ee.value}%` })
6046
6103
  }, null, 4)
6047
- ], 40, Ls), c("div", Rs, [
6104
+ ], 40, Vs), c("div", Hs, [
6048
6105
  c("button", {
6049
6106
  type: "button",
6050
6107
  "aria-label": A(u) ? a.value.pause : a.value.play,
@@ -6054,8 +6111,8 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
6054
6111
  name: A(u) ? "pause" : "play",
6055
6112
  class: "text-sm",
6056
6113
  "aria-hidden": "true"
6057
- }, null, 8, ["name"])], 8, zs),
6058
- c("span", Bs, k(F(W.value)) + " / " + k(F(A(m))), 1),
6114
+ }, null, 8, ["name"])], 8, Us),
6115
+ c("span", Ws, k(F(W.value)) + " / " + k(F(A(m))), 1),
6059
6116
  r[7] ||= c("div", { class: "flex-1" }, null, -1),
6060
6117
  c("button", {
6061
6118
  type: "button",
@@ -6066,7 +6123,7 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
6066
6123
  name: A(h) ? "volume-xmark" : "volume-high",
6067
6124
  class: "text-sm",
6068
6125
  "aria-hidden": "true"
6069
- }, null, 8, ["name"])], 8, Vs),
6126
+ }, null, 8, ["name"])], 8, Gs),
6070
6127
  c("button", {
6071
6128
  type: "button",
6072
6129
  "aria-label": A(_) ? a.value.exitFullscreen : a.value.enterFullscreen,
@@ -6076,10 +6133,10 @@ var Wa = { render: Ua }, Ga = { class: "flex items-center justify-end self-stret
6076
6133
  name: A(_) ? "compress" : "expand",
6077
6134
  class: "text-sm",
6078
6135
  "aria-hidden": "true"
6079
- }, null, 8, ["name"])], 8, Hs)
6136
+ }, null, 8, ["name"])], 8, Ks)
6080
6137
  ])], 4)) : o("", !0)
6081
6138
  ], 32));
6082
6139
  }
6083
6140
  });
6084
6141
  //#endregion
6085
- export { st as EVENT_CONFIG_MAP, ct as NexxtAnimatedNumber, mt as NexxtAnnouncement, yt as NexxtAvatar, xt as NexxtButton, $t as NexxtCalendar, nn as NexxtCard, an as NexxtCheckbox, on as NexxtChip, xn as NexxtColorPicker, Hn as NexxtCombobox, Vn as NexxtComboboxPanel, Yn as NexxtContactInfoCard, cr as NexxtCountrySelect, fr as NexxtCustomerJourneyTile, pr as NexxtDatePicker, xr as NexxtDoughnutChart, Er as NexxtDropdownButton, kr as NexxtEnergyLabel, $ as NexxtFloatingPanel, Nr as NexxtHeader, Q as NexxtIcon, Fr as NexxtInfoBlock, On as NexxtInputField, Ur as NexxtInputSelect, Gr as NexxtLoader, Yr as NexxtModal, Zr as NexxtModalFooter, ni as NexxtPageContainer, ri as NexxtPagination, si as NexxtPresenceIndicator, yi as NexxtProgressBar, Ai as NexxtPropertyCard, Ti as NexxtPropertyListing, Ki as NexxtReferenceCard, ea as NexxtSelect, ra as NexxtSelectableOption, la as NexxtSidebarMenuItem, da as NexxtSkeleton, ha as NexxtSkeletonPropertyCard, ya as NexxtSlider, Sa as NexxtSocialIcons, Ea as NexxtSocialMediaCustomTemplate, Fa as NexxtSocialMediaTemplate, Ya as NexxtSocialMediaType, co as NexxtSource, gt as NexxtSquircle, lo as NexxtStepperHeader, fo as NexxtSwitch, yo as NexxtTabbedContent, Mo as NexxtTable, ho as NexxtTabs, Uo as NexxtTemplateCard, qo as NexxtTextarea, es as NexxtTimelineEvent, ss as NexxtTimelineLanes, fs as NexxtTimelinePhaseblock, ms as NexxtTimelineTrack, xs as NexxtToast, Ss as NexxtToaster, As as NexxtToggleGroup, No as NexxtTooltip, Us as NexxtVideoPlayer, Z as PHASE_MAP, ao as SOURCE_MAP };
6142
+ export { st as EVENT_CONFIG_MAP, ct as NexxtAnimatedNumber, mt as NexxtAnnouncement, yt as NexxtAvatar, xt as NexxtButton, $t as NexxtCalendar, nn as NexxtCard, an as NexxtCheckbox, on as NexxtChip, xn as NexxtColorPicker, Hn as NexxtCombobox, Vn as NexxtComboboxPanel, Yn as NexxtContactInfoCard, cr as NexxtCountrySelect, fr as NexxtCustomerJourneyTile, pr as NexxtDatePicker, xr as NexxtDoughnutChart, Er as NexxtDropdownButton, kr as NexxtEnergyLabel, $ as NexxtFloatingPanel, Nr as NexxtHeader, Q as NexxtIcon, Fr as NexxtInfoBlock, On as NexxtInputField, Ur as NexxtInputSelect, Gr as NexxtLoader, Yr as NexxtModal, Zr as NexxtModalFooter, ni as NexxtPageContainer, ri as NexxtPagination, si as NexxtPresenceIndicator, yi as NexxtProgressBar, Ai as NexxtPropertyCard, Ti as NexxtPropertyListing, Ki as NexxtReferenceCard, ea as NexxtSelect, ra as NexxtSelectableOption, la as NexxtSidebarMenuItem, da as NexxtSkeleton, ha as NexxtSkeletonPropertyCard, ya as NexxtSlider, Sa as NexxtSocialIcons, Ea as NexxtSocialMediaCustomTemplate, Fa as NexxtSocialMediaTemplate, Ya as NexxtSocialMediaType, co as NexxtSource, gt as NexxtSquircle, lo as NexxtStepperHeader, fo as NexxtSwitch, yo as NexxtTabbedContent, Mo as NexxtTable, ho as NexxtTabs, qo as NexxtTemplateCard, Zo as NexxtTextarea, is as NexxtTimelineEvent, ds as NexxtTimelineLanes, gs as NexxtTimelinePhaseblock, vs as NexxtTimelineTrack, Ts as NexxtToast, Es as NexxtToaster, Ps as NexxtToggleGroup, No as NexxtTooltip, qs as NexxtVideoPlayer, Z as PHASE_MAP, ao as SOURCE_MAP };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nexxtmove/ui",
3
3
  "type": "module",
4
- "version": "1.13.0",
4
+ "version": "1.14.0",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -7,11 +7,31 @@ import NexxtTooltip from '../../atoms/Tooltip/Tooltip.vue'
7
7
  export type NexxtTemplateCardContentFeature = 'references' | 'valuation' | 'reviews' | 'packages'
8
8
 
9
9
  export interface NexxtTemplateCardProps {
10
+ /** Preview image of the template. Falls back to a placeholder of the same height when omitted. */
10
11
  image?: string
12
+ /** Template name, shown as the card heading. Truncated on one line. */
11
13
  title: string
14
+ /** Secondary line under the title, e.g. when the template was last changed. Truncated on one line. */
12
15
  subtitle?: string
16
+ /** Content blocks the template contains; each renders an icon with a tooltip. */
13
17
  contentFeatures?: NexxtTemplateCardContentFeature[]
18
+ /** Overrides the default Dutch tooltip text per content feature. */
14
19
  messages?: Partial<Record<NexxtTemplateCardContentFeature, string>>
20
+ /** Lays a full-card button over the content that emits `select`. Off keeps the card inert, so a read-only grid adds no empty tabstop per card. */
21
+ selectable?: boolean
22
+ /** Whether this card is currently chosen: draws the blue border and, with `checkmark`, the badge. Only does something with `selectable`. A prop plus the `select` emit rather than a `defineModel`, because the card owns no state: the parent grid decides which single card may be chosen. */
23
+ selected?: boolean
24
+ /** Shows a check badge in the top-right corner while selected. Only does something with `selectable`. */
25
+ checkmark?: boolean
26
+ /** Disables the select button, dims the card content and drops the overlay. Only does something with `selectable`. */
27
+ disabled?: boolean
28
+ }
29
+
30
+ export type NexxtTemplateCardSlots = {
31
+ /** Covers the whole card and fades in on hover or keyboard focus; the content underneath dims. Not rendered while the card is selectable and disabled. On a coarse pointer it shrinks to the image band, without veil or blur, so the title stays readable and the feature icons stay tappable. The layer is click-through, but `*:pointer-events-auto` only reaches its direct children: an own `absolute inset-0` wrapper inside the slot swallows the select click. */
32
+ overlay?(props: { selected: boolean }): unknown
33
+ /** Top-right corner of the card, for a preview or menu button. Stays interactive while `disabled`, which makes it the place for e.g. a preview button: the overlay is not rendered then. */
34
+ actions?(props: { selected: boolean }): unknown
15
35
  }
16
36
 
17
37
  type TemplateCardIcon = {
@@ -45,46 +65,152 @@ const props = withDefaults(defineProps<NexxtTemplateCardProps>(), {
45
65
  subtitle: undefined,
46
66
  contentFeatures: () => [],
47
67
  messages: () => ({}),
68
+ selectable: false,
69
+ selected: false,
70
+ checkmark: false,
71
+ disabled: false,
48
72
  })
49
73
 
74
+ const emit = defineEmits<{
75
+ /** The card was chosen. No payload: the consumer knows which template it rendered. */
76
+ select: []
77
+ }>()
78
+
79
+ // The root of the template stays a plain `<div>` and must stay single-root: the heading, the
80
+ // paragraph and the tooltip triggers are flow content, which is invalid inside a `<button>`, and
81
+ // wrapping them also dropped the heading role and turned the accessible name of the button into
82
+ // the whole card as one long string. A leading comment in the template would make it a fragment,
83
+ // after which class and attributes no longer reach the card, so the notes live here.
84
+ // Selection, hover and the dim are driven from the props and from the root's own `:hover`, not
85
+ // from the button. The slot checks are read inline rather than through a computed -- slot presence
86
+ // is not reactive, so a consumer toggling `<template v-if>` on a slot would never re-evaluate a
87
+ // cached computed.
88
+ //
89
+ // The overlay and the actions are siblings of the select button, never children: a button inside a
90
+ // button is invalid markup and swallows the inner click, and both layers stack above it so their
91
+ // own buttons get the click. They are handed the selection because their content often styles
92
+ // itself by it.
93
+ defineSlots<NexxtTemplateCardSlots>()
94
+
50
95
  const featureMessages = computed<Record<NexxtTemplateCardContentFeature, string>>(() => ({
51
96
  ...DEFAULT_MESSAGES,
52
97
  ...props.messages,
53
98
  }))
99
+
100
+ const onSelect = () => {
101
+ if (props.selectable && !props.disabled) emit('select')
102
+ }
54
103
  </script>
55
104
 
56
105
  <template>
57
- <div class="w-full max-w-105 rounded-xl border border-gray-200">
58
- <picture class="h-38 w-full">
59
- <img
60
- v-if="image"
61
- :src="image"
62
- :alt="title"
63
- loading="lazy"
64
- class="w-full rounded-t-xl object-cover"
65
- />
66
- <div v-else class="flex h-38 w-full items-center justify-center rounded-t-xl bg-gray-100">
67
- <NexxtIcon name="image" class="text-4xl text-gray-400" />
106
+ <div
107
+ class="group relative size-full max-w-105 rounded-xl border border-gray-200 transition-colors duration-200 ease-out"
108
+ :class="[
109
+ selectable && selected && 'border-cornflower-blue-500',
110
+ selectable && !selected && !disabled && 'hover:border-gray-300',
111
+ ]"
112
+ >
113
+ <div
114
+ class="relative z-10 transition-opacity duration-200 ease-out"
115
+ :class="[
116
+ // Lets a click anywhere on the card reach the stretched button underneath; only the
117
+ // tooltip triggers claim their own pointer events back.
118
+ selectable && 'pointer-events-none',
119
+ selectable && disabled && 'opacity-50',
120
+ $slots.overlay &&
121
+ !(selectable && disabled) &&
122
+ 'group-focus-within:opacity-40 group-hover:opacity-40 pointer-coarse:opacity-100!',
123
+ ]"
124
+ >
125
+ <picture class="block h-38 w-full">
126
+ <!-- Decorative: the template name is right below it as text. -->
127
+ <img
128
+ v-if="image"
129
+ :src="image"
130
+ alt=""
131
+ loading="lazy"
132
+ class="h-38 w-full rounded-t-xl object-cover"
133
+ />
134
+ <div v-else class="flex h-38 w-full items-center justify-center rounded-t-xl bg-gray-100">
135
+ <NexxtIcon name="image" class="text-4xl text-gray-400" />
136
+ </div>
137
+ </picture>
138
+ <div class="rounded-b-xl px-8 pt-3.5 pb-4">
139
+ <h3 class="truncate heading-4 text-gray-900">{{ title }}</h3>
140
+ <p v-if="subtitle" class="truncate body-normal text-gray-700">{{ subtitle }}</p>
141
+ <ul v-if="contentFeatures.length" class="mt-3 flex flex-wrap gap-2">
142
+ <li v-for="feature in contentFeatures" :key="feature">
143
+ <NexxtTooltip>
144
+ <!--
145
+ Claims its pointer events back from the content layer so the tooltip still opens on
146
+ hover, and forwards the click to the card so the icon is no dead zone on a
147
+ selectable card. `onSelect` guards on selectable and disabled itself.
148
+ NexxtIcon is aria-hidden and the trigger is not focusable, so the label is only
149
+ there for screen readers.
150
+ -->
151
+ <div
152
+ :class="[ICON_MAP[feature].backgroundColor, ICON_MAP[feature].textColor]"
153
+ class="pointer-events-auto flex size-8 items-center justify-center rounded-full"
154
+ @click="onSelect"
155
+ >
156
+ <NexxtIcon :name="ICON_MAP[feature].icon" />
157
+ <span class="sr-only">{{ featureMessages[feature] }}</span>
158
+ </div>
159
+ <template #tooltip>
160
+ <span class="small-normal text-gray-700">{{ featureMessages[feature] }}</span>
161
+ </template>
162
+ </NexxtTooltip>
163
+ </li>
164
+ </ul>
68
165
  </div>
69
- </picture>
70
- <div class="rounded-b-xl px-8 pt-3.5 pb-4">
71
- <h3 class="truncate heading-4 text-gray-900">{{ title }}</h3>
72
- <p v-if="subtitle" class="truncate body-normal text-gray-700">{{ subtitle }}</p>
73
- <ul v-if="contentFeatures.length" class="mt-3 flex flex-wrap gap-2">
74
- <li v-for="feature in contentFeatures" :key="feature">
75
- <NexxtTooltip>
76
- <div
77
- :class="[ICON_MAP[feature].backgroundColor, ICON_MAP[feature].textColor]"
78
- class="flex size-8 items-center justify-center rounded-full"
79
- >
80
- <NexxtIcon :name="ICON_MAP[feature].icon" />
81
- </div>
82
- <template #tooltip>
83
- <span class="small-normal text-gray-700">{{ featureMessages[feature] }}</span>
84
- </template>
85
- </NexxtTooltip>
86
- </li>
87
- </ul>
88
166
  </div>
167
+
168
+ <!--
169
+ A stretched button instead of a wrapper: it carries the click, the pressed state and the
170
+ focus ring, while the card content above it stays real document structure. It holds the
171
+ lowest stacking level of the layers, so a click on an actions or overlay button lands there
172
+ and not here. The focus ring follows Button.vue (`outline-3` plus offset), so a selected card
173
+ with keyboard focus shows two distinguishable indicators: blue border and ring.
174
+ -->
175
+ <button
176
+ v-if="selectable"
177
+ type="button"
178
+ :aria-label="title"
179
+ :aria-pressed="selected"
180
+ :disabled="disabled || undefined"
181
+ class="absolute inset-0 cursor-pointer rounded-xl focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-cornflower-blue-500 disabled:cursor-not-allowed"
182
+ @click="onSelect"
183
+ />
184
+
185
+ <div v-if="$slots.actions" class="absolute top-4 right-4 z-20">
186
+ <slot name="actions" :selected="selected" />
187
+ </div>
188
+
189
+ <!--
190
+ Dropped while selectable and disabled: an invisible layer would still hold tabbable buttons,
191
+ and a card that cannot be chosen reveals nothing. A read-only card (no `selectable`) keeps
192
+ its overlay, disabled or not.
193
+ `pointer-events-none` on the layer with `*:pointer-events-auto` on its children lets a click
194
+ on the veil fall through to the select button while the overlay's own buttons keep theirs.
195
+ On a coarse pointer there is no hover to reveal it, so it is always there -- but then without
196
+ veil or blur and shrunk to the image band at the top, because a permanent veil over the title
197
+ and the subtitle drops their contrast below 4.5:1 and a full-height layer would swallow the
198
+ taps on the feature icons.
199
+ jsdom resolves no Tailwind and the storybook project cannot hit-test, so the layering itself
200
+ is asserted by neither suite; only the DOM order is.
201
+ -->
202
+ <div
203
+ v-if="$slots.overlay && !(selectable && disabled)"
204
+ class="pointer-events-none absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 rounded-xl bg-white/60 opacity-0 backdrop-blur-xs transition-opacity duration-200 ease-out *:pointer-events-auto group-focus-within:opacity-100 group-hover:opacity-100 pointer-coarse:inset-x-0 pointer-coarse:top-0 pointer-coarse:bottom-auto pointer-coarse:h-38 pointer-coarse:justify-end pointer-coarse:bg-transparent pointer-coarse:p-4 pointer-coarse:opacity-100 pointer-coarse:backdrop-blur-none"
205
+ >
206
+ <slot name="overlay" :selected="selected" />
207
+ </div>
208
+
209
+ <span
210
+ v-if="selectable && selected && checkmark"
211
+ class="absolute -top-2 -right-2 z-30 flex size-6 items-center justify-center rounded-full bg-cornflower-blue-500 text-white shadow-sm"
212
+ >
213
+ <NexxtIcon name="check" type="solid" class="text-xs" />
214
+ </span>
89
215
  </div>
90
216
  </template>
package/src/index.ts CHANGED
@@ -13,6 +13,11 @@ export type { EnergyLabelLetter } from './components/atoms/EnergyLabel/EnergyLab
13
13
  export type { IconType, NexxtIconName } from './components/atoms/Icon/Icon.vue'
14
14
  export type { CustomIconName } from './components/atoms/Icon/customIcons'
15
15
  export type { NexxtTabItem } from './components/molecules/Tabs/Tabs.vue'
16
+ export type {
17
+ NexxtTemplateCardContentFeature,
18
+ NexxtTemplateCardProps,
19
+ NexxtTemplateCardSlots,
20
+ } from './components/molecules/TemplateCard/TemplateCard.vue'
16
21
 
17
22
  export { default as NexxtAnimatedNumber } from './components/atoms/AnimatedNumber/AnimatedNumber.vue'
18
23
  export { default as NexxtAnnouncement } from './components/molecules/Announcement/Announcement.vue'