@leavepulse/ui 0.27.0 → 0.27.1
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.
- package/dist/chunks/{LpCard-BmrTvWOe.js → LpCard-gu11ZmSt.js} +9 -1
- package/dist/chunks/{LpSection-BIvwZyE1.js → LpSection-aM6J_u51.js} +24 -9
- package/dist/chunks/{LpSkeleton-DpXLDcQm.js → LpSkeleton-CTI-vBgW.js} +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpSection.vue.d.ts +28 -2
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSkeleton.vue.js +1 -1
- package/dist/index.js +3 -3
- package/package.json +1 -1
- package/src/components/LpCard.vue +9 -1
- package/src/components/LpSection.vue +49 -13
- package/src/components/LpSkeleton.vue +1 -1
|
@@ -14,7 +14,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
14
14
|
},
|
|
15
15
|
setup(__props) {
|
|
16
16
|
const card = tv({
|
|
17
|
-
|
|
17
|
+
// break-words: a card is where text with no spaces in it ends up (a
|
|
18
|
+
// hostname, a URL, a container id, a stack frame), and an unbreakable run
|
|
19
|
+
// otherwise pushes straight out through the border, over whatever sits
|
|
20
|
+
// beside it. Set here rather than on the components that go inside, so that
|
|
21
|
+
// a paragraph written by hand behaves the same as one a kit component
|
|
22
|
+
// renders: two lines that look identical must not wrap differently.
|
|
23
|
+
// Anything that genuinely must not break (a code block, a log line) sets its
|
|
24
|
+
// own overflow and scrolls instead.
|
|
25
|
+
base: "rounded-card border bg-surface-raised break-words",
|
|
18
26
|
variants: {
|
|
19
27
|
variant: {
|
|
20
28
|
// raised opts into the skin painter (.lp-skin-panel) so it follows the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { defineComponent, computed, openBlock, createElementBlock, normalizeClass, createElementVNode, createBlock, createCommentVNode, resolveDynamicComponent, withCtx, renderSlot, createTextVNode, toDisplayString } from "vue";
|
|
1
|
+
import { defineComponent, useSlots, computed, openBlock, createElementBlock, normalizeClass, createElementVNode, createBlock, createCommentVNode, resolveDynamicComponent, withCtx, renderSlot, createTextVNode, toDisplayString } from "vue";
|
|
2
2
|
import { tv } from "tailwind-variants";
|
|
3
3
|
import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
|
|
4
|
-
const _hoisted_1 = { class: "min-w-0" };
|
|
4
|
+
const _hoisted_1 = { class: "min-w-0 flex-1 break-words" };
|
|
5
5
|
const _hoisted_2 = { class: "flex flex-wrap items-center gap-2" };
|
|
6
6
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
7
7
|
__name: "LpSection",
|
|
@@ -15,14 +15,21 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
15
15
|
setup(__props) {
|
|
16
16
|
const section = tv({
|
|
17
17
|
slots: {
|
|
18
|
-
|
|
19
|
-
// must stay level with the TITLE, not drift to the middle of the block.
|
|
20
|
-
root: "flex flex-wrap items-start justify-between gap-x-4 gap-y-2",
|
|
18
|
+
root: "flex flex-wrap justify-between gap-x-4 gap-y-2",
|
|
21
19
|
title: "font-semibold text-ink",
|
|
22
20
|
description: "mt-1 text-sm text-muted",
|
|
23
21
|
actions: "flex shrink-0 flex-wrap items-center gap-2"
|
|
24
22
|
},
|
|
25
23
|
variants: {
|
|
24
|
+
// Where the actions sit against a two-line block. With a description the
|
|
25
|
+
// heading is the top line, so the actions belong level with THAT rather
|
|
26
|
+
// than floating to the middle of the pair. With no description there is
|
|
27
|
+
// only one line, and pinning to the top instead misaligns anything taller
|
|
28
|
+
// than the text (an input, a segmented control) by a couple of pixels.
|
|
29
|
+
described: {
|
|
30
|
+
true: { root: "items-start" },
|
|
31
|
+
false: { root: "items-center" }
|
|
32
|
+
},
|
|
26
33
|
level: {
|
|
27
34
|
sm: { title: "text-sm" },
|
|
28
35
|
md: { title: "text-base" },
|
|
@@ -30,10 +37,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
30
37
|
xl: { title: "text-xl tracking-tight" }
|
|
31
38
|
}
|
|
32
39
|
},
|
|
33
|
-
defaultVariants: { level: "md" }
|
|
40
|
+
defaultVariants: { level: "md", described: false }
|
|
34
41
|
});
|
|
35
42
|
const props = __props;
|
|
36
|
-
const
|
|
43
|
+
const slots = useSlots();
|
|
44
|
+
const hasDescription = computed(() => !!props.description || !!slots.description);
|
|
45
|
+
const classes = computed(
|
|
46
|
+
() => section({ level: props.level, described: hasDescription.value })
|
|
47
|
+
);
|
|
37
48
|
return (_ctx, _cache) => {
|
|
38
49
|
return openBlock(), createElementBlock("div", {
|
|
39
50
|
class: normalizeClass(classes.value.root())
|
|
@@ -58,10 +69,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
58
69
|
}, 8, ["class"])),
|
|
59
70
|
renderSlot(_ctx.$slots, "meta")
|
|
60
71
|
]),
|
|
61
|
-
|
|
72
|
+
hasDescription.value ? (openBlock(), createElementBlock("p", {
|
|
62
73
|
key: 0,
|
|
63
74
|
class: normalizeClass(classes.value.description())
|
|
64
|
-
},
|
|
75
|
+
}, [
|
|
76
|
+
renderSlot(_ctx.$slots, "description", {}, () => [
|
|
77
|
+
createTextVNode(toDisplayString(__props.description), 1)
|
|
78
|
+
])
|
|
79
|
+
], 2)) : createCommentVNode("", true)
|
|
65
80
|
]),
|
|
66
81
|
_ctx.$slots.actions ? (openBlock(), createElementBlock("div", {
|
|
67
82
|
key: 0,
|
|
@@ -12,7 +12,7 @@ import '../assets/LpSkeleton-D7xU8IPV.css';const _sfc_main = /* @__PURE__ */ def
|
|
|
12
12
|
"aria-hidden": "true",
|
|
13
13
|
class: normalizeClass(["lp-skeleton", [
|
|
14
14
|
// `rounded` describes the PLACEHOLDER's own shape, so it only applies to
|
|
15
|
-
// the bare form. A wrapper is invisible
|
|
15
|
+
// the bare form. A wrapper is invisible, so rounding it would clip the
|
|
16
16
|
// layout inside without anything being drawn to justify the corner.
|
|
17
17
|
isBlock.value ? [
|
|
18
18
|
"lp-skeleton-item bg-surface-soft",
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { VariantProps } from 'tailwind-variants';
|
|
2
2
|
declare const section: import('tailwind-variants').TVReturnType<{
|
|
3
|
+
described: {
|
|
4
|
+
true: {
|
|
5
|
+
root: string;
|
|
6
|
+
};
|
|
7
|
+
false: {
|
|
8
|
+
root: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
3
11
|
level: {
|
|
4
12
|
sm: {
|
|
5
13
|
title: string;
|
|
@@ -20,6 +28,14 @@ declare const section: import('tailwind-variants').TVReturnType<{
|
|
|
20
28
|
description: string;
|
|
21
29
|
actions: string;
|
|
22
30
|
}, undefined, {
|
|
31
|
+
described: {
|
|
32
|
+
true: {
|
|
33
|
+
root: string;
|
|
34
|
+
};
|
|
35
|
+
false: {
|
|
36
|
+
root: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
23
39
|
level: {
|
|
24
40
|
sm: {
|
|
25
41
|
title: string;
|
|
@@ -40,6 +56,14 @@ declare const section: import('tailwind-variants').TVReturnType<{
|
|
|
40
56
|
description: string;
|
|
41
57
|
actions: string;
|
|
42
58
|
}, import('tailwind-variants').TVReturnType<{
|
|
59
|
+
described: {
|
|
60
|
+
true: {
|
|
61
|
+
root: string;
|
|
62
|
+
};
|
|
63
|
+
false: {
|
|
64
|
+
root: string;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
43
67
|
level: {
|
|
44
68
|
sm: {
|
|
45
69
|
title: string;
|
|
@@ -67,7 +91,7 @@ type __VLS_Props = {
|
|
|
67
91
|
description?: string;
|
|
68
92
|
/** Iconify name shown before the title, e.g. "lucide:server". */
|
|
69
93
|
icon?: string;
|
|
70
|
-
/** Visual size. Independent of `as
|
|
94
|
+
/** Visual size. Independent of `as`, see the note above. */
|
|
71
95
|
level?: SectionVariants["level"];
|
|
72
96
|
/** The heading element to render. Pick it for the document outline. */
|
|
73
97
|
as?: "h1" | "h2" | "h3" | "h4" | "p";
|
|
@@ -75,9 +99,11 @@ type __VLS_Props = {
|
|
|
75
99
|
type __VLS_Slots = {
|
|
76
100
|
/** Title markup, when a plain string is not enough. */
|
|
77
101
|
title?: () => unknown;
|
|
102
|
+
/** Description markup, when a plain string is not enough. */
|
|
103
|
+
description?: () => unknown;
|
|
78
104
|
/** Right-aligned controls: buttons, filters, a count. */
|
|
79
105
|
actions?: () => unknown;
|
|
80
|
-
/** Extra content beside the title
|
|
106
|
+
/** Extra content beside the title: a badge, a count, a status dot. */
|
|
81
107
|
meta?: () => unknown;
|
|
82
108
|
};
|
|
83
109
|
declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { _ as _8 } from "./chunks/LpBadge-BPodVcJa.js";
|
|
|
10
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
|
-
import { _ as _12 } from "./chunks/LpCard-
|
|
13
|
+
import { _ as _12 } from "./chunks/LpCard-gu11ZmSt.js";
|
|
14
14
|
import { _ as _13 } from "./chunks/LpChart-tNXs1ttr.js";
|
|
15
15
|
import { CHART_PALETTE, axisTicks, formatTick, niceScale, resolveSlices } from "./components/chart.js";
|
|
16
16
|
import { _ as _14 } from "./chunks/LpCheckbox-BO0zuuPh.js";
|
|
@@ -51,13 +51,13 @@ import { _ as _44 } from "./chunks/LpRadioGroup-B4yDTmi6.js";
|
|
|
51
51
|
import { _ as _45 } from "./chunks/LpRollingText-CM5gkSeF.js";
|
|
52
52
|
import { _ as _46 } from "./chunks/LpScrollArea-D8xyKXcF.js";
|
|
53
53
|
import { _ as _47 } from "./chunks/LpSegmented-BmpanyAo.js";
|
|
54
|
-
import { _ as _48 } from "./chunks/LpSection-
|
|
54
|
+
import { _ as _48 } from "./chunks/LpSection-aM6J_u51.js";
|
|
55
55
|
import { _ as _49 } from "./chunks/LpSelect-Cw8-gL5F.js";
|
|
56
56
|
import { _ as _50 } from "./chunks/LpServiceNode-D8uJ8sGk.js";
|
|
57
57
|
import { _ as _51 } from "./chunks/LpShift-DXNsauQZ.js";
|
|
58
58
|
import { default as default3 } from "./components/LpShortcutsDialog.vue.js";
|
|
59
59
|
import { _ as _52 } from "./chunks/LpSidebar-7Vpy2IJc.js";
|
|
60
|
-
import { _ as _53 } from "./chunks/LpSkeleton-
|
|
60
|
+
import { _ as _53 } from "./chunks/LpSkeleton-CTI-vBgW.js";
|
|
61
61
|
import { _ as _54 } from "./chunks/LpSlider-B1SF3pRV.js";
|
|
62
62
|
import { _ as _55 } from "./chunks/LpSparkline-C6xQ2dwv.js";
|
|
63
63
|
import { _ as _56 } from "./chunks/LpStat-CDanpzU0.js";
|
package/package.json
CHANGED
|
@@ -11,7 +11,15 @@ import { computed } from "vue"
|
|
|
11
11
|
import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
12
12
|
|
|
13
13
|
const card = tv({
|
|
14
|
-
|
|
14
|
+
// break-words: a card is where text with no spaces in it ends up (a
|
|
15
|
+
// hostname, a URL, a container id, a stack frame), and an unbreakable run
|
|
16
|
+
// otherwise pushes straight out through the border, over whatever sits
|
|
17
|
+
// beside it. Set here rather than on the components that go inside, so that
|
|
18
|
+
// a paragraph written by hand behaves the same as one a kit component
|
|
19
|
+
// renders: two lines that look identical must not wrap differently.
|
|
20
|
+
// Anything that genuinely must not break (a code block, a log line) sets its
|
|
21
|
+
// own overflow and scrolls instead.
|
|
22
|
+
base: "rounded-card border bg-surface-raised break-words",
|
|
15
23
|
variants: {
|
|
16
24
|
variant: {
|
|
17
25
|
// raised opts into the skin painter (.lp-skin-panel) so it follows the
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
*
|
|
10
10
|
* This is the block every app rebuilt: ~140 hand-written headings across the
|
|
11
11
|
* three of them, each picking its own size, its own gap, its own alignment.
|
|
12
|
-
* LeaveHosting had already extracted its own `PageHeader.vue`
|
|
13
|
-
*
|
|
12
|
+
* LeaveHosting had already extracted its own `PageHeader.vue` with the same
|
|
13
|
+
* title, description and actions slot, which is the clearest evidence the
|
|
14
14
|
* shape is shared rather than one app's habit.
|
|
15
15
|
*
|
|
16
16
|
* What it deliberately does NOT do:
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* the cabinet they sit inside `glass rounded-3xl` blocks and, more often,
|
|
20
20
|
* directly on the page. Owning a border here would fight two of the three.
|
|
21
21
|
* - It has no icon slot of its own beyond `icon`. The panel coloured each
|
|
22
|
-
* heading's icon from the raw palette
|
|
22
|
+
* heading's icon from the raw palette (amber beside rose beside sky) in
|
|
23
23
|
* pages where none of those states were amber, rose or blue. One muted
|
|
24
24
|
* colour, or none.
|
|
25
25
|
*
|
|
@@ -34,14 +34,21 @@ import LpIcon from "./LpIcon.vue"
|
|
|
34
34
|
|
|
35
35
|
const section = tv({
|
|
36
36
|
slots: {
|
|
37
|
-
|
|
38
|
-
// must stay level with the TITLE, not drift to the middle of the block.
|
|
39
|
-
root: "flex flex-wrap items-start justify-between gap-x-4 gap-y-2",
|
|
37
|
+
root: "flex flex-wrap justify-between gap-x-4 gap-y-2",
|
|
40
38
|
title: "font-semibold text-ink",
|
|
41
39
|
description: "mt-1 text-sm text-muted",
|
|
42
40
|
actions: "flex shrink-0 flex-wrap items-center gap-2",
|
|
43
41
|
},
|
|
44
42
|
variants: {
|
|
43
|
+
// Where the actions sit against a two-line block. With a description the
|
|
44
|
+
// heading is the top line, so the actions belong level with THAT rather
|
|
45
|
+
// than floating to the middle of the pair. With no description there is
|
|
46
|
+
// only one line, and pinning to the top instead misaligns anything taller
|
|
47
|
+
// than the text (an input, a segmented control) by a couple of pixels.
|
|
48
|
+
described: {
|
|
49
|
+
true: { root: "items-start" },
|
|
50
|
+
false: { root: "items-center" },
|
|
51
|
+
},
|
|
45
52
|
level: {
|
|
46
53
|
sm: { title: "text-sm" },
|
|
47
54
|
md: { title: "text-base" },
|
|
@@ -49,7 +56,7 @@ const section = tv({
|
|
|
49
56
|
xl: { title: "text-xl tracking-tight" },
|
|
50
57
|
},
|
|
51
58
|
},
|
|
52
|
-
defaultVariants: { level: "md" },
|
|
59
|
+
defaultVariants: { level: "md", described: false },
|
|
53
60
|
})
|
|
54
61
|
|
|
55
62
|
type SectionVariants = VariantProps<typeof section>
|
|
@@ -61,7 +68,7 @@ const props = withDefaults(
|
|
|
61
68
|
description?: string
|
|
62
69
|
/** Iconify name shown before the title, e.g. "lucide:server". */
|
|
63
70
|
icon?: string
|
|
64
|
-
/** Visual size. Independent of `as
|
|
71
|
+
/** Visual size. Independent of `as`, see the note above. */
|
|
65
72
|
level?: SectionVariants["level"]
|
|
66
73
|
/** The heading element to render. Pick it for the document outline. */
|
|
67
74
|
as?: "h1" | "h2" | "h3" | "h4" | "p"
|
|
@@ -69,21 +76,48 @@ const props = withDefaults(
|
|
|
69
76
|
{ level: "md", as: "h2" },
|
|
70
77
|
)
|
|
71
78
|
|
|
72
|
-
|
|
79
|
+
/*
|
|
80
|
+
* `title` and `description` each come in two forms, and they are one opening
|
|
81
|
+
* rather than two: the prop is what the slot renders when nothing fills it.
|
|
82
|
+
* Both are a plain string nine times out of ten (usually a t() call), and
|
|
83
|
+
* making those pass through a <template> would tax the common case for the
|
|
84
|
+
* sake of the rare one. But the rare one is real (a name with an id after it,
|
|
85
|
+
* a description carrying a link), and without the slot it is what sends people
|
|
86
|
+
* back to hand-writing the block the component exists to standardise.
|
|
87
|
+
*/
|
|
88
|
+
const slots = defineSlots<{
|
|
73
89
|
/** Title markup, when a plain string is not enough. */
|
|
74
90
|
title?: () => unknown
|
|
91
|
+
/** Description markup, when a plain string is not enough. */
|
|
92
|
+
description?: () => unknown
|
|
75
93
|
/** Right-aligned controls: buttons, filters, a count. */
|
|
76
94
|
actions?: () => unknown
|
|
77
|
-
/** Extra content beside the title
|
|
95
|
+
/** Extra content beside the title: a badge, a count, a status dot. */
|
|
78
96
|
meta?: () => unknown
|
|
79
97
|
}>()
|
|
80
98
|
|
|
81
|
-
|
|
99
|
+
// Either form counts as "described": the actions align against a two-line
|
|
100
|
+
// block whether the second line came from the prop or the slot.
|
|
101
|
+
const hasDescription = computed(() => !!props.description || !!slots.description)
|
|
102
|
+
|
|
103
|
+
const classes = computed(() =>
|
|
104
|
+
section({ level: props.level, described: hasDescription.value }),
|
|
105
|
+
)
|
|
82
106
|
</script>
|
|
83
107
|
|
|
84
108
|
<template>
|
|
85
109
|
<div :class="classes.root()">
|
|
86
|
-
|
|
110
|
+
<!-- `flex-1` matters as much as `min-w-0`: without it a long description
|
|
111
|
+
stretches this column across the whole row, and the actions wrap to a
|
|
112
|
+
line of their own even when they would have fitted beside the title.
|
|
113
|
+
With it the column takes the space that is left and shrinks first.
|
|
114
|
+
|
|
115
|
+
`break-words` covers the title AND the description in one place. Both
|
|
116
|
+
routinely carry something with no spaces in it (a hostname, a URL, a
|
|
117
|
+
container id), and an unbreakable run pushes straight out through the
|
|
118
|
+
side of whatever card the section sits in. min-w-0 lets the column
|
|
119
|
+
shrink; only this lets the text inside it give way. -->
|
|
120
|
+
<div class="min-w-0 flex-1 break-words">
|
|
87
121
|
<div class="flex flex-wrap items-center gap-2">
|
|
88
122
|
<LpIcon v-if="icon" :name="icon" :size="16" class="shrink-0 text-muted" />
|
|
89
123
|
<component :is="as" :class="classes.title()">
|
|
@@ -91,7 +125,9 @@ const classes = computed(() => section({ level: props.level }))
|
|
|
91
125
|
</component>
|
|
92
126
|
<slot name="meta" />
|
|
93
127
|
</div>
|
|
94
|
-
<p v-if="
|
|
128
|
+
<p v-if="hasDescription" :class="classes.description()">
|
|
129
|
+
<slot name="description">{{ description }}</slot>
|
|
130
|
+
</p>
|
|
95
131
|
</div>
|
|
96
132
|
|
|
97
133
|
<div v-if="$slots.actions" :class="classes.actions()">
|
|
@@ -44,7 +44,7 @@ const isBlock = computed(() => !slots.default)
|
|
|
44
44
|
class="lp-skeleton"
|
|
45
45
|
:class="[
|
|
46
46
|
// `rounded` describes the PLACEHOLDER's own shape, so it only applies to
|
|
47
|
-
// the bare form. A wrapper is invisible
|
|
47
|
+
// the bare form. A wrapper is invisible, so rounding it would clip the
|
|
48
48
|
// layout inside without anything being drawn to justify the corner.
|
|
49
49
|
isBlock
|
|
50
50
|
? [
|