Plan step 19, containment.md C-18. The row was the focusable `region` and the items were not focusable at all, which is the thing M3's accessibility page draws a Don't for: "use Tab to place initial focus on the first carousel item", "avoid focusing on the carousel container". Each item is now `tabindex="0"` with the focus ring drawn inside it, the row is out of the tab order, and from a focused item the arrows move one item (moving focus with them), Home and End go to the ends, and Space or Enter opens one that is not fully in view. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
57 lines
2.8 KiB
PHP
57 lines
2.8 KiB
PHP
{{-- One item of an `<x-carousel>`: a slide of art — an `<img>`, which fills and crops to the
|
|
item, or any element sized `size-full`.
|
|
|
|
<x-carousel-item label="Lake Constance">
|
|
<img src="{{ $photo->url }}" alt="Lake Constance at dusk" />
|
|
</x-carousel-item>
|
|
|
|
`label` lays a short line of text over the bottom of the art, on a scrim, pinned to the
|
|
item's visible edge and fading out as the item narrows — Compose's carousel sample, which
|
|
fades its label chip in once the mask is wide enough to hold it. The scrim is what M3 asks
|
|
for under text on an image; the text itself is `inverse-on-surface`, a role a scheme and a
|
|
contrast profile follow, rather than a literal white.
|
|
|
|
A focusable `group` with `aria-roledescription="slide"`, named "n of m" by the carousel
|
|
around it. M3 puts the tab stop on the item, not on the row: Tab reaches the first item, the
|
|
arrow keys move between them and Space or Enter opens the focused one
|
|
(docs/reference/m3/components-actions-communication-containment.md § Carousel →
|
|
Accessibility). The item is laid out at the carousel's large size and masked:
|
|
the surface inside is clipped by `--material-carousel-inset` from both sides with M3's
|
|
extra-large corner (28px, CarouselDefaults' item shape) and moved by
|
|
`--material-carousel-shift`, both written by resources/js/carousel.js. Without script the
|
|
item shows unmasked, at its `item-width`. Only inside `<x-carousel>`. --}}
|
|
|
|
@props([
|
|
'label' => null,
|
|
])
|
|
|
|
<div {{ $attributes
|
|
->class([
|
|
'focus-ring relative h-full w-(--material-carousel-slot) max-w-full shrink-0 snap-start snap-always rounded-corner-xl focus-visible:-outline-offset-3',
|
|
])
|
|
->merge([
|
|
'role' => 'group',
|
|
'aria-roledescription' => __('slide'),
|
|
'aria-label' => '[material-carousel-position]',
|
|
'data-material-carousel-item' => true,
|
|
'tabindex' => '0',
|
|
]) }}>
|
|
<div
|
|
data-material-carousel-surface
|
|
class="relative size-full overflow-hidden rounded-corner-xl bg-surface-container-highest text-on-surface translate-x-(--material-carousel-shift) [clip-path:inset(0_var(--material-carousel-inset,0px)_round_var(--md-sys-shape-corner-xl))]"
|
|
>
|
|
<div data-material-carousel-content class="size-full [&>img]:size-full [&>img]:object-cover">
|
|
{{ $slot }}
|
|
</div>
|
|
|
|
@if (filled($label))
|
|
<div
|
|
data-material-carousel-label
|
|
class="pointer-events-none absolute inset-x-0 bottom-0 flex bg-linear-to-t from-scrim/60 to-transparent px-4 pt-10 pb-4 opacity-(--material-carousel-label)"
|
|
>
|
|
<span class="truncate type-title-md text-inverse-on-surface translate-x-(--material-carousel-label-shift)">{{ $label }}</span>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|