Files
livewire-material/resources/css/components/list-item.css
T
Andreas Reinhold / reiniandClaude Opus 5 b7e16476cd Fill a selected row written by hand in secondary-container again
1.x drew `data-selected` on any `data-list-row` that was not a card in
secondary-container with on-secondary-container ink, whatever element
the row was. 2.0.0 kept that only for `<x-list-item selected>` and a
table row's `aria-selected="true"`, so an application's own `<li>`,
`<div>` or `<tr>` with `data-md-list-row` and `data-md-selected` - the
contract list-rows.js still describes - lost its selected state without
a word. list-item.css now fills such a row, cards and list items
excluded, and names the fill `--md-list-row-fill`, so hover, focus and
press tint it as they tint a selected list item rather than replacing
it (1.x's selected fill hid them). In `<x-table>`, `data-md-selected`
selects a plain row too, beside `aria-selected="true"`, which ARIA
allows on a row only in a grid. A browser test compares a hand-made
selected `<li>` and a plain selected `<tr>` with the role pair in the
light and dark themes, checks that a selected card keeps its own fill
and that hover tints the row's fill; it fails without the change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:18:10 +02:00

271 lines
11 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* <x-list-item>: a headline with what leads it, what supports it and what trails it, one- two- or
* three-line by `data-md-lines` (56/72/88px, `ListTokens.kt:180,323,347`, androidx Compose
* Material 3, Apache-2.0) — the tallest element sets the height, and a three-line item top-aligns
* rather than centring (docs/reference/m3/components-actions-communication-containment.md
* § Lists → Specs). Leading/trailing gap and container padding are both `space200` (16px,
* `ItemLeadingSpace`/`ItemTrailingSpace`/container padding, C-14).
*
* `data-md-list-row` (`resources/js/list-rows.js`, shared with `<x-card>` and an application's own
* table rows) and `[data-md-list-open]:focus-visible` answer with the state layer — the row's ink at
* the hover 8% and focus/press 10% state tokens — and a 3px secondary inset ring; excluded on
* `[data-md-card]`, which draws its own tint through the shared `md-state-layer` class instead
* (card.css). The layer is mixed over the row's own fill, `--md-list-row-fill` (a segmented tile's
* surface, a selected item's secondary-container, table.css's selected row), so a hovered, pressed
* or focused row keeps its fill under the tint instead of trading it for a translucent one (C-08's
* second fix). `data-md-selected` is M3's selected item, secondary-container filled, which also
* lifts the description/overline/trailing/icon colour rules below since they exclude it — the
* container's own colour takes over by inheritance. The same attribute on any other row that is
* not a card — an application's own `<li>`, `<div>` or `<tr>` — draws the same fill and ink, as
* 1.x's `data-selected` did; a card keeps its own container. A selected option draws a second cue, a
* trailing check, so selection is never colour alone (C-03). `aria-disabled` inks the whole item
* on-surface at the disabled-content opacity (38%) and blocks the pointer; the view drops the
* item's link entirely rather than leaving a focusable, activatable control behind it (C-02).
*
* `data-md-list="segmented"` items (M3 Expressive) are `surface-container` tiles (one tone up from
* `ItemSegmentedContainerColor`, so they read against the package's own `surface` page;
* C-21), 4px corners that open to 16px at the list's own ends and while hovered (12px), pressed,
* focused or selected (16px) — the specs page's interaction-state expressive shapes (C-22). Leading
* icons there are drawn at 20px, not scaled down from 24 — list-item.blade.php passes `size` to
* `<x-icon>` from the parent's `@aware(['segmented'])`, so no CSS override is needed here (C-26).
*
* `data-md-dividers` on the list insets its rule 16px from both ends (`DividerLeadingSpace`/
* `DividerTrailingSpace`, C-16) rather than full-bleed.
*
* Leading media: `data-md-list-item-avatar` is 40px, full corner, initials in primary-container
* when it is a `<span>`, an image otherwise; `data-md-list-item-image` is a 56px small-corner
* thumbnail; `data-md-list-item-video` is M3's leading video in its two sizes,
* `data-md-size="sm"` 100×56px (a two-line item) or `"lg"` 114×64px (a three-line item),
* `LeadingVideoSmall`/`LeadingVideoLarge`.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@import './icon.css';
@layer material.components {
[data-md-list-item] {
position: relative;
display: flex;
align-items: center;
gap: var(--md-sys-measurement-space200);
padding-inline: var(--md-sys-measurement-space200);
color: var(--md-sys-color-on-surface);
}
[data-md-list-item][data-md-lines='1'] {
min-height: 56px;
padding-block: var(--md-sys-measurement-space100);
}
[data-md-list-item][data-md-lines='2'] {
min-height: 72px;
padding-block: var(--md-sys-measurement-space100);
}
[data-md-list-item][data-md-lines='3'] {
min-height: 88px;
padding-block: 12px;
align-items: flex-start;
}
/* A row's fill, under its state layer. Declared on every row at no weight, so a row nested in a
filled one starts from nothing rather than inheriting its parent's fill. */
:where([data-md-list-row]) {
--md-list-row-fill: transparent;
}
/* M3 Expressive's segmented list gives each item its own container: ListTokens.kt:201,
`ItemSegmentedContainerColor get() = ColorSchemeKeyTokens.Surface`. One tone up from that,
because the token reads against a Compose scaffold and this package paints the page
`surface` itself (foundation/base.css): a surface tile on a surface page is a list nobody
can see. `surface-container` is the step M3 names for a container that has to read against
the page, and what 1.x drew. Before the selected rule, which outranks it on the same
weight. */
[data-md-list='segmented'] > [data-md-list-item] {
--md-list-row-fill: var(--md-sys-color-surface-container);
background-color: var(--md-list-row-fill);
}
[data-md-list-item][data-md-selected] {
--md-list-row-fill: var(--md-sys-color-secondary-container);
background-color: var(--md-list-row-fill);
color: var(--md-sys-color-on-secondary-container);
}
/* A row written by hand — list-rows.js's contract, not an `<x-list-item>` — selected the same
way. The state layers below are mixed over this fill, as they are over an item's. */
[data-md-list-row][data-md-selected]:not([data-md-card], [data-md-list-item]) {
--md-list-row-fill: var(--md-sys-color-secondary-container);
background-color: var(--md-list-row-fill);
color: var(--md-sys-color-on-secondary-container);
}
[data-md-list-item][aria-disabled='true'] {
pointer-events: none;
color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
}
[data-md-list-item]:not([data-md-selected], [aria-disabled='true']) :is([data-md-list-item-overline], [data-md-list-item-description], [data-md-list-item-trailing], [data-md-list-item-icon]) {
color: var(--md-sys-color-on-surface-variant);
}
[data-md-list-item-leading],
[data-md-list-item-end] {
display: flex;
flex-shrink: 0;
align-items: center;
}
[data-md-list-item-end] {
gap: var(--md-sys-measurement-space50);
}
[data-md-list-item-video] {
flex-shrink: 0;
overflow: hidden;
border-radius: var(--md-sys-shape-corner-sm);
background-color: var(--md-sys-color-surface-container-highest);
& > * {
width: 100%;
height: 100%;
object-fit: cover;
}
}
[data-md-list-item-video][data-md-size='sm'] {
width: 100px;
height: 56px;
}
[data-md-list-item-video][data-md-size='lg'] {
width: 114px;
height: 64px;
}
[data-md-list-item-avatar] {
flex-shrink: 0;
width: 40px;
height: 40px;
border-radius: var(--md-sys-shape-corner-full);
object-fit: cover;
}
span[data-md-list-item-avatar] {
display: grid;
place-items: center;
background-color: var(--md-sys-color-primary-container);
color: var(--md-sys-color-on-primary-container);
font: var(--md-sys-typescale-title-md);
letter-spacing: var(--md-sys-typescale-title-md-tracking);
}
[data-md-list-item-image] {
flex-shrink: 0;
width: 56px;
height: 56px;
border-radius: var(--md-sys-shape-corner-sm);
object-fit: cover;
}
[data-md-list-item-content] {
min-width: 0;
flex: 1 1 0%;
}
[data-md-list-item-overline] {
font: var(--md-sys-typescale-label-sm);
letter-spacing: var(--md-sys-typescale-label-sm-tracking);
}
[data-md-list-item-title] {
display: block;
overflow: hidden;
outline: none;
font: var(--md-sys-typescale-body-lg);
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
text-overflow: ellipsis;
white-space: nowrap;
}
[data-md-list-item-description] {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
font: var(--md-sys-typescale-body-md);
letter-spacing: var(--md-sys-typescale-body-md-tracking);
}
[data-md-list-item-trailing] {
flex-shrink: 0;
font: var(--md-sys-typescale-label-sm);
letter-spacing: var(--md-sys-typescale-label-sm-tracking);
}
[data-md-list][data-md-dividers] > [data-md-list-item]:not(:last-child)::after {
content: '';
position: absolute;
inset-inline: var(--md-sys-measurement-space200);
bottom: 0;
height: 1px;
background-color: var(--md-sys-color-outline-variant);
pointer-events: none;
}
[data-md-list-row] {
cursor: pointer;
transition-property: background-color, border-radius;
transition-duration: var(--md-sys-motion-effects-fast-duration), var(--md-sys-motion-spatial-fast-duration);
transition-timing-function: var(--md-sys-motion-effects-fast), var(--md-sys-motion-spatial-fast);
}
@media (hover: hover) {
[data-md-list-row]:not([data-md-card]):hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):hover)) {
background-color: color-mix(in srgb, currentColor calc(var(--md-sys-state-hover-state-layer-opacity) * 100%), var(--md-list-row-fill, transparent));
}
}
[data-md-list-row]:not([data-md-card]):has([data-md-list-open]:focus-visible) {
background-color: color-mix(in srgb, currentColor calc(var(--md-sys-state-focus-state-layer-opacity) * 100%), var(--md-list-row-fill, transparent));
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: -3px;
}
[data-md-list-row]:not([data-md-card]):active:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):active)) {
background-color: color-mix(in srgb, currentColor calc(var(--md-sys-state-pressed-state-layer-opacity) * 100%), var(--md-list-row-fill, transparent));
}
[data-md-list-row] [data-md-list-open]:focus-visible {
outline: none;
}
[data-md-list='segmented'] > [data-md-list-item] {
border-radius: var(--md-sys-shape-corner-xs);
}
[data-md-list='segmented'] > [data-md-list-item]:first-child {
border-start-start-radius: var(--md-sys-shape-corner-lg);
border-start-end-radius: var(--md-sys-shape-corner-lg);
}
[data-md-list='segmented'] > [data-md-list-item]:last-child {
border-end-start-radius: var(--md-sys-shape-corner-lg);
border-end-end-radius: var(--md-sys-shape-corner-lg);
}
@media (hover: hover) {
[data-md-list='segmented'] > [data-md-list-item][data-md-list-row]:hover {
border-radius: var(--md-sys-shape-corner-md);
}
}
[data-md-list='segmented'] > [data-md-list-item]:is([data-md-selected], [data-md-list-row]:active, [data-md-list-row]:has([data-md-list-open]:focus-visible)) {
border-radius: var(--md-sys-shape-corner-lg);
}
}