diff --git a/resources/css/components/list.css b/resources/css/components/list.css index e4c7d7ae..27431622 100644 --- a/resources/css/components/list.css +++ b/resources/css/components/list.css @@ -9,9 +9,36 @@ * * Unlayered where a component paints its fill as a utility (``): anything in a @layer loses * to a utility whatever its specificity. + * + * Every selector that paints a row is `:where()`, so they all weigh nothing and the later rule + * wins: the segmented list's own fill is declared first and a row's state layer, further down, + * paints over it. Written any other way — outside the layer, or with its real specificity — the + * fill would beat the state layer and a segmented row would answer nothing. */ @layer components { + /* + * M3 Expressive's segmented list gives each item its own container. ListTokens.kt:201 — + * `ItemSegmentedContainerColor get() = ColorSchemeKeyTokens.Surface`. + */ + :where([data-list='segmented'] > [data-list-item]) { + background-color: var(--md-sys-color-surface); + } + + /* + * ListTokens.kt:30,36 — `DividerLeadingSpace` and `DividerTrailingSpace` are 16dp, so a + * list's dividers are inset from both ends rather than full-bleed (``). + */ + :where([data-list][data-dividers] > [data-list-item]:not(:last-child))::after { + content: ''; + position: absolute; + inset-inline: 16px; + bottom: 0; + height: 1px; + background-color: var(--md-sys-color-outline-variant); + pointer-events: none; + } + :where([data-list-row]) { cursor: pointer; transition: background-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast), @@ -39,10 +66,6 @@ outline: none; } -[data-list='segmented'] > [data-list-item] { - background-color: var(--md-sys-color-surface-container); -} - :is([data-list-row], [data-list-item])[data-selected]:not([data-card]) { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); @@ -73,8 +96,10 @@ /* * M3 Expressive's segmented list (``): each item its own surface, 2px apart, - * extra-small corners that open to large at the ends, and to large while hovered, pressed or - * selected (ListTokens, androidx Compose Material 3, Apache-2.0). + * extra-small corners that open to medium while hovered and to large at the ends and while + * focused, pressed or selected — the specs page's interaction-state expressive shapes, and + * ListTokens' `ItemPressedContainerExpressiveShape` / `ItemDraggedContainerExpressiveShape` + * (androidx Compose Material 3, Apache-2.0). */ [data-list='segmented'] > [data-list-item] { border-radius: var(--md-sys-shape-corner-xs); @@ -96,6 +121,6 @@ } } -[data-list='segmented'] > [data-list-item]:is([data-selected], [data-list-row]:active) { +[data-list='segmented'] > [data-list-item]:is([data-selected], [data-list-row]:active, [data-list-row]:has([data-list-open]:focus-visible)) { border-radius: var(--md-sys-shape-corner-lg); } diff --git a/resources/views/components/list.blade.php b/resources/views/components/list.blade.php index 582eaeaa..e5ba9aaa 100644 --- a/resources/views/components/list.blade.php +++ b/resources/views/components/list.blade.php @@ -1,9 +1,11 @@ {{-- An M3 list: ``s, one under another. Plain by default — the items sit on the surface around them, with `dividers` between them if - asked. `segmented` is M3 Expressive's list: each item its own surface-container tile, 2px - apart, with small corners that open to large at the ends and while hovered, pressed or - selected (ListTokens, androidx Compose Material 3, Apache-2.0). + asked, inset 16px from both ends as ListTokens' `DividerLeadingSpace`/`DividerTrailingSpace` + say (resources/css/components/list.css draws them). `segmented` is M3 Expressive's list: each + item its own surface tile, 2px apart, with small corners that open to large at the ends and + while hovered, pressed, focused or selected (ListTokens, androidx Compose Material 3, + Apache-2.0). A plain list is a `role="list"` of `listitem`s. `selectable` (or `selection="single"` / `selection="multi"`) makes it the list a person chooses from: M3 maps single- and @@ -38,11 +40,11 @@ role="{{ $selection === null ? 'list' : 'listbox' }}" @if ($selection === 'multi') aria-multiselectable="true" @endif data-list="{{ $segmented ? 'segmented' : 'plain' }}" + @if ($dividers && ! $segmented) data-dividers @endif @if ($label) aria-label="{{ $label }}" @endif {{ $attributes->class([ 'flex flex-col', 'gap-0.5' => $segmented, - 'divide-y divide-outline-variant' => $dividers && ! $segmented, ]) }} > {{ $slot }} diff --git a/tests/Feature/Components/ListTest.php b/tests/Feature/Components/ListTest.php index 09b87260..5e1c650c 100644 --- a/tests/Feature/Components/ListTest.php +++ b/tests/Feature/Components/ListTest.php @@ -5,8 +5,11 @@ it('draws a plain list, with dividers on request', function () { ->toContain('role="list"') ->toContain('aria-label="Files"') ->toContain('data-list="plain"') - ->toContain('divide-y divide-outline-variant') - ->toContain('role="listitem"'); + ->toContain('data-dividers') + ->not->toContain('divide-y') + ->toContain('role="listitem"') + ->and((string) $this->blade(''))->not->toContain('data-dividers') + ->and((string) $this->blade(''))->not->toContain('data-dividers'); }); it('draws M3 Expressive\'s segmented list', function () {