Merge branch 'worktree-agent-ae17d09efb642a925'

This commit is contained in:
Andreas Reinhold / reini
2026-09-14 11:03:41 +02:00
8 changed files with 275 additions and 29 deletions
+42 -20
View File
@@ -28,7 +28,21 @@
the buttons at the bottom" (docs/reference/m3/components-actions-communication-containment.md
§ Dialogs → Behaviour): the header and the action row are their own rows of the flex column
and only the body between them scrolls, each with the 24dp padding the box used to carry.
`separator` draws M3's divider under the pinned header and above the pinned actions.
Between that body and the rows pinned around it go M3's optional dividers (the 1dp "Divider"
in both dialogs' anatomy, § Dialogs → Anatomy, Specs), each only while content is hidden on
its far side: the rule under the header once the body is scrolled away from its top, the rule
over the actions while more of the body is below, so a body that fits draws neither. On a
phone a full-screen dialog's top rule lies under its close-and-title bar, unless a subtitle
or an icon keeps the header there, and its action bar's rule follows the scroll like any
other. `separator` draws both rules whatever the scroll. The gaps M3 gives are split around
the rules — 8px above and 8px below the top one (16dp title to body), 8px above and 16px below
the bottom one (24dp body to actions) — so the scrolling body keeps room inside its edges for
a floating label or a focus ring, and a rule that appears moves nothing. Without a body there
is nothing to divide, and no rule. resources/css/components/dialog.css draws the rules;
`x-dialog-dividers` (resources/js/dialog.js), on the body, says which of them show, watching
the body and the one element wrapped around the slot, so content a Livewire render adds
counts.
`alert` is M3's "on web, basic dialogs should have the alert dialog role": it sets
`role="alertdialog"` and points `aria-describedby` at the body, for the dialog that
@@ -51,9 +65,12 @@
$model = $attributes->wire('model')->value() ?: null;
$id = $attributes->get('id') ?? 'material-dialog-'.substr(md5($model.'|'.$title), 0, 10);
$header = filled($title) || filled($subtitle) || $icon;
$body = $slot->isNotEmpty();
// On a phone a full-screen dialog's bar names it, and the header hides unless an icon or a subtitle keeps it.
$barAboveBody = $fullscreen && (! $header || (! $icon && blank($subtitle)));
$describedBy = implode(' ', array_filter([
filled($subtitle) ? $id.'-subtitle' : null,
$alert && $slot->isNotEmpty() ? $id.'-body' : null,
$alert && $body ? $id.'-body' : null,
]));
@endphp
@@ -89,7 +106,10 @@
$boxClass,
])>
@if ($fullscreen)
<div class="flex h-14 shrink-0 items-center gap-1 px-1 medium:hidden">
<div
@if ($body && $barAboveBody) data-dialog-head @if ($separator) data-separator @endif @endif
class="flex h-14 shrink-0 items-center gap-1 px-1 medium:hidden"
>
<x-livewire-material::button icon="close" :tooltip="__('Close')" x-on:click="close()" />
@if (filled($title))
@@ -100,7 +120,10 @@
@if ($header)
{{-- A full-screen dialog's bar names it on a phone, so only the subtitle stays there. --}}
<div @class(['shrink-0 px-6 pt-6', 'max-medium:hidden' => $fullscreen && ! $icon && blank($subtitle), 'text-center' => $icon])>
<div
@if ($body) data-dialog-head @if ($separator) data-separator @endif @endif
@class(['shrink-0 px-6 pt-6', 'pb-2' => $body, 'max-medium:hidden' => $fullscreen && ! $icon && blank($subtitle), 'text-center' => $icon])
>
@if ($icon)
<x-livewire-material::icon :name="$icon" class="mx-auto mb-4 size-6 text-secondary" />
@endif
@@ -112,33 +135,32 @@
@if (filled($subtitle))
<p id="{{ $id }}-subtitle" @class(['type-body-md text-on-surface-variant', 'mt-4' => filled($title) || $icon, 'max-medium:mt-0' => $fullscreen && ! $icon])>{{ $subtitle }}</p>
@endif
@if ($separator)
<x-livewire-material::divider class="mt-4" />
@endif
</div>
@endif
@if ($slot->isNotEmpty())
<div id="{{ $id }}-body" @class([
@if ($body)
<div id="{{ $id }}-body" x-dialog-dividers @class([
'min-h-0 flex-1 overflow-y-auto px-6 type-body-md text-on-surface-variant',
'pt-4' => $header,
'pt-2' => $header,
'max-medium:pt-4' => $header && $barAboveBody,
'pt-6' => ! $header,
'pb-2' => isset($actions),
'pb-6' => ! isset($actions),
])>
{{ $slot }}
<div data-dialog-content>{{ $slot }}</div>
</div>
@endif
@isset($actions)
@if ($separator)
<x-livewire-material::divider class="shrink-0" />
@endif
<div @class([
'flex shrink-0 flex-wrap items-center justify-end gap-2 px-6 pt-6 pb-6',
'max-medium:min-h-14 max-medium:border-t max-medium:border-outline-variant max-medium:pt-2 max-medium:pb-2' => $fullscreen,
])>
<div
@if ($body) data-dialog-actions @if ($separator) data-separator @endif @endif
@class([
'flex shrink-0 flex-wrap items-center justify-end gap-2 px-6 pb-6',
'pt-4' => $body,
'pt-6' => ! $body,
'max-medium:min-h-14 max-medium:pt-2 max-medium:pb-2' => $fullscreen,
])
>
{{ $actions }}
</div>
@endisset