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", "components",
"material", "material",
"material-design", "material-design",
"material-3", "material-3"
"tailwindcss"
], ],
"homepage": "https://gitea.nonameweb.ch/noNameWEB/livewire-material", "homepage": "https://gitea.nonameweb.ch/noNameWEB/livewire-material",
"authors": [ "authors": [
-671
View File
@@ -6,12 +6,10 @@
"": { "": {
"devDependencies": { "devDependencies": {
"@material/material-color-utilities": "^0.4.0", "@material/material-color-utilities": "^0.4.0",
"@tailwindcss/vite": "^4.3.3",
"esbuild": "^0.28.2", "esbuild": "^0.28.2",
"fontkit": "^2.0.4", "fontkit": "^2.0.4",
"laravel-vite-plugin": "^3.2.0", "laravel-vite-plugin": "^3.2.0",
"playwright": "^1.62.1", "playwright": "^1.62.1",
"tailwindcss": "^4.3.3",
"vite": "^8.2.2" "vite": "^8.2.2"
} }
}, },
@@ -457,56 +455,6 @@
"node": ">=18" "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": { "node_modules/@material/material-color-utilities": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.4.0.tgz", "resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.4.0.tgz",
@@ -814,290 +762,6 @@
"tslib": "^2.8.0" "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": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -1156,20 +820,6 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/esbuild": {
"version": "0.28.2", "version": "0.28.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", "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": "^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": { "node_modules/laravel-vite-plugin": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-3.2.0.tgz", "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": { "node_modules/nanoid": {
"version": "3.3.19", "version": "3.3.19",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
@@ -1752,27 +1102,6 @@
"node": ">=0.10.0" "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": { "node_modules/tiny-inflate": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
-2
View File
@@ -13,12 +13,10 @@
}, },
"devDependencies": { "devDependencies": {
"@material/material-color-utilities": "^0.4.0", "@material/material-color-utilities": "^0.4.0",
"@tailwindcss/vite": "^4.3.3",
"esbuild": "^0.28.2", "esbuild": "^0.28.2",
"fontkit": "^2.0.4", "fontkit": "^2.0.4",
"laravel-vite-plugin": "^3.2.0", "laravel-vite-plugin": "^3.2.0",
"playwright": "^1.62.1", "playwright": "^1.62.1",
"tailwindcss": "^4.3.3",
"vite": "^8.2.2" "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. `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 ```js
// resources/js/app.js // resources/js/app.js
import '../../vendor/nonameweb/livewire-material/resources/js/material.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 * 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 * 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 * 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 * scales M3's own sets replace, and colours written as a literal value instead of a role plus
* instead of a role plus whatever an application bans on top (roles its own design rules * whatever an application bans on top (roles its own design rules leave out, the two absolutes,
* leave out, the two absolutes, opacity used as emphasis, patterns it has retired). * opacity used as emphasis, patterns it has retired).
* *
* expect(DesignGuard::scan([resource_path('views'), resource_path('js'), app_path()]) * expect(DesignGuard::scan([resource_path('views'), resource_path('js'), app_path()])
* ->forbidColours(['tertiary', 'primary-container']) * ->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-])/'; 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 * Tailwind's breakpoint prefixes and the M3 window size class that replaces each. Tailwind's
* that replaces each. Tailwind's 640/768/1024/1280/1536 are 4088 px from M3's 600/840/1200/ * 640/768/1024/1280/1536 are 4088 px from M3's 600/840/1200/1600, so a prefix maps to the
* 1600, so a prefix maps to the class that carries the same intent, never to the same pixel: * class that carries the same intent, never to the same pixel: both of its phone breakpoints
* both of its phone breakpoints are M3's medium. * are M3's medium.
*/ */
protected const array WINDOW_CLASSES = [ protected const array WINDOW_CLASSES = [
'sm' => 'medium', 'sm' => 'medium',
@@ -293,7 +293,7 @@ class DesignGuard
/** /**
* The radius, shadow, type-size, weight, leading, tracking, easing and duration utilities * 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> * @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); preg_match_all('/<x-livewire-material::([a-z-]+)/', File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php"), $tags);
$rendered = collect($tags[1])->unique() $rendered = collect($tags[1])->unique()
// A component still written for Tailwind has no stylesheet of its own to import yet; its // A rendered tag that is not a components/ stylesheet at all (a layout component's, e.g.
// old stylesheet, if it has one, lives in tailwind.css without the layer statement. // <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')) ->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") ->map(fn (string $tag): string => "./{$tag}.css")
->values() ->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); preg_match_all('/<x-livewire-material::([a-z-]+)/', File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php"), $tags);
$rendered = collect($tags[1])->unique() $rendered = collect($tags[1])->unique()
// A component still written for Tailwind has no stylesheet of its own to import yet; its // A rendered tag that is not a components/ stylesheet at all (a layout component's, e.g.
// old stylesheet, if it has one, lives in tailwind.css without the layer statement. // <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')) ->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") ->map(fn (string $tag): string => "./{$tag}.css")
->values() ->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\\] \\{\\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\\);/') ->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\'])') ->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/all.css'))->toContain("@import './components/table.css';");
->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('table.css');
}); });
it('sorts by its column, ascending first and then flipping', function () { 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("@import './icon.css';")
->toContain('[data-md-field-box]') ->toContain('[data-md-field-box]')
->not->toMatch('/(?<![\\w-])\\.field\\b/') ->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/all.css'))->toContain("@import './components/field.css';");
->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('field.css');
}); });
it('takes a custom control marked as the field\'s control, and a hint class', function () { 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); preg_match_all('/<x-livewire-material::([a-z-]+)/', File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php"), $tags);
$rendered = collect($tags[1])->unique() $rendered = collect($tags[1])->unique()
// A component still written for Tailwind has no stylesheet of its own to import yet; its // A rendered tag that is not a components/ stylesheet at all (a layout component's, e.g.
// old stylesheet, if it has one, lives in tailwind.css without the layer statement. // <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')) ->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") ->map(fn (string $tag): string => "./{$tag}.css")
->values() ->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); preg_match_all('/<x-livewire-material::([a-z-]+)/', File::get(__DIR__."/../../../resources/views/{$view}.blade.php"), $tags);
$rendered = collect($tags[1])->unique() $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')) ->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") ->map(fn (string $tag): string => "./{$tag}.css")
->values() ->values()
@@ -128,8 +128,8 @@ it('imports the stylesheet of every component its view renders', function (strin
return $tags[1]; return $tags[1];
}) })
->unique() ->unique()
// A component still written for Tailwind has no stylesheet of its own to import yet; its // A rendered tag that is not a components/ stylesheet at all (a layout component's, e.g.
// old stylesheet, if it has one, lives in tailwind.css without the layer statement. // <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')) ->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") ->map(fn (string $tag): string => "./{$tag}.css")
->values() ->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. // 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);') ->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/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/material.js'))->toContain("import './dialog.js'")
->and(File::get(__DIR__.'/../../../resources/js/dialog.js')) ->and(File::get(__DIR__.'/../../../resources/js/dialog.js'))
->toContain("directive('dialog-dividers'") ->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-full-screen] [data-md-search-view] {')
->toContain("[data-md-search][data-md-trigger='icon']:not([data-md-open]) [data-md-search-bar] {") ->toContain("[data-md-search][data-md-trigger='icon']:not([data-md-open]) [data-md-search-bar] {")
->not->toMatch('/\\d(?:\\.\\d+)?rem/') ->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/all.css'))->toContain("@import './components/search.css';");
->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('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')) ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components/selection.css'))
->toContain('[data-md-selection-row]') ->toContain('[data-md-selection-row]')
->toContain('@layer material.components') ->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/all.css'))->toContain("@import './components/checkbox.css';");
->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('selection.css');
}); });
it('lays the radio group out from its stylesheet, inline only from 600px', function () { 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 () { it('loads the application\'s Vite entries', function () {
File::ensureDirectoryExists($this->temporary.'/build'); File::ensureDirectoryExists($this->temporary.'/build');
File::put($this->temporary.'/build/manifest.json', json_encode([ 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/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], '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') $this->get('/abort/404')
->assertNotFound() ->assertNotFound()
->assertSee('build/assets/package-probe.css', false)
->assertSee('build/assets/app-probe.css', false) ->assertSee('build/assets/app-probe.css', false)
->assertSee('build/assets/app-probe.js', false) ->assertSee('build/assets/app-probe.js', false)
->assertDontSee('data-md-error-fallback', 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 * 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. * 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, * Every package component and layout stylesheet is plain CSS now (plan step 36), and Tailwind
* Tailwind-free and breakpoint checks below run over the whole tree `all.css` reaches (foundation, * itself is gone from the package (plan step 39), so the shape, Tailwind-free and breakpoint checks
* layout and components together) rather than the foundation alone everything but * below run over the whole tree `all.css` reaches (foundation, layout and components together)
* `tailwind.css` and `tokens/theme.css`/`tokens/utilities.css`, which stay Tailwind until plan * rather than the foundation alone, and one file it does not reach `showcase.css`, checked on
* step 39 and are simply never reached from `all.css`. A component's own values (colours as * its own further down. A component's own values (colours as roles, shadows as elevation levels,
* roles, shadows as elevation levels, and so on) are each group's own concern, in * and so on) are each group's own concern, in tests/Feature/Components/*StylesheetsTest.php;
* tests/Feature/Components/*StylesheetsTest.php; `bundle()`, the PHP bundler `all.css` is built * `bundle()`, the PHP bundler `all.css` is built for, is tests/Feature/StylesheetsBundleTest.php's.
* 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;'; 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 * 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 * stylesheet, each once. Every one of them is plain CSS (plan step 36), and Tailwind itself is
* step 36) `tailwind.css` and its two token files are never in this tree, since all.css does * gone from the package (plan step 39) `tailwindcss`, `tailwind.css` and its two token files no
* not import them. * longer exist, so there is nothing left for this tree to exclude.
* *
* @return list<string> * @return list<string>
*/ */
@@ -194,7 +193,7 @@ function stylesheetName(string $file): string
return substr($file, strlen(stylesheetPath()) + 1); 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()); $foundation = array_map(stylesheetName(...), foundationStylesheets());
$parts = collect(File::files(stylesheetPath('foundation'))) $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']) ->toBe(['reset.css', 'hidden.css', 'tokens.css', 'base.css', 'interaction.css', 'text.css'])
->and($foundation) ->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') ->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([]); ->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 () { it('opens every stylesheet all.css reaches with a header, the layer statement and plain imports', function () {
foreach (allStylesheets() as $file) { foreach (allStylesheets() as $file) {
$name = stylesheetName($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); expect($actual)->toBe($expected);
}); });
it('gives every md-type class the declarations of its type utility', function () { it('gives every md-type class exactly font, letter-spacing and font-variation-settings', function () {
preg_match_all('/@utility (type-[\w-]+) \{\n(.*?)\n\}/s', File::get(stylesheetPath('tokens/utilities.css')), $matches, PREG_SET_ORDER); // 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])]); $classes = collect($matches)->mapWithKeys(fn (array $match): array => ["md-{$match[1]}" => stylesheetDeclarations($match[2])]);
$text = File::get(stylesheetPath('text.css'));
expect($utilities)->toHaveCount(30); expect($classes)->toHaveCount(30);
foreach ($utilities as $class => $declarations) { foreach ($classes as $class => $declarations) {
expect(stylesheetDeclarations(stylesheetBlock($text, ".{$class}")))->toBe($declarations, $class) expect(array_keys($declarations))->toBe(['font', 'letter-spacing', 'font-variation-settings'], $class);
->and(array_keys($declarations))->toBe(['font', 'letter-spacing', 'font-variation-settings']);
} }
}); });
@@ -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 () { // A test used to prove foundation/interaction.css's `.md-*` classes matched tokens/utilities.css's
$utilities = File::get(stylesheetPath('tokens/utilities.css')); // utility blocks byte for byte apart from two deliberate differences, because the components
$interaction = File::get(stylesheetPath('foundation/interaction.css')); // 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)); it('builds the package in the Workbench\'s one CSS entry, with no Tailwind left to sit between', function () {
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'));
$app = stylesheetWithoutComments(File::get(__DIR__.'/../../workbench/resources/css/app.css')); $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: // Opens by ordering the layers, so the order holds whichever the page links first — an
// above preflight, whose `* { padding: 0 }` would otherwise beat a component's padding, and // application with Tailwind of its own still opens its own entry the same way (its header).
// below the utilities. expect(trim($app))->toStartWith(MATERIAL_LAYER_STATEMENT)
foreach (['package.css' => $package, 'app.css' => $app] as $name => $css) { ->toContain("@import '../../../resources/css/all.css';")
expect(trim($css))->toStartWith('@layer properties, theme, base, material, components, utilities;', $name); ->toContain("@import '../../../resources/css/showcase.css';")
}
// 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';")
->toContain("@import './material-scheme.css';") ->toContain("@import './material-scheme.css';")
->not->toContain('tailwind') ->not->toMatch('/@(?:tailwind|source|theme|utility|variant|custom-variant|apply|config|plugin|reference)\b/')
->not->toMatch('/@(?:source|theme|utility|variant|custom-variant|apply)\b/') ->and(File::get(__DIR__.'/../../vite.config.js'))
->and($app)->toContain("@import 'tailwindcss'") ->toContain("input: ['workbench/resources/css/app.css', 'workbench/resources/js/app.js']")
->toContain("@import '../../../resources/css/tailwind.css';") ->not->toContain('tailwindcss')
->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'")
->and(config('livewire-material.showcase.vite'))->toBe([ ->and(config('livewire-material.showcase.vite'))->toBe([
'workbench/resources/css/package.css',
'workbench/resources/css/app.css', 'workbench/resources/css/app.css',
'workbench/resources/js/app.js', '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 * The body of every `.md-type-…` class in text.css, keyed by class name without the `md-` prefix
* tokens/utilities.css, apart from the tokens, so the foundation imports no Tailwind directive. * (`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> * @return array<string, string>
*/ */
function typeUtilities(): array 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)); 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 () { // Tailwind's theme.css used to turn every scheme role into a `--color-*` Tailwind colour, in an
$roles = array_keys(json_decode(File::get(packageCss('tokens/scheme.json')), true)['light']); // inline theme block so it resolved on the element and a nested data-theme still repainted it
$theme = File::get(packageCss('tokens/theme.css')); // 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,
foreach ($roles as $role) { // light and dark); the second no longer has anything to hold, because no file replaces theme.css's
expect($theme)->toContain("--color-{$role}: var(--md-sys-color-{$role});"); // 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 () { 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) { foreach (File::allFiles(packageCss()) as $file) {
expect($file->getContents()) expect($file->getContents())
+1 -3
View File
@@ -1,16 +1,14 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin' 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. // 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. // Applications never use this file; they import the package's CSS and JS into their own build.
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
laravel({ 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', publicDirectory: 'workbench/public',
refresh: ['resources/views/**', 'workbench/resources/views/**'], refresh: ['resources/views/**', 'workbench/resources/views/**'],
}), }),
tailwindcss(),
], ],
}) })
@@ -19,8 +19,8 @@ class WorkbenchServiceProvider extends ServiceProvider
public function boot(): void public function boot(): void
{ {
config(['livewire-material.showcase.vite' => [ config(['livewire-material.showcase.vite' => [
// The package's CSS apart from Tailwind's, so Vite deduplicates its imports (package.css). // The package's whole CSS in the Workbench's one entry now that Tailwind is gone
'workbench/resources/css/package.css', // (plan step 39), and the JavaScript.
'workbench/resources/css/app.css', 'workbench/resources/css/app.css',
'workbench/resources/js/app.js', '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 /* The package's CSS for the Workbench, built the way an application without Tailwind builds it
so the plans in docs/ are not scanned for class names. tailwind.css brings the theme and token (plan step 39: Tailwind leaves the whole stack): all.css (the foundation, every layout and every
utilities without declaring the tokens a second time. The package's own CSS is package.css, an component stylesheet), showcase.css (the showcase's own chrome, so a real Vite build an error
entry of its own (its header says why); the layer statement is the same in both, so the page's fallback-free path, a browser-test probe page's `@vite()` carries it too, even though
`material` layers sit between Tailwind's preflight and its utilities whichever loads first. */ the showcase's own pages take their CSS from the bundle route instead) and the Workbench's
@layer properties, theme, base, material, components, utilities; scheme, in the one CSS entry the Workbench now needs. Vite's own CSS pipeline inlines the
@import 'tailwindcss' source(none); imports here and keeps a stylesheet several components import once, at its first position the
@import '../../../resources/css/tailwind.css'; order an application gets.
@source '../../../resources/views'; Opens with the same layer statement every package stylesheet does, so the `material` layers hold
@source '../../../src'; 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';