{{-- A Material 3 Expressive button — label button, icon button or toggle — in one component. `variant` is M3's style: `filled`, `tonal`, `outlined`, `elevated` or `text` (the default). `color` is the role it is drawn in: `primary` (the default), `secondary`, `tertiary`, `error`, `success`, `warning`, `info`; `tone` is the same prop under ReStride's name. The shorthands `primary`, `danger` and `caution` mean filled primary, filled error and filled warning. Unknown values fall back to the defaults rather than rendering nothing. `size` is Expressive's scale — `xs` 32px, `sm` 40px (the default), `md` 56px, `lg` 96px, `xl` 136px — each with its own padding, type style and icon size. `shape` is `round` (the default) or `square`; either one squares off towards a smaller corner while pressed, on the fast spatial spring, while the colours cross on the fast effects spring beside it (a colour must never overshoot). With an `icon` and no label it is an icon button: `width` is `narrow`, `default` or `wide`, `variant="text"` is M3's standard icon button, and the tooltip or label names it for screen readers. Its glyph is filled, as M3 asks of a default icon button. `selected` makes it a toggle: `true` or `false` sets `aria-pressed` (not on a `link`, which is no toggle — give it `aria-current` instead) and M3's selected colours, and a selected round button turns square (a selected square icon button turns round); a toggle reads unselected outlined and selected filled, which is the rest of the same rule. Text buttons are not toggles in M3; a selected one takes the tonal container. Values from androidx Compose Material 3's tokens (Button*Tokens, *IconButtonTokens, Apache-2.0); the text button's label is primary, as Compose draws it, not the token's on-surface-variant, which its own source marks as wrong. Behaviour kept from maryUI: `link` renders an anchor with `wire:navigate` (unless `external` or `no-wire-navigate`); `disabled` works on a link too, as `aria-disabled`; `spinner` shows the loading indicator while the button's own `wire:click` runs (or the action named by a string), drawn in the button's own ink — M3 asks an indicator embedded in another component to take that component's label colour, so it stays visible on a filled or tonal container; `responsive` hides the label below `expanded` (840px, where M3 lets buttons reposition); `tooltip`, `tooltip-left`, `tooltip-right` and `tooltip-bottom` attach a plain tooltip. `fab` is a page's create action: an extended FAB pinned in the thumb zone on a compact window (below `medium`, 600px), a filled button from there — one element either way. It lifts clear of a bottom bar and of a snackbar on screen (`--material-bottom-bar`, `--material-snackbar-height`), because M3 puts a snackbar above a FAB and never over one. The view renders the props as `data-md-*` attributes and resources/css/components/button.css draws them, in the package's components layer: a caller's class or unlayered CSS outranks every rule there, `hidden` included. --}} @props([ 'label' => null, 'icon' => null, 'iconRight' => null, 'link' => null, 'external' => false, 'noWireNavigate' => false, 'variant' => null, 'color' => null, 'tone' => null, 'primary' => false, 'danger' => false, 'caution' => false, 'size' => 'sm', 'shape' => 'round', 'width' => 'default', 'selected' => null, 'spinner' => null, 'responsive' => false, 'tooltip' => null, 'tooltipLeft' => null, 'tooltipRight' => null, 'tooltipBottom' => null, 'fab' => false, 'disabled' => false, 'type' => 'button', ]) @php $variant = match (true) { in_array($variant, ['filled', 'tonal', 'outlined', 'text', 'elevated'], true) => $variant, $primary || $danger || $caution || $fab => 'filled', default => 'text', }; $color = match (true) { $danger => 'error', $caution => 'warning', in_array($color ?? $tone, ['primary', 'secondary', 'tertiary', 'error', 'success', 'warning', 'info'], true) => $color ?? $tone, default => 'primary', }; $size = in_array($size, ['xs', 'sm', 'md', 'lg', 'xl'], true) ? $size : 'sm'; $shape = $shape === 'square' ? 'square' : 'round'; $width = in_array($width, ['narrow', 'default', 'wide'], true) ? $width : 'default'; $iconOnly = filled($icon) && blank($label) && $slot->isEmpty(); $isLink = filled($link); $inert = $disabled && $isLink; $tip = $tooltip ?? $tooltipLeft ?? $tooltipRight ?? $tooltipBottom; $tipSide = match (true) { $tooltipLeft !== null => 'left', $tooltipRight !== null => 'right', $tooltipBottom !== null => 'bottom', default => 'top', }; $anchor = $tip !== null ? '--material-button-'.\Illuminate\Support\Str::lower(\Illuminate\Support\Str::random(10)) : null; $spinnerTarget = match (true) { $spinner === true, $spinner === 1, $spinner === '1' => $attributes->whereStartsWith('wire:click')->first(), is_string($spinner) && $spinner !== '' => $spinner, default => null, }; // The glyph's size in px (button.css draws the loading indicator at the same size). M3 draws a // symbol from its 20px cut below 24px, which the icon component picks from the size. $iconSize = $iconOnly ? ['xs' => 20, 'sm' => 24, 'md' => 24, 'lg' => 32, 'xl' => 40][$size] : ['xs' => 20, 'sm' => 20, 'md' => 24, 'lg' => 32, 'xl' => 40][$size]; $tag = $isLink ? 'a' : 'button'; $attributes = $attributes->merge(array_filter([ 'data-md-button' => true, 'data-md-variant' => $variant, 'data-md-color' => $color, 'data-md-size' => $size, 'data-md-shape' => $shape, 'data-md-icon-button' => $iconOnly ? true : null, 'data-md-width' => $iconOnly ? $width : null, 'data-md-selected' => $selected === null ? null : ($selected ? 'true' : 'false'), 'data-md-compact-fab' => $fab ? true : null, 'href' => $isLink ? $link : null, 'target' => $isLink && $external ? '_blank' : null, 'rel' => $isLink && $external ? 'noopener' : null, 'wire:navigate' => $isLink && ! $external && ! $noWireNavigate && ! $attributes->has('wire:navigate') ? true : null, 'aria-disabled' => $inert ? 'true' : null, 'tabindex' => $inert ? '-1' : null, 'type' => $isLink ? null : $type, 'disabled' => ! $isLink && $disabled ? true : null, 'aria-label' => $iconOnly && ! $attributes->has('aria-label') ? ($label ?? $tip) : null, // A link is not a toggle: ARIA defines aria-pressed for buttons only. A selected link keeps // the selected look; `aria-current` is the caller's to set (`:aria-current="'page'"`). 'aria-pressed' => $selected === null || $isLink ? null : ($selected ? 'true' : 'false'), 'wire:loading.attr' => $spinnerTarget ? 'disabled' : null, 'wire:target' => $spinnerTarget, 'style' => $anchor ? "anchor-name: {$anchor}" : null, ], fn ($value): bool => $value !== null)); @endphp <{{ $tag }} {{ $attributes }}> @if ($spinnerTarget) @endif @if ($icon) @endif @unless ($iconOnly) {{ $label ?? $slot }} @endunless @if ($iconRight) @endif @if ($tip !== null) @endif