From a237d33426315871610b5698c3f57d0695c9bd38 Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Mon, 14 Sep 2026 06:09:23 +0200 Subject: [PATCH] Ease a collapse open, which its own comment already promised Plan step 19, containment.md C-24. `interpolate-size: allow-keywords` was set and nothing transitioned, so the content snapped open. A new resources/css/components/collapse.css transitions `block-size` on `::details-content` over the fast spatial spring, with `content-visibility` `allow-discrete` beside it so the section stays rendered while it closes. The `
` carries `data-collapse` for the rule to find. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9 --- resources/css/components/collapse.css | 30 +++++++++++++++++++ resources/css/material.css | 1 + resources/views/components/collapse.blade.php | 4 ++- tests/Feature/Components/CollapseTest.php | 6 ++-- 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 resources/css/components/collapse.css diff --git a/resources/css/components/collapse.css b/resources/css/components/collapse.css new file mode 100644 index 00000000..0ba2d2cf --- /dev/null +++ b/resources/css/components/collapse.css @@ -0,0 +1,30 @@ +/* + * ``'s height, eased open. + * + * The native `
` opens and closes by itself; what it cannot do on its own is animate, so + * the component sets `interpolate-size: allow-keywords` (which makes `0` → `auto` an animatable + * pair) and this transitions `block-size` on `::details-content`, the pseudo-element that holds + * everything after the ``. `content-visibility` goes with it, `allow-discrete`, so the + * content stays rendered while it closes rather than vanishing on the first frame. + * + * The fast spatial spring, as the chevron beside it: M3's spatial track is for anything that + * changes size or position. Under reduced motion the duration token is zero + * (resources/css/tokens/motion.css), so the section snaps open with nothing else to do. + * + * Not an M3 component — M3 has expandable list items and menu expansion, no standalone disclosure + * — so the geometry is the library's and only the motion tokens are M3's. + */ + +@layer components { + [data-collapse]::details-content { + block-size: 0; + overflow: hidden; + transition: + block-size var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast), + content-visibility var(--md-sys-motion-spatial-fast-duration) allow-discrete; + } + + [data-collapse][open]::details-content { + block-size: auto; + } +} diff --git a/resources/css/material.css b/resources/css/material.css index 4ffefa6c..43682ca3 100644 --- a/resources/css/material.css +++ b/resources/css/material.css @@ -22,6 +22,7 @@ @import './tokens/state.css'; @import './components/groups.css'; @import './components/list.css'; +@import './components/collapse.css'; @import './components/field.css'; @import './components/menu.css'; @import './components/selection.css'; diff --git a/resources/views/components/collapse.blade.php b/resources/views/components/collapse.blade.php index e1ed8913..ec7ef45e 100644 --- a/resources/views/components/collapse.blade.php +++ b/resources/views/components/collapse.blade.php @@ -5,7 +5,8 @@ and is announced as a disclosure. Drawn in M3's terms: a title-medium `title` (or a `heading` slot), an optional leading `icon`, a chevron that turns over on the spatial spring, and — where the browser supports animating `details` content (`interpolate-size`) — a height - that eases open. `open` starts it open. `variant`: `plain` (the default, on the surface around + that eases open on the same spring (`data-collapse`, resources/css/components/collapse.css). + `open` starts it open. `variant`: `plain` (the default, on the surface around it) or `filled` (a surface-container tile with a large corner). Its open state can be bound, both ways: @@ -38,6 +39,7 @@
toContain('wire:ignore.self') ->toContain(' open ') ->toContain('rounded-corner-lg bg-surface-container') + ->toContain('data-collapse') + ->toContain('[interpolate-size:allow-keywords]') ->toContain('How long do links last?') ->toContain('Until the expiry.') ->toContain('group-open/collapse:rotate-180'); @@ -20,12 +22,12 @@ it('has no Alpine on it without a binding', function () { $html = (string) $this->blade('Body'); expect($html) - ->toMatch('/toMatch('/not->toContain('x-data') ->not->toContain('x-modelable') ->not->toContain('x-on:toggle') ->and((string) $this->blade('Body')) - ->toMatch('/toMatch('/