Resolve the package's own components through its namespace

Package views now write <x-livewire-material::button>, so a configured
prefix (which Blade spells <x-m::button>) and an application component
of the same name can no longer break or shadow them. The showcase
rewrites its examples to the configured prefix. Adds the README, and
waits for the adaptive rail to hear a resize before the navigation
tests press its menu button.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
This commit is contained in:
Andreas Reinhold / reini
2026-09-13 09:05:24 +02:00
co-authored by Claude Opus 5
parent 8f174520eb
commit 1e580c573e
62 changed files with 477 additions and 206 deletions
+164
View File
@@ -0,0 +1,164 @@
# Livewire Material
Material 3 Expressive components for Laravel and Livewire, built on Tailwind CSS 4.
- Anonymous Blade components for the current M3 Expressive catalogue: buttons and FABs, menus, chips, text fields, selection controls, sliders, pickers, dialogs and sheets, lists, cards, carousels, progress and loading indicators, snackbars, tabs, app bars, toolbars, navigation bars and rails, an adaptive app shell, data tables and pagination.
- A colour scheme generated from one seed colour with Google's colour science (`php artisan material:scheme`), light and dark, and a theme that is chosen before the first paint.
- The full Material Symbols Rounded set and the M3 Expressive shapes, drawn inline without an icon package.
- Error pages and a Markdown mail theme in the same scheme.
- A showcase of every component in the application's own scheme, a design guard for tests, and Laravel Boost guidelines and a skill for AI agents.
No JavaScript libraries beyond the Alpine that ships with Livewire. Browsers: Chrome 125+, Firefox 147+, Safari 18.4+.
## Requirements
PHP 8.4+, Laravel 13, Livewire 4, Tailwind CSS 4 with Vite, and Node (for `material:scheme`).
## Installation
The package is served from Gitea. Add the repository and require it:
```bash
composer config repositories.livewire-material vcs https://gitea.nonameweb.ch/noNameWEB/livewire-material.git
composer require nonameweb/livewire-material
```
### Stylesheet and script
The application's build imports from `vendor/`, so Composer packages must be installed before `npm run build` — in a Dockerfile, copy `composer.json`, run `composer install`, then build the assets.
```css
/* resources/css/app.css */
@import 'tailwindcss';
@import '../../vendor/nonameweb/livewire-material/resources/css/material.css';
@import './material-scheme.css';
@source '../../vendor/nonameweb/livewire-material/resources/views';
@source '../../vendor/nonameweb/livewire-material/src';
```
```js
// resources/js/app.js
import '../../vendor/nonameweb/livewire-material/resources/js/material.js'
```
Do not install Alpine separately; Livewire provides it.
### Layout
The theme script goes in `<head>`, before `@vite`, so the page paints in the visitor's theme:
```blade
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<x-theme-script />
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-surface font-sans text-on-surface antialiased">
{{ $slot }}
<x-toast />
</body>
</html>
```
### Colour scheme
Generate the scheme from a seed colour. It writes `resources/css/material-scheme.css` (imported above) and `material-scheme.json` (read by the mail theme):
```bash
php artisan material:scheme "#4f46e5" --variant=tonal-spot
```
Variants: `tonal-spot`, `vibrant`, `expressive`, `neutral`, `fidelity`, `content`, `monochrome`, `rainbow`, `fruit-salad`. `--contrast` runs from -1 to 1; `--success`, `--warning` and `--info` seed the state colours. Regenerate instead of editing the file.
### Configuration
```bash
php artisan vendor:publish --tag=livewire-material-config
```
- `prefix` — components are `<x-button>`, `<x-card>`… Set `'m'` when a name clashes with the application's own components, and they become `<x-m::button>`. `<x-livewire-material::button>` always works.
- `theme.default` (`light`, `dark` or `system`), `theme.storage_key`, `theme.legacy_keys` (an earlier toggle's localStorage keys, adopted once).
- `fields.variant` — text fields `outlined` (default) or `filled`.
- `pagination` — draw Laravel's and Livewire's paginators in M3 (default `true`).
- `showcase.enabled`, `showcase.path`, `showcase.middleware`, `showcase.vite`.
- `node` — the Node binary for `material:scheme`.
## Usage
```blade
<x-card title="holiday-photos.zip" subtitle="248 MB · expires in 3 days" variant="outlined">
<x-slot:actions>
<x-button label="Copy link" icon="content_copy" wire:click="copy" />
<x-button label="Delete" danger wire:click="$set('confirming', true)" />
</x-slot:actions>
</x-card>
<x-modal wire:model="confirming" title="Delete this share?" icon="delete">
Recipients lose access at once.
<x-slot:actions>
<x-button label="Cancel" x-on:click="close()" />
<x-button label="Delete" danger wire:click="delete" />
</x-slot:actions>
</x-modal>
```
```php
use NoNameWeb\LivewireMaterial\Concerns\Toasts;
class Shares extends Component
{
use Toasts;
public function copy(): void
{
$this->success('Link copied');
}
}
```
Every component, prop and slot is documented in the Boost skill (`resources/boost/skills/livewire-material-development/SKILL.md`) and shown in the showcase.
## Showcase
While the application runs locally (or with `MATERIAL_SHOWCASE=true`), `/material` renders every token and component, in every variant, in the application's own scheme and theme.
## Testing the design
```php
use NoNameWeb\LivewireMaterial\Testing\DesignGuard;
it('uses only what compiles', function () {
expect(DesignGuard::scan([resource_path('views'), resource_path('js'), app_path()])
->forbidColours(['tertiary'])
->violations())->toBe([]);
});
```
The guard fails on maryUI tags, daisyUI classes, colours the theme does not declare, unknown Material Symbol names and Blade directives written inside component tags.
## AI agents
With [Laravel Boost](https://github.com/laravel/boost), `php artisan boost:install` (or `boost:update --discover`) picks up the package's guideline and the `livewire-material-development` skill.
## Developing the package
```bash
composer install && npm install
npm run build # the Workbench's assets (or `npm run dev` while working)
composer serve # the showcase at http://127.0.0.1:8000/material
vendor/bin/pest --testsuite=Feature
npx playwright install
vendor/bin/pest --testsuite=Browser --browser chrome # also firefox, safari
```
## Credits
Material Symbols, the M3 Expressive shapes, Google Sans Flex, material-color-utilities and Jetpack Compose Material 3's tokens and algorithms are Google's and the Android Open Source Project's; see `NOTICE`.
## License
MIT. See `LICENSE`.
+4 -2
View File
@@ -8,8 +8,10 @@ return [
|--------------------------------------------------------------------------
|
| Every component is an anonymous Blade component. Without a prefix they are
| <x-button>, <x-card> and so on; set a prefix such as 'm-' when a name
| clashes with one of the application's own components (<x-m-button>).
| <x-button>, <x-card> and so on; set a prefix such as 'm' when a name
| clashes with one of the application's own components, and they become
| <x-m::button>, <x-m::card>. They are always <x-livewire-material::button>
| as well.
|
*/
@@ -764,7 +764,7 @@ It fails on maryUI tags, daisyUI classes, colours the theme does not declare, un
## Conventions
- Components are anonymous Blade components: `<x-name>` without a prefix, or `<x-{prefix}name>` when `config('livewire-material.prefix')` is set.
- Components are anonymous Blade components: `<x-name>` without a prefix, or `<x-{prefix}::name>` when `config('livewire-material.prefix')` is set; `<x-livewire-material::name>` always works.
- Write class names out whole. Tailwind cannot compile `'text-'.$tone` or `type-{{ $size }}`, and the design guard cannot read them.
- The showcase at `/material` (local only, `MATERIAL_SHOWCASE=true` to force it) renders every token and component.
@@ -32,7 +32,7 @@
$initials = $image ? null : ($avatar ?? \Illuminate\Support\Str::of((string) $name)->explode(' ')->filter()->take(2)->map(fn (string $word): string => mb_strtoupper(mb_substr($word, 0, 1)))->join(''));
@endphp
<x-menu :$label :$position {{ $attributes }}>
<x-livewire-material::menu :$label :$position {{ $attributes }}>
<x-slot:trigger>
<button
type="button"
@@ -45,7 +45,7 @@
@elseif (filled($initials))
{{ $initials }}
@else
<x-icon name="person" />
<x-livewire-material::icon name="person" />
@endif
</button>
</x-slot:trigger>
@@ -60,20 +60,20 @@
@endif
</div>
<x-menu-separator />
<x-livewire-material::menu-separator />
@endif
{{ $slot }}
@if ($theme)
<x-menu-item icon="contrast" x-on:click="$store.theme.toggle()" data-account-theme>
<x-livewire-material::menu-item icon="contrast" x-on:click="$store.theme.toggle()" data-account-theme>
<span x-text="$store.theme.resolved === 'dark' ? @js(__('Light theme')) : @js(__('Dark theme'))">{{ __('Theme') }}</span>
</x-menu-item>
</x-livewire-material::menu-item>
@endif
@isset($footer)
<x-menu-separator />
<x-livewire-material::menu-separator />
{{ $footer }}
@endisset
</x-menu>
</x-livewire-material::menu>
+2 -2
View File
@@ -40,7 +40,7 @@
{{ $attributes->class(['flex items-start gap-3 rounded-corner-md p-4', $colours]) }}
>
@if ($icon)
<x-icon :name="$icon" filled class="size-6" />
<x-livewire-material::icon :name="$icon" filled class="size-6" />
@endif
<div class="min-w-0 flex-1 space-y-1 self-center">
@@ -59,7 +59,7 @@
@if ($dismissible)
<button type="button" class="state-layer focus-ring -m-2 inline-flex size-10 shrink-0 items-center justify-center rounded-corner-full" aria-label="{{ __('Dismiss') }}" x-on:click="shown = false">
<x-icon name="close" class="size-5" />
<x-livewire-material::icon name="close" class="size-5" />
</button>
@endif
</div>
+10 -10
View File
@@ -88,7 +88,7 @@
class="sr-only focus:not-sr-only focus:fixed focus:start-4 focus:top-[calc(env(safe-area-inset-top)+1rem)] focus:z-[60] focus:rounded-corner-full focus:bg-inverse-surface focus:px-4 focus:py-2 focus:type-label-lg focus:text-inverse-on-surface focus:shadow-elevation-3 focus:outline-none"
>{{ __('Skip to content') }}</a>
<x-navigation-rail mode="adaptive" :label="$label" :width="$railWidth">
<x-livewire-material::navigation-rail mode="adaptive" :label="$label" :width="$railWidth">
@isset($brand)
<x-slot:brand>{{ $brand }}</x-slot:brand>
@endisset
@@ -99,14 +99,14 @@
@foreach ($groups as $group)
@if ($group->first()['section'] !== null)
<x-navigation-rail-section :label="$group->first()['section']">
<x-livewire-material::navigation-rail-section :label="$group->first()['section']">
@foreach ($group as $item)
<x-navigation-rail-item :label="$item['title']" :icon="$item['icon']" :link="$item['url']" :active="$item['active']" :badge="$item['badge']" :no-wire-navigate="! $item['navigate']" />
<x-livewire-material::navigation-rail-item :label="$item['title']" :icon="$item['icon']" :link="$item['url']" :active="$item['active']" :badge="$item['badge']" :no-wire-navigate="! $item['navigate']" />
@endforeach
</x-navigation-rail-section>
</x-livewire-material::navigation-rail-section>
@else
@foreach ($group as $item)
<x-navigation-rail-item :label="$item['title']" :icon="$item['icon']" :link="$item['url']" :active="$item['active']" :badge="$item['badge']" :no-wire-navigate="! $item['navigate']" />
<x-livewire-material::navigation-rail-item :label="$item['title']" :icon="$item['icon']" :link="$item['url']" :active="$item['active']" :badge="$item['badge']" :no-wire-navigate="! $item['navigate']" />
@endforeach
@endif
@endforeach
@@ -122,7 +122,7 @@
@endisset
</x-slot:footer>
@endif
</x-navigation-rail>
</x-livewire-material::navigation-rail>
<div class="flex min-w-0 flex-1 flex-col">
{{ $top ?? '' }}
@@ -134,13 +134,13 @@
@if ($barItems->isNotEmpty())
<div data-app-shell-bar class="fixed inset-x-0 bottom-0 z-30 sm:hidden">
<x-navigation-bar :label="$label">
<x-livewire-material::navigation-bar :label="$label">
@foreach ($barItems as $item)
<x-navigation-bar-item :label="$item['title']" :icon="$item['icon']" :link="$item['url']" :active="$item['active']" :badge="$item['badge']" :no-wire-navigate="! $item['navigate']" />
<x-livewire-material::navigation-bar-item :label="$item['title']" :icon="$item['icon']" :link="$item['url']" :active="$item['active']" :badge="$item['badge']" :no-wire-navigate="! $item['navigate']" />
@endforeach
</x-navigation-bar>
</x-livewire-material::navigation-bar>
</div>
@endif
<x-toast />
<x-livewire-material::toast />
</div>
+4 -4
View File
@@ -202,13 +202,13 @@
<{{ $tag }} {{ $attributes }}>
@if ($spinnerTarget)
<span wire:loading.flex wire:target="{{ $spinnerTarget }}" class="items-center justify-center">
<x-loading :class="$iconSize" :label="false" />
<x-livewire-material::loading :class="$iconSize" :label="false" />
</span>
@endif
@if ($icon)
<span class="contents" @if ($spinnerTarget) wire:loading.remove wire:target="{{ $spinnerTarget }}" @endif>
<x-icon :name="$icon" :filled="$selected === true" :class="\Illuminate\Support\Arr::toCssClasses([$iconSize, 'max-sm:size-6' => $fab])" />
<x-livewire-material::icon :name="$icon" :filled="$selected === true" :class="\Illuminate\Support\Arr::toCssClasses([$iconSize, 'max-sm:size-6' => $fab])" />
</span>
@endif
@@ -217,10 +217,10 @@
@endunless
@if ($iconRight)
<x-icon :name="$iconRight" :class="$iconSize" />
<x-livewire-material::icon :name="$iconRight" :class="$iconSize" />
@endif
@if ($tip !== null)
<x-tooltip :text="$tip" :side="$tipSide" :anchor="$anchor" />
<x-livewire-material::tooltip :text="$tip" :side="$tipSide" :anchor="$anchor" />
@endif
</{{ $tag }}>
+1 -1
View File
@@ -60,7 +60,7 @@
</div>
@if ($separator)
<x-divider class="mt-4" />
<x-livewire-material::divider class="mt-4" />
@endif
</div>
@endif
@@ -136,7 +136,7 @@
'hidden pointer-fine:flex' => $controls === null,
'flex' => $controls === true,
])>
<x-button
<x-livewire-material::button
icon="chevron_left"
variant="tonal"
:tooltip="__('Previous')"
@@ -145,7 +145,7 @@
x-on:click="previous()"
class="rtl:-scale-x-100"
/>
<x-button
<x-livewire-material::button
icon="chevron_right"
variant="tonal"
:tooltip="__('Next')"
@@ -36,8 +36,8 @@
@if ($indeterminate) data-indeterminate @endif
@if ($messages !== []) aria-invalid="true" aria-describedby="{{ $id }}-support" @endif
/>
<x-icon name="check" class="size-4" data-check />
<x-icon name="remove" class="size-4" data-mixed />
<x-livewire-material::icon name="check" class="size-4" data-check />
<x-livewire-material::icon name="remove" class="size-4" data-mixed />
</span>
@if (filled($label) || filled($hint))
+8 -8
View File
@@ -230,7 +230,7 @@
<img src="{{ $avatar }}" alt="" @class(['me-2 size-6 shrink-0 rounded-corner-full object-cover', 'opacity-38' => $disabled]) />
@endif
@elseif ($icon)
<x-icon :name="$icon" :class="\Illuminate\Support\Arr::toCssClasses(['me-2 size-4.5', $leadingInk])" />
<x-livewire-material::icon :name="$icon" :class="\Illuminate\Support\Arr::toCssClasses(['me-2 size-4.5', $leadingInk])" />
@endif
<span data-chip-label class="truncate">{{ $label ?? $slot }}</span>
@@ -251,7 +251,7 @@
'cursor-not-allowed' => $disabled,
])
>
<x-icon name="close" class="size-4.5" />
<x-livewire-material::icon name="close" class="size-4.5" />
</button>
@endif
@@ -260,7 +260,7 @@
@endif
@if ($tooltip !== null)
<x-tooltip :text="$tooltip" :anchor="$anchor" />
<x-livewire-material::tooltip :text="$tooltip" :anchor="$anchor" />
@endif
</span>
@else
@@ -277,22 +277,22 @@
'h-4.5 w-0 transition-[width] duration-(--md-sys-motion-effects-default-duration) ease-effects-default group-has-checked/chip:w-4.5 group-has-checked/chip:duration-(--md-sys-motion-spatial-fast-duration) group-has-checked/chip:ease-spatial-fast group-aria-pressed/chip:w-4.5 group-aria-pressed/chip:duration-(--md-sys-motion-spatial-fast-duration) group-aria-pressed/chip:ease-spatial-fast' => blank($icon),
])>
@if ($icon)
<x-icon :name="$icon" :class="\Illuminate\Support\Arr::toCssClasses(['size-4.5 transition-opacity duration-(--md-sys-motion-effects-fast-duration) ease-effects-fast group-has-checked/chip:opacity-0 group-aria-pressed/chip:opacity-0', $leadingInk])" />
<x-livewire-material::icon :name="$icon" :class="\Illuminate\Support\Arr::toCssClasses(['size-4.5 transition-opacity duration-(--md-sys-motion-effects-fast-duration) ease-effects-fast group-has-checked/chip:opacity-0 group-aria-pressed/chip:opacity-0', $leadingInk])" />
@endif
<x-icon name="check" data-chip-check :class="$check" />
<x-livewire-material::icon name="check" data-chip-check :class="$check" />
</span>
@elseif ($icon)
<x-icon :name="$icon" :class="\Illuminate\Support\Arr::toCssClasses(['me-2 size-4.5', $leadingInk])" />
<x-livewire-material::icon :name="$icon" :class="\Illuminate\Support\Arr::toCssClasses(['me-2 size-4.5', $leadingInk])" />
@endif
<span class="truncate">{{ $label ?? $slot }}</span>
@if ($iconRight)
<x-icon :name="$iconRight" :class="\Illuminate\Support\Arr::toCssClasses(['ms-2 size-4.5', $trailingInk])" />
<x-livewire-material::icon :name="$iconRight" :class="\Illuminate\Support\Arr::toCssClasses(['ms-2 size-4.5', $trailingInk])" />
@endif
@if ($tooltip !== null)
<x-tooltip :text="$tooltip" :anchor="$anchor" />
<x-livewire-material::tooltip :text="$tooltip" :anchor="$anchor" />
@endif
</{{ $tag }}>
@endif
+7 -7
View File
@@ -115,7 +115,7 @@
@if ($model === null) x-modelable="value" @endif
>
<div style="anchor-name: {{ $anchor }}">
<x-field :$id :$label :$hint :$messages :$icon :$variant>
<x-livewire-material::field :$id :$label :$hint :$messages :$icon :$variant>
<input
id="{{ $id }}"
type="text"
@@ -144,9 +144,9 @@
/>
<x-slot:trailing>
<x-icon name="arrow_drop_down" class="field-trailing field-arrow size-(--field-icon)" />
<x-livewire-material::icon name="arrow_drop_down" class="field-trailing field-arrow size-(--field-icon)" />
</x-slot:trailing>
</x-field>
</x-livewire-material::field>
</div>
<ul
@@ -170,7 +170,7 @@
class="field-option"
>
<span x-text="option.label"></span>
<x-icon name="check" class="field-check size-6" />
<x-livewire-material::icon name="check" class="field-check size-6" />
</li>
</template>
<li x-show="filtered.length === 0" class="px-4 py-3 type-body-md text-on-surface-variant">{{ __('Nothing matches') }}</li>
@@ -202,9 +202,9 @@
}"
@if ($model === null) x-modelable="selection" @endif
>
<x-chip-set :$label :$hint :error-field="$model">
<x-livewire-material::chip-set :$label :$hint :error-field="$model">
@foreach ($choices as $index => $choice)
<x-chip
<x-livewire-material::chip
type="filter"
:label="$choice['label']"
:selected="$isSelected($choice['value'])"
@@ -213,6 +213,6 @@
x-on:click="toggle({{ $index }})"
/>
@endforeach
</x-chip-set>
</x-livewire-material::chip-set>
</div>
@endif
@@ -28,12 +28,12 @@
'px-4' => $variant === 'filled',
])>
@if ($icon)
<x-icon :name="$icon" class="size-6 text-on-surface-variant" />
<x-livewire-material::icon :name="$icon" class="size-6 text-on-surface-variant" />
@endif
<span class="min-w-0 flex-1">{{ $heading ?? $title }}</span>
<x-icon name="expand_more" class="size-6 text-on-surface-variant transition-[rotate] duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast group-open/collapse:rotate-180" />
<x-livewire-material::icon name="expand_more" class="size-6 text-on-surface-variant transition-[rotate] duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast group-open/collapse:rotate-180" />
</summary>
<div @class(['pb-4 type-body-md text-on-surface-variant', 'px-4' => $variant === 'filled', 'pt-1'])>
+22 -22
View File
@@ -133,7 +133,7 @@
data-datepicker
>
<div style="anchor-name: {{ $anchor }}">
<x-field
<x-livewire-material::field
:$id
:$label
:$icon
@@ -191,10 +191,10 @@
@disabled($attributes->get('disabled') || $attributes->get('readonly'))
data-datepicker-toggle
>
<x-icon name="calendar_today" class="size-(--field-icon)" />
<x-livewire-material::icon name="calendar_today" class="size-(--field-icon)" />
</button>
</x-slot:trailing>
</x-field>
</x-livewire-material::field>
</div>
<div id="{{ $id }}-support" data-datepicker-support data-size="{{ in_array($size, ['sm', 'xs'], true) ? $size : 'md' }}">
@@ -241,10 +241,10 @@
<p data-datepicker-headline aria-live="polite" x-text="headline"></p>
<span x-show="! typing">
<x-button icon="edit" :tooltip="__('Switch to text input mode')" x-on:click="toggleTyping()" data-datepicker-switch />
<x-livewire-material::button icon="edit" :tooltip="__('Switch to text input mode')" x-on:click="toggleTyping()" data-datepicker-switch />
</span>
<span x-show="typing">
<x-button icon="date_range" :tooltip="__('Switch to calendar input mode')" x-on:click="toggleTyping()" data-datepicker-switch />
<x-livewire-material::button icon="date_range" :tooltip="__('Switch to calendar input mode')" x-on:click="toggleTyping()" data-datepicker-switch />
</span>
</div>
</div>
@@ -261,19 +261,19 @@
x-bind:aria-label="monthYear + ', ' + @js(__('Switch to selecting a year'))"
>
<span x-text="monthYear"></span>
<x-icon name="arrow_drop_down" class="size-4.5" data-datepicker-menu-arrow />
<x-livewire-material::icon name="arrow_drop_down" class="size-4.5" data-datepicker-menu-arrow />
</button>
<span data-datepicker-arrows x-bind:data-concealed="view !== 'days' ? '' : null">
<x-button icon="chevron_left" :tooltip="__('Previous month')" x-on:click="step(-1)" x-bind:disabled="view !== 'days' || ! canStep(-1)" data-datepicker-previous />
<x-button icon="chevron_right" :tooltip="__('Next month')" x-on:click="step(1)" x-bind:disabled="view !== 'days' || ! canStep(1)" data-datepicker-next />
<x-livewire-material::button icon="chevron_left" :tooltip="__('Previous month')" x-on:click="step(-1)" x-bind:disabled="view !== 'days' || ! canStep(-1)" data-datepicker-previous />
<x-livewire-material::button icon="chevron_right" :tooltip="__('Next month')" x-on:click="step(1)" x-bind:disabled="view !== 'days' || ! canStep(1)" data-datepicker-next />
</span>
</div>
<div data-datepicker-nav data-docked x-show="presentation === 'docked'">
<span data-datepicker-stepper>
<span data-datepicker-arrows x-bind:data-concealed="view !== 'days' ? '' : null">
<x-button icon="chevron_left" :tooltip="__('Previous month')" x-on:click="step(-1)" x-bind:disabled="view !== 'days' || ! canStep(-1)" />
<x-livewire-material::button icon="chevron_left" :tooltip="__('Previous month')" x-on:click="step(-1)" x-bind:disabled="view !== 'days' || ! canStep(-1)" />
</span>
<button
type="button"
@@ -283,16 +283,16 @@
x-bind:aria-label="monthLabel + ', ' + @js(__('Switch to selecting a month'))"
>
<span x-text="monthLabel"></span>
<x-icon name="arrow_drop_down" class="size-4.5" data-datepicker-menu-arrow />
<x-livewire-material::icon name="arrow_drop_down" class="size-4.5" data-datepicker-menu-arrow />
</button>
<span data-datepicker-arrows x-bind:data-concealed="view !== 'days' ? '' : null">
<x-button icon="chevron_right" :tooltip="__('Next month')" x-on:click="step(1)" x-bind:disabled="view !== 'days' || ! canStep(1)" />
<x-livewire-material::button icon="chevron_right" :tooltip="__('Next month')" x-on:click="step(1)" x-bind:disabled="view !== 'days' || ! canStep(1)" />
</span>
</span>
<span data-datepicker-stepper>
<span data-datepicker-arrows x-bind:data-concealed="view !== 'days' ? '' : null">
<x-button icon="chevron_left" :tooltip="__('Previous year')" x-on:click="step(-12)" x-bind:disabled="view !== 'days' || ! canStep(-12)" />
<x-livewire-material::button icon="chevron_left" :tooltip="__('Previous year')" x-on:click="step(-12)" x-bind:disabled="view !== 'days' || ! canStep(-12)" />
</span>
<button
type="button"
@@ -302,10 +302,10 @@
x-bind:aria-label="yearLabel + ', ' + @js(__('Switch to selecting a year'))"
>
<span x-text="yearLabel"></span>
<x-icon name="arrow_drop_down" class="size-4.5" data-datepicker-menu-arrow />
<x-livewire-material::icon name="arrow_drop_down" class="size-4.5" data-datepicker-menu-arrow />
</button>
<span data-datepicker-arrows x-bind:data-concealed="view !== 'days' ? '' : null">
<x-button icon="chevron_right" :tooltip="__('Next year')" x-on:click="step(12)" x-bind:disabled="view !== 'days' || ! canStep(12)" />
<x-livewire-material::button icon="chevron_right" :tooltip="__('Next year')" x-on:click="step(12)" x-bind:disabled="view !== 'days' || ! canStep(12)" />
</span>
</span>
</div>
@@ -372,7 +372,7 @@
x-on:click="month.disabled || showMonthOf(+shown.slice(0, 4), month.value)"
data-datepicker-option
>
<x-icon name="check" data-datepicker-check />
<x-livewire-material::icon name="check" data-datepicker-check />
<span x-text="month.label"></span>
</button>
</template>
@@ -388,7 +388,7 @@
x-on:click="showMonthOf(year.value, +shown.slice(5, 7))"
data-datepicker-option
>
<x-icon name="check" data-datepicker-check />
<x-livewire-material::icon name="check" data-datepicker-check />
<span x-text="year.label"></span>
</button>
</template>
@@ -397,7 +397,7 @@
<div x-show="typing" data-datepicker-entry>
<div @if ($range) data-range @endif data-datepicker-entry-fields>
<x-field id="{{ $id }}-entry" :label="$range ? __('Start date') : __('Date')" :$variant x-bind:data-invalid="entryError !== '' ? '' : null">
<x-livewire-material::field id="{{ $id }}-entry" :label="$range ? __('Start date') : __('Date')" :$variant x-bind:data-invalid="entryError !== '' ? '' : null">
<input
id="{{ $id }}-entry"
type="text"
@@ -412,10 +412,10 @@
x-on:keydown.enter.prevent="confirm()"
class="field-control"
/>
</x-field>
</x-livewire-material::field>
@if ($range)
<x-field id="{{ $id }}-entry-end" :label="__('End date')" :$variant x-bind:data-invalid="entryError !== '' ? '' : null">
<x-livewire-material::field id="{{ $id }}-entry-end" :label="__('End date')" :$variant x-bind:data-invalid="entryError !== '' ? '' : null">
<input
id="{{ $id }}-entry-end"
type="text"
@@ -429,7 +429,7 @@
x-on:keydown.enter.prevent="confirm()"
class="field-control"
/>
</x-field>
</x-livewire-material::field>
@endif
</div>
@@ -439,8 +439,8 @@
</div>
<div x-show="view === 'days' || typing || presentation === 'modal'" data-datepicker-actions>
<x-button :label="__('Cancel')" x-on:click="cancel()" data-datepicker-cancel />
<x-button :label="__('OK')" x-on:click="confirm()" data-datepicker-confirm />
<x-livewire-material::button :label="__('Cancel')" x-on:click="cancel()" data-datepicker-cancel />
<x-livewire-material::button :label="__('OK')" x-on:click="confirm()" data-datepicker-confirm />
</div>
</div>
</dialog>
+2 -2
View File
@@ -113,13 +113,13 @@
@if ($withCloseButton)
<span class="-me-3 -mt-2 inline-flex shrink-0">
<x-button icon="close" :tooltip-left="__('Close')" x-on:click="close()" />
<x-livewire-material::button icon="close" :tooltip-left="__('Close')" x-on:click="close()" />
</span>
@endif
</div>
@if ($separator)
<x-divider class="mt-4" />
<x-livewire-material::divider class="mt-4" />
@endif
</div>
@endif
@@ -19,8 +19,8 @@
<div {{ $attributes->class('flex flex-col items-center gap-4 px-4 py-10 text-center') }}>
<div class="relative grid size-28 place-items-center">
<x-shape :name="$shape" class="absolute inset-0 size-full text-secondary-container" />
<x-icon :name="$icon" class="relative size-12 text-on-secondary-container" />
<x-livewire-material::shape :name="$shape" class="absolute inset-0 size-full text-secondary-container" />
<x-livewire-material::icon :name="$icon" class="relative size-12 text-on-secondary-container" />
</div>
@if ($title)
@@ -40,7 +40,7 @@
<{{ $tag }} {{ $attributes }}>
@if ($icon)
<x-icon :name="$icon" class="size-6" />
<x-livewire-material::icon :name="$icon" class="size-6" />
@endif
<span>{{ $label ?? $slot }}</span>
@@ -49,8 +49,8 @@
$colours,
])
>
<x-icon :name="$icon" class="size-6 group-aria-expanded/fab:hidden" />
<x-icon name="close" class="hidden size-5 group-aria-expanded/fab:block" />
<x-livewire-material::icon :name="$icon" class="size-6 group-aria-expanded/fab:hidden" />
<x-livewire-material::icon name="close" class="hidden size-5 group-aria-expanded/fab:block" />
</button>
</span>
+2 -2
View File
@@ -65,7 +65,7 @@
<{{ $tag }} {{ $attributes }}>
@if ($icon)
<x-icon :name="$icon" :class="$iconSize" />
<x-livewire-material::icon :name="$icon" :class="$iconSize" />
@endif
@if ($extended)
@@ -73,6 +73,6 @@
@endif
@if ($tooltip !== null)
<x-tooltip :text="$tooltip" :anchor="$anchor" />
<x-livewire-material::tooltip :text="$tooltip" :anchor="$anchor" />
@endif
</{{ $tag }}>
+1 -1
View File
@@ -46,7 +46,7 @@
>
<div class="field-box">
@if ($icon)
<x-icon :name="$icon" class="field-icon size-(--field-icon)" />
<x-livewire-material::icon :name="$icon" class="field-icon size-(--field-icon)" />
@endif
@if (filled($prefix))
+2 -2
View File
@@ -26,7 +26,7 @@
: [];
@endphp
<x-field :$id :$label :$hint :$messages :$variant floated :class="$attributes->get('class')">
<x-livewire-material::field :$id :$label :$hint :$messages :$variant floated :class="$attributes->get('class')">
<input
{{ $attributes->except(['class', 'id', 'type']) }}
type="file"
@@ -35,4 +35,4 @@
@if ($messages !== [] || filled($hint)) aria-describedby="{{ $id }}-support" @endif
class="field-control"
/>
</x-field>
</x-livewire-material::field>
+1 -1
View File
@@ -16,7 +16,7 @@
@isset($actions)
@if ($separator)
<x-divider />
<x-livewire-material::divider />
@endif
<div {{ $actions->attributes->class(['flex flex-wrap items-center justify-end gap-2']) }}>
+1 -1
View File
@@ -79,7 +79,7 @@
/>
@if (filled(data_get($option, $optionIcon)))
<x-icon :name="data_get($option, $optionIcon)" :class="$iconSize" />
<x-livewire-material::icon :name="data_get($option, $optionIcon)" :class="$iconSize" />
@endif
<span class="truncate">{{ data_get($option, $optionLabel) }}</span>
+5 -5
View File
@@ -33,7 +33,7 @@
$messages = $model !== null && isset($errors) ? \Illuminate\Support\Arr::flatten($errors->get($model)) : [];
@endphp
<x-field :$id :$label :$hint :hint-class="$hintClass" :$messages :$icon :$prefix :$suffix :$size :$variant :$mono :class="$attributes->get('class')" :data-readonly="$attributes->get('readonly') ? '' : null">
<x-livewire-material::field :$id :$label :$hint :hint-class="$hintClass" :$messages :$icon :$prefix :$suffix :$size :$variant :$mono :class="$attributes->get('class')" :data-readonly="$attributes->get('readonly') ? '' : null">
<input
{{ $attributes->except(['class', 'id', 'placeholder'])->merge(['type' => 'text']) }}
id="{{ $id }}"
@@ -54,7 +54,7 @@
aria-label="{{ __('Clear') }}"
data-field-clear
>
<x-icon name="close" class="size-(--field-icon)" />
<x-livewire-material::icon name="close" class="size-(--field-icon)" />
</button>
@endif
@@ -67,13 +67,13 @@
aria-label="{{ __('Copy') }}"
data-field-copy
>
<x-icon name="content_copy" class="size-(--field-icon)" />
<x-livewire-material::icon name="content_copy" class="size-(--field-icon)" />
</button>
@endif
@if ($iconRight)
<x-icon :name="$iconRight" class="field-trailing size-(--field-icon)" />
<x-livewire-material::icon :name="$iconRight" class="field-trailing size-(--field-icon)" />
@endif
</x-slot:trailing>
@endif
</x-field>
</x-livewire-material::field>
@@ -61,7 +61,7 @@
@elseif ($image)
<img src="{{ $image }}" alt="" class="size-14 shrink-0 rounded-corner-sm object-cover" />
@elseif ($icon)
<x-icon :name="$icon" :class="\Illuminate\Support\Arr::toCssClasses(['size-6', 'text-on-surface-variant' => ! $selected && ! $disabled])" />
<x-livewire-material::icon :name="$icon" :class="\Illuminate\Support\Arr::toCssClasses(['size-6', 'text-on-surface-variant' => ! $selected && ! $disabled])" />
@endif
<div class="min-w-0 flex-1">
@@ -94,6 +94,6 @@
@endif
@if ($iconRight)
<x-icon :name="$iconRight" :class="\Illuminate\Support\Arr::toCssClasses(['size-6', 'text-on-surface-variant' => ! $selected && ! $disabled])" />
<x-livewire-material::icon :name="$iconRight" :class="\Illuminate\Support\Arr::toCssClasses(['size-6', 'text-on-surface-variant' => ! $selected && ! $disabled])" />
@endif
</div>
@@ -60,7 +60,7 @@
<{{ $tag }} {{ $attributes }}>
@if ($icon)
<x-icon :name="$icon" :filled="$selected === true" :class="'size-5 '.$iconInk" />
<x-livewire-material::icon :name="$icon" :filled="$selected === true" :class="'size-5 '.$iconInk" />
@endif
<span class="min-w-0 flex-1">
@@ -76,6 +76,6 @@
@endif
@if ($iconRight)
<x-icon :name="$iconRight" :class="'size-5 '.$iconInk" />
<x-livewire-material::icon :name="$iconRight" :class="'size-5 '.$iconInk" />
@endif
</{{ $tag }}>
+3 -3
View File
@@ -69,7 +69,7 @@
])>
@if ($fullscreen)
<div class="flex h-16 shrink-0 items-center gap-1 px-1 sm:hidden">
<x-button icon="close" :tooltip="__('Close')" x-on:click="close()" />
<x-livewire-material::button icon="close" :tooltip="__('Close')" x-on:click="close()" />
@if (filled($title))
<span class="truncate type-title-lg">{{ $title }}</span>
@@ -81,7 +81,7 @@
@if (filled($title) || $icon)
<div @class(['mb-4', 'max-sm:hidden' => $fullscreen && ! $icon, 'text-center' => $icon])>
@if ($icon)
<x-icon :name="$icon" class="mx-auto mb-4 size-6 text-secondary" />
<x-livewire-material::icon :name="$icon" class="mx-auto mb-4 size-6 text-secondary" />
@endif
@if (filled($title))
@@ -93,7 +93,7 @@
@endif
@if ($separator)
<x-divider class="mt-4" />
<x-livewire-material::divider class="mt-4" />
@endif
</div>
@endif
@@ -49,13 +49,13 @@
<span data-navigation-indicator>
<span class="relative inline-flex">
@if ($icon)
<x-icon :name="$icon" :filled="$active" class="size-6" />
<x-livewire-material::icon :name="$icon" :filled="$active" class="size-6" />
@endif
@if ($dot)
<x-badge floating />
<x-livewire-material::badge floating />
@elseif ($count)
<x-badge :value="$badge" max="999" floating />
<x-livewire-material::badge :value="$badge" max="999" floating />
@endif
</span>
</span>
@@ -51,13 +51,13 @@
<span data-navigation-indicator>
<span class="relative inline-flex">
@if ($icon)
<x-icon :name="$icon" :filled="$active" class="size-6" />
<x-livewire-material::icon :name="$icon" :filled="$active" class="size-6" />
@endif
@if ($dot)
<x-badge floating />
<x-livewire-material::badge floating />
@elseif ($count)
<span class="hidden rail-collapsed:contents"><x-badge :value="$badge" max="999" floating /></span>
<span class="hidden rail-collapsed:contents"><x-livewire-material::badge :value="$badge" max="999" floating /></span>
@endif
</span>
</span>
@@ -65,7 +65,7 @@
<span data-navigation-label>{{ $label ?? $slot }}</span>
@if ($count)
<span class="flex shrink-0 rail-collapsed:hidden"><x-badge :value="$badge" max="999" /></span>
<span class="flex shrink-0 rail-collapsed:hidden"><x-livewire-material::badge :value="$badge" max="999" /></span>
@endif
@if ($spoken !== null)
@@ -107,8 +107,8 @@
x-bind:aria-expanded="expanded.toString()"
class="state-layer focus-ring inline-flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-corner-full text-on-surface-variant after:absolute after:top-1/2 after:left-1/2 after:size-12 after:-translate-x-1/2 after:-translate-y-1/2"
>
<span class="contents rail-collapsed:hidden"><x-icon name="menu_open" class="size-6" /></span>
<span class="hidden rail-collapsed:contents"><x-icon name="menu" class="size-6" /></span>
<span class="contents rail-collapsed:hidden"><x-livewire-material::icon name="menu_open" class="size-6" /></span>
<span class="hidden rail-collapsed:contents"><x-livewire-material::icon name="menu" class="size-6" /></span>
</button>
@endif
@@ -20,7 +20,7 @@
$messages = $model !== null && isset($errors) ? \Illuminate\Support\Arr::flatten($errors->get($model)) : [];
@endphp
<x-field :$id :$label :$hint :hint-class="$hintClass" :$messages :$icon :$size :$variant :class="$attributes->get('class')" x-data="{ shown: false }">
<x-livewire-material::field :$id :$label :$hint :hint-class="$hintClass" :$messages :$icon :$size :$variant :class="$attributes->get('class')" x-data="{ shown: false }">
<input
{{ $attributes->except(['class', 'id', 'type', 'placeholder']) }}
id="{{ $id }}"
@@ -43,8 +43,8 @@
class="field-trailing field-button"
data-field-reveal
>
<x-icon name="visibility" class="size-(--field-icon)" x-show="! shown" />
<x-icon name="visibility_off" class="size-(--field-icon)" x-show="shown" x-cloak />
<x-livewire-material::icon name="visibility" class="size-(--field-icon)" x-show="! shown" />
<x-livewire-material::icon name="visibility_off" class="size-(--field-icon)" x-show="shown" x-cloak />
</button>
</x-slot:trailing>
</x-field>
</x-livewire-material::field>
+3 -3
View File
@@ -37,11 +37,11 @@
>
<div data-search-bar role="search" x-on:click="if ($event.target === $el) $refs.input.focus()">
<span data-search-leading x-show="! fullScreen">
<x-icon :name="$icon" />
<x-livewire-material::icon :name="$icon" />
</span>
<button type="button" data-search-leading data-search-back x-show="fullScreen" x-cloak x-on:click="close()" aria-label="{{ __('Back') }}">
<x-icon name="arrow_back" />
<x-livewire-material::icon name="arrow_back" />
</button>
<input
@@ -64,7 +64,7 @@
/>
<button type="button" data-search-clear x-on:click="clear()" aria-label="{{ __('Clear') }}">
<x-icon name="close" />
<x-livewire-material::icon name="close" />
</button>
@isset($trailing)
@@ -33,21 +33,21 @@
<div {{ $attributes->class(['min-w-0']) }} data-section-nav>
@if ($current)
<div class="sm:hidden" data-section-picker>
<x-menu :$label position="bottom-start">
<x-livewire-material::menu :$label position="bottom-start">
<x-slot:trigger>
<button type="button" class="focus-ring flex h-12 w-[min(20rem,calc(100vw-2rem))] cursor-pointer items-center gap-3 rounded-corner-xs border border-outline px-4 text-start type-body-lg text-on-surface">
@isset($current['icon'])
<x-icon :name="$current['icon']" class="size-5 text-on-surface-variant" />
<x-livewire-material::icon :name="$current['icon']" class="size-5 text-on-surface-variant" />
@endisset
<span class="min-w-0 flex-1 truncate">{{ $current['title'] }}</span>
<x-icon name="arrow_drop_down" class="size-6 text-on-surface-variant" />
<x-livewire-material::icon name="arrow_drop_down" class="size-6 text-on-surface-variant" />
</button>
</x-slot:trigger>
@foreach ($items as $item)
<x-menu-item :label="$item['title']" :icon="$item['icon'] ?? null" :link="$item['url']" :selected="$isCurrent($item)" :no-wire-navigate="$noWireNavigate" />
<x-livewire-material::menu-item :label="$item['title']" :icon="$item['icon'] ?? null" :link="$item['url']" :selected="$isCurrent($item)" :no-wire-navigate="$noWireNavigate" />
@endforeach
</x-menu>
</x-livewire-material::menu>
</div>
@endif
@@ -65,11 +65,11 @@
>
<span data-tab-content>
@isset($item['icon'])
<x-icon :name="$item['icon']" :filled="$on" class="size-5" />
<x-livewire-material::icon :name="$item['icon']" :filled="$on" class="size-5" />
@endisset
<span class="truncate">{{ $item['title'] }}</span>
@if (filled($item['badge'] ?? null))
<x-badge :value="$item['badge']" />
<x-livewire-material::badge :value="$item['badge']" />
@endif
<span data-tab-indicator aria-hidden="true"></span>
</span>
+3 -3
View File
@@ -32,7 +32,7 @@
$messages = $model !== null && isset($errors) ? \Illuminate\Support\Arr::flatten($errors->get($model)) : [];
@endphp
<x-field :$id :$label :$hint :hint-class="$hintClass" :$messages :$icon :$size :$variant floated :class="$attributes->get('class')">
<x-livewire-material::field :$id :$label :$hint :hint-class="$hintClass" :$messages :$icon :$size :$variant floated :class="$attributes->get('class')">
<select
{{ $attributes->except(['class', 'id']) }}
id="{{ $id }}"
@@ -52,6 +52,6 @@
</select>
<x-slot:trailing>
<x-icon name="arrow_drop_down" class="field-trailing field-arrow size-(--field-icon)" />
<x-livewire-material::icon name="arrow_drop_down" class="field-trailing field-arrow size-(--field-icon)" />
</x-slot:trailing>
</x-field>
</x-livewire-material::field>
+1 -1
View File
@@ -321,7 +321,7 @@
@class(['absolute top-1/2 flex -translate-y-1/2 rtl:-scale-x-100', $iconInk[$color], 'group-has-disabled/slider:text-on-surface/38'])
style="left: {{ $calc($iconAt) }}"
>
<x-icon :name="$icon" :class="$iconSize === 32 ? 'size-8' : 'size-6'" />
<x-livewire-material::icon :name="$icon" :class="$iconSize === 32 ? 'size-8' : 'size-6'" />
</span>
@endif
</div>
@@ -29,7 +29,7 @@
])
>
{{ $slot }}
<x-icon :name="$direction === 'desc' ? 'arrow_downward' : 'arrow_upward'" :class="\Illuminate\Support\Arr::toCssClasses([
<x-livewire-material::icon :name="$direction === 'desc' ? 'arrow_downward' : 'arrow_upward'" :class="\Illuminate\Support\Arr::toCssClasses([
'size-4 transition-opacity duration-(--md-sys-motion-effects-fast-duration)',
'opacity-0 group-hover/sort:opacity-60 group-focus-visible/sort:opacity-60' => ! $active,
])" />
@@ -37,7 +37,7 @@
@endphp
<div data-button-group="split" data-size="{{ $size }}" {{ $attributes->only('class')->class('inline-flex items-center gap-0.5') }}>
<x-button
<x-livewire-material::button
{{ $attributes->except('class') }}
:label="$label"
:icon="$icon"
@@ -51,9 +51,9 @@
:class="$leadingPadding"
/>
<x-menu :position="$position" :label="$menuLabel">
<x-livewire-material::menu :position="$position" :label="$menuLabel">
<x-slot:trigger>
<x-button
<x-livewire-material::button
icon="keyboard_arrow_down"
:aria-label="$menuLabel"
:variant="$variant"
@@ -67,5 +67,5 @@
</x-slot:trigger>
{{ $slot }}
</x-menu>
</x-livewire-material::menu>
</div>
+1 -1
View File
@@ -17,7 +17,7 @@
<div {{ $attributes->class('flex flex-col gap-1 rounded-corner-lg bg-surface-container p-4') }}>
<div class="flex items-center gap-2 type-label-lg text-on-surface-variant">
@if ($icon)
<x-icon :name="$icon" class="size-5" />
<x-livewire-material::icon :name="$icon" class="size-5" />
@endif
<span>{{ $title }}</span>
</div>
+2 -2
View File
@@ -72,12 +72,12 @@
>
<span data-tab-content>
@isset($tab['icon'])
<x-icon :name="$tab['icon']" class="size-6" />
<x-livewire-material::icon :name="$tab['icon']" class="size-6" />
@endisset
<span class="inline-flex items-center gap-1.5">
{{ $tab['label'] }}
@if (filled($tab['badge'] ?? null))
<x-badge :value="$tab['badge']" />
<x-livewire-material::badge :value="$tab['badge']" />
@endif
</span>
<span data-tab-indicator aria-hidden="true"></span>
@@ -22,7 +22,7 @@
$messages = $model !== null && isset($errors) ? \Illuminate\Support\Arr::flatten($errors->get($model)) : [];
@endphp
<x-field :$id :$label :$hint :hint-class="$hintClass" :$messages :$variant :class="$attributes->get('class')" :data-readonly="$attributes->get('readonly') ? '' : null">
<x-livewire-material::field :$id :$label :$hint :hint-class="$hintClass" :$messages :$variant :class="$attributes->get('class')" :data-readonly="$attributes->get('readonly') ? '' : null">
<textarea
{{ $attributes->except(['class', 'id', 'placeholder']) }}
id="{{ $id }}"
@@ -36,4 +36,4 @@
@if ($messages !== [] || filled($hint)) aria-describedby="{{ $id }}-support" @endif
class="field-control"
></textarea>
</x-field>
</x-livewire-material::field>
@@ -40,8 +40,8 @@
data-theme-option="{{ $choice }}"
class="state-layer focus-ring inline-flex min-w-0 flex-1 cursor-pointer items-center justify-center gap-2 border-outline px-3 type-label-lg text-on-surface not-first:border-s first:rounded-s-corner-full last:rounded-e-corner-full aria-checked:bg-secondary-container aria-checked:text-on-secondary-container"
>
<x-icon name="check" class="hidden size-4.5 in-aria-checked:block" />
<x-icon :name="$icon" class="size-4.5 in-aria-checked:hidden" />
<x-livewire-material::icon name="check" class="hidden size-4.5 in-aria-checked:block" />
<x-livewire-material::icon :name="$icon" class="size-4.5 in-aria-checked:hidden" />
{{ __($text) }}
</button>
@endforeach
@@ -65,12 +65,12 @@
{{ $attributes->class(['state-layer focus-ring inline-flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-corner-full text-on-surface-variant transition-[border-radius] duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast active:rounded-corner-sm']) }}
>
@if ($mode === 'cycle')
<x-icon name="light_mode" x-cloak x-show="$store.theme.choice === 'light'" />
<x-icon name="dark_mode" x-cloak x-show="$store.theme.choice === 'dark'" />
<x-icon name="brightness_auto" x-cloak x-show="$store.theme.choice === 'system'" />
<x-livewire-material::icon name="light_mode" x-cloak x-show="$store.theme.choice === 'light'" />
<x-livewire-material::icon name="dark_mode" x-cloak x-show="$store.theme.choice === 'dark'" />
<x-livewire-material::icon name="brightness_auto" x-cloak x-show="$store.theme.choice === 'system'" />
@else
<x-icon name="light_mode" x-cloak x-show="$store.theme.resolved === 'dark'" />
<x-icon name="dark_mode" x-cloak x-show="$store.theme.resolved !== 'dark'" />
<x-livewire-material::icon name="light_mode" x-cloak x-show="$store.theme.resolved === 'dark'" />
<x-livewire-material::icon name="dark_mode" x-cloak x-show="$store.theme.resolved !== 'dark'" />
@endif
</button>
@endif
@@ -138,7 +138,7 @@
x-data="materialTimepicker(@if ($model !== null) @entangle($attributes->wire('model')) @else @js($current) @endif, @js($config))"
@if ($model === null) x-modelable="value" @endif
>
<x-field :$id :$label :$hint :$messages :$icon :$size :$variant data-timepicker-field>
<x-livewire-material::field :$id :$label :$hint :$messages :$icon :$size :$variant data-timepicker-field>
<input
{{ $inputAttributes }}
id="{{ $id }}"
@@ -172,7 +172,7 @@
data-field-clear
@disabled($disabled)
>
<x-icon name="close" class="size-(--field-icon)" />
<x-livewire-material::icon name="close" class="size-(--field-icon)" />
</button>
@endif
@@ -185,10 +185,10 @@
data-timepicker-open
@disabled($disabled)
>
<x-icon name="schedule" class="size-(--field-icon)" />
<x-livewire-material::icon name="schedule" class="size-(--field-icon)" />
</button>
</x-slot:trailing>
</x-field>
</x-livewire-material::field>
@if (filled($name))
<input type="hidden" name="{{ $name }}" value="{{ $current }}" x-bind:value="value ?? ''" />
@@ -348,14 +348,14 @@
<div data-timepicker-actions>
<span data-timepicker-when="dial">
<x-button icon="keyboard" :tooltip="__('Switch to text input mode')" x-on:click="toggleMode()" data-timepicker-mode="input" />
<x-livewire-material::button icon="keyboard" :tooltip="__('Switch to text input mode')" x-on:click="toggleMode()" data-timepicker-mode="input" />
</span>
<span data-timepicker-when="input">
<x-button icon="schedule" :tooltip="__('Switch to clock mode')" x-on:click="toggleMode()" data-timepicker-mode="dial" />
<x-livewire-material::button icon="schedule" :tooltip="__('Switch to clock mode')" x-on:click="toggleMode()" data-timepicker-mode="dial" />
</span>
<span data-timepicker-spacer></span>
<x-button :label="__('Cancel')" x-on:click="cancel()" data-timepicker-cancel />
<x-button :label="__('OK')" x-on:click="confirm()" data-timepicker-confirm />
<x-livewire-material::button :label="__('Cancel')" x-on:click="cancel()" data-timepicker-cancel />
<x-livewire-material::button :label="__('OK')" x-on:click="confirm()" data-timepicker-confirm />
</div>
</div>
</dialog>
+5 -5
View File
@@ -47,10 +47,10 @@
'text-inverse-warning': current.type === 'warning',
'text-inverse-info': current.type === 'info',
}">
<span x-show="current.type === 'success'"><x-icon name="check_circle" filled class="size-6" /></span>
<span x-show="current.type === 'error'"><x-icon name="error" filled class="size-6" /></span>
<span x-show="current.type === 'warning'"><x-icon name="warning" filled class="size-6" /></span>
<span x-show="current.type === 'info'"><x-icon name="info" filled class="size-6" /></span>
<span x-show="current.type === 'success'"><x-livewire-material::icon name="check_circle" filled class="size-6" /></span>
<span x-show="current.type === 'error'"><x-livewire-material::icon name="error" filled class="size-6" /></span>
<span x-show="current.type === 'warning'"><x-livewire-material::icon name="warning" filled class="size-6" /></span>
<span x-show="current.type === 'info'"><x-livewire-material::icon name="info" filled class="size-6" /></span>
</span>
</template>
@@ -65,7 +65,7 @@
<template x-if="current.action || ! current.timeout">
<button type="button" class="state-layer focus-ring inline-flex size-10 shrink-0 items-center justify-center rounded-corner-full" aria-label="{{ __('Dismiss') }}" x-on:click="dismiss()">
<x-icon name="close" class="size-5" />
<x-livewire-material::icon name="close" class="size-5" />
</button>
</template>
</div>
+2 -2
View File
@@ -36,8 +36,8 @@
/>
<span data-handle>
@if ($icons)
<x-icon name="check" class="size-4" data-on />
<x-icon name="close" class="size-4" data-off />
<x-livewire-material::icon name="check" class="size-4" data-on />
<x-livewire-material::icon name="close" class="size-4" data-off />
@endif
</span>
</span>
@@ -11,6 +11,6 @@
@section('message', __('It was open for a while. Refresh it, then try again.'))
@section('actions')
<x-button :link="url()->previous()" :label="__('Refresh the page')" variant="filled" size="md" no-wire-navigate />
<x-button :link="url('/')" :label="__('Go home')" variant="text" size="md" no-wire-navigate />
<x-livewire-material::button :link="url()->previous()" :label="__('Refresh the page')" variant="filled" size="md" no-wire-navigate />
<x-livewire-material::button :link="url('/')" :label="__('Go home')" variant="text" size="md" no-wire-navigate />
@endsection
@@ -18,5 +18,5 @@
@section('message', $reason !== '' && $reason !== 'Service Unavailable' ? __($reason) : __('Were making some improvements. Please check back soon.'))
@section('actions')
<x-button :label="__('Try again')" variant="filled" size="md" onclick="location.reload()" />
<x-livewire-material::button :label="__('Try again')" variant="filled" size="md" onclick="location.reload()" />
@endsection
@@ -32,7 +32,7 @@
<title>@yield('title') · {{ config('app.name') }}</title>
<x-theme-script />
<x-livewire-material::theme-script />
@if ($assets !== null)
{{ $assets }}
@@ -49,7 +49,7 @@
<main data-error-page class="mx-auto flex min-h-dvh max-w-xl flex-col items-center justify-center px-6 py-12 text-center">
<div data-error-art class="relative grid size-48 shrink-0 place-items-center sm:size-60">
<div data-error-shape class="absolute inset-0">
<x-shape :name="trim($__env->yieldContent('shape', 'cookie-7'))" class="size-full text-primary-container" />
<x-livewire-material::shape :name="trim($__env->yieldContent('shape', 'cookie-7'))" class="size-full text-primary-container" />
</div>
<p data-error-code class="relative type-emphasized-display-lg text-on-primary-container tabular-nums">@yield('code')</p>
@@ -71,10 +71,10 @@
@hasSection('actions')
@yield('actions')
@else
<x-button :link="url('/')" :label="__('Go home')" variant="filled" size="md" no-wire-navigate />
<x-livewire-material::button :link="url('/')" :label="__('Go home')" variant="filled" size="md" no-wire-navigate />
@if ($back !== null)
<x-button :link="$back" :label="__('Go back')" variant="text" size="md" no-wire-navigate />
<x-livewire-material::button :link="$back" :label="__('Go back')" variant="text" size="md" no-wire-navigate />
@endif
@endif
</div>
@@ -4,15 +4,15 @@
@if ($paginator->hasPages())
<nav role="navigation" aria-label="{{ __('Pagination Navigation') }}" data-pagination class="flex items-center justify-between gap-4">
@if ($paginator->onFirstPage())
<x-button variant="outlined" icon="chevron_left" :label="__('Previous')" disabled />
<x-livewire-material::button variant="outlined" icon="chevron_left" :label="__('Previous')" disabled />
@else
<x-button variant="outlined" icon="chevron_left" :label="__('Previous')" :link="$paginator->previousPageUrl()" no-wire-navigate rel="prev" />
<x-livewire-material::button variant="outlined" icon="chevron_left" :label="__('Previous')" :link="$paginator->previousPageUrl()" no-wire-navigate rel="prev" />
@endif
@if ($paginator->hasMorePages())
<x-button variant="outlined" icon-right="chevron_right" :label="__('Next')" :link="$paginator->nextPageUrl()" no-wire-navigate rel="next" />
<x-livewire-material::button variant="outlined" icon-right="chevron_right" :label="__('Next')" :link="$paginator->nextPageUrl()" no-wire-navigate rel="next" />
@else
<x-button variant="outlined" icon-right="chevron_right" :label="__('Next')" disabled />
<x-livewire-material::button variant="outlined" icon-right="chevron_right" :label="__('Next')" disabled />
@endif
</nav>
@endif
@@ -19,11 +19,11 @@
<div class="flex items-center gap-1">
@if ($paginator->onFirstPage())
<span class="{{ $dead }}" aria-disabled="true" aria-label="{{ __('Previous') }}">
<x-icon name="chevron_left" class="size-6 rtl:-scale-x-100" />
<x-livewire-material::icon name="chevron_left" class="size-6 rtl:-scale-x-100" />
</span>
@else
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" class="{{ $live }}" aria-label="{{ __('Previous') }}">
<x-icon name="chevron_left" class="size-6 rtl:-scale-x-100" />
<x-livewire-material::icon name="chevron_left" class="size-6 rtl:-scale-x-100" />
</a>
@endif
@@ -45,11 +45,11 @@
@if ($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}" rel="next" class="{{ $live }}" aria-label="{{ __('Next') }}">
<x-icon name="chevron_right" class="size-6 rtl:-scale-x-100" />
<x-livewire-material::icon name="chevron_right" class="size-6 rtl:-scale-x-100" />
</a>
@else
<span class="{{ $dead }}" aria-disabled="true" aria-label="{{ __('Next') }}">
<x-icon name="chevron_right" class="size-6 rtl:-scale-x-100" />
<x-livewire-material::icon name="chevron_right" class="size-6 rtl:-scale-x-100" />
</span>
@endif
</div>
@@ -19,21 +19,21 @@
@if ($paginator->hasPages())
<nav role="navigation" aria-label="{{ __('Pagination Navigation') }}" data-pagination class="flex items-center justify-between gap-4">
@if ($paginator->onFirstPage())
<x-button variant="outlined" icon="chevron_left" :label="__('Previous')" disabled />
<x-livewire-material::button variant="outlined" icon="chevron_left" :label="__('Previous')" disabled />
@elseif ($cursor)
@php($previousCursor = $paginator->previousCursor() ?? $paginator->cursor())
<x-button variant="outlined" icon="chevron_left" :label="__('Previous')" wire:key="cursor-{{ $paginator->getCursorName() }}-{{ $previousCursor?->encode() }}" wire:click="setPage('{{ $previousCursor?->encode() }}', '{{ $paginator->getCursorName() }}')" x-on:click="{{ $scrollIntoViewJsSnippet }}" wire:loading.attr="disabled" dusk="previousPage" />
<x-livewire-material::button variant="outlined" icon="chevron_left" :label="__('Previous')" wire:key="cursor-{{ $paginator->getCursorName() }}-{{ $previousCursor?->encode() }}" wire:click="setPage('{{ $previousCursor?->encode() }}', '{{ $paginator->getCursorName() }}')" x-on:click="{{ $scrollIntoViewJsSnippet }}" wire:loading.attr="disabled" dusk="previousPage" />
@else
<x-button variant="outlined" icon="chevron_left" :label="__('Previous')" wire:click="previousPage('{{ $paginator->getPageName() }}')" x-on:click="{{ $scrollIntoViewJsSnippet }}" wire:loading.attr="disabled" dusk="previousPage" />
<x-livewire-material::button variant="outlined" icon="chevron_left" :label="__('Previous')" wire:click="previousPage('{{ $paginator->getPageName() }}')" x-on:click="{{ $scrollIntoViewJsSnippet }}" wire:loading.attr="disabled" dusk="previousPage" />
@endif
@if (! $paginator->hasMorePages())
<x-button variant="outlined" icon-right="chevron_right" :label="__('Next')" disabled />
<x-livewire-material::button variant="outlined" icon-right="chevron_right" :label="__('Next')" disabled />
@elseif ($cursor)
@php($nextCursor = $paginator->nextCursor() ?? $paginator->cursor())
<x-button variant="outlined" icon-right="chevron_right" :label="__('Next')" wire:key="cursor-{{ $paginator->getCursorName() }}-{{ $nextCursor?->encode() }}" wire:click="setPage('{{ $nextCursor?->encode() }}', '{{ $paginator->getCursorName() }}')" x-on:click="{{ $scrollIntoViewJsSnippet }}" wire:loading.attr="disabled" dusk="nextPage" />
<x-livewire-material::button variant="outlined" icon-right="chevron_right" :label="__('Next')" wire:key="cursor-{{ $paginator->getCursorName() }}-{{ $nextCursor?->encode() }}" wire:click="setPage('{{ $nextCursor?->encode() }}', '{{ $paginator->getCursorName() }}')" x-on:click="{{ $scrollIntoViewJsSnippet }}" wire:loading.attr="disabled" dusk="nextPage" />
@else
<x-button variant="outlined" icon-right="chevron_right" :label="__('Next')" wire:click="nextPage('{{ $paginator->getPageName() }}')" x-on:click="{{ $scrollIntoViewJsSnippet }}" wire:loading.attr="disabled" dusk="nextPage" />
<x-livewire-material::button variant="outlined" icon-right="chevron_right" :label="__('Next')" wire:click="nextPage('{{ $paginator->getPageName() }}')" x-on:click="{{ $scrollIntoViewJsSnippet }}" wire:loading.attr="disabled" dusk="nextPage" />
@endif
</nav>
@endif
@@ -35,11 +35,11 @@
<div class="flex items-center gap-1">
@if ($paginator->onFirstPage())
<span class="{{ $dead }}" aria-disabled="true" aria-label="{{ __('Previous') }}">
<x-icon name="chevron_left" class="size-6 rtl:-scale-x-100" />
<x-livewire-material::icon name="chevron_left" class="size-6 rtl:-scale-x-100" />
</span>
@else
<button type="button" wire:click="previousPage('{{ $paginator->getPageName() }}')" x-on:click="{{ $scrollIntoViewJsSnippet }}" wire:loading.attr="disabled" dusk="previousPage{{ $paginator->getPageName() == 'page' ? '' : '.'.$paginator->getPageName() }}" class="{{ $live }}" aria-label="{{ __('Previous') }}">
<x-icon name="chevron_left" class="size-6 rtl:-scale-x-100" />
<x-livewire-material::icon name="chevron_left" class="size-6 rtl:-scale-x-100" />
</button>
@endif
@@ -63,11 +63,11 @@
@if ($paginator->hasMorePages())
<button type="button" wire:click="nextPage('{{ $paginator->getPageName() }}')" x-on:click="{{ $scrollIntoViewJsSnippet }}" wire:loading.attr="disabled" dusk="nextPage{{ $paginator->getPageName() == 'page' ? '' : '.'.$paginator->getPageName() }}" class="{{ $live }}" aria-label="{{ __('Next') }}">
<x-icon name="chevron_right" class="size-6 rtl:-scale-x-100" />
<x-livewire-material::icon name="chevron_right" class="size-6 rtl:-scale-x-100" />
</button>
@else
<span class="{{ $dead }}" aria-disabled="true" aria-label="{{ __('Next') }}">
<x-icon name="chevron_right" class="size-6 rtl:-scale-x-100" />
<x-livewire-material::icon name="chevron_right" class="size-6 rtl:-scale-x-100" />
</span>
@endif
</div>
@@ -3,6 +3,21 @@
@props(['title' => null, 'code', 'stack' => false])
@php
// Written as an application without a prefix writes it; under a configured prefix the tags are
// rewritten to it, so what renders and what is shown is what this application would write.
$prefix = config('livewire-material.prefix');
if (filled($prefix)) {
$names = collect(glob(\NoNameWeb\LivewireMaterial\LivewireMaterialServiceProvider::componentPath().'/*.blade.php'))
->map(fn (string $path): string => preg_quote(basename($path, '.blade.php'), '/'))
->sortByDesc(fn (string $name): int => strlen($name))
->implode('|');
$code = preg_replace('/<(\/?)x-('.$names.')(?=[\s\/>])/', '<$1x-'.$prefix.'::$2', $code);
}
@endphp
<div class="space-y-4 rounded-corner-lg bg-surface-container p-4">
@if ($title)
<h3 class="type-title-md">{{ $title }}</h3>
+2 -2
View File
@@ -7,7 +7,7 @@
<title>@yield('title', 'Livewire Material')</title>
<x-theme-script />
<x-livewire-material::theme-script />
@vite(config('livewire-material.showcase.vite'))
@livewireStyles
@@ -40,7 +40,7 @@
@yield('content')
<x-toast />
<x-livewire-material::toast />
@livewireScripts
</body>
@@ -76,7 +76,7 @@
<x-button icon="format_underlined" aria-label="Underline" variant="tonal" :selected="false" />
</x-button-group>
BLADE,
'A choice as a connected group (<x-group>)' => <<<'BLADE'
'A choice as a connected group' => <<<'BLADE'
<div x-data="{ theme: 'system', days: ['mon'] }" class="grid w-full gap-6 md:grid-cols-2">
<x-group label="Theme" name="showcase-theme" x-model="theme" :options="[
['id' => 'light', 'name' => 'Light', 'icon' => 'light_mode'],
@@ -24,7 +24,7 @@
<div class="flex flex-wrap items-center gap-4">
<label class="flex min-w-72 flex-1 items-center gap-2 rounded-corner-xs border border-outline px-3 py-2 focus-within:outline-3 focus-within:outline-secondary">
<x-icon name="search" class="size-5 text-on-surface-variant" />
<x-livewire-material::icon name="search" class="size-5 text-on-surface-variant" />
<input type="search" x-model.debounce.150ms="query" placeholder="Search {{ number_format(count(\NoNameWeb\LivewireMaterial\Support\SvgFile::symbolNames())) }} symbols" class="w-full bg-transparent type-body-lg outline-none" />
</label>
@@ -8,9 +8,9 @@
<div class="flex flex-wrap items-center gap-2">
@foreach (['401', '402', '403', '404', '419', '429', '500', '503'] as $code)
<x-button :link="route('livewire-material.error', $code)" :label="$code" variant="tonal" no-wire-navigate />
<x-livewire-material::button :link="route('livewire-material.error', $code)" :label="$code" variant="tonal" no-wire-navigate />
@endforeach
<x-button :link="route('livewire-material.mail')" label="Sample mail" icon="mail" variant="outlined" no-wire-navigate />
<x-livewire-material::button :link="route('livewire-material.mail')" label="Sample mail" icon="mail" variant="outlined" no-wire-navigate />
</div>
</section>
@@ -17,7 +17,7 @@
<div class="grid grid-cols-3 gap-4 sm:grid-cols-5 lg:grid-cols-7">
@foreach (\NoNameWeb\LivewireMaterial\Support\SvgFile::shapeNames() as $shape)
<div class="space-y-2 text-center">
<x-shape :name="$shape" class="mx-auto size-20 text-secondary-container" />
<x-livewire-material::shape :name="$shape" class="mx-auto size-20 text-secondary-container" />
<code class="type-label-md text-on-surface-variant">{{ $shape }}</code>
</div>
@endforeach
+15 -15
View File
@@ -33,54 +33,54 @@
<title>{{ $current['title'] }} · App shell · Livewire Material</title>
<x-theme-script />
<x-livewire-material::theme-script />
@vite(config('livewire-material.showcase.vite'))
@livewireStyles
</head>
<body class="bg-surface font-sans text-on-surface antialiased">
<x-app-shell :destinations="$destinations">
<x-livewire-material::app-shell :destinations="$destinations">
<x-slot:brand>
<a href="{{ route('livewire-material.showcase') }}" class="block truncate rounded-corner-xs type-title-lg focus-ring">Livewire Material</a>
</x-slot:brand>
<x-slot:rail-header>
<span class="rail-collapsed:hidden"><x-fab label="Compose" icon="edit" x-on:click="materialToast('Compose opens here')" /></span>
<span class="hidden rail-collapsed:inline-flex"><x-fab icon="edit" tooltip-right="Compose" x-on:click="materialToast('Compose opens here')" /></span>
<span class="rail-collapsed:hidden"><x-livewire-material::fab label="Compose" icon="edit" x-on:click="materialToast('Compose opens here')" /></span>
<span class="hidden rail-collapsed:inline-flex"><x-livewire-material::fab icon="edit" tooltip-right="Compose" x-on:click="materialToast('Compose opens here')" /></span>
</x-slot:rail-header>
<x-slot:rail-footer>
<x-navigation-rail-item label="Settings" icon="settings" link="#settings" no-wire-navigate />
<x-livewire-material::navigation-rail-item label="Settings" icon="settings" link="#settings" no-wire-navigate />
</x-slot:rail-footer>
<x-slot:actions>
<x-button icon="dark_mode" tooltip-right="Switch theme" x-data x-on:click="$store.theme.toggle()" />
<x-button icon="help" tooltip-right="Help" x-on:click="materialToast('Help opens here')" />
<x-livewire-material::button icon="dark_mode" tooltip-right="Switch theme" x-data x-on:click="$store.theme.toggle()" />
<x-livewire-material::button icon="help" tooltip-right="Help" x-on:click="materialToast('Help opens here')" />
</x-slot:actions>
<x-slot:top>
<header class="sticky top-0 z-20 flex h-16 items-center gap-1 bg-surface px-1 pt-[env(safe-area-inset-top)] sm:px-4">
<span class="sm:hidden"><x-button icon="menu" tooltip="Open navigation" x-data x-on:click="$store.rail.show()" data-test="shell-menu" /></span>
<span class="sm:hidden"><x-livewire-material::button icon="menu" tooltip="Open navigation" x-data x-on:click="$store.rail.show()" data-test="shell-menu" /></span>
<h1 class="min-w-0 flex-1 truncate px-3 type-title-lg sm:px-0">{{ $current['title'] }}</h1>
<x-button icon="search" tooltip="Search" />
<x-livewire-material::button icon="search" tooltip="Search" />
</header>
</x-slot:top>
<div class="mx-auto w-full max-w-5xl space-y-4 px-4 pb-6 sm:px-6">
<p class="type-body-md text-on-surface-variant" data-test="shell-page">This is the {{ strtolower($current['title']) }} page.</p>
<x-list segmented>
<x-livewire-material::list segmented>
@foreach (range(1, 14) as $index)
<x-list-item :title="$current['title'].' item '.$index" description="A line of supporting text for this item" :icon="$current['icon']" trailing="{{ $index }}h" wire:key="item-{{ $index }}" />
<x-livewire-material::list-item :title="$current['title'].' item '.$index" description="A line of supporting text for this item" :icon="$current['icon']" trailing="{{ $index }}h" wire:key="item-{{ $index }}" />
@endforeach
</x-list>
</x-livewire-material::list>
<div class="flex flex-wrap gap-2">
<x-button label="Show a toast" variant="tonal" x-data x-on:click="materialToast('Moved to archive', { action: { label: 'Undo', handler: () => {} } })" />
<x-button label="Back to the showcase" link="{{ route('livewire-material.showcase') }}#navigation" no-wire-navigate />
<x-livewire-material::button label="Show a toast" variant="tonal" x-data x-on:click="materialToast('Moved to archive', { action: { label: 'Undo', handler: () => {} } })" />
<x-livewire-material::button label="Back to the showcase" link="{{ route('livewire-material.showcase') }}#navigation" no-wire-navigate />
</div>
</div>
</x-app-shell>
</x-livewire-material::app-shell>
@livewireScripts
</body>
+14 -1
View File
@@ -47,6 +47,15 @@ class LivewireMaterialServiceProvider extends ServiceProvider
}
}
/**
* The anonymous components. Blade names their view namespace after a hash of this string, and
* compiled views keep that name, so it stays written the way it always was.
*/
public static function componentPath(): string
{
return __DIR__.'/../resources/views/components';
}
/**
* The error-view root: a folder holding only `errors/`.
*/
@@ -105,11 +114,15 @@ class LivewireMaterialServiceProvider extends ServiceProvider
/**
* Register the components as anonymous Blade components under the configured prefix.
*
* The package's own views never go through this registration: they write
* `<x-livewire-material::button>`, which resolves through the view namespace whatever the
* prefix, and which an application's own `<x-button>` cannot shadow.
*/
protected function registerComponents(): void
{
Blade::anonymousComponentPath(
__DIR__.'/../resources/views/components',
static::componentPath(),
filled(config('livewire-material.prefix')) ? config('livewire-material.prefix') : null,
);
}
+4 -1
View File
@@ -15,7 +15,10 @@ function navigationReady(mixed $page): mixed
function shellPage(int $width = 1280, int $height = 900, string $path = '/material/shell'): mixed
{
return navigationReady(visit($path)->resize($width, $height));
// The window is resized after the page starts loading, and an adaptive rail hears of the new
// width from a media query's change event, which can arrive after the first key press.
return navigationReady(visit($path)->resize($width, $height))
->assertScript("window.eval(\"(() => { const rail = document.querySelector('[data-navigation-rail]'); return ! rail || rail.dataset.navigationRail !== 'adaptive' || Alpine.\$data(rail).wide === window.matchMedia('(min-width: 64rem)').matches; })()\")");
}
/**
+74
View File
@@ -0,0 +1,74 @@
<?php
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\View;
use NoNameWeb\LivewireMaterial\LivewireMaterialServiceProvider;
/**
* The package's own component names, longest first.
*
* @return list<string>
*/
function packageComponentNames(): array
{
return collect(File::files(LivewireMaterialServiceProvider::componentPath()))
->map(fn (SplFileInfo $file): string => $file->getBasename('.blade.php'))
->sortByDesc(fn (string $name): int => strlen($name))
->values()
->all();
}
it('refers to its own components through its namespace, which no prefix or app component changes', function () {
$names = implode('|', array_map(fn (string $name): string => preg_quote($name, '/'), packageComponentNames()));
$unqualified = collect(File::allFiles(__DIR__.'/../../resources/views'))
->filter(fn (SplFileInfo $file): bool => str_ends_with($file->getFilename(), '.blade.php'))
->flatMap(function (SplFileInfo $file) use ($names): array {
// Comments document usage as an application writes it, and the showcase's examples
// are that usage; neither is compiled as the package's own markup.
$source = preg_replace(['/\{\{--.*?--\}\}/s', "/<<<'BLADE'.*?^\\s*BLADE,/sm"], '', $file->getContents());
preg_match_all('/<\/?x-('.$names.')(?=[\s\/>])/', $source, $matches);
return array_map(fn (string $tag): string => $file->getRelativePathname().': '.$tag, $matches[0]);
})
->values();
expect($unqualified->all())->toBe([]);
});
it('renders components that use others under a prefix', function () {
config(['livewire-material.prefix' => 'm']);
Blade::anonymousComponentPath(LivewireMaterialServiceProvider::componentPath(), 'm');
expect(Blade::render('<x-m::drawer title="Share" with-close-button separator><x-m::input label="Name" clearable /></x-m::drawer>'))
->toContain('aria-label="Close"')
->toContain('role="separator"')
->toContain('data-field-clear');
});
it('is not shadowed by an application component of the same name', function () {
$views = sys_get_temp_dir().'/livewire-material-shadow-'.uniqid();
File::ensureDirectoryExists($views.'/components');
File::put($views.'/components/button.blade.php', '<span>the application\'s button</span>');
View::getFinder()->prependLocation($views);
try {
expect(Blade::render('<x-button />'))->toContain('the application\'s button')
->and(Blade::render('<x-drawer title="Share" with-close-button />'))->not->toContain('the application\'s button')
->toContain('aria-label="Close"');
} finally {
File::deleteDirectory($views);
}
});
it('shows the showcase\'s examples as an application with a prefix writes them', function () {
config(['livewire-material.prefix' => 'm']);
Blade::anonymousComponentPath(LivewireMaterialServiceProvider::componentPath(), 'm');
expect(Blade::render('<x-showcase::example title="Buttons" :code="$code" />', ['code' => '<x-button label="Save" />']))
->toContain('&lt;x-m::button label=&quot;Save&quot; /&gt;')
->toContain('Save</span>');
});
+2 -2
View File
@@ -26,12 +26,12 @@ it('registers the components without a prefix', function () {
});
it('registers the components under a configured prefix', function () {
config(['livewire-material.prefix' => 'm-']);
config(['livewire-material.prefix' => 'm']);
$provider = app()->getProvider(LivewireMaterialServiceProvider::class);
(fn () => $this->registerComponents())->call($provider);
expect(packageComponentPaths()->last()['prefix'])->toBe('m-');
expect(packageComponentPaths()->last()['prefix'])->toBe('m');
});
it('keeps blade-icons from registering an <x-icon> that would shadow ours', function () {