diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index 00c3bc2b..bb3087a3 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -274,6 +274,8 @@ M3's plain tooltip, standalone around any trigger: `` draws M3's line, `` M3 Expressive's grouped layout — no line, the cluster set 8px off its neighbours with its items 2px apart and its ends rounded. Reach for the divider first (M3: "on web, use dividers to separate items", and it is the only one a scrolling menu may use); reach for the gap for one or two clusters in a menu short enough not to scroll, and never vary the gap. `` takes `label` (optional) and `gap`; a labelled group without `gap` is the plain heading it always was. + ### `` A row of ``s: ``. `connected` sets them 2px apart with small inner corners (a selected toggle rounds fully). Pass the `size` of the buttons inside. diff --git a/resources/views/components/menu-group.blade.php b/resources/views/components/menu-group.blade.php index 61c73c91..a7686837 100644 --- a/resources/views/components/menu-group.blade.php +++ b/resources/views/components/menu-group.blade.php @@ -1,9 +1,48 @@ -{{-- A labelled group of items in an ``: "Sort by", "Share with". --}} +{{-- A group of items in an ``, labelled ("Sort by", "Share with"), set apart by a gap, or + both. -@props(['label']) + M3 Expressive gives a menu two ways to break its items into clusters, and they are not + interchangeable: -
class('py-1 first:pt-0 last:pb-0') }}> - + - ``, a line. M3: "dividers are more subtle and are the right choice for + scrollable menus or text-field dropdowns", and "on web, use dividers to separate items". + The default answer, and the only one for a menu long enough to scroll. + - ``, the Expressive "Grouped" layout: no line, a gap. M3: "gaps are the + more expressive way to separate item clusters" — but "limit to one or two gaps per menu", + "don't vary gap size", and "never use gaps in a scrollable menu" (unsupported). - {{ $slot }} + A gapped cluster sets its items 2px apart (SegmentedMenuTokens.SegmentedGap = 2dp) and holds + them in a box of their own, so the two ends of the cluster round the way the ends of a whole + list round and each cluster reads as one block. Clusters stand 8px apart, the same 8px a + separator keeps above and below its line, so a menu is the same height whichever it uses. + SegmentedMenuTokens' own GroupShape is 8dp where the library's list ends are 12dp; the + library's shape wins, so a cluster's ends and a list's ends match. --}} + +@props([ + 'label' => null, + 'gap' => false, +]) + +@php + $attributes = $attributes + ->class([ + 'py-1 first:pt-0 last:pb-0' => ! $gap, + 'not-first:mt-2' => $gap, + ]) + ->merge(array_filter([ + 'role' => 'group', + 'aria-label' => $label, + ], fn ($value): bool => filled($value))); +@endphp + +
+ @if (filled($label)) + + @endif + + @if ($gap) +
{{ $slot }}
+ @else + {{ $slot }} + @endif
diff --git a/resources/views/showcase/sections/menus.blade.php b/resources/views/showcase/sections/menus.blade.php index 446a9516..c8330721 100644 --- a/resources/views/showcase/sections/menus.blade.php +++ b/resources/views/showcase/sections/menus.blade.php @@ -60,6 +60,37 @@ BLADE, + 'Clusters: a gap, or a divider' => <<<'BLADE' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BLADE, 'Icons in their own colour' => <<<'BLADE' diff --git a/tests/Feature/Components/MenuTest.php b/tests/Feature/Components/MenuTest.php index 19c9c936..2f7b0da8 100644 --- a/tests/Feature/Components/MenuTest.php +++ b/tests/Feature/Components/MenuTest.php @@ -103,6 +103,25 @@ it('separates and labels groups', function () { ->assertSee('role="group" aria-label="Sort by"', false); }); +it('separates clusters by a gap instead of a divider when asked', function () { + $html = (string) $this->blade(''); + + // M3 Expressive's grouped layout: 2px between the items of a cluster, 8px between clusters — + // the same 8px the divider keeps above and below its line. + expect($html) + ->toContain('role="group"') + ->toContain('not-first:mt-2') + ->toContain('space-y-0.5') + ->not->toContain('aria-label') + ->not->toContain('py-1 first:pt-0') + ->and((string) $this->blade('')) + ->toContain('aria-label="Then"') + ->toContain('space-y-0.5') + ->and((string) $this->blade('')) + ->toContain('py-1 first:pt-0 last:pb-0') + ->not->toContain('space-y-0.5'); +}); + it('adds icon-class to the leading icon, over its own colour but not over disabled', function () { $leading = fn (string $html): string => preg_match('/]*class="([^"]*)"/', $html, $icon) ? $icon[1] : '';