@leavepulse/ui 0.22.22 → 0.23.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 (39) hide show
  1. package/dist/chunks/{LpAddressInput-peTpNSJK.js → LpAddressInput-BTqnUXZG.js} +1 -1
  2. package/dist/chunks/{LpAvatar-B1Ez0ZAx.js → LpAvatar-D-jHoHR7.js} +15 -5
  3. package/dist/chunks/{LpConfirmDialog-DOU_zaTI.js → LpConfirmDialog-CObRah9c.js} +10 -5
  4. package/dist/chunks/{LpFileTree-Cw50eG_y.js → LpFileTree-CN0IOIY7.js} +1 -1
  5. package/dist/chunks/{LpFileTreeNode-DFg4PhOK.js → LpFileTreeNode-D7WgUPcJ.js} +23 -21
  6. package/dist/chunks/{LpInput-BfLIWV-P.js → LpInput-jwqXlAy0.js} +5 -2
  7. package/dist/chunks/{LpLink-DHwOEp4e.js → LpLink-DU9kkKGW.js} +25 -10
  8. package/dist/chunks/{LpPasswordInput-BfjDrvZq.js → LpPasswordInput-Bmo6q9Kd.js} +11 -3
  9. package/dist/chunks/{LpTextarea-BDl7PR9U.js → LpTextarea-1ENb3ZAO.js} +6 -3
  10. package/dist/chunks/{LpThemeSwitcher-B_kj0T2b.js → LpThemeSwitcher-BgpJgrnP.js} +27 -0
  11. package/dist/components/LpAddressInput.vue.js +1 -1
  12. package/dist/components/LpAvatar.vue.d.ts +10 -0
  13. package/dist/components/LpAvatar.vue.js +1 -1
  14. package/dist/components/LpConfirmDialog.vue.d.ts +8 -0
  15. package/dist/components/LpConfirmDialog.vue.js +1 -1
  16. package/dist/components/LpFileTree.vue.js +1 -1
  17. package/dist/components/LpFileTreeNode.vue.js +1 -1
  18. package/dist/components/LpInput.vue.d.ts +12 -0
  19. package/dist/components/LpInput.vue.js +1 -1
  20. package/dist/components/LpLink.vue.d.ts +20 -2
  21. package/dist/components/LpLink.vue.js +1 -1
  22. package/dist/components/LpPasswordInput.vue.d.ts +8 -0
  23. package/dist/components/LpPasswordInput.vue.js +1 -1
  24. package/dist/components/LpTextarea.vue.d.ts +5 -0
  25. package/dist/components/LpTextarea.vue.js +1 -1
  26. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  27. package/dist/index.js +10 -10
  28. package/dist/theme/tokens.d.ts +3 -0
  29. package/package.json +1 -1
  30. package/src/components/LpAvatar.vue +17 -5
  31. package/src/components/LpConfirmDialog.vue +21 -6
  32. package/src/components/LpFileTreeNode.vue +11 -2
  33. package/src/components/LpInput.vue +8 -0
  34. package/src/components/LpLink.vue +40 -8
  35. package/src/components/LpPasswordInput.vue +16 -1
  36. package/src/components/LpTextarea.vue +7 -0
  37. package/src/theme/presets.ts +24 -0
  38. package/src/theme/tokens.ts +6 -0
  39. package/src/tokens/tokens.css +3 -0
@@ -5,7 +5,7 @@ import { _ as _sfc_main$2 } from "./LpAutocomplete-DExlIZe_.js";
5
5
  import { _ as _sfc_main$3 } from "./LpButton-5VXXCjwF.js";
6
6
  import { _ as _sfc_main$1 } from "./LpFormField-CesaPWPO.js";
7
7
  import { _ as _sfc_main$4 } from "./LpIcon-CCnX5_2j.js";
8
- import { _ as _sfc_main$5 } from "./LpInput-BfLIWV-P.js";
8
+ import { _ as _sfc_main$5 } from "./LpInput-jwqXlAy0.js";
9
9
  import { _ as _sfc_main$7 } from "./LpModal-D_o-56To.js";
10
10
  import { _ as _sfc_main$6 } from "./LpSelect-Cw8-gL5F.js";
11
11
  const _hoisted_1 = { class: "flex flex-col gap-3" };
@@ -11,19 +11,29 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
11
11
  alt: {},
12
12
  fallback: {},
13
13
  size: { default: "md" },
14
+ shape: { default: "circle" },
15
+ ring: { type: Boolean, default: false },
14
16
  menuItems: {}
15
17
  },
16
18
  setup(__props) {
17
19
  const avatar = tv({
18
- base: "inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-pill bg-surface-soft font-medium text-muted-strong",
20
+ base: "inline-flex shrink-0 select-none items-center justify-center overflow-hidden bg-surface-soft font-medium text-muted-strong",
19
21
  variants: {
20
22
  size: {
21
23
  sm: "size-7 text-xs",
22
24
  md: "size-9 text-sm",
23
25
  lg: "size-12 text-base"
24
- }
26
+ },
27
+ // People read as circles; things (servers, projects, orgs) read as
28
+ // rounded squares — the same distinction platform icons make.
29
+ shape: {
30
+ circle: "rounded-pill",
31
+ soft: "rounded-card",
32
+ square: "rounded-control"
33
+ },
34
+ ring: { true: "ring-2 ring-line", false: "" }
25
35
  },
26
- defaultVariants: { size: "md" }
36
+ defaultVariants: { size: "md", shape: "circle", ring: false }
27
37
  });
28
38
  const props = __props;
29
39
  const initials = computed(
@@ -40,7 +50,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
40
50
  }, {
41
51
  default: withCtx(() => [
42
52
  createVNode(unref(AvatarRoot), mergeProps({
43
- class: unref(avatar)({ size: __props.size })
53
+ class: unref(avatar)({ size: __props.size, shape: __props.shape, ring: __props.ring })
44
54
  }, _ctx.$attrs), {
45
55
  default: withCtx(() => [
46
56
  __props.src && mounted.value ? (openBlock(), createBlock(unref(AvatarImage), {
@@ -62,7 +72,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
62
72
  _: 1
63
73
  }, 8, ["items"])) : (openBlock(), createBlock(unref(AvatarRoot), mergeProps({
64
74
  key: 1,
65
- class: unref(avatar)({ size: __props.size })
75
+ class: unref(avatar)({ size: __props.size, shape: __props.shape, ring: __props.ring })
66
76
  }, _ctx.$attrs), {
67
77
  default: withCtx(() => [
68
78
  __props.src ? (openBlock(), createBlock(unref(AvatarImage), {
@@ -9,14 +9,17 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
9
9
  description: {},
10
10
  confirmLabel: { default: "Confirm" },
11
11
  cancelLabel: { default: "Cancel" },
12
- danger: { type: Boolean }
12
+ danger: { type: Boolean },
13
+ loading: { type: Boolean },
14
+ loadingLabel: { default: "Working…" }
13
15
  },
14
16
  emits: ["update:open", "confirm", "cancel"],
15
17
  setup(__props, { emit: __emit }) {
18
+ const props = __props;
16
19
  const emit = __emit;
17
20
  function confirm() {
18
21
  emit("confirm");
19
- emit("update:open", false);
22
+ if (!props.loading) emit("update:open", false);
20
23
  }
21
24
  function cancel() {
22
25
  emit("cancel");
@@ -32,22 +35,24 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
32
35
  footer: withCtx(() => [
33
36
  createVNode(_sfc_main$2, {
34
37
  variant: "ghost",
38
+ disabled: __props.loading,
35
39
  onClick: cancel
36
40
  }, {
37
41
  default: withCtx(() => [
38
42
  createTextVNode(toDisplayString(__props.cancelLabel), 1)
39
43
  ]),
40
44
  _: 1
41
- }),
45
+ }, 8, ["disabled"]),
42
46
  createVNode(_sfc_main$2, {
43
47
  variant: __props.danger ? "danger" : "solid",
48
+ disabled: __props.loading,
44
49
  onClick: confirm
45
50
  }, {
46
51
  default: withCtx(() => [
47
- createTextVNode(toDisplayString(__props.confirmLabel), 1)
52
+ createTextVNode(toDisplayString(__props.loading ? __props.loadingLabel : __props.confirmLabel), 1)
48
53
  ]),
49
54
  _: 1
50
- }, 8, ["variant"])
55
+ }, 8, ["variant", "disabled"])
51
56
  ]),
52
57
  default: withCtx(() => [
53
58
  renderSlot(_ctx.$slots, "default")
@@ -1,7 +1,7 @@
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
3
  import { _ as _sfc_main$1 } from "./LpEmptyState-CrfyRJUG.js";
4
- import { _ as _sfc_main$3 } from "./LpFileTreeNode-DFg4PhOK.js";
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 = {
7
7
  key: 0,
@@ -5,25 +5,24 @@ import { _ as _sfc_main$1 } from "./LpContextMenu-D0mzXqA7.js";
5
5
  import { _ as _sfc_main$2 } from "./LpIcon-CCnX5_2j.js";
6
6
  const _hoisted_1 = ["aria-level", "aria-selected", "aria-expanded", "aria-disabled"];
7
7
  const _hoisted_2 = ["data-node-id", "tabindex"];
8
- const _hoisted_3 = { class: "flex size-4 shrink-0 items-center justify-center" };
9
- const _hoisted_4 = { class: "min-w-0 flex-1 shrink-[2] truncate" };
10
- const _hoisted_5 = {
8
+ const _hoisted_3 = { class: "min-w-0 flex-1 shrink-[2] truncate" };
9
+ const _hoisted_4 = {
11
10
  key: 1,
12
11
  class: "shrink-0 rounded-pill bg-brand-soft px-1.5 py-0.5 text-[11px] font-medium tabular-nums text-brand"
13
12
  };
14
- const _hoisted_6 = {
13
+ const _hoisted_5 = {
15
14
  key: 2,
16
15
  class: "min-w-0 shrink truncate text-xs text-muted"
17
16
  };
18
- const _hoisted_7 = {
17
+ const _hoisted_6 = {
19
18
  key: 3,
20
19
  class: "shrink-0 text-xs tabular-nums text-muted/80"
21
20
  };
22
- const _hoisted_8 = {
21
+ const _hoisted_7 = {
23
22
  key: 4,
24
23
  class: "w-16 shrink-0 text-right text-xs tabular-nums text-muted"
25
24
  };
26
- const _hoisted_9 = {
25
+ const _hoisted_8 = {
27
26
  key: 0,
28
27
  role: "group",
29
28
  class: "flex min-w-0 flex-col overflow-hidden"
@@ -134,7 +133,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
134
133
  style: normalizeStyle({ paddingLeft: `${__props.depth * 14 + 6}px` }),
135
134
  onClick: withModifiers(onActivate, ["stop"])
136
135
  }, [
137
- createElementVNode("span", _hoisted_3, [
136
+ createElementVNode("span", {
137
+ class: normalizeClass(["flex size-4 shrink-0 items-center justify-center", canExpand.value ? "cursor-pointer rounded-sm hover:bg-line" : ""]),
138
+ onClick: _cache[0] || (_cache[0] = withModifiers(($event) => canExpand.value && !__props.node.disabled ? emit("toggle", __props.node) : void 0, ["stop"]))
139
+ }, [
138
140
  isLoading.value ? (openBlock(), createBlock(_sfc_main$2, {
139
141
  key: 0,
140
142
  name: "lucide:loader-circle",
@@ -146,7 +148,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
146
148
  size: 14,
147
149
  class: normalizeClass(["text-muted transition-transform duration-[var(--duration-fast)]", isOpen.value ? "rotate-90" : ""])
148
150
  }, null, 8, ["class"])) : createCommentVNode("", true)
149
- ]),
151
+ ], 2),
150
152
  __props.checkable ? (openBlock(), createBlock(_sfc_main$3, {
151
153
  key: 0,
152
154
  "model-value": checkState.value === "checked",
@@ -154,16 +156,16 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
154
156
  disabled: __props.node.disabled,
155
157
  class: "shrink-0",
156
158
  "aria-label": __props.node.name,
157
- onClick: _cache[0] || (_cache[0] = withModifiers(() => {
159
+ onClick: _cache[1] || (_cache[1] = withModifiers(() => {
158
160
  }, ["stop"])),
159
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => emit("check", __props.node, $event))
161
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => emit("check", __props.node, $event))
160
162
  }, null, 8, ["model-value", "indeterminate", "disabled", "aria-label"])) : createCommentVNode("", true),
161
163
  createVNode(_sfc_main$2, {
162
164
  name: unref(fileIcon)(__props.node, isOpen.value),
163
165
  size: __props.iconSize,
164
166
  class: normalizeClass(["shrink-0", __props.node.disabled ? "" : isDir.value ? "text-brand" : "text-muted"])
165
167
  }, null, 8, ["name", "size", "class"]),
166
- createElementVNode("span", _hoisted_4, [
168
+ createElementVNode("span", _hoisted_3, [
167
169
  renderSlot(_ctx.$slots, "row", {
168
170
  node: __props.node,
169
171
  depth: __props.depth,
@@ -173,10 +175,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
173
175
  createTextVNode(toDisplayString(__props.node.name), 1)
174
176
  ])
175
177
  ]),
176
- countLabel.value ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(countLabel.value), 1)) : createCommentVNode("", true),
177
- __props.node.meta ? (openBlock(), createElementBlock("span", _hoisted_6, toDisplayString(__props.node.meta), 1)) : createCommentVNode("", true),
178
- modifiedLabel.value ? (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString(modifiedLabel.value), 1)) : createCommentVNode("", true),
179
- sizeLabel.value ? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(sizeLabel.value), 1)) : createCommentVNode("", true)
178
+ countLabel.value ? (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString(countLabel.value), 1)) : createCommentVNode("", true),
179
+ __props.node.meta ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(__props.node.meta), 1)) : createCommentVNode("", true),
180
+ modifiedLabel.value ? (openBlock(), createElementBlock("span", _hoisted_6, toDisplayString(modifiedLabel.value), 1)) : createCommentVNode("", true),
181
+ sizeLabel.value ? (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString(sizeLabel.value), 1)) : createCommentVNode("", true)
180
182
  ], 14, _hoisted_2)
181
183
  ]),
182
184
  _: 3
@@ -187,7 +189,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
187
189
  onLeave: onBranchLeave
188
190
  }, {
189
191
  default: withCtx(() => [
190
- isOpen.value && hasChildren.value ? (openBlock(), createElementBlock("ul", _hoisted_9, [
192
+ isOpen.value && hasChildren.value ? (openBlock(), createElementBlock("ul", _hoisted_8, [
191
193
  (openBlock(true), createElementBlock(Fragment, null, renderList(children.value, (child, i) => {
192
194
  return openBlock(), createBlock(_component_LpFileTreeNode, {
193
195
  key: child.id,
@@ -206,10 +208,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
206
208
  stats: __props.stats,
207
209
  "show-size": __props.showSize,
208
210
  "show-modified": __props.showModified,
209
- onSelect: _cache[2] || (_cache[2] = ($event) => emit("select", $event)),
210
- onToggle: _cache[3] || (_cache[3] = ($event) => emit("toggle", $event)),
211
- onFocus: _cache[4] || (_cache[4] = ($event) => emit("focus", $event)),
212
- onCheck: _cache[5] || (_cache[5] = (n, v) => emit("check", n, v))
211
+ onSelect: _cache[3] || (_cache[3] = ($event) => emit("select", $event)),
212
+ onToggle: _cache[4] || (_cache[4] = ($event) => emit("toggle", $event)),
213
+ onFocus: _cache[5] || (_cache[5] = ($event) => emit("focus", $event)),
214
+ onCheck: _cache[6] || (_cache[6] = (n, v) => emit("check", n, v))
213
215
  }, {
214
216
  row: withCtx((slotProps) => [
215
217
  renderSlot(_ctx.$slots, "row", mergeProps({ ref_for: true }, slotProps))
@@ -7,7 +7,7 @@ const _hoisted_1 = {
7
7
  key: 0,
8
8
  class: "flex shrink-0 items-center text-muted"
9
9
  };
10
- const _hoisted_2 = ["type", "value", "placeholder", "disabled", "aria-invalid"];
10
+ const _hoisted_2 = ["type", "value", "placeholder", "disabled", "required", "aria-invalid"];
11
11
  const __default__ = { inheritAttrs: false };
12
12
  const _sfc_main = /* @__PURE__ */ defineComponent({
13
13
  ...__default__,
@@ -23,6 +23,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
23
23
  hint: {},
24
24
  error: {},
25
25
  icon: {},
26
+ required: { type: Boolean },
26
27
  pattern: {},
27
28
  restrict: {}
28
29
  },
@@ -90,6 +91,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
90
91
  label: hasField.value ? __props.label : void 0,
91
92
  hint: hasField.value ? __props.hint : void 0,
92
93
  error: hasField.value ? __props.error : void 0,
94
+ required: hasField.value ? __props.required : void 0,
93
95
  class: normalizeClass(hasField.value ? rootClass.value : void 0),
94
96
  style: normalizeStyle(hasField.value ? rootStyle.value : void 0)
95
97
  }, {
@@ -116,6 +118,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
116
118
  value: __props.modelValue,
117
119
  placeholder: __props.placeholder,
118
120
  disabled: __props.disabled,
121
+ required: __props.required || void 0,
119
122
  "aria-invalid": isInvalidState.value || void 0,
120
123
  class: [
121
124
  "lp-autofill min-w-0 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted",
@@ -132,7 +135,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
132
135
  ], 6)
133
136
  ]),
134
137
  _: 3
135
- }, 8, ["label", "hint", "error", "class", "style"]);
138
+ }, 8, ["label", "hint", "error", "required", "class", "style"]);
136
139
  };
137
140
  }
138
141
  });
@@ -1,5 +1,4 @@
1
- import { defineComponent, computed, openBlock, createElementBlock, normalizeClass, renderSlot } from "vue";
2
- const _hoisted_1 = ["href", "target", "rel"];
1
+ import { defineComponent, computed, openBlock, createBlock, resolveDynamicComponent, normalizeClass, withCtx, renderSlot } from "vue";
3
2
  const revealClass = "relative after:absolute after:inset-x-0 after:-bottom-0.5 after:h-px after:origin-left after:scale-x-0 after:bg-current after:transition-transform after:duration-[var(--duration-medium)] hover:after:scale-x-100 focus-visible:after:scale-x-100";
4
3
  const _sfc_main = /* @__PURE__ */ defineComponent({
5
4
  __name: "LpLink",
@@ -7,27 +6,43 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
7
6
  href: {},
8
7
  external: { type: Boolean },
9
8
  muted: { type: Boolean },
9
+ tone: {},
10
+ as: { default: "a" },
10
11
  variant: { default: "default" }
11
12
  },
12
13
  setup(__props) {
13
14
  const props = __props;
15
+ const isAnchor = computed(() => props.as === "a");
14
16
  const rel = computed(() => props.external ? "noopener noreferrer" : void 0);
15
17
  const target = computed(() => props.external ? "_blank" : void 0);
16
- const colorClass = computed(
17
- () => props.muted ? "text-muted hover:text-ink" : "text-brand hover:text-brand-hover"
18
- );
18
+ const colorClass = computed(() => {
19
+ const tone = props.tone ?? (props.muted ? "muted" : "brand");
20
+ switch (tone) {
21
+ case "ink":
22
+ return "text-ink";
23
+ case "muted":
24
+ return "text-muted-strong hover:text-ink";
25
+ case "soft":
26
+ return "text-muted hover:text-ink";
27
+ default:
28
+ return "text-brand hover:text-brand-hover";
29
+ }
30
+ });
19
31
  return (_ctx, _cache) => {
20
- return openBlock(), createElementBlock("a", {
21
- href: __props.href,
32
+ return openBlock(), createBlock(resolveDynamicComponent(__props.as), {
33
+ href: isAnchor.value ? __props.href : void 0,
22
34
  target: target.value,
23
35
  rel: rel.value,
24
36
  class: normalizeClass(["inline-flex items-center gap-1 underline-offset-2 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring", [
25
37
  colorClass.value,
26
38
  __props.variant === "reveal" ? revealClass : "hover:underline"
27
39
  ]])
28
- }, [
29
- renderSlot(_ctx.$slots, "default")
30
- ], 10, _hoisted_1);
40
+ }, {
41
+ default: withCtx(() => [
42
+ renderSlot(_ctx.$slots, "default")
43
+ ]),
44
+ _: 3
45
+ }, 8, ["href", "target", "rel", "class"]);
31
46
  };
32
47
  }
33
48
  });
@@ -1,6 +1,6 @@
1
1
  import { defineComponent, ref, openBlock, createBlock, createSlots, withCtx, createElementVNode, createVNode, renderSlot } from "vue";
2
2
  import { _ as _sfc_main$2 } from "./LpIcon-CCnX5_2j.js";
3
- import { _ as _sfc_main$1 } from "./LpInput-BfLIWV-P.js";
3
+ import { _ as _sfc_main$1 } from "./LpInput-jwqXlAy0.js";
4
4
  const _hoisted_1 = ["aria-label"];
5
5
  const __default__ = { inheritAttrs: false };
6
6
  const _sfc_main = /* @__PURE__ */ defineComponent({
@@ -10,7 +10,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
10
10
  modelValue: {},
11
11
  placeholder: {},
12
12
  invalid: { type: Boolean },
13
- disabled: { type: Boolean }
13
+ disabled: { type: Boolean },
14
+ label: {},
15
+ hint: {},
16
+ error: {},
17
+ required: { type: Boolean }
14
18
  },
15
19
  emits: ["update:modelValue"],
16
20
  setup(__props, { expose: __expose }) {
@@ -30,6 +34,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
30
34
  placeholder: __props.placeholder,
31
35
  invalid: __props.invalid,
32
36
  disabled: __props.disabled,
37
+ label: __props.label,
38
+ hint: __props.hint,
39
+ error: __props.error,
40
+ required: __props.required,
33
41
  "onUpdate:modelValue": _cache[1] || (_cache[1] = (v) => _ctx.$emit("update:modelValue", v))
34
42
  }, createSlots({
35
43
  trailing: withCtx(() => [
@@ -55,7 +63,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
55
63
  ]),
56
64
  key: "0"
57
65
  } : void 0
58
- ]), 1032, ["model-value", "type", "placeholder", "invalid", "disabled"]);
66
+ ]), 1032, ["model-value", "type", "placeholder", "invalid", "disabled", "label", "hint", "error", "required"]);
59
67
  };
60
68
  }
61
69
  });
@@ -1,6 +1,6 @@
1
1
  import { defineComponent, computed, openBlock, createBlock, resolveDynamicComponent, normalizeClass, withCtx, createElementVNode } from "vue";
2
2
  import { _ as _sfc_main$1 } from "./LpFormField-CesaPWPO.js";
3
- const _hoisted_1 = ["value", "placeholder", "rows", "disabled"];
3
+ const _hoisted_1 = ["value", "placeholder", "rows", "disabled", "required"];
4
4
  const _sfc_main = /* @__PURE__ */ defineComponent({
5
5
  __name: "LpTextarea",
6
6
  props: {
@@ -11,7 +11,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
11
11
  disabled: { type: Boolean },
12
12
  label: {},
13
13
  hint: {},
14
- error: {}
14
+ error: {},
15
+ required: { type: Boolean }
15
16
  },
16
17
  emits: ["update:modelValue"],
17
18
  setup(__props) {
@@ -22,6 +23,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
22
23
  label: hasField.value ? __props.label : void 0,
23
24
  hint: hasField.value ? __props.hint : void 0,
24
25
  error: hasField.value ? __props.error : void 0,
26
+ required: hasField.value ? __props.required : void 0,
25
27
  class: normalizeClass(hasField.value ? void 0 : "contents")
26
28
  }, {
27
29
  default: withCtx(() => [
@@ -30,12 +32,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
30
32
  placeholder: __props.placeholder,
31
33
  rows: __props.rows ?? 4,
32
34
  disabled: __props.disabled,
35
+ required: __props.required || void 0,
33
36
  class: normalizeClass(["w-full resize-y rounded-control border bg-surface-soft px-3 py-2 text-sm text-ink outline-none transition-colors duration-[var(--duration-fast)] placeholder:text-muted focus:border-brand focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-55", __props.invalid || __props.error ? "border-danger focus:border-danger focus:ring-danger-soft" : "border-line"]),
34
37
  onInput: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("update:modelValue", $event.target.value))
35
38
  }, null, 42, _hoisted_1)
36
39
  ]),
37
40
  _: 1
38
- }, 8, ["label", "hint", "error", "class"]);
41
+ }, 8, ["label", "hint", "error", "required", "class"]);
39
42
  };
40
43
  }
41
44
  });
@@ -23,7 +23,10 @@ const dark = {
23
23
  brandSoft: "rgba(0, 188, 255, 0.16)",
24
24
  action: "#39e58c",
25
25
  actionHover: "#4dffa1",
26
+ actionSoft: "rgba(57, 229, 140, 0.14)",
26
27
  accent: "#f0b35a",
28
+ accentHover: "#f2bf74",
29
+ accentSoft: "rgba(240, 179, 90, 0.14)",
27
30
  danger: "#ff6f6f",
28
31
  dangerHover: "#ff8585",
29
32
  dangerSoft: "rgba(255, 111, 111, 0.14)",
@@ -51,7 +54,10 @@ const light = {
51
54
  brandSoft: "rgba(0, 134, 199, 0.12)",
52
55
  action: "#1f9d5f",
53
56
  actionHover: "#23b06b",
57
+ actionSoft: "rgba(31, 157, 95, 0.12)",
54
58
  accent: "#aa6f1d",
59
+ accentHover: "#b88641",
60
+ accentSoft: "rgba(170, 111, 29, 0.12)",
55
61
  danger: "#c2333b",
56
62
  dangerHover: "#d23b44",
57
63
  dangerSoft: "rgba(194, 51, 59, 0.12)",
@@ -79,7 +85,10 @@ const lime = {
79
85
  brandSoft: "rgba(196, 240, 66, 0.14)",
80
86
  action: "#5fd97a",
81
87
  actionHover: "#74e88d",
88
+ actionSoft: "rgba(95, 217, 122, 0.14)",
82
89
  accent: "#ffb84d",
90
+ accentHover: "#ffc369",
91
+ accentSoft: "rgba(255, 184, 77, 0.14)",
83
92
  danger: "#ff6f6f",
84
93
  dangerHover: "#ff8585",
85
94
  dangerSoft: "rgba(255, 111, 111, 0.14)",
@@ -120,7 +129,10 @@ const leavepulse = {
120
129
  brandSoft: "rgba(0, 188, 255, 0.2)",
121
130
  action: "#44c2ad",
122
131
  actionHover: "#56d3be",
132
+ actionSoft: "rgba(68, 194, 173, 0.14)",
123
133
  accent: "#d36a2c",
134
+ accentHover: "#da824e",
135
+ accentSoft: "rgba(211, 106, 44, 0.14)",
124
136
  danger: "#f87171",
125
137
  dangerHover: "#fca5a5",
126
138
  dangerSoft: "rgba(248, 113, 113, 0.14)",
@@ -152,7 +164,10 @@ const rose = {
152
164
  brandSoft: "rgba(251, 113, 133, 0.14)",
153
165
  action: "#34d399",
154
166
  actionHover: "#4adeaa",
167
+ actionSoft: "rgba(52, 211, 153, 0.14)",
155
168
  accent: "#fbbf24",
169
+ accentHover: "#fcc947",
170
+ accentSoft: "rgba(251, 191, 36, 0.14)",
156
171
  danger: "#ef4444",
157
172
  dangerHover: "#f56565",
158
173
  dangerSoft: "rgba(239, 68, 68, 0.14)",
@@ -180,7 +195,10 @@ const amber = {
180
195
  brandSoft: "rgba(245, 158, 11, 0.14)",
181
196
  action: "#4ade80",
182
197
  actionHover: "#5cea90",
198
+ actionSoft: "rgba(74, 222, 128, 0.14)",
183
199
  accent: "#38bdf8",
200
+ accentHover: "#58c8f9",
201
+ accentSoft: "rgba(56, 189, 248, 0.14)",
184
202
  danger: "#f87171",
185
203
  dangerHover: "#fa8a8a",
186
204
  dangerSoft: "rgba(248, 113, 113, 0.14)",
@@ -208,7 +226,10 @@ const violet = {
208
226
  brandSoft: "rgba(167, 139, 250, 0.14)",
209
227
  action: "#34d399",
210
228
  actionHover: "#4adeaa",
229
+ actionSoft: "rgba(52, 211, 153, 0.14)",
211
230
  accent: "#fbbf24",
231
+ accentHover: "#fcc947",
232
+ accentSoft: "rgba(251, 191, 36, 0.14)",
212
233
  danger: "#f87171",
213
234
  dangerHover: "#fa8a8a",
214
235
  dangerSoft: "rgba(248, 113, 113, 0.14)",
@@ -236,7 +257,10 @@ const nord = {
236
257
  brandSoft: "rgba(136, 192, 208, 0.16)",
237
258
  action: "#a3be8c",
238
259
  actionHover: "#b3cb9f",
260
+ actionSoft: "rgba(163, 190, 140, 0.16)",
239
261
  accent: "#ebcb8b",
262
+ accentHover: "#eed39e",
263
+ accentSoft: "rgba(235, 203, 139, 0.16)",
240
264
  danger: "#bf616a",
241
265
  dangerHover: "#cb727b",
242
266
  dangerSoft: "rgba(191, 97, 106, 0.16)",
@@ -275,7 +299,10 @@ const COLOR_VARS = {
275
299
  brandSoft: "--color-brand-soft",
276
300
  action: "--color-action",
277
301
  actionHover: "--color-action-hover",
302
+ actionSoft: "--color-action-soft",
278
303
  accent: "--color-accent",
304
+ accentHover: "--color-accent-hover",
305
+ accentSoft: "--color-accent-soft",
279
306
  danger: "--color-danger",
280
307
  dangerHover: "--color-danger-hover",
281
308
  dangerSoft: "--color-danger-soft",
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpAddressInput-peTpNSJK.js";
1
+ import { _ as _sfc_main } from "../chunks/LpAddressInput-BTqnUXZG.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -6,6 +6,10 @@ declare const __VLS_export: import('vue').DefineComponent<{
6
6
  alt?: string;
7
7
  fallback?: string;
8
8
  size?: "sm" | "md" | "lg" | undefined;
9
+ /** Silhouette: circle for people, soft/square for servers and projects. */
10
+ shape?: "circle" | "square" | "soft" | undefined;
11
+ /** Draws a hairline ring, for avatars laid over imagery or other avatars. */
12
+ ring?: boolean;
9
13
  /** Right-click menu (e.g. account quick actions). Consumer-supplied. */
10
14
  menuItems?: ContextMenuItemDef[];
11
15
  }, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{
@@ -13,8 +17,14 @@ declare const __VLS_export: import('vue').DefineComponent<{
13
17
  alt?: string;
14
18
  fallback?: string;
15
19
  size?: "sm" | "md" | "lg" | undefined;
20
+ /** Silhouette: circle for people, soft/square for servers and projects. */
21
+ shape?: "circle" | "square" | "soft" | undefined;
22
+ /** Draws a hairline ring, for avatars laid over imagery or other avatars. */
23
+ ring?: boolean;
16
24
  /** Right-click menu (e.g. account quick actions). Consumer-supplied. */
17
25
  menuItems?: ContextMenuItemDef[];
18
26
  }> & Readonly<{}>, {
19
27
  size: "sm" | "md" | "lg";
28
+ shape: "circle" | "square" | "soft";
29
+ ring: boolean;
20
30
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpAvatar-B1Ez0ZAx.js";
1
+ import { _ as _sfc_main } from "../chunks/LpAvatar-D-jHoHR7.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -5,6 +5,13 @@ type __VLS_Props = {
5
5
  confirmLabel?: string;
6
6
  cancelLabel?: string;
7
7
  danger?: boolean;
8
+ /**
9
+ * The confirm action is in flight: the button disables and swaps to
10
+ * `loadingLabel`, and confirming no longer closes the dialog — the owner
11
+ * closes it once the work settles, so a failure can stay on screen.
12
+ */
13
+ loading?: boolean;
14
+ loadingLabel?: string;
8
15
  };
9
16
  declare var __VLS_10: {};
10
17
  type __VLS_Slots = {} & {
@@ -21,6 +28,7 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {},
21
28
  }>, {
22
29
  confirmLabel: string;
23
30
  cancelLabel: string;
31
+ loadingLabel: string;
24
32
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
25
33
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
26
34
  declare const _default: typeof __VLS_export;
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpConfirmDialog-DOU_zaTI.js";
1
+ import { _ as _sfc_main } from "../chunks/LpConfirmDialog-CObRah9c.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpFileTree-Cw50eG_y.js";
1
+ import { _ as _sfc_main } from "../chunks/LpFileTree-CN0IOIY7.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpFileTreeNode-DFg4PhOK.js";
1
+ import { _ as _sfc_main } from "../chunks/LpFileTreeNode-D7WgUPcJ.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -11,6 +11,12 @@ declare const __VLS_export: __VLS_WithSlots<import('vue').DefineComponent<{
11
11
  hint?: string;
12
12
  error?: string;
13
13
  icon?: string;
14
+ /**
15
+ * Marks the label with the required indicator. The attribute still reaches
16
+ * the underlying <input> for native validation; this only makes the field
17
+ * read as required.
18
+ */
19
+ required?: boolean;
14
20
  /**
15
21
  * RegExp/string the whole value must match to be valid. Validation only —
16
22
  * never blocks typing; a non-matching non-empty value shows the invalid
@@ -40,6 +46,12 @@ declare const __VLS_export: __VLS_WithSlots<import('vue').DefineComponent<{
40
46
  hint?: string;
41
47
  error?: string;
42
48
  icon?: string;
49
+ /**
50
+ * Marks the label with the required indicator. The attribute still reaches
51
+ * the underlying <input> for native validation; this only makes the field
52
+ * read as required.
53
+ */
54
+ required?: boolean;
43
55
  /**
44
56
  * RegExp/string the whole value must match to be valid. Validation only —
45
57
  * never blocks typing; a non-matching non-empty value shows the invalid
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpInput-BfLIWV-P.js";
1
+ import { _ as _sfc_main } from "../chunks/LpInput-jwqXlAy0.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,7 +1,24 @@
1
+ import { Component } from 'vue';
1
2
  type __VLS_Props = {
2
3
  href?: string;
3
4
  external?: boolean;
5
+ /** Shorthand for `tone="muted"`. */
4
6
  muted?: boolean;
7
+ /**
8
+ * "brand" — the accent link colour (default).
9
+ * "ink" — inherits body text colour; for links that sit inside prose or
10
+ * chrome where an accent would shout (breadcrumbs, footers, nav).
11
+ * "muted" / "soft" — progressively quieter, both resolving to ink on hover.
12
+ */
13
+ tone?: "brand" | "ink" | "muted" | "soft";
14
+ /**
15
+ * Root element/component. Defaults to a native <a>. Pass a router link
16
+ * component (e.g. NuxtLink or RouterLink) to keep client-side navigation;
17
+ * the kit stays DOM-agnostic, the consumer supplies the link component.
18
+ * Router links take their destination through their own prop (`to`), which
19
+ * falls through as a plain attribute.
20
+ */
21
+ as?: string | Component;
5
22
  /**
6
23
  * "default" — plain hover underline.
7
24
  * "reveal" — an underline bar that grows in from the left on hover
@@ -10,11 +27,12 @@ type __VLS_Props = {
10
27
  */
11
28
  variant?: "default" | "reveal";
12
29
  };
13
- declare var __VLS_1: {};
30
+ declare var __VLS_8: {};
14
31
  type __VLS_Slots = {} & {
15
- default?: (props: typeof __VLS_1) => any;
32
+ default?: (props: typeof __VLS_8) => any;
16
33
  };
17
34
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
35
+ as: string | Component;
18
36
  variant: "default" | "reveal";
19
37
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
20
38
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpLink-DHwOEp4e.js";
1
+ import { _ as _sfc_main } from "../chunks/LpLink-DU9kkKGW.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -5,6 +5,10 @@ declare const __VLS_export: __VLS_WithSlots<import('vue').DefineComponent<{
5
5
  placeholder?: string;
6
6
  invalid?: boolean;
7
7
  disabled?: boolean;
8
+ label?: string;
9
+ hint?: string;
10
+ error?: string;
11
+ required?: boolean;
8
12
  }, {
9
13
  focus: (options?: FocusOptions) => void | undefined;
10
14
  blur: () => void | undefined;
@@ -16,6 +20,10 @@ declare const __VLS_export: __VLS_WithSlots<import('vue').DefineComponent<{
16
20
  placeholder?: string;
17
21
  invalid?: boolean;
18
22
  disabled?: boolean;
23
+ label?: string;
24
+ hint?: string;
25
+ error?: string;
26
+ required?: boolean;
19
27
  }> & Readonly<{
20
28
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
21
29
  }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>, {
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpPasswordInput-BfjDrvZq.js";
1
+ import { _ as _sfc_main } from "../chunks/LpPasswordInput-Bmo6q9Kd.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -7,6 +7,11 @@ type __VLS_Props = {
7
7
  label?: string;
8
8
  hint?: string;
9
9
  error?: string;
10
+ /**
11
+ * Marks the label with the required indicator; the attribute still reaches
12
+ * the <textarea> for native validation.
13
+ */
14
+ required?: boolean;
10
15
  };
11
16
  declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
12
17
  "update:modelValue": (value: string) => any;
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpTextarea-BDl7PR9U.js";
1
+ import { _ as _sfc_main } from "../chunks/LpTextarea-1ENb3ZAO.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-B_kj0T2b.js";
1
+ import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-BgpJgrnP.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { _ } from "./chunks/LayoutCanvas-Dd20boUG.js";
2
- import { _ as _2 } from "./chunks/LpAddressInput-peTpNSJK.js";
2
+ import { _ as _2 } from "./chunks/LpAddressInput-BTqnUXZG.js";
3
3
  import { _ as _3 } from "./chunks/LpMapPicker-DFKrd1Ne.js";
4
4
  import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
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-B1Ez0ZAx.js";
8
+ import { _ as _7 } from "./chunks/LpAvatar-D-jHoHR7.js";
9
9
  import { _ as _8 } from "./chunks/LpBadge-BPodVcJa.js";
10
10
  import { _ as _9 } from "./chunks/LpBreadcrumbs-B7Aqatzw.js";
11
11
  import { _ as _10 } from "./chunks/LpButton-5VXXCjwF.js";
@@ -14,7 +14,7 @@ import { _ as _12 } from "./chunks/LpCard-BmrTvWOe.js";
14
14
  import { _ as _13 } from "./chunks/LpCheckbox-BO0zuuPh.js";
15
15
  import { _ as _14 } from "./chunks/LpCodeBlock-CSiOSPs8.js";
16
16
  import { default as default2 } from "./components/LpCommandPalette.vue.js";
17
- import { _ as _15 } from "./chunks/LpConfirmDialog-DOU_zaTI.js";
17
+ import { _ as _15 } from "./chunks/LpConfirmDialog-CObRah9c.js";
18
18
  import { _ as _16 } from "./chunks/LpContextMenu-D0mzXqA7.js";
19
19
  import { _ as _17 } from "./chunks/LpDatePicker-dY2q4dr4.js";
20
20
  import { _ as _18 } from "./chunks/LpDisclosure-DP6yfgfM.js";
@@ -22,22 +22,22 @@ 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
24
  import { _ as _22 } from "./chunks/LpEmptyState-CrfyRJUG.js";
25
- import { _ as _23 } from "./chunks/LpFileTree-Cw50eG_y.js";
25
+ import { _ as _23 } from "./chunks/LpFileTree-CN0IOIY7.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";
29
29
  import { _ as _26 } from "./chunks/LpInfraNode-qIL3l7IO.js";
30
- import { _ as _27 } from "./chunks/LpInput-BfLIWV-P.js";
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-DHwOEp4e.js";
33
+ import { _ as _29 } from "./chunks/LpLink-DU9kkKGW.js";
34
34
  import { _ as _30 } from "./chunks/LpLogViewer-B1aLUVdz.js";
35
35
  import { _ as _31 } from "./chunks/LpModal-D_o-56To.js";
36
36
  import { _ as _32 } from "./chunks/LpNotificationBell-a08cl0yY.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";
40
- import { _ as _36 } from "./chunks/LpPasswordInput-BfjDrvZq.js";
40
+ import { _ as _36 } from "./chunks/LpPasswordInput-Bmo6q9Kd.js";
41
41
  import { _ as _37 } from "./chunks/LpPhoneInput-NXXjciJJ.js";
42
42
  import { flagEmoji, loadCountries, matchCountryByValue } from "./components/countries.js";
43
43
  import { _ as _38 } from "./chunks/LpPopover-8268UmlC.js";
@@ -59,9 +59,9 @@ 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";
61
61
  import { _ as _55 } from "./chunks/LpTabs-Cefe_lys.js";
62
- import { _ as _56 } from "./chunks/LpTextarea-BDl7PR9U.js";
63
- import { p as presets, a as parseTheme, s as serializeTheme } from "./chunks/LpThemeSwitcher-B_kj0T2b.js";
64
- import { D, _ as _57, N, T, b, c, d, e, f, g, i, l, h, j, n, r, t, k, m, u, v } from "./chunks/LpThemeSwitcher-B_kj0T2b.js";
62
+ import { _ as _56 } from "./chunks/LpTextarea-1ENb3ZAO.js";
63
+ import { p as presets, a as parseTheme, s as serializeTheme } from "./chunks/LpThemeSwitcher-BgpJgrnP.js";
64
+ import { D, _ as _57, N, T, b, c, d, e, f, g, i, l, h, j, n, r, t, k, m, u, v } from "./chunks/LpThemeSwitcher-BgpJgrnP.js";
65
65
  import { _ as _58 } from "./chunks/LpTilt-DuyKbpkA.js";
66
66
  import { _ as _59 } from "./chunks/LpToaster-gDB1EaU3.js";
67
67
  import { _ as _60 } from "./chunks/LpTooltip-D1hbjQWG.js";
@@ -11,7 +11,10 @@ export interface ColorTokens {
11
11
  brandSoft: string;
12
12
  action: string;
13
13
  actionHover: string;
14
+ actionSoft: string;
14
15
  accent: string;
16
+ accentHover: string;
17
+ accentSoft: string;
15
18
  danger: string;
16
19
  dangerHover: string;
17
20
  dangerSoft: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.22.22",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -12,15 +12,23 @@ import { computed, onMounted, ref } from "vue"
12
12
  import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
13
13
 
14
14
  const avatar = tv({
15
- base: "inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-pill bg-surface-soft font-medium text-muted-strong",
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
18
  sm: "size-7 text-xs",
19
19
  md: "size-9 text-sm",
20
20
  lg: "size-12 text-base",
21
21
  },
22
+ // People read as circles; things (servers, projects, orgs) read as
23
+ // rounded squares — the same distinction platform icons make.
24
+ shape: {
25
+ circle: "rounded-pill",
26
+ soft: "rounded-card",
27
+ square: "rounded-control",
28
+ },
29
+ ring: { true: "ring-2 ring-line", false: "" },
22
30
  },
23
- defaultVariants: { size: "md" },
31
+ defaultVariants: { size: "md", shape: "circle", ring: false },
24
32
  })
25
33
 
26
34
  type AvatarVariants = VariantProps<typeof avatar>
@@ -31,10 +39,14 @@ const props = withDefaults(
31
39
  alt?: string
32
40
  fallback?: string
33
41
  size?: AvatarVariants["size"]
42
+ /** Silhouette: circle for people, soft/square for servers and projects. */
43
+ shape?: AvatarVariants["shape"]
44
+ /** Draws a hairline ring, for avatars laid over imagery or other avatars. */
45
+ ring?: boolean
34
46
  /** Right-click menu (e.g. account quick actions). Consumer-supplied. */
35
47
  menuItems?: ContextMenuItemDef[]
36
48
  }>(),
37
- { size: "md" },
49
+ { size: "md", shape: "circle", ring: false },
38
50
  )
39
51
 
40
52
  const initials = computed(
@@ -55,12 +67,12 @@ onMounted(() => {
55
67
 
56
68
  <template>
57
69
  <LpContextMenu v-if="menuItems?.length" :items="menuItems">
58
- <AvatarRoot :class="avatar({ size })" v-bind="$attrs">
70
+ <AvatarRoot :class="avatar({ size, shape, ring })" v-bind="$attrs">
59
71
  <AvatarImage v-if="src && mounted" :src="src" :alt="alt" class="size-full object-cover" />
60
72
  <AvatarFallback>{{ initials }}</AvatarFallback>
61
73
  </AvatarRoot>
62
74
  </LpContextMenu>
63
- <AvatarRoot v-else :class="avatar({ size })" v-bind="$attrs">
75
+ <AvatarRoot v-else :class="avatar({ size, shape, ring })" v-bind="$attrs">
64
76
  <AvatarImage v-if="src" :src="src" :alt="alt" class="size-full object-cover" />
65
77
  <AvatarFallback>{{ initials }}</AvatarFallback>
66
78
  </AvatarRoot>
@@ -3,7 +3,7 @@
3
3
  import LpButton from "./LpButton.vue"
4
4
  import LpModal from "./LpModal.vue"
5
5
 
6
- withDefaults(
6
+ const props = withDefaults(
7
7
  defineProps<{
8
8
  open?: boolean
9
9
  title?: string
@@ -11,8 +11,15 @@ withDefaults(
11
11
  confirmLabel?: string
12
12
  cancelLabel?: string
13
13
  danger?: boolean
14
+ /**
15
+ * The confirm action is in flight: the button disables and swaps to
16
+ * `loadingLabel`, and confirming no longer closes the dialog — the owner
17
+ * closes it once the work settles, so a failure can stay on screen.
18
+ */
19
+ loading?: boolean
20
+ loadingLabel?: string
14
21
  }>(),
15
- { confirmLabel: "Confirm", cancelLabel: "Cancel" },
22
+ { confirmLabel: "Confirm", cancelLabel: "Cancel", loadingLabel: "Working…" },
16
23
  )
17
24
 
18
25
  const emit = defineEmits<{
@@ -23,7 +30,9 @@ const emit = defineEmits<{
23
30
 
24
31
  function confirm() {
25
32
  emit("confirm")
26
- emit("update:open", false)
33
+ // An async confirm owns its own dismissal; closing here would tear the
34
+ // dialog down before the caller knows whether the action succeeded.
35
+ if (!props.loading) emit("update:open", false)
27
36
  }
28
37
  function cancel() {
29
38
  emit("cancel")
@@ -40,9 +49,15 @@ function cancel() {
40
49
  >
41
50
  <slot />
42
51
  <template #footer>
43
- <LpButton variant="ghost" @click="cancel">{{ cancelLabel }}</LpButton>
44
- <LpButton :variant="danger ? 'danger' : 'solid'" @click="confirm">
45
- {{ confirmLabel }}
52
+ <LpButton variant="ghost" :disabled="loading" @click="cancel">
53
+ {{ cancelLabel }}
54
+ </LpButton>
55
+ <LpButton
56
+ :variant="danger ? 'danger' : 'solid'"
57
+ :disabled="loading"
58
+ @click="confirm"
59
+ >
60
+ {{ loading ? loadingLabel : confirmLabel }}
46
61
  </LpButton>
47
62
  </template>
48
63
  </LpModal>
@@ -178,8 +178,17 @@ function onActivate() {
178
178
  @click.stop="onActivate"
179
179
  >
180
180
  <!-- Chevron sits in a fixed-width slot so names line up whether or not
181
- the row can expand. Leaves get the same gutter, left empty. -->
182
- <span class="flex size-4 shrink-0 items-center justify-center">
181
+ the row can expand. Leaves get the same gutter, left empty.
182
+
183
+ It is its own hit target, like the checkbox: opening a folder to
184
+ see what is in it is a different intent from selecting it, and on
185
+ a tree that drives a details pane the two must not be the same
186
+ click. The row as a whole still does both. -->
187
+ <span
188
+ class="flex size-4 shrink-0 items-center justify-center"
189
+ :class="canExpand ? 'cursor-pointer rounded-sm hover:bg-line' : ''"
190
+ @click.stop="canExpand && !node.disabled ? emit('toggle', node) : undefined"
191
+ >
183
192
  <LpIcon
184
193
  v-if="isLoading"
185
194
  name="lucide:loader-circle"
@@ -54,6 +54,12 @@ const props = withDefaults(
54
54
  hint?: string
55
55
  error?: string
56
56
  icon?: string
57
+ /**
58
+ * Marks the label with the required indicator. The attribute still reaches
59
+ * the underlying <input> for native validation; this only makes the field
60
+ * read as required.
61
+ */
62
+ required?: boolean
57
63
  /**
58
64
  * RegExp/string the whole value must match to be valid. Validation only —
59
65
  * never blocks typing; a non-matching non-empty value shows the invalid
@@ -118,6 +124,7 @@ defineExpose({
118
124
  :label="hasField ? label : undefined"
119
125
  :hint="hasField ? hint : undefined"
120
126
  :error="hasField ? error : undefined"
127
+ :required="hasField ? required : undefined"
121
128
  :class="hasField ? rootClass : undefined"
122
129
  :style="hasField ? rootStyle : undefined"
123
130
  >
@@ -136,6 +143,7 @@ defineExpose({
136
143
  :value="modelValue"
137
144
  :placeholder="placeholder"
138
145
  :disabled="disabled"
146
+ :required="required || undefined"
139
147
  :aria-invalid="isInvalidState || undefined"
140
148
  :class="[
141
149
  'lp-autofill min-w-0 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted',
@@ -1,11 +1,27 @@
1
1
  <script setup lang="ts">
2
- import { computed } from "vue"
2
+ import { computed, type Component } from "vue"
3
3
 
4
4
  const props = withDefaults(
5
5
  defineProps<{
6
6
  href?: string
7
7
  external?: boolean
8
+ /** Shorthand for `tone="muted"`. */
8
9
  muted?: boolean
10
+ /**
11
+ * "brand" — the accent link colour (default).
12
+ * "ink" — inherits body text colour; for links that sit inside prose or
13
+ * chrome where an accent would shout (breadcrumbs, footers, nav).
14
+ * "muted" / "soft" — progressively quieter, both resolving to ink on hover.
15
+ */
16
+ tone?: "brand" | "ink" | "muted" | "soft"
17
+ /**
18
+ * Root element/component. Defaults to a native <a>. Pass a router link
19
+ * component (e.g. NuxtLink or RouterLink) to keep client-side navigation;
20
+ * the kit stays DOM-agnostic, the consumer supplies the link component.
21
+ * Router links take their destination through their own prop (`to`), which
22
+ * falls through as a plain attribute.
23
+ */
24
+ as?: string | Component
9
25
  /**
10
26
  * "default" — plain hover underline.
11
27
  * "reveal" — an underline bar that grows in from the left on hover
@@ -14,15 +30,30 @@ const props = withDefaults(
14
30
  */
15
31
  variant?: "default" | "reveal"
16
32
  }>(),
17
- { variant: "default" },
33
+ { variant: "default", as: "a" },
18
34
  )
19
35
 
36
+ // A router link component owns its destination through its own prop (`to`),
37
+ // so `href` is only bound when this really renders an anchor. `target`/`rel`
38
+ // stay in play either way — router links honour them for external targets.
39
+ const isAnchor = computed(() => props.as === "a")
40
+
20
41
  const rel = computed(() => (props.external ? "noopener noreferrer" : undefined))
21
42
  const target = computed(() => (props.external ? "_blank" : undefined))
22
43
 
23
- const colorClass = computed(() =>
24
- props.muted ? "text-muted hover:text-ink" : "text-brand hover:text-brand-hover",
25
- )
44
+ const colorClass = computed(() => {
45
+ const tone = props.tone ?? (props.muted ? "muted" : "brand")
46
+ switch (tone) {
47
+ case "ink":
48
+ return "text-ink"
49
+ case "muted":
50
+ return "text-muted-strong hover:text-ink"
51
+ case "soft":
52
+ return "text-muted hover:text-ink"
53
+ default:
54
+ return "text-brand hover:text-brand-hover"
55
+ }
56
+ })
26
57
 
27
58
  // The reveal bar: an ::after pseudo-element pinned to the baseline, full-width
28
59
  // but scaled to 0 on the x-axis from the left, growing to 1 on hover/focus. The
@@ -33,8 +64,9 @@ const revealClass =
33
64
  </script>
34
65
 
35
66
  <template>
36
- <a
37
- :href="href"
67
+ <component
68
+ :is="as"
69
+ :href="isAnchor ? href : undefined"
38
70
  :target="target"
39
71
  :rel="rel"
40
72
  class="inline-flex items-center gap-1 underline-offset-2 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
@@ -44,5 +76,5 @@ const revealClass =
44
76
  ]"
45
77
  >
46
78
  <slot />
47
- </a>
79
+ </component>
48
80
  </template>
@@ -9,7 +9,18 @@ import { ref } from "vue"
9
9
  import LpIcon from "./LpIcon.vue"
10
10
  import LpInput from "./LpInput.vue"
11
11
 
12
- defineProps<{ modelValue?: string; placeholder?: string; invalid?: boolean; disabled?: boolean }>()
12
+ defineProps<{
13
+ modelValue?: string
14
+ placeholder?: string
15
+ invalid?: boolean
16
+ disabled?: boolean
17
+ // Field wrapping mirrors LpInput: passing any of these wraps the control in
18
+ // LpFormField, so a password field labels itself like every other field.
19
+ label?: string
20
+ hint?: string
21
+ error?: string
22
+ required?: boolean
23
+ }>()
13
24
  defineEmits<{ (e: "update:modelValue", value: string): void }>()
14
25
 
15
26
  const visible = ref(false)
@@ -29,6 +40,10 @@ defineExpose({
29
40
  :placeholder="placeholder"
30
41
  :invalid="invalid"
31
42
  :disabled="disabled"
43
+ :label="label"
44
+ :hint="hint"
45
+ :error="error"
46
+ :required="required"
32
47
  @update:model-value="(v) => $emit('update:modelValue', v)"
33
48
  >
34
49
  <template v-if="$slots.leading" #leading><slot name="leading" /></template>
@@ -11,6 +11,11 @@ const props = defineProps<{
11
11
  label?: string
12
12
  hint?: string
13
13
  error?: string
14
+ /**
15
+ * Marks the label with the required indicator; the attribute still reaches
16
+ * the <textarea> for native validation.
17
+ */
18
+ required?: boolean
14
19
  }>()
15
20
 
16
21
  defineEmits<{ (e: "update:modelValue", value: string): void }>()
@@ -24,6 +29,7 @@ const hasField = computed(() => !!props.label || !!props.hint || !!props.error)
24
29
  :label="hasField ? label : undefined"
25
30
  :hint="hasField ? hint : undefined"
26
31
  :error="hasField ? error : undefined"
32
+ :required="hasField ? required : undefined"
27
33
  :class="hasField ? undefined : 'contents'"
28
34
  >
29
35
  <textarea
@@ -31,6 +37,7 @@ const hasField = computed(() => !!props.label || !!props.hint || !!props.error)
31
37
  :placeholder="placeholder"
32
38
  :rows="rows ?? 4"
33
39
  :disabled="disabled"
40
+ :required="required || undefined"
34
41
  class="w-full resize-y rounded-control border bg-surface-soft px-3 py-2 text-sm text-ink outline-none transition-colors duration-[var(--duration-fast)] placeholder:text-muted focus:border-brand focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-55"
35
42
  :class="(invalid || error) ? 'border-danger focus:border-danger focus:ring-danger-soft' : 'border-line'"
36
43
  @input="$emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
@@ -35,7 +35,10 @@ export const dark: TokenSet = {
35
35
  brandSoft: "rgba(0, 188, 255, 0.16)",
36
36
  action: "#39e58c",
37
37
  actionHover: "#4dffa1",
38
+ actionSoft: "rgba(57, 229, 140, 0.14)",
38
39
  accent: "#f0b35a",
40
+ accentHover: "#f2bf74",
41
+ accentSoft: "rgba(240, 179, 90, 0.14)",
39
42
  danger: "#ff6f6f",
40
43
  dangerHover: "#ff8585",
41
44
  dangerSoft: "rgba(255, 111, 111, 0.14)",
@@ -64,7 +67,10 @@ export const light: TokenSet = {
64
67
  brandSoft: "rgba(0, 134, 199, 0.12)",
65
68
  action: "#1f9d5f",
66
69
  actionHover: "#23b06b",
70
+ actionSoft: "rgba(31, 157, 95, 0.12)",
67
71
  accent: "#aa6f1d",
72
+ accentHover: "#b88641",
73
+ accentSoft: "rgba(170, 111, 29, 0.12)",
68
74
  danger: "#c2333b",
69
75
  dangerHover: "#d23b44",
70
76
  dangerSoft: "rgba(194, 51, 59, 0.12)",
@@ -93,7 +99,10 @@ export const lime: TokenSet = {
93
99
  brandSoft: "rgba(196, 240, 66, 0.14)",
94
100
  action: "#5fd97a",
95
101
  actionHover: "#74e88d",
102
+ actionSoft: "rgba(95, 217, 122, 0.14)",
96
103
  accent: "#ffb84d",
104
+ accentHover: "#ffc369",
105
+ accentSoft: "rgba(255, 184, 77, 0.14)",
97
106
  danger: "#ff6f6f",
98
107
  dangerHover: "#ff8585",
99
108
  dangerSoft: "rgba(255, 111, 111, 0.14)",
@@ -148,7 +157,10 @@ export const leavepulse: TokenSet = {
148
157
  brandSoft: "rgba(0, 188, 255, 0.2)",
149
158
  action: "#44c2ad",
150
159
  actionHover: "#56d3be",
160
+ actionSoft: "rgba(68, 194, 173, 0.14)",
151
161
  accent: "#d36a2c",
162
+ accentHover: "#da824e",
163
+ accentSoft: "rgba(211, 106, 44, 0.14)",
152
164
  danger: "#f87171",
153
165
  dangerHover: "#fca5a5",
154
166
  dangerSoft: "rgba(248, 113, 113, 0.14)",
@@ -181,7 +193,10 @@ export const rose: TokenSet = {
181
193
  brandSoft: "rgba(251, 113, 133, 0.14)",
182
194
  action: "#34d399",
183
195
  actionHover: "#4adeaa",
196
+ actionSoft: "rgba(52, 211, 153, 0.14)",
184
197
  accent: "#fbbf24",
198
+ accentHover: "#fcc947",
199
+ accentSoft: "rgba(251, 191, 36, 0.14)",
185
200
  danger: "#ef4444",
186
201
  dangerHover: "#f56565",
187
202
  dangerSoft: "rgba(239, 68, 68, 0.14)",
@@ -210,7 +225,10 @@ export const amber: TokenSet = {
210
225
  brandSoft: "rgba(245, 158, 11, 0.14)",
211
226
  action: "#4ade80",
212
227
  actionHover: "#5cea90",
228
+ actionSoft: "rgba(74, 222, 128, 0.14)",
213
229
  accent: "#38bdf8",
230
+ accentHover: "#58c8f9",
231
+ accentSoft: "rgba(56, 189, 248, 0.14)",
214
232
  danger: "#f87171",
215
233
  dangerHover: "#fa8a8a",
216
234
  dangerSoft: "rgba(248, 113, 113, 0.14)",
@@ -239,7 +257,10 @@ export const violet: TokenSet = {
239
257
  brandSoft: "rgba(167, 139, 250, 0.14)",
240
258
  action: "#34d399",
241
259
  actionHover: "#4adeaa",
260
+ actionSoft: "rgba(52, 211, 153, 0.14)",
242
261
  accent: "#fbbf24",
262
+ accentHover: "#fcc947",
263
+ accentSoft: "rgba(251, 191, 36, 0.14)",
243
264
  danger: "#f87171",
244
265
  dangerHover: "#fa8a8a",
245
266
  dangerSoft: "rgba(248, 113, 113, 0.14)",
@@ -268,7 +289,10 @@ export const nord: TokenSet = {
268
289
  brandSoft: "rgba(136, 192, 208, 0.16)",
269
290
  action: "#a3be8c",
270
291
  actionHover: "#b3cb9f",
292
+ actionSoft: "rgba(163, 190, 140, 0.16)",
271
293
  accent: "#ebcb8b",
294
+ accentHover: "#eed39e",
295
+ accentSoft: "rgba(235, 203, 139, 0.16)",
272
296
  danger: "#bf616a",
273
297
  dangerHover: "#cb727b",
274
298
  dangerSoft: "rgba(191, 97, 106, 0.16)",
@@ -18,7 +18,10 @@ export interface ColorTokens {
18
18
  brandSoft: string
19
19
  action: string
20
20
  actionHover: string
21
+ actionSoft: string
21
22
  accent: string
23
+ accentHover: string
24
+ accentSoft: string
22
25
  danger: string
23
26
  dangerHover: string
24
27
  dangerSoft: string
@@ -99,7 +102,10 @@ export const COLOR_VARS: Record<keyof ColorTokens, string> = {
99
102
  brandSoft: "--color-brand-soft",
100
103
  action: "--color-action",
101
104
  actionHover: "--color-action-hover",
105
+ actionSoft: "--color-action-soft",
102
106
  accent: "--color-accent",
107
+ accentHover: "--color-accent-hover",
108
+ accentSoft: "--color-accent-soft",
103
109
  danger: "--color-danger",
104
110
  dangerHover: "--color-danger-hover",
105
111
  dangerSoft: "--color-danger-soft",
@@ -36,7 +36,10 @@
36
36
  --color-brand-soft: rgba(0, 188, 255, 0.16);
37
37
  --color-action: #39e58c;
38
38
  --color-action-hover: #4dffa1;
39
+ --color-action-soft: rgba(57, 229, 140, 0.14);
39
40
  --color-accent: #f0b35a;
41
+ --color-accent-hover: #f2bf74;
42
+ --color-accent-soft: rgba(240, 179, 90, 0.14);
40
43
  --color-danger: #ff6f6f;
41
44
  --color-danger-hover: #ff8585;
42
45
  --color-danger-soft: rgba(255, 111, 111, 0.14);