mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Add shared Icon primitive and controlled glyph vocabulary (Carbon, filled)
This commit is contained in:
@@ -171,7 +171,176 @@ UI by swapping one set of values. Borrowed from Carbon's layering model:
|
||||
|
||||
---
|
||||
|
||||
## 5. Charts (`src/core/vega-themes.ts`)
|
||||
## 5. Iconography
|
||||
|
||||
> Geometry and the accessibility floor are set elsewhere: icons keep Carbon's
|
||||
> rounded 2px geometry and are exempt from `--radius` (§3.4), and every icon-only
|
||||
> control carries an accessible name with meaning never resting on colour alone
|
||||
> ([arch 10 §5–6](10-interaction-and-feedback.md)). This section is the **usage**
|
||||
> contract — _when_ a thing earns an icon, and how the set stays coherent.
|
||||
|
||||
**Stance: balanced, label-first.** Astrolabe is a tool its user returns to often
|
||||
— the one context where GOV.UK concedes icons earn their place: _"Icons can be
|
||||
more useful in case working systems, where users are familiar with the interface
|
||||
and return to it frequently … In most cases it's still helpful to include a
|
||||
visible text label alongside any icons"_ (GOV.UK, `styles/images`). So we are
|
||||
neither icon-rich (Carbon's default density) nor icon-austere (GOV.UK's
|
||||
public-service default): the default is **text**; an icon is added only when it
|
||||
does real work, and usually _alongside_ the text, not instead of it.
|
||||
|
||||
### 5.1 The icon-vs-text decision
|
||||
|
||||
Apply in order:
|
||||
|
||||
1. **Default to text.** If a label alone is clear, ship the label. An icon that
|
||||
only decorates fails principle 1 (Considered) and invites the ambiguity GOV.UK
|
||||
warns of — _"people can understand a single icon to mean different things."_
|
||||
2. **Add an icon when it does a job** — one of: speeds scanning of a list/row read
|
||||
repeatedly (dataset marker), signals status/type at a glance (draft dot), or
|
||||
affords a high-frequency action (delete). Carbon's rule holds: _"employ icons
|
||||
sparingly and strategically … to reduce cognitive load."_
|
||||
3. **Pair icon + text by default.** In any labelled control, menu item, or row,
|
||||
the icon rides _alongside_ its text — recognition support (NN/g #6), not a
|
||||
replacement for the word.
|
||||
4. **Icon-only is the exception, and a closed set.** Permitted only for the
|
||||
**universal set** (§5.2) — glyphs whose meaning is unambiguous and which recur
|
||||
everywhere. A new icon-only control is never created ad-hoc; admitting one to
|
||||
the set is a contract change, not a per-component decision.
|
||||
|
||||
| Form | When | Accessible name |
|
||||
| ----------- | -------------------------------------------------------- | ------------------------------------------------- |
|
||||
| Text only | The default. Label is clear on its own. | The visible text |
|
||||
| Icon + text | Icon aids scanning/status; text stays the primary label. | The visible text; icon `aria-hidden` (decorative) |
|
||||
| Icon only | Universal-set glyph in a space-constrained control. | `aria-label` on the control (APG button pattern) |
|
||||
|
||||
> Accessible-name mechanics (APG button pattern): a control's name comes from its
|
||||
> text content, or from `aria-label`/`aria-labelledby` when there is none. So an
|
||||
> icon **beside visible text** is `aria-hidden` (the text names it — avoiding the
|
||||
> duplicate screen-reader readout GOV.UK flags); an icon **alone** needs an
|
||||
> `aria-label`.
|
||||
|
||||
### 5.2 The icon vocabulary (controlled set)
|
||||
|
||||
One icon = one meaning, **app-wide** — GOV.UK: _"Do not use a single icon to
|
||||
represent more than one thing."_ The vocabulary is a **uniqueness ledger**, not a
|
||||
hall of fame: every glyph is registered here so the same meaning always reuses its
|
||||
glyph and no glyph is ever repurposed — even a one-off gets a row, so it can't be
|
||||
reused for something else later. The registry lives in code at
|
||||
[`src/app/components/Icon.tsx`](../../src/app/components/Icon.tsx) (the `IconName`
|
||||
union + `GLYPHS` map); this table is its prose mirror. Glyphs are traced from
|
||||
Carbon and drawn `fill: currentColor`.
|
||||
|
||||
**Core set** — recurring, cross-surface:
|
||||
|
||||
| Meaning | Carbon glyph | Form | Surfaces |
|
||||
| -------------------- | --------------- | --------------------- | ------------------------------------------------- |
|
||||
| Close / dismiss | `Close` (✕) | icon-only ⭐ | `ModalShell`, `Toaster` |
|
||||
| Theme → dark | `Asleep` (moon) | icon-only ⭐ | `ThemeToggle` (shown when light) |
|
||||
| Theme → light | `Light` (sun) | icon-only ⭐ | `ThemeToggle` (shown when dark) |
|
||||
| References a dataset | `DataTable` | icon + text | Library row marker, Linked-datasets list |
|
||||
| Add / create-new | `Add` | icon + text | Library "Create New Snippet", Datasets "New …" |
|
||||
| Delete | `TrashCan` | icon-only ⭐ (danger) | Library row delete¹ — text "Delete" in the panel² |
|
||||
| Unpublished draft | (CSS dot) | status-glyph | Library row (paired with a hidden label) |
|
||||
|
||||
**Status set** — Carbon's **filled** notification glyphs, one per severity. Unlike
|
||||
the outline UI set, these are coloured **by status** (not by surrounding text) and
|
||||
are a deliberate _filled_ sub-family. They add a **redundant, non-colour severity
|
||||
channel** (WCAG 1.4.1): meaning never rests on the bar colour alone, and the
|
||||
triangle shape-codes warning apart from the round error/success/info — so severity
|
||||
survives colour-blindness. Used wherever a status is signalled (toasts today; inline
|
||||
notifications/validation as they arrive):
|
||||
|
||||
| Meaning | Carbon glyph | Colour | Surfaces |
|
||||
| ------- | -------------------- | ----------------------- | ------------------------------------------- |
|
||||
| Error | `ErrorFilled` | `--support-error` | `Toaster` (error) |
|
||||
| Warning | `WarningAltFilled` ▲ | `--support-warning-fg`³ | `Toaster` (warning), Chart Builder warnings |
|
||||
| Success | `CheckmarkFilled` | `--support-success` | `Toaster` (success) |
|
||||
| Info | `InformationFilled` | `--support-info` | `Toaster` (info) |
|
||||
|
||||
**Scoped set** — registered (glyph reserved) but single-surface and **not yet
|
||||
implemented**:
|
||||
|
||||
| Meaning | Carbon glyph | Form | Surface / note |
|
||||
| ---------------- | ------------------ | ----------- | ----------------------------------------------------- |
|
||||
| Swap / transpose | `ArrowsHorizontal` | icon + text | Chart Builder "Swap X/Y" — one button; icon is polish |
|
||||
|
||||
⭐ = **icon-only set**: close + theme (truly universal), plus **delete** as a
|
||||
deliberate destructive-row exception — a dense, repeated list action where a label
|
||||
would cost more than it gives. ✕ means **close only**; delete is `TrashCan`, never
|
||||
✕ (that collision is exactly what one-glyph-one-meaning forbids).
|
||||
|
||||
¹ Row delete is hover/focus-revealed and reddens on hover/focus (arch 10 — reveal &
|
||||
destructive-intent rules). ² "Duplicate" and "Delete" in the detail panel stay
|
||||
**text** (label-first; lower frequency, not a dense row). ³ The raw warning yellow
|
||||
fails contrast on light surfaces, so the warning glyph uses `--support-warning-fg`
|
||||
(darkened amber; the yellow `--toast-accent` stays on the decorative border).
|
||||
|
||||
### 5.3 Size
|
||||
|
||||
Carbon's icon scale, paired to our type. The tokens are live in
|
||||
`styles/tokens.css`; the `Icon` component's `size` prop selects one:
|
||||
|
||||
| Token | Size | Pairs with | Use |
|
||||
| ----------- | ---- | ------------------------------ | ------------------------------------------- |
|
||||
| `--icon-sm` | 16px | 14px body (`--font-size-base`) | Default — inline with text, row markers |
|
||||
| `--icon-md` | 20px | 16px text | Slightly emphasised controls (theme toggle) |
|
||||
| `--icon-lg` | 24px | — | When a larger icon is genuinely needed |
|
||||
| `--icon-xl` | 32px | — | Rare; large display only |
|
||||
|
||||
- _"16px and 20px icons are optimized to feel balanced when paired with 14pt and
|
||||
16pt IBM Plex"_ (Carbon) → **16px (`sm`) is our default**, since body is 14px.
|
||||
- Use an icon **at its scale** — don't rescale a 16px glyph to 11px or 13px (the
|
||||
old 11px dataset glyph and 18px toggle were the drift this fixed).
|
||||
- Carbon's glyphs are drawn on a 32-unit grid with a built-in stroke weight per
|
||||
size; because we render them **filled** (see §5.4) there is no stroke token to
|
||||
set — sizing the SVG is all that's needed.
|
||||
|
||||
### 5.4 Style, colour & alignment
|
||||
|
||||
- **Geometry & fill:** Carbon's rounded 2px corners, per §3.4. Carbon's UI icons
|
||||
are **filled** shapes (`fill: currentColor`) that read as outlines — _not_
|
||||
`stroke`-drawn. Our `Icon` primitive draws fill; the size classes set width and
|
||||
height only. (The two original hand-rolls used `stroke`; tracing the real Carbon
|
||||
glyphs moved us to fill.)
|
||||
- **Colour:** monochrome, one colour, **inherits `currentColor`** so it matches
|
||||
its text — Carbon: _"match your icon colour with your text colour … don't use
|
||||
different colours for text and icons"_; must pass contrast. Two sanctioned
|
||||
recolours: **destructive intent** (delete reddens to `--support-error` on
|
||||
hover/focus — arch 10) and the **status sub-family** (§5.2), coloured by severity
|
||||
rather than by text — those are graphical status objects (WCAG 3:1), and warning
|
||||
uses the darkened `--support-warning-fg` so it clears contrast on light surfaces.
|
||||
- **Alignment:** centre-align with adjacent text — never baseline-align (Carbon).
|
||||
- **Sourcing:** Carbon is **not a dependency**; we transcribe the glyph's SVG
|
||||
geometry into `Icon.tsx`'s `GLYPHS` map (Carbon's third-party rule, inverted — a
|
||||
new glyph must be _"visually balanced"_ with the set). Match an existing icon's
|
||||
32-grid when adding one.
|
||||
- **Hit area:** the interactive target (the button), not the glyph, owns the click
|
||||
size — our 32/40px buttons already clear comfortable targets; never shrink the
|
||||
target down to the icon.
|
||||
|
||||
### 5.5 Current state
|
||||
|
||||
The contract is implemented across the M1–M4 surfaces:
|
||||
|
||||
- **Infrastructure:** `--icon-*` tokens in `styles/tokens.css`; a shared
|
||||
[`Icon`](../../src/app/components/Icon.tsx) primitive + the `GLYPHS` registry as
|
||||
the single source of truth. Components import `Icon`, never inline an SVG.
|
||||
- **Core set live:** `Close` (ModalShell, Toaster — replacing the bare ✕/×),
|
||||
`Asleep`/`Light` (ThemeToggle, now on-scale), `DataTable` (library row + linked
|
||||
list, replacing the 11px cylinder), `TrashCan` (row delete), `Add` (both
|
||||
"create-new" buttons). The draft dot is unchanged.
|
||||
- **Status set live:** the four filled glyphs (`ErrorFilled` / `WarningAltFilled` /
|
||||
`CheckmarkFilled` / `InformationFilled`) in `Toaster`, coloured by kind; the
|
||||
Chart-Builder warnings reuse `WarningAltFilled` (replacing the old ⚠ character).
|
||||
- **Deferred (scoped set):** only the Chart-Builder `ArrowsHorizontal` (swap-axes) —
|
||||
registered, not built; a one-button polish revisited with the next Chart-Builder
|
||||
pass.
|
||||
|
||||
No open status thread remains — the status-glyph question is settled here.
|
||||
|
||||
---
|
||||
|
||||
## 6. Charts (`src/core/vega-themes.ts`)
|
||||
|
||||
The chart `Config` is themed to match the app, per theme:
|
||||
|
||||
@@ -185,7 +354,7 @@ The chart `Config` is themed to match the app, per theme:
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementation map
|
||||
## 7. Implementation map
|
||||
|
||||
| Artifact | Role |
|
||||
| ------------------------------------------------ | ----------------------------------------------------- |
|
||||
@@ -212,7 +381,7 @@ keeps the specimen finite, honest, and worth trusting.
|
||||
|
||||
---
|
||||
|
||||
## 7. Inspiration sources — where to look for more
|
||||
## 8. Inspiration sources — where to look for more
|
||||
|
||||
We treat IBM/Carbon as inspiration, so we mine its **source repos**, not the live
|
||||
doc sites. The sites (`carbondesignsystem.com`, `ibm.com/design/language`) are
|
||||
@@ -220,13 +389,13 @@ JS-rendered and don't fetch cleanly — **clone the repo and read it locally ins
|
||||
Convention: clone under `/Users/oleh/code/reference/` with
|
||||
`git clone --depth 1 https://github.com/carbon-design-system/<repo>.git`.
|
||||
|
||||
| Need | Repo | Where it lives |
|
||||
| -------------------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Principles / the "why"** (philosophy, 2x grid, color rationale, type, motion, icon geometry) | `design-language-website` | `src/pages/`: `philosophy/principles.mdx`, `2x-grid.mdx`, `color.mdx`, `typography/*.mdx`, `animation/overview.mdx`, `iconography/ui-icons/design.mdx` (~1.4 GB clone — image-heavy; the MDX is what we want) |
|
||||
| **Token values** (gray/blue ramps, type scale, font families, motion durations/easings, theme role→value maps) | `carbon` | `packages/colors/src/colors.ts`, `packages/type/src/{scale,fontFamily,fontWeight}.ts`, `packages/motion/src/index.ts`, `packages/themes/src/{white,g100}.ts` |
|
||||
| **Component-level usage guidance** | `carbon-website` | `src/pages/**/*.mdx` |
|
||||
| **Data-viz categorical chart palette** (for `vega-themes.ts` `range.category`) | `carbon-charts` | cloned in M1.5 → `packages/core/scss/_color-palette.scss` (the `'14'` pairing, white + g100); token→hex resolved against `carbon` `packages/colors/src/colors.ts` |
|
||||
| Need | Repo | Where it lives |
|
||||
| -------------------------------------------------------------------------------------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Principles / the "why"** (philosophy, 2x grid, color rationale, type, motion, icon geometry) | `design-language-website` | `src/pages/`: `philosophy/principles.mdx`, `2x-grid.mdx`, `color.mdx`, `typography/*.mdx`, `animation/overview.mdx`, `iconography/ui-icons/{design,usage}.mdx` (~1.4 GB clone — image-heavy; the MDX is what we want). Icon **usage** rules (§5) also draw on `carbon-website/src/pages/elements/icons/usage.mdx` + GOV.UK `styles/images/index.md` |
|
||||
| **Token values** (gray/blue ramps, type scale, font families, motion durations/easings, theme role→value maps) | `carbon` | `packages/colors/src/colors.ts`, `packages/type/src/{scale,fontFamily,fontWeight}.ts`, `packages/motion/src/index.ts`, `packages/themes/src/{white,g100}.ts` |
|
||||
| **Component-level usage guidance** | `carbon-website` | `src/pages/**/*.mdx` |
|
||||
| **Data-viz categorical chart palette** (for `vega-themes.ts` `range.category`) | `carbon-charts` | cloned in M1.5 → `packages/core/scss/_color-palette.scss` (the `'14'` pairing, white + g100); token→hex resolved against `carbon` `packages/colors/src/colors.ts` |
|
||||
|
||||
> The decisions we made _from_ these sources are captured above (§1–6) and in the
|
||||
> The decisions we made _from_ these sources are captured above (§1–7) and in the
|
||||
> specimen, so we don't need to re-derive them — only return to the repos to extend
|
||||
> the research (e.g. the chart palette, or a component pattern we haven't tackled).
|
||||
|
||||
Reference in New Issue
Block a user