Delete Tailwind's half and build the Workbench without it

Plan step 39 (parts 1-3, folded into one commit: PHP tests read the
deleted files directly, so they cannot land apart from it). Tailwind
leaves the whole stack:

- Delete resources/css/tailwind.css, tokens/theme.css and
  tokens/utilities.css. Neither token file declared an --md-sys-*
  custom property of its own (both only referenced tokens declared
  elsewhere), so nothing loses a value; the md-* interaction and text
  classes already mirror utilities.css's declarations exactly
  (foundation/interaction.css, text.css).
- npm uninstall tailwindcss @tailwindcss/vite; vite.config.js drops the
  plugin and its import; composer.json drops the tailwindcss keyword
  (no lock change — keywords are outside Composer's content hash).
- The Workbench now builds one CSS entry, workbench/resources/css/app.css
  (all.css, showcase.css and the scheme; package.css is folded in and
  removed) instead of two, used by ErrorPage::assets() and every
  browser-test probe page's raw @vite() call; the showcase's own pages
  still take their CSS from the bundle route.
- TokensTest and StylesheetsTest: the two facts theme.css and
  utilities.css carried (every scheme role becomes a colour, resolved
  on the element; md-type-* matches the type-* utilities) are asserted
  directly against the scheme and text.css now that there is no second
  copy to cross-check; StylesheetsTest gained a full-tree scan (every
  .css file under resources/css/ is reached from all.css or
  showcase.css, no exclusions left for Tailwind); the Workbench-entry
  test and every "moved out of tailwind.css" assertion updated for the
  single entry and its removal.
- DesignGuard.php's comments and the development skill's setup section
  no longer name the deleted files or a second Tailwind entry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-15 06:49:03 +02:00
co-authored by Claude Sonnet 5
parent cfc57a9867
commit f21943746e
24 changed files with 109 additions and 1310 deletions
+1 -2
View File
@@ -11,8 +11,7 @@
"components",
"material",
"material-design",
"material-3",
"tailwindcss"
"material-3"
],
"homepage": "https://gitea.nonameweb.ch/noNameWEB/livewire-material",
"authors": [
-671
View File
@@ -6,12 +6,10 @@
"": {
"devDependencies": {
"@material/material-color-utilities": "^0.4.0",
"@tailwindcss/vite": "^4.3.3",
"esbuild": "^0.28.2",
"fontkit": "^2.0.4",
"laravel-vite-plugin": "^3.2.0",
"playwright": "^1.62.1",
"tailwindcss": "^4.3.3",
"vite": "^8.2.2"
}
},
@@ -457,56 +455,6 @@
"node": ">=18"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
"integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@material/material-color-utilities": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.4.0.tgz",
@@ -814,290 +762,6 @@
"tslib": "^2.8.0"
}
},
"node_modules/@tailwindcss/node": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
"integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
"enhanced-resolve": "^5.24.1",
"jiti": "^2.7.0",
"lightningcss": "1.32.0",
"magic-string": "^0.30.21",
"source-map-js": "^1.2.1",
"tailwindcss": "4.3.3"
}
},
"node_modules/@tailwindcss/oxide": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
"integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 20"
},
"optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.3.3",
"@tailwindcss/oxide-darwin-arm64": "4.3.3",
"@tailwindcss/oxide-darwin-x64": "4.3.3",
"@tailwindcss/oxide-freebsd-x64": "4.3.3",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3",
"@tailwindcss/oxide-linux-arm64-gnu": "4.3.3",
"@tailwindcss/oxide-linux-arm64-musl": "4.3.3",
"@tailwindcss/oxide-linux-x64-gnu": "4.3.3",
"@tailwindcss/oxide-linux-x64-musl": "4.3.3",
"@tailwindcss/oxide-wasm32-wasi": "4.3.3",
"@tailwindcss/oxide-win32-arm64-msvc": "4.3.3",
"@tailwindcss/oxide-win32-x64-msvc": "4.3.3"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz",
"integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz",
"integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz",
"integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz",
"integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz",
"integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz",
"integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz",
"integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz",
"integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz",
"integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz",
"integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
"@emnapi/runtime",
"@tybys/wasm-util",
"@emnapi/wasi-threads",
"tslib"
],
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.11.1",
"@emnapi/runtime": "^1.11.1",
"@emnapi/wasi-threads": "^1.2.2",
"@napi-rs/wasm-runtime": "^1.1.4",
"@tybys/wasm-util": "^0.10.2",
"tslib": "^2.8.1"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
"integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz",
"integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/vite": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz",
"integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@tailwindcss/node": "4.3.3",
"@tailwindcss/oxide": "4.3.3",
"tailwindcss": "4.3.3"
},
"peerDependencies": {
"vite": "^5.2.0 || ^6 || ^7 || ^8"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -1156,20 +820,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/enhanced-resolve": {
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.0.tgz",
"integrity": "sha512-ghq3mhs649mvbarTCAlZn2wRhbfHmzAFiKxoWA14B3VtqnxtZt+wz8BroKXU0tF3GiJsnUldjVncQGZ8qk4rdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.3.3"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/esbuild": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
@@ -1270,23 +920,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/jiti": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/laravel-vite-plugin": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-3.2.0.tgz",
@@ -1314,289 +947,6 @@
}
}
},
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-android-arm64": "1.32.0",
"lightningcss-darwin-arm64": "1.32.0",
"lightningcss-darwin-x64": "1.32.0",
"lightningcss-freebsd-x64": "1.32.0",
"lightningcss-linux-arm-gnueabihf": "1.32.0",
"lightningcss-linux-arm64-gnu": "1.32.0",
"lightningcss-linux-arm64-musl": "1.32.0",
"lightningcss-linux-x64-gnu": "1.32.0",
"lightningcss-linux-x64-musl": "1.32.0",
"lightningcss-win32-arm64-msvc": "1.32.0",
"lightningcss-win32-x64-msvc": "1.32.0"
}
},
"node_modules/lightningcss-android-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/nanoid": {
"version": "3.3.19",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
@@ -1752,27 +1102,6 @@
"node": ">=0.10.0"
}
},
"node_modules/tailwindcss": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
"integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
"dev": true,
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
-2
View File
@@ -13,12 +13,10 @@
},
"devDependencies": {
"@material/material-color-utilities": "^0.4.0",
"@tailwindcss/vite": "^4.3.3",
"esbuild": "^0.28.2",
"fontkit": "^2.0.4",
"laravel-vite-plugin": "^3.2.0",
"playwright": "^1.62.1",
"tailwindcss": "^4.3.3",
"vite": "^8.2.2"
}
}
@@ -24,8 +24,6 @@ Composer packages must be installed before the Vite build (in Dockerfiles and CI
`foundation.css` is required and first; then one stylesheet per component the views render, under `resources/css/components/` or `resources/css/layout/`, named like the component (`<x-tab>` is drawn by `tabs.css`, and `<x-theme-script>` needs none). Each imports the stylesheets of the components it draws (a split button's button and menu), and Vite keeps a file several of them import once. `resources/css/all.css` stands for the foundation and every component at once. A component rendered without its stylesheet is unstyled, so add the import with the tag.
While the application's own views still write Tailwind classes, Tailwind goes in a second entry, never beside these imports (`@tailwindcss/vite` would repeat every shared stylesheet): `@import 'tailwindcss'`, `resources/css/tailwind.css` and the two `@source` lines for `resources/views` and `src`, with `@layer properties, theme, base, material, components, utilities;` at the top of both entries.
```js
// resources/js/app.js
import '../../vendor/nonameweb/livewire-material/resources/js/material.js'
-22
View File
@@ -1,22 +0,0 @@
/*
* Livewire Material's Tailwind half: the theme that turns the tokens into utilities and the token
* utilities themselves (`state-layer`, `focus-ring`, `touch-target`, `link`, `type-*`), for a view
* that still writes Tailwind classes — the showcase until step 38, and an application's own views
* until it drops Tailwind. Every component stylesheet is plain CSS (plan step 36) and needs
* nothing from here. This file goes when the last Tailwind class leaves (step 39).
*
* It carries no tokens, so it builds beside the package's CSS without declaring them twice, in an
* entry of its own — `@tailwindcss/vite` inlines the imports of an entry that uses Tailwind without
* deduplicating them, so the package's stylesheets stay in another (the Workbench's package.css).
* The Workbench's Tailwind entry, like an application's:
*
* @layer properties, theme, base, material, components, utilities;
* @import 'tailwindcss' source(none);
* @import '../../../resources/css/tailwind.css';
*
* with the same layer statement at the top of the package's entry. It replaces nothing of the 1.x
* `material.css` shortcut, which is gone: the components come from `all.css` or their own files.
*/
@import './tokens/theme.css';
@import './tokens/utilities.css';
-179
View File
@@ -1,179 +0,0 @@
/*
* The Tailwind theme: every token as a utility.
*
* `--color-*: initial` clears Tailwind's default palette, so every colour a template can name
* is an M3 role (bg-primary, text-on-surface-variant, border-outline-variant…) and a
* `text-red-600` compiles to nothing. White and black come back as the two absolutes. An
* application that wants more colours adds them in its own @theme after importing this file.
*
* The colour blocks are `@theme inline`: `bg-primary` compiles to
* `background-color: var(--md-sys-color-primary)`, resolved on the element itself. Without
* `inline` it compiles to `var(--color-primary)`, a variable computed once on :root — the
* page-wide theme still switches, but a `data-theme="dark"` section inside a light page keeps
* the light colours. Only blocks whose values are variables may be inline; a literal value in
* an inline block would be copied into every utility and lose its variable.
*/
@theme {
--color-*: initial;
--color-white: #ffffff;
--color-black: #000000;
--font-sans: var(--md-ref-typeface-brand);
}
/*
* Breakpoints are M3's window size classes, and only those: compact is below `medium`, then
* medium 600, expanded 840, large 1200 and extra-large 1600 (`medium:`, `expanded:`, `large:`,
* `extra-large:`, and `max-medium:` … for "below"). Tailwind's sm/md/lg/xl/2xl are cleared —
* a `sm:` compiles to nothing — because a layout decision keyed on 640px means nothing in M3.
* resources/js/breakpoints.js carries the same four numbers for scripts.
*/
@theme {
--breakpoint-*: initial;
--breakpoint-medium: 600px;
--breakpoint-expanded: 840px;
--breakpoint-large: 1200px;
--breakpoint-extra-large: 1600px;
}
/*
* Only M3's scales compile. Tailwind's default radius, shadow, text-size, weight, leading,
* tracking and easing utilities are cleared like its palette, so `rounded-lg`, `text-sm`,
* `font-medium`, `shadow-md` or `ease-in-out` compile to nothing; the M3 sets are declared
* below (`rounded-corner-*`, `shadow-elevation-*`, `type-*`, `ease-spatial-*`…). The 4px
* spacing scale stays: it is M3's spacing grid.
*/
@theme {
--radius-*: initial;
--shadow-*: initial;
--inset-shadow-*: initial;
--drop-shadow-*: initial;
--text-*: initial;
--font-weight-*: initial;
--leading-*: initial;
--tracking-*: initial;
--ease-*: initial;
}
/* The colour roles, as generated by `php artisan material:scheme`. */
@theme inline {
--color-background: var(--md-sys-color-background);
--color-on-background: var(--md-sys-color-on-background);
--color-surface: var(--md-sys-color-surface);
--color-surface-dim: var(--md-sys-color-surface-dim);
--color-surface-bright: var(--md-sys-color-surface-bright);
--color-surface-container-lowest: var(--md-sys-color-surface-container-lowest);
--color-surface-container-low: var(--md-sys-color-surface-container-low);
--color-surface-container: var(--md-sys-color-surface-container);
--color-surface-container-high: var(--md-sys-color-surface-container-high);
--color-surface-container-highest: var(--md-sys-color-surface-container-highest);
--color-on-surface: var(--md-sys-color-on-surface);
--color-on-surface-variant: var(--md-sys-color-on-surface-variant);
--color-outline: var(--md-sys-color-outline);
--color-outline-variant: var(--md-sys-color-outline-variant);
--color-inverse-surface: var(--md-sys-color-inverse-surface);
--color-inverse-on-surface: var(--md-sys-color-inverse-on-surface);
--color-primary: var(--md-sys-color-primary);
--color-primary-dim: var(--md-sys-color-primary-dim);
--color-on-primary: var(--md-sys-color-on-primary);
--color-primary-container: var(--md-sys-color-primary-container);
--color-on-primary-container: var(--md-sys-color-on-primary-container);
--color-primary-fixed: var(--md-sys-color-primary-fixed);
--color-primary-fixed-dim: var(--md-sys-color-primary-fixed-dim);
--color-on-primary-fixed: var(--md-sys-color-on-primary-fixed);
--color-on-primary-fixed-variant: var(--md-sys-color-on-primary-fixed-variant);
--color-inverse-primary: var(--md-sys-color-inverse-primary);
--color-secondary: var(--md-sys-color-secondary);
--color-secondary-dim: var(--md-sys-color-secondary-dim);
--color-on-secondary: var(--md-sys-color-on-secondary);
--color-secondary-container: var(--md-sys-color-secondary-container);
--color-on-secondary-container: var(--md-sys-color-on-secondary-container);
--color-secondary-fixed: var(--md-sys-color-secondary-fixed);
--color-secondary-fixed-dim: var(--md-sys-color-secondary-fixed-dim);
--color-on-secondary-fixed: var(--md-sys-color-on-secondary-fixed);
--color-on-secondary-fixed-variant: var(--md-sys-color-on-secondary-fixed-variant);
--color-tertiary: var(--md-sys-color-tertiary);
--color-tertiary-dim: var(--md-sys-color-tertiary-dim);
--color-on-tertiary: var(--md-sys-color-on-tertiary);
--color-tertiary-container: var(--md-sys-color-tertiary-container);
--color-on-tertiary-container: var(--md-sys-color-on-tertiary-container);
--color-tertiary-fixed: var(--md-sys-color-tertiary-fixed);
--color-tertiary-fixed-dim: var(--md-sys-color-tertiary-fixed-dim);
--color-on-tertiary-fixed: var(--md-sys-color-on-tertiary-fixed);
--color-on-tertiary-fixed-variant: var(--md-sys-color-on-tertiary-fixed-variant);
--color-error: var(--md-sys-color-error);
--color-error-dim: var(--md-sys-color-error-dim);
--color-on-error: var(--md-sys-color-on-error);
--color-error-container: var(--md-sys-color-error-container);
--color-on-error-container: var(--md-sys-color-on-error-container);
--color-success: var(--md-sys-color-success);
--color-on-success: var(--md-sys-color-on-success);
--color-success-container: var(--md-sys-color-success-container);
--color-on-success-container: var(--md-sys-color-on-success-container);
--color-warning: var(--md-sys-color-warning);
--color-on-warning: var(--md-sys-color-on-warning);
--color-warning-container: var(--md-sys-color-warning-container);
--color-on-warning-container: var(--md-sys-color-on-warning-container);
--color-info: var(--md-sys-color-info);
--color-on-info: var(--md-sys-color-on-info);
--color-info-container: var(--md-sys-color-info-container);
--color-on-info-container: var(--md-sys-color-on-info-container);
--color-inverse-error: var(--md-sys-color-inverse-error);
--color-inverse-success: var(--md-sys-color-inverse-success);
--color-inverse-warning: var(--md-sys-color-inverse-warning);
--color-inverse-info: var(--md-sys-color-inverse-info);
--color-shadow: var(--md-sys-color-shadow);
--color-scrim: var(--md-sys-color-scrim);
}
/*
* Ink and lines by what they are: body copy, metadata, decoration; a structural line, a line
* around chrome, a divider. Each is an M3 role, never an opacity: M3 marks emphasis with
* on-surface-variant and outline, and reserves 38% for disabled content, so an ink dimmed by
* opacity reads as disabled and loses its contrast guarantee.
*/
@theme inline {
--color-body: var(--md-sys-color-on-surface-variant);
--color-meta: var(--md-sys-color-on-surface-variant);
--color-quiet: var(--md-sys-color-outline);
--color-structure: var(--md-sys-color-outline-variant);
--color-chrome: var(--md-sys-color-outline-variant);
--color-divider: var(--md-sys-color-outline-variant);
}
@theme {
--radius-corner-none: var(--md-sys-shape-corner-none);
--radius-corner-xs: var(--md-sys-shape-corner-xs);
--radius-corner-sm: var(--md-sys-shape-corner-sm);
--radius-corner-md: var(--md-sys-shape-corner-md);
--radius-corner-lg: var(--md-sys-shape-corner-lg);
--radius-corner-lg-increased: var(--md-sys-shape-corner-lg-increased);
--radius-corner-xl: var(--md-sys-shape-corner-xl);
--radius-corner-xl-increased: var(--md-sys-shape-corner-xl-increased);
--radius-corner-xxl: var(--md-sys-shape-corner-xxl);
--radius-corner-full: var(--md-sys-shape-corner-full);
--shadow-elevation-1: var(--md-sys-elevation-1);
--shadow-elevation-2: var(--md-sys-elevation-2);
--shadow-elevation-3: var(--md-sys-elevation-3);
--shadow-elevation-4: var(--md-sys-elevation-4);
--shadow-elevation-5: var(--md-sys-elevation-5);
--ease-spatial-fast: var(--md-sys-motion-spatial-fast);
--ease-spatial-default: var(--md-sys-motion-spatial-default);
--ease-spatial-slow: var(--md-sys-motion-spatial-slow);
--ease-effects-fast: var(--md-sys-motion-effects-fast);
--ease-effects-default: var(--md-sys-motion-effects-default);
--ease-effects-slow: var(--md-sys-motion-effects-slow);
--ease-standard: var(--md-sys-motion-easing-standard);
--ease-emphasized: var(--md-sys-motion-easing-emphasized);
--ease-emphasized-decelerate: var(--md-sys-motion-easing-emphasized-decelerate);
--ease-emphasized-accelerate: var(--md-sys-motion-easing-emphasized-accelerate);
}
/*
* `dark:` follows the page's theme, not the operating system: <x-theme-script> has already
* resolved `system` into data-theme, and the OS was never meant to be a second voice.
*/
@custom-variant dark (&:where([data-theme='dark'], [data-theme='dark'] *));
-286
View File
@@ -1,286 +0,0 @@
/*
* The token utilities, for the components still written in Tailwind.
*
* Tailwind's side of tokens/state.css and tokens/type.css, kept apart so that the token files the
* foundation imports carry no Tailwind directive. tailwind.css imports this file after Tailwind;
* it goes when the last component leaves Tailwind. New markup uses the plain-CSS classes instead,
* which carry the same declarations: `md-state-layer`, `md-focus-ring`, `md-touch-target` and
* `md-link` (foundation/interaction.css), and `md-type-*` (text.css).
*
* `state-layer` puts M3's state layer on an element. Hover only where the pointer can hover,
* because a touch screen keeps the last hover after a tap; `data-dragged` on the element draws
* the dragged layer. The layer is a ::before, so the element becomes `position: relative` and
* `isolation: isolate`; never pass `static` or a z-index to it.
*
* `touch-target` extends an element's pointer target to M3's 48×48px minimum without changing
* what is drawn: a ::after centred on it, at least 48px each way. It is `::after`-based, so a
* component that already draws with ::after wraps its control in an element carrying it.
*
* `focus-ring` is M3's focus indicator — 3px in secondary, 2px out, drawn only for keyboard
* focus. The site gives no thickness or offset; the values are @material/web's focus ring
* tokens (Apache-2.0, © Google LLC). `link` is a link in running text: underlined in the
* colour of the words around it.
*
* `type-*` is one of M3's 30 type styles (tokens/type.css): size, line height, weight and
* tracking together. M3 asks for emphasis one element at a time, and `font-variation-settings`
* inherits, so every regular utility resets it: body copy inside an emphasized heading reads as
* itself again.
*/
@utility state-layer {
position: relative;
isolation: isolate;
&::before {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
background-color: currentColor;
opacity: 0;
pointer-events: none;
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
}
@media (hover: hover) {
&:hover::before {
opacity: var(--md-sys-state-hover-state-layer-opacity);
}
}
&:focus-visible::before {
opacity: var(--md-sys-state-focus-state-layer-opacity);
}
&:active::before {
opacity: var(--md-sys-state-pressed-state-layer-opacity);
}
&[data-dragged]::before {
opacity: var(--md-sys-state-dragged-state-layer-opacity);
}
&:disabled::before,
&[aria-disabled='true']::before {
display: none;
}
}
@utility touch-target {
position: relative;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
min-width: 3rem;
min-height: 3rem;
transform: translate(-50%, -50%);
}
}
@utility focus-ring {
outline: none;
&:focus-visible {
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: 2px;
}
}
@utility link {
cursor: pointer;
text-decoration-line: underline;
text-decoration-thickness: from-font;
text-underline-offset: 0.15em;
border-radius: var(--md-sys-shape-corner-xs);
&:focus-visible {
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: 2px;
}
}
@utility type-display-lg {
font: var(--md-sys-typescale-display-lg);
letter-spacing: var(--md-sys-typescale-display-lg-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-display-lg {
font: var(--md-sys-typescale-emphasized-display-lg);
letter-spacing: var(--md-sys-typescale-emphasized-display-lg-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-display-md {
font: var(--md-sys-typescale-display-md);
letter-spacing: var(--md-sys-typescale-display-md-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-display-md {
font: var(--md-sys-typescale-emphasized-display-md);
letter-spacing: var(--md-sys-typescale-emphasized-display-md-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-display-sm {
font: var(--md-sys-typescale-display-sm);
letter-spacing: var(--md-sys-typescale-display-sm-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-display-sm {
font: var(--md-sys-typescale-emphasized-display-sm);
letter-spacing: var(--md-sys-typescale-emphasized-display-sm-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-headline-lg {
font: var(--md-sys-typescale-headline-lg);
letter-spacing: var(--md-sys-typescale-headline-lg-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-headline-lg {
font: var(--md-sys-typescale-emphasized-headline-lg);
letter-spacing: var(--md-sys-typescale-emphasized-headline-lg-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-headline-md {
font: var(--md-sys-typescale-headline-md);
letter-spacing: var(--md-sys-typescale-headline-md-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-headline-md {
font: var(--md-sys-typescale-emphasized-headline-md);
letter-spacing: var(--md-sys-typescale-emphasized-headline-md-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-headline-sm {
font: var(--md-sys-typescale-headline-sm);
letter-spacing: var(--md-sys-typescale-headline-sm-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-headline-sm {
font: var(--md-sys-typescale-emphasized-headline-sm);
letter-spacing: var(--md-sys-typescale-emphasized-headline-sm-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-title-lg {
font: var(--md-sys-typescale-title-lg);
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-title-lg {
font: var(--md-sys-typescale-emphasized-title-lg);
letter-spacing: var(--md-sys-typescale-emphasized-title-lg-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-title-md {
font: var(--md-sys-typescale-title-md);
letter-spacing: var(--md-sys-typescale-title-md-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-title-md {
font: var(--md-sys-typescale-emphasized-title-md);
letter-spacing: var(--md-sys-typescale-emphasized-title-md-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-title-sm {
font: var(--md-sys-typescale-title-sm);
letter-spacing: var(--md-sys-typescale-title-sm-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-title-sm {
font: var(--md-sys-typescale-emphasized-title-sm);
letter-spacing: var(--md-sys-typescale-emphasized-title-sm-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-body-lg {
font: var(--md-sys-typescale-body-lg);
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-body-lg {
font: var(--md-sys-typescale-emphasized-body-lg);
letter-spacing: var(--md-sys-typescale-emphasized-body-lg-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-body-md {
font: var(--md-sys-typescale-body-md);
letter-spacing: var(--md-sys-typescale-body-md-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-body-md {
font: var(--md-sys-typescale-emphasized-body-md);
letter-spacing: var(--md-sys-typescale-emphasized-body-md-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-body-sm {
font: var(--md-sys-typescale-body-sm);
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-body-sm {
font: var(--md-sys-typescale-emphasized-body-sm);
letter-spacing: var(--md-sys-typescale-emphasized-body-sm-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-label-lg {
font: var(--md-sys-typescale-label-lg);
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-label-lg {
font: var(--md-sys-typescale-emphasized-label-lg);
letter-spacing: var(--md-sys-typescale-emphasized-label-lg-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-label-md {
font: var(--md-sys-typescale-label-md);
letter-spacing: var(--md-sys-typescale-label-md-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-label-md {
font: var(--md-sys-typescale-emphasized-label-md);
letter-spacing: var(--md-sys-typescale-emphasized-label-md-tracking);
font-variation-settings: "ROND" 100;
}
@utility type-label-sm {
font: var(--md-sys-typescale-label-sm);
letter-spacing: var(--md-sys-typescale-label-sm-tracking);
font-variation-settings: normal;
}
@utility type-emphasized-label-sm {
font: var(--md-sys-typescale-emphasized-label-sm);
letter-spacing: var(--md-sys-typescale-emphasized-label-sm-tracking);
font-variation-settings: "ROND" 100;
}
+8 -8
View File
@@ -10,9 +10,9 @@ use Symfony\Component\Finder\Finder;
* Finds what compiles to nothing and fails silently: maryUI tags, daisyUI classes, colours the
* theme does not declare, icon names that are not Material Symbols, Tailwind's breakpoint
* prefixes and the radius, shadow, type-size, weight, leading, tracking, easing and duration
* scales that tokens/theme.css clears for M3's own, and colours written as a literal value
* instead of a role — plus whatever an application bans on top (roles its own design rules
* leave out, the two absolutes, opacity used as emphasis, patterns it has retired).
* scales M3's own sets replace, and colours written as a literal value instead of a role — plus
* whatever an application bans on top (roles its own design rules leave out, the two absolutes,
* opacity used as emphasis, patterns it has retired).
*
* expect(DesignGuard::scan([resource_path('views'), resource_path('js'), app_path()])
* ->forbidColours(['tertiary', 'primary-container'])
@@ -52,10 +52,10 @@ class DesignGuard
protected const string OPACITY_INK = '/(?<![\w-])(?:bg|text|border(?:-[trblxyse])?)-[a-z][a-z\d-]*\/\d{1,3}(?![\w-])/';
/**
* Tailwind's breakpoint prefixes, cleared by tokens/theme.css, and the M3 window size class
* that replaces each. Tailwind's 640/768/1024/1280/1536 are 4088 px from M3's 600/840/1200/
* 1600, so a prefix maps to the class that carries the same intent, never to the same pixel:
* both of its phone breakpoints are M3's medium.
* Tailwind's breakpoint prefixes and the M3 window size class that replaces each. Tailwind's
* 640/768/1024/1280/1536 are 4088 px from M3's 600/840/1200/1600, so a prefix maps to the
* class that carries the same intent, never to the same pixel: both of its phone breakpoints
* are M3's medium.
*/
protected const array WINDOW_CLASSES = [
'sm' => 'medium',
@@ -293,7 +293,7 @@ class DesignGuard
/**
* The radius, shadow, type-size, weight, leading, tracking, easing and duration utilities
* Tailwind ships and tokens/theme.css clears.
* Tailwind ships and M3's own scales replace.
*
* @return list<string>
*/
@@ -61,8 +61,8 @@ it('imports the stylesheet of every component its view renders', function (strin
preg_match_all('/<x-livewire-material::([a-z-]+)/', File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php"), $tags);
$rendered = collect($tags[1])->unique()
// A component still written for Tailwind has no stylesheet of its own to import yet; its
// old stylesheet, if it has one, lives in tailwind.css without the layer statement.
// A rendered tag that is not a components/ stylesheet at all (a layout component's, e.g.
// <x-pane>) has no import of its own to check here.
->filter(fn (string $tag): bool => is_file(ComponentStylesheet::path($tag)) && str_contains(File::get(ComponentStylesheet::path($tag)), '@layer material.components'))
->map(fn (string $tag): string => "./{$tag}.css")
->values()
@@ -90,8 +90,8 @@ it('imports the stylesheet of every component its view renders', function (strin
preg_match_all('/<x-livewire-material::([a-z-]+)/', File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php"), $tags);
$rendered = collect($tags[1])->unique()
// A component still written for Tailwind has no stylesheet of its own to import yet; its
// old stylesheet, if it has one, lives in tailwind.css without the layer statement.
// A rendered tag that is not a components/ stylesheet at all (a layout component's, e.g.
// <x-pane>) has no import of its own to check here.
->filter(fn (string $tag): bool => is_file(ComponentStylesheet::path($tag)) && str_contains(File::get(ComponentStylesheet::path($tag)), '@layer material.components'))
->map(fn (string $tag): string => "./{$tag}.css")
->values()
+1 -2
View File
@@ -26,8 +26,7 @@ it('draws 52px rows, 36px dense ones, from its stylesheet in the components laye
->toMatch('/\\[data-md-table\\] \\{\\s*--cell-x: 12px;\\s*--cell-y: var\\(--md-sys-measurement-space200\\);/')
->toMatch('/\\[data-md-table\\]\\[data-md-dense\\] \\{\\s*--cell-y: var\\(--md-sys-measurement-space100\\);/')
->toContain('[data-md-table] :where(tbody tr[aria-selected=\'true\'])')
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/table.css';")
->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('table.css');
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/table.css';");
});
it('sorts by its column, ascending first and then flipping', function () {
+1 -2
View File
@@ -235,8 +235,7 @@ it('keeps the field\'s chrome in its own stylesheet in the components layer', fu
->toContain("@import './icon.css';")
->toContain('[data-md-field-box]')
->not->toMatch('/(?<![\\w-])\\.field\\b/')
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/field.css';")
->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('field.css');
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/field.css';");
});
it('takes a custom control marked as the field\'s control, and a hint class', function () {
@@ -136,8 +136,8 @@ it('imports the stylesheet of every component its view renders', function (strin
preg_match_all('/<x-livewire-material::([a-z-]+)/', File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php"), $tags);
$rendered = collect($tags[1])->unique()
// A component still written for Tailwind has no stylesheet of its own to import yet; its
// old stylesheet, if it has one, lives in tailwind.css without the layer statement.
// A rendered tag that is not a components/ stylesheet at all (a layout component's, e.g.
// <x-pane>) has no import of its own to check here.
->filter(fn (string $tag): bool => is_file(ComponentStylesheet::path($tag)) && str_contains(File::get(ComponentStylesheet::path($tag)), '@layer material.components'))
->map(fn (string $tag): string => "./{$tag}.css")
->values()
@@ -193,6 +193,8 @@ it('imports the stylesheet of every component pagination\'s views render', funct
preg_match_all('/<x-livewire-material::([a-z-]+)/', File::get(__DIR__."/../../../resources/views/{$view}.blade.php"), $tags);
$rendered = collect($tags[1])->unique()
// A rendered tag that is not a components/ stylesheet at all (a layout component's, e.g.
// <x-pane>) has no import of its own to check here.
->filter(fn (string $tag): bool => is_file(ComponentStylesheet::path($tag)) && str_contains(File::get(ComponentStylesheet::path($tag)), '@layer material.components'))
->map(fn (string $tag): string => "./{$tag}.css")
->values()
@@ -128,8 +128,8 @@ it('imports the stylesheet of every component its view renders', function (strin
return $tags[1];
})
->unique()
// A component still written for Tailwind has no stylesheet of its own to import yet; its
// old stylesheet, if it has one, lives in tailwind.css without the layer statement.
// A rendered tag that is not a components/ stylesheet at all (a layout component's, e.g.
// <x-pane>) has no import of its own to check here.
->filter(fn (string $tag): bool => is_file(ComponentStylesheet::path($tag)) && str_contains(File::get(ComponentStylesheet::path($tag)), '@layer material.components'))
->map(fn (string $tag): string => "./{$tag}.css")
->values()
-1
View File
@@ -101,7 +101,6 @@ it('divides a scrolling body from its header and actions only while content is h
// A token that reduced motion sets to zero, so there the rule simply appears.
->toContain('transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);')
->and(File::get(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/modal.css';")
->and(File::get(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('dialog.css')
->and(File::get(__DIR__.'/../../../resources/js/material.js'))->toContain("import './dialog.js'")
->and(File::get(__DIR__.'/../../../resources/js/dialog.js'))
->toContain("directive('dialog-dividers'")
+1 -2
View File
@@ -114,6 +114,5 @@ it('draws the bar and the view from its stylesheet, at M3\'s widths', function (
->toContain('[data-md-search][data-md-full-screen] [data-md-search-view] {')
->toContain("[data-md-search][data-md-trigger='icon']:not([data-md-open]) [data-md-search-bar] {")
->not->toMatch('/\\d(?:\\.\\d+)?rem/')
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/search.css';")
->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('search.css');
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/search.css';");
});
+1 -2
View File
@@ -151,8 +151,7 @@ it('draws the checkbox from its stylesheet, on the row the selection controls sh
->and((string) file_get_contents(__DIR__.'/../../../resources/css/components/selection.css'))
->toContain('[data-md-selection-row]')
->toContain('@layer material.components')
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/checkbox.css';")
->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('selection.css');
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/checkbox.css';");
});
it('lays the radio group out from its stylesheet, inline only from 600px', function () {
-2
View File
@@ -97,7 +97,6 @@ it('lets the application\'s own error view win', function () {
it('loads the application\'s Vite entries', function () {
File::ensureDirectoryExists($this->temporary.'/build');
File::put($this->temporary.'/build/manifest.json', json_encode([
'workbench/resources/css/package.css' => ['file' => 'assets/package-probe.css', 'src' => 'workbench/resources/css/package.css', 'isEntry' => true],
'workbench/resources/css/app.css' => ['file' => 'assets/app-probe.css', 'src' => 'workbench/resources/css/app.css', 'isEntry' => true],
'workbench/resources/js/app.js' => ['file' => 'assets/app-probe.js', 'src' => 'workbench/resources/js/app.js', 'isEntry' => true],
]));
@@ -107,7 +106,6 @@ it('loads the application\'s Vite entries', function () {
$this->get('/abort/404')
->assertNotFound()
->assertSee('build/assets/package-probe.css', false)
->assertSee('build/assets/app-probe.css', false)
->assertSee('build/assets/app-probe.js', false)
->assertDontSee('data-md-error-fallback', false);
+53 -68
View File
@@ -10,14 +10,13 @@ use NoNameWeb\LivewireMaterial\Support\Layout;
* would nest the layer twice); then its own `@layer material.<x>` blocks. The only rules outside
* a layer are foundation/hidden.css's two `!important` ones.
*
* Every package component and layout stylesheet is plain CSS now (plan step 36), so the shape,
* Tailwind-free and breakpoint checks below run over the whole tree `all.css` reaches (foundation,
* layout and components together) rather than the foundation alone everything but
* `tailwind.css` and `tokens/theme.css`/`tokens/utilities.css`, which stay Tailwind until plan
* step 39 and are simply never reached from `all.css`. A component's own values (colours as
* roles, shadows as elevation levels, and so on) are each group's own concern, in
* tests/Feature/Components/*StylesheetsTest.php; `bundle()`, the PHP bundler `all.css` is built
* for, is tests/Feature/StylesheetsBundleTest.php's.
* Every package component and layout stylesheet is plain CSS now (plan step 36), and Tailwind
* itself is gone from the package (plan step 39), so the shape, Tailwind-free and breakpoint checks
* below run over the whole tree `all.css` reaches (foundation, layout and components together)
* rather than the foundation alone, and one file it does not reach `showcase.css`, checked on
* its own further down. A component's own values (colours as roles, shadows as elevation levels,
* and so on) are each group's own concern, in tests/Feature/Components/*StylesheetsTest.php;
* `bundle()`, the PHP bundler `all.css` is built for, is tests/Feature/StylesheetsBundleTest.php's.
*/
const MATERIAL_LAYER_STATEMENT = '@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;';
@@ -178,9 +177,9 @@ function foundationStylesheets(): array
/**
* Everything all.css reaches: the foundation, every layout stylesheet and every component
* stylesheet, each once. What a check runs over now that every one of them is plain CSS (plan
* step 36) `tailwind.css` and its two token files are never in this tree, since all.css does
* not import them.
* stylesheet, each once. Every one of them is plain CSS (plan step 36), and Tailwind itself is
* gone from the package (plan step 39) `tailwindcss`, `tailwind.css` and its two token files no
* longer exist, so there is nothing left for this tree to exclude.
*
* @return list<string>
*/
@@ -194,7 +193,7 @@ function stylesheetName(string $file): string
return substr($file, strlen(stylesheetPath()) + 1);
}
it('brings every foundation file and text.css into the foundation, and nothing of Tailwind', function () {
it('brings every foundation file and text.css into the foundation, and nothing else', function () {
$foundation = array_map(stylesheetName(...), foundationStylesheets());
$parts = collect(File::files(stylesheetPath('foundation')))
@@ -207,11 +206,24 @@ it('brings every foundation file and text.css into the foundation, and nothing o
->toBe(['reset.css', 'hidden.css', 'tokens.css', 'base.css', 'interaction.css', 'text.css'])
->and($foundation)
->toContain('tokens/scheme.css', 'tokens/shape.css', 'tokens/elevation.css', 'tokens/motion.css', 'tokens/type.css', 'tokens/state.css', 'tokens/spacing.css', 'tokens/font.css')
// The Tailwind half: the theme, the token utilities and the components still written for it.
->and(array_values(array_intersect($foundation, ['tokens/theme.css', 'tokens/utilities.css', 'tailwind.css'])))->toBe([])
->and(array_values(preg_grep('/^components\//', $foundation)))->toBe([]);
});
it('leaves no stylesheet under resources/css/ outside all.css or showcase.css', function () {
// The full tree now that Tailwind is gone (plan step 39): every .css file on disk, not just the
// ones a bundler happens to reach — a stray or orphaned file would show up here even though
// nothing imports it, which the checks above (walking @import from all.css) cannot catch.
$onDisk = collect(File::allFiles(stylesheetPath()))
->filter(fn (SplFileInfo $file): bool => $file->getExtension() === 'css')
->map(fn (SplFileInfo $file): string => stylesheetName((string) $file->getRealPath()))
->sort()
->values();
$reached = collect(allStylesheets())->map(stylesheetName(...))->push('showcase.css')->sort()->values();
expect($onDisk->all())->toBe($reached->all());
});
it('opens every stylesheet all.css reaches with a header, the layer statement and plain imports', function () {
foreach (allStylesheets() as $file) {
$name = stylesheetName($file);
@@ -548,17 +560,19 @@ it('hides an element below and from every breakpoint but compact, and nothing el
expect($actual)->toBe($expected);
});
it('gives every md-type class the declarations of its type utility', function () {
preg_match_all('/@utility (type-[\w-]+) \{\n(.*?)\n\}/s', File::get(stylesheetPath('tokens/utilities.css')), $matches, PREG_SET_ORDER);
it('gives every md-type class exactly font, letter-spacing and font-variation-settings', function () {
// Tailwind's tokens/utilities.css used to declare the same 30 styles as `type-*` utility
// blocks, and this test proved text.css's `.md-type-*` classes matched them; deleted with
// Tailwind (plan step 39), text.css is the only copy left, so the shape it gives each class is
// asserted directly rather than cross-checked against a second copy.
preg_match_all('/\.md-(type-[\w-]+) \{\n(.*?)\n {4}\}/s', File::get(stylesheetPath('text.css')), $matches, PREG_SET_ORDER);
$utilities = collect($matches)->mapWithKeys(fn (array $match): array => ["md-{$match[1]}" => stylesheetDeclarations($match[2])]);
$text = File::get(stylesheetPath('text.css'));
$classes = collect($matches)->mapWithKeys(fn (array $match): array => ["md-{$match[1]}" => stylesheetDeclarations($match[2])]);
expect($utilities)->toHaveCount(30);
expect($classes)->toHaveCount(30);
foreach ($utilities as $class => $declarations) {
expect(stylesheetDeclarations(stylesheetBlock($text, ".{$class}")))->toBe($declarations, $class)
->and(array_keys($declarations))->toBe(['font', 'letter-spacing', 'font-variation-settings']);
foreach ($classes as $class => $declarations) {
expect(array_keys($declarations))->toBe(['font', 'letter-spacing', 'font-variation-settings'], $class);
}
});
@@ -611,57 +625,28 @@ it('ships exactly the documented text classes, each ink an M3 role', function ()
}
});
it('draws the interaction classes as the utilities the Tailwind components still use', function () {
$utilities = File::get(stylesheetPath('tokens/utilities.css'));
$interaction = File::get(stylesheetPath('foundation/interaction.css'));
// A test used to prove foundation/interaction.css's `.md-*` classes matched tokens/utilities.css's
// utility blocks byte for byte apart from two deliberate differences, because the components
// still written in Tailwind read the utilities while every other component read the classes.
// Deleted with Tailwind (plan step 39): every component reads the classes now, there is no second
// copy left to drift from, and interaction.css's own StylesheetsTest coverage above (the layer it
// writes into, no Tailwind directive) already holds it to the same shape every other foundation
// file keeps to.
$normalise = fn (string $body): string => trim((string) preg_replace('/\s+/', ' ', $body));
foreach (['state-layer', 'focus-ring', 'touch-target', 'link'] as $name) {
$class = stylesheetBlock($interaction, ".md-{$name}");
expect($normalise(str_replace(
// The two deliberate differences: the data-md-* hook in place of the old one, and M3's
// 48 CSS pixels for the target where the utility reads 3rem.
['&[data-md-dragged]', 'var(--md-sys-measurement-space600)'],
['&[data-dragged]', '3rem'],
$class,
)))->toBe($normalise(stylesheetBlock($utilities, "@utility {$name}")), $name);
}
expect(stylesheetBlock($interaction, '.md-state-layer'))->toContain('&[data-md-dragged]::before')
->not->toContain('[data-dragged]')
->and(stylesheetBlock($interaction, '.md-touch-target'))->toContain('min-width: var(--md-sys-measurement-space600);');
});
it('builds the package in a Workbench entry of its own, with the material layers between Tailwind\'s preflight and its utilities', function () {
$package = stylesheetWithoutComments(File::get(__DIR__.'/../../workbench/resources/css/package.css'));
it('builds the package in the Workbench\'s one CSS entry, with no Tailwind left to sit between', function () {
$app = stylesheetWithoutComments(File::get(__DIR__.'/../../workbench/resources/css/app.css'));
// Both entries open by ordering the layers, so the order holds whichever the page links first:
// above preflight, whose `* { padding: 0 }` would otherwise beat a component's padding, and
// below the utilities.
foreach (['package.css' => $package, 'app.css' => $app] as $name => $css) {
expect(trim($css))->toStartWith('@layer properties, theme, base, material, components, utilities;', $name);
}
// The package's CSS never shares an entry with Tailwind: `@tailwindcss/vite` inlines the
// imports of an entry that uses Tailwind without deduplicating them, so every shared component
// stylesheet would repeat later in the cascade instead of landing once, as it does for an
// application (StylesheetsBundleTest.php pins that against a real Vite build).
expect($package)->toContain("@import '../../../resources/css/all.css';")
// Opens by ordering the layers, so the order holds whichever the page links first — an
// application with Tailwind of its own still opens its own entry the same way (its header).
expect(trim($app))->toStartWith(MATERIAL_LAYER_STATEMENT)
->toContain("@import '../../../resources/css/all.css';")
->toContain("@import '../../../resources/css/showcase.css';")
->toContain("@import './material-scheme.css';")
->not->toContain('tailwind')
->not->toMatch('/@(?:source|theme|utility|variant|custom-variant|apply)\b/')
->and($app)->toContain("@import 'tailwindcss'")
->toContain("@import '../../../resources/css/tailwind.css';")
->not->toContain('all.css')
->not->toContain('foundation.css')
->not->toContain('/components/')
->not->toContain('material-scheme.css')
->and(File::get(__DIR__.'/../../vite.config.js'))->toContain("'workbench/resources/css/package.css', 'workbench/resources/css/app.css'")
->not->toMatch('/@(?:tailwind|source|theme|utility|variant|custom-variant|apply|config|plugin|reference)\b/')
->and(File::get(__DIR__.'/../../vite.config.js'))
->toContain("input: ['workbench/resources/css/app.css', 'workbench/resources/js/app.js']")
->not->toContain('tailwindcss')
->and(config('livewire-material.showcase.vite'))->toBe([
'workbench/resources/css/package.css',
'workbench/resources/css/app.css',
'workbench/resources/js/app.js',
]);
+15 -20
View File
@@ -37,14 +37,15 @@ function typeStyles(): array
}
/**
* The body of every `@utility type-…` block, keyed by utility name. The utilities live in
* tokens/utilities.css, apart from the tokens, so the foundation imports no Tailwind directive.
* The body of every `.md-type-…` class in text.css, keyed by class name without the `md-` prefix
* (`type-display-lg`, `type-emphasized-display-lg`, ). Plain CSS now (plan step 39 deleted the
* Tailwind utilities they used to be); text.css is theirs alone.
*
* @return array<string, string>
*/
function typeUtilities(): array
{
preg_match_all('/@utility (type-[\w-]+) \{\n(.*?)\n\}/s', File::get(packageCss('tokens/utilities.css')), $matches, PREG_SET_ORDER);
preg_match_all('/\.md-(type-[\w-]+) \{\n(.*?)\n {4}\}/s', File::get(packageCss('text.css')), $matches, PREG_SET_ORDER);
return array_combine(array_column($matches, 1), array_column($matches, 2));
}
@@ -84,26 +85,20 @@ it('ships M3\'s medium and high contrast levels beside the standard one', functi
}
});
it('turns every role into a Tailwind colour', function () {
$roles = array_keys(json_decode(File::get(packageCss('tokens/scheme.json')), true)['light']);
$theme = File::get(packageCss('tokens/theme.css'));
foreach ($roles as $role) {
expect($theme)->toContain("--color-{$role}: var(--md-sys-color-{$role});");
// Tailwind's theme.css used to turn every scheme role into a `--color-*` Tailwind colour, in an
// inline theme block so it resolved on the element and a nested data-theme still repainted it
// rather than reading a value computed once on :root. Both went with Tailwind (plan step 39): the
// first fact is already the scheme test above (every role a `--md-sys-color-*` custom property,
// light and dark); the second no longer has anything to hold, because no file replaces theme.css's
// alias — every package rule reads `--md-sys-color-*` directly at its point of use, so there is no
// second custom property that could be computed once on :root and go stale under a nested
// data-theme in the first place.
it('never aliases a colour role to a second custom property resolved once on :root', function () {
foreach (File::allFiles(packageCss()) as $file) {
expect($file->getContents())->not->toMatch('/--color-[\w-]+:\s*var\(--md-sys-color-/', $file->getRelativePathname());
}
});
it('resolves colour utilities on the element, so a nested data-theme repaints them', function () {
$theme = File::get(packageCss('tokens/theme.css'));
preg_match_all('/@theme inline \{(.*?)\}/s', $theme, $inline);
expect(implode("\n", $inline[1]))
->toContain('--color-primary: var(--md-sys-color-primary);')
->toContain('--color-body: var(--md-sys-color-on-surface-variant);')
->not->toMatch('/--[\w-]+:\s*#/');
});
it('leaves the choice of theme and of contrast to the head script, never to a media query', function () {
foreach (File::allFiles(packageCss()) as $file) {
expect($file->getContents())
+1 -3
View File
@@ -1,16 +1,14 @@
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import tailwindcss from '@tailwindcss/vite'
// Builds the Workbench, the Laravel app the showcase and the browser tests run in.
// Applications never use this file; they import the package's CSS and JS into their own build.
export default defineConfig({
plugins: [
laravel({
input: ['workbench/resources/css/package.css', 'workbench/resources/css/app.css', 'workbench/resources/js/app.js'],
input: ['workbench/resources/css/app.css', 'workbench/resources/js/app.js'],
publicDirectory: 'workbench/public',
refresh: ['resources/views/**', 'workbench/resources/views/**'],
}),
tailwindcss(),
],
})
@@ -19,8 +19,8 @@ class WorkbenchServiceProvider extends ServiceProvider
public function boot(): void
{
config(['livewire-material.showcase.vite' => [
// The package's CSS apart from Tailwind's, so Vite deduplicates its imports (package.css).
'workbench/resources/css/package.css',
// The package's whole CSS in the Workbench's one entry now that Tailwind is gone
// (plan step 39), and the JavaScript.
'workbench/resources/css/app.css',
'workbench/resources/js/app.js',
]]);
+15 -10
View File
@@ -1,11 +1,16 @@
/* The Workbench's Tailwind entry, for the classes the showcase still writes, with explicit sources
so the plans in docs/ are not scanned for class names. tailwind.css brings the theme and token
utilities without declaring the tokens a second time. The package's own CSS is package.css, an
entry of its own (its header says why); the layer statement is the same in both, so the
`material` layers sit between Tailwind's preflight and its utilities whichever loads first. */
@layer properties, theme, base, material, components, utilities;
@import 'tailwindcss' source(none);
@import '../../../resources/css/tailwind.css';
/* 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.
@source '../../../resources/views';
@source '../../../src';
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. */
@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';
@import './material-scheme.css';
-16
View File
@@ -1,16 +0,0 @@
/* The package's CSS for the Workbench, built the way an application without Tailwind builds it:
all.css (the foundation, every layout and every component stylesheet) and the Workbench's
scheme, in an entry Tailwind never touches. 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. `@tailwindcss/vite` inlines the imports of an entry that uses Tailwind without
that deduplication, so all.css imported beside `tailwindcss` would repeat every shared rule later
in the cascade; app.css keeps Tailwind, for the showcase's classes, apart.
Both entries open with the same layer statement, so the order holds whichever the page links
first: the `material` layers above Tailwind's preflight (`base`), whose `* { padding: 0 }` would
otherwise beat a component's padding, and below its `components` and `utilities`. `properties`
is the layer Tailwind hoists to the top of its output (the `--tw-*` fallbacks for a browser
without `@property`); named first, it stays lowest, as it was when both shared one entry. */
@layer properties, theme, base, material, components, utilities;
@import '../../../resources/css/all.css';
@import './material-scheme.css';