@atelic-action/ui 3.7.0 → 3.8.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/README.md +2 -2
- package/package.json +1 -1
- package/src/artifact/Shot.tsx +41 -5
- package/src/styles/artifact.css +14 -0
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ Every export, grouped by the entry it is imported from. The gallery column links
|
|
|
39
39
|
| `Button`, `Chip`, `Eyebrow`, `Lead`, `SectionHeading` | `@atelic-action/ui/primitives` | The small pieces a page is set in | [#primitives](https://ui.atelic.me/#primitives) |
|
|
40
40
|
| `GradeChip`, `gradeLabel`, `WriteupGrade` | `@atelic-action/ui/artifact` | A section's grade, A to D, as a chip in its color, the letter alone, and the grade type | [#grade-chip](https://ui.atelic.me/#grade-chip) |
|
|
41
41
|
| `SignOff`, `WriteupPerson` | `@atelic-action/ui/artifact` | The close a document ends on, and the person it names | [#sign-off](https://ui.atelic.me/#sign-off) |
|
|
42
|
-
| `Shot`, `WriteupShot` | `@atelic-action/ui/artifact` | The owner's own screen, opening the full picture, over its caption | [#shot](https://ui.atelic.me/#shot) |
|
|
42
|
+
| `Shot`, `WriteupShot` | `@atelic-action/ui/artifact` | The owner's own screen, opening the full picture, over its caption; a portrait crop is marked `data-portrait` and held to 200px (1.8in in print) | [#shot](https://ui.atelic.me/#shot) |
|
|
43
43
|
| `Paragraph`, `Runs`, `ArtifactAnchor`, `isEmpty`, `ArtifactLink`, `ArtifactParagraph` | `@atelic-action/ui/artifact` | Plain data text a routine can write as JSON: a paragraph, its runs alone, one link, the empty test, and the two types | [#paragraph](https://ui.atelic.me/#paragraph) |
|
|
44
44
|
| `ArtifactShell` | `@atelic-action/ui/artifact` | The chrome around a document page: skip link, dark header, credit band, watermark | [#site-header](https://ui.atelic.me/#site-header) (its dark bar) |
|
|
45
45
|
| `AppErrorBoundary`, `ErrorFallback` | `@atelic-action/ui/artifact` | The error boundary and the page it falls back to | none |
|
|
@@ -338,7 +338,7 @@ The two pieces a reader sees are ordinary components, in `@atelic-action/ui/comp
|
|
|
338
338
|
|
|
339
339
|
`@atelic-action/ui/artifact` is the pieces an artifact document is built from and the site around it, so a fix to a piece reaches every business's documents with one version bump. The composed documents themselves (`Writeup`, `Read`, `Report`, `Note`, and the sealed `Gate`, with their content types and their stylesheet) live in [`@atelic-action/artifact`](https://github.com/atelic-action/artifact) since 2.0.0, which depends on this package for everything below:
|
|
340
340
|
|
|
341
|
-
- **The pieces:** `GradeChip` and `gradeLabel` with the `WriteupGrade` type, `SignOff` with the `WriteupPerson` type, `Shot` with the `WriteupShot` type, and the plain data text (`Paragraph`, `Runs`, `ArtifactAnchor`, and `isEmpty`, with the `ArtifactLink` and `ArtifactParagraph` types: a paragraph is a string or runs of text and `{ text, href }` links, so a routine can write a document as JSON).
|
|
341
|
+
- **The pieces:** `GradeChip` and `gradeLabel` with the `WriteupGrade` type, `SignOff` with the `WriteupPerson` type, `Shot` with the `WriteupShot` type (a crop taller than it is wide is measured in the browser, marked `data-portrait`, and held to 200px wide on screen and 1.8in in print; a layout that sizes its shots itself sets `--op-shot-portrait-max: none` to opt out, on screen and in print alike), and the plain data text (`Paragraph`, `Runs`, `ArtifactAnchor`, and `isEmpty`, with the `ArtifactLink` and `ArtifactParagraph` types: a paragraph is a string or runs of text and `{ text, href }` links, so a routine can write a document as JSON).
|
|
342
342
|
- **The shell:** `ArtifactShell`, the `.atelic-ui` scope with the skip link, the dark header, the credit band, and the watermark around a page.
|
|
343
343
|
- **The contract and the head:** the `ArtifactConfig` types, and `buildPageHead` and `canonicalURL` with their `PageMeta` type.
|
|
344
344
|
- **Who opened it:** `mintToken`, `sealReader`, `openReader`, `readerId`, `readerLink`, and `whoIsReading`. The same six are also built and exported alone as `@atelic-action/ui/artifact/readers`, because a Playwright spec and a script run under Node, which compiles nothing inside `node_modules`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atelic-action/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Shared UI for the Atelic templates: site chrome, base styles, the component library, the access gate, and the app shell they install",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
package/src/artifact/Shot.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Maximize2 } from "lucide-react";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
3
|
import { newTabProps } from "../lib/newTabProps";
|
|
3
4
|
|
|
4
5
|
export interface WriteupShot {
|
|
@@ -10,6 +11,12 @@ export interface WriteupShot {
|
|
|
10
11
|
caption: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
/** The src of a loaded image taller than it is wide; null for any other. */
|
|
15
|
+
function portraitSrcOf(img: HTMLImageElement): string | null {
|
|
16
|
+
if (!img.naturalWidth || !img.naturalHeight) return null;
|
|
17
|
+
return img.naturalHeight > img.naturalWidth ? img.getAttribute("src") : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
/**
|
|
14
21
|
* The owner's own screen: the crop, opening the full screenshot in a new tab
|
|
15
22
|
* when it has one, over its caption. The writeup, the read, and the note in
|
|
@@ -18,21 +25,50 @@ export interface WriteupShot {
|
|
|
18
25
|
* own sheet. A linked crop says it opens: a hover title, and an expand badge
|
|
19
26
|
* hidden by default that a document may turn on (the note does, on its
|
|
20
27
|
* thumbnails).
|
|
28
|
+
*
|
|
29
|
+
* A crop taller than it is wide (a phone screen) marks the figure
|
|
30
|
+
* `data-portrait`, and the base sheet holds it far narrower than its column.
|
|
31
|
+
* The mark is measured in the browser, after the image has a size: on load,
|
|
32
|
+
* or in an effect when the image finished loading before hydration, so the
|
|
33
|
+
* prerendered page carries no mark and nothing reads the window at render.
|
|
21
34
|
*/
|
|
22
35
|
export function Shot({ shot }: { shot: WriteupShot }) {
|
|
36
|
+
const img = useRef<HTMLImageElement>(null);
|
|
37
|
+
// The src the portrait mark was measured on, so a new src never wears the
|
|
38
|
+
// last image's mark while it loads or if it fails.
|
|
39
|
+
const [portraitSrc, setPortraitSrc] = useState<string | null>(null);
|
|
40
|
+
const portrait = portraitSrc !== null && portraitSrc === shot.src;
|
|
41
|
+
|
|
42
|
+
// An image that loaded before hydration fired its load event before React
|
|
43
|
+
// was listening, so read its size once on mount. A later src change fires
|
|
44
|
+
// load again, which the handler below measures.
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const current = img.current;
|
|
47
|
+
if (current?.complete) setPortraitSrc(portraitSrcOf(current));
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
const picture = (
|
|
51
|
+
<img
|
|
52
|
+
ref={img}
|
|
53
|
+
src={shot.src}
|
|
54
|
+
alt={shot.alt}
|
|
55
|
+
loading="lazy"
|
|
56
|
+
onLoad={(event) => setPortraitSrc(portraitSrcOf(event.currentTarget))}
|
|
57
|
+
onError={() => setPortraitSrc(null)}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
|
|
23
61
|
return (
|
|
24
|
-
<figure className="op-shot">
|
|
62
|
+
<figure className="op-shot" data-portrait={portrait ? "" : undefined}>
|
|
25
63
|
{shot.href ? (
|
|
26
64
|
<a href={shot.href} title="Open the full picture" {...newTabProps(shot.href, true)}>
|
|
27
|
-
|
|
65
|
+
{picture}
|
|
28
66
|
<span className="op-shot-open" aria-hidden="true">
|
|
29
67
|
<Maximize2 size={12} />
|
|
30
68
|
</span>
|
|
31
69
|
</a>
|
|
32
70
|
) : (
|
|
33
|
-
<span className="op-shot-frame">
|
|
34
|
-
<img src={shot.src} alt={shot.alt} loading="lazy" />
|
|
35
|
-
</span>
|
|
71
|
+
<span className="op-shot-frame">{picture}</span>
|
|
36
72
|
)}
|
|
37
73
|
<figcaption>{shot.caption}</figcaption>
|
|
38
74
|
</figure>
|
package/src/styles/artifact.css
CHANGED
|
@@ -109,10 +109,24 @@
|
|
|
109
109
|
line-height: 1.45;
|
|
110
110
|
color: var(--op-ink-faint, var(--neutral-500));
|
|
111
111
|
}
|
|
112
|
+
/* A portrait crop (a phone screen, marked by Shot once its image has a
|
|
113
|
+
size) is held far narrower than its column, at the column's start, so
|
|
114
|
+
it never stands as a tall wall beside its findings. The cap is on the
|
|
115
|
+
figure, so the frame, its border, and the caption all hug the picture.
|
|
116
|
+
A layout that sizes its shots itself (fixed frames side by side) sets
|
|
117
|
+
--op-shot-portrait-max to none to keep them all the same size. The one
|
|
118
|
+
property governs print as well; a document that sets a size of its own
|
|
119
|
+
restates it under @media print for the sheet. */
|
|
120
|
+
.atelic-ui .op-shot[data-portrait] {
|
|
121
|
+
max-width: var(--op-shot-portrait-max, 200px);
|
|
122
|
+
}
|
|
112
123
|
@media print {
|
|
113
124
|
.atelic-ui .op-shot a {
|
|
114
125
|
cursor: auto;
|
|
115
126
|
}
|
|
127
|
+
.atelic-ui .op-shot[data-portrait] {
|
|
128
|
+
max-width: var(--op-shot-portrait-max, 1.8in);
|
|
129
|
+
}
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
/* ---- print: the shell's chrome steps aside on every artifact page ----
|