@heyramzi/ui 2.4.0 → 2.6.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.
- package/COMPONENTS.md +4 -0
- package/package.json +1 -1
- package/src/charts/area-chart.svelte +11 -1
- package/src/record/info-list.svelte +87 -7
- package/src/record/types.ts +7 -0
package/COMPONENTS.md
CHANGED
|
@@ -133,6 +133,10 @@ Generic pieces built on the primitives, imported from `@heyramzi/ui/components/<
|
|
|
133
133
|
- `typing-dots` — TypingDots
|
|
134
134
|
- `voice-waveform` — VoiceWaveform
|
|
135
135
|
|
|
136
|
+
## Record (`src/record`)
|
|
137
|
+
|
|
138
|
+
- `info-list` — InfoList. An editable label and value `<dl>`; `layout="row"` makes it a read-only band of items that wrap and truncate, with `term`, `content` and `action` snippets per row and a `trailing` snippet after the list
|
|
139
|
+
|
|
136
140
|
---
|
|
137
141
|
|
|
138
142
|
Skipped on purpose: brand assets (logos, marks, brand renderer), kanban, list, gantt and status (they live in `@heyramzi/ui/tasks`), and charts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyramzi/ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Svelte 5 UI for apps: primitives, effects and animations, plus tasks, charts, slides, an editor, onboarding and a canvas as subpaths.",
|
|
5
5
|
"homepage": "https://ui.heyramzi.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
xTicks?: number;
|
|
46
46
|
/** A target drawn across the plot, like the deal a cost runs up to. */
|
|
47
47
|
reference?: ChartReference;
|
|
48
|
+
/** The headline while no point is hovered, like a period total. Defaults to the last point. */
|
|
49
|
+
resting?: { value: number; label: string };
|
|
48
50
|
class?: string;
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -65,6 +67,7 @@
|
|
|
65
67
|
formatX = (v) => String(v),
|
|
66
68
|
xTicks = 6,
|
|
67
69
|
reference,
|
|
70
|
+
resting,
|
|
68
71
|
class: className,
|
|
69
72
|
}: Props = $props();
|
|
70
73
|
|
|
@@ -127,7 +130,14 @@
|
|
|
127
130
|
aria-label="Area chart"
|
|
128
131
|
onpointerleave={() => (active = null)}
|
|
129
132
|
>
|
|
130
|
-
{#if
|
|
133
|
+
{#if resting && !active && series[0]}
|
|
134
|
+
<Figure
|
|
135
|
+
series={[{ value: resting.value, label: series[0].label, color: series[0].color }]}
|
|
136
|
+
eyebrow={resting.label}
|
|
137
|
+
format={formatValue}
|
|
138
|
+
size="md"
|
|
139
|
+
/>
|
|
140
|
+
{:else if figureRows && display}
|
|
131
141
|
<Figure series={figureRows} eyebrow={formatX(display[xKey])} format={formatValue} size="md" />
|
|
132
142
|
{:else}
|
|
133
143
|
<Figure value="—" eyebrow="Latest" size="md" />
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// =========================
|
|
3
3
|
// IMPORTS
|
|
4
4
|
// =========================
|
|
5
|
+
import type { Snippet } from "svelte";
|
|
5
6
|
import { Check, CaretUpDown } from "phosphor-svelte";
|
|
6
7
|
import { cn } from "../lib/utils.js";
|
|
7
8
|
import { Button } from "../shadcn/button/index.js";
|
|
@@ -23,9 +24,32 @@
|
|
|
23
24
|
/** A heading over the list. Left out, nothing is drawn. */
|
|
24
25
|
title?: string | null;
|
|
25
26
|
labels?: Partial<RecordLabels>;
|
|
27
|
+
/**
|
|
28
|
+
* `list` is the editable label and value grid. `row` is a read-only band of items that
|
|
29
|
+
* wrap, with values that truncate. Rows ignore `onSave` there and keep their empty ones.
|
|
30
|
+
*/
|
|
31
|
+
layout?: "list" | "row";
|
|
32
|
+
/** `row` only. Where the label sits against its value. */
|
|
33
|
+
labelPosition?: "above" | "beside";
|
|
34
|
+
/** `row` only. Classes for the `<dl>`, to set a grid or a gap. */
|
|
35
|
+
listClass?: string;
|
|
36
|
+
/** `row` only. Classes for each item, to cap its width. */
|
|
37
|
+
itemClass?: string;
|
|
38
|
+
/** `row` only. Sits after the list on the same line, a button for the whole band. */
|
|
39
|
+
trailing?: Snippet;
|
|
26
40
|
class?: string;
|
|
27
41
|
}
|
|
28
|
-
let {
|
|
42
|
+
let {
|
|
43
|
+
rows,
|
|
44
|
+
title = null,
|
|
45
|
+
labels,
|
|
46
|
+
layout = "list",
|
|
47
|
+
labelPosition = "above",
|
|
48
|
+
listClass,
|
|
49
|
+
itemClass,
|
|
50
|
+
trailing,
|
|
51
|
+
class: className,
|
|
52
|
+
}: Props = $props();
|
|
29
53
|
|
|
30
54
|
// =========================
|
|
31
55
|
// STATE
|
|
@@ -42,9 +66,13 @@
|
|
|
42
66
|
let t = $derived(resolveLabels(labels));
|
|
43
67
|
// A read-only row with nothing in it is noise. An editable one stays, as the way to fill it.
|
|
44
68
|
let shown = $derived(
|
|
45
|
-
rows.filter(
|
|
69
|
+
(layout === "row" ? rows.filter((r) => !r.divider) : rows).filter(
|
|
46
70
|
(r) =>
|
|
47
|
-
|
|
71
|
+
layout === "row" ||
|
|
72
|
+
r.divider ||
|
|
73
|
+
r.onSave ||
|
|
74
|
+
(r.value != null && r.value !== "") ||
|
|
75
|
+
(r.chips?.length ?? 0) > 0,
|
|
48
76
|
),
|
|
49
77
|
);
|
|
50
78
|
// Past this many options a plain list is slower than typing.
|
|
@@ -135,7 +163,7 @@
|
|
|
135
163
|
<!-- =========================== -->
|
|
136
164
|
<!-- MARKUP -->
|
|
137
165
|
<!-- =========================== -->
|
|
138
|
-
{#snippet display(row: InfoRow)}
|
|
166
|
+
{#snippet display(row: InfoRow, clamp = false)}
|
|
139
167
|
{#if row.chips?.length}
|
|
140
168
|
<span class="flex flex-wrap items-center gap-1">
|
|
141
169
|
{#each row.chips as chip (chip.label)}
|
|
@@ -147,12 +175,17 @@
|
|
|
147
175
|
{:else if row.href && row.value}
|
|
148
176
|
<a
|
|
149
177
|
href={row.href}
|
|
150
|
-
class=
|
|
178
|
+
class={cn(
|
|
179
|
+
"min-w-0 truncate text-sm font-medium underline-offset-2 hover:underline",
|
|
180
|
+
clamp &&
|
|
181
|
+
"rounded-xs focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
|
182
|
+
)}>{row.value}</a
|
|
151
183
|
>
|
|
152
184
|
{:else if row.value}
|
|
153
185
|
<span
|
|
154
186
|
class={cn(
|
|
155
|
-
"min-w-0 text-sm font-medium tabular-nums
|
|
187
|
+
"min-w-0 text-sm font-medium tabular-nums",
|
|
188
|
+
clamp ? "truncate" : "wrap-anywhere whitespace-pre-line",
|
|
156
189
|
row.muted && "text-muted-foreground",
|
|
157
190
|
)}>{row.value}</span
|
|
158
191
|
>
|
|
@@ -215,7 +248,54 @@
|
|
|
215
248
|
</Popover.Root>
|
|
216
249
|
{/snippet}
|
|
217
250
|
|
|
218
|
-
{#if shown.length > 0}
|
|
251
|
+
{#if layout === "row" && (shown.length > 0 || trailing)}
|
|
252
|
+
<div class={cn("flex items-start justify-between gap-4", className)}>
|
|
253
|
+
<dl class={cn("flex min-w-0 flex-1 flex-wrap gap-x-8 gap-y-2", listClass)}>
|
|
254
|
+
{#each shown as row (keyOf(row))}
|
|
255
|
+
<div
|
|
256
|
+
class={cn(
|
|
257
|
+
"min-w-0",
|
|
258
|
+
labelPosition === "beside" && "flex items-baseline gap-2",
|
|
259
|
+
itemClass,
|
|
260
|
+
)}
|
|
261
|
+
>
|
|
262
|
+
<dt
|
|
263
|
+
class={cn(
|
|
264
|
+
"flex items-center gap-1.5 text-xs text-muted-foreground",
|
|
265
|
+
labelPosition === "beside" && "shrink-0",
|
|
266
|
+
)}
|
|
267
|
+
>
|
|
268
|
+
{#if row.term}
|
|
269
|
+
{@render row.term()}
|
|
270
|
+
{:else}
|
|
271
|
+
{#if row.icon}
|
|
272
|
+
{@const Icon = row.icon}
|
|
273
|
+
<Icon class="size-3.5 shrink-0" aria-hidden="true" />
|
|
274
|
+
{/if}
|
|
275
|
+
{row.label}
|
|
276
|
+
{/if}
|
|
277
|
+
</dt>
|
|
278
|
+
<dd
|
|
279
|
+
class="flex min-w-0 items-center gap-1.5"
|
|
280
|
+
title={row.content ? undefined : (row.value ?? undefined)}
|
|
281
|
+
>
|
|
282
|
+
{#if row.content}
|
|
283
|
+
{@render row.content()}
|
|
284
|
+
{:else if isEmpty(row)}
|
|
285
|
+
<span class="min-w-0 truncate text-sm text-muted-foreground" title={row.placeholder}
|
|
286
|
+
>{row.placeholder ?? "-"}</span
|
|
287
|
+
>
|
|
288
|
+
{:else}
|
|
289
|
+
{@render display(row, true)}
|
|
290
|
+
{/if}
|
|
291
|
+
{@render row.action?.()}
|
|
292
|
+
</dd>
|
|
293
|
+
</div>
|
|
294
|
+
{/each}
|
|
295
|
+
</dl>
|
|
296
|
+
{@render trailing?.()}
|
|
297
|
+
</div>
|
|
298
|
+
{:else if shown.length > 0}
|
|
219
299
|
<div class={cn("flex flex-col gap-2", className)}>
|
|
220
300
|
{#if title}
|
|
221
301
|
<h2 class="text-xs font-medium tracking-wide text-muted-foreground uppercase">{title}</h2>
|
package/src/record/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The view types of a client or project record. Plain data in, nothing loaded:
|
|
2
2
|
// the host maps its own rows onto these and passes them down.
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
3
4
|
import type { PhosphorIcon } from "../lib/icon-types.js";
|
|
4
5
|
import type { Tone } from "../lib/tone.js";
|
|
5
6
|
|
|
@@ -186,6 +187,12 @@ export type InfoRow = {
|
|
|
186
187
|
/** Without it the row is read-only. A multi row passes the picked keys. */
|
|
187
188
|
onSave?: (value: string | string[]) => void | Promise<void>;
|
|
188
189
|
divider?: boolean;
|
|
190
|
+
/** `layout="row"` only. Replaces the label text inside the `<dt>` (key chips, say). `label` still names the row. */
|
|
191
|
+
term?: Snippet;
|
|
192
|
+
/** `layout="row"` only. Replaces the drawn value with arbitrary content. A row with one is never hidden for lacking a value. */
|
|
193
|
+
content?: Snippet;
|
|
194
|
+
/** `layout="row"` only. Draws after the value, inside the `<dd>`. */
|
|
195
|
+
action?: Snippet;
|
|
189
196
|
};
|
|
190
197
|
|
|
191
198
|
/** A kind of event: how its filter chip and its default row read. */
|