Plan step 36 review of f04e31b3. The card rendered md-state-layer only
on a row, so a card the application marks data-md-dragged without being
a row, as the showcase's "A card being dragged" is, lost the 16% dragged
layer its old inset shadow drew and its header still promises. Every
card now renders the class, since the attribute arrives from script
after render, and card.css keeps the layer off a card that is neither a
row nor dragged. The divider under the header is matched as its child.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
185 lines
7.1 KiB
CSS
185 lines
7.1 KiB
CSS
/*
|
|
* <x-card>: M3's three card variants (ElevatedCardTokens.kt, FilledCardTokens.kt,
|
|
* OutlinedCardTokens.kt, androidx Compose Material 3, Apache-2.0; docs/reference/m3
|
|
* § Cards → Specs) — filled (surface-container-highest), elevated (surface-container-low at
|
|
* elevation 1) and outlined (surface, an outline-variant edge); one 12dp corner for all three,
|
|
* which never morphs (M3 specifies shape morph for buttons, FABs and list items, never cards —
|
|
* docs/audits/m3-alignment/containment.md, C-23).
|
|
*
|
|
* Every card renders the shared `md-state-layer` class, and a row (`data-md-list-row`,
|
|
* `resources/js/list-rows.js`) `md-focus-ring` too (foundation/interaction.css), for its tint and
|
|
* its ring; this file adds only what a card needs beyond them:
|
|
*
|
|
* - *no layer* on a card that is neither a row nor dragged: a card that does not open anything
|
|
* answers no pointer, but any card can be carried, and the application sets `data-md-dragged`
|
|
* from script long after the view rendered;
|
|
* - per-state *elevation*, which the shared class has no opinion on: elevated 1dp rest → 2dp
|
|
* hover → 1dp focus/pressed; filled and outlined 0dp rest → 1dp hover → 0dp focus/pressed
|
|
* (the cross-check table's Hover/Focus/Pressed rows). Pressed and focus fall back to the rest
|
|
* level, so the `:active` rule (declared after `:hover`, same specificity) resets it even
|
|
* while still hovering;
|
|
* - the *press/hover exclusion*: a card's own nested button (the "Copy link" in a card that is
|
|
* also a row) should light only itself, never the row's tint or elevation. `material.components`
|
|
* out-layers `material.base`, so these two rules win over the shared class's plain `:hover`/
|
|
* `:active` without needing extra specificity;
|
|
* - the *opener-focused ring*: a non-actionable row's focus never lands on the card itself (its
|
|
* opener carries the tab stop), so `md-focus-ring`'s own `:focus-visible` never fires for it;
|
|
* `:has([data-md-list-open]:focus-visible)` draws the same ring from here instead. An
|
|
* actionable card (`tabindex="0"`) is focused directly, which `md-focus-ring` already covers.
|
|
*
|
|
* `data-md-dragged` (an application sets it while its drag is in progress) is the top of the
|
|
* elevation scale — Level4 (8dp) elevated, Level3 (6dp) filled and outlined — under the shared
|
|
* class's own 16% dragged tint, which already reads `data-md-dragged` without any help from here.
|
|
*/
|
|
|
|
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
|
|
|
@import './divider.css';
|
|
|
|
@layer material.components {
|
|
[data-md-card] {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
border-radius: var(--md-sys-shape-corner-md);
|
|
color: var(--md-sys-color-on-surface);
|
|
}
|
|
|
|
[data-md-card='filled'] {
|
|
background-color: var(--md-sys-color-surface-container-highest);
|
|
}
|
|
|
|
[data-md-card='elevated'] {
|
|
background-color: var(--md-sys-color-surface-container-low);
|
|
box-shadow: var(--md-sys-elevation-1);
|
|
}
|
|
|
|
[data-md-card='outlined'] {
|
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
background-color: var(--md-sys-color-surface);
|
|
}
|
|
|
|
[data-md-card]:not([data-md-list-row], [data-md-dragged])::before {
|
|
display: none;
|
|
}
|
|
|
|
[data-md-card][data-md-list-row] {
|
|
cursor: pointer;
|
|
transition-property: box-shadow;
|
|
transition-duration: var(--md-sys-motion-spatial-default-duration);
|
|
transition-timing-function: var(--md-sys-motion-spatial-default);
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
[data-md-card][data-md-list-row]:not([data-md-dragged]):hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):hover)) {
|
|
box-shadow: var(--md-sys-elevation-1);
|
|
}
|
|
|
|
[data-md-card='elevated'][data-md-list-row]:not([data-md-dragged]):hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):hover)) {
|
|
box-shadow: var(--md-sys-elevation-2);
|
|
}
|
|
|
|
[data-md-card][data-md-list-row]:not([data-md-dragged]):hover:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):hover)::before {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
[data-md-card][data-md-list-row]:not([data-md-dragged]):active:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):active)) {
|
|
box-shadow: none;
|
|
}
|
|
|
|
[data-md-card='elevated'][data-md-list-row]:not([data-md-dragged]):active:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):active)) {
|
|
box-shadow: var(--md-sys-elevation-1);
|
|
}
|
|
|
|
[data-md-card][data-md-list-row]:not([data-md-dragged]):has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):active)::before {
|
|
opacity: 0;
|
|
}
|
|
|
|
[data-md-card][data-md-dragged] {
|
|
box-shadow: var(--md-sys-elevation-3);
|
|
}
|
|
|
|
[data-md-card='elevated'][data-md-dragged] {
|
|
box-shadow: var(--md-sys-elevation-4);
|
|
}
|
|
|
|
[data-md-card][data-md-list-row]:has([data-md-list-open]:focus-visible) {
|
|
outline: 3px solid var(--md-sys-color-secondary);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
[data-md-card][data-md-list-row] [data-md-list-open]:focus-visible {
|
|
outline: none;
|
|
}
|
|
|
|
[data-md-card-figure] {
|
|
flex-shrink: 0;
|
|
overflow: hidden;
|
|
|
|
& > img {
|
|
width: 100%;
|
|
object-fit: cover;
|
|
}
|
|
}
|
|
|
|
[data-md-card-body] {
|
|
display: flex;
|
|
flex: 1 1 0%;
|
|
flex-direction: column;
|
|
gap: var(--md-sys-measurement-space200);
|
|
padding: var(--md-sys-measurement-space200);
|
|
}
|
|
|
|
[data-md-card-header-row] {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: var(--md-sys-measurement-space100);
|
|
}
|
|
|
|
[data-md-card-heading] {
|
|
min-width: 0;
|
|
flex: 1 1 0%;
|
|
}
|
|
|
|
[data-md-card-title] {
|
|
font: var(--md-sys-typescale-title-md);
|
|
letter-spacing: var(--md-sys-typescale-title-md-tracking);
|
|
}
|
|
|
|
[data-md-card-subtitle] {
|
|
margin-top: var(--md-sys-measurement-space25);
|
|
color: var(--md-sys-color-on-surface-variant);
|
|
font: var(--md-sys-typescale-body-md);
|
|
letter-spacing: var(--md-sys-typescale-body-md-tracking);
|
|
}
|
|
|
|
[data-md-card-menu] {
|
|
display: flex;
|
|
flex-shrink: 0;
|
|
align-items: center;
|
|
gap: var(--md-sys-measurement-space50);
|
|
margin-block-start: calc(-1 * var(--md-sys-measurement-space100));
|
|
margin-inline-end: calc(-1 * var(--md-sys-measurement-space100));
|
|
}
|
|
|
|
[data-md-card-header] > [data-md-divider] {
|
|
margin-top: var(--md-sys-measurement-space200);
|
|
}
|
|
|
|
[data-md-card-content] {
|
|
flex: 1 1 0%;
|
|
font: var(--md-sys-typescale-body-md);
|
|
letter-spacing: var(--md-sys-typescale-body-md-tracking);
|
|
}
|
|
|
|
[data-md-card-actions] {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
gap: var(--md-sys-measurement-space100);
|
|
}
|
|
}
|