Add buttons, menus and the rest of M3 Expressive's actions
tests / browser (firefox, firefox) (push) Successful in 1m54s
tests / browser (safari, webkit) (push) Successful in 2m17s
tests / lint (push) Successful in 59s
tests / feature (8.4) (push) Successful in 1m7s
tests / feature (8.5) (push) Successful in 1m0s
tests / browser (chrome, chromium) (push) Successful in 1m49s
tests / browser (firefox, firefox) (push) Successful in 1m54s
tests / browser (safari, webkit) (push) Successful in 2m17s
tests / lint (push) Successful in 59s
tests / feature (8.4) (push) Successful in 1m7s
tests / feature (8.5) (push) Successful in 1m0s
tests / browser (chrome, chromium) (push) Successful in 1m49s
<x-button> (label buttons, icon buttons and toggles in five sizes, with filled, tonal, outlined, elevated and text variants in any colour role), <x-tooltip>, <x-menu> with items, groups and separators, <x-button-group>, <x-group> as a connected button group, <x-split-button>, <x-fab>, <x-fab-menu> and <x-loading>. Sizes, colours and shapes come from androidx Compose Material 3's tokens; the loading indicator ports its Morph into SVG + SMIL. Menus follow WAI-ARIA's menu button pattern on popovers placed by CSS anchor positioning. Browser tests run in Chromium, Firefox and WebKit. The showcase fetches the icon names on demand: inlined, they tripped Pest's test server into HTTP 431s under Firefox. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
This commit is contained in:
co-authored by
Claude Opus 5
parent
b48e879254
commit
cd64f4f371
@@ -107,6 +107,112 @@ One of M3 Expressive's 35 shapes, filled in the text colour, `aria-hidden`, size
|
||||
|
||||
The theme decided before the first paint. Exactly once per layout, in `<head>`, before `@vite`. No props; configured in `config/livewire-material.php`.
|
||||
|
||||
### `<x-button>`
|
||||
|
||||
Label button, icon button, toggle and responsive FAB in one component.
|
||||
|
||||
| Prop | Default | |
|
||||
|---|---|---|
|
||||
| `label` / slot | | the words; without them and with an `icon` it is an icon button |
|
||||
| `variant` | `text` | `filled`, `tonal`, `outlined`, `elevated`, `text` |
|
||||
| `color` (alias `tone`) | `primary` | `primary`, `secondary`, `tertiary`, `error`, `success`, `warning`, `info` |
|
||||
| `primary`, `danger`, `caution` | | shorthands: filled primary, filled error, filled warning |
|
||||
| `size` | `sm` | `xs` 32px, `sm` 40px, `md` 56px, `lg` 96px, `xl` 136px |
|
||||
| `shape` | `round` | or `square`; both square off further while pressed |
|
||||
| `icon`, `icon-right` | | Material Symbol names |
|
||||
| `width` | `default` | icon buttons only: `narrow`, `default`, `wide` |
|
||||
| `selected` | `null` | `true`/`false` makes it a toggle (`aria-pressed`, selected colours and shape) |
|
||||
| `link`, `external`, `no-wire-navigate` | | renders `<a>`, with `wire:navigate` unless external |
|
||||
| `spinner` | | `true` shows the loading indicator while its `wire:click` runs; a string names the action |
|
||||
| `tooltip`, `tooltip-left`, `tooltip-right`, `tooltip-bottom` | | plain tooltip; also the icon button's accessible name |
|
||||
| `disabled`, `type`, `responsive`, `fab` | | `responsive` hides the label below `lg`; `fab` is an extended FAB below `sm`, a filled button above |
|
||||
|
||||
```blade
|
||||
<x-button label="Create link" icon="link" variant="filled" size="md" wire:click="create" spinner />
|
||||
<x-button icon="delete" tooltip="Delete share" wire:click="delete({{ $share->id }})" />
|
||||
<x-button icon="favorite" aria-label="Keep" variant="tonal" :selected="$kept" wire:click="toggleKeep" />
|
||||
```
|
||||
|
||||
### `<x-tooltip>`
|
||||
|
||||
M3's plain tooltip, standalone around any trigger: `<x-tooltip text="Copy link" side="bottom"><button>…</button></x-tooltip>`. `side`: `top` (default), `bottom`, `left`, `right`. Shows on hover (fine pointers) and keyboard focus; `aria-hidden`, so the trigger still needs its own accessible name. Buttons and FABs take a `tooltip` prop instead.
|
||||
|
||||
### `<x-menu>`, `<x-menu-item>`, `<x-menu-group>`, `<x-menu-separator>`
|
||||
|
||||
```blade
|
||||
<x-menu label="Share actions" position="bottom-end">
|
||||
<x-slot:trigger>
|
||||
<x-button icon="more_vert" tooltip="More" />
|
||||
</x-slot:trigger>
|
||||
|
||||
<x-menu-group label="Sort by">
|
||||
<x-menu-item label="Newest" :selected="$sort === 'newest'" wire:click="$set('sort', 'newest')" keep-open />
|
||||
</x-menu-group>
|
||||
<x-menu-separator />
|
||||
<x-menu-item label="Settings" icon="settings" link="{{ route('settings') }}" />
|
||||
<x-menu-item label="Delete" icon="delete" wire:click="delete" description="Recipients lose access" shortcut="⌘⌫" />
|
||||
</x-menu>
|
||||
```
|
||||
|
||||
`<x-menu>`: `trigger` slot (its first button or link becomes the menu button), `label`, `position` (`bottom-start` default, `bottom-end`, `top-start`, `top-end`), `vibrant`. `<x-menu-item>`: `label`, `icon`, `icon-right`, `description`, `shortcut`, `link`, `external`, `selected` (makes it a `menuitemcheckbox`), `disabled`, `keep-open`. Choosing an item closes the menu unless `keep-open`. Keyboard: arrows, Home, End, a letter, Escape (focus returns to the trigger), Tab.
|
||||
|
||||
### `<x-button-group>`
|
||||
|
||||
A row of `<x-button>`s: `<x-button-group label="View" size="md">…</x-button-group>`. `connected` sets them 2px apart with small inner corners (a selected toggle rounds fully). Pass the `size` of the buttons inside.
|
||||
|
||||
### `<x-group>`
|
||||
|
||||
A choice between a few options as a connected button group of native radios (checkboxes with `multiple`):
|
||||
|
||||
```blade
|
||||
<x-group label="Expires after" wire:model.live="expiry" :options="[
|
||||
['id' => '1h', 'name' => '1 hour'],
|
||||
['id' => '1d', 'name' => '1 day', 'icon' => 'today'],
|
||||
['id' => '7d', 'name' => '7 days', 'disabled' => true],
|
||||
]" hint="Recipients lose access after that" />
|
||||
```
|
||||
|
||||
Props: `label`, `hint`, `name` (required with `x-model`), `options`, `option-value` (`id`), `option-label` (`name`), `option-icon` (`icon`), `size`, `variant` (`tonal`, `filled`, `outlined`), `multiple`, `inline` (intrinsic width instead of sharing the row). A validation error for the bound property replaces the hint.
|
||||
|
||||
### `<x-split-button>`
|
||||
|
||||
```blade
|
||||
<x-split-button label="Download all" icon="download" wire:click="downloadZip" menu-label="Download options">
|
||||
<x-menu-item label="Download files one by one" wire:click="downloadEach" />
|
||||
</x-split-button>
|
||||
```
|
||||
|
||||
Attributes go to the leading button; the slot is the menu. `variant` (`filled` default, `tonal`, `outlined`, `elevated`), `color`, `size`, `disabled`, `spinner`, `menu-label`, `position`.
|
||||
|
||||
### `<x-fab>`
|
||||
|
||||
`<x-fab icon="add" tooltip="New share" />` — `size` `sm` 56px (default), `md` 80px, `lg` 96px; with `label` it is an extended FAB. `color` `primary`/`secondary`/`tertiary`, drawn in the container, or `variant="filled"`. It does not position itself; wrap it (`<div class="fixed end-4 bottom-4">`). `link`, `external`, `disabled`, `type`.
|
||||
|
||||
### `<x-fab-menu>`, `<x-fab-menu-item>`
|
||||
|
||||
```blade
|
||||
<div class="fixed end-4 bottom-4">
|
||||
<x-fab-menu label="New">
|
||||
<x-fab-menu-item label="Upload files" icon="upload_file" wire:click="uploadFiles" />
|
||||
<x-fab-menu-item label="Paste text" icon="content_paste" link="{{ route('paste') }}" />
|
||||
</x-fab-menu>
|
||||
</div>
|
||||
```
|
||||
|
||||
Two to six items open above the FAB, which turns into a close button. `<x-fab-menu>`: `icon` (`add`), `label`, `color`, `position` (`top-end` default). Give items the same `color`. Keyboard as `<x-menu>`.
|
||||
|
||||
### `<x-loading>`
|
||||
|
||||
M3 Expressive's loading indicator — a shape morphing through seven Expressive shapes as it turns — for a wait of unknown length. 48px and `primary` unless sized or coloured by class; `contained` sets it on a primary-container circle. A `progressbar` named by `label` ("Loading"); `:label="false"` makes it decorative. It rests under reduced motion.
|
||||
|
||||
```blade
|
||||
<x-loading />
|
||||
<x-loading contained class="size-8" label="Uploading" />
|
||||
<div wire:loading.flex wire:target="upload"><x-loading /></div>
|
||||
```
|
||||
|
||||
`<x-button spinner>` shows a decorative one in place of its icon.
|
||||
|
||||
## Testing the design
|
||||
|
||||
```php
|
||||
|
||||
Reference in New Issue
Block a user