@leavepulse/ui 0.12.0 → 0.14.6

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.
Files changed (162) hide show
  1. package/dist/assets/LpCommandPalette-D315B47-.css +16 -0
  2. package/dist/chunks/LayoutCanvas-Dd20boUG.js +50 -0
  3. package/dist/chunks/LayoutNode-DGpZA9LX.js +225 -0
  4. package/dist/chunks/LpAlert-BAYHDSS1.js +69 -0
  5. package/dist/chunks/LpAppShell-DBTsNQaM.js +170 -0
  6. package/dist/chunks/LpAutocomplete-B9uoCs4t.js +196 -0
  7. package/dist/chunks/LpAvatar-B1Ez0ZAx.js +88 -0
  8. package/dist/chunks/LpBadge-CXzBPnwO.js +42 -0
  9. package/dist/chunks/LpBreadcrumbs-B7Aqatzw.js +42 -0
  10. package/dist/chunks/LpButton-5VXXCjwF.js +101 -0
  11. package/dist/chunks/LpCalendar-B7ZL0ty3.js +150 -0
  12. package/dist/chunks/LpCard-1yvWT9fx.js +66 -0
  13. package/dist/chunks/LpCheckbox-BO0zuuPh.js +55 -0
  14. package/dist/chunks/LpCodeBlock-CSiOSPs8.js +253 -0
  15. package/dist/chunks/LpConfirmDialog-KLtTtCiT.js +62 -0
  16. package/dist/chunks/LpContextMenu-D0mzXqA7.js +151 -0
  17. package/dist/chunks/LpDatePicker-dY2q4dr4.js +122 -0
  18. package/dist/chunks/LpDisclosure-DP6yfgfM.js +76 -0
  19. package/dist/chunks/LpDivider-CUX46KR0.js +43 -0
  20. package/dist/chunks/LpDrawer-Ce6g9ZMr.js +310 -0
  21. package/dist/chunks/LpDropdownMenu-DJpoqRCb.js +65 -0
  22. package/dist/chunks/LpEmptyState-CrfyRJUG.js +47 -0
  23. package/dist/chunks/LpFormField-Bn-ZwC6z.js +47 -0
  24. package/dist/chunks/LpIcon-CCnX5_2j.js +41 -0
  25. package/dist/chunks/LpInfraNode-C_PHcthC.js +99 -0
  26. package/dist/chunks/LpInput-BauZRyzm.js +136 -0
  27. package/dist/chunks/LpLaneNode-HtnQf2FF.js +33 -0
  28. package/dist/chunks/LpLink-DHwOEp4e.js +36 -0
  29. package/dist/chunks/LpLogViewer-hC0OKkeE.js +482 -0
  30. package/dist/chunks/LpModal-CWoOExzT.js +128 -0
  31. package/dist/chunks/LpNotificationBell-BDy0tQwr.js +232 -0
  32. package/dist/chunks/LpNumberField-C5ar-OwE.js +55 -0
  33. package/dist/chunks/LpOtpInput-DqaT0Z75.js +65 -0
  34. package/dist/chunks/LpPagination-CeGuNe_q.js +132 -0
  35. package/dist/chunks/LpPasswordInput-DIHFw4u0.js +64 -0
  36. package/dist/chunks/LpPhoneInput-LQvVFGMw.js +199 -0
  37. package/dist/chunks/LpPopover-8268UmlC.js +54 -0
  38. package/dist/chunks/LpProgress-BhI0IbjC.js +37 -0
  39. package/dist/chunks/LpRadio-CL-pI0_O.js +39 -0
  40. package/dist/chunks/LpRadioGroup-B4yDTmi6.js +52 -0
  41. package/dist/chunks/LpScrollArea-K1x-azgY.js +67 -0
  42. package/dist/chunks/LpSegmented-BmpanyAo.js +74 -0
  43. package/dist/chunks/LpSelect-DznhPEWX.js +222 -0
  44. package/dist/chunks/LpServiceNode-Dc-Ut5RS.js +51 -0
  45. package/dist/chunks/LpSidebar-BPQabjae.js +152 -0
  46. package/dist/chunks/LpSidebarNav-CpF7rZxx.js +126 -0
  47. package/dist/chunks/LpSkeleton-eSFIo4kD.js +22 -0
  48. package/dist/chunks/LpSlider-B1SF3pRV.js +53 -0
  49. package/dist/chunks/LpStat-CdbEFOvn.js +88 -0
  50. package/dist/chunks/LpStepper-5dFTrW21.js +53 -0
  51. package/dist/chunks/LpSwitch-v6fnccSM.js +28 -0
  52. package/dist/chunks/LpTable-CJ4Zt1UH.js +229 -0
  53. package/dist/chunks/LpTableOfContents-CjvxPMpU.js +155 -0
  54. package/dist/chunks/LpTabs-D7aTVPOb.js +115 -0
  55. package/dist/chunks/LpTextarea-CRAkhemZ.js +44 -0
  56. package/dist/chunks/LpThemeSwitcher-BgWwfkxs.js +601 -0
  57. package/dist/chunks/LpTilt-DuyKbpkA.js +49 -0
  58. package/dist/chunks/LpToaster-gDB1EaU3.js +196 -0
  59. package/dist/chunks/LpTooltip-CIwoRCag.js +49 -0
  60. package/dist/chunks/LpTopologyCanvas-DVwyQfKa.js +237 -0
  61. package/dist/chunks/LpUptimeBar-CB366k0O.js +127 -0
  62. package/dist/component-names.d.ts +1 -1
  63. package/dist/components/LayoutCanvas.vue.js +4 -0
  64. package/dist/components/LayoutNode.vue.js +4 -0
  65. package/dist/components/LpAlert.vue.js +4 -0
  66. package/dist/components/LpAppShell.vue.js +4 -0
  67. package/dist/components/LpAutocomplete.vue.js +4 -0
  68. package/dist/components/LpAvatar.vue.js +4 -0
  69. package/dist/components/LpBadge.vue.js +4 -0
  70. package/dist/components/LpBreadcrumbs.vue.js +4 -0
  71. package/dist/components/LpButton.vue.js +4 -0
  72. package/dist/components/LpCalendar.vue.js +4 -0
  73. package/dist/components/LpCard.vue.js +4 -0
  74. package/dist/components/LpCheckbox.vue.js +4 -0
  75. package/dist/components/LpCodeBlock.vue.js +4 -0
  76. package/dist/components/LpCommandPalette.vue.js +310 -0
  77. package/dist/components/LpConfirmDialog.vue.js +4 -0
  78. package/dist/components/LpContextMenu.vue.d.ts +7 -0
  79. package/dist/components/LpContextMenu.vue.js +4 -0
  80. package/dist/components/LpDatePicker.vue.js +4 -0
  81. package/dist/components/LpDisclosure.vue.js +4 -0
  82. package/dist/components/LpDivider.vue.js +4 -0
  83. package/dist/components/LpDrawer.vue.d.ts +8 -0
  84. package/dist/components/LpDrawer.vue.js +4 -0
  85. package/dist/components/LpDropdownMenu.vue.js +4 -0
  86. package/dist/components/LpEmptyState.vue.js +4 -0
  87. package/dist/components/LpFormField.vue.js +4 -0
  88. package/dist/components/LpIcon.vue.js +4 -0
  89. package/dist/components/LpInfraNode.vue.js +4 -0
  90. package/dist/components/LpInput.vue.js +4 -0
  91. package/dist/components/LpLaneNode.vue.js +4 -0
  92. package/dist/components/LpLink.vue.js +4 -0
  93. package/dist/components/LpLogViewer.vue.js +4 -0
  94. package/dist/components/LpModal.vue.js +4 -0
  95. package/dist/components/LpNotificationBell.vue.js +4 -0
  96. package/dist/components/LpNumberField.vue.js +4 -0
  97. package/dist/components/LpOtpInput.vue.js +4 -0
  98. package/dist/components/LpPagination.vue.js +4 -0
  99. package/dist/components/LpPasswordInput.vue.js +4 -0
  100. package/dist/components/LpPhoneInput.vue.js +4 -0
  101. package/dist/components/LpPopover.vue.js +4 -0
  102. package/dist/components/LpProgress.vue.js +4 -0
  103. package/dist/components/LpRadio.vue.js +4 -0
  104. package/dist/components/LpRadioGroup.vue.js +4 -0
  105. package/dist/components/LpScrollArea.vue.d.ts +2 -2
  106. package/dist/components/LpScrollArea.vue.js +4 -0
  107. package/dist/components/LpSegmented.vue.js +4 -0
  108. package/dist/components/LpSelect.vue.d.ts +2 -0
  109. package/dist/components/LpSelect.vue.js +4 -0
  110. package/dist/components/LpServiceNode.vue.js +4 -0
  111. package/dist/components/LpSidebar.vue.js +4 -0
  112. package/dist/components/LpSidebarNav.vue.js +4 -0
  113. package/dist/components/LpSkeleton.vue.js +4 -0
  114. package/dist/components/LpSlider.vue.js +4 -0
  115. package/dist/components/LpStat.vue.js +4 -0
  116. package/dist/components/LpStepper.vue.js +4 -0
  117. package/dist/components/LpSwitch.vue.js +4 -0
  118. package/dist/components/LpTable.vue.js +4 -0
  119. package/dist/components/LpTableOfContents.vue.js +4 -0
  120. package/dist/components/LpTabs.vue.d.ts +4 -2
  121. package/dist/components/LpTabs.vue.js +4 -0
  122. package/dist/components/LpTextarea.vue.js +4 -0
  123. package/dist/components/LpThemeSwitcher.vue.js +4 -0
  124. package/dist/components/LpTilt.vue.d.ts +31 -0
  125. package/dist/components/LpTilt.vue.js +4 -0
  126. package/dist/components/LpToaster.vue.js +4 -0
  127. package/dist/components/LpTooltip.vue.js +4 -0
  128. package/dist/components/LpTopologyCanvas.vue.js +4 -0
  129. package/dist/components/LpUptimeBar.vue.js +4 -0
  130. package/dist/components/codeHighlight.js +230 -0
  131. package/dist/{countries-data-9rgqJWHo.js → components/countries-data.js} +2 -2
  132. package/dist/components/countries.js +38 -0
  133. package/dist/components/dropdown.js +14 -0
  134. package/dist/components/sidebar.js +1 -0
  135. package/dist/composables/useInputFilter.js +44 -0
  136. package/dist/composables/usePillTransition.js +11 -0
  137. package/dist/composables/useTilt.d.ts +23 -0
  138. package/dist/composables/useTilt.js +57 -0
  139. package/dist/composables/useToast.d.ts +2 -0
  140. package/dist/composables/useToast.js +49 -0
  141. package/dist/index.d.ts +3 -0
  142. package/dist/index.js +197 -17596
  143. package/dist/layout/registry.js +10 -0
  144. package/dist/layout/tree.js +192 -0
  145. package/dist/layout/useLayout.js +61 -0
  146. package/package.json +5 -3
  147. package/src/component-names.ts +1 -0
  148. package/src/components/LpContextMenu.vue +14 -4
  149. package/src/components/LpDrawer.vue +29 -1
  150. package/src/components/LpLogViewer.vue +180 -31
  151. package/src/components/LpScrollArea.vue +20 -15
  152. package/src/components/LpSelect.vue +0 -0
  153. package/src/components/LpSidebar.vue +5 -2
  154. package/src/components/LpTabs.vue +19 -6
  155. package/src/components/LpThemeSwitcher.vue +13 -2
  156. package/src/components/LpTilt.vue +61 -0
  157. package/src/components/LpToaster.vue +11 -4
  158. package/src/composables/useTilt.ts +103 -0
  159. package/src/composables/useToast.ts +2 -0
  160. package/src/index.ts +3 -0
  161. package/src/nuxt.ts +10 -1
  162. package/dist/index.css +0 -1
@@ -0,0 +1,88 @@
1
+ import { defineComponent, computed, openBlock, createElementBlock, createElementVNode, createCommentVNode, createBlock, toDisplayString, normalizeClass, createVNode, createTextVNode } from "vue";
2
+ import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
3
+ const _hoisted_1 = { class: "flex flex-col gap-2" };
4
+ const _hoisted_2 = { class: "flex items-center gap-2" };
5
+ const _hoisted_3 = {
6
+ key: 0,
7
+ class: "size-1.5 rounded-full bg-action"
8
+ };
9
+ const _hoisted_4 = { class: "text-[11px] font-semibold uppercase tracking-[0.08em] text-muted" };
10
+ const _hoisted_5 = { class: "flex items-baseline gap-2" };
11
+ const _hoisted_6 = { class: "font-mono text-3xl font-medium tracking-tight text-ink" };
12
+ const _hoisted_7 = {
13
+ key: 0,
14
+ class: "text-sm text-muted"
15
+ };
16
+ const _sfc_main = /* @__PURE__ */ defineComponent({
17
+ __name: "LpStat",
18
+ props: {
19
+ label: {},
20
+ value: {},
21
+ hint: {},
22
+ icon: {},
23
+ online: { type: Boolean },
24
+ delta: {},
25
+ deltaSuffix: {},
26
+ trend: { default: "auto" },
27
+ invertTrend: { type: Boolean }
28
+ },
29
+ setup(__props) {
30
+ const props = __props;
31
+ const dir = computed(() => {
32
+ if (props.trend !== "auto") return props.trend;
33
+ if (typeof props.delta === "number") {
34
+ if (props.delta > 0) return "up";
35
+ if (props.delta < 0) return "down";
36
+ }
37
+ return "flat";
38
+ });
39
+ const trendIcon = computed(
40
+ () => dir.value === "up" ? "lucide:trending-up" : dir.value === "down" ? "lucide:trending-down" : "lucide:minus"
41
+ );
42
+ const trendColor = computed(() => {
43
+ if (dir.value === "flat") return "text-muted";
44
+ const good = props.invertTrend ? dir.value === "down" : dir.value === "up";
45
+ return good ? "text-action" : "text-danger";
46
+ });
47
+ const deltaText = computed(() => {
48
+ if (props.delta == null) return "";
49
+ if (typeof props.delta === "number") {
50
+ const sign = props.delta > 0 ? "+" : "";
51
+ return `${sign}${props.delta}${props.deltaSuffix ?? ""}`;
52
+ }
53
+ return props.delta;
54
+ });
55
+ const showDelta = computed(() => props.delta != null);
56
+ return (_ctx, _cache) => {
57
+ return openBlock(), createElementBlock("div", _hoisted_1, [
58
+ createElementVNode("div", _hoisted_2, [
59
+ __props.online ? (openBlock(), createElementBlock("span", _hoisted_3)) : createCommentVNode("", true),
60
+ __props.icon ? (openBlock(), createBlock(_sfc_main$1, {
61
+ key: 1,
62
+ name: __props.icon,
63
+ size: 14,
64
+ class: "text-muted"
65
+ }, null, 8, ["name"])) : createCommentVNode("", true),
66
+ createElementVNode("span", _hoisted_4, toDisplayString(__props.label), 1)
67
+ ]),
68
+ createElementVNode("div", _hoisted_5, [
69
+ createElementVNode("span", _hoisted_6, toDisplayString(__props.value), 1),
70
+ showDelta.value ? (openBlock(), createElementBlock("span", {
71
+ key: 0,
72
+ class: normalizeClass(["inline-flex items-center gap-0.5 text-sm font-medium tabular-nums", trendColor.value])
73
+ }, [
74
+ createVNode(_sfc_main$1, {
75
+ name: trendIcon.value,
76
+ size: 14
77
+ }, null, 8, ["name"]),
78
+ createTextVNode(" " + toDisplayString(deltaText.value), 1)
79
+ ], 2)) : createCommentVNode("", true)
80
+ ]),
81
+ __props.hint ? (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString(__props.hint), 1)) : createCommentVNode("", true)
82
+ ]);
83
+ };
84
+ }
85
+ });
86
+ export {
87
+ _sfc_main as _
88
+ };
@@ -0,0 +1,53 @@
1
+ import { defineComponent, openBlock, createElementBlock, Fragment, renderList, createElementVNode, normalizeClass, createBlock, createTextVNode, toDisplayString, createCommentVNode } from "vue";
2
+ import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
3
+ const _hoisted_1 = { class: "flex items-center gap-2" };
4
+ const _hoisted_2 = ["aria-current"];
5
+ const _hoisted_3 = { class: "flex items-center gap-2" };
6
+ const _hoisted_4 = { class: "sr-only" };
7
+ const _sfc_main = /* @__PURE__ */ defineComponent({
8
+ __name: "LpStepper",
9
+ props: {
10
+ steps: {},
11
+ current: {}
12
+ },
13
+ setup(__props) {
14
+ return (_ctx, _cache) => {
15
+ return openBlock(), createElementBlock("ol", _hoisted_1, [
16
+ (openBlock(true), createElementBlock(Fragment, null, renderList(__props.steps, (step, i) => {
17
+ return openBlock(), createElementBlock("li", {
18
+ key: i,
19
+ class: "flex flex-1 items-center gap-2 last:flex-none",
20
+ "aria-current": i === __props.current ? "step" : void 0
21
+ }, [
22
+ createElementVNode("div", _hoisted_3, [
23
+ createElementVNode("span", {
24
+ class: normalizeClass(["flex size-7 shrink-0 items-center justify-center rounded-pill border text-xs font-semibold transition-colors", i < __props.current ? "border-transparent bg-brand text-ink-inverse" : i === __props.current ? "border-brand text-brand" : "border-line text-muted"])
25
+ }, [
26
+ i < __props.current ? (openBlock(), createBlock(_sfc_main$1, {
27
+ key: 0,
28
+ name: "lucide:check",
29
+ size: 14
30
+ })) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
31
+ createTextVNode(toDisplayString(i + 1), 1)
32
+ ], 64))
33
+ ], 2),
34
+ createElementVNode("span", {
35
+ class: normalizeClass(["text-sm", i <= __props.current ? "text-ink" : "text-muted"])
36
+ }, [
37
+ createTextVNode(toDisplayString(step.label) + " ", 1),
38
+ createElementVNode("span", _hoisted_4, toDisplayString(i < __props.current ? "(completed)" : i === __props.current ? "(current step)" : ""), 1)
39
+ ], 2)
40
+ ]),
41
+ i < __props.steps.length - 1 ? (openBlock(), createElementBlock("span", {
42
+ key: 0,
43
+ class: normalizeClass(["h-px flex-1", i < __props.current ? "bg-brand" : "bg-line"])
44
+ }, null, 2)) : createCommentVNode("", true)
45
+ ], 8, _hoisted_2);
46
+ }), 128))
47
+ ]);
48
+ };
49
+ }
50
+ });
51
+ export {
52
+ _sfc_main as _
53
+ };
@@ -0,0 +1,28 @@
1
+ import { defineComponent, openBlock, createBlock, unref, withCtx, createVNode } from "vue";
2
+ import { SwitchRoot, SwitchThumb } from "reka-ui";
3
+ const _sfc_main = /* @__PURE__ */ defineComponent({
4
+ __name: "LpSwitch",
5
+ props: {
6
+ modelValue: { type: Boolean },
7
+ disabled: { type: Boolean }
8
+ },
9
+ emits: ["update:modelValue"],
10
+ setup(__props) {
11
+ return (_ctx, _cache) => {
12
+ return openBlock(), createBlock(unref(SwitchRoot), {
13
+ "model-value": __props.modelValue,
14
+ disabled: __props.disabled,
15
+ class: "group inline-flex h-5 w-9 shrink-0 items-center rounded-pill border border-line bg-surface-soft px-0.5 outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-transparent data-[state=checked]:bg-brand disabled:cursor-not-allowed disabled:opacity-55",
16
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = (v) => _ctx.$emit("update:modelValue", v))
17
+ }, {
18
+ default: withCtx(() => [
19
+ createVNode(unref(SwitchThumb), { class: "h-3.5 w-3.5 rounded-full bg-ink shadow transition-[translate,width,background-color] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] group-active:w-5 group-data-[state=checked]:translate-x-4 group-data-[state=checked]:bg-ink-inverse group-data-[state=checked]:group-active:-translate-x-1 motion-reduce:group-active:w-3.5" })
20
+ ]),
21
+ _: 1
22
+ }, 8, ["model-value", "disabled"]);
23
+ };
24
+ }
25
+ });
26
+ export {
27
+ _sfc_main as _
28
+ };
@@ -0,0 +1,229 @@
1
+ import { defineComponent, computed, ref, watch, onBeforeUnmount, openBlock, createBlock, withCtx, createElementVNode, normalizeClass, createElementBlock, createVNode, createCommentVNode, Fragment, renderList, normalizeStyle, createTextVNode, toDisplayString, withModifiers, renderSlot } from "vue";
2
+ import { _ as _sfc_main$2 } from "./LpCheckbox-BO0zuuPh.js";
3
+ import { _ as _sfc_main$4 } from "./LpContextMenu-D0mzXqA7.js";
4
+ import { _ as _sfc_main$3 } from "./LpIcon-CCnX5_2j.js";
5
+ import { _ as _sfc_main$1 } from "./LpScrollArea-K1x-azgY.js";
6
+ const _hoisted_1 = { class: "w-full border-collapse text-sm" };
7
+ const _hoisted_2 = { class: "border-b border-line bg-surface-soft" };
8
+ const _hoisted_3 = {
9
+ key: 0,
10
+ class: "w-px px-4 py-3"
11
+ };
12
+ const _hoisted_4 = ["aria-sort"];
13
+ const _hoisted_5 = ["onClick"];
14
+ const _hoisted_6 = { key: 0 };
15
+ const _hoisted_7 = ["colspan"];
16
+ const _hoisted_8 = ["onClick"];
17
+ const _sfc_main = /* @__PURE__ */ defineComponent({
18
+ __name: "LpTable",
19
+ props: {
20
+ columns: {},
21
+ rows: {},
22
+ rowKey: {},
23
+ emptyLabel: { default: "Nothing here yet" },
24
+ emptyIcon: { default: "lucide:inbox" },
25
+ selectable: { type: Boolean, default: false },
26
+ selected: { default: () => [] },
27
+ sort: { default: null },
28
+ stickyHeader: { type: Boolean },
29
+ rowMenu: {}
30
+ },
31
+ emits: ["update:selected", "update:sort", "rowClick"],
32
+ setup(__props, { emit: __emit }) {
33
+ const props = __props;
34
+ const emit = __emit;
35
+ function keyFor(row, index) {
36
+ if (typeof props.rowKey === "function") return props.rowKey(row);
37
+ if (props.rowKey) return row[props.rowKey];
38
+ return index;
39
+ }
40
+ function alignClass(align) {
41
+ if (align === "right") return "text-right";
42
+ if (align === "center") return "text-center";
43
+ return "text-left";
44
+ }
45
+ function menuFor(row) {
46
+ return props.rowMenu?.(row) ?? [];
47
+ }
48
+ const displayRows = computed(() => {
49
+ const s = props.sort;
50
+ if (!s) return props.rows;
51
+ const col = props.columns.find((c) => c.key === s.key);
52
+ if (!col?.sortable) return props.rows;
53
+ const factor = s.dir === "asc" ? 1 : -1;
54
+ return [...props.rows].sort((a, b) => compare(a[s.key], b[s.key]) * factor);
55
+ });
56
+ function compare(a, b) {
57
+ if (a == null && b == null) return 0;
58
+ if (a == null) return -1;
59
+ if (b == null) return 1;
60
+ if (typeof a === "number" && typeof b === "number") return a - b;
61
+ return String(a).localeCompare(String(b), void 0, { numeric: true });
62
+ }
63
+ function toggleSort(col) {
64
+ if (!col.sortable) return;
65
+ const s = props.sort;
66
+ let next;
67
+ if (!s || s.key !== col.key) next = { key: col.key, dir: "asc" };
68
+ else if (s.dir === "asc") next = { key: col.key, dir: "desc" };
69
+ else next = null;
70
+ emit("update:sort", next);
71
+ }
72
+ function sortIcon(col) {
73
+ if (props.sort?.key !== col.key) return "lucide:chevrons-up-down";
74
+ return props.sort.dir === "asc" ? "lucide:arrow-up" : "lucide:arrow-down";
75
+ }
76
+ const selectedSet = computed(() => new Set(props.selected));
77
+ const allKeys = computed(() => props.rows.map((r, i) => keyFor(r, i)));
78
+ const allChecked = computed(
79
+ () => allKeys.value.length > 0 && allKeys.value.every((k) => selectedSet.value.has(k))
80
+ );
81
+ const someChecked = computed(
82
+ () => !allChecked.value && allKeys.value.some((k) => selectedSet.value.has(k))
83
+ );
84
+ function toggleAll(checked) {
85
+ emit("update:selected", checked ? [...allKeys.value] : []);
86
+ }
87
+ function toggleRow(key, checked) {
88
+ const next = new Set(selectedSet.value);
89
+ if (checked) next.add(key);
90
+ else next.delete(key);
91
+ emit("update:selected", [...next]);
92
+ }
93
+ const colSpan = computed(() => props.columns.length + (props.selectable ? 1 : 0));
94
+ const headEl = ref(null);
95
+ const headHeight = ref(0);
96
+ let ro;
97
+ watch(
98
+ [headEl, () => props.stickyHeader],
99
+ ([el, sticky]) => {
100
+ ro?.disconnect();
101
+ if (!el || !sticky || typeof ResizeObserver === "undefined") {
102
+ headHeight.value = 0;
103
+ return;
104
+ }
105
+ ro = new ResizeObserver(() => {
106
+ headHeight.value = el.offsetHeight;
107
+ });
108
+ ro.observe(el);
109
+ headHeight.value = el.offsetHeight;
110
+ },
111
+ { immediate: true, flush: "post" }
112
+ );
113
+ onBeforeUnmount(() => ro?.disconnect());
114
+ const barInsetTop = computed(
115
+ () => props.stickyHeader && headHeight.value ? `${headHeight.value}px` : void 0
116
+ );
117
+ return (_ctx, _cache) => {
118
+ return openBlock(), createBlock(_sfc_main$1, {
119
+ class: "rounded-card border border-line",
120
+ "bar-inset-top": barInsetTop.value
121
+ }, {
122
+ default: withCtx(() => [
123
+ createElementVNode("table", _hoisted_1, [
124
+ createElementVNode("thead", {
125
+ ref_key: "headEl",
126
+ ref: headEl,
127
+ class: normalizeClass(__props.stickyHeader ? "sticky top-0 z-10" : "")
128
+ }, [
129
+ createElementVNode("tr", _hoisted_2, [
130
+ __props.selectable ? (openBlock(), createElementBlock("th", _hoisted_3, [
131
+ createVNode(_sfc_main$2, {
132
+ "model-value": allChecked.value,
133
+ indeterminate: someChecked.value,
134
+ "onUpdate:modelValue": toggleAll
135
+ }, null, 8, ["model-value", "indeterminate"])
136
+ ])) : createCommentVNode("", true),
137
+ (openBlock(true), createElementBlock(Fragment, null, renderList(__props.columns, (col) => {
138
+ return openBlock(), createElementBlock("th", {
139
+ key: col.key,
140
+ style: normalizeStyle(col.width ? { width: col.width } : void 0),
141
+ class: normalizeClass(["px-4 py-3 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted", alignClass(col.align)]),
142
+ "aria-sort": __props.sort?.key === col.key ? __props.sort.dir === "asc" ? "ascending" : "descending" : void 0
143
+ }, [
144
+ col.sortable ? (openBlock(), createElementBlock("button", {
145
+ key: 0,
146
+ type: "button",
147
+ class: normalizeClass(["group/sort -mx-1 inline-flex items-center gap-1 rounded px-1 outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring", [
148
+ col.align === "right" ? "flex-row-reverse" : "",
149
+ __props.sort?.key === col.key ? "text-ink" : ""
150
+ ]]),
151
+ onClick: ($event) => toggleSort(col)
152
+ }, [
153
+ createTextVNode(toDisplayString(col.label) + " ", 1),
154
+ createVNode(_sfc_main$3, {
155
+ name: sortIcon(col),
156
+ size: 13,
157
+ class: normalizeClass(["shrink-0 transition-[transform,opacity] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)]", __props.sort?.key === col.key ? "opacity-100" : "opacity-40 group-hover/sort:opacity-70"])
158
+ }, null, 8, ["name", "class"])
159
+ ], 10, _hoisted_5)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
160
+ createTextVNode(toDisplayString(col.label), 1)
161
+ ], 64))
162
+ ], 14, _hoisted_4);
163
+ }), 128))
164
+ ])
165
+ ], 2),
166
+ createElementVNode("tbody", null, [
167
+ displayRows.value.length === 0 ? (openBlock(), createElementBlock("tr", _hoisted_6, [
168
+ createElementVNode("td", {
169
+ colspan: colSpan.value,
170
+ class: "px-4 py-10 text-center text-muted"
171
+ }, [
172
+ createVNode(_sfc_main$3, {
173
+ name: __props.emptyIcon,
174
+ size: 22,
175
+ class: "mx-auto mb-2 opacity-60"
176
+ }, null, 8, ["name"]),
177
+ createElementVNode("div", null, toDisplayString(__props.emptyLabel), 1)
178
+ ], 8, _hoisted_7)
179
+ ])) : createCommentVNode("", true),
180
+ (openBlock(true), createElementBlock(Fragment, null, renderList(displayRows.value, (row, index) => {
181
+ return openBlock(), createBlock(_sfc_main$4, {
182
+ key: keyFor(row, index),
183
+ items: menuFor(row)
184
+ }, {
185
+ default: withCtx(() => [
186
+ createElementVNode("tr", {
187
+ class: normalizeClass(["border-b border-line/60 transition-colors last:border-0 hover:bg-surface-soft/60", selectedSet.value.has(keyFor(row, index)) ? "bg-brand-soft/40" : ""]),
188
+ onClick: ($event) => emit("rowClick", row)
189
+ }, [
190
+ __props.selectable ? (openBlock(), createElementBlock("td", {
191
+ key: 0,
192
+ class: "w-px px-4 py-3",
193
+ onClick: _cache[0] || (_cache[0] = withModifiers(() => {
194
+ }, ["stop"]))
195
+ }, [
196
+ createVNode(_sfc_main$2, {
197
+ "model-value": selectedSet.value.has(keyFor(row, index)),
198
+ "onUpdate:modelValue": (v) => toggleRow(keyFor(row, index), v)
199
+ }, null, 8, ["model-value", "onUpdate:modelValue"])
200
+ ])) : createCommentVNode("", true),
201
+ (openBlock(true), createElementBlock(Fragment, null, renderList(__props.columns, (col) => {
202
+ return openBlock(), createElementBlock("td", {
203
+ key: col.key,
204
+ class: normalizeClass(["px-4 py-3 text-ink", alignClass(col.align)])
205
+ }, [
206
+ renderSlot(_ctx.$slots, `cell-${col.key}`, {
207
+ row,
208
+ value: row[col.key]
209
+ }, () => [
210
+ createTextVNode(toDisplayString(row[col.key]), 1)
211
+ ])
212
+ ], 2);
213
+ }), 128))
214
+ ], 10, _hoisted_8)
215
+ ]),
216
+ _: 2
217
+ }, 1032, ["items"]);
218
+ }), 128))
219
+ ])
220
+ ])
221
+ ]),
222
+ _: 3
223
+ }, 8, ["bar-inset-top"]);
224
+ };
225
+ }
226
+ });
227
+ export {
228
+ _sfc_main as _
229
+ };
@@ -0,0 +1,155 @@
1
+ import { defineComponent, ref, useId, computed, onMounted, watch, onBeforeUnmount, openBlock, createElementBlock, createElementVNode, toDisplayString, Fragment, renderList, normalizeClass, createBlock, unref, createCommentVNode, createTextVNode } from "vue";
2
+ import { Motion } from "motion-v";
3
+ import { usePillTransition } from "../composables/usePillTransition.js";
4
+ const _hoisted_1 = ["aria-label"];
5
+ const _hoisted_2 = { class: "mb-2.5 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted" };
6
+ const _hoisted_3 = ["href", "onClick", "onMouseenter"];
7
+ const _hoisted_4 = {
8
+ key: 0,
9
+ class: "flex flex-col gap-0.5"
10
+ };
11
+ const _hoisted_5 = ["href", "onClick", "onMouseenter"];
12
+ const _sfc_main = /* @__PURE__ */ defineComponent({
13
+ __name: "LpTableOfContents",
14
+ props: {
15
+ links: {},
16
+ title: { default: "On this page" },
17
+ offset: { default: 96 }
18
+ },
19
+ setup(__props) {
20
+ const props = __props;
21
+ const activeId = ref("");
22
+ const hoveredId = ref(null);
23
+ const pillId = `lp-toc-${useId()}`;
24
+ const pillTransition = usePillTransition();
25
+ const litId = computed(() => hoveredId.value ?? activeId.value);
26
+ function flatIds(links) {
27
+ return links.flatMap((l) => [l.id, ...l.children ? flatIds(l.children) : []]);
28
+ }
29
+ let observer = null;
30
+ let spySuppressed = false;
31
+ let rafId = null;
32
+ let safetyTimer = null;
33
+ function setup() {
34
+ observer?.disconnect();
35
+ const ids = flatIds(props.links);
36
+ if (!ids.length) return;
37
+ const visible = /* @__PURE__ */ new Set();
38
+ observer = new IntersectionObserver(
39
+ (entries) => {
40
+ for (const e of entries) {
41
+ if (e.isIntersecting) visible.add(e.target.id);
42
+ else visible.delete(e.target.id);
43
+ }
44
+ if (spySuppressed) return;
45
+ const firstVisible = ids.find((id) => visible.has(id));
46
+ if (firstVisible) activeId.value = firstVisible;
47
+ },
48
+ { rootMargin: `-${props.offset}px 0px -70% 0px`, threshold: 0 }
49
+ );
50
+ for (const id of ids) {
51
+ const el = document.getElementById(id);
52
+ if (el) observer.observe(el);
53
+ }
54
+ if (!activeId.value) activeId.value = ids[0] ?? "";
55
+ }
56
+ function unfreeze() {
57
+ spySuppressed = false;
58
+ if (rafId != null) cancelAnimationFrame(rafId);
59
+ if (safetyTimer != null) clearTimeout(safetyTimer);
60
+ rafId = null;
61
+ safetyTimer = null;
62
+ }
63
+ function suppressSpyUntilScrollEnds() {
64
+ spySuppressed = true;
65
+ if (rafId != null) cancelAnimationFrame(rafId);
66
+ if (safetyTimer != null) clearTimeout(safetyTimer);
67
+ let last = window.scrollY;
68
+ const tick = () => {
69
+ const y = window.scrollY;
70
+ if (y === last) {
71
+ unfreeze();
72
+ return;
73
+ }
74
+ last = y;
75
+ rafId = requestAnimationFrame(tick);
76
+ };
77
+ rafId = requestAnimationFrame(tick);
78
+ safetyTimer = setTimeout(unfreeze, 1e3);
79
+ }
80
+ function onClick(id, ev) {
81
+ const el = document.getElementById(id);
82
+ if (!el) return;
83
+ ev.preventDefault();
84
+ activeId.value = id;
85
+ suppressSpyUntilScrollEnds();
86
+ el.scrollIntoView({ behavior: "smooth", block: "start" });
87
+ history.replaceState(null, "", `#${id}`);
88
+ }
89
+ onMounted(setup);
90
+ watch(() => props.links, setup, { deep: true });
91
+ onBeforeUnmount(() => {
92
+ observer?.disconnect();
93
+ unfreeze();
94
+ });
95
+ return (_ctx, _cache) => {
96
+ return openBlock(), createElementBlock("nav", {
97
+ "aria-label": __props.title,
98
+ class: "text-sm"
99
+ }, [
100
+ createElementVNode("p", _hoisted_2, toDisplayString(__props.title), 1),
101
+ createElementVNode("ul", {
102
+ class: "relative flex flex-col gap-0.5 border-l border-line",
103
+ onMouseleave: _cache[0] || (_cache[0] = ($event) => hoveredId.value = null)
104
+ }, [
105
+ (openBlock(true), createElementBlock(Fragment, null, renderList(__props.links, (link) => {
106
+ return openBlock(), createElementBlock("li", {
107
+ key: link.id,
108
+ class: "flex flex-col"
109
+ }, [
110
+ createElementVNode("a", {
111
+ href: `#${link.id}`,
112
+ class: normalizeClass(["relative -ml-px block py-1 pl-3.5 leading-snug transition-colors duration-[var(--duration-fast)]", litId.value === link.id ? "text-brand" : "text-muted hover:text-ink"]),
113
+ onClick: ($event) => onClick(link.id, $event),
114
+ onMouseenter: ($event) => hoveredId.value = link.id
115
+ }, [
116
+ litId.value === link.id ? (openBlock(), createBlock(unref(Motion), {
117
+ key: 0,
118
+ "layout-id": pillId,
119
+ transition: unref(pillTransition),
120
+ class: "absolute -left-px top-0 h-full w-0.5 rounded-full bg-brand"
121
+ }, null, 8, ["transition"])) : createCommentVNode("", true),
122
+ createTextVNode(" " + toDisplayString(link.text), 1)
123
+ ], 42, _hoisted_3),
124
+ link.children?.length ? (openBlock(), createElementBlock("ul", _hoisted_4, [
125
+ (openBlock(true), createElementBlock(Fragment, null, renderList(link.children, (child) => {
126
+ return openBlock(), createElementBlock("li", {
127
+ key: child.id
128
+ }, [
129
+ createElementVNode("a", {
130
+ href: `#${child.id}`,
131
+ class: normalizeClass(["relative -ml-px block py-1 pl-7 text-[13px] leading-snug transition-colors duration-[var(--duration-fast)]", litId.value === child.id ? "text-brand" : "text-muted hover:text-ink"]),
132
+ onClick: ($event) => onClick(child.id, $event),
133
+ onMouseenter: ($event) => hoveredId.value = child.id
134
+ }, [
135
+ litId.value === child.id ? (openBlock(), createBlock(unref(Motion), {
136
+ key: 0,
137
+ "layout-id": pillId,
138
+ transition: unref(pillTransition),
139
+ class: "absolute -left-px top-0 h-full w-0.5 rounded-full bg-brand"
140
+ }, null, 8, ["transition"])) : createCommentVNode("", true),
141
+ createTextVNode(" " + toDisplayString(child.text), 1)
142
+ ], 42, _hoisted_5)
143
+ ]);
144
+ }), 128))
145
+ ])) : createCommentVNode("", true)
146
+ ]);
147
+ }), 128))
148
+ ], 32)
149
+ ], 8, _hoisted_1);
150
+ };
151
+ }
152
+ });
153
+ export {
154
+ _sfc_main as _
155
+ };
@@ -0,0 +1,115 @@
1
+ import { defineComponent, useId, ref, computed, openBlock, createBlock, unref, withCtx, createVNode, normalizeClass, createElementBlock, Fragment, renderList, createCommentVNode, createElementVNode, toDisplayString, renderSlot } from "vue";
2
+ import { useReducedMotion, Motion } from "motion-v";
3
+ import { TabsRoot, TabsList, TabsTrigger, TabsContent } from "reka-ui";
4
+ import { usePillTransition } from "../composables/usePillTransition.js";
5
+ import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
6
+ const _hoisted_1 = { class: "relative z-10" };
7
+ const _sfc_main = /* @__PURE__ */ defineComponent({
8
+ __name: "LpTabs",
9
+ props: {
10
+ modelValue: {},
11
+ items: {},
12
+ variant: { default: "contained" },
13
+ accent: { type: Boolean, default: false },
14
+ block: { type: Boolean, default: false }
15
+ },
16
+ emits: ["update:modelValue"],
17
+ setup(__props) {
18
+ const pillId = `lp-tab-indicator-${useId()}`;
19
+ const hovered = ref(null);
20
+ const pillTransition = usePillTransition();
21
+ const reduceMotion = useReducedMotion();
22
+ const panelLayoutTransition = computed(
23
+ () => reduceMotion.value ? { duration: 0 } : { type: "spring", stiffness: 520, damping: 42, mass: 0.9 }
24
+ );
25
+ function pillUnder(value, active) {
26
+ return hovered.value ? hovered.value === value : active === value;
27
+ }
28
+ return (_ctx, _cache) => {
29
+ return openBlock(), createBlock(unref(TabsRoot), {
30
+ "model-value": __props.modelValue,
31
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = (v) => _ctx.$emit("update:modelValue", v))
32
+ }, {
33
+ default: withCtx(() => [
34
+ createVNode(unref(TabsList), {
35
+ class: normalizeClass(["gap-1", [
36
+ __props.block ? "flex w-full" : "inline-flex",
37
+ __props.variant === "contained" ? "rounded-control border border-line bg-surface-soft p-1" : "",
38
+ __props.variant === "underline" ? "border-b border-line" : ""
39
+ ]]),
40
+ onPointerleave: _cache[0] || (_cache[0] = ($event) => hovered.value = null)
41
+ }, {
42
+ default: withCtx(() => [
43
+ (openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
44
+ return openBlock(), createBlock(unref(TabsTrigger), {
45
+ key: item.value,
46
+ value: item.value,
47
+ class: normalizeClass(["relative inline-flex items-center justify-center gap-1.5 text-sm outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring", [
48
+ __props.block ? "flex-1" : "",
49
+ __props.variant === "underline" ? "rounded-t-md px-3 pt-1.5 pb-2" : "rounded-md px-3 py-1.5",
50
+ __props.accent ? "data-[state=active]:text-brand" : "data-[state=active]:text-ink",
51
+ hovered.value === item.value ? "text-ink" : "text-muted"
52
+ ]]),
53
+ onPointerenter: ($event) => hovered.value = item.value
54
+ }, {
55
+ default: withCtx(() => [
56
+ pillUnder(item.value, __props.modelValue) ? (openBlock(), createBlock(unref(Motion), {
57
+ key: 0,
58
+ "layout-id": pillId,
59
+ transition: unref(pillTransition),
60
+ class: normalizeClass(__props.variant === "underline" ? [
61
+ "absolute inset-x-0 -bottom-px z-0 h-0.5 rounded-full",
62
+ __props.accent ? "bg-brand" : "bg-ink"
63
+ ] : [
64
+ "absolute inset-0 z-0 rounded-md shadow-sm",
65
+ __props.accent ? "border border-brand/35 bg-brand/12" : "border border-line bg-surface-raised"
66
+ ])
67
+ }, null, 8, ["transition", "class"])) : createCommentVNode("", true),
68
+ item.icon ? (openBlock(), createBlock(_sfc_main$1, {
69
+ key: 1,
70
+ name: item.icon,
71
+ size: 14,
72
+ class: "relative z-10"
73
+ }, null, 8, ["name"])) : createCommentVNode("", true),
74
+ createElementVNode("span", _hoisted_1, toDisplayString(item.label), 1)
75
+ ]),
76
+ _: 2
77
+ }, 1032, ["value", "class", "onPointerenter"]);
78
+ }), 128))
79
+ ]),
80
+ _: 1
81
+ }, 8, ["class"]),
82
+ _ctx.$slots.panel ? (openBlock(), createBlock(unref(Motion), {
83
+ key: 0,
84
+ layout: true,
85
+ transition: panelLayoutTransition.value,
86
+ class: "mt-3 overflow-hidden"
87
+ }, {
88
+ default: withCtx(() => [
89
+ (openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
90
+ return openBlock(), createBlock(unref(TabsContent), {
91
+ key: item.value,
92
+ value: item.value,
93
+ class: "outline-none data-[state=active]:animate-[tab-in_200ms_var(--ease-emphasized)]"
94
+ }, {
95
+ default: withCtx(() => [
96
+ renderSlot(_ctx.$slots, "panel", {
97
+ value: item.value
98
+ })
99
+ ]),
100
+ _: 2
101
+ }, 1032, ["value"]);
102
+ }), 128))
103
+ ]),
104
+ _: 3
105
+ }, 8, ["transition"])) : createCommentVNode("", true),
106
+ renderSlot(_ctx.$slots, "default")
107
+ ]),
108
+ _: 3
109
+ }, 8, ["model-value"]);
110
+ };
111
+ }
112
+ });
113
+ export {
114
+ _sfc_main as _
115
+ };