@leavepulse/ui 0.23.0 → 0.24.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.
Files changed (28) hide show
  1. package/dist/chunks/{LpAvatar-D-jHoHR7.js → LpAvatar-CzCE5cXg.js} +17 -5
  2. package/dist/chunks/LpBreadcrumbs-5uvViCXi.js +60 -0
  3. package/dist/chunks/LpEmptyState-BV3q7Qyi.js +49 -0
  4. package/dist/chunks/{LpFileTree-CN0IOIY7.js → LpFileTree-nU1wRRxZ.js} +1 -1
  5. package/dist/chunks/{LpLink-DU9kkKGW.js → LpLink-Bu01SZzB.js} +1 -1
  6. package/dist/chunks/{LpNotificationBell-a08cl0yY.js → LpNotificationBell-X1M30e0X.js} +1 -1
  7. package/dist/chunks/{LpStepper-5dFTrW21.js → LpStepper-Wtl-6nUh.js} +27 -10
  8. package/dist/components/LpAvatar.vue.d.ts +7 -3
  9. package/dist/components/LpAvatar.vue.js +1 -1
  10. package/dist/components/LpBreadcrumbs.vue.d.ts +13 -1
  11. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  12. package/dist/components/LpEmptyState.vue.d.ts +5 -0
  13. package/dist/components/LpEmptyState.vue.js +1 -1
  14. package/dist/components/LpFileTree.vue.js +1 -1
  15. package/dist/components/LpLink.vue.d.ts +4 -2
  16. package/dist/components/LpLink.vue.js +1 -1
  17. package/dist/components/LpNotificationBell.vue.js +1 -1
  18. package/dist/components/LpStepper.vue.d.ts +15 -1
  19. package/dist/components/LpStepper.vue.js +1 -1
  20. package/dist/index.js +7 -7
  21. package/package.json +1 -1
  22. package/src/components/LpAvatar.vue +18 -2
  23. package/src/components/LpBreadcrumbs.vue +30 -7
  24. package/src/components/LpEmptyState.vue +18 -5
  25. package/src/components/LpLink.vue +8 -2
  26. package/src/components/LpStepper.vue +49 -9
  27. package/dist/chunks/LpBreadcrumbs-B7Aqatzw.js +0 -42
  28. package/dist/chunks/LpEmptyState-CrfyRJUG.js +0 -47
@@ -11,6 +11,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
11
11
  alt: {},
12
12
  fallback: {},
13
13
  size: { default: "md" },
14
+ pixelSize: {},
14
15
  shape: { default: "circle" },
15
16
  ring: { type: Boolean, default: false },
16
17
  menuItems: {}
@@ -20,9 +21,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
20
21
  base: "inline-flex shrink-0 select-none items-center justify-center overflow-hidden bg-surface-soft font-medium text-muted-strong",
21
22
  variants: {
22
23
  size: {
24
+ xs: "size-5 text-[10px]",
23
25
  sm: "size-7 text-xs",
24
26
  md: "size-9 text-sm",
25
- lg: "size-12 text-base"
27
+ lg: "size-12 text-base",
28
+ xl: "size-16 text-lg"
26
29
  },
27
30
  // People read as circles; things (servers, projects, orgs) read as
28
31
  // rounded squares — the same distinction platform icons make.
@@ -36,6 +39,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
36
39
  defaultVariants: { size: "md", shape: "circle", ring: false }
37
40
  });
38
41
  const props = __props;
42
+ const sizeStyle = computed(
43
+ () => props.pixelSize ? {
44
+ width: `${props.pixelSize}px`,
45
+ height: `${props.pixelSize}px`,
46
+ fontSize: `${Math.max(10, Math.round(props.pixelSize * 0.4))}px`
47
+ } : void 0
48
+ );
39
49
  const initials = computed(
40
50
  () => props.fallback ?? props.alt?.slice(0, 2).toUpperCase() ?? "?"
41
51
  );
@@ -50,7 +60,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
50
60
  }, {
51
61
  default: withCtx(() => [
52
62
  createVNode(unref(AvatarRoot), mergeProps({
53
- class: unref(avatar)({ size: __props.size, shape: __props.shape, ring: __props.ring })
63
+ class: unref(avatar)({ size: __props.pixelSize ? void 0 : __props.size, shape: __props.shape, ring: __props.ring }),
64
+ style: sizeStyle.value
54
65
  }, _ctx.$attrs), {
55
66
  default: withCtx(() => [
56
67
  __props.src && mounted.value ? (openBlock(), createBlock(unref(AvatarImage), {
@@ -67,12 +78,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
67
78
  })
68
79
  ]),
69
80
  _: 1
70
- }, 16, ["class"])
81
+ }, 16, ["class", "style"])
71
82
  ]),
72
83
  _: 1
73
84
  }, 8, ["items"])) : (openBlock(), createBlock(unref(AvatarRoot), mergeProps({
74
85
  key: 1,
75
- class: unref(avatar)({ size: __props.size, shape: __props.shape, ring: __props.ring })
86
+ class: unref(avatar)({ size: __props.pixelSize ? void 0 : __props.size, shape: __props.shape, ring: __props.ring }),
87
+ style: sizeStyle.value
76
88
  }, _ctx.$attrs), {
77
89
  default: withCtx(() => [
78
90
  __props.src ? (openBlock(), createBlock(unref(AvatarImage), {
@@ -89,7 +101,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
89
101
  })
90
102
  ]),
91
103
  _: 1
92
- }, 16, ["class"]));
104
+ }, 16, ["class", "style"]));
93
105
  };
94
106
  }
95
107
  });
@@ -0,0 +1,60 @@
1
+ import { defineComponent, openBlock, createElementBlock, Fragment, renderList, createBlock, createCommentVNode, resolveDynamicComponent, withCtx, createTextVNode, toDisplayString, normalizeClass } from "vue";
2
+ import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
3
+ const _hoisted_1 = {
4
+ class: "flex items-center gap-1.5 text-sm",
5
+ "aria-label": "Breadcrumb"
6
+ };
7
+ const _sfc_main = /* @__PURE__ */ defineComponent({
8
+ __name: "LpBreadcrumbs",
9
+ props: {
10
+ items: {},
11
+ as: { default: "a" }
12
+ },
13
+ emits: ["navigate"],
14
+ setup(__props) {
15
+ return (_ctx, _cache) => {
16
+ return openBlock(), createElementBlock("nav", _hoisted_1, [
17
+ (openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item, i) => {
18
+ return openBlock(), createElementBlock(Fragment, { key: i }, [
19
+ i > 0 ? (openBlock(), createBlock(_sfc_main$1, {
20
+ key: 0,
21
+ name: "lucide:chevron-right",
22
+ size: 14,
23
+ class: "text-muted"
24
+ })) : createCommentVNode("", true),
25
+ (item.href || item.to) && i < __props.items.length - 1 ? (openBlock(), createBlock(resolveDynamicComponent(__props.as), {
26
+ key: 1,
27
+ href: __props.as === "a" ? item.href : void 0,
28
+ to: __props.as === "a" ? void 0 : item.to,
29
+ class: "inline-flex items-center gap-1.5 rounded-xs text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring",
30
+ onClick: ($event) => _ctx.$emit("navigate", item, i)
31
+ }, {
32
+ default: withCtx(() => [
33
+ item.icon ? (openBlock(), createBlock(_sfc_main$1, {
34
+ key: 0,
35
+ name: item.icon,
36
+ size: 14
37
+ }, null, 8, ["name"])) : createCommentVNode("", true),
38
+ createTextVNode(" " + toDisplayString(item.label), 1)
39
+ ]),
40
+ _: 2
41
+ }, 1032, ["href", "to", "onClick"])) : (openBlock(), createElementBlock("span", {
42
+ key: 2,
43
+ class: normalizeClass(["inline-flex items-center gap-1.5", i === __props.items.length - 1 ? "text-ink" : "text-muted"])
44
+ }, [
45
+ item.icon ? (openBlock(), createBlock(_sfc_main$1, {
46
+ key: 0,
47
+ name: item.icon,
48
+ size: 14
49
+ }, null, 8, ["name"])) : createCommentVNode("", true),
50
+ createTextVNode(" " + toDisplayString(item.label), 1)
51
+ ], 2))
52
+ ], 64);
53
+ }), 128))
54
+ ]);
55
+ };
56
+ }
57
+ });
58
+ export {
59
+ _sfc_main as _
60
+ };
@@ -0,0 +1,49 @@
1
+ import { defineComponent, openBlock, createElementBlock, normalizeClass, createVNode, createCommentVNode, createElementVNode, toDisplayString, renderSlot } from "vue";
2
+ import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
3
+ const _hoisted_1 = { class: "flex flex-col gap-1" };
4
+ const _hoisted_2 = {
5
+ key: 0,
6
+ class: "max-w-sm text-sm text-muted"
7
+ };
8
+ const _hoisted_3 = {
9
+ key: 1,
10
+ class: "mt-1"
11
+ };
12
+ const _sfc_main = /* @__PURE__ */ defineComponent({
13
+ __name: "LpEmptyState",
14
+ props: {
15
+ icon: {},
16
+ title: {},
17
+ description: {},
18
+ compact: { type: Boolean }
19
+ },
20
+ setup(__props) {
21
+ return (_ctx, _cache) => {
22
+ return openBlock(), createElementBlock("div", {
23
+ class: normalizeClass(["flex flex-col items-center px-6 text-center", __props.compact ? "gap-2 py-6" : "gap-3 py-12"])
24
+ }, [
25
+ __props.icon ? (openBlock(), createElementBlock("div", {
26
+ key: 0,
27
+ class: normalizeClass(["flex items-center justify-center rounded-pill bg-surface-soft text-muted", __props.compact ? "size-9" : "size-12"])
28
+ }, [
29
+ createVNode(_sfc_main$1, {
30
+ name: __props.icon,
31
+ size: __props.compact ? 16 : 22
32
+ }, null, 8, ["name", "size"])
33
+ ], 2)) : createCommentVNode("", true),
34
+ createElementVNode("div", _hoisted_1, [
35
+ createElementVNode("p", {
36
+ class: normalizeClass(["font-semibold text-ink", __props.compact ? "text-sm" : ""])
37
+ }, toDisplayString(__props.title), 3),
38
+ __props.description ? (openBlock(), createElementBlock("p", _hoisted_2, toDisplayString(__props.description), 1)) : createCommentVNode("", true)
39
+ ]),
40
+ _ctx.$slots.default ? (openBlock(), createElementBlock("div", _hoisted_3, [
41
+ renderSlot(_ctx.$slots, "default")
42
+ ])) : createCommentVNode("", true)
43
+ ], 2);
44
+ };
45
+ }
46
+ });
47
+ export {
48
+ _sfc_main as _
49
+ };
@@ -1,6 +1,6 @@
1
1
  import { defineComponent, ref, watch, computed, openBlock, createElementBlock, Fragment, renderList, normalizeStyle, createBlock, createVNode, withCtx, createElementVNode, renderSlot, mergeProps, normalizeProps, guardReactiveProps, createTextVNode, toDisplayString, createCommentVNode } from "vue";
2
2
  import { computeStats, formatSize, sortNodes, subtreeIds, ancestorIds } from "../components/fileTree.js";
3
- import { _ as _sfc_main$1 } from "./LpEmptyState-CrfyRJUG.js";
3
+ import { _ as _sfc_main$1 } from "./LpEmptyState-BV3q7Qyi.js";
4
4
  import { _ as _sfc_main$3 } from "./LpFileTreeNode-D7WgUPcJ.js";
5
5
  import { _ as _sfc_main$2 } from "./LpScrollArea-D8xyKXcF.js";
6
6
  const _hoisted_1 = {
@@ -35,7 +35,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
35
35
  rel: rel.value,
36
36
  class: normalizeClass(["inline-flex items-center gap-1 underline-offset-2 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring", [
37
37
  colorClass.value,
38
- __props.variant === "reveal" ? revealClass : "hover:underline"
38
+ __props.variant === "reveal" ? revealClass : __props.variant === "underline" ? "underline decoration-current/40 hover:decoration-current" : "hover:underline"
39
39
  ]])
40
40
  }, {
41
41
  default: withCtx(() => [
@@ -2,7 +2,7 @@ import { defineComponent, computed, useModel, openBlock, createBlock, withCtx, c
2
2
  import { _ as _sfc_main$8 } from "./LpBadge-BPodVcJa.js";
3
3
  import { _ as _sfc_main$7 } from "./LpButton-5VXXCjwF.js";
4
4
  import { _ as _sfc_main$5 } from "./LpContextMenu-D0mzXqA7.js";
5
- import { _ as _sfc_main$3 } from "./LpEmptyState-CrfyRJUG.js";
5
+ import { _ as _sfc_main$3 } from "./LpEmptyState-BV3q7Qyi.js";
6
6
  import { _ as _sfc_main$6 } from "./LpIcon-CCnX5_2j.js";
7
7
  import { _ as _sfc_main$1 } from "./LpPopover-8268UmlC.js";
8
8
  import { _ as _sfc_main$4 } from "./LpScrollArea-D8xyKXcF.js";
@@ -1,4 +1,4 @@
1
- import { defineComponent, openBlock, createElementBlock, Fragment, renderList, createElementVNode, normalizeClass, createBlock, createTextVNode, toDisplayString, createCommentVNode } from "vue";
1
+ import { defineComponent, computed, openBlock, createElementBlock, Fragment, renderList, createElementVNode, normalizeClass, createBlock, createTextVNode, toDisplayString, createCommentVNode } from "vue";
2
2
  import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
3
3
  const _hoisted_1 = { class: "flex items-center gap-2" };
4
4
  const _hoisted_2 = ["aria-current"];
@@ -8,39 +8,56 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
8
8
  __name: "LpStepper",
9
9
  props: {
10
10
  steps: {},
11
- current: {}
11
+ current: {},
12
+ completed: {}
12
13
  },
13
14
  setup(__props) {
15
+ const props = __props;
16
+ const currentIndex = computed(() => {
17
+ if (typeof props.current === "number") return props.current;
18
+ return props.steps.findIndex((s) => s.key === props.current);
19
+ });
20
+ function isCompleted(step, i) {
21
+ if (!props.completed) return i < currentIndex.value;
22
+ return props.completed.includes(i) || !!step.key && props.completed.includes(step.key);
23
+ }
24
+ function isCurrent(i) {
25
+ return i === currentIndex.value;
26
+ }
14
27
  return (_ctx, _cache) => {
15
28
  return openBlock(), createElementBlock("ol", _hoisted_1, [
16
29
  (openBlock(true), createElementBlock(Fragment, null, renderList(__props.steps, (step, i) => {
17
30
  return openBlock(), createElementBlock("li", {
18
- key: i,
31
+ key: step.key ?? i,
19
32
  class: "flex flex-1 items-center gap-2 last:flex-none",
20
- "aria-current": i === __props.current ? "step" : void 0
33
+ "aria-current": isCurrent(i) ? "step" : void 0
21
34
  }, [
22
35
  createElementVNode("div", _hoisted_3, [
23
36
  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"])
37
+ class: normalizeClass(["flex size-7 shrink-0 items-center justify-center rounded-pill border text-xs font-semibold transition-colors", isCompleted(step, i) ? "border-transparent bg-brand text-ink-inverse" : isCurrent(i) ? "border-brand text-brand" : "border-line text-muted"])
25
38
  }, [
26
- i < __props.current ? (openBlock(), createBlock(_sfc_main$1, {
39
+ isCompleted(step, i) ? (openBlock(), createBlock(_sfc_main$1, {
27
40
  key: 0,
28
41
  name: "lucide:check",
29
42
  size: 14
30
- })) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
43
+ })) : step.icon ? (openBlock(), createBlock(_sfc_main$1, {
44
+ key: 1,
45
+ name: step.icon,
46
+ size: 14
47
+ }, null, 8, ["name"])) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
31
48
  createTextVNode(toDisplayString(i + 1), 1)
32
49
  ], 64))
33
50
  ], 2),
34
51
  createElementVNode("span", {
35
- class: normalizeClass(["text-sm", i <= __props.current ? "text-ink" : "text-muted"])
52
+ class: normalizeClass(["text-sm", isCompleted(step, i) || isCurrent(i) ? "text-ink" : "text-muted"])
36
53
  }, [
37
54
  createTextVNode(toDisplayString(step.label) + " ", 1),
38
- createElementVNode("span", _hoisted_4, toDisplayString(i < __props.current ? "(completed)" : i === __props.current ? "(current step)" : ""), 1)
55
+ createElementVNode("span", _hoisted_4, toDisplayString(isCompleted(step, i) ? "(completed)" : isCurrent(i) ? "(current step)" : ""), 1)
39
56
  ], 2)
40
57
  ]),
41
58
  i < __props.steps.length - 1 ? (openBlock(), createElementBlock("span", {
42
59
  key: 0,
43
- class: normalizeClass(["h-px flex-1", i < __props.current ? "bg-brand" : "bg-line"])
60
+ class: normalizeClass(["h-px flex-1", isCompleted(step, i) ? "bg-brand" : "bg-line"])
44
61
  }, null, 2)) : createCommentVNode("", true)
45
62
  ], 8, _hoisted_2);
46
63
  }), 128))
@@ -5,7 +5,9 @@ declare const __VLS_export: import('vue').DefineComponent<{
5
5
  src?: string;
6
6
  alt?: string;
7
7
  fallback?: string;
8
- size?: "sm" | "md" | "lg" | undefined;
8
+ size?: "sm" | "md" | "lg" | "xs" | "xl" | undefined;
9
+ /** Explicit pixel size, wins over `size` — for avatars keyed to a layout. */
10
+ pixelSize?: number;
9
11
  /** Silhouette: circle for people, soft/square for servers and projects. */
10
12
  shape?: "circle" | "square" | "soft" | undefined;
11
13
  /** Draws a hairline ring, for avatars laid over imagery or other avatars. */
@@ -16,7 +18,9 @@ declare const __VLS_export: import('vue').DefineComponent<{
16
18
  src?: string;
17
19
  alt?: string;
18
20
  fallback?: string;
19
- size?: "sm" | "md" | "lg" | undefined;
21
+ size?: "sm" | "md" | "lg" | "xs" | "xl" | undefined;
22
+ /** Explicit pixel size, wins over `size` — for avatars keyed to a layout. */
23
+ pixelSize?: number;
20
24
  /** Silhouette: circle for people, soft/square for servers and projects. */
21
25
  shape?: "circle" | "square" | "soft" | undefined;
22
26
  /** Draws a hairline ring, for avatars laid over imagery or other avatars. */
@@ -24,7 +28,7 @@ declare const __VLS_export: import('vue').DefineComponent<{
24
28
  /** Right-click menu (e.g. account quick actions). Consumer-supplied. */
25
29
  menuItems?: ContextMenuItemDef[];
26
30
  }> & Readonly<{}>, {
27
- size: "sm" | "md" | "lg";
31
+ size: "sm" | "md" | "lg" | "xs" | "xl";
28
32
  shape: "circle" | "square" | "soft";
29
33
  ring: boolean;
30
34
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpAvatar-D-jHoHR7.js";
1
+ import { _ as _sfc_main } from "../chunks/LpAvatar-CzCE5cXg.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,14 +1,26 @@
1
+ import { Component } from 'vue';
1
2
  export interface Crumb {
2
3
  label: string;
3
4
  href?: string;
5
+ /** Destination for router links; passed straight to the `as` component. */
6
+ to?: string | Record<string, unknown>;
7
+ /** Leading icon, for crumbs that carry a glyph (a home root, a section). */
8
+ icon?: string;
4
9
  }
5
10
  type __VLS_Props = {
6
11
  items: Crumb[];
12
+ /**
13
+ * Root element/component for linked crumbs. Defaults to a native <a>;
14
+ * pass a router link component to keep client-side navigation.
15
+ */
16
+ as?: string | Component;
7
17
  };
8
18
  declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
9
19
  navigate: (item: Crumb, index: number) => any;
10
20
  }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
11
21
  onNavigate?: ((item: Crumb, index: number) => any) | undefined;
12
- }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
22
+ }>, {
23
+ as: string | Component;
24
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
13
25
  declare const _default: typeof __VLS_export;
14
26
  export default _default;
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpBreadcrumbs-B7Aqatzw.js";
1
+ import { _ as _sfc_main } from "../chunks/LpBreadcrumbs-5uvViCXi.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -2,6 +2,11 @@ type __VLS_Props = {
2
2
  icon?: string;
3
3
  title: string;
4
4
  description?: string;
5
+ /**
6
+ * Tightens the block for empty states nested inside a card or a panel,
7
+ * where the full-page padding would dwarf its container.
8
+ */
9
+ compact?: boolean;
5
10
  };
6
11
  declare var __VLS_6: {};
7
12
  type __VLS_Slots = {} & {
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpEmptyState-CrfyRJUG.js";
1
+ import { _ as _sfc_main } from "../chunks/LpEmptyState-BV3q7Qyi.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpFileTree-CN0IOIY7.js";
1
+ import { _ as _sfc_main } from "../chunks/LpFileTree-nU1wRRxZ.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -21,11 +21,13 @@ type __VLS_Props = {
21
21
  as?: string | Component;
22
22
  /**
23
23
  * "default" — plain hover underline.
24
+ * "underline" — underlined at rest; for links inside running prose,
25
+ * where hover-only underlines leave the link invisible until pointed at.
24
26
  * "reveal" — an underline bar that grows in from the left on hover
25
27
  * (origin-left scale-x 0→1). The LeavePulse landing "monitoring" link feel;
26
28
  * good for inline prose links and nav items that want a bit of motion.
27
29
  */
28
- variant?: "default" | "reveal";
30
+ variant?: "default" | "underline" | "reveal";
29
31
  };
30
32
  declare var __VLS_8: {};
31
33
  type __VLS_Slots = {} & {
@@ -33,7 +35,7 @@ type __VLS_Slots = {} & {
33
35
  };
34
36
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
35
37
  as: string | Component;
36
- variant: "default" | "reveal";
38
+ variant: "default" | "underline" | "reveal";
37
39
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
38
40
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
39
41
  declare const _default: typeof __VLS_export;
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpLink-DU9kkKGW.js";
1
+ import { _ as _sfc_main } from "../chunks/LpLink-Bu01SZzB.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpNotificationBell-a08cl0yY.js";
1
+ import { _ as _sfc_main } from "../chunks/LpNotificationBell-X1M30e0X.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,9 +1,23 @@
1
1
  export interface Step {
2
2
  label: string;
3
+ /** Stable id, for flows whose steps are addressed by name rather than order. */
4
+ key?: string;
5
+ /** Replaces the step number while the step is pending or current. */
6
+ icon?: string;
3
7
  }
4
8
  type __VLS_Props = {
5
9
  steps: Step[];
6
- current: number;
10
+ /**
11
+ * The active step: an index, or a step `key` for keyed flows.
12
+ */
13
+ current: number | string;
14
+ /**
15
+ * Which steps count as done. Omit it and everything before `current` is
16
+ * complete, which is the linear case. Pass keys (or indices) when a flow can
17
+ * finish steps out of order — a verification that succeeds before its
18
+ * prerequisite is filled in, say.
19
+ */
20
+ completed?: Array<string | number>;
7
21
  };
8
22
  declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
9
23
  declare const _default: typeof __VLS_export;
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpStepper-5dFTrW21.js";
1
+ import { _ as _sfc_main } from "../chunks/LpStepper-Wtl-6nUh.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
package/dist/index.js CHANGED
@@ -5,9 +5,9 @@ import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, projec
5
5
  import { _ as _4 } from "./chunks/LpAlert-BAYHDSS1.js";
6
6
  import { _ as _5 } from "./chunks/LpAppShell-CaQYQKZV.js";
7
7
  import { _ as _6 } from "./chunks/LpAutocomplete-DExlIZe_.js";
8
- import { _ as _7 } from "./chunks/LpAvatar-D-jHoHR7.js";
8
+ import { _ as _7 } from "./chunks/LpAvatar-CzCE5cXg.js";
9
9
  import { _ as _8 } from "./chunks/LpBadge-BPodVcJa.js";
10
- import { _ as _9 } from "./chunks/LpBreadcrumbs-B7Aqatzw.js";
10
+ import { _ as _9 } from "./chunks/LpBreadcrumbs-5uvViCXi.js";
11
11
  import { _ as _10 } from "./chunks/LpButton-5VXXCjwF.js";
12
12
  import { _ as _11 } from "./chunks/LpCalendar-B7ZL0ty3.js";
13
13
  import { _ as _12 } from "./chunks/LpCard-BmrTvWOe.js";
@@ -21,8 +21,8 @@ import { _ as _18 } from "./chunks/LpDisclosure-DP6yfgfM.js";
21
21
  import { _ as _19 } from "./chunks/LpDivider-CUX46KR0.js";
22
22
  import { _ as _20 } from "./chunks/LpDrawer-DAJN5rGK.js";
23
23
  import { _ as _21 } from "./chunks/LpDropdownMenu-DJpoqRCb.js";
24
- import { _ as _22 } from "./chunks/LpEmptyState-CrfyRJUG.js";
25
- import { _ as _23 } from "./chunks/LpFileTree-CN0IOIY7.js";
24
+ import { _ as _22 } from "./chunks/LpEmptyState-BV3q7Qyi.js";
25
+ import { _ as _23 } from "./chunks/LpFileTree-nU1wRRxZ.js";
26
26
  import { ancestorIds, checkStateOf, checkedCount, computeStats, fileIcon, formatModified, formatSize, sortNodes, subtreeIds, subtreeSize } from "./components/fileTree.js";
27
27
  import { _ as _24 } from "./chunks/LpFormField-CesaPWPO.js";
28
28
  import { _ as _25 } from "./chunks/LpIcon-CCnX5_2j.js";
@@ -30,10 +30,10 @@ import { _ as _26 } from "./chunks/LpInfraNode-qIL3l7IO.js";
30
30
  import { _ as _27 } from "./chunks/LpInput-jwqXlAy0.js";
31
31
  import { _ as _28 } from "./chunks/LpLightbox-Ci35Y_xP.js";
32
32
  import { fileNameOf } from "./components/lightbox.js";
33
- import { _ as _29 } from "./chunks/LpLink-DU9kkKGW.js";
33
+ import { _ as _29 } from "./chunks/LpLink-Bu01SZzB.js";
34
34
  import { _ as _30 } from "./chunks/LpLogViewer-B1aLUVdz.js";
35
35
  import { _ as _31 } from "./chunks/LpModal-D_o-56To.js";
36
- import { _ as _32 } from "./chunks/LpNotificationBell-a08cl0yY.js";
36
+ import { _ as _32 } from "./chunks/LpNotificationBell-X1M30e0X.js";
37
37
  import { _ as _33 } from "./chunks/LpNumberField-B3JuQFol.js";
38
38
  import { _ as _34 } from "./chunks/LpOtpInput-DqaT0Z75.js";
39
39
  import { _ as _35 } from "./chunks/LpPagination-CeGuNe_q.js";
@@ -54,7 +54,7 @@ import { _ as _47 } from "./chunks/LpSidebar-D3GGLSE7.js";
54
54
  import { _ as _48 } from "./chunks/LpSkeleton-eSFIo4kD.js";
55
55
  import { _ as _49 } from "./chunks/LpSlider-B1SF3pRV.js";
56
56
  import { _ as _50 } from "./chunks/LpStat-CdbEFOvn.js";
57
- import { _ as _51 } from "./chunks/LpStepper-5dFTrW21.js";
57
+ import { _ as _51 } from "./chunks/LpStepper-Wtl-6nUh.js";
58
58
  import { _ as _52 } from "./chunks/LpSwitch-v6fnccSM.js";
59
59
  import { _ as _53 } from "./chunks/LpTable-DTj52qlg.js";
60
60
  import { _ as _54 } from "./chunks/LpTableOfContents-CjvxPMpU.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -15,9 +15,11 @@ const avatar = tv({
15
15
  base: "inline-flex shrink-0 select-none items-center justify-center overflow-hidden bg-surface-soft font-medium text-muted-strong",
16
16
  variants: {
17
17
  size: {
18
+ xs: "size-5 text-[10px]",
18
19
  sm: "size-7 text-xs",
19
20
  md: "size-9 text-sm",
20
21
  lg: "size-12 text-base",
22
+ xl: "size-16 text-lg",
21
23
  },
22
24
  // People read as circles; things (servers, projects, orgs) read as
23
25
  // rounded squares — the same distinction platform icons make.
@@ -39,6 +41,8 @@ const props = withDefaults(
39
41
  alt?: string
40
42
  fallback?: string
41
43
  size?: AvatarVariants["size"]
44
+ /** Explicit pixel size, wins over `size` — for avatars keyed to a layout. */
45
+ pixelSize?: number
42
46
  /** Silhouette: circle for people, soft/square for servers and projects. */
43
47
  shape?: AvatarVariants["shape"]
44
48
  /** Draws a hairline ring, for avatars laid over imagery or other avatars. */
@@ -49,6 +53,18 @@ const props = withDefaults(
49
53
  { size: "md", shape: "circle", ring: false },
50
54
  )
51
55
 
56
+ // A pixel size drives both box and glyph, so the initials keep their
57
+ // proportion instead of staying at the preset's step.
58
+ const sizeStyle = computed(() =>
59
+ props.pixelSize
60
+ ? {
61
+ width: `${props.pixelSize}px`,
62
+ height: `${props.pixelSize}px`,
63
+ fontSize: `${Math.max(10, Math.round(props.pixelSize * 0.4))}px`,
64
+ }
65
+ : undefined,
66
+ )
67
+
52
68
  const initials = computed(
53
69
  () => props.fallback ?? props.alt?.slice(0, 2).toUpperCase() ?? "?",
54
70
  )
@@ -67,12 +83,12 @@ onMounted(() => {
67
83
 
68
84
  <template>
69
85
  <LpContextMenu v-if="menuItems?.length" :items="menuItems">
70
- <AvatarRoot :class="avatar({ size, shape, ring })" v-bind="$attrs">
86
+ <AvatarRoot :class="avatar({ size: pixelSize ? undefined : size, shape, ring })" :style="sizeStyle" v-bind="$attrs">
71
87
  <AvatarImage v-if="src && mounted" :src="src" :alt="alt" class="size-full object-cover" />
72
88
  <AvatarFallback>{{ initials }}</AvatarFallback>
73
89
  </AvatarRoot>
74
90
  </LpContextMenu>
75
- <AvatarRoot v-else :class="avatar({ size, shape, ring })" v-bind="$attrs">
91
+ <AvatarRoot v-else :class="avatar({ size: pixelSize ? undefined : size, shape, ring })" :style="sizeStyle" v-bind="$attrs">
76
92
  <AvatarImage v-if="src" :src="src" :alt="alt" class="size-full object-cover" />
77
93
  <AvatarFallback>{{ initials }}</AvatarFallback>
78
94
  </AvatarRoot>
@@ -1,12 +1,27 @@
1
1
  <script setup lang="ts">
2
+ import type { Component } from "vue"
2
3
  import LpIcon from "./LpIcon.vue"
3
4
 
4
5
  export interface Crumb {
5
6
  label: string
6
7
  href?: string
8
+ /** Destination for router links; passed straight to the `as` component. */
9
+ to?: string | Record<string, unknown>
10
+ /** Leading icon, for crumbs that carry a glyph (a home root, a section). */
11
+ icon?: string
7
12
  }
8
13
 
9
- defineProps<{ items: Crumb[] }>()
14
+ withDefaults(
15
+ defineProps<{
16
+ items: Crumb[]
17
+ /**
18
+ * Root element/component for linked crumbs. Defaults to a native <a>;
19
+ * pass a router link component to keep client-side navigation.
20
+ */
21
+ as?: string | Component
22
+ }>(),
23
+ { as: "a" },
24
+ )
10
25
  defineEmits<{ (e: "navigate", item: Crumb, index: number): void }>()
11
26
  </script>
12
27
 
@@ -19,15 +34,23 @@ defineEmits<{ (e: "navigate", item: Crumb, index: number): void }>()
19
34
  :size="14"
20
35
  class="text-muted"
21
36
  />
22
- <a
23
- v-if="item.href && i < items.length - 1"
24
- :href="item.href"
25
- class="rounded-xs text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
37
+ <component
38
+ :is="as"
39
+ v-if="(item.href || item.to) && i < items.length - 1"
40
+ :href="as === 'a' ? item.href : undefined"
41
+ :to="as === 'a' ? undefined : item.to"
42
+ class="inline-flex items-center gap-1.5 rounded-xs text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
26
43
  @click="$emit('navigate', item, i)"
27
44
  >
45
+ <LpIcon v-if="item.icon" :name="item.icon" :size="14" />
28
46
  {{ item.label }}
29
- </a>
30
- <span v-else :class="i === items.length - 1 ? 'text-ink' : 'text-muted'">
47
+ </component>
48
+ <span
49
+ v-else
50
+ class="inline-flex items-center gap-1.5"
51
+ :class="i === items.length - 1 ? 'text-ink' : 'text-muted'"
52
+ >
53
+ <LpIcon v-if="item.icon" :name="item.icon" :size="14" />
31
54
  {{ item.label }}
32
55
  </span>
33
56
  </template>
@@ -1,19 +1,32 @@
1
1
  <script setup lang="ts">
2
2
  import LpIcon from "./LpIcon.vue"
3
3
 
4
- defineProps<{ icon?: string; title: string; description?: string }>()
4
+ defineProps<{
5
+ icon?: string
6
+ title: string
7
+ description?: string
8
+ /**
9
+ * Tightens the block for empty states nested inside a card or a panel,
10
+ * where the full-page padding would dwarf its container.
11
+ */
12
+ compact?: boolean
13
+ }>()
5
14
  </script>
6
15
 
7
16
  <template>
8
- <div class="flex flex-col items-center gap-3 px-6 py-12 text-center">
17
+ <div
18
+ class="flex flex-col items-center px-6 text-center"
19
+ :class="compact ? 'gap-2 py-6' : 'gap-3 py-12'"
20
+ >
9
21
  <div
10
22
  v-if="icon"
11
- class="flex size-12 items-center justify-center rounded-pill bg-surface-soft text-muted"
23
+ class="flex items-center justify-center rounded-pill bg-surface-soft text-muted"
24
+ :class="compact ? 'size-9' : 'size-12'"
12
25
  >
13
- <LpIcon :name="icon" :size="22" />
26
+ <LpIcon :name="icon" :size="compact ? 16 : 22" />
14
27
  </div>
15
28
  <div class="flex flex-col gap-1">
16
- <p class="font-semibold text-ink">{{ title }}</p>
29
+ <p class="font-semibold text-ink" :class="compact ? 'text-sm' : ''">{{ title }}</p>
17
30
  <p v-if="description" class="max-w-sm text-sm text-muted">{{ description }}</p>
18
31
  </div>
19
32
  <div v-if="$slots.default" class="mt-1"><slot /></div>
@@ -24,11 +24,13 @@ const props = withDefaults(
24
24
  as?: string | Component
25
25
  /**
26
26
  * "default" — plain hover underline.
27
+ * "underline" — underlined at rest; for links inside running prose,
28
+ * where hover-only underlines leave the link invisible until pointed at.
27
29
  * "reveal" — an underline bar that grows in from the left on hover
28
30
  * (origin-left scale-x 0→1). The LeavePulse landing "monitoring" link feel;
29
31
  * good for inline prose links and nav items that want a bit of motion.
30
32
  */
31
- variant?: "default" | "reveal"
33
+ variant?: "default" | "underline" | "reveal"
32
34
  }>(),
33
35
  { variant: "default", as: "a" },
34
36
  )
@@ -72,7 +74,11 @@ const revealClass =
72
74
  class="inline-flex items-center gap-1 underline-offset-2 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
73
75
  :class="[
74
76
  colorClass,
75
- variant === 'reveal' ? revealClass : 'hover:underline',
77
+ variant === 'reveal'
78
+ ? revealClass
79
+ : variant === 'underline'
80
+ ? 'underline decoration-current/40 hover:decoration-current'
81
+ : 'hover:underline',
76
82
  ]"
77
83
  >
78
84
  <slot />
@@ -1,45 +1,85 @@
1
1
  <script setup lang="ts">
2
+ import { computed } from "vue"
2
3
  import LpIcon from "./LpIcon.vue"
3
4
 
4
5
  export interface Step {
5
6
  label: string
7
+ /** Stable id, for flows whose steps are addressed by name rather than order. */
8
+ key?: string
9
+ /** Replaces the step number while the step is pending or current. */
10
+ icon?: string
6
11
  }
7
12
 
8
- defineProps<{ steps: Step[]; current: number }>()
13
+ const props = defineProps<{
14
+ steps: Step[]
15
+ /**
16
+ * The active step: an index, or a step `key` for keyed flows.
17
+ */
18
+ current: number | string
19
+ /**
20
+ * Which steps count as done. Omit it and everything before `current` is
21
+ * complete, which is the linear case. Pass keys (or indices) when a flow can
22
+ * finish steps out of order — a verification that succeeds before its
23
+ * prerequisite is filled in, say.
24
+ */
25
+ completed?: Array<string | number>
26
+ }>()
27
+
28
+ const currentIndex = computed(() => {
29
+ if (typeof props.current === "number") return props.current
30
+ return props.steps.findIndex((s) => s.key === props.current)
31
+ })
32
+
33
+ function isCompleted(step: Step, i: number): boolean {
34
+ if (!props.completed) return i < currentIndex.value
35
+ return props.completed.includes(i) || (!!step.key && props.completed.includes(step.key))
36
+ }
37
+
38
+ function isCurrent(i: number): boolean {
39
+ return i === currentIndex.value
40
+ }
9
41
  </script>
10
42
 
11
43
  <template>
12
44
  <ol class="flex items-center gap-2">
13
45
  <li
14
46
  v-for="(step, i) in steps"
15
- :key="i"
47
+ :key="step.key ?? i"
16
48
  class="flex flex-1 items-center gap-2 last:flex-none"
17
- :aria-current="i === current ? 'step' : undefined"
49
+ :aria-current="isCurrent(i) ? 'step' : undefined"
18
50
  >
19
51
  <div class="flex items-center gap-2">
20
52
  <span
21
53
  class="flex size-7 shrink-0 items-center justify-center rounded-pill border text-xs font-semibold transition-colors"
22
- :class="i < current
54
+ :class="isCompleted(step, i)
23
55
  ? 'border-transparent bg-brand text-ink-inverse'
24
- : i === current
56
+ : isCurrent(i)
25
57
  ? 'border-brand text-brand'
26
58
  : 'border-line text-muted'"
27
59
  >
28
- <LpIcon v-if="i < current" name="lucide:check" :size="14" />
60
+ <LpIcon v-if="isCompleted(step, i)" name="lucide:check" :size="14" />
61
+ <LpIcon v-else-if="step.icon" :name="step.icon" :size="14" />
29
62
  <template v-else>{{ i + 1 }}</template>
30
63
  </span>
31
- <span class="text-sm" :class="i <= current ? 'text-ink' : 'text-muted'">
64
+ <span
65
+ class="text-sm"
66
+ :class="isCompleted(step, i) || isCurrent(i) ? 'text-ink' : 'text-muted'"
67
+ >
32
68
  {{ step.label }}
33
69
  <!-- Status for screen readers; the colour/check are visual only. -->
34
70
  <span class="sr-only">{{
35
- i < current ? "(completed)" : i === current ? "(current step)" : ""
71
+ isCompleted(step, i)
72
+ ? "(completed)"
73
+ : isCurrent(i)
74
+ ? "(current step)"
75
+ : ""
36
76
  }}</span>
37
77
  </span>
38
78
  </div>
39
79
  <span
40
80
  v-if="i < steps.length - 1"
41
81
  class="h-px flex-1"
42
- :class="i < current ? 'bg-brand' : 'bg-line'"
82
+ :class="isCompleted(step, i) ? 'bg-brand' : 'bg-line'"
43
83
  />
44
84
  </li>
45
85
  </ol>
@@ -1,42 +0,0 @@
1
- import { defineComponent, openBlock, createElementBlock, Fragment, renderList, createBlock, createCommentVNode, toDisplayString, normalizeClass } from "vue";
2
- import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
3
- const _hoisted_1 = {
4
- class: "flex items-center gap-1.5 text-sm",
5
- "aria-label": "Breadcrumb"
6
- };
7
- const _hoisted_2 = ["href", "onClick"];
8
- const _sfc_main = /* @__PURE__ */ defineComponent({
9
- __name: "LpBreadcrumbs",
10
- props: {
11
- items: {}
12
- },
13
- emits: ["navigate"],
14
- setup(__props) {
15
- return (_ctx, _cache) => {
16
- return openBlock(), createElementBlock("nav", _hoisted_1, [
17
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item, i) => {
18
- return openBlock(), createElementBlock(Fragment, { key: i }, [
19
- i > 0 ? (openBlock(), createBlock(_sfc_main$1, {
20
- key: 0,
21
- name: "lucide:chevron-right",
22
- size: 14,
23
- class: "text-muted"
24
- })) : createCommentVNode("", true),
25
- item.href && i < __props.items.length - 1 ? (openBlock(), createElementBlock("a", {
26
- key: 1,
27
- href: item.href,
28
- class: "rounded-xs text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring",
29
- onClick: ($event) => _ctx.$emit("navigate", item, i)
30
- }, toDisplayString(item.label), 9, _hoisted_2)) : (openBlock(), createElementBlock("span", {
31
- key: 2,
32
- class: normalizeClass(i === __props.items.length - 1 ? "text-ink" : "text-muted")
33
- }, toDisplayString(item.label), 3))
34
- ], 64);
35
- }), 128))
36
- ]);
37
- };
38
- }
39
- });
40
- export {
41
- _sfc_main as _
42
- };
@@ -1,47 +0,0 @@
1
- import { defineComponent, openBlock, createElementBlock, createVNode, createCommentVNode, createElementVNode, toDisplayString, renderSlot } from "vue";
2
- import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
3
- const _hoisted_1 = { class: "flex flex-col items-center gap-3 px-6 py-12 text-center" };
4
- const _hoisted_2 = {
5
- key: 0,
6
- class: "flex size-12 items-center justify-center rounded-pill bg-surface-soft text-muted"
7
- };
8
- const _hoisted_3 = { class: "flex flex-col gap-1" };
9
- const _hoisted_4 = { class: "font-semibold text-ink" };
10
- const _hoisted_5 = {
11
- key: 0,
12
- class: "max-w-sm text-sm text-muted"
13
- };
14
- const _hoisted_6 = {
15
- key: 1,
16
- class: "mt-1"
17
- };
18
- const _sfc_main = /* @__PURE__ */ defineComponent({
19
- __name: "LpEmptyState",
20
- props: {
21
- icon: {},
22
- title: {},
23
- description: {}
24
- },
25
- setup(__props) {
26
- return (_ctx, _cache) => {
27
- return openBlock(), createElementBlock("div", _hoisted_1, [
28
- __props.icon ? (openBlock(), createElementBlock("div", _hoisted_2, [
29
- createVNode(_sfc_main$1, {
30
- name: __props.icon,
31
- size: 22
32
- }, null, 8, ["name"])
33
- ])) : createCommentVNode("", true),
34
- createElementVNode("div", _hoisted_3, [
35
- createElementVNode("p", _hoisted_4, toDisplayString(__props.title), 1),
36
- __props.description ? (openBlock(), createElementBlock("p", _hoisted_5, toDisplayString(__props.description), 1)) : createCommentVNode("", true)
37
- ]),
38
- _ctx.$slots.default ? (openBlock(), createElementBlock("div", _hoisted_6, [
39
- renderSlot(_ctx.$slots, "default")
40
- ])) : createCommentVNode("", true)
41
- ]);
42
- };
43
- }
44
- });
45
- export {
46
- _sfc_main as _
47
- };