@humanforest/nuxt-layer 0.3.2 → 0.3.3
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanforest/nuxt-layer",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.3",
|
|
5
5
|
"description": "Forest design system as a Nuxt layer — extend it to inherit the theme, colour roles, icons and brand fonts in one line.",
|
|
6
6
|
"main": "./nuxt.config.ts",
|
|
7
7
|
"files": [
|
|
@@ -50,6 +50,12 @@ const props = withDefaults(
|
|
|
50
50
|
area?: boolean;
|
|
51
51
|
/** Marks the last point, so the series has an end rather than running off the edge. */
|
|
52
52
|
endpoint?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Takes the height its container gives it, with `height` as the floor. For a graphic in a space
|
|
55
|
+
* its surroundings have made taller (a KPI bleed row that a stretched card or a group has given
|
|
56
|
+
* more height), where the chart is the part that should grow. Works for both marks.
|
|
57
|
+
*/
|
|
58
|
+
fill?: boolean;
|
|
53
59
|
/**
|
|
54
60
|
* Monotone interpolation, on by default. Monotone rather than a plain spline: it stays inside
|
|
55
61
|
* the data's own range, so no peak appears between two points that neither of them reached.
|
|
@@ -71,7 +77,7 @@ const props = withDefaults(
|
|
|
71
77
|
*/
|
|
72
78
|
mark?: 'line' | 'bar';
|
|
73
79
|
}>(),
|
|
74
|
-
{ height: 32, color: 'var(--ui-primary)', area: false, endpoint: false, curve: true, mark: 'line', compare: undefined, compareColor: undefined },
|
|
80
|
+
{ height: 32, color: 'var(--ui-primary)', area: false, endpoint: false, fill: false, curve: true, mark: 'line', compare: undefined, compareColor: undefined },
|
|
75
81
|
);
|
|
76
82
|
|
|
77
83
|
if (import.meta.env.DEV) {
|
|
@@ -155,14 +161,14 @@ const endpointTop = computed(() => {
|
|
|
155
161
|
</script>
|
|
156
162
|
|
|
157
163
|
<template>
|
|
158
|
-
<div class="relative w-full" :style="{ height: `${height}px`, color }">
|
|
164
|
+
<div class="relative w-full" :class="fill ? 'h-full' : ''" :style="fill ? { minHeight: `${height}px`, color } : { height: `${height}px`, color }">
|
|
159
165
|
<!-- Plain DOM rather than a mark: there are no axes, scales or tooltip to share with the XY
|
|
160
166
|
container, so a flex row of columns is the whole implementation and costs no chart runtime.
|
|
161
167
|
The 2px gap is fixed, so the bucket count sets how thin the bars get: n buckets across w
|
|
162
168
|
pixels leaves (w - 2(n - 1)) / n each. A tile 280px wide holds 12 buckets at 21px and 60 at
|
|
163
169
|
2.7px — still bars; past roughly 60 the gap outweighs the bar and the row reads as a
|
|
164
170
|
texture, where the measure belongs to a line instead. -->
|
|
165
|
-
<div v-if="mark === 'bar'" class="flex
|
|
171
|
+
<div v-if="mark === 'bar'" class="flex w-full items-end gap-0.5" :class="fill ? 'absolute inset-0' : 'h-full'" aria-hidden="true">
|
|
166
172
|
<span
|
|
167
173
|
v-for="(v, i) in data"
|
|
168
174
|
:key="i"
|
|
@@ -180,12 +186,17 @@ const endpointTop = computed(() => {
|
|
|
180
186
|
<!-- No bottom margin: the graphic sits on the tile's own bottom edge, and 2px of clearance
|
|
181
187
|
there reads as a misaligned card rather than as breathing room. The top keeps its 2px so a
|
|
182
188
|
peak is not clipped by the stroke's own width, and the right reserves room for the endpoint
|
|
183
|
-
dot when there is one.
|
|
189
|
+
dot when there is one. Under `fill` the container is laid over the root rather than sizing
|
|
190
|
+
it: the root takes the space it is given, floored at `height`, and Unovis measures that box,
|
|
191
|
+
never its own SVG (which would feed back) and never its 300px fallback for an empty box.
|
|
192
|
+
An inline style, not a utility: Unovis's own stylesheet pins `.unovis-xy-container` to
|
|
193
|
+
`position: relative`, and a class loses to it. -->
|
|
184
194
|
<VisXYContainer
|
|
185
195
|
v-else
|
|
186
196
|
:key="themeVersion"
|
|
197
|
+
:style="fill ? { position: 'absolute', inset: '0' } : undefined"
|
|
187
198
|
:data="points"
|
|
188
|
-
:height="height"
|
|
199
|
+
:height="fill ? undefined : height"
|
|
189
200
|
:margin="{ top: 2, right: endpoint ? 4 : 0, bottom: 0, left: 0 }"
|
|
190
201
|
:y-domain="domain ?? undefined"
|
|
191
202
|
:duration="motionDuration('slow')"
|
|
@@ -117,13 +117,20 @@ const props = withDefaults(
|
|
|
117
117
|
* body rather than the card's.
|
|
118
118
|
*/
|
|
119
119
|
flush?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* The card fills the height its row gives it instead of hugging its content, so cards in a row
|
|
122
|
+
* end level. Only for content that can take the extra height: an FKpi whose bleed chart is a
|
|
123
|
+
* `fill` sparkline grows into it. A bar, a table or a line of text cannot, and the spare height
|
|
124
|
+
* lands as a gap under it.
|
|
125
|
+
*/
|
|
126
|
+
stretch?: boolean;
|
|
120
127
|
|
|
121
128
|
/** Footer action label. `to` makes it a link; without one it is a button. */
|
|
122
129
|
action?: string;
|
|
123
130
|
to?: string;
|
|
124
131
|
|
|
125
132
|
}>(),
|
|
126
|
-
{ gap: 8, outlined: true, bare: false, flush: false },
|
|
133
|
+
{ gap: 8, outlined: true, bare: false, flush: false, stretch: false },
|
|
127
134
|
);
|
|
128
135
|
|
|
129
136
|
const slots = useSlots();
|
|
@@ -151,8 +158,11 @@ if (import.meta.env.DEV) {
|
|
|
151
158
|
// Set as a var rather than a class so the radius follows it.
|
|
152
159
|
const style = computed(() => ({ '--forest-card-pad': PAD[props.gap] }));
|
|
153
160
|
|
|
161
|
+
// Under `stretch` the body takes whatever height the card is given beyond its strip.
|
|
154
162
|
const ui = computed(() => ({
|
|
155
|
-
body: hasFooter.value ? BODY.footer : hasHeader.value ? BODY.header : BODY.none,
|
|
163
|
+
body: [hasFooter.value ? BODY.footer : hasHeader.value ? BODY.header : BODY.none, props.stretch ? 'flex-1' : '']
|
|
164
|
+
.filter(Boolean)
|
|
165
|
+
.join(' '),
|
|
156
166
|
header: STRIP.header,
|
|
157
167
|
footer: STRIP.footer,
|
|
158
168
|
}));
|
|
@@ -175,13 +185,15 @@ const ui = computed(() => ({
|
|
|
175
185
|
// ★ `h-fit` IS LOAD-BEARING, not a layout preference. A grid or flex row stretches its children by
|
|
176
186
|
// default, and a stretched card grows past its content — the extra height lands BELOW the box, so
|
|
177
187
|
// the even inset turns into a lopsided gap at the bottom and the frame stops reading as a frame.
|
|
178
|
-
// The card must hug its content vertically.
|
|
188
|
+
// The card must hug its content vertically. `stretch` drops it for a row whose cards should end
|
|
189
|
+
// level: the card becomes a flex column and its body takes the spare height, which only reads well
|
|
190
|
+
// when the content can grow into it.
|
|
179
191
|
// ⚠ NOT `self-start`, which was the first cut: `align-self` acts on the CROSS axis, so in a flex
|
|
180
192
|
// COLUMN it stops the card filling the width instead of capping its height — the card shrank to its
|
|
181
193
|
// content in every column layout. `h-fit` caps the height and leaves width alone, which is the axis
|
|
182
194
|
// actually at issue.
|
|
183
195
|
const rootClass = computed(() =>
|
|
184
|
-
['h-fit', props.outlined ? 'ring ring-[var(--forest-card-ring)]' : ''].filter(Boolean).join(' '),
|
|
196
|
+
[props.stretch ? 'flex flex-col' : 'h-fit', props.outlined ? 'ring ring-[var(--forest-card-ring)]' : ''].filter(Boolean).join(' '),
|
|
185
197
|
);
|
|
186
198
|
|
|
187
199
|
// ★ THE BOX IS ALWAYS RAISED — there is no ramp option, on purpose. Letting the nesting ladder fill
|
|
@@ -197,7 +209,7 @@ const rootClass = computed(() =>
|
|
|
197
209
|
// pads at ≥640px. The box keeps its own overflow-hidden either way, which is what clips a flush
|
|
198
210
|
// child to the rounded corner; a child that must scroll (UTable) already scrolls itself.
|
|
199
211
|
const boxUi = computed(() => ({
|
|
200
|
-
root: `bg-[var(--forest-card-inset)] ${PAD_CLASS[props.gap]}`,
|
|
212
|
+
root: `bg-[var(--forest-card-inset)] ${PAD_CLASS[props.gap]}${props.stretch ? ' h-full' : ''}`,
|
|
201
213
|
...(props.flush ? { body: 'p-0 sm:p-0' } : {}),
|
|
202
214
|
}));
|
|
203
215
|
|
|
@@ -209,7 +221,7 @@ const boxUi = computed(() => ({
|
|
|
209
221
|
// expression, so `visual-fit="bleed"` still zeroes the bottom and meets both edges. A padding
|
|
210
222
|
// class cannot do this: FKpi merges `ui.root` last, so a `pb-4` here would beat the bleed's `pb-0`.
|
|
211
223
|
const selfBoxUi = computed(() => ({
|
|
212
|
-
root: `bg-[var(--forest-card-inset)] [--forest-kpi-pad:1rem] ${PAD_CLASS[props.gap]}`,
|
|
224
|
+
root: `bg-[var(--forest-card-inset)] [--forest-kpi-pad:1rem] ${PAD_CLASS[props.gap]}${props.stretch ? ' h-full' : ''}`,
|
|
213
225
|
}));
|
|
214
226
|
</script>
|
|
215
227
|
|
|
@@ -22,8 +22,8 @@ export const kpiTheme = tv({
|
|
|
22
22
|
// bleed pulls by the same expression, so the graphic meets the edge the padding leaves.
|
|
23
23
|
//
|
|
24
24
|
// No row template: the rows are implicit, one per part present, so a tile without a visual has
|
|
25
|
-
// no empty track under its caption, and no gap in front of one. A
|
|
26
|
-
// from the group instead (`aligned`).
|
|
25
|
+
// no empty track under its caption, and no gap in front of one. A bleed visual adds a flexible
|
|
26
|
+
// last row (`visualFit`); a grouped tile takes its rows from the group instead (`aligned`).
|
|
27
27
|
//
|
|
28
28
|
// One explicit minmax(0,1fr) column: without it the implicit column sizes to max-content, and a
|
|
29
29
|
// chart with an intrinsic width (Unovis defaults to 300px) makes the tile's own grid wider than
|
|
@@ -81,7 +81,10 @@ export const kpiTheme = tv({
|
|
|
81
81
|
* `bleed` (the default) is for a graphic that IS data and nothing else, a sparkline being the
|
|
82
82
|
* case: it runs through the tile's gutter to both side edges and sits on the bottom edge, which
|
|
83
83
|
* the tile reaches by zeroing its own bottom padding rather than by a negative margin — a
|
|
84
|
-
* margin would shrink the grid row and pull the graphic up over the caption.
|
|
84
|
+
* margin would shrink the grid row and pull the graphic up over the caption. Its row is the
|
|
85
|
+
* tile's last and flexible one, and the graphic spans it bottom-aligned: a tile given more
|
|
86
|
+
* height than it needs — a group's shared row, a stretched FInsetCard — grows a `fill`
|
|
87
|
+
* sparkline into the spare height and leaves any other graphic on the bottom edge.
|
|
85
88
|
*
|
|
86
89
|
* `inset` is for a graphic carrying text, FDistributionBar's legend being the case. Text set
|
|
87
90
|
* against the card's edge reads as a mistake, so the padding stays. It also top-aligns, and
|
|
@@ -89,7 +92,7 @@ export const kpiTheme = tv({
|
|
|
89
92
|
* content keeps the extra height below the graphic rather than spread between its rows.
|
|
90
93
|
*/
|
|
91
94
|
visualFit: {
|
|
92
|
-
bleed: { root: 'pb-0', visual: 'self-end -mx-[var(--forest-kpi-pad,var(--forest-card-pad))]' },
|
|
95
|
+
bleed: { root: 'pb-0 grid-rows-[auto_auto_auto_1fr]', visual: 'self-stretch flex flex-col justify-end -mx-[var(--forest-kpi-pad,var(--forest-card-pad))]' },
|
|
93
96
|
inset: { root: 'content-start', visual: 'self-start' },
|
|
94
97
|
},
|
|
95
98
|
// A grouped tile spans exactly the rows its group declares (kpiGroupRows), so a group with no
|