cortena-ui 1.10.0 → 1.12.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/CHANGELOG.md +65 -0
- package/README.md +135 -2
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/chart/nivo-theme.js +23 -5
- package/dist/components/chart/nivo-theme.js.map +1 -1
- package/dist/components/chart/recharts-charts.js +9 -14
- package/dist/components/chart/recharts-charts.js.map +1 -1
- package/dist/components/chart/tokens.js +21 -1
- package/dist/components/chart/tokens.js.map +1 -1
- package/dist/components/markdown.js +32 -38
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/rich-text-editor/extensions.d.ts +43 -0
- package/dist/components/rich-text-editor/extensions.js +278 -0
- package/dist/components/rich-text-editor/extensions.js.map +1 -0
- package/dist/components/rich-text-editor/index.d.ts +6 -0
- package/dist/components/rich-text-editor/markdown-bridge.d.ts +81 -0
- package/dist/components/rich-text-editor/markdown-bridge.js +618 -0
- package/dist/components/rich-text-editor/markdown-bridge.js.map +1 -0
- package/dist/components/rich-text-editor/rich-text-editor.d.ts +109 -0
- package/dist/components/rich-text-editor/rich-text-editor.js +357 -0
- package/dist/components/rich-text-editor/rich-text-editor.js.map +1 -0
- package/dist/components/rich-text-editor/toolbar.d.ts +27 -0
- package/dist/components/rich-text-editor/toolbar.js +222 -0
- package/dist/components/rich-text-editor/toolbar.js.map +1 -0
- package/dist/core.d.ts +2 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +5 -1
- package/dist/lib/prose.js +85 -0
- package/dist/lib/prose.js.map +1 -0
- package/dist/rich-text-editor.d.ts +7 -0
- package/dist/rich-text-editor.js +6 -0
- package/package.json +22 -8
- package/src/components/chart/nivo-theme.ts +23 -4
- package/src/components/chart/recharts-charts.tsx +13 -10
- package/src/components/chart/tokens.ts +19 -0
- package/src/components/markdown.tsx +29 -172
- package/src/components/rich-text-editor/extensions.ts +328 -0
- package/src/components/rich-text-editor/index.ts +19 -0
- package/src/components/rich-text-editor/markdown-bridge.ts +791 -0
- package/src/components/rich-text-editor/rich-text-editor.tsx +607 -0
- package/src/components/rich-text-editor/toolbar.tsx +281 -0
- package/src/entries/core.ts +1 -0
- package/src/entries/rich-text-editor.ts +53 -0
- package/src/index.ts +2 -0
- package/src/lib/prose.ts +149 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,71 @@
|
|
|
3
3
|
Notable changes per release. Versions before 1.6.0 are recorded in the git log
|
|
4
4
|
and in `../../CONSUMING.md`; this file starts where the changelog does.
|
|
5
5
|
|
|
6
|
+
## 1.12.0
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- **`RenderProp` is exported** from `cortena-ui/core` and the root barrel, so a
|
|
11
|
+
consumer can name the type its wrappers around `Card`, `Breadcrumb` and
|
|
12
|
+
`ButtonLink` already use (DESIGN-51).
|
|
13
|
+
- **`cortena-design` peer range is bounded** at `>=3.4.0 <4`; a future 4.x no
|
|
14
|
+
longer resolves silently (DESIGN-51).
|
|
15
|
+
- **Chart SSR fallbacks match the tokens.** The no-DOM fallbacks for
|
|
16
|
+
`--ds-radius-md` and `--ds-radius-sm` were 6 and 4; the tokens are 12 and 8,
|
|
17
|
+
so a server-rendered Nivo tooltip drew at half radius until hydration. The
|
|
18
|
+
plot margin now reads `--ds-space-2` through `plotMargin()` instead of a raw
|
|
19
|
+
8. Pixel-identical in the browser; asserted by test (DESIGN-51).
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- `@nivo/bar`, `@nivo/core`, `@nivo/line`, `@nivo/pie`, `@nivo/scatterplot`
|
|
24
|
+
as dependencies — nothing imported them; those chart types render on the
|
|
25
|
+
Recharts side. `@nivo/core` stays as a transitive of the four Nivo charts
|
|
26
|
+
that remain (DESIGN-51).
|
|
27
|
+
|
|
28
|
+
## 1.11.0
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **`RichTextEditor`** (DESIGN-54). Rich text editing, which nothing in the
|
|
33
|
+
fleet had. KeyStone needs it for vision documents, PRDs and requirement
|
|
34
|
+
documents; Tasks needs it to be able to replace a `<textarea>` it chose on
|
|
35
|
+
purpose. TipTap on ProseMirror, React bindings, value is Markdown (GFM) in
|
|
36
|
+
and out. Exported from the barrel and from its own subpath entry,
|
|
37
|
+
`cortena-ui/rich-text-editor` — TipTap and ProseMirror are 619 kB and MCP app
|
|
38
|
+
HTML is capped at 512 kB, so `test/bundle-probe.test.mjs` now asserts that no
|
|
39
|
+
other entry carries a byte of the editor engine (`button` from the barrel is
|
|
40
|
+
still 37.7 kB and pulls none of it).
|
|
41
|
+
- **The round trip is the feature.** An untouched document serialises
|
|
42
|
+
byte-identically — same line endings, same trailing whitespace — and
|
|
43
|
+
editing one paragraph changes one paragraph. Tasks uses a plain textarea
|
|
44
|
+
because a rich editor "would reformat text nobody touched and land it as a
|
|
45
|
+
spurious revision"; that constraint is a requirement of this component.
|
|
46
|
+
`markdown-bridge.ts` is mdast-based and source-preserving rather than
|
|
47
|
+
`tiptap-markdown`, which serialises through Turndown and renormalises
|
|
48
|
+
everything it touches. Asserted over an 18-file corpus in
|
|
49
|
+
`test/fixtures/rich-text/`.
|
|
50
|
+
- **Supported subset**: paragraphs, h2–h4, bold, italic, strikethrough,
|
|
51
|
+
inline code, links, ordered/bullet/task lists, blockquote, fenced code, GFM
|
|
52
|
+
tables, horizontal rule, images by URL. Anything outside it — front matter,
|
|
53
|
+
HTML blocks, footnotes, display maths, h1, h5, h6 — is preserved as a raw
|
|
54
|
+
Markdown block, never dropped.
|
|
55
|
+
- **Slots, not features**: `mentionProvider`, `selectionActions`,
|
|
56
|
+
`renderChip`. A mention is stored as `[@Priya](mention:user-42)`, so it is
|
|
57
|
+
still Markdown. Editor JSON and HTML never leave the component.
|
|
58
|
+
- **Controlled `value` with an explicit `version`**: a new revision arriving
|
|
59
|
+
while the author is focused does not clobber them; `onExternalChange` fires
|
|
60
|
+
with an `apply` for a conflict banner.
|
|
61
|
+
- Toolbar on the existing `Toolbar` primitive, keyboard shortcuts, and the
|
|
62
|
+
Markdown input rules (`## `, `- `, `1. `, `> `, backticks).
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- The prose class map moved to `src/lib/prose.ts` and is now shared by
|
|
67
|
+
`Markdown` and `RichTextEditor`. No visual change — the strings are the same
|
|
68
|
+
ones — but the read view and the edit view can no longer drift apart, which
|
|
69
|
+
is what keeps a document from shifting when it enters edit mode.
|
|
70
|
+
|
|
6
71
|
## 1.10.0
|
|
7
72
|
|
|
8
73
|
1.9.0 was never released: it was published by mistake and has been deprecated.
|
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ consumer's bundler getting tree-shaking right:
|
|
|
36
36
|
| `cortena-ui/a2ui` | the A2UI catalogue and renderer | most of the package, by design |
|
|
37
37
|
| `cortena-ui/sortable-list` | `SortableList`, `SortableHandle`, `arrayMove` | dnd-kit |
|
|
38
38
|
| `cortena-ui/agent-chat` | `AgentChatPopup`, `AgentChat`, `createAguiAgentChatClient` | the A2UI catalogue, plus `@ag-ui/client` (rxjs, zod 3, uuid, protobuf) |
|
|
39
|
+
| `cortena-ui/rich-text-editor` | `RichTextEditor`, `parseMarkdown`, `serializeMarkdown` | TipTap on ProseMirror, 619 kB — the heaviest entry in the package |
|
|
39
40
|
|
|
40
41
|
Each component has exactly one home, so the barrel re-exports every entry
|
|
41
42
|
without an ambiguous name. **Adding a component means adding it to its area
|
|
@@ -121,8 +122,9 @@ regression; `test/chart-bundle.test.tsx` says which line caused it.
|
|
|
121
122
|
`text-[color:var(--ds-foreground)]` and `text-[length:var(--ds-text-caption)]`.
|
|
122
123
|
Backgrounds, borders and radii are unambiguous and need no hint.
|
|
123
124
|
- **Tested in a real browser.** Tests assert computed pixels against the
|
|
124
|
-
token the browser resolved
|
|
125
|
-
|
|
125
|
+
token the browser resolved. A dangling `var()` paints transparent, which the
|
|
126
|
+
tests catch and jsdom cannot. Most tests pin a theme and run once; see
|
|
127
|
+
"Which tests run in both themes".
|
|
126
128
|
- **Every component appears in the guide.** `guide/sections.tsx` is the visual
|
|
127
129
|
check in light, dark and system-dark; add an entry with every variant and
|
|
128
130
|
size when adding a component.
|
|
@@ -431,6 +433,119 @@ names the session (the key is still written, so one window stays on one
|
|
|
431
433
|
session), `listSessions: false` for a host with its own session list, and
|
|
432
434
|
`mintSessionKey` for a host that binds sessions some other way.
|
|
433
435
|
|
|
436
|
+
## RichTextEditor
|
|
437
|
+
|
|
438
|
+
WYSIWYG editing whose value is Markdown (GFM), in and out. KeyStone needs it for
|
|
439
|
+
vision documents, PRDs and requirement documents; nothing else in the fleet has
|
|
440
|
+
rich text at all.
|
|
441
|
+
|
|
442
|
+
```tsx
|
|
443
|
+
import { RichTextEditor } from "cortena-ui/rich-text-editor";
|
|
444
|
+
|
|
445
|
+
<RichTextEditor
|
|
446
|
+
value={markdown}
|
|
447
|
+
version={revision}
|
|
448
|
+
aria-label="Vision document"
|
|
449
|
+
onChange={setMarkdown}
|
|
450
|
+
onExternalChange={({ apply }) => setConflict(() => apply)}
|
|
451
|
+
mentionProvider={({ trigger, query }) => search(trigger, query)}
|
|
452
|
+
selectionActions={({ text, editor }) => <AskAgent text={text} />}
|
|
453
|
+
/>
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Editor JSON and HTML never leave the component. There is no `getHTML` escape
|
|
457
|
+
hatch on purpose: the moment a consumer stores ProseMirror JSON, the document
|
|
458
|
+
stops being a file that anything else can read.
|
|
459
|
+
|
|
460
|
+
### The round trip is the feature
|
|
461
|
+
|
|
462
|
+
Tasks deliberately uses a plain `<textarea>` for its section editor, because a
|
|
463
|
+
rich editor "would reformat text nobody touched and land it as a spurious
|
|
464
|
+
revision". That constraint is a requirement of this component, not a nice-to-have:
|
|
465
|
+
|
|
466
|
+
- **An untouched document serialises byte-identically.** Same bytes, including
|
|
467
|
+
line endings, trailing whitespace and a byte order mark.
|
|
468
|
+
- **Editing one paragraph changes one paragraph.** Every other block is written
|
|
469
|
+
back from the source it was parsed from.
|
|
470
|
+
|
|
471
|
+
`test/rich-text-markdown.test.tsx` asserts both over an 18-file corpus in
|
|
472
|
+
`test/fixtures/rich-text/`, covering nested lists, fences containing fences,
|
|
473
|
+
tables with escaped pipes, links with titles, CRLF and trailing whitespace.
|
|
474
|
+
|
|
475
|
+
### Why the bridge is written rather than adopted
|
|
476
|
+
|
|
477
|
+
`tiptap-markdown` is the obvious candidate and it fails the one requirement
|
|
478
|
+
above. It serialises by rendering the editor to HTML and running Turndown over
|
|
479
|
+
it, so the output is whatever Turndown's rules produce: bullets become `*`,
|
|
480
|
+
emphasis becomes `_`, tables are re-padded, and a document nobody edited comes
|
|
481
|
+
back different from how it went in. A component that cannot replace the Tasks
|
|
482
|
+
textarea has not delivered anything.
|
|
483
|
+
|
|
484
|
+
A plain mdast serializer does not fix it either. Any AST round trip normalises —
|
|
485
|
+
`mdast-util-to-markdown` has one way to write a bullet — and the moment the
|
|
486
|
+
author's file disagrees, every untouched paragraph in it is rewritten.
|
|
487
|
+
|
|
488
|
+
So `markdown-bridge.ts` is mdast-based **and** source-preserving. Parsing keeps
|
|
489
|
+
the exact source slice of every top-level block; a block whose ProseMirror JSON
|
|
490
|
+
is byte-for-byte what parsing produced is written back from that slice, and only
|
|
491
|
+
a block the user actually changed goes through `mdast-util-to-markdown`. The
|
|
492
|
+
dependencies are `mdast-util-from-markdown`, `mdast-util-to-markdown`,
|
|
493
|
+
`mdast-util-gfm` and `micromark-extension-frontmatter` (all MIT), which is the
|
|
494
|
+
same layer react-markdown already uses — `src/lib/prose.ts` is imported by both
|
|
495
|
+
views and reaches neither react-markdown nor ProseMirror.
|
|
496
|
+
|
|
497
|
+
### The supported subset, and what happens outside it
|
|
498
|
+
|
|
499
|
+
Paragraphs; headings h2–h4 (h1 is reserved for the record's own title); bold,
|
|
500
|
+
italic, strikethrough, inline code; links; ordered, bullet and task lists;
|
|
501
|
+
blockquote; fenced code; GFM tables; horizontal rule; images by URL.
|
|
502
|
+
|
|
503
|
+
Anything else — front matter, HTML blocks, footnotes, display maths, h1, h5, h6 —
|
|
504
|
+
becomes a **raw block**: a code-like node holding the original Markdown as text,
|
|
505
|
+
editable as raw Markdown and serialised with no escaping. It is never dropped.
|
|
506
|
+
The rule applies at the block boundary, so a paragraph containing a footnote
|
|
507
|
+
reference goes raw whole rather than losing the reference from an otherwise
|
|
508
|
+
editable paragraph.
|
|
509
|
+
|
|
510
|
+
Not in scope: maths rendering, collaborative cursors, comments UI, image upload,
|
|
511
|
+
syntax highlighting in fences.
|
|
512
|
+
|
|
513
|
+
### Slots, not features
|
|
514
|
+
|
|
515
|
+
`@` and `#` mean nothing to this component. `mentionProvider` is asked for items
|
|
516
|
+
and a chip is inserted; whether that resolves to a person, a task or a page is
|
|
517
|
+
the consumer's business. A mention is stored as `[@Priya](mention:user-42)`, so
|
|
518
|
+
the document is still ordinary Markdown and the editor reads the chip back on
|
|
519
|
+
the next open.
|
|
520
|
+
|
|
521
|
+
What survives in the **read** view is the label, not the link: `Markdown` passes
|
|
522
|
+
URLs through react-markdown's `urlTransform`, which drops any protocol it does
|
|
523
|
+
not know, so `[@Priya](mention:user-42)` renders as the text `@Priya` with no
|
|
524
|
+
href. A host that wants mentions to be clickable in the read view passes its own
|
|
525
|
+
`urlTransform` to `Markdown` and resolves `mention:` itself.
|
|
526
|
+
|
|
527
|
+
`selectionActions` renders whatever the host wants over the current selection,
|
|
528
|
+
and `renderChip` replaces how a mention looks in the menu.
|
|
529
|
+
|
|
530
|
+
### The controlled value and `version`
|
|
531
|
+
|
|
532
|
+
`value` is the document and `version` is the revision it belongs to. A new
|
|
533
|
+
revision arriving while the author is focused does **not** replace what they are
|
|
534
|
+
writing: `onExternalChange` fires with `{ value, version, apply }` so the
|
|
535
|
+
consumer can show a conflict banner and adopt it on the author's say-so. When
|
|
536
|
+
the author is not in the editor it is adopted silently. `version` defaults to
|
|
537
|
+
`value`, so a consumer that simply echoes `onChange` back still behaves — an
|
|
538
|
+
echo of our own output is never treated as a conflict.
|
|
539
|
+
|
|
540
|
+
### Styling
|
|
541
|
+
|
|
542
|
+
Prose comes from `src/lib/prose.ts`, the same class strings the `Markdown`
|
|
543
|
+
renderer paints the read view with, handed to TipTap as each node's
|
|
544
|
+
`HTMLAttributes.class`. That is the only reason a document does not shift when
|
|
545
|
+
it enters edit mode, and `test/rich-text-editor.test.tsx` compares the computed
|
|
546
|
+
font size, family, weight and line height of both components on the same source.
|
|
547
|
+
Changing a prose style means changing it in `prose.ts`, for both views at once.
|
|
548
|
+
|
|
434
549
|
## Column widths
|
|
435
550
|
|
|
436
551
|
`DataTable` lays the grid out `table-fixed`: every column is drawn at its
|
|
@@ -582,6 +697,22 @@ action is shaped like A2UI's client-to-server `action`, and the catalogue is a
|
|
|
582
697
|
schema-per-component registry. Swapping the engine later is mechanical. Nothing
|
|
583
698
|
from `@a2ui/*` is installed, so `vitest.config.ts` has no entry for it.
|
|
584
699
|
|
|
700
|
+
## Which tests run in both themes
|
|
701
|
+
|
|
702
|
+
`vitest.config.ts` has two projects. **`light`** runs every file once in a
|
|
703
|
+
Chromium whose OS colour scheme is light; **`dark`** runs a second time, in a
|
|
704
|
+
dark Chromium, only the files whose name ends `.theme.test.tsx`. Tag a test with
|
|
705
|
+
that suffix when its result depends on the browser's own colour scheme — that
|
|
706
|
+
is, when it asserts a theme-dependent value while no `data-theme` attribute is
|
|
707
|
+
set, the `@media (prefers-color-scheme: dark)` branch of `tokens.css`. Nothing
|
|
708
|
+
else needs the tag: an explicit `data-theme` always beats the media query, so a
|
|
709
|
+
test that calls `setTheme("light")` or `setTheme("dark")` computes the same
|
|
710
|
+
bytes in either browser, and the `for (const theme of ["light", "dark"])` loops
|
|
711
|
+
already paint both branches and assert that they differ, inside one run. If one
|
|
712
|
+
test in a file needs the tag, move that test to a sibling `*.theme.test.tsx`
|
|
713
|
+
rather than paying for the whole file twice — six such files exist today, and
|
|
714
|
+
`pnpm test:dark` runs exactly them.
|
|
715
|
+
|
|
585
716
|
## Commands
|
|
586
717
|
|
|
587
718
|
```bash
|
|
@@ -589,6 +720,7 @@ pnpm check # types
|
|
|
589
720
|
pnpm lint:design # no literals where a token exists; no dangling var()
|
|
590
721
|
pnpm build # dist/ via tsdown (ESM + d.ts); also runs on prepack
|
|
591
722
|
pnpm test # changed-only: the suites of this package your diff affects
|
|
723
|
+
pnpm test:dark # the dark project alone: *.theme.test.tsx in dark Chromium
|
|
592
724
|
pnpm test:bundle # bundle budgets alone (builds first, then real Vite apps)
|
|
593
725
|
pnpm bundle:probe # the size table, printed, without asserting anything
|
|
594
726
|
pnpm guide # three-theme guide on a local Vite server
|
|
@@ -611,6 +743,7 @@ src/styles/index.css what consumers import after the tokens: animation
|
|
|
611
743
|
utilities and the Base UI data-attribute variants
|
|
612
744
|
scripts/bundle-probe.mjs builds a consumer app per entry and weighs it
|
|
613
745
|
test/ browser tests; setup.css is the consumer recipe verbatim
|
|
746
|
+
*.theme.test.tsx also run in a dark Chromium
|
|
614
747
|
*.test.mjs are the Node-side bundle budgets
|
|
615
748
|
guide/ Vite app: ?theme=light|dark|system frames, or all three
|
|
616
749
|
```
|
|
@@ -3,7 +3,7 @@ import { VariantProps } from "class-variance-authority";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
//#region src/components/badge.d.ts
|
|
5
5
|
declare const badgeVariants: (props?: ({
|
|
6
|
-
variant?: "default" | "success" | "warn" | "destructive" | "danger" | "info" | "
|
|
6
|
+
variant?: "default" | "outline" | "success" | "warn" | "destructive" | "danger" | "info" | "warning" | "secondary" | "neutral" | null | undefined;
|
|
7
7
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
8
|
export interface BadgeProps extends React.ComponentProps<"span">, VariantProps<typeof badgeVariants> {}
|
|
9
9
|
declare function Badge({ className, variant, ...props }: BadgeProps): React.JSX.Element;
|
|
@@ -14,7 +14,7 @@ import { Button } from "@base-ui/react/button";
|
|
|
14
14
|
* rendered element, which is Base UI's equivalent.
|
|
15
15
|
*/
|
|
16
16
|
declare const buttonVariants: (props?: ({
|
|
17
|
-
variant?: "link" | "
|
|
17
|
+
variant?: "link" | "outline" | "destructive" | "primary" | "secondary" | "ghost" | "soft" | null | undefined;
|
|
18
18
|
size?: "sm" | "md" | "lg" | "icon" | "icon-sm" | null | undefined;
|
|
19
19
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
20
20
|
export interface ButtonProps extends React.ComponentProps<typeof Button>, VariantProps<typeof buttonVariants> {}
|
|
@@ -5,6 +5,24 @@ import { useChart } from "./container.js";
|
|
|
5
5
|
import { useMemo } from "react";
|
|
6
6
|
//#region src/components/chart/nivo-theme.ts
|
|
7
7
|
/**
|
|
8
|
+
* What these four sizes are when there is no DOM to read them from.
|
|
9
|
+
*
|
|
10
|
+
* `nivoTheme` runs on the server too, where `getComputedStyle` does not exist
|
|
11
|
+
* and every `tokenPx` call comes back undefined. Each number below is the
|
|
12
|
+
* pixel value its token emits at the default scale, so the server markup and
|
|
13
|
+
* the first client paint agree. `chart.test.tsx` asserts them against the
|
|
14
|
+
* live tokens, which is what stops them drifting the way the two radii had:
|
|
15
|
+
* `--ds-radius-md` is 12px and fell back to 6, `--ds-radius-sm` is 8px and
|
|
16
|
+
* fell back to 4, so every server-rendered tooltip drew half-round corners
|
|
17
|
+
* until hydration.
|
|
18
|
+
*/
|
|
19
|
+
const NIVO_SIZE_FALLBACK = {
|
|
20
|
+
captionSm: 11,
|
|
21
|
+
caption: 12,
|
|
22
|
+
radiusMd: 12,
|
|
23
|
+
radiusSm: 8
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
8
26
|
* Build the Nivo theme from the tokens, resolved on `el` (default `<html>`).
|
|
9
27
|
* Every colour is read from a `--ds-*` custom property and converted to hex,
|
|
10
28
|
* because Nivo's canvas renderer and its colour derivation cannot read
|
|
@@ -15,10 +33,10 @@ import { useMemo } from "react";
|
|
|
15
33
|
*/
|
|
16
34
|
function nivoTheme(el) {
|
|
17
35
|
const fontFamily = readToken("--ds-font-body", el) || "var(--ds-font-body)";
|
|
18
|
-
const captionSm = tokenPx("--ds-text-caption-sm", el) ??
|
|
19
|
-
const caption = tokenPx("--ds-text-caption", el) ??
|
|
20
|
-
const radiusMd = tokenPx("--ds-radius-md", el) ??
|
|
21
|
-
const radiusSm = tokenPx("--ds-radius-sm", el) ??
|
|
36
|
+
const captionSm = tokenPx("--ds-text-caption-sm", el) ?? NIVO_SIZE_FALLBACK.captionSm;
|
|
37
|
+
const caption = tokenPx("--ds-text-caption", el) ?? NIVO_SIZE_FALLBACK.caption;
|
|
38
|
+
const radiusMd = tokenPx("--ds-radius-md", el) ?? NIVO_SIZE_FALLBACK.radiusMd;
|
|
39
|
+
const radiusSm = tokenPx("--ds-radius-sm", el) ?? NIVO_SIZE_FALLBACK.radiusSm;
|
|
22
40
|
const foreground = resolveColor("--ds-foreground", el);
|
|
23
41
|
const muted = resolveColor("--ds-muted-foreground", el);
|
|
24
42
|
const border = resolveColor("--ds-border", el);
|
|
@@ -156,6 +174,6 @@ function useNivoTheme() {
|
|
|
156
174
|
return useMemo(() => nivoTheme(el), [el, version]);
|
|
157
175
|
}
|
|
158
176
|
//#endregion
|
|
159
|
-
export { nivoTheme, useNivoTheme };
|
|
177
|
+
export { NIVO_SIZE_FALLBACK, nivoTheme, useNivoTheme };
|
|
160
178
|
|
|
161
179
|
//# sourceMappingURL=nivo-theme.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nivo-theme.js","names":[],"sources":["../../../src/components/chart/nivo-theme.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useChart } from \"./container\";\nimport { readToken, resolveColor, tokenPx, useThemeVersion } from \"./tokens\";\n\n/**\n * The Nivo theme object, typed structurally so no `@nivo/*` package is a\n * static import of this module (the Nivo packages are lazy-loaded). It is a\n * subset of `PartialTheme` from `@nivo/theming` and assignable to every\n * chart's `theme` prop.\n */\nexport interface NivoTheme {\n background: string;\n text: { fontFamily: string; fontSize: number; fill: string };\n axis: {\n domain: { line: { stroke: string; strokeWidth: number } };\n ticks: {\n line: { stroke: string; strokeWidth: number };\n text: { fill: string; fontSize: number };\n };\n legend: { text: { fill: string; fontSize: number; fontWeight: number } };\n };\n grid: { line: { stroke: string; strokeWidth: number } };\n crosshair: { line: { stroke: string; strokeWidth: number; strokeOpacity: number; strokeDasharray: string } };\n legends: {\n text: { fill: string; fontSize: number };\n title: { text: { fill: string; fontSize: number } };\n ticks: { line: { stroke: string; strokeWidth: number }; text: { fill: string; fontSize: number } };\n };\n labels: { text: { fill: string; fontSize: number; fontWeight: number } };\n dots: { text: { fill: string; fontSize: number } };\n markers: { lineColor: string; lineStrokeWidth: number; text: { fill: string; fontSize: number } };\n annotations: {\n text: { fill: string; fontSize: number; outlineWidth: number; outlineColor: string };\n link: { stroke: string; strokeWidth: number; outlineWidth: number; outlineColor: string };\n outline: { stroke: string; strokeWidth: number; outlineWidth: number; outlineColor: string };\n symbol: { fill: string; outlineWidth: number; outlineColor: string };\n };\n tooltip: {\n container: {\n background: string;\n color: string;\n fontSize: number;\n fontFamily: string;\n borderRadius: number;\n boxShadow: string;\n border: string;\n padding: string;\n };\n chip: { borderRadius: number };\n };\n}\n\n/**\n * Build the Nivo theme from the tokens, resolved on `el` (default `<html>`).\n * Every colour is read from a `--ds-*` custom property and converted to hex,\n * because Nivo's canvas renderer and its colour derivation cannot read\n * `var()`. Sizes come from the type scale with `--ds-text-caption-sm` (11px)\n * as the floor for ticks and labels.\n *\n * Moved here from cortenaweb's webchat renderer; nothing is hard-coded.\n */\nexport function nivoTheme(el?: Element | null): NivoTheme {\n const fontFamily = readToken(\"--ds-font-body\", el) || \"var(--ds-font-body)\";\n const captionSm = tokenPx(\"--ds-text-caption-sm\", el) ??
|
|
1
|
+
{"version":3,"file":"nivo-theme.js","names":[],"sources":["../../../src/components/chart/nivo-theme.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useChart } from \"./container\";\nimport { readToken, resolveColor, tokenPx, useThemeVersion } from \"./tokens\";\n\n/**\n * The Nivo theme object, typed structurally so no `@nivo/*` package is a\n * static import of this module (the Nivo packages are lazy-loaded). It is a\n * subset of `PartialTheme` from `@nivo/theming` and assignable to every\n * chart's `theme` prop.\n */\nexport interface NivoTheme {\n background: string;\n text: { fontFamily: string; fontSize: number; fill: string };\n axis: {\n domain: { line: { stroke: string; strokeWidth: number } };\n ticks: {\n line: { stroke: string; strokeWidth: number };\n text: { fill: string; fontSize: number };\n };\n legend: { text: { fill: string; fontSize: number; fontWeight: number } };\n };\n grid: { line: { stroke: string; strokeWidth: number } };\n crosshair: { line: { stroke: string; strokeWidth: number; strokeOpacity: number; strokeDasharray: string } };\n legends: {\n text: { fill: string; fontSize: number };\n title: { text: { fill: string; fontSize: number } };\n ticks: { line: { stroke: string; strokeWidth: number }; text: { fill: string; fontSize: number } };\n };\n labels: { text: { fill: string; fontSize: number; fontWeight: number } };\n dots: { text: { fill: string; fontSize: number } };\n markers: { lineColor: string; lineStrokeWidth: number; text: { fill: string; fontSize: number } };\n annotations: {\n text: { fill: string; fontSize: number; outlineWidth: number; outlineColor: string };\n link: { stroke: string; strokeWidth: number; outlineWidth: number; outlineColor: string };\n outline: { stroke: string; strokeWidth: number; outlineWidth: number; outlineColor: string };\n symbol: { fill: string; outlineWidth: number; outlineColor: string };\n };\n tooltip: {\n container: {\n background: string;\n color: string;\n fontSize: number;\n fontFamily: string;\n borderRadius: number;\n boxShadow: string;\n border: string;\n padding: string;\n };\n chip: { borderRadius: number };\n };\n}\n\n/**\n * What these four sizes are when there is no DOM to read them from.\n *\n * `nivoTheme` runs on the server too, where `getComputedStyle` does not exist\n * and every `tokenPx` call comes back undefined. Each number below is the\n * pixel value its token emits at the default scale, so the server markup and\n * the first client paint agree. `chart.test.tsx` asserts them against the\n * live tokens, which is what stops them drifting the way the two radii had:\n * `--ds-radius-md` is 12px and fell back to 6, `--ds-radius-sm` is 8px and\n * fell back to 4, so every server-rendered tooltip drew half-round corners\n * until hydration.\n */\nexport const NIVO_SIZE_FALLBACK = {\n captionSm: 11,\n caption: 12,\n radiusMd: 12,\n radiusSm: 8,\n} as const;\n\n/**\n * Build the Nivo theme from the tokens, resolved on `el` (default `<html>`).\n * Every colour is read from a `--ds-*` custom property and converted to hex,\n * because Nivo's canvas renderer and its colour derivation cannot read\n * `var()`. Sizes come from the type scale with `--ds-text-caption-sm` (11px)\n * as the floor for ticks and labels.\n *\n * Moved here from cortenaweb's webchat renderer; nothing is hard-coded.\n */\nexport function nivoTheme(el?: Element | null): NivoTheme {\n const fontFamily = readToken(\"--ds-font-body\", el) || \"var(--ds-font-body)\";\n const captionSm = tokenPx(\"--ds-text-caption-sm\", el) ?? NIVO_SIZE_FALLBACK.captionSm;\n const caption = tokenPx(\"--ds-text-caption\", el) ?? NIVO_SIZE_FALLBACK.caption;\n const radiusMd = tokenPx(\"--ds-radius-md\", el) ?? NIVO_SIZE_FALLBACK.radiusMd;\n const radiusSm = tokenPx(\"--ds-radius-sm\", el) ?? NIVO_SIZE_FALLBACK.radiusSm;\n const foreground = resolveColor(\"--ds-foreground\", el);\n const muted = resolveColor(\"--ds-muted-foreground\", el);\n const border = resolveColor(\"--ds-border\", el);\n const borderSubtle = resolveColor(\"--ds-border-subtle\", el);\n const borderStrong = resolveColor(\"--ds-border-strong\", el);\n const popover = resolveColor(\"--ds-popover\", el);\n const popoverForeground = resolveColor(\"--ds-popover-foreground\", el);\n const card = resolveColor(\"--ds-card\", el);\n const shadow = readToken(\"--ds-shadow-md\", el) || \"var(--ds-shadow-md)\";\n const space2 = readToken(\"--ds-space-2\", el) || \"var(--ds-space-2)\";\n const space3 = readToken(\"--ds-space-3\", el) || \"var(--ds-space-3)\";\n\n return {\n background: \"transparent\",\n text: { fontFamily, fontSize: captionSm, fill: foreground },\n axis: {\n domain: { line: { stroke: border, strokeWidth: 1 } },\n ticks: {\n line: { stroke: border, strokeWidth: 1 },\n text: { fill: muted, fontSize: captionSm },\n },\n legend: { text: { fill: foreground, fontSize: caption, fontWeight: 500 } },\n },\n grid: { line: { stroke: borderSubtle, strokeWidth: 1 } },\n crosshair: { line: { stroke: borderStrong, strokeWidth: 1, strokeOpacity: 1, strokeDasharray: \"4 4\" } },\n legends: {\n text: { fill: muted, fontSize: captionSm },\n title: { text: { fill: foreground, fontSize: caption } },\n ticks: { line: { stroke: border, strokeWidth: 1 }, text: { fill: muted, fontSize: captionSm } },\n },\n labels: { text: { fill: foreground, fontSize: captionSm, fontWeight: 500 } },\n dots: { text: { fill: foreground, fontSize: captionSm } },\n markers: { lineColor: borderStrong, lineStrokeWidth: 1, text: { fill: muted, fontSize: captionSm } },\n annotations: {\n text: { fill: foreground, fontSize: captionSm, outlineWidth: 2, outlineColor: card },\n link: { stroke: borderStrong, strokeWidth: 1, outlineWidth: 2, outlineColor: card },\n outline: { stroke: borderStrong, strokeWidth: 1, outlineWidth: 2, outlineColor: card },\n symbol: { fill: foreground, outlineWidth: 2, outlineColor: card },\n },\n tooltip: {\n container: {\n background: popover,\n color: popoverForeground,\n fontSize: caption,\n fontFamily,\n borderRadius: radiusMd,\n boxShadow: shadow,\n border: `1px solid ${borderSubtle}`,\n padding: `${space2} ${space3}`,\n },\n chip: { borderRadius: radiusSm },\n },\n };\n}\n\n/**\n * The Nivo theme for the current theme, re-resolved when `data-theme` or the\n * OS scheme flips. Inside a ChartContainer the tokens are read on its element,\n * so a nested theme scope is honoured.\n */\nexport function useNivoTheme(): NivoTheme {\n const version = useThemeVersion();\n const chart = useChart();\n const el = chart?.element ?? null;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- version is the cache key for the DOM read\n return useMemo(() => nivoTheme(el), [el, version]);\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
"use client";
|
|
3
|
-
import { contrastLabel, useThemeVersion } from "./tokens.js";
|
|
3
|
+
import { contrastLabel, plotMargin, useThemeVersion } from "./tokens.js";
|
|
4
4
|
import { seriesColor, useChart } from "./container.js";
|
|
5
5
|
import { toRows, toSlices } from "./data.js";
|
|
6
6
|
import { ChartLegend, ChartTooltip } from "./recharts-parts.js";
|
|
@@ -8,12 +8,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
import * as React from "react";
|
|
9
9
|
import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, Funnel, FunnelChart, Line, LineChart, Pie, PieChart, PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart, Scatter, ScatterChart, XAxis, YAxis } from "recharts";
|
|
10
10
|
//#region src/components/chart/recharts-charts.tsx
|
|
11
|
-
const MARGIN = {
|
|
12
|
-
top: 8,
|
|
13
|
-
right: 8,
|
|
14
|
-
bottom: 0,
|
|
15
|
-
left: 0
|
|
16
|
-
};
|
|
17
11
|
/**
|
|
18
12
|
* Where an arc label sits, as a fraction of the way from the inner radius to
|
|
19
13
|
* the outer one. Just inside the middle of the band: the arc is widest towards
|
|
@@ -69,6 +63,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
|
|
|
69
63
|
const themeVersion = useThemeVersion();
|
|
70
64
|
const element = useChart()?.element ?? null;
|
|
71
65
|
const colourOn = React.useMemo(() => contrastLabel(element), [element, themeVersion]);
|
|
66
|
+
const margin = React.useMemo(() => plotMargin(element), [element, themeVersion]);
|
|
72
67
|
const tip = tooltip ? /* @__PURE__ */ jsx(ChartTooltip, { formatter: valueFormatter ? (value) => valueFormatter(Number(value)) : void 0 }) : null;
|
|
73
68
|
const leg = legend ? /* @__PURE__ */ jsx(ChartLegend, {}) : null;
|
|
74
69
|
switch (type) {
|
|
@@ -76,7 +71,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
|
|
|
76
71
|
const { rows, indexBy: index, keys: series } = toRows(data, indexBy, keys);
|
|
77
72
|
return /* @__PURE__ */ jsxs(BarChart, {
|
|
78
73
|
data: rows,
|
|
79
|
-
margin
|
|
74
|
+
margin,
|
|
80
75
|
children: [
|
|
81
76
|
grid ? /* @__PURE__ */ jsx(CartesianGrid, { vertical: false }) : null,
|
|
82
77
|
/* @__PURE__ */ jsx(XAxis, {
|
|
@@ -112,7 +107,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
|
|
|
112
107
|
const { rows, indexBy: index, keys: series } = toRows(data, indexBy, keys);
|
|
113
108
|
return /* @__PURE__ */ jsxs(LineChart, {
|
|
114
109
|
data: rows,
|
|
115
|
-
margin
|
|
110
|
+
margin,
|
|
116
111
|
children: [
|
|
117
112
|
grid ? /* @__PURE__ */ jsx(CartesianGrid, { vertical: false }) : null,
|
|
118
113
|
/* @__PURE__ */ jsx(XAxis, {
|
|
@@ -144,7 +139,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
|
|
|
144
139
|
const { rows, indexBy: index, keys: series } = toRows(data, indexBy, keys);
|
|
145
140
|
return /* @__PURE__ */ jsxs(AreaChart, {
|
|
146
141
|
data: rows,
|
|
147
|
-
margin
|
|
142
|
+
margin,
|
|
148
143
|
children: [
|
|
149
144
|
grid ? /* @__PURE__ */ jsx(CartesianGrid, { vertical: false }) : null,
|
|
150
145
|
/* @__PURE__ */ jsx(XAxis, {
|
|
@@ -178,7 +173,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
|
|
|
178
173
|
const slices = toSlices(data);
|
|
179
174
|
const fillOf = (i) => seriesColor(config, slices[i]?.id ?? String(i), i);
|
|
180
175
|
return /* @__PURE__ */ jsxs(PieChart, {
|
|
181
|
-
margin
|
|
176
|
+
margin,
|
|
182
177
|
children: [
|
|
183
178
|
tip,
|
|
184
179
|
leg,
|
|
@@ -200,7 +195,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
|
|
|
200
195
|
}
|
|
201
196
|
case "scatter":
|
|
202
197
|
case "scatterplot": return /* @__PURE__ */ jsxs(ScatterChart, {
|
|
203
|
-
margin
|
|
198
|
+
margin,
|
|
204
199
|
children: [
|
|
205
200
|
grid ? /* @__PURE__ */ jsx(CartesianGrid, {}) : null,
|
|
206
201
|
/* @__PURE__ */ jsx(XAxis, {
|
|
@@ -232,7 +227,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
|
|
|
232
227
|
const { rows, indexBy: index, keys: series } = toRows(data, indexBy, keys);
|
|
233
228
|
return /* @__PURE__ */ jsxs(RadarChart, {
|
|
234
229
|
data: rows,
|
|
235
|
-
margin
|
|
230
|
+
margin,
|
|
236
231
|
children: [
|
|
237
232
|
grid ? /* @__PURE__ */ jsx(PolarGrid, {}) : null,
|
|
238
233
|
/* @__PURE__ */ jsx(PolarAngleAxis, { dataKey: index }),
|
|
@@ -256,7 +251,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
|
|
|
256
251
|
case "funnel": {
|
|
257
252
|
const slices = toSlices(data);
|
|
258
253
|
return /* @__PURE__ */ jsxs(FunnelChart, {
|
|
259
|
-
margin
|
|
254
|
+
margin,
|
|
260
255
|
children: [
|
|
261
256
|
tip,
|
|
262
257
|
leg,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recharts-charts.js","names":["series"],"sources":["../../../src/components/chart/recharts-charts.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Cell,\n Funnel,\n FunnelChart,\n Line,\n LineChart,\n Pie,\n PieChart,\n PolarAngleAxis,\n PolarGrid,\n PolarRadiusAxis,\n Radar,\n RadarChart,\n Scatter,\n ScatterChart,\n XAxis,\n YAxis,\n} from \"recharts\";\nimport { seriesColor, useChart } from \"./container\";\nimport { ChartLegend, ChartTooltip } from \"./recharts-parts\";\nimport { toRows, toSlices } from \"./data\";\nimport { contrastLabel, useThemeVersion } from \"./tokens\";\nimport type { ChartConfig, ChartLabels, ChartRow, ChartSeries, ChartSlice, ChartType } from \"./types\";\n\nexport type RechartsType = Extract<\n ChartType,\n \"bar\" | \"line\" | \"area\" | \"pie\" | \"donut\" | \"scatter\" | \"scatterplot\" | \"radar\" | \"funnel\"\n>;\n\nexport interface RechartsRenderProps {\n type: RechartsType;\n data: unknown;\n config: ChartConfig;\n indexBy?: string;\n keys?: string[];\n stacked?: boolean;\n legend: boolean;\n grid: boolean;\n tooltip: boolean;\n /** Per-arc labels for pie and donut. Ignored by every other type. */\n labels: ChartLabels;\n valueFormatter?: (value: number) => string;\n /**\n * Entry animation. Off under `testMode`: Recharts draws pie labels only\n * once the animation has finished, so a test that asserts on them would\n * otherwise be racing the clock.\n */\n animate?: boolean;\n}\n\nconst MARGIN = { top: 8, right: 8, bottom: 0, left: 0 };\n\n/**\n * Where an arc label sits, as a fraction of the way from the inner radius to\n * the outer one. Just inside the middle of the band: the arc is widest towards\n * its outer edge, so a label pushed slightly out has more room than a centred\n * one, without ever touching the rim.\n */\nconst ARC_LABEL_RADIUS = 0.58;\n\n/**\n * Arcs smaller than this share of the whole are left unlabelled.\n *\n * Below roughly a twentieth the arc is narrower than the text it would carry,\n * so the label either overflows into its neighbours or has to be dragged\n * outside on a leader line. Leader lines on a small chart are worse than no\n * label — they collide with each other and with the legend, and the reader\n * still has to trace them. The tooltip covers the slices that fall out.\n */\nconst ARC_LABEL_MIN_FRACTION = 0.05;\n\n/**\n * What Recharts hands a `<Pie label={…}>` renderer. Every geometry field is\n * optional on its side — the same callback types the label of a chart that has\n * not measured itself yet — so each is checked before it is used rather than\n * asserted away.\n */\ninterface ArcLabelProps {\n cx?: number;\n cy?: number;\n midAngle?: number;\n innerRadius?: number;\n outerRadius?: number;\n percent?: number;\n value?: number;\n index?: number;\n}\n\n/**\n * A per-arc label, drawn inside the arc it belongs to.\n *\n * Inside rather than outside so there is nothing to collide: each label is\n * bounded by its own sector, and a sector too thin to hold one simply does not\n * get one. The text colour comes from `contrastLabel`, the same measured\n * ink-or-white choice the Nivo charts make, so a label stays legible on a pale\n * series colour and on a dark one, in either theme.\n */\nfunction arcLabel(\n labels: Exclude<ChartLabels, \"none\">,\n colourOn: (fill: string) => string,\n fillOf: (index: number) => string,\n valueFormatter?: (value: number) => string,\n) {\n return function ArcLabel({ cx, cy, midAngle, innerRadius, outerRadius, percent, value, index }: ArcLabelProps) {\n if (cx == null || cy == null || midAngle == null || innerRadius == null || outerRadius == null) return null;\n if (!(percent != null && percent >= ARC_LABEL_MIN_FRACTION)) return null;\n const radius = innerRadius + (outerRadius - innerRadius) * ARC_LABEL_RADIUS;\n // Recharts measures angles anticlockwise from the positive x-axis, SVG's y\n // grows downward, so the sine is negated to land on the drawn arc.\n const radians = (-midAngle * Math.PI) / 180;\n const text =\n labels === \"percent\"\n ? `${Math.round(percent * 100)}%`\n : (valueFormatter?.(Number(value)) ?? String(value ?? \"\"));\n return (\n <text\n data-slot=\"chart-arc-label\"\n x={cx + radius * Math.cos(radians)}\n y={cy + radius * Math.sin(radians)}\n fill={colourOn(fillOf(index ?? 0))}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className=\"pointer-events-none text-[length:var(--ds-text-caption)] font-medium tabular-nums\"\n >\n {text}\n </text>\n );\n };\n}\n\n/**\n * The Recharts side of `Chart`. Returns a chart element for the\n * ResponsiveContainer inside ChartContainer; colours come from `seriesColor`\n * (config colour or `var(--ds-viz-N)` by series index) and every other visual\n * value from the container's token styling.\n */\nexport function RechartsChart({\n type,\n data,\n config,\n indexBy,\n keys,\n stacked,\n legend,\n grid,\n tooltip,\n labels,\n valueFormatter,\n animate = true,\n}: RechartsRenderProps): React.ReactElement {\n // Arc labels sit on top of a series colour, so their ink depends on which\n // colour and on the theme. `useThemeVersion` is the cache key for that DOM\n // read; `element` scopes it to a nested theme rather than the document.\n const themeVersion = useThemeVersion();\n const element = useChart()?.element ?? null;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- themeVersion is the cache key for the DOM read\n const colourOn = React.useMemo(() => contrastLabel(element), [element, themeVersion]);\n\n const tip = tooltip ? (\n <ChartTooltip\n formatter={valueFormatter ? (value: unknown) => valueFormatter(Number(value)) : undefined}\n />\n ) : null;\n const leg = legend ? <ChartLegend /> : null;\n\n switch (type) {\n case \"bar\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <BarChart data={rows} margin={MARGIN}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Bar\n key={key}\n dataKey={key}\n name={key}\n fill={seriesColor(config, key, i)}\n stackId={stacked ? \"stack\" : undefined}\n radius={stacked ? 0 : [2, 2, 0, 0]}\n maxBarSize={48}\n />\n ))}\n </BarChart>\n );\n }\n\n case \"line\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <LineChart data={rows} margin={MARGIN}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Line\n key={key}\n type=\"monotone\"\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4 }}\n />\n ))}\n </LineChart>\n );\n }\n\n case \"area\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <AreaChart data={rows} margin={MARGIN}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Area\n key={key}\n type=\"monotone\"\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n fill={seriesColor(config, key, i)}\n fillOpacity={0.2}\n strokeWidth={2}\n stackId={stacked ? \"stack\" : undefined}\n />\n ))}\n </AreaChart>\n );\n }\n\n case \"pie\":\n case \"donut\": {\n const slices = toSlices(data as ChartSlice[]);\n const fillOf = (i: number) => seriesColor(config, slices[i]?.id ?? String(i), i);\n return (\n <PieChart margin={MARGIN}>\n {tip}\n {leg}\n <Pie\n data={slices}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={type === \"donut\" ? \"55%\" : 0}\n outerRadius=\"80%\"\n paddingAngle={type === \"donut\" ? 2 : 0}\n strokeWidth={1}\n isAnimationActive={animate}\n label={labels === \"none\" ? undefined : arcLabel(labels, colourOn, fillOf, valueFormatter)}\n // The labels are inside their arcs, so there is nothing to lead to.\n labelLine={false}\n >\n {slices.map((slice, i) => (\n <Cell key={slice.id} fill={fillOf(i)} />\n ))}\n </Pie>\n </PieChart>\n );\n }\n\n case \"scatter\":\n case \"scatterplot\": {\n const series = data as ChartSeries[];\n return (\n <ScatterChart margin={MARGIN}>\n {grid ? <CartesianGrid /> : null}\n <XAxis dataKey=\"x\" type=\"number\" name=\"x\" tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis dataKey=\"y\" type=\"number\" name=\"y\" tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((s, i) => (\n <Scatter key={String(s.id)} name={String(s.id)} data={s.data} fill={seriesColor(config, String(s.id), i)} />\n ))}\n </ScatterChart>\n );\n }\n\n case \"radar\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <RadarChart data={rows} margin={MARGIN}>\n {grid ? <PolarGrid /> : null}\n <PolarAngleAxis dataKey={index} />\n <PolarRadiusAxis tick={false} axisLine={false} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Radar\n key={key}\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n fill={seriesColor(config, key, i)}\n fillOpacity={0.2}\n strokeWidth={2}\n />\n ))}\n </RadarChart>\n );\n }\n\n case \"funnel\": {\n const slices = toSlices(data as ChartSlice[]);\n return (\n <FunnelChart margin={MARGIN}>\n {tip}\n {leg}\n <Funnel data={slices} dataKey=\"value\" nameKey=\"name\">\n {slices.map((slice, i) => (\n <Cell key={slice.id} fill={seriesColor(config, slice.id, i)} />\n ))}\n </Funnel>\n </FunnelChart>\n );\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"recharts-charts.js","names":["series"],"sources":["../../../src/components/chart/recharts-charts.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Cell,\n Funnel,\n FunnelChart,\n Line,\n LineChart,\n Pie,\n PieChart,\n PolarAngleAxis,\n PolarGrid,\n PolarRadiusAxis,\n Radar,\n RadarChart,\n Scatter,\n ScatterChart,\n XAxis,\n YAxis,\n} from \"recharts\";\nimport { seriesColor, useChart } from \"./container\";\nimport { ChartLegend, ChartTooltip } from \"./recharts-parts\";\nimport { toRows, toSlices } from \"./data\";\nimport { contrastLabel, plotMargin, useThemeVersion } from \"./tokens\";\nimport type { ChartConfig, ChartLabels, ChartRow, ChartSeries, ChartSlice, ChartType } from \"./types\";\n\nexport type RechartsType = Extract<\n ChartType,\n \"bar\" | \"line\" | \"area\" | \"pie\" | \"donut\" | \"scatter\" | \"scatterplot\" | \"radar\" | \"funnel\"\n>;\n\nexport interface RechartsRenderProps {\n type: RechartsType;\n data: unknown;\n config: ChartConfig;\n indexBy?: string;\n keys?: string[];\n stacked?: boolean;\n legend: boolean;\n grid: boolean;\n tooltip: boolean;\n /** Per-arc labels for pie and donut. Ignored by every other type. */\n labels: ChartLabels;\n valueFormatter?: (value: number) => string;\n /**\n * Entry animation. Off under `testMode`: Recharts draws pie labels only\n * once the animation has finished, so a test that asserts on them would\n * otherwise be racing the clock.\n */\n animate?: boolean;\n}\n\n/**\n * Where an arc label sits, as a fraction of the way from the inner radius to\n * the outer one. Just inside the middle of the band: the arc is widest towards\n * its outer edge, so a label pushed slightly out has more room than a centred\n * one, without ever touching the rim.\n */\nconst ARC_LABEL_RADIUS = 0.58;\n\n/**\n * Arcs smaller than this share of the whole are left unlabelled.\n *\n * Below roughly a twentieth the arc is narrower than the text it would carry,\n * so the label either overflows into its neighbours or has to be dragged\n * outside on a leader line. Leader lines on a small chart are worse than no\n * label — they collide with each other and with the legend, and the reader\n * still has to trace them. The tooltip covers the slices that fall out.\n */\nconst ARC_LABEL_MIN_FRACTION = 0.05;\n\n/**\n * What Recharts hands a `<Pie label={…}>` renderer. Every geometry field is\n * optional on its side — the same callback types the label of a chart that has\n * not measured itself yet — so each is checked before it is used rather than\n * asserted away.\n */\ninterface ArcLabelProps {\n cx?: number;\n cy?: number;\n midAngle?: number;\n innerRadius?: number;\n outerRadius?: number;\n percent?: number;\n value?: number;\n index?: number;\n}\n\n/**\n * A per-arc label, drawn inside the arc it belongs to.\n *\n * Inside rather than outside so there is nothing to collide: each label is\n * bounded by its own sector, and a sector too thin to hold one simply does not\n * get one. The text colour comes from `contrastLabel`, the same measured\n * ink-or-white choice the Nivo charts make, so a label stays legible on a pale\n * series colour and on a dark one, in either theme.\n */\nfunction arcLabel(\n labels: Exclude<ChartLabels, \"none\">,\n colourOn: (fill: string) => string,\n fillOf: (index: number) => string,\n valueFormatter?: (value: number) => string,\n) {\n return function ArcLabel({ cx, cy, midAngle, innerRadius, outerRadius, percent, value, index }: ArcLabelProps) {\n if (cx == null || cy == null || midAngle == null || innerRadius == null || outerRadius == null) return null;\n if (!(percent != null && percent >= ARC_LABEL_MIN_FRACTION)) return null;\n const radius = innerRadius + (outerRadius - innerRadius) * ARC_LABEL_RADIUS;\n // Recharts measures angles anticlockwise from the positive x-axis, SVG's y\n // grows downward, so the sine is negated to land on the drawn arc.\n const radians = (-midAngle * Math.PI) / 180;\n const text =\n labels === \"percent\"\n ? `${Math.round(percent * 100)}%`\n : (valueFormatter?.(Number(value)) ?? String(value ?? \"\"));\n return (\n <text\n data-slot=\"chart-arc-label\"\n x={cx + radius * Math.cos(radians)}\n y={cy + radius * Math.sin(radians)}\n fill={colourOn(fillOf(index ?? 0))}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className=\"pointer-events-none text-[length:var(--ds-text-caption)] font-medium tabular-nums\"\n >\n {text}\n </text>\n );\n };\n}\n\n/**\n * The Recharts side of `Chart`. Returns a chart element for the\n * ResponsiveContainer inside ChartContainer; colours come from `seriesColor`\n * (config colour or `var(--ds-viz-N)` by series index) and every other visual\n * value from the container's token styling.\n */\nexport function RechartsChart({\n type,\n data,\n config,\n indexBy,\n keys,\n stacked,\n legend,\n grid,\n tooltip,\n labels,\n valueFormatter,\n animate = true,\n}: RechartsRenderProps): React.ReactElement {\n // Arc labels sit on top of a series colour, so their ink depends on which\n // colour and on the theme. `useThemeVersion` is the cache key for that DOM\n // read; `element` scopes it to a nested theme rather than the document.\n const themeVersion = useThemeVersion();\n const element = useChart()?.element ?? null;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- themeVersion is the cache key for the DOM read\n const colourOn = React.useMemo(() => contrastLabel(element), [element, themeVersion]);\n // The plot margin is `--ds-space-2`, read from the tokens like every colour\n // here rather than frozen as a number, and on the same cache key so a theme\n // or scale change re-reads it.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- themeVersion is the cache key for the DOM read\n const margin = React.useMemo(() => plotMargin(element), [element, themeVersion]);\n\n const tip = tooltip ? (\n <ChartTooltip\n formatter={valueFormatter ? (value: unknown) => valueFormatter(Number(value)) : undefined}\n />\n ) : null;\n const leg = legend ? <ChartLegend /> : null;\n\n switch (type) {\n case \"bar\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <BarChart data={rows} margin={margin}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Bar\n key={key}\n dataKey={key}\n name={key}\n fill={seriesColor(config, key, i)}\n stackId={stacked ? \"stack\" : undefined}\n radius={stacked ? 0 : [2, 2, 0, 0]}\n maxBarSize={48}\n />\n ))}\n </BarChart>\n );\n }\n\n case \"line\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <LineChart data={rows} margin={margin}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Line\n key={key}\n type=\"monotone\"\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4 }}\n />\n ))}\n </LineChart>\n );\n }\n\n case \"area\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <AreaChart data={rows} margin={margin}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Area\n key={key}\n type=\"monotone\"\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n fill={seriesColor(config, key, i)}\n fillOpacity={0.2}\n strokeWidth={2}\n stackId={stacked ? \"stack\" : undefined}\n />\n ))}\n </AreaChart>\n );\n }\n\n case \"pie\":\n case \"donut\": {\n const slices = toSlices(data as ChartSlice[]);\n const fillOf = (i: number) => seriesColor(config, slices[i]?.id ?? String(i), i);\n return (\n <PieChart margin={margin}>\n {tip}\n {leg}\n <Pie\n data={slices}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={type === \"donut\" ? \"55%\" : 0}\n outerRadius=\"80%\"\n paddingAngle={type === \"donut\" ? 2 : 0}\n strokeWidth={1}\n isAnimationActive={animate}\n label={labels === \"none\" ? undefined : arcLabel(labels, colourOn, fillOf, valueFormatter)}\n // The labels are inside their arcs, so there is nothing to lead to.\n labelLine={false}\n >\n {slices.map((slice, i) => (\n <Cell key={slice.id} fill={fillOf(i)} />\n ))}\n </Pie>\n </PieChart>\n );\n }\n\n case \"scatter\":\n case \"scatterplot\": {\n const series = data as ChartSeries[];\n return (\n <ScatterChart margin={margin}>\n {grid ? <CartesianGrid /> : null}\n <XAxis dataKey=\"x\" type=\"number\" name=\"x\" tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis dataKey=\"y\" type=\"number\" name=\"y\" tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((s, i) => (\n <Scatter key={String(s.id)} name={String(s.id)} data={s.data} fill={seriesColor(config, String(s.id), i)} />\n ))}\n </ScatterChart>\n );\n }\n\n case \"radar\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <RadarChart data={rows} margin={margin}>\n {grid ? <PolarGrid /> : null}\n <PolarAngleAxis dataKey={index} />\n <PolarRadiusAxis tick={false} axisLine={false} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Radar\n key={key}\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n fill={seriesColor(config, key, i)}\n fillOpacity={0.2}\n strokeWidth={2}\n />\n ))}\n </RadarChart>\n );\n }\n\n case \"funnel\": {\n const slices = toSlices(data as ChartSlice[]);\n return (\n <FunnelChart margin={margin}>\n {tip}\n {leg}\n <Funnel data={slices} dataKey=\"value\" nameKey=\"name\">\n {slices.map((slice, i) => (\n <Cell key={slice.id} fill={seriesColor(config, slice.id, i)} />\n ))}\n </Funnel>\n </FunnelChart>\n );\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -40,6 +40,26 @@ function tokenPx(name, el) {
|
|
|
40
40
|
if (raw.endsWith("rem")) return n * (Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16);
|
|
41
41
|
return n;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* The margin a Recharts plot is drawn with, in pixels, resolved on `el`
|
|
45
|
+
* (default `<html>`).
|
|
46
|
+
*
|
|
47
|
+
* Recharts takes a margin as numbers rather than CSS, so the token has to be
|
|
48
|
+
* read and handed over already in pixels. It is `--ds-space-2` at the top and
|
|
49
|
+
* on the right, which is what keeps the topmost y tick label and the last x
|
|
50
|
+
* tick label inside the box; the bottom and the left stay zero because the
|
|
51
|
+
* axes draw their own gutter there. Without a DOM — SSR — the fallback is the
|
|
52
|
+
* 8px that token emits at the default scale, asserted in `chart.test.tsx`.
|
|
53
|
+
*/
|
|
54
|
+
function plotMargin(el) {
|
|
55
|
+
const space2 = tokenPx("--ds-space-2", el) ?? 8;
|
|
56
|
+
return {
|
|
57
|
+
top: space2,
|
|
58
|
+
right: space2,
|
|
59
|
+
bottom: 0,
|
|
60
|
+
left: 0
|
|
61
|
+
};
|
|
62
|
+
}
|
|
43
63
|
let probe;
|
|
44
64
|
function hex2(n) {
|
|
45
65
|
return n.toString(16).padStart(2, "0");
|
|
@@ -187,6 +207,6 @@ function useThemeVersion() {
|
|
|
187
207
|
return version;
|
|
188
208
|
}
|
|
189
209
|
//#endregion
|
|
190
|
-
export { VIZ_SERIES_COUNT, chartScales, contrastLabel, isDarkSurface, piecewiseHex, readToken, resolveColor, resolveVizSeries, toHex, tokenPx, useThemeVersion, vizSeriesVars, vizVar };
|
|
210
|
+
export { VIZ_SERIES_COUNT, chartScales, contrastLabel, isDarkSurface, piecewiseHex, plotMargin, readToken, resolveColor, resolveVizSeries, toHex, tokenPx, useThemeVersion, vizSeriesVars, vizVar };
|
|
191
211
|
|
|
192
212
|
//# sourceMappingURL=tokens.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","names":[],"sources":["../../../src/components/chart/tokens.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { vizSeries } from \"cortena-design/tokens\";\n\n/**\n * Token plumbing for the chart engines.\n *\n * Recharts draws SVG, so it can take `var(--ds-viz-1)` verbatim and the\n * browser resolves it. Nivo cannot: its canvas renderer hands colours to a\n * 2D context, and its SVG renderer runs colours through d3-color for label and\n * border derivation, and neither understands `var()`. So every Nivo value is\n * read from the `--ds-*` custom property at runtime and handed over as a\n * concrete hex. Nothing in this file is a literal; on the server (no DOM) the\n * `var()` form is returned so SSR markup still resolves in the browser.\n */\n\n/** Number of categorical series colours the design system defines (`--ds-viz-1..n`). */\nexport const VIZ_SERIES_COUNT = vizSeries.length;\n\n/** `var(--ds-viz-N)` for a zero-based series index, wrapping past the palette. */\nexport function vizVar(index: number): string {\n const n = VIZ_SERIES_COUNT;\n return `var(--ds-viz-${(((index % n) + n) % n) + 1})`;\n}\n\n/** The categorical palette as `var()` references, in series order. */\nexport const vizSeriesVars: readonly string[] = /* @__PURE__ */ Array.from({ length: VIZ_SERIES_COUNT }, (_, i) =>\n vizVar(i),\n);\n\nfunction hasDom(): boolean {\n return typeof document !== \"undefined\" && typeof getComputedStyle === \"function\";\n}\n\n/** Read a custom property as the browser computes it on `el` (default: `<html>`). */\nexport function readToken(name: string, el?: Element | null): string {\n if (!hasDom()) return \"\";\n return getComputedStyle(el ?? document.documentElement).getPropertyValue(name).trim();\n}\n\n/** A `--ds-text-*` size (rem or px) as a pixel number, for engines that want numbers. */\nexport function tokenPx(name: string, el?: Element | null): number | undefined {\n const raw = readToken(name, el);\n if (!raw) return undefined;\n const n = Number.parseFloat(raw);\n if (Number.isNaN(n)) return undefined;\n if (raw.endsWith(\"rem\")) {\n const root = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;\n return n * root;\n }\n return n;\n}\n\nlet probe: CanvasRenderingContext2D | null | undefined;\n\nfunction hex2(n: number): string {\n return n.toString(16).padStart(2, \"0\");\n}\n\n/**\n * Convert any CSS colour the browser understands (oklch, color-mix, rgba…) to\n * `#rrggbb`, by painting it into an offscreen 2D context. Alpha is composited\n * against transparent, so translucent tokens come back opaque; chart fills\n * never use those.\n */\nexport function toHex(css: string): string | undefined {\n if (!hasDom() || !css) return undefined;\n if (probe === undefined) {\n probe = document.createElement(\"canvas\").getContext(\"2d\", { willReadFrequently: true });\n }\n if (!probe) return undefined;\n probe.clearRect(0, 0, 1, 1);\n probe.fillStyle = css;\n probe.fillRect(0, 0, 1, 1);\n const d = probe.getImageData(0, 0, 1, 1).data;\n return `#${hex2(d[0] ?? 0)}${hex2(d[1] ?? 0)}${hex2(d[2] ?? 0)}`;\n}\n\n/**\n * A token as a concrete colour. Returns hex in the browser; on the server the\n * `var()` form, which is fine for SVG markup and irrelevant for canvas.\n */\nexport function resolveColor(token: string, el?: Element | null): string {\n return toHex(readToken(token, el)) ?? `var(${token})`;\n}\n\n/** The categorical palette resolved to concrete colours, in series order. */\nexport function resolveVizSeries(el?: Element | null): string[] {\n return vizSeriesVars.map((_, i) => resolveColor(`--ds-viz-${i + 1}`, el));\n}\n\n/** Relative luminance of a `#rrggbb`, for choosing a scale direction. */\nfunction luminance(hex: string): number {\n const c = (i: number) => {\n const v = Number.parseInt(hex.slice(i, i + 2), 16) / 255;\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * c(1) + 0.7152 * c(3) + 0.0722 * c(5);\n}\n\n/**\n * A label colour that reads on a given fill: the design system's ink on light\n * fills, its white on dark ones. Nivo cells and arcs paint their labels with\n * this so a value on a deep sequential cell stays legible.\n */\nexport function contrastLabel(el?: Element | null): (fill: string) => string {\n const ink = resolveColor(\"--ds-base-text-ink\", el);\n const white = resolveColor(\"--ds-base-white\", el);\n return (fill: string) => {\n const hex = fill.startsWith(\"#\") && fill.length === 7 ? fill : toHex(fill);\n return hex && luminance(hex) < 0.3 ? white : ink;\n };\n}\n\n/** True when the resolved `--ds-background` is dark, whatever set the theme (attribute or OS). */\nexport function isDarkSurface(el?: Element | null): boolean {\n const bg = toHex(readToken(\"--ds-background\", el));\n return bg ? luminance(bg) < 0.4 : false;\n}\n\n/** Linear interpolation through a list of hex stops; `t` in [0, 1]. */\nexport function piecewiseHex(stops: string[]): (t: number) => string {\n const rgb = stops.map((h) => [1, 3, 5].map((i) => Number.parseInt(h.slice(i, i + 2), 16)));\n return (t: number) => {\n const clamped = Math.min(1, Math.max(0, Number.isNaN(t) ? 0 : t));\n const pos = clamped * (rgb.length - 1);\n const i = Math.max(0, Math.min(rgb.length - 2, Math.floor(pos)));\n const f = pos - i;\n const a = rgb[i] ?? [0, 0, 0];\n const b = rgb[i + 1] ?? a;\n const mix = (k: number) => Math.round((a[k] ?? 0) + ((b[k] ?? 0) - (a[k] ?? 0)) * f);\n return `#${hex2(mix(0))}${hex2(mix(1))}${hex2(mix(2))}`;\n };\n}\n\nexport interface ChartScales {\n /** Low → high, as concrete hex stops. */\n sequential: string[];\n /** Negative → neutral → positive, as concrete hex stops. */\n diverging: string[];\n /** Interpolators over the stops, `t` in [0, 1], for Nivo's `interpolator` configs. */\n sequentialInterpolator: (t: number) => string;\n divergingInterpolator: (t: number) => string;\n /** `n` evenly spaced sequential swatches, for quantised fills like the calendar. */\n sequentialSteps: (n: number) => string[];\n /** Fill for cells with no value. */\n empty: string;\n /** The chart's surface (`--ds-background`), for the gaps between cells and arcs. */\n surface: string;\n}\n\n/**\n * Sequential and diverging scales from the tokens.\n *\n * The token set has only the eight categorical `--ds-viz-*` colours and no\n * sequential ramp, so the sequential scale is derived from the\n * `--ds-primary-100..900` ladder: light surfaces run 100 → 700 (pale to deep),\n * dark surfaces run 900 → 400 (deep to bright), so low values sit near the\n * background in both themes. The diverging scale runs `--ds-viz-5` (rose)\n * through the theme's neutral (`--ds-neutral-200` light, `--ds-neutral-700`\n * dark) to `--ds-viz-1` (blue).\n */\nexport function chartScales(el?: Element | null): ChartScales {\n const dark = isDarkSurface(el);\n const seqTokens = dark\n ? [\"--ds-primary-900\", \"--ds-primary-800\", \"--ds-primary-600\", \"--ds-primary-400\"]\n : [\"--ds-primary-100\", \"--ds-primary-300\", \"--ds-primary-500\", \"--ds-primary-700\"];\n const sequential = seqTokens.map((t) => resolveColor(t, el));\n const diverging = [\"--ds-viz-5\", dark ? \"--ds-neutral-700\" : \"--ds-neutral-200\", \"--ds-viz-1\"].map(\n (t) => resolveColor(t, el),\n );\n const sequentialInterpolator = piecewiseHex(sequential);\n const divergingInterpolator = piecewiseHex(diverging);\n return {\n sequential,\n diverging,\n sequentialInterpolator,\n divergingInterpolator,\n sequentialSteps: (n) =>\n Array.from({ length: n }, (_, i) => sequentialInterpolator(n === 1 ? 1 : i / (n - 1))),\n empty: resolveColor(\"--ds-muted\", el),\n surface: resolveColor(\"--ds-background\", el),\n };\n}\n\n/**\n * A counter that bumps whenever the theme may have changed: the `data-theme`\n * or `class` attribute on `<html>`, or the OS colour scheme. Resolved token\n * values are memoised on it so Nivo re-reads them on a flip.\n */\nexport function useThemeVersion(): number {\n const [version, setVersion] = useState(0);\n useEffect(() => {\n if (!hasDom()) return;\n const bump = () => setVersion((v) => v + 1);\n const observer = new MutationObserver(bump);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-theme\", \"class\", \"style\"],\n });\n const media = window.matchMedia?.(\"(prefers-color-scheme: dark)\");\n media?.addEventListener?.(\"change\", bump);\n return () => {\n observer.disconnect();\n media?.removeEventListener?.(\"change\", bump);\n };\n }, []);\n return version;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tokens.js","names":[],"sources":["../../../src/components/chart/tokens.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { vizSeries } from \"cortena-design/tokens\";\n\n/**\n * Token plumbing for the chart engines.\n *\n * Recharts draws SVG, so it can take `var(--ds-viz-1)` verbatim and the\n * browser resolves it. Nivo cannot: its canvas renderer hands colours to a\n * 2D context, and its SVG renderer runs colours through d3-color for label and\n * border derivation, and neither understands `var()`. So every Nivo value is\n * read from the `--ds-*` custom property at runtime and handed over as a\n * concrete hex. Nothing in this file is a literal; on the server (no DOM) the\n * `var()` form is returned so SSR markup still resolves in the browser.\n */\n\n/** Number of categorical series colours the design system defines (`--ds-viz-1..n`). */\nexport const VIZ_SERIES_COUNT = vizSeries.length;\n\n/** `var(--ds-viz-N)` for a zero-based series index, wrapping past the palette. */\nexport function vizVar(index: number): string {\n const n = VIZ_SERIES_COUNT;\n return `var(--ds-viz-${(((index % n) + n) % n) + 1})`;\n}\n\n/** The categorical palette as `var()` references, in series order. */\nexport const vizSeriesVars: readonly string[] = /* @__PURE__ */ Array.from({ length: VIZ_SERIES_COUNT }, (_, i) =>\n vizVar(i),\n);\n\nfunction hasDom(): boolean {\n return typeof document !== \"undefined\" && typeof getComputedStyle === \"function\";\n}\n\n/** Read a custom property as the browser computes it on `el` (default: `<html>`). */\nexport function readToken(name: string, el?: Element | null): string {\n if (!hasDom()) return \"\";\n return getComputedStyle(el ?? document.documentElement).getPropertyValue(name).trim();\n}\n\n/** A `--ds-text-*` size (rem or px) as a pixel number, for engines that want numbers. */\nexport function tokenPx(name: string, el?: Element | null): number | undefined {\n const raw = readToken(name, el);\n if (!raw) return undefined;\n const n = Number.parseFloat(raw);\n if (Number.isNaN(n)) return undefined;\n if (raw.endsWith(\"rem\")) {\n const root = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;\n return n * root;\n }\n return n;\n}\n\n/** The plot margin in pixels when there is no DOM: what `--ds-space-2` emits. */\nexport const PLOT_MARGIN_FALLBACK = 8;\n\n/**\n * The margin a Recharts plot is drawn with, in pixels, resolved on `el`\n * (default `<html>`).\n *\n * Recharts takes a margin as numbers rather than CSS, so the token has to be\n * read and handed over already in pixels. It is `--ds-space-2` at the top and\n * on the right, which is what keeps the topmost y tick label and the last x\n * tick label inside the box; the bottom and the left stay zero because the\n * axes draw their own gutter there. Without a DOM — SSR — the fallback is the\n * 8px that token emits at the default scale, asserted in `chart.test.tsx`.\n */\nexport function plotMargin(el?: Element | null): { top: number; right: number; bottom: number; left: number } {\n const space2 = tokenPx(\"--ds-space-2\", el) ?? PLOT_MARGIN_FALLBACK;\n return { top: space2, right: space2, bottom: 0, left: 0 };\n}\n\nlet probe: CanvasRenderingContext2D | null | undefined;\n\nfunction hex2(n: number): string {\n return n.toString(16).padStart(2, \"0\");\n}\n\n/**\n * Convert any CSS colour the browser understands (oklch, color-mix, rgba…) to\n * `#rrggbb`, by painting it into an offscreen 2D context. Alpha is composited\n * against transparent, so translucent tokens come back opaque; chart fills\n * never use those.\n */\nexport function toHex(css: string): string | undefined {\n if (!hasDom() || !css) return undefined;\n if (probe === undefined) {\n probe = document.createElement(\"canvas\").getContext(\"2d\", { willReadFrequently: true });\n }\n if (!probe) return undefined;\n probe.clearRect(0, 0, 1, 1);\n probe.fillStyle = css;\n probe.fillRect(0, 0, 1, 1);\n const d = probe.getImageData(0, 0, 1, 1).data;\n return `#${hex2(d[0] ?? 0)}${hex2(d[1] ?? 0)}${hex2(d[2] ?? 0)}`;\n}\n\n/**\n * A token as a concrete colour. Returns hex in the browser; on the server the\n * `var()` form, which is fine for SVG markup and irrelevant for canvas.\n */\nexport function resolveColor(token: string, el?: Element | null): string {\n return toHex(readToken(token, el)) ?? `var(${token})`;\n}\n\n/** The categorical palette resolved to concrete colours, in series order. */\nexport function resolveVizSeries(el?: Element | null): string[] {\n return vizSeriesVars.map((_, i) => resolveColor(`--ds-viz-${i + 1}`, el));\n}\n\n/** Relative luminance of a `#rrggbb`, for choosing a scale direction. */\nfunction luminance(hex: string): number {\n const c = (i: number) => {\n const v = Number.parseInt(hex.slice(i, i + 2), 16) / 255;\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * c(1) + 0.7152 * c(3) + 0.0722 * c(5);\n}\n\n/**\n * A label colour that reads on a given fill: the design system's ink on light\n * fills, its white on dark ones. Nivo cells and arcs paint their labels with\n * this so a value on a deep sequential cell stays legible.\n */\nexport function contrastLabel(el?: Element | null): (fill: string) => string {\n const ink = resolveColor(\"--ds-base-text-ink\", el);\n const white = resolveColor(\"--ds-base-white\", el);\n return (fill: string) => {\n const hex = fill.startsWith(\"#\") && fill.length === 7 ? fill : toHex(fill);\n return hex && luminance(hex) < 0.3 ? white : ink;\n };\n}\n\n/** True when the resolved `--ds-background` is dark, whatever set the theme (attribute or OS). */\nexport function isDarkSurface(el?: Element | null): boolean {\n const bg = toHex(readToken(\"--ds-background\", el));\n return bg ? luminance(bg) < 0.4 : false;\n}\n\n/** Linear interpolation through a list of hex stops; `t` in [0, 1]. */\nexport function piecewiseHex(stops: string[]): (t: number) => string {\n const rgb = stops.map((h) => [1, 3, 5].map((i) => Number.parseInt(h.slice(i, i + 2), 16)));\n return (t: number) => {\n const clamped = Math.min(1, Math.max(0, Number.isNaN(t) ? 0 : t));\n const pos = clamped * (rgb.length - 1);\n const i = Math.max(0, Math.min(rgb.length - 2, Math.floor(pos)));\n const f = pos - i;\n const a = rgb[i] ?? [0, 0, 0];\n const b = rgb[i + 1] ?? a;\n const mix = (k: number) => Math.round((a[k] ?? 0) + ((b[k] ?? 0) - (a[k] ?? 0)) * f);\n return `#${hex2(mix(0))}${hex2(mix(1))}${hex2(mix(2))}`;\n };\n}\n\nexport interface ChartScales {\n /** Low → high, as concrete hex stops. */\n sequential: string[];\n /** Negative → neutral → positive, as concrete hex stops. */\n diverging: string[];\n /** Interpolators over the stops, `t` in [0, 1], for Nivo's `interpolator` configs. */\n sequentialInterpolator: (t: number) => string;\n divergingInterpolator: (t: number) => string;\n /** `n` evenly spaced sequential swatches, for quantised fills like the calendar. */\n sequentialSteps: (n: number) => string[];\n /** Fill for cells with no value. */\n empty: string;\n /** The chart's surface (`--ds-background`), for the gaps between cells and arcs. */\n surface: string;\n}\n\n/**\n * Sequential and diverging scales from the tokens.\n *\n * The token set has only the eight categorical `--ds-viz-*` colours and no\n * sequential ramp, so the sequential scale is derived from the\n * `--ds-primary-100..900` ladder: light surfaces run 100 → 700 (pale to deep),\n * dark surfaces run 900 → 400 (deep to bright), so low values sit near the\n * background in both themes. The diverging scale runs `--ds-viz-5` (rose)\n * through the theme's neutral (`--ds-neutral-200` light, `--ds-neutral-700`\n * dark) to `--ds-viz-1` (blue).\n */\nexport function chartScales(el?: Element | null): ChartScales {\n const dark = isDarkSurface(el);\n const seqTokens = dark\n ? [\"--ds-primary-900\", \"--ds-primary-800\", \"--ds-primary-600\", \"--ds-primary-400\"]\n : [\"--ds-primary-100\", \"--ds-primary-300\", \"--ds-primary-500\", \"--ds-primary-700\"];\n const sequential = seqTokens.map((t) => resolveColor(t, el));\n const diverging = [\"--ds-viz-5\", dark ? \"--ds-neutral-700\" : \"--ds-neutral-200\", \"--ds-viz-1\"].map(\n (t) => resolveColor(t, el),\n );\n const sequentialInterpolator = piecewiseHex(sequential);\n const divergingInterpolator = piecewiseHex(diverging);\n return {\n sequential,\n diverging,\n sequentialInterpolator,\n divergingInterpolator,\n sequentialSteps: (n) =>\n Array.from({ length: n }, (_, i) => sequentialInterpolator(n === 1 ? 1 : i / (n - 1))),\n empty: resolveColor(\"--ds-muted\", el),\n surface: resolveColor(\"--ds-background\", el),\n };\n}\n\n/**\n * A counter that bumps whenever the theme may have changed: the `data-theme`\n * or `class` attribute on `<html>`, or the OS colour scheme. Resolved token\n * values are memoised on it so Nivo re-reads them on a flip.\n */\nexport function useThemeVersion(): number {\n const [version, setVersion] = useState(0);\n useEffect(() => {\n if (!hasDom()) return;\n const bump = () => setVersion((v) => v + 1);\n const observer = new MutationObserver(bump);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-theme\", \"class\", \"style\"],\n });\n const media = window.matchMedia?.(\"(prefers-color-scheme: dark)\");\n media?.addEventListener?.(\"change\", bump);\n return () => {\n observer.disconnect();\n media?.removeEventListener?.(\"change\", bump);\n };\n }, []);\n return version;\n}\n"],"mappings":"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"}
|