Files
livewire-material/resources/views/components/bottom-sheet.blade.php
T
Andreas Reinhold / reiniandClaude Opus 5 f02fa1a78d Give the bottom sheet's drag handle a 48dp target
Plan step 12, containment.md C-01. The handle button was 32x4px: the
padding that makes M3's 48dp hit target sat on the wrapper, which is not
the control. `touch-target` on the button and 22px above and below it —
SheetDefaults.kt's DragHandleVerticalPadding — make the pressed area
48x48 while the handle is still drawn 32x4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:53:46 +02:00

80 lines
4.1 KiB
PHP
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.
{{-- An M3 bottom sheet: secondary content or actions anchored to the bottom of the screen.
Modal by default: over a scrim, the page inert and still, dismissed by the scrim, Escape, or a
downward drag on its handle (or anywhere on the sheet when its content is scrolled to the top).
`standard` makes it part of the page instead no scrim, nothing inert, and it stays until closed.
The open state works as for `<x-drawer>`: `wire:model` (a flag or an id, written back `false` or
`null` on close) or `open` in the surrounding Alpine scope; `close()` is in scope inside it.
SheetBottomTokens (androidx Compose Material 3, Apache-2.0): surface-container-low, extra-large
top corners, elevation 1, a 32×4px drag handle in on-surface-variant; 640px wide at most, centred
on a wide screen; it rises on emphasized decelerate. `title` and `actions` as on a dialog.
`height` caps it (default: 90% of the screen); content scrolls inside.
The handle is drawn 32×4px and pressed 48×48: `touch-target` on the button and 22px above and
below it, which is M3's "drag handle has an accessible 48dp hit target" and SheetDefaults.kt's
`DragHandleVerticalPadding = 22.dp` (docs/reference/m3/components-actions-communication-containment.md § Bottom sheets Specs). --}}
@props([
'title' => null,
'standard' => false,
'height' => '90dvh',
])
@php
$model = $attributes->wire('model')->value() ?: null;
$id = $attributes->get('id') ?? 'material-bottom-sheet-'.substr(md5($model.'|'.$title), 0, 10);
@endphp
<div
x-data="{
...materialBottomSheet({{ $standard ? 'true' : 'false' }}),
@if ($model !== null) open: @entangle($attributes->wire('model')).live, @endif
}"
x-on:keydown.window.escape="if (open && ! standard) close()"
>
@unless ($standard)
<div x-cloak x-show="open" x-transition.opacity.duration.200ms x-on:click="close()" class="fixed inset-0 z-40 bg-scrim/32" aria-hidden="true"></div>
@endunless
<section
x-cloak
x-show="open"
x-ref="sheet"
@unless ($standard) x-trap.inert.noscroll="open" @endunless
x-transition:enter="transition-[translate] duration-(--md-sys-motion-spatial-default-duration) ease-emphasized-decelerate"
x-transition:enter-start="translate-y-full"
x-transition:enter-end="translate-y-0"
x-transition:leave="transition-[translate] duration-(--md-sys-motion-effects-default-duration) ease-emphasized-accelerate"
x-transition:leave-start="translate-y-0"
x-transition:leave-end="translate-y-full"
x-bind:style="dragged ? { translate: `0 ${dragged}px`, transition: 'none' } : {}"
x-on:pointerdown="dragStart($event)"
id="{{ $id }}"
role="dialog"
@unless ($standard) aria-modal="true" @endunless
@if (filled($title)) aria-labelledby="{{ $id }}-title" @endif
style="--sheet-max-height: {{ $height }}"
{{ $attributes->whereDoesntStartWith('wire:model')->except(['id', 'class'])->class([
'fixed inset-x-0 bottom-0 z-50 mx-auto flex max-h-(--sheet-max-height) w-full max-w-160 touch-pan-y flex-col rounded-t-corner-xl bg-surface-container-low pb-[var(--material-safe-bottom,env(safe-area-inset-bottom))] text-on-surface shadow-elevation-1',
$attributes->get('class'),
]) }}
>
<div class="flex shrink-0 cursor-grab justify-center py-5.5 active:cursor-grabbing" data-drag-handle>
<button type="button" class="touch-target h-1 w-8 rounded-corner-full bg-on-surface-variant/40 outline-offset-4 focus-visible:outline-3 focus-visible:outline-secondary" aria-label="{{ __('Close') }}" x-on:click="close()"></button>
</div>
<div x-ref="body" class="min-h-0 flex-1 overflow-y-auto px-6 pb-6">
@if (filled($title))
<h2 id="{{ $id }}-title" class="mb-4 type-title-lg">{{ $title }}</h2>
@endif
{{ $slot }}
</div>
@isset($actions)
<div class="flex shrink-0 flex-wrap items-center justify-end gap-2 px-6 pb-6">{{ $actions }}</div>
@endisset
</section>
</div>