Give a card per-state elevation and M3's two keyboard models
Plan step 19, containment.md C-13, C-23 and the directly actionable card of C-13/C-18. A card had one elevation and never changed it, and its hover replaced the elevated card's shadow with the state layer; it now names its variant in `data-card` and moves a step on hover (elevated 1 to 2, filled and outlined 0 to 1) with the layer over it. The 12 to 16 corner morph on hover is gone: M3 gives a card one shape and lists shape morph for buttons, FABs and list items only. New `actionable` prop for M3's directly actionable card — the card is the one tab stop, with `role="button"` (or `link`), its `title` as the name, and Enter or Space reaching the opener from resources/js/list-rows.js. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
26a9230a48
commit
675cfc806a
@@ -7,11 +7,24 @@
|
||||
`separator` (a divider under the header), and the `menu` (top-end, beside the title),
|
||||
`figure` (full-bleed media on top) and `actions` (end-aligned, under the content) slots.
|
||||
|
||||
A card that opens something is a row: give it `data-list-row` and one `data-list-open`
|
||||
control inside (the title link or a button), and a press anywhere on it reaches that control
|
||||
while its other buttons keep their own (resources/js/list-rows.js). It answers with a state
|
||||
layer and its corner opening a step. Never a stretched link, and never a whole-card `<a>`
|
||||
around buttons.
|
||||
M3 draws two kinds of card a person can press, and they differ in the keyboard: on a
|
||||
**non-actionable card with actionable elements** Tab moves through each control inside before
|
||||
the next card; on a **directly actionable card** Tab moves to the card, then to the next card
|
||||
(docs/reference/m3/components-actions-communication-containment.md § Cards → Accessibility).
|
||||
|
||||
Both are a row: give the card `data-list-row` and one `data-list-open` control inside (the
|
||||
title link or a button), and a press anywhere on it reaches that control while its other
|
||||
buttons keep their own (resources/js/list-rows.js). That is the first kind — the opener is
|
||||
the tab stop, the accessible name and the owner of the `href` or the `wire:click`.
|
||||
|
||||
`actionable` makes it the second: the card takes `tabindex="0"` and `role="button"` (pass
|
||||
`role="link"` where it goes somewhere), is named by its `title`, and Enter or Space on it
|
||||
reaches the opener. Put `tabindex="-1"` on that opener so the card is the one stop, and leave
|
||||
the card's other actions as they are — they follow it in the tab order.
|
||||
|
||||
A row answers with M3's state layer and one step of elevation (elevated 1 → 2 hovered,
|
||||
filled and outlined 0 → 1); its corner does not move, because M3 gives a card one shape.
|
||||
Never a stretched link, and never a whole-card `<a>` around buttons.
|
||||
|
||||
Do not pass a `bg-*` class to change its fill — it races the card's own in Tailwind's emit
|
||||
order; use `variant`, or colour a wrapper inside. --}}
|
||||
@@ -21,14 +34,24 @@
|
||||
'subtitle' => null,
|
||||
'variant' => 'filled',
|
||||
'separator' => false,
|
||||
'actionable' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
$variant = in_array($variant, ['filled', 'elevated', 'outlined'], true) ? $variant : 'filled';
|
||||
$role = $actionable ? ($attributes->get('role') ?: 'button') : null;
|
||||
$attributes = $actionable ? $attributes->except('role') : $attributes;
|
||||
@endphp
|
||||
|
||||
<div
|
||||
data-card
|
||||
data-card="{{ $variant }}"
|
||||
@if ($actionable)
|
||||
data-list-row
|
||||
data-list-actionable
|
||||
tabindex="0"
|
||||
role="{{ $role }}"
|
||||
@if (filled($title)) aria-label="{{ $title }}" @endif
|
||||
@endif
|
||||
{{ $attributes->class([
|
||||
'relative flex flex-col overflow-hidden rounded-corner-md text-on-surface',
|
||||
'bg-surface-container-highest' => $variant === 'filled',
|
||||
|
||||
@@ -26,6 +26,15 @@
|
||||
</x-slot:actions>
|
||||
</x-card>
|
||||
BLADE,
|
||||
'A directly actionable card: the card is the tab stop' => <<<'BLADE'
|
||||
<x-card variant="elevated" actionable role="link" title="contract.pdf" class="w-full max-w-sm">
|
||||
<a href="#containment" data-list-open tabindex="-1" class="type-title-md">contract.pdf</a>
|
||||
<p class="mt-1 text-on-surface-variant">1.2 MB · downloaded twice</p>
|
||||
<x-slot:actions>
|
||||
<x-button label="Copy link" icon="content_copy" x-on:click="materialToast('Link copied', { type: 'success' })" />
|
||||
</x-slot:actions>
|
||||
</x-card>
|
||||
BLADE,
|
||||
'Lists' => <<<'BLADE'
|
||||
<div class="grid w-full gap-6 medium:grid-cols-2">
|
||||
<x-list dividers label="Files">
|
||||
|
||||
Reference in New Issue
Block a user