Skip to content

Release v4.12.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 08:43
366dee8

Summary

Components draws a glyph in 66 places; 6 of them could be replaced before this change (Dialog.closeIcon, TagGroup.removeIcon, Breadcrumbs.separator, and the per-instance icon on Message and on a queued toast). A stable part styles the element around a glyph and cannot replace its children, so Display/Chip's × and DataTableCore's ▲/▼ had no hook at all. Rather than add sixty more per-component props one release at a time, the provider — which already owns locale and every Components-owned label — now owns a product-wide icon vocabulary, with the per-component props kept as the single-call-site override.

This is a minor (additive provider option, unchanged defaults, unchanged props). No label applied, as requested.

Added

  • icons on CratisComponentsProvider, replacing Components-owned glyphs product-wide and resolved per site as per-component prop → provider icon → built-in glyph (#299)
  • First cut of the icon vocabulary: close, remove, expand, sortAscending, sortDescending, previous, next and clear (#299)
  • useCratisIcon() and CratisIconResolver, the resolver Components uses at every icon site, exported from the setup-only root barrel for product components that want to follow the same vocabulary (#299)
  • Chip, DataTableCore's sort indicators, ComboBox, Dropdown, TablePaginator, DatePickerInput, ProgressSpinner, a toast's dismiss and loading marks, CommandForm's chips field, ChatSidebar, the Chat panel and PivotViewer's filter/detail panels now resolve their glyphs through that vocabulary instead of hard-coding them (#299)
  • Documentation/Common/cratis-components-provider.md documents icons the way messages is documented: the whole vocabulary with what each name draws, its default, and every component that uses it (#299)

Review request — the vocabulary names

The mechanism is settled by the decision on #299; the names are not, and a name is harder to withdraw than to add, so this PR is for review rather than merge. Each name earns its place by being a concept more than one component draws, or one that has been asked for:

Name Draws Default Sites it serves (from the inventory)
close Dismiss this surface × Dialogs/DialogImplementation.tsx:303, Notifications/Toaster.tsx:193, Chat/ChatSidebar.tsx:345, Chat/Kit/Chat.tsx:262, PivotViewer/components/DetailPanel.tsx:115 — 5 sites
remove Remove this value × Common/TagGroup.tsx:118, Display/Chip.tsx:51, CommandForm/fields/ChipsField.tsx:123 — 3 sites. Chip is the inventory's clearest "consumer would want this and can't"
expand Open a closed surface ⌄ (▾ in ComboBox) Dropdown/DropdownImplementation.tsx:305,528,674, Common/ComboBox.tsx:395 — 4 sites, 2 components, 2 different built-in glyphs
sortAscending Ascending sort ▲ DataTables/DataTableCore.tsx:516 — 1 component, named in the decision, and previously not even styleable
sortDescending Descending sort ▼ DataTables/DataTableCore.tsx:517 — same
previous Step backwards ‹ DataTables/TablePaginatorImplementation.tsx:76, Common/DatePickerInputImplementation.tsx:342 — 2 components, same glyph, same concept
next Step forwards › DataTables/TablePaginatorImplementation.tsx:94, Common/DatePickerInputImplementation.tsx:366 — same
clear Clear the current value × Dropdown/DropdownImplementation.tsx:326,463,549,696, Filter/FilterPanel.tsx:408 — 5 sites. Deliberately not merged into close: dismissing a surface and emptying a value are different concepts that happen to share a glyph today
busy Busy/loading mark spinner ring (◌ in a toast) Display/ProgressSpinner.tsx, Notifications/Toaster.tsx:173 — the decision's "busy mark"; the spinner's shape was previously unreachable

Open questions for the maintainer:

  1. expand vs chevron vs expandCollapse. expand names the affordance, not the shape, which is why the same name covers Dropdown's ⌄ and ComboBox's ▾. If the preferred convention is to name the mark rather than the action, say so before this ships.
  2. clear vs folding it into remove. Kept separate on purpose (see the table). Three names currently default to ×; a product registering one icon for all three writes three lines.
  3. previous/next vs previousPage/nextPage. Kept generic so the paginator and the calendar share them. The paginator's «/» (first/last page) are not in this cut, so a product re-iconing pagination still meets two hard-coded glyphs — deliberate, since no second component draws "first"/"last".
  4. busy vs spinner/loading. busy matches the existing data-busy attribute convention; loading matches data-loading and the toast's own loading flag.
  5. Plural grouping. messages groups by component (messages.dialog.close); icons is deliberately flat, because an icon concept is shared across components while a label belongs to one. Confirm this is the intended shape before the names become contract.

Verification

  • Source: yarn ci — 445 spec files, 1660 tests, green. node scripts/verify-api-docs.mjs — green.
  • Storybook: yarn build && yarn verify-indexes && yarn test-storybook — 1704 story/appearance/axe cases, green. Story counts are unchanged (327 stories / 74 docs), so verify-storybook-indexes.mjs needed no edit.
  • Documentation/verify-markdown.sh — green.
  • Zero-change proof. A temporary harness rendered every wired component with no icons registered — Dialog, two TagGroups (one with removeIcon), Chip, all three Dropdown branches with showClear, ComboBox, DataTableCore unsorted/ascending/descending, TablePaginator, ProgressSpinner, DatePickerInput with the calendar open, ChatSidebar, and a loading plus a success toast — and captured document.body.innerHTML at five points. The capture was taken with this change reverted and again with it applied: both are 90,983 bytes and sha256 21d7b0cd90cb12f33c17dea0b4cb268967882e5612c0a409e907d2ec86458bc0, diff clean. The markup contains every glyph under test (×, ⌄, ▾, ▲, ▼, ‹, ›, ◌, the spinner <svg>), so the comparison is meaningful rather than vacuously equal. Common/for_CratisComponentsProvider/when_no_icons_are_registered.tsx keeps that property under CI.
  • Specs proven by reverting. With the implementation reverted (specs kept), 11 assertions fail across when_every_first_cut_icon_is_overridden, when_an_icon_prop_and_a_provider_icon_are_both_set ("should use the provider icon for a sibling site that sets no prop") and when_merging_icons ("should carry an element icon over by identity"). when_no_icons_are_registered passes in both states, which is exactly its job.

Not in this change

Every remaining inventory site keeps its glyph and stays unwired, because no second component draws the same concept: the paginator's «/», the date picker's ▦ calendar trigger, Checkbox/CheckboxField's ✓, NumberInput's −/+, PasswordField's ◉/○, RatingField's ★, ColumnFilterMenu's ⌕, Breadcrumbs.separator, Message/Toaster severity marks (they already take a per-instance icon), FilterPanel's • and its CSS-border chevron (not a glyph), and the whole react-icons-based composite set — SchemaEditor (9), Chat/Chat/Kit (9 remaining), Canvas (5), TimeMachine (4), ObjectContentEditor/ObjectNavigationalBar (4), PivotViewer's toolbar (3). Those name product actions rather than generic affordances and are a separate scoping question, as the inventory notes.


Measured after review (maintainer notes)

Two review findings were folded in, and one of them turned out to be a win rather than a cost.

The configuration seam moved out of Common. Placing useCratisIcon in Source/Common/ made Chat, Filter and PivotViewer reach the Common area for the first time, and because per-area stylesheets are derived from each subpath's runtime import closure (#301), their sheets swallowed the whole of Common's CSS — Chat 6 656 → 9 642 gzip, Filter 3 584 → 6 651, PivotViewer 7 680 → 10 655, failing 31 checks. A CSS-free hook must not carry a stylesheet, so the shared seam now lives in Source/configuration/. Every per-area budget passes without any ceiling being raised, and two areas came out smaller than on main for the same reason the others had grown: Dialogs 6 582 → 2 972 and Notifications 4 988 → 1 099, because they no longer reach Common either.

The setup-only root allowlist. CratisComponentsIcons, CratisIconResolver and useCratisIcon are exported from the setup-only root barrel, which both the codemod and the ESLint rule gate with an allowlist that a spec holds identical across the two copies. They are on it now.

Gates after both: ESLint, Migrator and Source yarn ci (449 files / 1 672 specs), the packed archive with all 21 per-area sheets in budget, Storybook build, index attestation and the axe matrix — all green.

Still awaiting a decision on the vocabulary names before this merges.


Vocabulary reviewed: eight names, not nine

busy is out. It reads like a glyph and is not one: ProgressSpinner draws an svg carrying the svg, track and range parts, and a registered icon replacing it would silently delete three parts a product may already be styling. Where the parts contract and the icon vocabulary disagree, the parts contract wins — a consumer can restyle a parted subtree, but cannot get a deleted part back. The spinner is restored byte for byte against main, the loading toast keeps its own ◌, and the name goes with them, since a vocabulary entry serving one glyph in one component has not earned a place.

The rule that falls out is now documented and pinned by the spec that guards the vocabulary:

The vocabulary replaces a glyph, never an element that publishes a part.

That matters beyond this PR: several of the inventory's 66 sites are parted subtrees rather than glyphs, and the same question will arrive with each of them.

The other eight are verified site by site to render the icon as a child of their parted element, so every part survives a registration. sortAscending and sortDescending serve one component each and are kept deliberately: they are the clearest case in the inventory of a glyph a product cannot reach at all today. clear stays separate from close despite sharing × today, because emptying a value and dismissing a surface are different concepts that need not share a shape forever.

One inconsistency is documented rather than fixed: expand has two built-in glyphs, ⌄ on a Dropdown trigger and ▾ on a ComboBox trigger. Registering expand makes them consistent; registering nothing changes nothing. Unifying the defaults would move pixels for every consumer, which is a separate decision and should not ride along inside an additive change.

Gates after the revision: Source yarn ci (449 files / 1 670 specs), API-docs coverage, Migrator and ESLint ci, the packed archive with the aggregate and all 21 per-area sheets in budget with no ceiling raised, Storybook build, index attestation and the axe matrix — all green.