@atelic-action/ui 1.1.0 → 2.1.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 +21 -24
- package/dist/vite/artifact-config.js +6 -4
- package/package.json +1 -1
- package/src/artifact/GradeChip.tsx +4 -3
- package/src/artifact/Paragraph.tsx +2 -2
- package/src/artifact/Shot.tsx +6 -4
- package/src/artifact/SignOff.tsx +3 -2
- package/src/artifact/index.ts +17 -7
- package/src/components/DataTable.tsx +25 -18
- package/src/components/IconList.tsx +43 -0
- package/src/components/StackedBar.tsx +1 -1
- package/src/components/index.ts +1 -0
- package/src/styles/artifact.css +48 -2458
- package/src/styles/components.css +46 -1
- package/src/vite/artifact-config.ts +6 -4
- package/src/artifact/Gate.tsx +0 -340
- package/src/artifact/Note.tsx +0 -158
- package/src/artifact/Read.tsx +0 -406
- package/src/artifact/Report.tsx +0 -131
- package/src/artifact/Writeup.tsx +0 -767
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ The package ships source, not a build. Its TSX and CSS arrive as written and com
|
|
|
6
6
|
|
|
7
7
|
## Component Index
|
|
8
8
|
|
|
9
|
-
Every export, grouped by the entry it is imported from. The gallery column links the section of [ui.atelic.me](https://ui.atelic.me) that shows it; none marks an export that draws nothing.
|
|
9
|
+
Every export, grouped by the entry it is imported from. The gallery column links the section of [ui.atelic.me](https://ui.atelic.me) that shows it; none marks an export that draws nothing. The composed documents (the writeup, the read, the monthly page, the note, and the sealed document) are not here: since 2.0.0 they live in [`@atelic-action/artifact`](https://github.com/atelic-action/artifact), which builds them from the pieces this package exports.
|
|
10
10
|
|
|
11
11
|
| Export | Entry | What it is | Gallery |
|
|
12
12
|
|---|---|---|---|
|
|
@@ -20,6 +20,7 @@ Every export, grouped by the entry it is imported from. The gallery column links
|
|
|
20
20
|
| `Brand`, `BrandLogo`, `CallToAction`, `HoursRow`, `MenuContact`, `NavLink`, `Phone`, `PostalAddress`, `SiteLink`, `SocialLink`, `PageStop` | `@atelic-action/ui/chrome` | The shapes the chrome renders from (types) | none |
|
|
21
21
|
| `NotFound` | `@atelic-action/ui/components` | The body of the page an unknown path renders | [#not-found](https://ui.atelic.me/#not-found) |
|
|
22
22
|
| `Checklist` | `@atelic-action/ui/components` | A short list of checks, each passed or failed | [#checklist](https://ui.atelic.me/#checklist) |
|
|
23
|
+
| `IconList` | `@atelic-action/ui/components` | A list whose bullets are icons, one for the list or one per item, each in a tone | [#icon-list](https://ui.atelic.me/#icon-list) |
|
|
23
24
|
| `DataTable` | `@atelic-action/ui/components` | A table whose first column names each row, sortable, stacking on a phone | [#data-table](https://ui.atelic.me/#data-table) |
|
|
24
25
|
| `StatRow` | `@atelic-action/ui/components` | A row of large figures with periods, deltas, and trends | [#stat-row](https://ui.atelic.me/#stat-row) |
|
|
25
26
|
| `Callout` | `@atelic-action/ui/components` | One finding: an icon in its tone, the title, the explainer | [#callout](https://ui.atelic.me/#callout) |
|
|
@@ -32,12 +33,10 @@ Every export, grouped by the entry it is imported from. The gallery column links
|
|
|
32
33
|
| `Watermark` | `@atelic-action/ui/components` | The signed in reader's confidential stamp, pill or tiled | [#watermark](https://ui.atelic.me/#watermark) |
|
|
33
34
|
| `readCookie`, `Tone` | `@atelic-action/ui/components` | The cookie reader the watermark uses, and the tone type | none |
|
|
34
35
|
| `Button`, `Chip`, `Eyebrow`, `Lead`, `SectionHeading` | `@atelic-action/ui/primitives` | The small pieces a page is set in | [#primitives](https://ui.atelic.me/#primitives) |
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `Gate`, `normalizeEmail` | `@atelic-action/ui/artifact` | The sealed document, opened in the browser by the reader's own address | [#gate](https://ui.atelic.me/#gate) |
|
|
40
|
-
| `GradeChip`, `gradeLabel`, `SignOff` | `@atelic-action/ui/artifact` | The grade letter and the sign off the documents share | [#writeup](https://ui.atelic.me/#writeup) |
|
|
36
|
+
| `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) |
|
|
37
|
+
| `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) |
|
|
38
|
+
| `Shot`, `WriteupShot` | `@atelic-action/ui/artifact` | The owner's own screen, opening the full picture, over its caption | [#shot](https://ui.atelic.me/#shot) |
|
|
39
|
+
| `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) |
|
|
41
40
|
| `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) |
|
|
42
41
|
| `AppErrorBoundary`, `ErrorFallback` | `@atelic-action/ui/artifact` | The error boundary and the page it falls back to | none |
|
|
43
42
|
| `buildPageHead`, `canonicalUrl`, `newTabProps`, the `ArtifactConfig` types | `@atelic-action/ui/artifact` | A page's head, a link's new tab props, and a site's contract | none |
|
|
@@ -126,10 +125,12 @@ Prerendering and server rendering have to compile the package too, so keep it ou
|
|
|
126
125
|
```ts
|
|
127
126
|
// vite.config.ts
|
|
128
127
|
export default defineConfig({
|
|
129
|
-
ssr: { noExternal: ["@atelic-action/ui"] },
|
|
128
|
+
ssr: { noExternal: ["@atelic-action/ui", "@atelic-action/artifact"] },
|
|
130
129
|
});
|
|
131
130
|
```
|
|
132
131
|
|
|
132
|
+
A site that renders a document needs `@atelic-action/artifact` kept internal the same way, since it too ships TSX and CSS as written; a site that renders none lists `@atelic-action/ui` alone.
|
|
133
|
+
|
|
133
134
|
## CSS Import Order
|
|
134
135
|
|
|
135
136
|
Import the stylesheets in this order, from the root route or the site's base sheet:
|
|
@@ -141,8 +142,9 @@ Import the stylesheets in this order, from the root route or the site's base she
|
|
|
141
142
|
5. `@atelic-action/ui/styles/primitives.css`
|
|
142
143
|
6. `@atelic-action/ui/styles/gate.css`, on a site that mounts the access gate
|
|
143
144
|
7. `@atelic-action/ui/styles/artifact.css`, on a site that hosts the artifact documents
|
|
144
|
-
8.
|
|
145
|
-
9. The site's
|
|
145
|
+
8. `@atelic-action/artifact/styles/documents.css`, on the same site, for the documents themselves
|
|
146
|
+
9. The site's own CSS
|
|
147
|
+
10. The site's `theme.css`, last
|
|
146
148
|
|
|
147
149
|
Every rule in the package sheets is scoped under `.atelic-ui`, the one root class a consumer wears: a site puts it on its outermost wrapper, an app on the region that holds a package component. Which sheets it imports decides what it gets, so a site importing `base.css` gets the resets and typography under that class and an app importing `components.css` alone gets none of them.
|
|
148
150
|
|
|
@@ -330,9 +332,9 @@ The two pieces a reader sees are ordinary components, in `@atelic-action/ui/comp
|
|
|
330
332
|
|
|
331
333
|
## The Artifact Documents
|
|
332
334
|
|
|
333
|
-
`@atelic-action/ui/artifact` is
|
|
335
|
+
`@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:
|
|
334
336
|
|
|
335
|
-
- **The
|
|
337
|
+
- **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).
|
|
336
338
|
- **The shell:** `ArtifactShell`, the `.atelic-ui` scope with the skip link, the dark header, the credit band, and the watermark around a page.
|
|
337
339
|
- **The contract and the head:** the `ArtifactConfig` types, and `buildPageHead` and `canonicalUrl` with their `PageMeta` type.
|
|
338
340
|
- **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`.
|
|
@@ -351,13 +353,7 @@ export function ArtifactShell(props: Omit<ArtifactShellProps, "currentPath">) {
|
|
|
351
353
|
}
|
|
352
354
|
```
|
|
353
355
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
**The read** is what a customer gets at the end of an engagement's first week, before anything changes: the baseline's grades explained by cause (FOOTPRINT.md, What the Owner Gets). It is the writeup's sibling and wears the writeup's own `.op` root, so its column, head, grade chip and key, tier headings, section cards, screens, sources, close, and print rules are the writeup's; `artifact.css` adds only its `.rd` classes. Top to bottom: a title, the dateline, and a lede of two or three sentences; the gist table (Section, Grade, The Gist), drawn from the tiers so it can never disagree with the body, each section's name linking to its card and a section with no grade showing a quiet dash; then each tier's sections, each headed by its question with its trade name over it and its grade beside it, in three beats (what we found, with an optional short list of examples and any proof pictures in a row under their captions; why, one sentence set apart; what changes) and the tools it was read from; then the plan, numbered, each step a bold title and a sentence or two, with `who` naming a step that is the owner's own; then one closing line over the shared `SignOff`. A later tier (Conversion, once the analytics access lands) is one more entry in `tiers` and appears in the table and the body alike, with no change to the props, and a tier with no sections yet is left out of both. Every prop is plain data, the note's paragraph shape (a string, or runs of text and `{ text, href }` links), so a routine can write a read as JSON. The fixed words (the column heads, the beat labels, the plan's heading) are `readLabels`, and `labels` overrides any of them. A site wraps it in `ArtifactShell` exactly as it wraps the writeup; it sets no `@page`, so the writeup's stays the one that wins.
|
|
357
|
-
|
|
358
|
-
**The sealed document's classes.** `Gate` renders the opened document as the HTML it was sealed from, so its markup is the content's, and `artifact.css` styles what that HTML may wear inside `.gate-content`: the tier row (`.gate-tiers` of `.gate-tier` cards, `.is-picked` on the recommended one, `.gate-tier-name`, `.gate-tier-price`, `.gate-tier-note`, and the `.gate-tier-math` sum column of `.gate-tier-was`, `.gate-tier-credit`, `.gate-tier-mathlabel`, and the price), `ul.icon-list` for a list led by inline lucide icons (sized for the card inside a tier), `ul.gate-checks`, and, from 1.0.0 (ATE-599, pulled up from the Wynkoop and Outdoors Geek hosts), the `.gate-addons` row that seats two `.gate-tier.is-addon` monthly cards side by side under the builds, an inline SVG mark inside `.gate-tier-mathlabel` that stays on the label's line, and the `.gate-footnote` line under the tiers that the mark points at. A folder stylesheet that carried any of these deletes it.
|
|
359
|
-
|
|
360
|
-
The documents import no stylesheet of their own. A site imports `@atelic-action/ui/styles/artifact.css` in its base sheet after `gate.css` and before its `theme.css`, so every page carries the rules whichever route it is. The sheet holds two `@page` rules, the monthly page's and then the writeup's, and the later one wins for every page a site prints; the order inside the sheet is the order the template's build bundled them in and is load bearing.
|
|
356
|
+
The pieces import no stylesheet of their own. A site imports `@atelic-action/ui/styles/artifact.css` in its base sheet after `gate.css`, then `@atelic-action/artifact/styles/documents.css`, and then its `theme.css`, so every page carries the rules whichever route it is. This sheet holds the pieces alone (the grade chip, the sign off, the shot, and the shell's logo rule) and sets no `@page`; both `@page` rules, the monthly page's and then the writeup's, moved with the documents into their sheet, where their order stays load bearing.
|
|
361
357
|
|
|
362
358
|
### The Artifact Vite Config
|
|
363
359
|
|
|
@@ -370,7 +366,7 @@ import { artifactConfig } from "@atelic-action/ui/vite";
|
|
|
370
366
|
export default artifactConfig({ pages: ["/writeup", "/proposal", "/report"] });
|
|
371
367
|
```
|
|
372
368
|
|
|
373
|
-
`artifactConfig` returns the TanStack Start plugin with prerendering on, the crawler following links but kept out of `/images/` (where a prerendered page would overwrite a real file), one prerendered page for `/`, for each path the site names, and for `/404`, and no sitemap; the React plugin; `privateChunks()`; the `@` alias to the site's `src`, resolved from the directory Vite is run in; and `ssr.noExternal: ["@atelic-action/ui"]
|
|
369
|
+
`artifactConfig` returns the TanStack Start plugin with prerendering on, the crawler following links but kept out of `/images/` (where a prerendered page would overwrite a real file), one prerendered page for `/`, for each path the site names, and for `/404`, and no sitemap; the React plugin; `privateChunks()`; the `@` alias to the site's `src`, resolved from the directory Vite is run in; and `ssr.noExternal: ["@atelic-action/ui", "@atelic-action/artifact"]`, so the documents compile too. Nothing links to an artifact page, so a page a site leaves out of `pages` ships with no HTML in it. `artifactPages` is the list alone.
|
|
374
370
|
|
|
375
371
|
A site with a real need of its own passes `extend`, which is merged over the result with Vite's `mergeConfig`:
|
|
376
372
|
|
|
@@ -402,7 +398,7 @@ The package installs the scripts every artifact site used to carry as files, so
|
|
|
402
398
|
| `atelic-reader-link <email> [path] [--token <token>] [--qr \| --no-qr]` | Mints a named reader: prints the sealed line for `analytics.identify` and the link to send, and writes `public/images/qr.png` for a public writeup's first reader | `reader` |
|
|
403
399
|
| `atelic-serve-static` | Serves `dist/client` on `127.0.0.1` the way Vercel does, on `PORT` or 4173 | The `webServer` command in `playwright.config.ts` |
|
|
404
400
|
| `atelic-print-sheets <url or dist/client> <out.pdf> [sheets] [page] [port]` | Prints a page to PDF with headless Chrome, reports the sheet count, and fails when a count is given and missed | `print` |
|
|
405
|
-
| `atelic-seal-document <config.json> [out.json]` | Seals a document into the payload `Gate` reads, one wrapped key for each reader. The payload names no one: no address, and readers numbered `r1`, `r2` whatever the config calls them | `seal` |
|
|
401
|
+
| `atelic-seal-document <config.json> [out.json]` | Seals a document into the payload that `@atelic-action/artifact`'s `Gate` reads, one wrapped key for each reader. The payload names no one: no address, and readers numbered `r1`, `r2` whatever the config calls them | `seal` |
|
|
406
402
|
| `atelic-clean-sitemap` | Strips fragment and trailing slash duplicates out of `dist/client/sitemap.xml`; says so and passes when a based build wrote none | A marketing site's `build`, after vite |
|
|
407
403
|
| `atelic-check-base-path` | Fails the build when a root path in `dist/client` lacks the base path, or `vercel.json` lacks (or strays from) the two rewrites a based cut needs; a `VITE_BASE_PATH` rehearsal only warns about `vercel.json` off Vercel | Last step of a marketing site's `build` |
|
|
408
404
|
| `atelic-contact-canary` | Posts a token bearing test message to `$SITE_URL/api/contact` and passes only on a Resend id and the expected destination (`CONTACT_EXPECTED_TO`, `CONTACT_CANARY_TOKEN`); a `.invalid` `SITE_URL` is the off switch | `check:contact`, run daily by the canary workflow |
|
|
@@ -699,7 +695,8 @@ The blocks a page composes, each styled in `styles/components.css` inside the `a
|
|
|
699
695
|
| Component | What it is | Notes |
|
|
700
696
|
|---|---|---|
|
|
701
697
|
| `Checklist` | A short list of checks, each passed or failed | Two columns when there is room, one on a phone. `passLabel` and `failLabel` name the icons for a screen reader |
|
|
702
|
-
| `
|
|
698
|
+
| `IconList` | A list whose bullets are icons | `icon` and `tone` on the list set every bullet; an item's own override them. The bullet is decoration, so a screen reader hears only the items. A dot when no icon is set; the text's color when no tone is |
|
|
699
|
+
| `DataTable` | A table whose first column names each row, sortable by any column marked `sortable` | A click on a heading sorts on a wide screen. On a phone each row stacks, every fact wearing its column's label, and a dropdown sorts. A cell is a node, or `{ display, sort }` when what it shows is not what it sorts by; a formatted number in a string (`"$12,047"`, `"0.24%"`) sorts as a number. It renders in `defaultSort` on the server. Every other row sits on a tinted ground; `striped={false}` turns that off. A column marked `fit` hugs its content, for a swatch or a mark beside a figure |
|
|
703
700
|
| `StatRow` | A row of large figures, each over what it counts and its `period`. `variant="quiet"` is the glance row for a working page: a light figure in the text color over a small capitalized label, hairlines between; `unit` sets a figure's unit small beside it | `trend` draws an arrow before a figure that is itself a change. `deltas` list comparisons beneath: an arrow, how far, against what (`basis`), and the earlier figure (`was`), each in its own tone, since a spend falling and a cost falling read differently |
|
|
704
701
|
| `Callout` | One finding: an icon in its tone beside the title, the explainer under it | `variant="box"` sets it apart on a tinted ground, for something missing or owed |
|
|
705
702
|
| `Disclosure` | A section the reader opens and closes, in two forms chosen by whether `open` is passed | Controlled (`open` and `onToggle`), it is an app's card: the caller remembers what was folded, a closed body is not rendered, the label is the toggle, `meta` (a count, a flag) sits beside it outside its name, and `headingLevel` makes the label a real heading. Uncontrolled, it is a document's fold: a native `details` with a `summary` line that carries the point and `defaultOpen`, so it works before any script loads, find in page opens it, and it prints open. `Collapsible`, the fold's old name, was folded in at 0.18.0 |
|
|
@@ -892,14 +889,14 @@ An `expiresAt` an hour out keeps the refresh path quiet; one inside five minutes
|
|
|
892
889
|
|
|
893
890
|
## The Gallery
|
|
894
891
|
|
|
895
|
-
`gallery/` is two static pages. The Components shows everything the package exports that draws (the chrome, the page components, the sections, the
|
|
892
|
+
`gallery/` is two static pages. The Components shows everything the package exports that draws (the chrome, the page components, the sections, the pieces a document is built from, and the email) on its own with made up data for a fictional bakery, wearing the practice's own palette and type. It lives at [ui.atelic.me](https://ui.atelic.me), noindexed and disallowed in its `robots.txt`, since it is for the people building with the package and never search inventory. It imports the components and stylesheets from `src/` by relative path rather than from npm, so it always shows the code in the repo, and it reads the package name and version from `package.json` at build time. It sits outside `src/`, so it is never published. The second page, [ui.atelic.me/app](https://ui.atelic.me/app) (`gallery/app.html`), runs the app shell for a made up bakery's ops app, on Tailwind through `@tailwindcss/vite`, with its own stylesheet set up the way an app's `globals.css` is; the plugin leaves The Components' plain sheets as they are.
|
|
896
893
|
|
|
897
894
|
```bash
|
|
898
895
|
bun run gallery:dev # a dev server on the repo's own source
|
|
899
896
|
bun run gallery:build # the static page, written to gallery/dist
|
|
900
897
|
```
|
|
901
898
|
|
|
902
|
-
`vercel.json` builds it the same way and serves `gallery/dist`. Typecheck and lint cover it, and CI builds it, so a component change that breaks the gallery fails CI. **A new component gets a gallery section in the same change**, with each of its meaningfully different forms shown. `gallery/parts.tsx` holds the frame pieces (`Piece`, `Example`, and `Frame`, which holds a fixed piece inside the page), and a group of pieces lives in a file of its own beside `Gallery.tsx` (`chrome.tsx`, `
|
|
899
|
+
`vercel.json` builds it the same way and serves `gallery/dist`. Typecheck and lint cover it, and CI builds it, so a component change that breaks the gallery fails CI. **A new component gets a gallery section in the same change**, with each of its meaningfully different forms shown. `gallery/parts.tsx` holds the frame pieces (`Piece`, `Example`, and `Frame`, which holds a fixed piece inside the page), and a group of pieces lives in a file of its own beside `Gallery.tsx` (`chrome.tsx`, `artifact.tsx`, `email.tsx`), mounted there with one element. The composed documents are shown by `@atelic-action/artifact`, through its own stubs.
|
|
903
900
|
|
|
904
901
|
## Releasing
|
|
905
902
|
|
|
@@ -33,7 +33,8 @@ export function artifactConfig({ pages, extend }) {
|
|
|
33
33
|
const config = {
|
|
34
34
|
// Every stylesheet rides in the one base sheet the root route links
|
|
35
35
|
// (src/styles/base.css), the documents' own included, since they come from
|
|
36
|
-
// @atelic-action/
|
|
36
|
+
// @atelic-action/artifact and its pieces from @atelic-action/ui. Nothing
|
|
37
|
+
// imports CSS from a component, so there is no
|
|
37
38
|
// component bundle to fold: `build.cssCodeSplit: false`, which sat here to
|
|
38
39
|
// fold it, now fails the build, because TanStack Start looks for the
|
|
39
40
|
// `style.css` that setting emits and none is written. A site that gives a
|
|
@@ -73,10 +74,11 @@ export function artifactConfig({ pages, extend }) {
|
|
|
73
74
|
"@": resolve(process.cwd(), "./src"),
|
|
74
75
|
},
|
|
75
76
|
},
|
|
76
|
-
// @atelic-action/ui
|
|
77
|
-
//
|
|
77
|
+
// @atelic-action/ui and @atelic-action/artifact (the documents) both ship
|
|
78
|
+
// TSX and CSS as written, so the prerender's server build has to compile
|
|
79
|
+
// them rather than hand them to Node as externals.
|
|
78
80
|
ssr: {
|
|
79
|
-
noExternal: ["@atelic-action/ui"],
|
|
81
|
+
noExternal: ["@atelic-action/ui", "@atelic-action/artifact"],
|
|
80
82
|
},
|
|
81
83
|
};
|
|
82
84
|
return extend ? mergeConfig(config, extend) : config;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atelic-action/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.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",
|
|
@@ -16,9 +16,10 @@ export const gradeLabel: Record<WriteupGrade, string> = {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* The letter as a chip in its grade's color. The
|
|
20
|
-
*
|
|
21
|
-
* (styles/artifact.css), never in
|
|
19
|
+
* The letter as a chip in its grade's color. The documents in
|
|
20
|
+
* @atelic-action/artifact (the writeup, the read, the monthly page) wear it,
|
|
21
|
+
* so its styles live in the base sheet chain (styles/artifact.css), never in
|
|
22
|
+
* a document's stylesheet.
|
|
22
23
|
*/
|
|
23
24
|
export function GradeChip({ grade }: { grade: WriteupGrade }) {
|
|
24
25
|
return (
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { newTabProps } from "../lib/newTabProps";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Plain data text for the documents a routine
|
|
5
|
-
* and the read): a paragraph is a string, or runs of text and links in
|
|
4
|
+
* Plain data text for the documents in @atelic-action/artifact a routine
|
|
5
|
+
* can write as JSON (the note and the read): a paragraph is a string, or runs of text and links in
|
|
6
6
|
* order. A link opens in a new tab when it leaves the page, and in place
|
|
7
7
|
* when it is an anchor or a mail link.
|
|
8
8
|
*/
|
package/src/artifact/Shot.tsx
CHANGED
|
@@ -12,10 +12,12 @@ export interface WriteupShot {
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* The owner's own screen: the crop, opening the full screenshot in a new tab
|
|
15
|
-
* when it has one, over its caption. The writeup and the note
|
|
16
|
-
* its styles (.op-shot)
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* when it has one, over its caption. The writeup, the read, and the note in
|
|
16
|
+
* @atelic-action/artifact all wear it, so its styles (.op-shot) live in the
|
|
17
|
+
* base sheet chain (styles/artifact.css), and each document sizes it in its
|
|
18
|
+
* own sheet. A linked crop says it opens: a hover title, and an expand badge
|
|
19
|
+
* hidden by default that a document may turn on (the note does, on its
|
|
20
|
+
* thumbnails).
|
|
19
21
|
*/
|
|
20
22
|
export function Shot({ shot }: { shot: WriteupShot }) {
|
|
21
23
|
return (
|
package/src/artifact/SignOff.tsx
CHANGED
|
@@ -15,8 +15,9 @@ export interface WriteupPerson {
|
|
|
15
15
|
/**
|
|
16
16
|
* The sign off a printed artifact closes on: the script line, then the person
|
|
17
17
|
* lockup, the photo beside the name, the role, and the address
|
|
18
|
-
* (Brand/voice.md, Pitch Surfaces 9). The
|
|
19
|
-
*
|
|
18
|
+
* (Brand/voice.md, Pitch Surfaces 9). The documents in @atelic-action/artifact
|
|
19
|
+
* (the writeup, the read, the monthly page, the note) all wear it, so its
|
|
20
|
+
* styles live in the base sheet chain
|
|
20
21
|
* (styles/artifact.css); its container sets the type size and the color.
|
|
21
22
|
*/
|
|
22
23
|
export function SignOff({ signOff, person }: { signOff: string; person: WriteupPerson }) {
|
package/src/artifact/index.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The artifact entry: the pieces a document is built from and the site
|
|
3
|
+
* around it. The composed documents (the writeup, the read, the monthly
|
|
4
|
+
* page, the note, and the sealed document) live in @atelic-action/artifact,
|
|
5
|
+
* which builds them from what this entry exports.
|
|
6
|
+
*/
|
|
1
7
|
export { newTabProps } from "../lib/newTabProps";
|
|
2
8
|
export { AppErrorBoundary } from "./AppErrorBoundary";
|
|
3
9
|
export { ArtifactShell, type ArtifactShellProps } from "./ArtifactShell";
|
|
@@ -9,12 +15,16 @@ export type {
|
|
|
9
15
|
GateConfig,
|
|
10
16
|
} from "./artifactConfig";
|
|
11
17
|
export { ErrorFallback } from "./ErrorFallback";
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
|
|
18
|
+
export { GradeChip, gradeLabel, type WriteupGrade } from "./GradeChip";
|
|
19
|
+
export {
|
|
20
|
+
ArtifactAnchor,
|
|
21
|
+
type ArtifactLink,
|
|
22
|
+
type ArtifactParagraph,
|
|
23
|
+
isEmpty,
|
|
24
|
+
Paragraph,
|
|
25
|
+
Runs,
|
|
26
|
+
} from "./Paragraph";
|
|
15
27
|
export { buildPageHead, canonicalUrl, type PageMeta } from "./pageHead";
|
|
16
|
-
export * from "./Read";
|
|
17
|
-
export * from "./Report";
|
|
18
28
|
export {
|
|
19
29
|
mintToken,
|
|
20
30
|
openReader,
|
|
@@ -23,5 +33,5 @@ export {
|
|
|
23
33
|
sealReader,
|
|
24
34
|
whoIsReading,
|
|
25
35
|
} from "./readers";
|
|
26
|
-
export {
|
|
27
|
-
export
|
|
36
|
+
export { Shot, type WriteupShot } from "./Shot";
|
|
37
|
+
export { SignOff, type WriteupPerson } from "./SignOff";
|
|
@@ -13,6 +13,8 @@ export interface DataTableColumn {
|
|
|
13
13
|
sortable?: boolean;
|
|
14
14
|
/** The direction a first click sorts in; numbers usually read best largest first. */
|
|
15
15
|
firstSort?: SortDirection;
|
|
16
|
+
/** The column hugs its content (a swatch, a mark) instead of sharing the width. */
|
|
17
|
+
fit?: boolean;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/** A cell whose display is not what it sorts by, e.g. an icon over a number. */
|
|
@@ -37,6 +39,8 @@ export interface DataTableProps {
|
|
|
37
39
|
defaultSort?: { column: number; direction: SortDirection };
|
|
38
40
|
/** The label on the phone's sort control. */
|
|
39
41
|
sortLabel?: string;
|
|
42
|
+
/** Every other row on a tinted ground, so a wide table reads across. On by default. */
|
|
43
|
+
striped?: boolean;
|
|
40
44
|
className?: string;
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -71,6 +75,7 @@ export function DataTable({
|
|
|
71
75
|
rows,
|
|
72
76
|
defaultSort,
|
|
73
77
|
sortLabel = "Sort by",
|
|
78
|
+
striped = true,
|
|
74
79
|
className,
|
|
75
80
|
}: DataTableProps) {
|
|
76
81
|
const [sort, setSort] = useState(defaultSort ?? null);
|
|
@@ -102,7 +107,7 @@ export function DataTable({
|
|
|
102
107
|
};
|
|
103
108
|
|
|
104
109
|
return (
|
|
105
|
-
<div className={["data-table", className].filter(Boolean).join(" ")}>
|
|
110
|
+
<div className={["data-table", striped && "is-striped", className].filter(Boolean).join(" ")}>
|
|
106
111
|
{anySortable && (
|
|
107
112
|
<div className="data-table-sort">
|
|
108
113
|
<label htmlFor={selectId}>{sortLabel}</label>
|
|
@@ -135,21 +140,20 @@ export function DataTable({
|
|
|
135
140
|
{columns.map((column, index) => {
|
|
136
141
|
const active = sort?.column === index;
|
|
137
142
|
const Icon = !active ? ArrowUpDown : sort?.direction === "asc" ? ArrowUp : ArrowDown;
|
|
143
|
+
const head = {
|
|
144
|
+
"data-align": column.align ?? "left",
|
|
145
|
+
"data-fit": column.fit || undefined,
|
|
146
|
+
"aria-sort": column.sortable
|
|
147
|
+
? active
|
|
148
|
+
? sort?.direction === "asc"
|
|
149
|
+
? ("ascending" as const)
|
|
150
|
+
: ("descending" as const)
|
|
151
|
+
: ("none" as const)
|
|
152
|
+
: undefined,
|
|
153
|
+
};
|
|
138
154
|
return (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
key={column.label}
|
|
142
|
-
data-align={column.align ?? "left"}
|
|
143
|
-
aria-sort={
|
|
144
|
-
column.sortable
|
|
145
|
-
? active
|
|
146
|
-
? sort?.direction === "asc"
|
|
147
|
-
? "ascending"
|
|
148
|
-
: "descending"
|
|
149
|
-
: "none"
|
|
150
|
-
: undefined
|
|
151
|
-
}
|
|
152
|
-
>
|
|
155
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: columns sit in order, and two may share a blank label
|
|
156
|
+
<th scope="col" key={index} {...head}>
|
|
153
157
|
{column.sortable ? (
|
|
154
158
|
<button
|
|
155
159
|
type="button"
|
|
@@ -171,16 +175,19 @@ export function DataTable({
|
|
|
171
175
|
{sorted.map(({ row, index }) => (
|
|
172
176
|
<tr key={index} data-tone={row.tone}>
|
|
173
177
|
{row.cells.map((cell, c) => {
|
|
174
|
-
const align = columns[c]?.align ?? "left";
|
|
175
178
|
const body = isValueCell(cell) ? cell.display : cell;
|
|
179
|
+
const shape = {
|
|
180
|
+
"data-align": columns[c]?.align ?? "left",
|
|
181
|
+
"data-fit": columns[c]?.fit || undefined,
|
|
182
|
+
};
|
|
176
183
|
return c === 0 ? (
|
|
177
184
|
// biome-ignore lint/suspicious/noArrayIndexKey: cells sit in column order
|
|
178
|
-
<th scope="row" key={c}
|
|
185
|
+
<th scope="row" key={c} {...shape}>
|
|
179
186
|
{body}
|
|
180
187
|
</th>
|
|
181
188
|
) : (
|
|
182
189
|
// biome-ignore lint/suspicious/noArrayIndexKey: cells sit in column order
|
|
183
|
-
<td key={c}
|
|
190
|
+
<td key={c} {...shape} data-label={columns[c]?.label}>
|
|
184
191
|
{body}
|
|
185
192
|
</td>
|
|
186
193
|
);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Dot } from "lucide-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { Tone } from "./tone";
|
|
4
|
+
|
|
5
|
+
export interface IconListItem {
|
|
6
|
+
/** The item itself. */
|
|
7
|
+
body: ReactNode;
|
|
8
|
+
/** The icon it leads with, e.g. a lucide icon at size 16; the list's when left out. */
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
/** The tone its bullet reads in; the list's when left out. */
|
|
11
|
+
tone?: Tone;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IconListProps {
|
|
15
|
+
items: IconListItem[];
|
|
16
|
+
/** The bullet every item without its own leads with; a dot when left out. */
|
|
17
|
+
icon?: ReactNode;
|
|
18
|
+
/** The tone every bullet without its own reads in; the text's color when left out. */
|
|
19
|
+
tone?: Tone;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A list whose bullets are icons: one for the whole list, or one per item,
|
|
25
|
+
* each in a tone set the same two ways. The bullets are decoration; the
|
|
26
|
+
* items carry the meaning, so a screen reader hears the list and not the
|
|
27
|
+
* icons.
|
|
28
|
+
*/
|
|
29
|
+
export function IconList({ items, icon, tone, className }: IconListProps) {
|
|
30
|
+
return (
|
|
31
|
+
<ul className={["icon-list", className].filter(Boolean).join(" ")} data-tone={tone}>
|
|
32
|
+
{items.map((item, i) => (
|
|
33
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: items are static content in a fixed order
|
|
34
|
+
<li key={i} data-tone={item.tone}>
|
|
35
|
+
<span className="icon-list-bullet" aria-hidden="true">
|
|
36
|
+
{item.icon ?? icon ?? <Dot size={16} strokeWidth={4} />}
|
|
37
|
+
</span>
|
|
38
|
+
<span className="icon-list-body">{item.body}</span>
|
|
39
|
+
</li>
|
|
40
|
+
))}
|
|
41
|
+
</ul>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -48,8 +48,8 @@ export function StackedBar({ segments, showKey = true, className }: StackedBarPr
|
|
|
48
48
|
{segments.map((segment) => (
|
|
49
49
|
<li key={segment.label} data-tone={segment.tone} style={fill(segment)}>
|
|
50
50
|
<span className="chart-swatch" aria-hidden="true" />
|
|
51
|
-
<b>{figure(segment)}</b>
|
|
52
51
|
<span>{segment.label}</span>
|
|
52
|
+
<b>{figure(segment)}</b>
|
|
53
53
|
</li>
|
|
54
54
|
))}
|
|
55
55
|
</ul>
|
package/src/components/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ export {
|
|
|
23
23
|
type DisclosureProps,
|
|
24
24
|
} from "./Disclosure";
|
|
25
25
|
export { GateLogin, type GateLoginProps } from "./GateLogin";
|
|
26
|
+
export { IconList, type IconListItem, type IconListProps } from "./IconList";
|
|
26
27
|
export { NotFound, type NotFoundProps } from "./NotFound";
|
|
27
28
|
export { StackedBar, type StackedBarProps, type StackedBarSegment } from "./StackedBar";
|
|
28
29
|
export { describeStarRating, StarRating, type StarRatingProps } from "./StarRating";
|