4ce22d6d05
CI / commits (pull_request) Successful in 3m31s
CI / scan (pull_request) Successful in 4m28s
CI / check (pull_request) Failing after 8m9s
CI / a11y (pull_request) Successful in 3m56s
Docs site / build (pull_request) Successful in 4m59s
CI / perf (pull_request) Successful in 10m8s
Implementation of ADR-0023 (PR 1). The next chantier wires these
components into the audit-log page; this PR delivers the lib + the
three starter chart types + the a11y contract.
Deps:
* d3 + @types/d3 (top-level)
* d3-shape + @types/d3-shape (donut's pie/arc generators)
* d3-scale-chromatic + @types/d3-scale-chromatic (palettes)
* @observablehq/plot (bar + stacked-bar)
Lib `libs/shared/charts/`:
* `_internal/palette.ts` — single source of palettes. Sequential
(Viridis / Cividis for dark mode) + categorical (ColorBrewer
Set2). Colour-blind-safe per the ColorBrewer review.
* `_internal/a11y.ts` — chartId generator, SVG <title>/<desc>
injector, `findChartSvg` for Plot's legend-wrapped output,
prefers-reduced-motion guard, theme resolver.
* `_internal/chart-envelope.scss` — shared figure/caption/
fallback rules consumed via `@use` by each chart's .scss.
Extracted at the third consumer per CLAUDE.md's "three similar
things" rule.
* `_internal/chart-types.ts` — common `ChartBaseInputs<T>`
interface every component extends.
Three Plot-or-D3-backed components:
* `<lib-bar-chart>` — Plot.barY, categorical-by-default palette.
* `<lib-donut-chart>` — raw d3-shape (pie + arc); Plot has no
donut mark so we reach for D3 directly. Each slice carries its
own <title> child for SVG-tooltip accessibility.
* `<lib-stacked-bar-chart>` — Plot.barY with `fill` set to the
series key (auto-stacked), legend on.
Each component honours ADR-0023's six commitments:
1. <figure role="img" aria-labelledby aria-describedby>
2. SVG <title> + <desc> as first two children (injected post-
render because Plot doesn't emit them)
3. <details> tabular fallback rendering every data point
4. Palette from the canonical list (no ad-hoc colours)
5. AA-contrast text on axis ticks (theme-aware grayscale via
the .chart-canvas svg text rule)
6. prefers-reduced-motion → data-no-transitions marker on the
SVG, CSS strips animations.
A custom ESLint rule in `libs/shared/charts/eslint.config.mjs`
bans direct imports of `d3-scale-chromatic` outside
`_internal/palette.ts`, so a future contributor can't bypass the
colour-blind-safe contract.
Tests: 13 specs across the three components. Each component covers
the figure ARIA contract, the SVG title/desc injection, the
tabular fallback, and data-reactivity. Donut spec additionally
asserts per-slice <title> labels for screen readers.
Verification:
* `pnpm nx lint test build --projects=shared-charts,portal-shell,portal-admin`
— 12/12 tasks green.
* `pnpm nx build portal-shell --configuration=production` —
i18n-strict prod build clean (the lib ships no i18n marks
since axis labels are caller-supplied).
75 lines
2.0 KiB
JavaScript
75 lines
2.0 KiB
JavaScript
import nx from '@nx/eslint-plugin';
|
|
|
|
export default [
|
|
...nx.configs['flat/base'],
|
|
...nx.configs['flat/typescript'],
|
|
...nx.configs['flat/javascript'],
|
|
{
|
|
"ignores": [
|
|
"**/dist",
|
|
"**/out-tsc",
|
|
"**/vitest.config.*.timestamp*"
|
|
]
|
|
},
|
|
{
|
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
|
rules: {
|
|
'@nx/enforce-module-boundaries': [
|
|
'error',
|
|
{
|
|
enforceBuildableLibDependency: true,
|
|
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$'],
|
|
// Module boundaries per ADR-0003.
|
|
//
|
|
// Scope tags partition the workspace into front (portal-shell) /
|
|
// back (portal-bff) / shared (consumable by both). Type tags
|
|
// partition into apps / feature libs / shared libs.
|
|
//
|
|
// Both axes apply: a lib must satisfy both scope and type rules
|
|
// to be importable.
|
|
depConstraints: [
|
|
{
|
|
sourceTag: 'scope:portal-shell',
|
|
onlyDependOnLibsWithTags: ['scope:portal-shell', 'scope:shared'],
|
|
},
|
|
{
|
|
sourceTag: 'scope:portal-bff',
|
|
onlyDependOnLibsWithTags: ['scope:portal-bff', 'scope:shared'],
|
|
},
|
|
{
|
|
sourceTag: 'scope:shared',
|
|
onlyDependOnLibsWithTags: ['scope:shared'],
|
|
},
|
|
{
|
|
sourceTag: 'type:app',
|
|
onlyDependOnLibsWithTags: ['type:feature', 'type:shared'],
|
|
},
|
|
{
|
|
sourceTag: 'type:feature',
|
|
onlyDependOnLibsWithTags: ['type:feature', 'type:shared'],
|
|
},
|
|
{
|
|
sourceTag: 'type:shared',
|
|
onlyDependOnLibsWithTags: ['type:shared'],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: [
|
|
'**/*.ts',
|
|
'**/*.tsx',
|
|
'**/*.cts',
|
|
'**/*.mts',
|
|
'**/*.js',
|
|
'**/*.jsx',
|
|
'**/*.cjs',
|
|
'**/*.mjs',
|
|
],
|
|
// Override or add rules here
|
|
rules: {},
|
|
},
|
|
];
|