From fb7007c976b52023cb927e37a6bcc1734b778118 Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Thu, 17 Sep 2026 21:07:39 +0200 Subject: [PATCH] Take Tailwind out of the package, and its detection out of the guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tailwind left the stack in 2.0.0, but the package still carried about 330 mentions of it. What the guard's Tailwind detection protected — a class that compiles to nothing — is now protected by a check that does not care where a dead class came from. DesignGuard: about 500 lines of Tailwind tables, scales, palettes and "2.0.0 replacement" hints give way to one check — a class a view or PHP file writes that neither the application's stylesheets nor the package's own declare. It catches a utility of any framework, a typo and a class whose rules were deleted alike, so it also found two classes ReStride draws nothing with. A stylesheet has to be in reach for it: the `.css` files among the scanned paths, or what the `missingStylesheets()` entry imports. The class reader no longer mistakes an array index for a class list (`$block['base']`), and it reads the array a class helper is given, where it read nothing before. The package's own three Tailwind self-guards go with it. Only their one unique check stays, as a test of its own: every `matchMedia` width in resources/js is an M3 breakpoint. The pagination views are `material.blade.php` and `simple-material.blade.php`; only Laravel's and Livewire's default theme names ever made them `tailwind`. The provider sets `Paginator`'s default views and switches `livewire.pagination_theme` to `material` when it is still Livewire's own default, so no application can forget the config; a theme an application chose, and a component's own `$paginationTheme` or `paginationView()`, still win. The rest is prose: the layer-order guidance for an application that still builds Tailwind, the Tailwind wording in the README, the Boost guidelines and the development skill, and about 25 "this used to be a Tailwind utility" comments, along with every "plan step NN" pointer into a gitignored folder. The reset keeps its credit, and NOTICE now carries it too. Feature suite 1159 passed, Chrome browser suite 299 passed. Co-Authored-By: Claude Opus 5 (1M context) --- NOTICE | 5 + README.md | 8 +- UPGRADE.md | 28 +- config/livewire-material.php | 9 +- resources/boost/guidelines/core.blade.php | 2 +- .../boost/guidelines/material-3.blade.php | 2 +- .../livewire-material-development/SKILL.md | 10 +- resources/css/all.css | 6 +- resources/css/components/error-page.css | 8 +- resources/css/components/navigation-bar.css | 13 +- resources/css/components/pagination.css | 7 +- resources/css/foundation.css | 4 - resources/css/foundation/base.css | 2 +- resources/css/foundation/reset.css | 8 +- resources/css/layout/pane.css | 2 +- resources/css/layout/scaffold.css | 17 +- resources/css/showcase.css | 4 +- ...{tailwind.blade.php => material.blade.php} | 9 +- ...nd.blade.php => simple-material.blade.php} | 5 +- ...{tailwind.blade.php => material.blade.php} | 8 +- ...nd.blade.php => simple-material.blade.php} | 6 +- .../components/breakpoint-readout.blade.php | 2 +- .../views/showcase/layout/feed.blade.php | 6 +- .../showcase/layout/list-detail.blade.php | 6 +- .../showcase/layout/supporting-pane.blade.php | 4 +- .../views/showcase/sections/layout.blade.php | 4 +- routes/showcase.php | 4 +- .../Controllers/ShowcaseAssetController.php | 12 +- src/Http/Controllers/ShowcaseController.php | 4 +- src/LivewireMaterialServiceProvider.php | 34 +- src/Showcase/Sections.php | 18 +- src/Support/SchemeStylesheet.php | 8 +- src/Support/Stylesheets.php | 14 +- src/Testing/DesignGuard.php | 687 ++++-------------- tests/Browser/ActionsTest.php | 16 +- tests/Browser/CarouselTest.php | 6 +- tests/Browser/CascadeTest.php | 18 +- tests/Browser/ContainmentTest.php | 5 +- tests/Browser/LayoutTest.php | 8 +- tests/Browser/NavigationTest.php | 2 +- tests/Feature/BoostVocabularyTest.php | 118 +-- tests/Feature/BreakpointsTest.php | 118 --- .../Components/CallerAttributesTest.php | 9 +- .../Components/ComponentStylesheetsTest.php | 34 +- .../Feature/Components/NavigationBarTest.php | 2 +- .../Feature/Components/NavigationRailTest.php | 16 +- tests/Feature/Components/SearchTest.php | 2 +- tests/Feature/DesignGuardTest.php | 464 ++---------- tests/Feature/InternalComponentsTest.php | 2 +- tests/Feature/ServiceProviderTest.php | 61 ++ tests/Feature/ShowcaseAssetsTest.php | 2 +- tests/Feature/StylesheetsBundleTest.php | 32 +- tests/Feature/StylesheetsTest.php | 107 +-- tests/Feature/TokensTest.php | 14 +- tests/Fixtures/dedup-app.css | 10 +- tests/Fixtures/dedup.vite.config.mjs | 16 +- .../design-guard/breakpoints/layout.blade.php | 3 - .../design-guard/colour/badge.blade.php | 6 - .../design-guard/{exempt => declared}/app.css | 4 + .../design-guard/declared/bindings.blade.php | 8 + .../design-guard/declared/lookups.blade.php | 10 + .../declared/package-classes.blade.php | 1 + .../{exempt => declared}/view.blade.php | 0 .../design-guard/running-text.blade.php | 4 - .../design-guard/scale/corners.blade.php | 5 - .../design-guard/scale/elevation.blade.php | 4 - .../design-guard/scale/motion.blade.php | 4 - tests/Fixtures/design-guard/scale/motion.js | 4 - .../design-guard/scale/type.blade.php | 7 - .../stylesheets/foreign-imports.css | 2 +- .../design-guard/tailwind/arbitrary.blade.php | 4 - .../design-guard/tailwind/bindings.blade.php | 8 - .../tailwind/custom-property.blade.php | 1 - .../design-guard/tailwind/display.blade.php | 3 - .../design-guard/tailwind/inks.blade.php | 1 - .../design-guard/tailwind/layout.blade.php | 3 - .../design-guard/tailwind/roles.blade.php | 2 - .../design-guard/tailwind/sizing.blade.php | 3 - .../design-guard/tailwind/spacing.blade.php | 3 - .../design-guard/tailwind/text.blade.php | 3 - .../tailwind/theme-colours.blade.php | 8 - .../design-guard/tailwind/utilities.blade.php | 4 - .../design-guard/tailwind/variant.blade.php | 3 - .../design-guard/views/page.blade.php | 2 +- tests/Pest.php | 2 +- tests/Support/ViewClasses.php | 9 +- .../Providers/WorkbenchServiceProvider.php | 3 +- workbench/resources/css/app.css | 18 +- 88 files changed, 649 insertions(+), 1521 deletions(-) rename resources/views/pagination/laravel/{tailwind.blade.php => material.blade.php} (87%) rename resources/views/pagination/laravel/{simple-tailwind.blade.php => simple-material.blade.php} (82%) rename resources/views/pagination/livewire/{tailwind.blade.php => material.blade.php} (92%) rename resources/views/pagination/livewire/{simple-tailwind.blade.php => simple-material.blade.php} (90%) delete mode 100644 tests/Feature/BreakpointsTest.php delete mode 100644 tests/Fixtures/design-guard/breakpoints/layout.blade.php delete mode 100644 tests/Fixtures/design-guard/colour/badge.blade.php rename tests/Fixtures/design-guard/{exempt => declared}/app.css (50%) create mode 100644 tests/Fixtures/design-guard/declared/bindings.blade.php create mode 100644 tests/Fixtures/design-guard/declared/lookups.blade.php create mode 100644 tests/Fixtures/design-guard/declared/package-classes.blade.php rename tests/Fixtures/design-guard/{exempt => declared}/view.blade.php (100%) delete mode 100644 tests/Fixtures/design-guard/running-text.blade.php delete mode 100644 tests/Fixtures/design-guard/scale/corners.blade.php delete mode 100644 tests/Fixtures/design-guard/scale/elevation.blade.php delete mode 100644 tests/Fixtures/design-guard/scale/motion.blade.php delete mode 100644 tests/Fixtures/design-guard/scale/motion.js delete mode 100644 tests/Fixtures/design-guard/scale/type.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/arbitrary.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/bindings.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/custom-property.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/display.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/inks.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/layout.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/roles.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/sizing.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/spacing.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/text.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/theme-colours.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/utilities.blade.php delete mode 100644 tests/Fixtures/design-guard/tailwind/variant.blade.php diff --git a/NOTICE b/NOTICE index 5ba3c223..f3a60e92 100644 --- a/NOTICE +++ b/NOTICE @@ -52,6 +52,10 @@ Spring motion constants (resources/css/tokens/motion.css) From androidx Compose Material 3, tokens/ExpressiveMotionTokens.kt. Copyright The Android Open Source Project. Apache License 2.0. +CSS reset (resources/css/foundation/reset.css) + Derived rule for rule from Tailwind CSS 4's preflight, after modern-normalize. + Copyright Tailwind Labs. MIT License. + Material Design 3 documentation (resources/boost/guidelines/material-3.blade.php, resources/boost/skills/material-3-design/SKILL.md, docs/reference/m3) Rules, tables and wording condensed from https://m3.material.io (Foundations, Styles, @@ -62,3 +66,4 @@ resources/boost/skills/material-3-design/SKILL.md, docs/reference/m3) Copyright The Android Open Source Project (Apache License 2.0). A copy of the Apache License 2.0 is available at https://www.apache.org/licenses/LICENSE-2.0. +A copy of the MIT License is available at https://opensource.org/licenses/MIT. diff --git a/README.md b/README.md index a4dfbf91..b6b8d985 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ composer require nonameweb/livewire-material 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. -The package's stylesheets are plain CSS, no build step of its own and no Tailwind anywhere in the stack. `all.css` brings the foundation and every component; an application can instead import `foundation.css` first and then the stylesheet of each component its views render (`resources/css/components/button.css`, `resources/css/layout/scaffold.css`, …), since each imports the stylesheets of the components it draws and Vite keeps a file several of them import once. The error pages need no import: they take the foundation, scheme and font from the application's build and inline their own layout's rules beside it. +The package's stylesheets are plain CSS, no build step of its own. `all.css` brings the foundation and every component; an application can instead import `foundation.css` first and then the stylesheet of each component its views render (`resources/css/components/button.css`, `resources/css/layout/scaffold.css`, …), since each imports the stylesheets of the components it draws and Vite keeps a file several of them import once. The error pages need no import: they take the foundation, scheme and font from the application's build and inline their own layout's rules beside it. ```css /* resources/css/app.css */ @@ -94,7 +94,7 @@ php artisan vendor:publish --tag=livewire-material-config - `motion.scheme` — M3's motion scheme: `expressive` (default) or `standard`, the restrained springs, written to ``. - `profiles`, `profile` — colour profiles and the default one (see Colour profiles). - `fields.variant` — text fields `outlined` (default) or `filled`. -- `pagination` — draw Laravel's and Livewire's paginators in M3 (default `true`). +- `pagination` — draw Laravel's and Livewire's paginators in M3 (default `true`); the provider also sets `livewire.pagination_theme` to `material` whenever it still reads as Livewire's own default (`tailwind`), unless an application's own theme, or a component's `$paginationTheme` property or `paginationView()` method, already chose one. - `showcase.enabled`, `showcase.path`, `showcase.middleware`, `showcase.vite`. - `node` — the Node binary for `material:scheme`. @@ -150,9 +150,9 @@ it('uses only what compiles', function () { }); ``` -The guard reads every path it is given and fails, with `path:line` and the 2.0.0 replacement, on any Tailwind utility or variant still in a view, PHP or JS file, a colour of the application's own Tailwind theme (`bg-brand`) included — none compiles, since the application carries no Tailwind — each with its replacement: a layout component and prop (`flex gap-4` → ``), an `md-*` class (`truncate` → `md-truncate`), or a token for the application's own CSS (`rounded-lg` → `var(--md-sys-shape-corner-lg)`). A class the application's own stylesheets declare is exempt. In the `.css` files it is given (`material-scheme.css` skipped) it fails on a literal colour, radius, shadow, font, easing or duration, and on a media query off M3's 600/840/1200/1600px; a `var()`, or a `calc()`/`min()`/`max()`/`clamp()` built on one, is fine. It also fails on unknown Material Symbol names and Blade directives written inside component tags. Markdown mail components (under `mail.markdown.paths`, `resources/views/vendor/mail` by default) are the mail theme's to style: their classes are not read as Tailwind, and a mail theme stylesheet there is neither checked for literals nor a source of exempt classes. +The guard reads every path it is given and fails, with `path:line`, on a class written in a view or PHP file that no stylesheet declares — not the application's own CSS, not the package's `md-*` — whatever it came from: a utility of a framework that isn't in the stack, a typo, or a class whose rule was deleted. Without a `.css` file among the scanned paths or a `missingStylesheets()` entry to supply the application's import graph, it has no declared class to compare against and reports every one of them. In the `.css` files it is given (`material-scheme.css` skipped) it fails on a literal colour, radius, shadow, font, easing or duration, and on a media query off M3's 600/840/1200/1600px; a `var()`, or a `calc()`/`min()`/`max()`/`clamp()` built on one, is fine. It also fails on unknown Material Symbol names and Blade directives written inside component tags. Markdown mail components (under `mail.markdown.paths`, `resources/views/vendor/mail` by default) are the mail theme's to style: their classes are not read by the class check, and a mail theme stylesheet there is neither checked for literals nor a source of declared classes. -`missingStylesheets($cssEntry)` checks the entry's relative `@import` graph, followed through every package file's own imports, against the package tags the views render (unprefixed, under the configured prefix, or ``), `->links()` and the rows they write by hand (`data-md-list-row` on anything but `` needs `components/list-item.css`), and names each missing `@import` line once; a tag the application shadows with its own component is reported instead. It reads imports only, so leave `resource_path('css')` out of `scan()` while the stylesheets still hold literals. `forbidColours([...])` names roles the application leaves out, and fails wherever one (with its `on-` and container roles) is still written: `var(--md-sys-color-…)` in CSS or an inline `style`, its `md-ink-*` class, or a component's `color`/`tone` prop. `forbid($pattern, $reason)` adds any pattern of your own, line by line. +`missingStylesheets($cssEntry)` checks the entry's relative `@import` graph, followed through every package file's own imports, against the package tags the views render (unprefixed, under the configured prefix, or ``), `->links()` and the rows they write by hand (`data-md-list-row` on anything but `` needs `components/list-item.css`), and names each missing `@import` line once; a tag the application shadows with its own component is reported instead. It reads imports only, so leave `resource_path('css')` out of `scan()` while the stylesheets still hold literals — as long as `missingStylesheets()` is given an entry, its import graph still supplies the declared classes family (i) needs. `forbidColours([...])` names roles the application leaves out, and fails wherever one (with its `on-` and container roles) is still written: `var(--md-sys-color-…)` in CSS or an inline `style`, its `md-ink-*` class, or a component's `color`/`tone` prop. `forbid($pattern, $reason)` adds any pattern of your own, line by line. ## AI agents diff --git a/UPGRADE.md b/UPGRADE.md index 31b568f8..fde1f3a1 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,6 +1,6 @@ # Upgrading -## From 2.1.0 to 2.1.1 +## From 2.1.0 to 2.2.0 - **Run `php artisan view:clear` after upgrading.** The components are no longer registered under a view namespace named after a hash of their folder, which only views compiled before 1.0.1 still @@ -28,14 +28,6 @@ under that path is no longer checked for literal values, and its classes no longer exempt the same names in the application's other views. A filter an application wrote around such a finding can go. -- **`DesignGuard`** reports a colour utility on a colour the application's own Tailwind theme - named (`bg-brand`, `text-sport-run`, `from-brand`, `border-l-zone-4`, `bg-brand/8`) in a class - list, with `var(--…)` of the application's own as its replacement, unless the application's - CSS declares the class. None of them has compiled since 2.0.0, and they were the leftovers its - count never showed. Tailwind's other utilities on the same prefixes are named by their own - family now too: `border-collapse`, `border-spacing-*`, `from-10%`, `via-none`, `fill-none`, - `stroke-*`, `text-shadow-*`, `text-inherit`, and Tailwind 4.2's `mauve`, `olive`, `mist` and - `taupe` palettes. A test that expects no findings may fail on them. - **`DesignGuard`** counts a row written by hand. `data-md-list-row` on an `
  • `, a `
    `, a `` or `` takes its hover, focus and press state layer and its selected fill from `components/list-item.css`, but `missingStylesheets()` and `unusedStylesheets()` read only tags @@ -121,6 +113,24 @@ itself from outside; a Tab onto the control, including the one that wraps round to it, and focus moved within the layer still show them. An `autofocus` an application put on another control to keep the focus off the close button can go. +- **`DesignGuard`** reports any class a view or PHP file writes that no stylesheet declares — + not the package's `md-*`, not the application's own CSS — instead of naming Tailwind's + utilities, variants and theme colours with their 2.0.0 replacement. It now catches a dead class + from any source: a typo, a utility of a framework that isn't in the stack, a class whose rule + was deleted — not only Tailwind's — but a class assembled in a PHP string (`'text-'.$tone`) is + still not seen. A test that asserted on the old Tailwind-shaped message needs the new one + instead. +- **The pagination views are `material.blade.php` and `simple-material.blade.php`**, no longer named + after Tailwind. The provider sets `Paginator::defaultView('pagination::material')` and + `defaultSimpleView('pagination::simple-material')`, and switches `livewire.pagination_theme` to + `material` whenever it still reads as Livewire's own default (`tailwind`, including when the key + is missing), so an application can no longer forget the config and silently render Livewire's own + Tailwind view. An application that already chose another theme, or a component with its own + `$paginationTheme` property or `paginationView()` method, is untouched — either still wins. +- **`foundation.css` no longer explains how to order layers beside Tailwind.** No consumer builds + Tailwind any more, so the paragraph about opening both entries with + `@layer properties, theme, base, material, components, utilities;` is gone; the layer statement + and the order it declares are otherwise unchanged. ## From 2.0.0 to 2.1.0 diff --git a/config/livewire-material.php b/config/livewire-material.php index e3da0a48..4cba71ad 100644 --- a/config/livewire-material.php +++ b/config/livewire-material.php @@ -107,10 +107,11 @@ return [ | Pagination |-------------------------------------------------------------------------- | - | Draw Laravel's and Livewire's paginators in M3: the package's views are - | put in front of `pagination::tailwind` and `livewire::tailwind` (and - | their simple versions). An application's own published pagination views - | still win. + | Draw Laravel's and Livewire's paginators in M3: the package's views + | become `Paginator::defaultView()`/`defaultSimpleView()`, and + | `livewire.pagination_theme` is taken over while it is still Livewire's + | own default. An application's own published pagination views still + | win, and so does a theme it chose deliberately. | */ diff --git a/resources/boost/guidelines/core.blade.php b/resources/boost/guidelines/core.blade.php index 86815f4d..9f428717 100644 --- a/resources/boost/guidelines/core.blade.php +++ b/resources/boost/guidelines/core.blade.php @@ -1,7 +1,7 @@ @verbatim ## Livewire Material -This application uses `nonameweb/livewire-material`: Material 3 Expressive components for Laravel and Livewire, in plain CSS. It replaces UI kits such as maryUI, daisyUI and Flux, and Tailwind CSS, in this application. +This application uses `nonameweb/livewire-material`: Material 3 Expressive components for Laravel and Livewire, in plain CSS. No utility classes — Tailwind, daisyUI or any other — belong here: a class your CSS does not declare does nothing, and `DesignGuard` fails it. - Components are anonymous Blade components, unprefixed unless `config/livewire-material.php` sets a `prefix`. Before writing or changing a view that uses them, activate the `livewire-material-development` skill for the props, slots and traps of each component. - The CSS entry imports `foundation.css` first, then the stylesheet of each component the views render (or `all.css` for all of them). A component whose stylesheet is not imported renders unstyled; `DesignGuard::missingStylesheets()` names each missing `@import`. diff --git a/resources/boost/guidelines/material-3.blade.php b/resources/boost/guidelines/material-3.blade.php index 21630482..3837dcb9 100644 --- a/resources/boost/guidelines/material-3.blade.php +++ b/resources/boost/guidelines/material-3.blade.php @@ -3,7 +3,7 @@ Every view in this application is Material 3 Expressive (m3.material.io), through `nonameweb/livewire-material`. These rules decide what to write; the `material-3-design` skill carries the tables, the numbers and Google's source pages behind each one — activate it before designing a screen. -The library is plain CSS on M3's tokens, and there are no utility classes: a Tailwind class, one of the library's 1.x utilities (bg-primary, type-body-md, medium:hidden) or a daisyUI class compiles to nothing, and so does a maryUI tag. A view is written three ways: +The library is plain CSS on M3's tokens. No utility classes — Tailwind, daisyUI or any other — belong here: a class your CSS does not declare does nothing, and `DesignGuard` fails it. A view is written three ways: - Components and their props: ``, and the layout components ``, ``, ``, ``, `` and ``, whose `gap` and `padding` take a spacing token (`space200`) and whose `hide-below`, `hide-from` and `stack-below` take a window size class. - A fixed set of classes for text and interaction on plain elements: `md-type-*`, `md-ink-*`, `md-text-*`, `md-truncate`, `md-tabular`, `md-visually-hidden`, `md-state-layer`, `md-focus-ring`, `md-touch-target` and `md-link`. - The application's own CSS, named by the application, whose values are `--md-sys-*` custom properties. diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index 7a54c9a7..63e1206e 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -90,7 +90,7 @@ Scheme::resolveProfileUsing(fn (): ?string => Setting::get('color_profile')); ## Tokens -2.0.0's vocabulary is plain CSS. There are no utility classes: a Tailwind class, or one of 1.x's own colour, type, shape, elevation, motion or breakpoint utilities, compiles to nothing, and the design guard names the replacement for each (see Testing the design). `resources/css/foundation.css` is the one required import, first in the entry, followed by the stylesheets of the components the views render (see Setup; 1.x's `material.css` shortcut is gone). It declares the layer order `material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility`, so an application's unlayered CSS beats every package rule, and `hidden` or `x-cloak` hides any element. It brings the reset, the page's `surface` and `on-surface` in the brand typeface smoothed in grayscale (drop Tailwind's `antialiased`), every `--md-sys-*` token, and the classes below. An application's views reach for four things, in this order: +2.0.0's vocabulary is plain CSS. There are no utility classes: one of 1.x's own colour, type, shape, elevation, motion or breakpoint utilities, or any other class no stylesheet declares, compiles to nothing, and the design guard fails on it (see Testing the design). `resources/css/foundation.css` is the one required import, first in the entry, followed by the stylesheets of the components the views render (see Setup; 1.x's `material.css` shortcut is gone). It declares the layer order `material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility`, so an application's unlayered CSS beats every package rule, and `hidden` or `x-cloak` hides any element. It brings the reset, the page's `surface` and `on-surface` in the brand typeface smoothed in grayscale, every `--md-sys-*` token, and the classes below. An application's views reach for four things, in this order: - **A component's own props** — `color`, `variant`, `size`, `dense` — before anything else. - **Layout components** for arrangement and spacing: ``, ``, ``, ``, `` inside ``s (see Layout), with `gap` and `padding` as spacing tokens (`space25` … `space900`, 2–72px) and `hide-below`, `hide-from` and `stack-below` for the window size classes. Nothing arranges by class. @@ -1164,7 +1164,7 @@ A data table: write plain ``, ``, ``, `` inside ``; {{ $shares->links() }} ``` -Pagination: `$paginator->links()` (Laravel and Livewire, full and simple/cursor) is drawn in M3 — current page in secondary-container, "Page 2 of 7" on a phone. Turn off with `config('livewire-material.pagination')` = `false`; published `vendor/pagination` or `vendor/livewire` views still win. +Pagination: `$paginator->links()` (Laravel and Livewire, full and simple/cursor) is drawn in M3 — current page in secondary-container, "Page 2 of 7" on a phone. Turn off with `config('livewire-material.pagination')` = `false`; published `vendor/pagination` or `vendor/livewire` views still win. The provider also sets `livewire.pagination_theme` to `material` whenever it still reads as Livewire's own default (`tailwind`); an application's own theme, or a component's `$paginationTheme` property or `paginationView()` method, still wins. ## Testing the design @@ -1180,13 +1180,13 @@ it('uses only what compiles', function () { }); ``` -It reads every path it is given and fails with `path:line` on unknown symbol names, Blade directives written inside a component tag (where they do not compile), and every Tailwind utility or variant — none compiles in a Tailwind-free application — each match printed beside its own 2.0.0 replacement (a layout component and prop, an `md-*` class, a `var(--md-sys-…)` token, or your own CSS), so run the check and read its output rather than a table here. A class the application's own stylesheets declare is exempt, and so is every `md-*` class. +It reads every path it is given and fails with `path:line` on unknown symbol names, Blade directives written inside a component tag (where they do not compile), and a class no stylesheet declares — not your own CSS, not the package's `md-*` — whatever it came from: a leftover utility, a typo, a class whose rule was deleted. Without a `.css` file among the scanned paths or a `missingStylesheets()` entry to supply your import graph, it has no declared class to compare against and reports every one of them. In the `.css` files it is given (`material-scheme.css` skipped) it fails on a literal colour, radius, shadow, font, font size, weight, line height, letter spacing, easing or duration, and on a media query width off 600/840/1200/1600px; a `var()`, or a `calc()`/`min()`/`max()`/`clamp()` built on one, is fine. -Markdown mail components — whatever lies under `config('mail.markdown.paths')`, `resources/views/vendor/mail` by default — are drawn by the mail theme, not by your CSS entry, and its classes (`table`, `button`, `panel`, `break-all`) share Tailwind's names. So the guard skips the class checks there (it still reads their icon names, the directives in their component tags and your own bans), and a mail theme stylesheet under that path, which has to write literal values, is neither checked for literals nor a source of exempt classes. +Markdown mail components — whatever lies under `config('mail.markdown.paths')`, `resources/views/vendor/mail` by default — are drawn by the mail theme, not by your CSS entry. So the guard skips the class check there (it still reads their icon names, the directives in their component tags and your own bans), and a mail theme stylesheet under that path, which has to write literal values, is neither checked for literals nor a source of declared classes. -`missingStylesheets($cssEntry)` follows the entry's relative `@import`s through every package file's own imports (`split-button.css` counts `button.css` and `menu.css` too; `tailwindcss` or a font URL is skipped) and checks them against the package tags a scanned view renders (plain, prefixed or ``), `->links()`, and a row a view of yours writes by hand (`data-md-list-row` on anything but ``, which needs `components/list-item.css`; the package's own views are not read for it); each missing stylesheet names its `@import` line once, and a tag the application shadows with its own component is reported instead — the application's component wins in Blade. It reads imports only: leave `resource_path('css')` out of `scan()` until the stylesheets are on tokens. `unusedStylesheets($cssEntry)` is the other way round: a package stylesheet the entry imports directly that no scanned view needs — no tag, `->links()` or row written by hand — even through a needed stylesheet's own imports, named at its `@import` line to remove (an entry importing `all.css` is left alone). `forbidColours([...])` fails wherever a left-out role (with its `on-` and container roles) is still written: `var(--md-sys-color-…)` in CSS or an inline `style`, its `md-ink-*` class, or a component's `color`/`tone` prop. `forbid($pattern, $reason)` adds a pattern of your own. +`missingStylesheets($cssEntry)` follows the entry's relative `@import`s through every package file's own imports (`split-button.css` counts `button.css` and `menu.css` too; a package name or a font URL is skipped) and checks them against the package tags a scanned view renders (plain, prefixed or ``), `->links()`, and a row a view of yours writes by hand (`data-md-list-row` on anything but ``, which needs `components/list-item.css`; the package's own views are not read for it); each missing stylesheet names its `@import` line once, and a tag the application shadows with its own component is reported instead — the application's component wins in Blade. It reads imports only: leave `resource_path('css')` out of `scan()` until the stylesheets are on tokens, as long as this entry is what `missingStylesheets()` reads — its import graph is still what supplies the declared classes above. `unusedStylesheets($cssEntry)` is the other way round: a package stylesheet the entry imports directly that no scanned view needs — no tag, `->links()` or row written by hand — even through a needed stylesheet's own imports, named at its `@import` line to remove (an entry importing `all.css` is left alone). `forbidColours([...])` fails wherever a left-out role (with its `on-` and container roles) is still written: `var(--md-sys-color-…)` in CSS or an inline `style`, its `md-ink-*` class, or a component's `color`/`tone` prop. `forbid($pattern, $reason)` adds a pattern of your own. ## Conventions diff --git a/resources/css/all.css b/resources/css/all.css index 3ae4a2c7..ec1eeadd 100644 --- a/resources/css/all.css +++ b/resources/css/all.css @@ -1,9 +1,9 @@ /* * Livewire Material for an application that wants everything: the foundation, every layout * stylesheet and every component stylesheet, each imported once. The blocks below are the same - * groups `components.css` and `layout.css` used before this file replaced both of them (plan step - * 37) — one block per group, so a stylesheet still lands in a predictable place — plus the - * Layout block those two files didn't share. + * groups `components.css` and `layout.css` used before this file replaced both of them — one + * block per group, so a stylesheet still lands in a predictable place — plus the Layout block + * those two files didn't share. * * This is not the recommended way to bring the package's CSS in: importing `all.css` pulls in * every component's rules whether an application's views render them or not. The recommended path diff --git a/resources/css/components/error-page.css b/resources/css/components/error-page.css index 3ee157ad..1ced1801 100644 --- a/resources/css/components/error-page.css +++ b/resources/css/components/error-page.css @@ -20,10 +20,10 @@ * The page carries this file itself: `src/Support/ErrorPage.php` inlines it, with its imports, into * every error page (`Stylesheets::bundle()`) — beside the application's Vite tags when there is a * build (`layoutStyles()`), inside the whole fallback stylesheet when there is none - * (`fallbackStyles()`, plan step 40) — so an application imports nothing for its error pages - * (plan step 46). The body is still matched only when it holds the layout - * (`body:has(> [data-md-error-page])`): an application that imports `all.css` has this file in - * its bundle beside every other page, where a bare `body` rule would restyle them all. + * (`fallbackStyles()`) — so an application imports nothing for its error pages. The body is still + * matched only when it holds the layout (`body:has(> [data-md-error-page])`): an application that + * imports `all.css` has this file in its bundle beside every other page, where a bare `body` rule + * would restyle them all. * * The page's font is the foundation's, `--md-ref-typeface-brand` on `html` (foundation/base.css), * as the old `font-sans` was; only the fallback, which has no `@font-face`, uses a system stack. diff --git a/resources/css/components/navigation-bar.css b/resources/css/components/navigation-bar.css index 83282b15..fc99ee3b 100644 --- a/resources/css/components/navigation-bar.css +++ b/resources/css/components/navigation-bar.css @@ -78,14 +78,11 @@ * A bar that has slid off the bottom of the window is no longer there to clear, so the offset * everything pinned to the bottom reads drops to the bottom safe area and whatever the * application has docked on the bar — a `fab` button, the snackbar and the page's own bottom - * padding all follow it down and come back up with it. Layered now, unlike before this - * rewrite: used to publish --material-bottom-bar with a Tailwind utility, which - * no rule in any layer could outrank regardless of specificity; now scaffold.css publishes it - * itself in `material.layout` (layout/scaffold.css), a layer this file's own - * `material.components` always outranks by declaration order alone — the same reason - * toolbar.css's and fab.css's overrides only have to beat another `material.components` rule, - * not chase a Tailwind utility out of the cascade. Keyed on `data-md-scaffold`, the hook the - * scaffold renders on its root, not `data-app-shell`, which that rewrite also retired. + * padding all follow it down and come back up with it. `` publishes + * `--material-bottom-bar` itself, in `material.layout` (layout/scaffold.css), a layer this + * file's own `material.components` always outranks by declaration order alone — the same + * reason toolbar.css's and fab.css's overrides only have to beat another `material.components` + * rule. Keyed on `data-md-scaffold`, the hook the scaffold renders on its root. */ [data-md-scaffold]:has([data-md-navigation-bar][data-md-hide-on-scroll][data-md-hidden]) { --material-bottom-bar: calc(var(--material-safe-bottom, env(safe-area-inset-bottom)) + var(--material-bottom-extra, 0px)); diff --git a/resources/css/components/pagination.css b/resources/css/components/pagination.css index 3daf7c48..3771f16d 100644 --- a/resources/css/components/pagination.css +++ b/resources/css/components/pagination.css @@ -1,7 +1,8 @@ /* - * The paginators, Laravel's and Livewire's, drawn in M3 (resources/views/pagination/**): the package - * puts its views in front of the frameworks' `tailwind` and `simple-tailwind` ones, whose names they - * keep. + * The paginators, Laravel's and Livewire's, drawn in M3 (resources/views/pagination/**): named + * `material`/`simple-material`, put in front of the frameworks' own namespaces and set as + * Laravel's default view (`Paginator::defaultView()`/`defaultSimpleView()`) and, where Livewire + * still reads its own default, as its `pagination_theme`. * * The numbered paginator is a row: on its start, what the page holds in body-small on-surface-variant * — "21–30 of 95", or "Page 3 of 10" below `medium` (600px), where the page numbers give way and only diff --git a/resources/css/foundation.css b/resources/css/foundation.css index b2347212..8bbdd52e 100644 --- a/resources/css/foundation.css +++ b/resources/css/foundation.css @@ -24,10 +24,6 @@ * therefore outranks every package rule, whatever the selector — an application's broad selector * (`button { … }`) restyles the components too. foundation/hidden.css keeps its two `!important` * rules outside the layers, so `hidden` and `x-cloak` hide an element whatever sets its display. - * - * An application still building Tailwind keeps it in a separate entry and opens both entries with - * `@layer properties, theme, base, material, components, utilities;`, so the `material` layers sit - * above Tailwind's preflight and below its utilities. */ @layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; diff --git a/resources/css/foundation/base.css b/resources/css/foundation/base.css index bbb36fd7..4b5d86ce 100644 --- a/resources/css/foundation/base.css +++ b/resources/css/foundation/base.css @@ -11,7 +11,7 @@ * Text is smoothed in grayscale where a platform offers the choice (macOS browsers): Google Sans * Flex is drawn for it, and subpixel smoothing thickens its strokes on light text over a dark * surface. Every surface the package draws — the showcase, the error pages — is set this way, - * and so is an application, which drops Tailwind's `antialiased` class with nothing to replace. + * and so is an application, with no class of its own needed. * * In `material.base`, above the reset and below every component and text class. */ diff --git a/resources/css/foundation/reset.css b/resources/css/foundation/reset.css index 3d11fe16..ce3fbd77 100644 --- a/resources/css/foundation/reset.css +++ b/resources/css/foundation/reset.css @@ -2,10 +2,10 @@ * The reset: what a browser draws before any stylesheet, taken back to a clean start. * * Rule for rule the reset the components were built on, Tailwind CSS 4's preflight (MIT, - * © Tailwind Labs, after modern-normalize), so a component looks the same whether an application - * still builds Tailwind or not: every box `border-box` with a `0 solid` border and no margin or - * padding, headings and lists unstyled, form controls inheriting font, colour and tracking with no - * background or radius, media as blocks that never overflow their parent. Three differences: + * © Tailwind Labs, after modern-normalize): every box `border-box` with a `0 solid` border and no + * margin or padding, headings and lists unstyled, form controls inheriting font, colour and + * tracking with no background or radius, media as blocks that never overflow their parent. Three + * differences: * * - no typeface here: foundation/base.css sets `--md-ref-typeface-brand` on ; * - a placeholder is `on-surface-variant`, the colour of M3's text field placeholder (androidx diff --git a/resources/css/layout/pane.css b/resources/css/layout/pane.css index c539c783..ccf9bbd0 100644 --- a/resources/css/layout/pane.css +++ b/resources/css/layout/pane.css @@ -21,7 +21,7 @@ * way it goes, so it mirrors in a right-to-left document (§ Layout → Bidirectionality / RTL). * * In `material.layout`; `hide-below`/`hide-from` come from visibility.css. Imports app-bar.css and - * button.css for the pane's own top app bar and its back button (plan step 36 carry-over). + * button.css for the pane's own top app bar and its back button. */ @layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; diff --git a/resources/css/layout/scaffold.css b/resources/css/layout/scaffold.css index 7f1ff5a2..f3426fd7 100644 --- a/resources/css/layout/scaffold.css +++ b/resources/css/layout/scaffold.css @@ -2,9 +2,7 @@ * : M3's scaffold — bars, then rails, then panes (docs/reference/m3/foundations.md * § Layout → Scaffold) — as a column: an optional `banner` full width, a row below it holding the * adaptive navigation rail and the content region, the bottom navigation bar on a compact window, - * and the snackbar host. Plan step 36 (navigation group) moves in everything the view drew with - * Tailwind; `data-md-scaffold-fab` and the content region's `--md-layout-margin` are step 35's, - * unchanged below. + * and the snackbar host. * * `--material-margin` is M3's window margin, which the content region pads itself with and an * application's own rules can read: 16px below `medium`, 24px from it @@ -23,11 +21,11 @@ * `[data-md-scaffold-content]` — the rail's sibling — is always a flex column, so the page's own * bar, the FAB wrapper and `
    ` stack regardless of the row's own display. * - * `[data-md-scaffold-actions]`'s column layout is `rail-collapsed:flex-col`, the one Tailwind - * variant scaffold.blade.php still used (`resources/css/tailwind.css`, removed with this rule): the - * actions row is always inside a rail, never the rail element itself, so it reads - * `--md-navigation-rail-value` with `@container style()` like any other application content does - * (`resources/css/components/navigation-rail.css`), rather than copying the rail's own conditions. + * `[data-md-scaffold-actions]`'s column layout comes from a container style query on + * `--md-navigation-rail-value: collapsed`: the actions row is always inside a rail, never the rail + * element itself, so it reads that property with `@container style()` like any other application + * content does (`resources/css/components/navigation-rail.css`), rather than copying the rail's + * own conditions. * * `[data-md-scaffold-bar]` pins the navigation bar to the window's bottom edge below `medium`, at * z-index 30: over the page, under the rail's scrim (40) and the snackbar (50, toast.css). While a @@ -38,8 +36,7 @@ * (toast.css), a placed toolbar (toolbar.css) and the content region's bottom padding. Without a * bar, or from `medium`, the scaffold publishes nothing and each reader falls back to 0px. A bar that has slid * away on scroll drops its own height from the variable (navigation-bar.css); that rule wins - * because `material.components` comes after this file's `material.layout`, where before this - * rewrite the variable was a Tailwind utility and the override had to sit outside every layer. + * because `material.components` comes after this file's `material.layout` in the cascade. * * The content region's `
    ` is matched as the content wrapper's own child, so a `
    ` an * application nests inside the page takes none of the region's padding or clipping. diff --git a/resources/css/showcase.css b/resources/css/showcase.css index d94199b6..4b08d673 100644 --- a/resources/css/showcase.css +++ b/resources/css/showcase.css @@ -1,7 +1,7 @@ /* * The showcase's own chrome: what its pages draw beyond the layout components, the `md-*` text - * classes and the shared interaction classes (plan step 38) — the rail's wordmark, the search box - * and its results, a keyboard key in running text, an example's card and its code sample, and the + * classes and the shared interaction classes — the rail's wordmark, the search box and its + * results, a keyboard key in running text, an example's card and its code sample, and the * foundation pages' specimens: a colour swatch, a type-scale row, a corner swatch, an elevation * tile, a motion track and its dot, an icon search result and the Layout page's breakpoint cards. * diff --git a/resources/views/pagination/laravel/tailwind.blade.php b/resources/views/pagination/laravel/material.blade.php similarity index 87% rename from resources/views/pagination/laravel/tailwind.blade.php rename to resources/views/pagination/laravel/material.blade.php index 53774977..a813d936 100644 --- a/resources/views/pagination/laravel/tailwind.blade.php +++ b/resources/views/pagination/laravel/material.blade.php @@ -1,8 +1,9 @@ {{-- Laravel's paginator (`$items->links()` outside Livewire), drawn in M3 as Livewire's is - (resources/views/pagination/livewire/tailwind.blade.php): links instead of actions, the current - page in secondary-container, "Page 2 of 7" below 600px. The package puts this in front of - `pagination::tailwind` (config `livewire-material.pagination`); the name is Laravel's, the drawing - resources/css/components/pagination.css. --}} + (resources/views/pagination/livewire/material.blade.php): links instead of actions, the + current page in secondary-container, "Page 2 of 7" below 600px. The name is `material`; the + provider replaces Laravel's own default with + `Paginator::defaultView('pagination::material')` (config `livewire-material.pagination`). The + drawing is resources/css/components/pagination.css. --}} @if ($paginator->hasPages())
    diff --git a/tests/Pest.php b/tests/Pest.php index 938942c4..44aa1943 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -62,7 +62,7 @@ if (($timeout = (int) getenv('BROWSER_TIMEOUT')) > 0) { /** * The named block of resources/css/all.css — the content between its `/* Heading *\/` comment and * the next one, or the end of the file for the last block ("Navigation"). components.css and - * layout.css grouped their imports the same way before plan step 37 folded both into all.css; this + * layout.css grouped their imports the same way before all.css folded both together; this * is the one place that block lookup lives now, so a stylesheet test asks "is this imported from * the Containment block" without repeating the search in every file that needs it. It is here, * rather than in tests/Feature/StylesheetsTest.php, because Pest.php is always loaded, whichever diff --git a/tests/Support/ViewClasses.php b/tests/Support/ViewClasses.php index 7d778721..e4907147 100644 --- a/tests/Support/ViewClasses.php +++ b/tests/Support/ViewClasses.php @@ -3,11 +3,10 @@ namespace NoNameWeb\LivewireMaterial\Tests\Support; /** - * The classes a package view rewritten without Tailwind may write (plan step 36; the brief's - * "Interaction is the shared classes"): the three interaction classes from - * foundation/interaction.css, the fixed text classes resources/css/text.css defines (plan step 34, - * "The component views use the same set"), and a caller's own class handed on whole. Everything - * else a view draws comes from its `data-md-*` hooks. + * The classes a package view may write: the three interaction classes from + * foundation/interaction.css, the fixed text classes resources/css/text.css defines, and a + * caller's own class handed on whole. Everything else a view draws comes from its `data-md-*` + * hooks. * * One rule for every group's stylesheet test, so the groups cannot drift apart. */ diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php index 55f13d10..84e6f83c 100644 --- a/workbench/app/Providers/WorkbenchServiceProvider.php +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -19,8 +19,7 @@ class WorkbenchServiceProvider extends ServiceProvider public function boot(): void { config(['livewire-material.showcase.vite' => [ - // The package's whole CSS in the Workbench's one entry now that Tailwind is gone - // (plan step 39), and the JavaScript. + // The package's whole CSS in the Workbench's one entry, and the JavaScript. 'workbench/resources/css/app.css', 'workbench/resources/js/app.js', ]]); diff --git a/workbench/resources/css/app.css b/workbench/resources/css/app.css index 0c4e5a72..9970b1bc 100644 --- a/workbench/resources/css/app.css +++ b/workbench/resources/css/app.css @@ -1,15 +1,13 @@ -/* The package's CSS for the Workbench, built the way an application without Tailwind builds it - (plan step 39: Tailwind leaves the whole stack): all.css (the foundation, every layout and every - component stylesheet), showcase.css (the showcase's own chrome, so a real Vite build — an error - page's fallback-free path, a browser-test probe page's `@vite()` — carries it too, even though - the showcase's own pages take their CSS from the bundle route instead) and the Workbench's - scheme, in the one CSS entry the Workbench now needs. Vite's own CSS pipeline inlines the - imports here and keeps a stylesheet several components import once, at its first position — the - order an application gets. +/* The package's CSS for the Workbench, built the way an application builds it: all.css (the + foundation, every layout and every component stylesheet), showcase.css (the showcase's own + chrome, so a real Vite build — an error page's fallback-free path, a browser-test probe page's + `@vite()` — carries it too, even though the showcase's own pages take their CSS from the bundle + route instead) and the Workbench's scheme, in the one CSS entry the Workbench needs. Vite's own + CSS pipeline inlines the imports here and keeps a stylesheet several components import once, at + its first position — the order an application gets. Opens with the same layer statement every package stylesheet does, so the `material` layers hold - their place whichever file a page links first; an application with no Tailwind of its own needs - nothing more than this. */ + their place whichever file a page links first; an application needs nothing more than this. */ @layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; @import '../../../resources/css/all.css'; @import '../../../resources/css/showcase.css';