@nanisoft/prism-ui 0.15.0 → 0.16.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/.tsbuildinfo +1 -1
- package/dist/blocks/data-table-01/data-table.d.ts +10 -0
- package/dist/blocks/data-table-01/data-table.d.ts.map +1 -1
- package/dist/blocks/data-table-01/data-table.js +2 -1
- package/dist/blocks/page-header-01/page-header.d.ts +7 -0
- package/dist/blocks/page-header-01/page-header.d.ts.map +1 -1
- package/dist/blocks/page-header-01/page-header.js +9 -1
- package/dist/blocks/run-console-01/run-console.d.ts.map +1 -1
- package/dist/blocks/run-console-01/run-console.js +2 -1
- package/dist/blocks/site-footer/site-footer.d.ts +16 -0
- package/dist/blocks/site-footer/site-footer.d.ts.map +1 -1
- package/dist/blocks/site-footer/site-footer.js +17 -1
- package/dist/blocks/stack-grid-01/stack-grid.d.ts +13 -0
- package/dist/blocks/stack-grid-01/stack-grid.d.ts.map +1 -1
- package/dist/blocks/stack-grid-01/stack-grid.js +14 -1
- package/dist/components/ui/diagram.d.ts +38 -7
- package/dist/components/ui/diagram.d.ts.map +1 -1
- package/dist/components/ui/diagram.js +134 -52
- package/dist/components/ui/fact-list.d.ts +17 -0
- package/dist/components/ui/fact-list.d.ts.map +1 -1
- package/dist/components/ui/fact-list.js +18 -1
- package/dist/components/ui/prose.d.ts +11 -0
- package/dist/components/ui/prose.d.ts.map +1 -1
- package/dist/components/ui/prose.js +12 -1
- package/dist/components/ui/pulse-graph.d.ts +36 -4
- package/dist/components/ui/pulse-graph.d.ts.map +1 -1
- package/dist/components/ui/pulse-graph.js +174 -60
- package/dist/components/ui/section.d.ts +33 -31
- package/dist/components/ui/section.d.ts.map +1 -1
- package/dist/components/ui/section.js +33 -31
- package/dist/lib/figure.d.ts +149 -0
- package/dist/lib/figure.d.ts.map +1 -0
- package/dist/lib/figure.js +242 -0
- package/dist/live/run-stream-01/run-stream.d.ts.map +1 -1
- package/dist/live/run-stream-01/run-stream.js +2 -1
- package/dist/pages/blog-post-page/index.d.ts +36 -15
- package/dist/pages/blog-post-page/index.d.ts.map +1 -1
- package/dist/pages/blog-post-page/index.js +35 -10
- package/dist/pages/docs-shell/docs-shell.d.ts +131 -17
- package/dist/pages/docs-shell/docs-shell.d.ts.map +1 -1
- package/dist/pages/docs-shell/docs-shell.js +230 -24
- package/dist/pages/error-page/index.d.ts.map +1 -1
- package/dist/pages/error-page/index.js +7 -3
- package/dist/pages/not-found-page/index.d.ts.map +1 -1
- package/dist/pages/not-found-page/index.js +4 -3
- package/dist/styles.css +131 -12
- package/package.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { INK_CLEARANCE, fitPoints, labelHalfHeight, labelHalfWidth, strokeBetween, } from '../../lib/figure.js';
|
|
2
3
|
import { cn } from '../../lib/utils.js';
|
|
3
4
|
/**
|
|
4
5
|
* The drawing's accessible name, derived from the text its own marks carry.
|
|
@@ -37,9 +38,40 @@ function derivedName(nodes) {
|
|
|
37
38
|
* drawing would come out with 3px labels in one call and 40px labels in another.
|
|
38
39
|
* Fitting the caller's points into a fixed canvas makes the label a constant
|
|
39
40
|
* and makes the drawing a function of the shape rather than of the scale.
|
|
41
|
+
*
|
|
42
|
+
* **A fixed canvas fixes the ratio, and the ratio was the half of the argument
|
|
43
|
+
* that was missing.** Every label's rendered size is its size in user units times
|
|
44
|
+
* the rendered width over this number, so a drawing that is right at a desktop
|
|
45
|
+
* width shrinks with its container until it says nothing. In the 342 pixels a
|
|
46
|
+
* phone leaves a Diagram, the largest label drawn here came to just under seven
|
|
47
|
+
* pixels. The canvas is still the right answer and it is what keeps a label a
|
|
48
|
+
* constant rather than a function of the caller's arithmetic; what it does not do
|
|
49
|
+
* is set a floor, and `LEGIBLE_MIN_WIDTH` is that floor.
|
|
40
50
|
*/
|
|
41
51
|
const CANVAS_WIDTH = 640;
|
|
42
52
|
const CANVAS_HEIGHT = 400;
|
|
53
|
+
/**
|
|
54
|
+
* The width below which the drawing stops being legible, as a spacing multiple.
|
|
55
|
+
*
|
|
56
|
+
* The floor is `CANVAS_WIDTH` itself, so the rule is one sentence: the drawing is
|
|
57
|
+
* never rendered narrower than its own coordinate space, and one user unit is
|
|
58
|
+
* therefore never less than one pixel. Every label is then at least its own size
|
|
59
|
+
* in user units, and the smallest of them is `RELATION_NAME_SIZE`, which is the
|
|
60
|
+
* size `--text-xs` resolves to. So no label this drawing sets is smaller than
|
|
61
|
+
* `text-xs`, and at the floor the smallest is exactly it.
|
|
62
|
+
*
|
|
63
|
+
* **A container narrower than the floor scrolls rather than shrinking, and that
|
|
64
|
+
* is the answer this repository already gives a table and a board.** A table that
|
|
65
|
+
* restacks has stopped being a table, and a schematic whose labels are six pixels
|
|
66
|
+
* tall has stopped being a schematic: it is the shape with nothing written on it
|
|
67
|
+
* a reader can read. So the drawing holds its size and the container moves under
|
|
68
|
+
* it, which is what `Table` does with seven columns and what `Gantt01` does with
|
|
69
|
+
* a schedule of names and bars.
|
|
70
|
+
*
|
|
71
|
+
* `160` is `CANVAS_WIDTH` as a multiple of the authored spacing base, which is
|
|
72
|
+
* how a width nobody held a separate decision for is written.
|
|
73
|
+
*/
|
|
74
|
+
const LEGIBLE_MIN_WIDTH = 'min-w-160';
|
|
43
75
|
/**
|
|
44
76
|
* The band left empty around the fitted points, in user units.
|
|
45
77
|
*
|
|
@@ -65,11 +97,17 @@ const NODE_RADIUS = 5;
|
|
|
65
97
|
* a font size is a coordinate and not a length on the page. These are geometry,
|
|
66
98
|
* the one thing Tailwind stays authoritative for, and they are the only numbers
|
|
67
99
|
* in this file that are not a token.
|
|
100
|
+
*
|
|
101
|
+
* The two supporting sizes are twelve user units, which is what `--text-xs`
|
|
102
|
+
* resolves to, so at `LEGIBLE_MIN_WIDTH` they render at exactly that step and
|
|
103
|
+
* never below it. The name is one unit larger, which is the same ratio between
|
|
104
|
+
* the two this drawing has always drawn, so the hierarchy between a name and the
|
|
105
|
+
* line under it is the hierarchy it was.
|
|
68
106
|
*/
|
|
69
107
|
const NODE_NAME_SIZE = 13;
|
|
70
108
|
const NODE_NAME_OFFSET = 20;
|
|
71
|
-
const NODE_SUBTITLE_SIZE =
|
|
72
|
-
const RELATION_NAME_SIZE =
|
|
109
|
+
const NODE_SUBTITLE_SIZE = 12;
|
|
110
|
+
const RELATION_NAME_SIZE = 12;
|
|
73
111
|
/**
|
|
74
112
|
* The subtitle's baseline, measured down from the mark's centre.
|
|
75
113
|
*
|
|
@@ -80,35 +118,51 @@ const RELATION_NAME_SIZE = 11;
|
|
|
80
118
|
*/
|
|
81
119
|
const NODE_SUBTITLE_OFFSET = NODE_NAME_OFFSET + NODE_SUBTITLE_SIZE + 5;
|
|
82
120
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
121
|
+
* Where a relation's label sits, which is beside the line and never on it.
|
|
122
|
+
*
|
|
123
|
+
* The offset is the label's own half-width measured across the line plus its half
|
|
124
|
+
* height, then the clearance. A single number cannot do this: on a horizontal
|
|
125
|
+
* line a label's width runs along the line and its height across it, and on a
|
|
126
|
+
* vertical line those two swap, so a constant that clears a horizontal relation
|
|
127
|
+
* leaves a vertical one sitting on its own stroke.
|
|
128
|
+
*
|
|
129
|
+
* It is measured from the middle of the stroke a reader can see rather than of
|
|
130
|
+
* the line between the two mark centres, so the label stays beside the line that
|
|
131
|
+
* was drawn.
|
|
85
132
|
*/
|
|
86
|
-
|
|
133
|
+
function relationLabel(label, segment) {
|
|
134
|
+
const dx = segment.to.x - segment.from.x;
|
|
135
|
+
const dy = segment.to.y - segment.from.y;
|
|
136
|
+
const length = Math.hypot(dx, dy);
|
|
137
|
+
const middle = {
|
|
138
|
+
x: (segment.from.x + segment.to.x) / 2,
|
|
139
|
+
y: (segment.from.y + segment.to.y) / 2,
|
|
140
|
+
};
|
|
141
|
+
if (length === 0)
|
|
142
|
+
return middle;
|
|
143
|
+
const across = { x: -dy / length, y: dx / length };
|
|
144
|
+
const extent = Math.abs(across.x) * labelHalfWidth(label, RELATION_NAME_SIZE) +
|
|
145
|
+
Math.abs(across.y) * labelHalfHeight(RELATION_NAME_SIZE);
|
|
146
|
+
const offset = extent + INK_CLEARANCE;
|
|
147
|
+
return { x: middle.x + across.x * offset, y: middle.y + across.y * offset };
|
|
148
|
+
}
|
|
87
149
|
/**
|
|
88
|
-
* The
|
|
89
|
-
*
|
|
150
|
+
* The labels this drawing prints under one node, which are what a stroke leaving it
|
|
151
|
+
* has to be trimmed against.
|
|
90
152
|
*
|
|
91
|
-
*
|
|
92
|
-
* the
|
|
93
|
-
*
|
|
94
|
-
*
|
|
153
|
+
* Each label is its own obstacle rather than one box enclosing both, and that is the
|
|
154
|
+
* whole of the trim rule as it applies here: a node's name and its second line are
|
|
155
|
+
* both below its mark, so a relation leaving sideways runs along the mark's centre
|
|
156
|
+
* line and clears both, while one leaving downward enters both. The arithmetic is
|
|
157
|
+
* `lib/figure`, which is where the shared answer lives; what is left here is the two
|
|
158
|
+
* labels this drawing prints and where it prints them.
|
|
95
159
|
*/
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const minY = Math.min(...ys);
|
|
103
|
-
const spanX = Math.max(...xs) - minX;
|
|
104
|
-
const spanY = Math.max(...ys) - minY;
|
|
105
|
-
const roomX = CANVAS_WIDTH - PADDING * 2;
|
|
106
|
-
const roomY = CANVAS_HEIGHT - PADDING * 2;
|
|
107
|
-
const scale = Math.min(spanX === 0 ? Number.POSITIVE_INFINITY : roomX / spanX, spanY === 0 ? Number.POSITIVE_INFINITY : roomY / spanY);
|
|
108
|
-
const unit = Number.isFinite(scale) && scale > 0 ? scale : 1;
|
|
109
|
-
const offsetX = PADDING + (roomX - spanX * unit) / 2 - minX * unit;
|
|
110
|
-
const offsetY = PADDING + (roomY - spanY * unit) / 2 - minY * unit;
|
|
111
|
-
return (node) => ({ x: node.x * unit + offsetX, y: node.y * unit + offsetY });
|
|
160
|
+
function nodeLabels(node) {
|
|
161
|
+
const labels = [{ text: node.name, size: NODE_NAME_SIZE, offset: NODE_NAME_OFFSET }];
|
|
162
|
+
if (node.subtitle !== undefined) {
|
|
163
|
+
labels.push({ text: node.subtitle, size: NODE_SUBTITLE_SIZE, offset: NODE_SUBTITLE_OFFSET });
|
|
164
|
+
}
|
|
165
|
+
return labels;
|
|
112
166
|
}
|
|
113
167
|
/**
|
|
114
168
|
* A diagram of named things and the relations between them.
|
|
@@ -175,15 +229,38 @@ function fit(nodes) {
|
|
|
175
229
|
* caller `stroke`, so the surface is `nodes`, `relations`, `label`,
|
|
176
230
|
* `decorative` and `className` and nothing else.
|
|
177
231
|
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
232
|
+
* **No stroke crosses a label, and every relation still reaches the marks it
|
|
233
|
+
* joins.** A relation used to run from one mark's centre to the other, and a node's
|
|
234
|
+
* name is printed under its mark, so every edge heading downward struck through the
|
|
235
|
+
* name of the node it left. Each label a node prints is now its own obstacle rather
|
|
236
|
+
* than part of one box enclosing the whole node, and a relation is trimmed only
|
|
237
|
+
* against the labels it would actually enter. That direction is the whole of the
|
|
238
|
+
* rule: a name and its second line both sit below their mark, so a relation
|
|
239
|
+
* leaving sideways runs along the mark's centre line and clears both, and it stops
|
|
240
|
+
* at the mark's own edge with the ink clearance between. Backing every relation off
|
|
241
|
+
* the widest label at its node instead detached them from the marks they connect,
|
|
242
|
+
* which is a drawing of a shape with no relations on it. The Component cannot know
|
|
243
|
+
* that two labels will not collide with one another, which is the caller's
|
|
244
|
+
* positions to settle, and it does know every mark's radius, both label offsets and
|
|
245
|
+
* every label's own width, because a monospaced label's width is its character
|
|
246
|
+
* count. A relation's
|
|
247
|
+
* own label is offset by its own extent across the line. Those are two pieces of
|
|
248
|
+
* geometry the drawing had and did not use, and `test/diagram.test.tsx` asserts
|
|
249
|
+
* both halves as geometry rather than the presence of a class name.
|
|
250
|
+
*
|
|
251
|
+
* **No label is drawn below `text-xs`.** The drawing is wrapped in a container
|
|
252
|
+
* that scrolls sideways, and the drawing itself holds a floor of its own
|
|
253
|
+
* coordinate space, so a phone sees the whole diagram at a legible size rather
|
|
254
|
+
* than the whole diagram at six pixels. That is the same bargain `Table` strikes
|
|
255
|
+
* with seven columns, and it is a bargain rather than an accident because the
|
|
256
|
+
* alternative is a picture of a shape with nothing written on it. A relation
|
|
180
257
|
* naming a node that is not in `nodes` is dropped, and the count of dropped
|
|
181
258
|
* relations is on the element as `data-unresolved-relations` rather than
|
|
182
259
|
* swallowed, because a line that silently did not draw is the failure this
|
|
183
260
|
* Component exists to make impossible.
|
|
184
261
|
*/
|
|
185
262
|
function Diagram({ nodes, relations, label, decorative = false, className, }) {
|
|
186
|
-
const place =
|
|
263
|
+
const place = fitPoints(nodes, { width: CANVAS_WIDTH, height: CANVAS_HEIGHT, padding: PADDING });
|
|
187
264
|
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
188
265
|
// An empty `label` is the same answer as no `label`, so the drawing is named by
|
|
189
266
|
// what its own marks carry rather than announced as an unnamed image. A blank
|
|
@@ -193,28 +270,33 @@ function Diagram({ nodes, relations, label, decorative = false, className, }) {
|
|
|
193
270
|
const drawn = relations.flatMap((relation) => {
|
|
194
271
|
const from = byId.get(relation.from);
|
|
195
272
|
const to = byId.get(relation.to);
|
|
196
|
-
|
|
273
|
+
if (!from || !to)
|
|
274
|
+
return [];
|
|
275
|
+
// Each end carries its mark's centre and the labels printed under it, because
|
|
276
|
+
// a stroke is trimmed against the labels it would enter and the two are not the
|
|
277
|
+
// same thing.
|
|
278
|
+
const end = (node) => ({ at: place(node), labels: nodeLabels(node) });
|
|
279
|
+
return [{ relation, from: end(from), to: end(to) }];
|
|
197
280
|
});
|
|
198
|
-
return (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
})] }));
|
|
281
|
+
return (
|
|
282
|
+
// The sideways region, for the reason `LEGIBLE_MIN_WIDTH` states: the drawing
|
|
283
|
+
// holds a legible floor and a container narrower than it moves under the
|
|
284
|
+
// drawing rather than the drawing shrinking below what it says.
|
|
285
|
+
_jsx("div", { "data-slot": "diagram-container", className: "w-full overflow-x-auto", children: _jsxs("svg", { "data-slot": "diagram", "data-unresolved-relations": relations.length - drawn.length, viewBox: `0 0 ${CANVAS_WIDTH} ${CANVAS_HEIGHT}`, xmlns: "http://www.w3.org/2000/svg", role: decorative ? undefined : 'img', "aria-label": decorative ? undefined : name, "aria-hidden": decorative || undefined, className: cn('h-auto w-full', LEGIBLE_MIN_WIDTH, className), children: [drawn.map(({ relation, from, to }) => {
|
|
286
|
+
const line = strokeBetween(from.at, to.at, NODE_RADIUS, from.labels, to.labels);
|
|
287
|
+
const at = relationLabel(relation.label, line);
|
|
288
|
+
return (_jsxs("g", { "data-slot": "diagram-relation", "data-relation": `${relation.from}-${relation.to}`, children: [_jsx("path", { d: `M ${line.from.x} ${line.from.y} L ${line.to.x} ${line.to.y}`, strokeWidth: 1, strokeDasharray: relation.indirect ? '4 4' : undefined, className: "stroke-muted-foreground fill-none" }), _jsx("text", { x: at.x, y: at.y, fontSize: RELATION_NAME_SIZE, textAnchor: "middle", className: "fill-muted-foreground font-mono", children: relation.label })] }, `${relation.from}-${relation.to}`));
|
|
289
|
+
}), nodes.map((node) => {
|
|
290
|
+
const at = place(node);
|
|
291
|
+
// `default` is the absence of the attribute, spelled the way the token
|
|
292
|
+
// build spells it, so a node on the base pack resolves its fill and its
|
|
293
|
+
// stroke from the page rather than from a selector nothing emits.
|
|
294
|
+
const boundary = node.pack !== undefined && node.pack !== 'default' ? node.pack : undefined;
|
|
295
|
+
return (_jsxs("g", { "data-slot": "diagram-node", "data-node": node.id, "data-emphasis": node.emphasis || undefined, children: [_jsx("circle", { cx: at.x, cy: at.y, r: NODE_RADIUS, "data-pack": boundary, strokeWidth: node.emphasis ? 2 : 1, className: cn(node.emphasis ? 'fill-accent stroke-brand-ink' : 'fill-card stroke-border') }), _jsx("text", { x: at.x, y: at.y + NODE_NAME_OFFSET, fontSize: NODE_NAME_SIZE, textAnchor: "middle", className: "fill-foreground font-mono", children: node.name }), node.subtitle ? (_jsx("text", { "data-slot": "diagram-node-subtitle", x: at.x, y: at.y + NODE_SUBTITLE_OFFSET, fontSize: NODE_SUBTITLE_SIZE, textAnchor: "middle",
|
|
296
|
+
// Muted ink rather than the name's own: the line below a name is
|
|
297
|
+
// supporting, and a reader's eye reaches the name first. Both are
|
|
298
|
+
// contract roles, so a boundary above either moves it.
|
|
299
|
+
className: "fill-muted-foreground font-mono", children: node.subtitle })) : null] }, node.id));
|
|
300
|
+
})] }) }));
|
|
219
301
|
}
|
|
220
302
|
export { Diagram };
|
|
@@ -75,6 +75,23 @@ export type FactListProps = ComponentProps<'dl'> & {
|
|
|
75
75
|
* wraps and a value that does not are both common and centring one of them reads
|
|
76
76
|
* as an error.
|
|
77
77
|
*
|
|
78
|
+
* **The value is set flush left, and the reason is the values rather than the
|
|
79
|
+
* rows.** A right-aligned value is the right answer in a column of figures, where
|
|
80
|
+
* the digits are the same width and the comparison a reader makes is between the
|
|
81
|
+
* ends of them. This Component has no numeric mode, no `tabular-nums` and no
|
|
82
|
+
* monospaced arm, so nothing here is a figure and nothing lines up on its last
|
|
83
|
+
* digit: what lines up is the first letter of each answer, and right alignment
|
|
84
|
+
* moves that edge to a different place on every row. A value long enough to wrap
|
|
85
|
+
* is the case that shows it, because the second line starts wherever the first
|
|
86
|
+
* one ended and the column reads as a mistake rather than as a decision. `text-
|
|
87
|
+
* balance` stays, because it is the same reason it was there: a two-line answer
|
|
88
|
+
* should break where it breaks well, not where the measure runs out.
|
|
89
|
+
*
|
|
90
|
+
* A fact whose value *is* a figure is not the case this loses. A caller that wants
|
|
91
|
+
* a right-aligned figure column composes it: the value is a `ReactNode`, so a
|
|
92
|
+
* caller can pass the span it wants, and Prism holds no opinion about a value it
|
|
93
|
+
* did not lay out.
|
|
94
|
+
*
|
|
78
95
|
* The hairline between rows is `border-border`, so a list of facts reads as a
|
|
79
96
|
* table without becoming one: no header row, because a fact has no column
|
|
80
97
|
* header, and no `Table` semantics, because these are terms and their answers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fact-list.d.ts","sourceRoot":"","sources":["../../../src/components/ui/fact-list.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItD;;;;;;;GAOG;AACH,MAAM,MAAM,IAAI,GAAG;IACjB;;;;OAIG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB,6DAA6D;IAC7D,KAAK,EAAE,SAAS,CAAA;IAChB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG;IACjD;;;;OAIG;IACH,KAAK,EAAE,SAAS,IAAI,EAAE,CAAA;IACtB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"fact-list.d.ts","sourceRoot":"","sources":["../../../src/components/ui/fact-list.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItD;;;;;;;GAOG;AACH,MAAM,MAAM,IAAI,GAAG;IACjB;;;;OAIG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB,6DAA6D;IAC7D,KAAK,EAAE,SAAS,CAAA;IAChB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG;IACjD;;;;OAIG;IACH,KAAK,EAAE,SAAS,IAAI,EAAE,CAAA;IACtB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,iBAAS,QAAQ,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,aAAa,sCAuCrE;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|
|
@@ -20,6 +20,23 @@ import { cn } from '../../lib/utils.js';
|
|
|
20
20
|
* wraps and a value that does not are both common and centring one of them reads
|
|
21
21
|
* as an error.
|
|
22
22
|
*
|
|
23
|
+
* **The value is set flush left, and the reason is the values rather than the
|
|
24
|
+
* rows.** A right-aligned value is the right answer in a column of figures, where
|
|
25
|
+
* the digits are the same width and the comparison a reader makes is between the
|
|
26
|
+
* ends of them. This Component has no numeric mode, no `tabular-nums` and no
|
|
27
|
+
* monospaced arm, so nothing here is a figure and nothing lines up on its last
|
|
28
|
+
* digit: what lines up is the first letter of each answer, and right alignment
|
|
29
|
+
* moves that edge to a different place on every row. A value long enough to wrap
|
|
30
|
+
* is the case that shows it, because the second line starts wherever the first
|
|
31
|
+
* one ended and the column reads as a mistake rather than as a decision. `text-
|
|
32
|
+
* balance` stays, because it is the same reason it was there: a two-line answer
|
|
33
|
+
* should break where it breaks well, not where the measure runs out.
|
|
34
|
+
*
|
|
35
|
+
* A fact whose value *is* a figure is not the case this loses. A caller that wants
|
|
36
|
+
* a right-aligned figure column composes it: the value is a `ReactNode`, so a
|
|
37
|
+
* caller can pass the span it wants, and Prism holds no opinion about a value it
|
|
38
|
+
* did not lay out.
|
|
39
|
+
*
|
|
23
40
|
* The hairline between rows is `border-border`, so a list of facts reads as a
|
|
24
41
|
* table without becoming one: no header row, because a fact has no column
|
|
25
42
|
* header, and no `Table` semantics, because these are terms and their answers
|
|
@@ -33,6 +50,6 @@ import { cn } from '../../lib/utils.js';
|
|
|
33
50
|
function FactList({ facts, label, className, ...props }) {
|
|
34
51
|
if (facts.length === 0)
|
|
35
52
|
return null;
|
|
36
|
-
return (_jsxs("dl", { "data-slot": "fact-list", className: cn('flex w-full flex-col text-sm', className), ...props, children: [label ? (_jsx("dt", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase", children: label })) : null, facts.map((fact, index) => (_jsxs("div", { "data-slot": "fact-list-row", className: "border-border flex items-start justify-between gap-6 border-b py-2 last:border-b-0", children: [_jsx("dt", { className: "text-muted-foreground shrink-0 font-medium", children: fact.label }), _jsx("dd", { className: "text-
|
|
53
|
+
return (_jsxs("dl", { "data-slot": "fact-list", className: cn('flex w-full flex-col text-sm', className), ...props, children: [label ? (_jsx("dt", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase", children: label })) : null, facts.map((fact, index) => (_jsxs("div", { "data-slot": "fact-list-row", className: "border-border flex items-start justify-between gap-6 border-b py-2 last:border-b-0", children: [_jsx("dt", { className: "text-muted-foreground shrink-0 font-medium", children: fact.label }), _jsx("dd", { className: "text-left text-balance", children: fact.href ? (_jsx("a", { href: fact.href, rel: fact.newTab ? 'noopener noreferrer' : undefined, target: fact.newTab ? '_blank' : undefined, className: "underline underline-offset-4 hover:underline", children: fact.value })) : (fact.value) })] }, index)))] }));
|
|
37
54
|
}
|
|
38
55
|
export { FactList };
|
|
@@ -105,6 +105,17 @@ export type ProseProps = ComponentProps<'div'> & {
|
|
|
105
105
|
* is: the caller's own flow content.
|
|
106
106
|
*
|
|
107
107
|
* It is a server Component: no hook, no context and no client code.
|
|
108
|
+
*
|
|
109
|
+
* **The heading ladder here is the one `SectionHeading` walks, entered one rung
|
|
110
|
+
* down, and the reason is the measure rather than a second decision.** A run of
|
|
111
|
+
* prose is held to `--container-measure`, 42rem. The Display step is 3rem, and a
|
|
112
|
+
* 42rem column is fourteen of them, so a document title set at Display is a
|
|
113
|
+
* title broken into fragments rather than a title. So the child treatments set
|
|
114
|
+
* `h1` at `4xl` and descend one authored step per level to `lg` at `h5` and
|
|
115
|
+
* `h6`, which is the same scale, the same floor and the same rule about a
|
|
116
|
+
* heading never rendering smaller than the copy it introduces, entered at the
|
|
117
|
+
* rung that fits the column. A document and a page are therefore one hierarchy
|
|
118
|
+
* at different reading widths rather than two answers to one question.
|
|
108
119
|
*/
|
|
109
120
|
declare function Prose({ children, size, fullWidth, className, ...props }: ProseProps): import("react").JSX.Element;
|
|
110
121
|
export { Prose };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prose.d.ts","sourceRoot":"","sources":["../../../src/components/ui/prose.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItD;;;;;;;GAOG;AACH,QAAA,MAAM,WAAW;;;CAGP,CAAA;AAEV;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,WAAW,CAAA;AAEhD;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG;IAC/C;;;;OAIG;IACH,QAAQ,EAAE,SAAS,CAAA;IACnB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"prose.d.ts","sourceRoot":"","sources":["../../../src/components/ui/prose.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItD;;;;;;;GAOG;AACH,QAAA,MAAM,WAAW;;;CAGP,CAAA;AAEV;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,WAAW,CAAA;AAEhD;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG;IAC/C;;;;OAIG;IACH,QAAQ,EAAE,SAAS,CAAA;IACnB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,iBAAS,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAa,EAAE,SAAiB,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,UAAU,+BA0C7F;AAED,OAAO,EAAE,KAAK,EAAE,CAAA"}
|
|
@@ -65,13 +65,24 @@ const PROSE_SIZES = {
|
|
|
65
65
|
* is: the caller's own flow content.
|
|
66
66
|
*
|
|
67
67
|
* It is a server Component: no hook, no context and no client code.
|
|
68
|
+
*
|
|
69
|
+
* **The heading ladder here is the one `SectionHeading` walks, entered one rung
|
|
70
|
+
* down, and the reason is the measure rather than a second decision.** A run of
|
|
71
|
+
* prose is held to `--container-measure`, 42rem. The Display step is 3rem, and a
|
|
72
|
+
* 42rem column is fourteen of them, so a document title set at Display is a
|
|
73
|
+
* title broken into fragments rather than a title. So the child treatments set
|
|
74
|
+
* `h1` at `4xl` and descend one authored step per level to `lg` at `h5` and
|
|
75
|
+
* `h6`, which is the same scale, the same floor and the same rule about a
|
|
76
|
+
* heading never rendering smaller than the copy it introduces, entered at the
|
|
77
|
+
* rung that fits the column. A document and a page are therefore one hierarchy
|
|
78
|
+
* at different reading widths rather than two answers to one question.
|
|
68
79
|
*/
|
|
69
80
|
function Prose({ children, size = 'base', fullWidth = false, className, ...props }) {
|
|
70
81
|
return (_jsx("div", { "data-slot": "prose", "data-size": size, className: cn('text-pretty flex flex-col gap-4 leading-relaxed', fullWidth ? 'max-w-none' : 'max-w-measure', PROSE_SIZES[size],
|
|
71
82
|
// The block treatments. Every child is styled by its own tag so a
|
|
72
83
|
// consumer's plain elements read as the system, and the gaps the flex
|
|
73
84
|
// column owns are the rhythm between blocks.
|
|
74
|
-
'[&_a]:text-foreground [&_a]:underline [&_a]:underline-offset-4 [&_a:hover]:underline', '[&_blockquote]:border-border [&_blockquote]:text-muted-foreground [&_blockquote]:border-l-2 [&_blockquote]:pl-4 [&_blockquote]:italic', '[&_code]:bg-muted [&_code]:text-muted-foreground [&_code]:rounded-sm [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-xs', '[&_h1]:text-balance [&_h1]:text-
|
|
85
|
+
'[&_a]:text-foreground [&_a]:underline [&_a]:underline-offset-4 [&_a:hover]:underline', '[&_blockquote]:border-border [&_blockquote]:text-muted-foreground [&_blockquote]:border-l-2 [&_blockquote]:pl-4 [&_blockquote]:italic', '[&_code]:bg-muted [&_code]:text-muted-foreground [&_code]:rounded-sm [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-xs', '[&_h1]:text-balance [&_h1]:text-4xl [&_h1]:font-semibold [&_h1]:tracking-tight', '[&_h2]:text-balance [&_h2]:mt-2 [&_h2]:text-3xl [&_h2]:font-semibold [&_h2]:tracking-tight', '[&_h3]:text-balance [&_h3]:mt-2 [&_h3]:text-2xl [&_h3]:font-semibold [&_h3]:tracking-tight', '[&_h4]:text-balance [&_h4]:text-xl [&_h4]:font-semibold', '[&_h5]:text-balance [&_h5]:text-lg [&_h5]:font-semibold', '[&_h6]:text-balance [&_h6]:text-lg [&_h6]:font-semibold', '[&_hr]:bg-border [&_hr]:h-px [&_hr]:my-2', '[&_img]:rounded-lg', '[&_li]:leading-relaxed [&_ol]:list-decimal [&_ol]:pl-6 [&_ul]:list-disc [&_ul]:pl-6', '[&_p]:leading-relaxed', '[&_pre]:bg-muted [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:p-4 [&_pre]:font-mono [&_pre]:text-xs', '[&_pre_code]:bg-transparent [&_pre_code]:p-0', '[&_strong]:font-semibold',
|
|
75
86
|
// The one treatment that changes a box type rather than decorating one.
|
|
76
87
|
// The reasoning, and the two spec rules it rests on, are in this
|
|
77
88
|
// component's JSDoc: a `display: table` box cannot be a scroll container,
|
|
@@ -87,7 +87,12 @@ type PulseFigure = {
|
|
|
87
87
|
relations?: readonly PulseRelation[];
|
|
88
88
|
/**
|
|
89
89
|
* Layout only, exactly as on every Component: grid placement and width.
|
|
90
|
-
* Changing a Prism-owned visual property from here is prohibited
|
|
90
|
+
* Changing a Prism-owned visual property from here is prohibited, and this
|
|
91
|
+
* Component spreads no other prop, so there is no second way to do it.
|
|
92
|
+
*
|
|
93
|
+
* It lands on the drawing inside the sideways region rather than on the region
|
|
94
|
+
* itself, so a caller narrowing the figure narrows the drawing and the region
|
|
95
|
+
* still holds the floor.
|
|
91
96
|
*/
|
|
92
97
|
className?: string;
|
|
93
98
|
};
|
|
@@ -175,9 +180,36 @@ export type PulseGraphProps = PulseFigure & ({
|
|
|
175
180
|
* sentence is what a screen reader gets. Pass `decorative` when that sentence
|
|
176
181
|
* already says what the drawing shows.
|
|
177
182
|
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
183
|
+
* **No stroke crosses a label, and every connector still reaches the marks it
|
|
184
|
+
* joins.** An edge used to run from one mark's centre to the other, and a node's
|
|
185
|
+
* name is printed under its mark, so every edge heading downward struck through the
|
|
186
|
+
* name of the node it left. Each label a node prints is now its own obstacle rather
|
|
187
|
+
* than part of one box enclosing the whole node, and an edge is trimmed only against
|
|
188
|
+
* the labels it would actually enter. That direction is the whole of the rule: a
|
|
189
|
+
* name and a note both sit below their mark, so an edge leaving sideways runs along
|
|
190
|
+
* the mark's centre line and clears both, and it stops at the mark's own edge with
|
|
191
|
+
* the ink clearance between. Backing every edge off the widest label at its node
|
|
192
|
+
* instead detached all five connectors on a six-stage rail from the circles they
|
|
193
|
+
* connect, which is a drawing of a shape with no relations on it. The Component
|
|
194
|
+
* cannot know that two labels will not collide with one another, which is the
|
|
195
|
+
* caller's positions to settle, and it does know every mark's radius, both label
|
|
196
|
+
* offsets and every label's own width, because a monospaced label's width is its
|
|
197
|
+
* character count. The rail is held to
|
|
198
|
+
* the same clearance rather than to a fixed distance from the canvas bottom,
|
|
199
|
+
* because a note is the deepest ink a node draws and a lane whose mark lands low
|
|
200
|
+
* put it under the rail. `test/pulse-graph.test.tsx` asserts both halves as
|
|
201
|
+
* geometry rather than the presence of a class name.
|
|
202
|
+
*
|
|
203
|
+
* **No label is drawn below `text-xs`.** The drawing is wrapped in a container
|
|
204
|
+
* that scrolls sideways, and the drawing itself holds a floor of its own
|
|
205
|
+
* coordinate space, so a phone sees the whole graph at a legible size rather than
|
|
206
|
+
* the whole graph at six pixels. That is the same bargain `Table` strikes with
|
|
207
|
+
* seven columns, and it is a bargain rather than an accident because the
|
|
208
|
+
* alternative is a picture of a shape with nothing written on it.
|
|
209
|
+
*
|
|
210
|
+
* Positions are the caller's, and two labels crossing one another is still the
|
|
211
|
+
* caller's to settle. A relation naming a node that is not in `nodes` is dropped
|
|
212
|
+
* and counted on the element.
|
|
181
213
|
*/
|
|
182
214
|
declare function PulseGraph({ nodes, relations, label, decorative, className, }: PulseGraphProps): import("react").JSX.Element;
|
|
183
215
|
export { PulseGraph };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pulse-graph.d.ts","sourceRoot":"","sources":["../../../src/components/ui/pulse-graph.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pulse-graph.d.ts","sourceRoot":"","sources":["../../../src/components/ui/pulse-graph.tsx"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,oEAAoE;IACpE,EAAE,EAAE,MAAM,CAAA;IACV;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,CAAC,EAAE,MAAM,CAAA;IACT,0EAA0E;IAC1E,CAAC,EAAE,MAAM,CAAA;IACT;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,mEAAmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAA;IACV,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,iDAAiD;AACjD,KAAK,WAAW,GAAG;IACjB;;;;;OAKG;IACH,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IAC3B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;IACpC;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,GACvC,CACI;IACE;;;;OAIG;IACH,UAAU,EAAE,IAAI,CAAA;IAChB,KAAK,CAAC,EAAE,KAAK,CAAA;CACd,GACD;IACE,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,KAAK,CAAA;CACnB,CACJ,CAAA;AA2MH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8FG;AACH,iBAAS,UAAU,CAAC,EAClB,KAAK,EACL,SAAc,EACd,KAAK,EACL,UAAkB,EAClB,SAAS,GACV,EAAE,eAAe,+BAoMjB;AAED,OAAO,EAAE,UAAU,EAAE,CAAA"}
|