Add buttons, menus and the rest of M3 Expressive's actions
tests / browser (firefox, firefox) (push) Successful in 1m54s
tests / browser (safari, webkit) (push) Successful in 2m17s
tests / lint (push) Successful in 59s
tests / feature (8.4) (push) Successful in 1m7s
tests / feature (8.5) (push) Successful in 1m0s
tests / browser (chrome, chromium) (push) Successful in 1m49s
tests / browser (firefox, firefox) (push) Successful in 1m54s
tests / browser (safari, webkit) (push) Successful in 2m17s
tests / lint (push) Successful in 59s
tests / feature (8.4) (push) Successful in 1m7s
tests / feature (8.5) (push) Successful in 1m0s
tests / browser (chrome, chromium) (push) Successful in 1m49s
<x-button> (label buttons, icon buttons and toggles in five sizes, with filled, tonal, outlined, elevated and text variants in any colour role), <x-tooltip>, <x-menu> with items, groups and separators, <x-button-group>, <x-group> as a connected button group, <x-split-button>, <x-fab>, <x-fab-menu> and <x-loading>. Sizes, colours and shapes come from androidx Compose Material 3's tokens; the loading indicator ports its Morph into SVG + SMIL. Menus follow WAI-ARIA's menu button pattern on popovers placed by CSS anchor positioning. Browser tests run in Chromium, Firefox and WebKit. The showcase fetches the icon names on demand: inlined, they tripped Pest's test server into HTTP 431s under Firefox. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
This commit is contained in:
co-authored by
Claude Opus 5
parent
b48e879254
commit
cd64f4f371
@@ -145,7 +145,7 @@ tokens, `training-row`, `map-shell`, `map-chip`, `product-shot`, `star-rating`,
|
||||
mail colours cannot drift from the app.
|
||||
- **Boost guideline + `livewire-material-development` skill** in the package, and a test that
|
||||
fails when a component is missing from the skill.
|
||||
- **Versioning: `0.x` per wave, `1.0.0` when the catalogue is complete.**
|
||||
- **No version tags until the whole catalogue is done; then `1.0.0`** — the waves land on `main` untagged (decided 2026-09-13; replaces "`0.x` per wave").
|
||||
- **Semantic ink and line utilities carried over from ReStride** (`text-body`, `text-meta`,
|
||||
`text-quiet`, `border-structure`, `border-chrome`, `border-divider`) — cheap `@theme` names
|
||||
ReStride's templates already use, derived from M3 roles.
|
||||
@@ -177,7 +177,7 @@ tokens, `training-row`, `map-shell`, `map-chip`, `product-shot`, `star-rating`,
|
||||
Actions on). SealShare's `vcs` repository entry uses that URL.
|
||||
2. **Split the plan.** *Done 2026-09-13.* This file; SealShare keeps its adoption steps.
|
||||
|
||||
### Phase 1 — Package skeleton (`0.1.0`)
|
||||
### Phase 1 — Package skeleton
|
||||
|
||||
3. **Repository.** `composer.json` (`nonameweb/livewire-material`, PSR-4
|
||||
`NoNameWeb\LivewireMaterial\`, requires `php ^8.4`, `laravel/framework ^13`,
|
||||
@@ -235,7 +235,7 @@ tokens, `training-row`, `map-shell`, `map-chip`, `product-shot`, `star-rating`,
|
||||
and without `icons:cache` it scans the folders first. With ~7,800 symbols that is per
|
||||
request under PHP-FPM. Decided before step 13.
|
||||
|
||||
### Phase 2 — Foundation (`0.2.0`)
|
||||
### Phase 2 — Foundation
|
||||
|
||||
9. **Scheme command.** `resources/node/scheme.mjs` built once in the package repo (esbuild bundle of
|
||||
`@material/material-color-utilities`, committed, Apache-2.0 header); `material:scheme {seed}
|
||||
@@ -317,7 +317,7 @@ tokens, `training-row`, `map-shell`, `map-chip`, `product-shot`, `star-rating`,
|
||||
springs and `x-figure`, and an icon search drawing matches as CSS masks from a showcase-only
|
||||
symbol route (relative URLs — an absolute one carries `APP_URL` and misses the dev server's port).
|
||||
|
||||
### Phase 3 — Actions (`0.3.0`)
|
||||
### Phase 3 — Actions
|
||||
|
||||
19. Primitives the actions need: `loading` (M3 Expressive loading indicator, contained and
|
||||
not), plain `tooltip` (from a fine pointer only, not laid out while hidden), `menu` /
|
||||
@@ -329,18 +329,69 @@ tokens, `training-row`, `map-shell`, `map-chip`, `product-shot`, `star-rating`,
|
||||
21. `icon-button` behaviour inside `button` (icon, no label): standard/filled/tonal/outlined,
|
||||
`selected` toggle (`aria-pressed`), widths.
|
||||
22. `button-group` (standard and connected; `<x-group>` alias with `wire:model` options),
|
||||
`split-button`, `fab` (medium default, large; `size="sm"` → medium), extended FAB, and the
|
||||
`split-button`, `fab` (56px default — M3's deprecated small FAB is gone, so `sm` is the baseline FAB — 80px `md`, 96px `lg`), extended FAB, and the
|
||||
responsive `fab` prop (extended FAB below `sm`, filled header button above — one element),
|
||||
`fab-menu`.
|
||||
|
||||
### Phase 4 — Communication (`0.4.0`)
|
||||
**Phase 3 is done (2026-09-13).** What changed from the steps above:
|
||||
|
||||
- **Values from androidx Compose Material 3's generated tokens** (`Button*Tokens`,
|
||||
`*IconButtonTokens`, `Fab*Tokens`, `ExtendedFab*Tokens`, `FabMenuBaselineTokens`,
|
||||
`ButtonGroupSmallTokens`, `ConnectedButtonGroupSmallTokens`, `SplitButton*Tokens`,
|
||||
`StandardMenuTokens`, `VibrantMenuTokens`, `SegmentedMenuTokens`, `PlainTooltipTokens`,
|
||||
`LoadingIndicatorTokens`), cited in each component's header. Two tokens are wrong and Compose
|
||||
overrides them in code, so the package does too: the text button's label is `primary`, not
|
||||
`on-surface-variant`, and the extra-small button's padding is 12px, not 16px.
|
||||
- **`<x-button>` is label button, icon button and toggle in one**, with `data-icon-button` on the
|
||||
icon-only form. A selected round button squares off; a selected square icon button rounds. A
|
||||
`corners` prop lets composite components (the split button) draw the corners themselves.
|
||||
- **Group and split corners are unlayered CSS** (`resources/css/components/groups.css`): a
|
||||
child button's corners are utilities, and anything in a `@layer` loses to a utility. Inner
|
||||
corners ride a `--group-corner` variable so pressing and selecting change one value while the
|
||||
rounded outer corners stay put. The standard group's press expansion is padding moved from the
|
||||
neighbours to the pressed button (a fixed step per size); icon buttons keep their width.
|
||||
- **`<x-group>` stays ReStride's native-radio design** (checkboxes with `multiple`), restyled as a
|
||||
connected button group — `wire:model`, `x-model` and the arrow keys need no script.
|
||||
- **Tooltips and menus are popovers placed by CSS anchor positioning**, with per-render anchor
|
||||
names generated in Blade (a morph updates the trigger and the popover together). `popover`
|
||||
elements must never get a `display` utility (`flex`), which beats the UA's `display: none` for
|
||||
a closed popover; use `open:flex`.
|
||||
- **Menu keyboard is WAI-ARIA's menu button**; `aria-expanded` and the first item's focus are set
|
||||
synchronously in `open()`, because the popover `toggle` event is queued and a test (or a screen
|
||||
reader) reading in between saw a shut menu. **Bug found by the browser tests:** the guard
|
||||
against a light-dismiss press reopening the menu compared against `closedAt = 0`, so every click
|
||||
in the first 250ms after page load was swallowed; it starts at `-Infinity` now, with a test.
|
||||
- **Anonymous component trap:** every prop is a local variable, so a helper variable in `@php`
|
||||
must not reuse a prop's name — a local `$corners` array silently replaced the `corners` prop.
|
||||
- **The loading indicator is androidx's own geometry, in SVG + SMIL** (`bin/loading-indicator.mjs`,
|
||||
`resources/svg/loading-indicator/`): `Morph` is ported, so each of the seven morphs is a path
|
||||
whose control points SMIL can interpolate in every engine (CSS `d:` has no WebKit support).
|
||||
The spring is two keySplines (<1% error), each morph's path shrinks under its successor at the
|
||||
hand-over, and the per-morph quarter turn runs on its own 2.6s cycle so the 630° per shape
|
||||
cycle never needs a reset. 21.7 KB. Two deliberate differences from Compose: the spring settles
|
||||
inside the 650ms instead of snapping back from 9% past, and frames centre on exact curve bounds,
|
||||
so there is no 0.1–0.18 unit jump at three hand-overs. Reduced motion shows `static.svg`.
|
||||
- **Showcase examples are Blade strings rendered with `Blade::render()` beside their source**
|
||||
(`<x-showcase::example>`, an anonymous component path registered only when the showcase is
|
||||
enabled), so the snippet can never disagree with what is drawn.
|
||||
- **Browser tests in three engines, locally too** (Playwright's Firefox and WebKit are installed):
|
||||
- WebKit, like Safari on macOS, leaves buttons out of the Tab order; focus them directly.
|
||||
- Firefox counts a scripted focus as `:focus-visible` only after a key press.
|
||||
- Firefox flaked ~3 runs in 4 with **HTTP 431 from Pest's in-process Amp server** on
|
||||
`livewire.js`, so Alpine never started. It appeared once the showcase inlined a 60 KB list of
|
||||
symbol names; the icon search now fetches `symbols.json` on `x-intersect.once`, and the suite
|
||||
passed 4 of 4. Keep showcase pages lean.
|
||||
- A click that lands before Alpine starts does nothing; tests wait for `networkidle`, and the
|
||||
showcase's theme switch is `x-cloak` so Playwright's click waits for it.
|
||||
|
||||
### Phase 4 — Communication
|
||||
|
||||
23. `badge` (dot, count, label; variant/colour), `progress` (linear, circular, **wavy**,
|
||||
determinate and indeterminate), `toast` (M3 snackbar, action, timeout, stacked), rich
|
||||
`tooltip`, `alert` (tinted container, icon, actions slot), `stat` (figure with
|
||||
`x-figure`), `empty-state`.
|
||||
|
||||
### Phase 5 — Containment (`0.5.0`)
|
||||
### Phase 5 — Containment
|
||||
|
||||
24. `card` (elevated, filled, outlined; `title`, `subtitle`, `actions` slot; clickable row
|
||||
contract), `divider`, `list` / `list-item` (one-, two-, three-line; leading/trailing;
|
||||
@@ -350,7 +401,7 @@ tokens, `training-row`, `map-shell`, `map-chip`, `product-shot`, `star-rating`,
|
||||
standard; `pane` for list-detail from `xl`; `width` prop), `carousel` (multi-browse, uncontained, hero,
|
||||
full-screen on CSS scroll-snap), `collapse`.
|
||||
|
||||
### Phase 6 — Text inputs and selection (`0.6.0`)
|
||||
### Phase 6 — Text inputs and selection
|
||||
|
||||
25. `form`, `field` (the shared shell: **outlined and filled**, floating label via `:has()`,
|
||||
notch, `hint` replaced by error, `aria-invalid` / `aria-describedby`, `data-*` state
|
||||
@@ -363,13 +414,13 @@ tokens, `training-row`, `map-shell`, `map-chip`, `product-shot`, `star-rating`,
|
||||
inputs, value label), `search` (search bar and search view, results through a Livewire
|
||||
property).
|
||||
|
||||
### Phase 7 — Pickers (`0.7.0`)
|
||||
### Phase 7 — Pickers
|
||||
|
||||
27. `datepicker` (docked, modal, modal input; `Intl` month/day names and week start from the
|
||||
app locale; `min`/`max`; single and range; `wire:model` stores `Y-m-d`), `timepicker`
|
||||
(dial and input; 12/24h from locale; stores `H:i`). APG grid keyboard for the calendar.
|
||||
|
||||
### Phase 8 — Navigation (`0.8.0`)
|
||||
### Phase 8 — Navigation
|
||||
|
||||
28. `app-bar` (small, center-aligned, medium flexible, large flexible, search app bar; sticky,
|
||||
scroll-elevation), `navigation-bar` (flexible), `navigation-rail` (collapsed, expanded,
|
||||
@@ -382,7 +433,7 @@ tokens, `training-row`, `map-shell`, `map-chip`, `product-shot`, `star-rating`,
|
||||
the theme script), content region with `wire:transition.navigate`, snackbar host. Nothing
|
||||
app-specific inside; apps pass destinations and extra chrome as slots.
|
||||
|
||||
### Phase 9 — Data, pages, mail (`0.9.0`)
|
||||
### Phase 9 — Data, pages, mail
|
||||
|
||||
30. `table` (`.data-table`, descendant selectors, fine-pointer density, `position: relative`),
|
||||
`sort-header` (`sortBy` array shape, `aria-sort`), Livewire and Laravel pagination views
|
||||
@@ -433,7 +484,7 @@ Tracked in SealShare's `docs/plans/livewire-material.md`, after `1.0.0`.
|
||||
## Risks and open questions
|
||||
|
||||
- **Scope and time.** The whole catalogue (~45 components plus extras) comes before any app
|
||||
uses it. Mitigation: waves tagged `0.x`, each reviewed in the showcase.
|
||||
uses it. Mitigation: each wave is reviewed in the showcase and green on CI before the next.
|
||||
- **Accessibility is entirely ours** — menus, pickers, carousel, sheets. Mitigation: APG
|
||||
patterns, native elements first, ARIA in render tests, keyboard in browser tests across
|
||||
three engines.
|
||||
|
||||
Reference in New Issue
Block a user