Close the design guard's family-table gaps on real application markup
Plan step 41 review: run over SealShare's and ReStride's views, family (i) missed about a quarter of the class tokens, most of them 1.x's own utilities (type-body-md, text-meta, rounded-corner-lg, focus-ring, link) and common Tailwind families (relative, inset-0, z-10, overflow-hidden, border, opacity-50, shrink-0, -mx-4, cursor-pointer, font-mono). Each now has a hint, and the ones with a layout component name its prop: gap-4 is gap="space200" (16px), a step between two of M3's names both, items-center align="center", grid-cols-3 :columns="3"; variants say which selector or media query replaces them. Shapes are tightened to real Tailwind values, so an application class such as content-header or p-card is not matched. Class lists are also read from wire:*.class, x-transition:*, unquoted :class keys, double-quoted PHP strings, 'class' => '…' and plain PHP files; a string compared in a condition (view === 'grid') is skipped, and each token carries its own line in a multi-line list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
d5972b8ddb
commit
9595722f35
+224
-67
@@ -3,6 +3,7 @@
|
|||||||
namespace NoNameWeb\LivewireMaterial\Testing;
|
namespace NoNameWeb\LivewireMaterial\Testing;
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use NoNameWeb\LivewireMaterial\Support\Layout;
|
||||||
use NoNameWeb\LivewireMaterial\Support\Stylesheets;
|
use NoNameWeb\LivewireMaterial\Support\Stylesheets;
|
||||||
use NoNameWeb\LivewireMaterial\Support\SvgFile;
|
use NoNameWeb\LivewireMaterial\Support\SvgFile;
|
||||||
use SplFileInfo;
|
use SplFileInfo;
|
||||||
@@ -14,12 +15,13 @@ use Symfony\Component\Finder\Finder;
|
|||||||
*
|
*
|
||||||
* (i) any Tailwind utility or variant in a view, PHP or JS file — a breakpoint prefix, a
|
* (i) any Tailwind utility or variant in a view, PHP or JS file — a breakpoint prefix, a
|
||||||
* cleared scale (radius, shadow, type size/weight/leading/tracking, easing, duration), a
|
* cleared scale (radius, shadow, type size/weight/leading/tracking, easing, duration), a
|
||||||
* layout, spacing, sizing or display utility, a text-layout utility, an M3 role utility
|
* layout, spacing, sizing, position, border, effect, interactivity, text or display
|
||||||
* (`text-on-surface-variant`, `bg-primary`), a pseudo-class variant, or an arbitrary
|
* utility, a colour utility on a role, a 1.x ink, white or black (`text-meta`, `bg-white`,
|
||||||
* `[…]` value — each with its 2.0.0 replacement: a layout component and prop, an `md-*`
|
* `text-on-surface/60`), a 1.x package utility (`type-body-md`, `focus-ring`,
|
||||||
|
* `rounded-corner-lg`), a variant, or an arbitrary `[…]` value or property — each with its
|
||||||
|
* 2.0.0 replacement: a layout component and prop (`gap-4` → `gap="space200"`), an `md-*`
|
||||||
* class, or a token for the application's own CSS. A class the application's own
|
* class, or a token for the application's own CSS. A class the application's own
|
||||||
* stylesheets declare is exempt, and so is every `md-*` class and the package's shared
|
* stylesheets declare is exempt, and so is every `md-*` class.
|
||||||
* interaction hooks (`md-state-layer`, `md-focus-ring`, `md-touch-target`, `md-link`).
|
|
||||||
* (ii) `missingStylesheets($cssEntry)`: a package component tag used in a view — unprefixed,
|
* (ii) `missingStylesheets($cssEntry)`: a package component tag used in a view — unprefixed,
|
||||||
* under the configured prefix, or `<x-livewire-material::…>` — whose stylesheet the
|
* under the configured prefix, or `<x-livewire-material::…>` — whose stylesheet the
|
||||||
* entry's `@import` graph does not reach (followed through every package file's own
|
* entry's `@import` graph does not reach (followed through every package file's own
|
||||||
@@ -46,16 +48,16 @@ use Symfony\Component\Finder\Finder;
|
|||||||
* ->violations())->toBe([]);
|
* ->violations())->toBe([]);
|
||||||
*
|
*
|
||||||
* Each violation is "path:line what", the path relative to the base path. False positives are
|
* Each violation is "path:line what", the path relative to the base path. False positives are
|
||||||
* kept low two ways: family (i)'s new checks — everything but the breakpoint, scale and
|
* kept low two ways: family (i)'s bare-word checks match only a class already isolated from a
|
||||||
* colour-value patterns kept from before 2.0.0 — match only a class already isolated from a
|
* class list in a `.php` file (`class="…"`, `wire:loading.class`, `x-transition:enter`, `:class`,
|
||||||
* `class`/`@class`/`:class`/`x-bind:class`/`->class()`/`Arr::toCssClasses()` list in a `.blade.php`
|
* `@class`, `->class()`, `Arr::toCssClasses()`, `'class' => '…'`), never a word scanned across a
|
||||||
* file, never a bare word scanned across a whole line, which is what keeps an English sentence
|
* whole line — which keeps "this creates a grid of cards" from matching `grid` — and skip a string
|
||||||
* ("this creates a grid of cards") from matching `grid`; and a line-by-line match (used for the
|
* a condition compares (`view === 'grid'`); a line-by-line match (the breakpoint, scale and
|
||||||
* breakpoint, scale and colour-value families, which also have to see a class assembled as a
|
* colour-value families, which also have to see a class assembled as a plain PHP or JS string, as
|
||||||
* plain PHP or JS string, as an enum's own literal colour string does) requires the utility's actual
|
* an enum's own literal colour string does) requires the utility's actual shape — a digit, a
|
||||||
* shape — a digit, a known scale step or a colour function — never a bare word. The trade-off:
|
* known scale step or a colour function — never a bare word. The trade-off: a bare-word utility
|
||||||
* `Arr::toCssClasses()` and `->class()` outside a `.blade.php` file, and a bare-word utility
|
* (`flex`, `hidden`) anywhere but inside a class list, and an application's own Tailwind theme
|
||||||
* (`flex`, `hidden`) anywhere but inside a class list, are invisible to this guard.
|
* names (`bg-brand`), are invisible to this guard.
|
||||||
*/
|
*/
|
||||||
class DesignGuard
|
class DesignGuard
|
||||||
{
|
{
|
||||||
@@ -116,33 +118,59 @@ class DesignGuard
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tailwind's flex, grid and gap utilities: `<x-row>`, `<x-grid>` and `<x-stack>` and their
|
* Every other Tailwind utility family an application's views wrote, as [pattern, hint] pairs
|
||||||
* `gap`/`align`/`justify`/`columns` props replace every one of them.
|
* tried in order on a class-list token (so a bare word like `flex` is only ever matched there).
|
||||||
|
* `%s` in a hint is the token. The spacing, container and text families with a finer hint are
|
||||||
|
* handled before this table (see `tailwindFamilyHint()`); the breakpoint, scale and palette
|
||||||
|
* families are the line-by-line checks'.
|
||||||
*/
|
*/
|
||||||
protected const string LAYOUT_UTILITY = '/^(?:flex|inline-flex|grid|inline-grid|contents|flex-(?:row|row-reverse|col|col-reverse|wrap|wrap-reverse|nowrap|1|auto|initial|none)|grid-cols-\d+|grid-rows-\d+|col-span-\d+|row-span-\d+|(?:items|justify|content|place-items|place-content|place-self|self)-[a-z]+|gap(?:-[xy])?-[\w.\/]+)$/';
|
protected const array FAMILIES = [
|
||||||
|
// 1.x's own utilities, which compiled through the package's Tailwind theme until 2.0.0.
|
||||||
|
['/^(?:focus-ring|state-layer|touch-target|link)$/', '1.x utility `%s` compiles to nothing — use `md-%s` (interaction.css)'],
|
||||||
|
['/^rounded(?:-(?:ss|se|ee|es|tl|tr|br|bl|t|r|b|l|s|e))?-corner-(?<corner>[a-z-]+)$/', '1.x utility `%s` compiles to nothing — use `var(--md-sys-shape-corner-{corner})` in your own CSS, or `<x-surface corner="{corner}">`'],
|
||||||
|
['/^shadow-elevation-(?<level>[0-5])$/', '1.x utility `%s` compiles to nothing — use `var(--md-sys-elevation-{level})` in your own CSS'],
|
||||||
|
['/^ease-(?<easing>(?:spatial|effects)-(?:fast|default|slow))$/', '1.x utility `%s` compiles to nothing — pair `var(--md-sys-motion-{easing})` with `var(--md-sys-motion-{easing}-duration)` in your own `transition`'],
|
||||||
|
['/^ease-(?<easing>standard|emphasized)(?<phase>-accelerate|-decelerate)?$/', '1.x utility `%s` compiles to nothing — use `var(--md-sys-motion-easing-{easing}{phase})` in your own `transition`'],
|
||||||
|
['/^[a-z][a-z-]*-\((?<property>--[\w-]+)\)$/', 'Tailwind custom-property utility `%s` compiles to nothing — write `var({property})` in your own CSS'],
|
||||||
|
|
||||||
/** Tailwind's padding, margin and gap-adjacent space-between utilities. */
|
['/^(?:static|fixed|absolute|relative|sticky)$/', "Tailwind position utility `%s` compiles to nothing — write `position` in your own CSS (a FAB goes in `<x-scaffold>`'s `fab` slot)"],
|
||||||
protected const string SPACING_UTILITY = '/^(?:[pm][trblxyse]?|space-[xy])-[\w.\/-]+$/';
|
['/^-?(?:inset(?:-[xyse])?|top|right|bottom|left|start|end)-(?:\d+(?:\.\d+)?|px|full|auto|\d+\/\d+)$/', 'Tailwind inset utility `%s` compiles to nothing — write the offset in your own CSS, from `var(--md-sys-measurement-space*)` where it is a spacing step'],
|
||||||
|
['/^-?z-(?:\d+|auto)$/', 'Tailwind z-index utility `%s` compiles to nothing — write `z-index` in your own CSS'],
|
||||||
|
['/^(?:overflow|overscroll)(?:-[xy])?-(?:auto|hidden|clip|visible|scroll|contain|none)$/', 'Tailwind overflow utility `%s` compiles to nothing — write `overflow` in your own CSS'],
|
||||||
|
['/^(?:flex-(?:1|auto|initial|none|row-reverse|col-reverse|nowrap|wrap-reverse)|grow(?:-\d+)?|shrink(?:-\d+)?|basis-[\w.\/]+|order-(?:\d+|first|last|none)|contents|grid-(?:rows|cols)-(?:none|subgrid|\d+)|(?:col|row)-(?:span-(?:\d+|full)|start-\d+|end-\d+|auto)|grid-flow-[\w-]+|auto-(?:cols|rows)-[\w]+|justify-(?:around|evenly|stretch|normal|items-[\w-]+|self-[\w-]+)|(?:content|place-content|place-items|place-self|self)-(?:start|end|center|between|around|evenly|stretch|baseline|normal|auto|none))$/', "Tailwind flex/grid item utility `%s` compiles to nothing — the layout components arrange their children; an item's own `flex`, `order` or `grid-column` is a rule in your own CSS"],
|
||||||
|
['/^(?:min-|max-)?(?:w|h|size)-(?:\d+(?:\.\d+)?|\d+\/\d+|px|full|screen|auto|min|max|fit|dvh|svh|lvh|dvw|svw|lvw|lh|prose|none|3xs|2xs|xs|sm|md|lg|xl|[2-7]xl|screen-(?:sm|md|lg|xl|2xl))$/', 'Tailwind sizing utility `%s` compiles to nothing — M3 keeps no size scale: `<x-pane width>` sets a content column\'s measure, `<x-icon size>` an icon\'s; anything else is a length in your own CSS'],
|
||||||
|
['/^container$/', 'Tailwind\'s `container` compiles to nothing — use `<x-pane width>`, which sets M3\'s margins and a measure'],
|
||||||
|
['/^(?:table(?:-[a-z-]+)?|flow-root|list-item|inline-table)$/', 'Tailwind display utility `%s` compiles to nothing — write the `display` rule in your own CSS'],
|
||||||
|
['/^(?:border(?:-[trblxyse])?(?:-\d+)?|border-(?:solid|dashed|dotted|double|hidden|none)|divide-[xy](?:-\d+|-reverse)?|divide-(?:solid|dashed|dotted|double|none))$/', 'Tailwind border utility `%s` compiles to nothing — a line is `<x-divider>` or `<x-surface outlined>`; any other border is your own CSS, in `var(--md-sys-color-outline-variant)`'],
|
||||||
|
['/^rounded(?:-(?:ss|se|ee|es|tl|tr|br|bl|t|r|b|l|s|e))?$/', 'Tailwind radius utility `%s` compiles to nothing — use `var(--md-sys-shape-corner-xs)` in your own CSS, or `<x-surface corner="xs">`'],
|
||||||
|
['/^(?:shadow|shadow-none|inset-shadow(?:-[\w]+)?|drop-shadow(?:-[\w]+)?)$/', 'Tailwind shadow utility `%s` compiles to nothing — use `var(--md-sys-elevation-*)` in your own CSS'],
|
||||||
|
['/^(?:outline(?:-none|-hidden|-\d+|-offset-\d+|-dashed|-dotted|-double|-solid)?|ring(?:-\d+|-inset)?|ring-offset-\d+)$/', 'Tailwind outline utility `%s` compiles to nothing — M3\'s focus indicator is `md-focus-ring` (interaction.css); any other outline is your own CSS'],
|
||||||
|
['/^(?:transition(?:-(?:all|colors|opacity|shadow|transform|none|discrete))?|animate-[\w-]+)$/', 'Tailwind motion utility `%s` compiles to nothing — pair `var(--md-sys-motion-spatial-*)`/`var(--md-sys-motion-effects-*)` with its `-duration` in your own `transition` or `animation`'],
|
||||||
|
['/^opacity-\d+$/', 'Tailwind opacity utility `%s` compiles to nothing — write `opacity` in your own CSS (M3\'s disabled content is 38 %%)'],
|
||||||
|
['/^(?:transform(?:-none|-gpu|-cpu)?|-?(?:scale|rotate|skew-[xy]|translate-[xy]|scale-[xy])-[\w.\/]+|origin-[\w-]+|will-change-[\w-]+|blur(?:-\w+)?|backdrop-[\w-]+|mix-blend-[\w-]+|isolate|isolation-auto)$/', 'Tailwind effect utility `%s` compiles to nothing — write the rule in your own CSS'],
|
||||||
|
['/^(?:cursor-[\w-]+|pointer-events-(?:none|auto)|select-(?:none|text|all|auto)|touch-[\w-]+|resize(?:-[xy]|-none)?|appearance-(?:none|auto)|scroll-(?:smooth|auto)|-?scroll-[mp][trblxyse]?-[\w.]+|snap-[\w-]+)$/', 'Tailwind interactivity utility `%s` compiles to nothing — write the rule in your own CSS'],
|
||||||
|
['/^(?:aspect-(?:auto|square|video|\d+\/\d+)|object-(?:contain|cover|fill|none|scale-down|top|bottom|center|left|right|left-top|left-bottom|right-top|right-bottom))$/', 'Tailwind media utility `%s` compiles to nothing — write `aspect-ratio`/`object-fit` in your own CSS'],
|
||||||
|
['/^font-(?:sans|serif)$/', 'Tailwind\'s `%s` compiles to nothing — the foundation already sets the brand typeface; any other `font-family` is your own CSS'],
|
||||||
|
['/^(?:underline|no-underline)$/', 'Tailwind\'s `%s` compiles to nothing — `md-link` draws a link (interaction.css); any other decoration is your own CSS'],
|
||||||
|
['/^(?:uppercase|lowercase|capitalize|normal-case|italic|not-italic|overline|line-through|underline-offset-\w+|decoration-(?:\d+|solid|double|dotted|dashed|wavy|auto|from-font|clone|slice)|font-mono|antialiased|subpixel-antialiased|whitespace-(?:normal|pre|pre-line|pre-wrap|break-spaces)|break-(?:words|all|keep|normal)|wrap-(?:break-word|anywhere|normal)|text-(?:wrap|balance|pretty|ellipsis|clip|justify)|line-clamp-(?:\d+|none)|not-sr-only|list-(?:disc|decimal|none|inside|outside)|align-(?:baseline|top|middle|bottom|text-top|text-bottom|sub|super)|indent-[\w.]+|hyphens-(?:none|manual|auto)|(?:normal|lining|oldstyle|proportional)-nums|ordinal|slashed-zero|(?:diagonal|stacked)-fractions)$/', 'Tailwind text utility `%s` compiles to nothing — write the rule in your own CSS'],
|
||||||
|
['/^bg-(?:(?:gradient-to|linear-to)-[a-z]+|radial|conic|cover|contain|auto|center|top|bottom|left|right|no-repeat|repeat(?:-[xy])?|fixed|local|scroll|none|clip-[a-z]+|origin-[a-z]+)$/', 'Tailwind background utility `%s` compiles to nothing — write the rule in your own CSS'],
|
||||||
|
];
|
||||||
|
|
||||||
/** Tailwind's width and height utilities. M3 keeps no size scale of its own. */
|
/** Bare display utilities, matched only as a whole class-list token. */
|
||||||
protected const string SIZING_UTILITY = '/^(?:min-|max-)?(?:w|h|size)-[\w.\/%-]+$/';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bare display utilities: no digit or bracket sets them apart from an English word, so these
|
|
||||||
* are matched only as a whole class-list token (never scanned line by line), which is what
|
|
||||||
* keeps "hidden" or "block" in running text from matching.
|
|
||||||
*/
|
|
||||||
protected const array DISPLAY_UTILITY = ['block', 'inline-block', 'inline', 'invisible', 'visible'];
|
protected const array DISPLAY_UTILITY = ['block', 'inline-block', 'inline', 'invisible', 'visible'];
|
||||||
|
|
||||||
/** Text-layout utilities with a direct `md-*` replacement (text.css). */
|
/** Text utilities with a direct `md-*` replacement (text.css). */
|
||||||
protected const array TEXT_LAYOUT_UTILITY = [
|
protected const array TEXT_LAYOUT_UTILITY = [
|
||||||
'text-left' => 'md-text-start',
|
'text-left' => 'md-text-start',
|
||||||
|
'text-start' => 'md-text-start',
|
||||||
'text-center' => 'md-text-center',
|
'text-center' => 'md-text-center',
|
||||||
'text-right' => 'md-text-end',
|
'text-right' => 'md-text-end',
|
||||||
|
'text-end' => 'md-text-end',
|
||||||
'truncate' => 'md-truncate',
|
'truncate' => 'md-truncate',
|
||||||
'line-clamp-2' => 'md-line-clamp-2',
|
'line-clamp-2' => 'md-line-clamp-2',
|
||||||
'line-clamp-3' => 'md-line-clamp-3',
|
'line-clamp-3' => 'md-line-clamp-3',
|
||||||
'whitespace-nowrap' => 'md-nowrap',
|
'whitespace-nowrap' => 'md-nowrap',
|
||||||
|
'text-nowrap' => 'md-nowrap',
|
||||||
'sr-only' => 'md-visually-hidden',
|
'sr-only' => 'md-visually-hidden',
|
||||||
'tabular-nums' => 'md-tabular',
|
'tabular-nums' => 'md-tabular',
|
||||||
];
|
];
|
||||||
@@ -314,13 +342,17 @@ class DesignGuard
|
|||||||
|
|
||||||
if ($isBlade) {
|
if ($isBlade) {
|
||||||
$contents = $this->withoutBladeComments($contents);
|
$contents = $this->withoutBladeComments($contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_ends_with($file->getFilename(), '.php')) {
|
||||||
foreach ($this->literalClasses($contents) as [$line, $class]) {
|
foreach ($this->literalClasses($contents) as [$line, $class]) {
|
||||||
if (($hint = $this->tailwindFamilyHint($class)) !== null) {
|
if (($hint = $this->tailwindFamilyHint($class)) !== null) {
|
||||||
$violations[] = "{$where}:{$line} {$hint}";
|
$violations[] = "{$where}:{$line} {$hint}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isBlade) {
|
||||||
foreach ($this->iconNames($contents) as [$line, $name]) {
|
foreach ($this->iconNames($contents) as [$line, $name]) {
|
||||||
$violations[] = "{$where}:{$line} unknown Material Symbol `{$name}`";
|
$violations[] = "{$where}:{$line} unknown Material Symbol `{$name}`";
|
||||||
}
|
}
|
||||||
@@ -563,10 +595,11 @@ class DesignGuard
|
|||||||
* A Tailwind-shaped class token's 2.0.0 replacement, or null when the token is not this
|
* A Tailwind-shaped class token's 2.0.0 replacement, or null when the token is not this
|
||||||
* guard's to report: it is not Tailwind-shaped at all (an application's own class, an ARIA or
|
* guard's to report: it is not Tailwind-shaped at all (an application's own class, an ARIA or
|
||||||
* data token, a plain word), it is on the small set nothing here ever flags (`md-*` — the
|
* data token, a plain word), it is on the small set nothing here ever flags (`md-*` — the
|
||||||
* text classes and the shared interaction hooks), or the application's own CSS declares it
|
* text classes and the shared interaction hooks), the application's own CSS declares it
|
||||||
* (`applicationClasses()`). Only ever called with a token `literalClasses()` already isolated
|
* (`applicationClasses()`), or a line-by-line check reports it already (a breakpoint prefix, a
|
||||||
* from a class list, which is what makes it safe to match a bare word like `flex` or `hidden`
|
* cleared scale step, an arbitrary or palette colour). Only ever called with a token
|
||||||
* (see the class header's false-positive note).
|
* `literalClasses()` already isolated from a class list, which is what makes it safe to match a
|
||||||
|
* bare word like `flex` or `hidden` (see the class header's false-positive note).
|
||||||
*/
|
*/
|
||||||
protected function tailwindFamilyHint(string $token): ?string
|
protected function tailwindFamilyHint(string $token): ?string
|
||||||
{
|
{
|
||||||
@@ -574,41 +607,40 @@ class DesignGuard
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = rtrim($token, '!');
|
$token = trim($token, '!');
|
||||||
|
|
||||||
if (preg_match(self::ARBITRARY_COLOUR, $token) === 1) {
|
if (preg_match(self::ARBITRARY_COLOUR, $token) === 1 || preg_match($this->colourPattern(), $token) === 1 || $this->outsideTheScale($token) !== []) {
|
||||||
return null; // colourValues() already reports this, line by line.
|
return null; // colourValues(), the palette check and outsideTheScale() already report this, line by line.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str_contains($token, ':')) {
|
if (preg_match('/^(?<lead>\[[^\]]*\]|[^:\[]+(?:\[[^\]]*\])?):/', $token, $variant) === 1) {
|
||||||
$lead = explode(':', $token, 2)[0];
|
$bare = str_starts_with($variant['lead'], 'max-') ? substr($variant['lead'], 4) : $variant['lead'];
|
||||||
$bare = str_starts_with($lead, 'max-') ? substr($lead, 4) : $lead;
|
|
||||||
|
|
||||||
if (isset(self::WINDOW_CLASSES[$bare])) {
|
if (isset(self::WINDOW_CLASSES[$bare])) {
|
||||||
return null; // breakpointPrefixes() already names the M3 breakpoint for this one.
|
return null; // breakpointPrefixes() already names the M3 breakpoint for this one.
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Tailwind variant class `{$token}` compiles to nothing — a state is `:has()`/`aria-*`/`data-md-*` in your own CSS, a breakpoint a layout component's `hide-below`/`hide-from`/`stack-below` prop";
|
return "Tailwind variant class `{$token}` compiles to nothing — ".$this->variantHint($variant['lead']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('/\[[^\]]*\]/', $token) === 1) {
|
if (preg_match('/^(?:type-(?:emphasized-)?(?:display|headline|title|body|label)-(?:lg|md|sm))$/', $token) === 1) {
|
||||||
|
return "1.x utility `{$token}` compiles to nothing — use `md-{$token}` (text.css)";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^\[(?<property>-{0,2}[a-z][\w-]*):[^\]]+\]$/', $token, $arbitrary) === 1) {
|
||||||
|
return "Tailwind arbitrary property `{$token}` compiles to nothing — write `{$arbitrary['property']}` in your own CSS";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^-?[a-z][\w-]*-\[[^\]]*\]$/', $token) === 1) {
|
||||||
return "Tailwind arbitrary value `{$token}` compiles to nothing — write the literal value in your own CSS, or use an M3 token";
|
return "Tailwind arbitrary value `{$token}` compiles to nothing — write the literal value in your own CSS, or use an M3 token";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match(self::LAYOUT_UTILITY, $token) === 1) {
|
if (($hint = $this->containerHint($token) ?? $this->spacingHint($token)) !== null) {
|
||||||
return "Tailwind layout utility `{$token}` compiles to nothing — use `<x-row>`, `<x-grid>` or `<x-stack>` and their `gap`/`align`/`justify`/`columns` props";
|
return $hint;
|
||||||
}
|
|
||||||
|
|
||||||
if (preg_match(self::SPACING_UTILITY, $token) === 1) {
|
|
||||||
return "Tailwind spacing utility `{$token}` compiles to nothing — use a layout component's `gap`/`padding` prop (`<x-stack gap>`, `<x-surface padding>`), or `var(--md-sys-measurement-space*)` in your own CSS";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preg_match(self::SIZING_UTILITY, $token) === 1) {
|
|
||||||
return "Tailwind sizing utility `{$token}` compiles to nothing — M3 keeps no size scale; write the literal length in your own CSS, or use `<x-pane width>`/`<x-grid min-item>` where it fits";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($token === 'hidden') {
|
if ($token === 'hidden') {
|
||||||
return "Tailwind's `hidden` compiles to nothing — use a layout component's `hide-below`/`hide-from` prop, or the reset's `[hidden]` attribute";
|
return "Tailwind's `hidden` compiles to nothing — use a layout component's `hide-below`/`hide-from` prop, the `hidden` attribute (the reset keeps it hidden), or `x-show`";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array($token, self::DISPLAY_UTILITY, true)) {
|
if (in_array($token, self::DISPLAY_UTILITY, true)) {
|
||||||
@@ -619,7 +651,119 @@ class DesignGuard
|
|||||||
return "Tailwind's `{$token}` compiles to nothing — use `".self::TEXT_LAYOUT_UTILITY[$token].'` (text.css)';
|
return "Tailwind's `{$token}` compiles to nothing — use `".self::TEXT_LAYOUT_UTILITY[$token].'` (text.css)';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->colourUtilityHint($token);
|
if (($hint = $this->colourUtilityHint($token)) !== null) {
|
||||||
|
return $hint;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (self::FAMILIES as [$pattern, $hint]) {
|
||||||
|
if (preg_match($pattern, $token, $match) === 1) {
|
||||||
|
return (string) preg_replace_callback(
|
||||||
|
'/\{(\w+)\}/',
|
||||||
|
fn (array $name): string => (string) ($match[$name[1]] ?? ''),
|
||||||
|
str_replace('%%', '%', str_replace('%s', $token, $hint)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 2.0.0 replacement for a Tailwind variant, by its first segment: a state, the theme, a
|
||||||
|
* structural pseudo-class, a direction, an M3 breakpoint variant 1.x compiled, or an
|
||||||
|
* arbitrary selector.
|
||||||
|
*/
|
||||||
|
protected function variantHint(string $lead): string
|
||||||
|
{
|
||||||
|
$m3 = ['medium' => 600, 'expanded' => 840, 'large' => 1200, 'extra-large' => 1600];
|
||||||
|
$bare = str_starts_with($lead, 'max-') ? substr($lead, 4) : $lead;
|
||||||
|
|
||||||
|
return match (true) {
|
||||||
|
isset($m3[$bare]) => '`@media (width '.($bare === $lead ? '>=' : '<')." {$m3[$bare]}px)` in your own CSS, or a layout component's `hide-below`/`hide-from`/`stack-below` prop",
|
||||||
|
$lead === 'dark' => "the roles already switch with the theme; a dark-only rule is `[data-theme='dark'] …` in your own CSS",
|
||||||
|
$lead === 'rail-collapsed' => "`:root[data-rail='collapsed'] …` in your own CSS",
|
||||||
|
in_array($lead, ['motion-reduce', 'motion-safe'], true) => '`@media (prefers-reduced-motion: reduce)` in your own CSS',
|
||||||
|
in_array($lead, ['rtl', 'ltr'], true) => "`:is([dir='rtl'], [dir='rtl'] *)` in your own CSS",
|
||||||
|
$lead === 'print' => '`@media print` in your own CSS',
|
||||||
|
preg_match('/^(?:hover|focus|focus-visible|focus-within|active|pressed)$/', $lead) === 1 => "M3's hover, focus and press states are `md-state-layer` and `md-focus-ring` (interaction.css); any other state rule is `:hover`/`:focus-visible` in your own CSS",
|
||||||
|
preg_match('/^(?:group|peer|has|not|in)(?:-|\/|$)/', $lead) === 1 => '`:has()` or a descendant selector in your own CSS',
|
||||||
|
preg_match('/^(?:aria|data)-/', $lead) === 1 => 'an `[aria-…]` or `[data-…]` attribute selector in your own CSS',
|
||||||
|
preg_match('/^(?:before|after|placeholder|file|marker|selection|backdrop|first-line|first-letter)$/', $lead) === 1 => 'a pseudo-element rule in your own CSS',
|
||||||
|
str_starts_with($lead, '[') => 'write the arbitrary selector as a rule in your own CSS',
|
||||||
|
default => 'write the state or condition as a selector or `@media` rule in your own CSS',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layout component and prop for a Tailwind flex or grid container utility, or null.
|
||||||
|
*/
|
||||||
|
protected function containerHint(string $token): ?string
|
||||||
|
{
|
||||||
|
$dead = "Tailwind layout utility `{$token}` compiles to nothing";
|
||||||
|
|
||||||
|
return match (true) {
|
||||||
|
in_array($token, ['flex', 'inline-flex', 'flex-row'], true) => "{$dead} — use `<x-row>` (`gap`, `align`, `justify`, `wrap`, `stack-below`), or `<x-stack>` for a column",
|
||||||
|
$token === 'flex-col' => "{$dead} — use `<x-stack>` (`gap`, `align`)",
|
||||||
|
$token === 'flex-wrap' => "{$dead} — use `<x-row wrap>`",
|
||||||
|
in_array($token, ['grid', 'inline-grid'], true) => "{$dead} — use `<x-grid>` (`:columns` per breakpoint, `gap`, `min-item`), or `<x-feed>` for a grid of cards",
|
||||||
|
preg_match('/^grid-cols-(\d+)$/', $token, $m) === 1 => "{$dead} — use `<x-grid :columns=\"{$m[1]}\">`, or a per-breakpoint map (`:columns=\"['compact' => 1, 'medium' => {$m[1]}]\"`)",
|
||||||
|
preg_match('/^items-(start|end|center|stretch)$/', $token, $m) === 1 => "{$dead} — use `align=\"{$m[1]}\"` on `<x-row>` or `<x-stack>`",
|
||||||
|
$token === 'items-baseline' => "{$dead} — use `align=\"baseline\"` on `<x-row>`",
|
||||||
|
preg_match('/^justify-(start|end|center|between)$/', $token, $m) === 1 => "{$dead} — use `justify=\"{$m[1]}\"` on `<x-row>`",
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The M3 spacing step for a Tailwind gap, padding, margin or space-between utility, or null.
|
||||||
|
* Tailwind's spacing unit is 4px (`gap-4` is 16px, `space200`); a value between two of M3's
|
||||||
|
* steps names both neighbours.
|
||||||
|
*/
|
||||||
|
protected function spacingHint(string $token): ?string
|
||||||
|
{
|
||||||
|
if (preg_match('/^(?<negative>-)?(?<kind>gap(?:-[xy])?|[pm][trblxyse]?|space-[xy])-(?<value>\d+(?:\.\d+)?|px|auto|reverse)$/', $token, $m) !== 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dead = "Tailwind spacing utility `{$token}` compiles to nothing";
|
||||||
|
|
||||||
|
if ($m['value'] === 'auto') {
|
||||||
|
return in_array($m['kind'], ['m', 'mx'], true)
|
||||||
|
? "{$dead} — `<x-pane width>` centres its content; otherwise `margin-inline: auto` in your own CSS"
|
||||||
|
: "{$dead} — an `auto` margin is a rule in your own CSS";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($m['value'] === 'reverse') {
|
||||||
|
return "{$dead} — a reversed row is a `flex-direction` rule in your own CSS";
|
||||||
|
}
|
||||||
|
|
||||||
|
$px = $m['value'] === 'px' ? 1.0 : (float) $m['value'] * 4;
|
||||||
|
$steps = array_combine(Layout::SPACING, array_map(
|
||||||
|
fn (string $step): float => (float) substr($step, 5) / 100 * 8,
|
||||||
|
Layout::SPACING,
|
||||||
|
));
|
||||||
|
$exact = array_search($px, $steps, true);
|
||||||
|
$below = array_key_last(array_filter($steps, fn (float $step): bool => $step < $px));
|
||||||
|
$above = array_key_first(array_filter($steps, fn (float $step): bool => $step > $px));
|
||||||
|
$pxText = rtrim(rtrim(number_format($px, 2, '.', ''), '0'), '.').'px';
|
||||||
|
|
||||||
|
$choice = fn (string $attribute): string => $exact !== false
|
||||||
|
? "`{$attribute}=\"{$exact}\"` ({$pxText})"
|
||||||
|
: implode(' or ', array_map(
|
||||||
|
fn (string $step): string => "`{$attribute}=\"{$step}\"` (".rtrim(rtrim(number_format($steps[$step], 2, '.', ''), '0'), '.').'px)',
|
||||||
|
array_values(array_filter([$below, $above])),
|
||||||
|
))." — {$pxText} is not an M3 spacing step";
|
||||||
|
$token = $exact !== false ? "`var(--md-sys-measurement-{$exact})`" : '`var(--md-sys-measurement-space*)`';
|
||||||
|
|
||||||
|
return match (true) {
|
||||||
|
$px === 0.0 => "{$dead} — leave it out: the reset zeroes margins and padding",
|
||||||
|
$m['negative'] === '-' => "{$dead} — a negative margin is `calc(-1 * ".trim($token, '`').')` in your own CSS',
|
||||||
|
str_starts_with($m['kind'], 'gap') => "{$dead} — use ".$choice('gap').' on `<x-row>`, `<x-stack>`, `<x-grid>` or `<x-feed>`',
|
||||||
|
$m['kind'] === 'space-y' => "{$dead} — use `<x-stack>` with ".$choice('gap'),
|
||||||
|
$m['kind'] === 'space-x' => "{$dead} — use `<x-row>` with ".$choice('gap'),
|
||||||
|
str_starts_with($m['kind'], 'p') => "{$dead} — use ".$choice('padding')." on `<x-surface>`, or {$token} in your own CSS",
|
||||||
|
default => "{$dead} — space between siblings is a layout component's ".$choice('gap')."; any other margin is {$token} in your own CSS",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -897,10 +1041,14 @@ class DesignGuard
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Every class written out literally: in `class="…"`, and in the string keys and values of
|
* Every class written out literally: in `class="…"`, Livewire's `wire:loading.class="…"` and
|
||||||
* `:class`/`x-bind:class`, `@class([...])`, `->class([...])` and `Arr::toCssClasses([...])`.
|
* Alpine's `x-transition:enter="…"` lists, and in the strings of `:class`/`x-bind:class` (its
|
||||||
* Echoes inside a list are skipped. A token is returned exactly as written, variant prefix
|
* object keys too, quoted or not), `@class([...])`, `->class(...)`, `Arr::toCssClasses([...])`
|
||||||
* (`sm:`, `hover:`) and all, since `tailwindFamilyHint()` reports the prefix itself.
|
* and a `'class' => '…'` pair. A string compared in a condition (`view === 'grid'`,
|
||||||
|
* `$status === 'hidden'`) or passed to a call (`isActive('grid')`) is not a class; echoes
|
||||||
|
* inside a list are skipped. A token is returned exactly as written, variant prefix (`sm:`,
|
||||||
|
* `hover:`) and all, since `tailwindFamilyHint()` reports the prefix itself, with the line it
|
||||||
|
* sits on.
|
||||||
*
|
*
|
||||||
* @return list<array{0: int, 1: string}>
|
* @return list<array{0: int, 1: string}>
|
||||||
*/
|
*/
|
||||||
@@ -908,33 +1056,42 @@ class DesignGuard
|
|||||||
{
|
{
|
||||||
$lists = [];
|
$lists = [];
|
||||||
|
|
||||||
preg_match_all('/(?<![\w:.-])class="([^"]*)"/', $contents, $attributes, PREG_OFFSET_CAPTURE);
|
preg_match_all('/(?:(?<![\w:.-])class|wire:[\w.-]*\.class(?:\.remove)?|x-transition:(?:enter|leave)(?:-start|-end)?)="([^"]*)"/', $contents, $attributes, PREG_OFFSET_CAPTURE);
|
||||||
|
|
||||||
foreach ($attributes[1] as [$list, $offset]) {
|
foreach ($attributes[1] as [$list, $offset]) {
|
||||||
$lists[] = [$list, $offset];
|
$lists[] = [$list, $offset];
|
||||||
}
|
}
|
||||||
|
|
||||||
preg_match_all('/(?::class="([^"]*)"|@class\(\[(.*?)\]\)|->class\(\[(.*?)\]\)|Arr::toCssClasses\(\[(.*?)\]\))/s', $contents, $bindings, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
|
preg_match_all('/:class="(?<alpine>[^"]*)"|(?:@class|->class|Arr::toCssClasses)\((?<php>\[.*?\]|\'[^\']*\'|"[^"]*")\)|([\'"])class\3\s*=>\s*(?<pair>\'[^\']*\'|"[^"]*")/s', $contents, $bindings, PREG_OFFSET_CAPTURE | PREG_SET_ORDER | PREG_UNMATCHED_AS_NULL);
|
||||||
|
|
||||||
foreach ($bindings as $binding) {
|
foreach ($bindings as $binding) {
|
||||||
[$body, $offset] = array_values(array_filter(array_slice($binding, 1), fn (array $group): bool => $group[1] !== -1))[0] ?? ['', 0];
|
$alpine = $binding['alpine'][0] !== null;
|
||||||
|
[$body, $offset] = $binding['alpine'][0] !== null ? $binding['alpine'] : ($binding['php'][0] !== null ? $binding['php'] : $binding['pair']);
|
||||||
|
|
||||||
preg_match_all("/'([^']*)'/", $body, $strings, PREG_OFFSET_CAPTURE);
|
preg_match_all($alpine ? "/'([^']*)'|(?<=[{,])\\s*([A-Za-z_][\\w]*)\\s*:/" : "/'([^']*)'|\"([^\"]*)\"/", $body, $strings, PREG_OFFSET_CAPTURE | PREG_SET_ORDER | PREG_UNMATCHED_AS_NULL);
|
||||||
|
|
||||||
|
foreach ($strings as $string) {
|
||||||
|
[$list, $inner] = $string[1][0] !== null ? $string[1] : $string[2];
|
||||||
|
$before = substr($body, 0, $string[0][1]);
|
||||||
|
$after = substr($body, $string[0][1] + strlen($string[0][0]));
|
||||||
|
|
||||||
|
if (preg_match('/(?:[=!]==?|[<>]=?|\(|\?\?)\s*$/', $before) === 1 || preg_match('/^\s*(?:[=!]==?|[<>]=?)/', $after) === 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($strings[1] as [$list, $inner]) {
|
|
||||||
$lists[] = [$list, $offset + $inner];
|
$lists[] = [$list, $offset + $inner];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$found = [];
|
$found = [];
|
||||||
|
usort($lists, fn (array $a, array $b): int => $a[1] <=> $b[1]);
|
||||||
|
|
||||||
foreach ($lists as [$list, $offset]) {
|
foreach ($lists as [$list, $offset]) {
|
||||||
$line = substr_count(substr($contents, 0, $offset), "\n") + 1;
|
$list = (string) preg_replace_callback('/\{\{.*?\}\}|\{!!.*?!!\}/s', fn (array $echo): string => (string) preg_replace('/[^\n]/', ' ', $echo[0]), $list);
|
||||||
$list = (string) preg_replace('/\{\{.*?\}\}|\{!!.*?!!\}/', ' ', $list);
|
|
||||||
|
|
||||||
foreach (preg_split('/\s+/', $list, -1, PREG_SPLIT_NO_EMPTY) ?: [] as $token) {
|
foreach (preg_split('/\s+/', $list, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE) ?: [] as [$token, $at]) {
|
||||||
if (preg_match('/[$@(){}]/', $token) !== 1) {
|
if (preg_match('/[$@{}\\\\]/', $token) !== 1 && substr_count($token, '(') === substr_count($token, ')')) {
|
||||||
$found[] = [$line, $token];
|
$found[] = [substr_count(substr($contents, 0, $offset + $at), "\n") + 1, $token];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ it('finds what compiles to nothing', function () {
|
|||||||
|
|
||||||
expect(fixtureRelative($violations))->toBe([
|
expect(fixtureRelative($violations))->toBe([
|
||||||
'app/Status.php:14 colour the theme does not declare `text-red-500`',
|
'app/Status.php:14 colour the theme does not declare `text-red-500`',
|
||||||
"views/page.blade.php:2 Tailwind spacing utility `p-4` compiles to nothing — use a layout component's `gap`/`padding` prop (`<x-stack gap>`, `<x-surface padding>`), or `var(--md-sys-measurement-space*)` in your own CSS",
|
'views/page.blade.php:2 Tailwind spacing utility `p-4` compiles to nothing — use `padding="space200"` (16px) on `<x-surface>`, or `var(--md-sys-measurement-space200)` in your own CSS',
|
||||||
'views/page.blade.php:6 Tailwind colour utility `text-tertiary` compiles to nothing — use `var(--md-sys-color-tertiary)` in your own CSS',
|
'views/page.blade.php:6 Tailwind colour utility `text-tertiary` compiles to nothing — use `var(--md-sys-color-tertiary)` in your own CSS',
|
||||||
'views/page.blade.php:8 Tailwind sizing utility `size-4` compiles to nothing — M3 keeps no size scale; write the literal length in your own CSS, or use `<x-pane width>`/`<x-grid min-item>` where it fits',
|
'views/page.blade.php:6 1.x utility `focus-ring` compiles to nothing — use `md-focus-ring` (interaction.css)',
|
||||||
|
"views/page.blade.php:8 Tailwind sizing utility `size-4` compiles to nothing — M3 keeps no size scale: `<x-pane width>` sets a content column's measure, `<x-icon size>` an icon's; anything else is a length in your own CSS",
|
||||||
'views/page.blade.php:3 unknown Material Symbol `o-home`',
|
'views/page.blade.php:3 unknown Material Symbol `o-home`',
|
||||||
'views/page.blade.php:4 unknown Material Symbol `not_a_symbol`',
|
'views/page.blade.php:4 unknown Material Symbol `not_a_symbol`',
|
||||||
'views/page.blade.php:8 Blade directive `@class` inside a component tag, where it does not compile',
|
'views/page.blade.php:8 Blade directive `@class` inside a component tag, where it does not compile',
|
||||||
@@ -52,10 +53,10 @@ it('names the 2.0.0 replacement for a pseudo-class variant, but leaves a breakpo
|
|||||||
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/variant.blade.php'))->violations());
|
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/variant.blade.php'))->violations());
|
||||||
|
|
||||||
expect($violations)->toBe([
|
expect($violations)->toBe([
|
||||||
"tailwind/variant.blade.php:1 Tailwind variant class `hover:underline` compiles to nothing — a state is `:has()`/`aria-*`/`data-md-*` in your own CSS, a breakpoint a layout component's `hide-below`/`hide-from`/`stack-below` prop",
|
"tailwind/variant.blade.php:1 Tailwind variant class `hover:underline` compiles to nothing — M3's hover, focus and press states are `md-state-layer` and `md-focus-ring` (interaction.css); any other state rule is `:hover`/`:focus-visible` in your own CSS",
|
||||||
"tailwind/variant.blade.php:1 Tailwind variant class `dark:opacity-50` compiles to nothing — a state is `:has()`/`aria-*`/`data-md-*` in your own CSS, a breakpoint a layout component's `hide-below`/`hide-from`/`stack-below` prop",
|
"tailwind/variant.blade.php:1 Tailwind variant class `dark:opacity-50` compiles to nothing — the roles already switch with the theme; a dark-only rule is `[data-theme='dark'] …` in your own CSS",
|
||||||
"tailwind/variant.blade.php:2 Tailwind variant class `group-hover:flex` compiles to nothing — a state is `:has()`/`aria-*`/`data-md-*` in your own CSS, a breakpoint a layout component's `hide-below`/`hide-from`/`stack-below` prop",
|
'tailwind/variant.blade.php:2 Tailwind variant class `group-hover:flex` compiles to nothing — `:has()` or a descendant selector in your own CSS',
|
||||||
"tailwind/variant.blade.php:2 Tailwind variant class `focus:md:gap-2` compiles to nothing — a state is `:has()`/`aria-*`/`data-md-*` in your own CSS, a breakpoint a layout component's `hide-below`/`hide-from`/`stack-below` prop",
|
"tailwind/variant.blade.php:2 Tailwind variant class `focus:md:gap-2` compiles to nothing — M3's hover, focus and press states are `md-state-layer` and `md-focus-ring` (interaction.css); any other state rule is `:hover`/`:focus-visible` in your own CSS",
|
||||||
"tailwind/variant.blade.php:2 Tailwind breakpoint `md:` compiles to nothing — M3's medium (600px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width >= 600px)` in your own CSS",
|
"tailwind/variant.blade.php:2 Tailwind breakpoint `md:` compiles to nothing — M3's medium (600px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width >= 600px)` in your own CSS",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -189,50 +190,44 @@ it('names the md-ink-*, divider/surface or token replacement for a dead M3 role
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends flex, grid and gap utilities to the layout components', function () {
|
it('sends flex, grid and gap utilities to the layout component and prop that replaces each', function () {
|
||||||
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/layout.blade.php'))->violations());
|
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/layout.blade.php'))->violations());
|
||||||
|
|
||||||
$hint = 'compiles to nothing — use `<x-row>`, `<x-grid>` or `<x-stack>` and their `gap`/`align`/`justify`/`columns` props';
|
|
||||||
|
|
||||||
expect($violations)->toBe([
|
expect($violations)->toBe([
|
||||||
"tailwind/layout.blade.php:1 Tailwind layout utility `flex` {$hint}",
|
'tailwind/layout.blade.php:1 Tailwind layout utility `flex` compiles to nothing — use `<x-row>` (`gap`, `align`, `justify`, `wrap`, `stack-below`), or `<x-stack>` for a column',
|
||||||
"tailwind/layout.blade.php:1 Tailwind layout utility `grid` {$hint}",
|
'tailwind/layout.blade.php:1 Tailwind layout utility `grid` compiles to nothing — use `<x-grid>` (`:columns` per breakpoint, `gap`, `min-item`), or `<x-feed>` for a grid of cards',
|
||||||
"tailwind/layout.blade.php:1 Tailwind layout utility `inline-flex` {$hint}",
|
'tailwind/layout.blade.php:1 Tailwind layout utility `inline-flex` compiles to nothing — use `<x-row>` (`gap`, `align`, `justify`, `wrap`, `stack-below`), or `<x-stack>` for a column',
|
||||||
"tailwind/layout.blade.php:1 Tailwind layout utility `flex-col` {$hint}",
|
'tailwind/layout.blade.php:1 Tailwind layout utility `flex-col` compiles to nothing — use `<x-stack>` (`gap`, `align`)',
|
||||||
"tailwind/layout.blade.php:2 Tailwind layout utility `grid-cols-3` {$hint}",
|
"tailwind/layout.blade.php:2 Tailwind layout utility `grid-cols-3` compiles to nothing — use `<x-grid :columns=\"3\">`, or a per-breakpoint map (`:columns=\"['compact' => 1, 'medium' => 3]\"`)",
|
||||||
"tailwind/layout.blade.php:2 Tailwind layout utility `col-span-2` {$hint}",
|
"tailwind/layout.blade.php:2 Tailwind flex/grid item utility `col-span-2` compiles to nothing — the layout components arrange their children; an item's own `flex`, `order` or `grid-column` is a rule in your own CSS",
|
||||||
"tailwind/layout.blade.php:2 Tailwind layout utility `items-center` {$hint}",
|
'tailwind/layout.blade.php:2 Tailwind layout utility `items-center` compiles to nothing — use `align="center"` on `<x-row>` or `<x-stack>`',
|
||||||
"tailwind/layout.blade.php:2 Tailwind layout utility `justify-between` {$hint}",
|
'tailwind/layout.blade.php:2 Tailwind layout utility `justify-between` compiles to nothing — use `justify="between"` on `<x-row>`',
|
||||||
"tailwind/layout.blade.php:2 Tailwind layout utility `gap-4` {$hint}",
|
'tailwind/layout.blade.php:2 Tailwind spacing utility `gap-4` compiles to nothing — use `gap="space200"` (16px) on `<x-row>`, `<x-stack>`, `<x-grid>` or `<x-feed>`',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends padding, margin and space-between utilities to a layout component\'s gap/padding prop', function () {
|
it('sends padding, margin and space-between utilities to the M3 spacing step they are (gap-4 is space200)', function () {
|
||||||
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/spacing.blade.php'))->violations());
|
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/spacing.blade.php'))->violations());
|
||||||
|
|
||||||
$hint = "compiles to nothing — use a layout component's `gap`/`padding` prop (`<x-stack gap>`, `<x-surface padding>`), or `var(--md-sys-measurement-space*)` in your own CSS";
|
|
||||||
|
|
||||||
expect($violations)->toBe([
|
expect($violations)->toBe([
|
||||||
"tailwind/spacing.blade.php:1 Tailwind spacing utility `p-4` {$hint}",
|
'tailwind/spacing.blade.php:1 Tailwind spacing utility `p-4` compiles to nothing — use `padding="space200"` (16px) on `<x-surface>`, or `var(--md-sys-measurement-space200)` in your own CSS',
|
||||||
"tailwind/spacing.blade.php:1 Tailwind spacing utility `px-2` {$hint}",
|
'tailwind/spacing.blade.php:1 Tailwind spacing utility `px-2` compiles to nothing — use `padding="space100"` (8px) on `<x-surface>`, or `var(--md-sys-measurement-space100)` in your own CSS',
|
||||||
"tailwind/spacing.blade.php:1 Tailwind spacing utility `mt-2` {$hint}",
|
"tailwind/spacing.blade.php:1 Tailwind spacing utility `mt-2` compiles to nothing — space between siblings is a layout component's `gap=\"space100\"` (8px); any other margin is `var(--md-sys-measurement-space100)` in your own CSS",
|
||||||
"tailwind/spacing.blade.php:1 Tailwind spacing utility `space-y-4` {$hint}",
|
'tailwind/spacing.blade.php:1 Tailwind spacing utility `space-y-4` compiles to nothing — use `<x-stack>` with `gap="space200"` (16px)',
|
||||||
"tailwind/spacing.blade.php:2 Tailwind spacing utility `m-4` {$hint}",
|
"tailwind/spacing.blade.php:2 Tailwind spacing utility `m-4` compiles to nothing — space between siblings is a layout component's `gap=\"space200\"` (16px); any other margin is `var(--md-sys-measurement-space200)` in your own CSS",
|
||||||
"tailwind/spacing.blade.php:2 Tailwind spacing utility `space-x-2` {$hint}",
|
'tailwind/spacing.blade.php:2 Tailwind spacing utility `space-x-2` compiles to nothing — use `<x-row>` with `gap="space100"` (8px)',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends width and height utilities to a literal length, since M3 keeps no size scale', function () {
|
it('sends width and height utilities to a literal length, since M3 keeps no size scale', function () {
|
||||||
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/sizing.blade.php'))->violations());
|
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/sizing.blade.php'))->violations());
|
||||||
|
|
||||||
$hint = 'compiles to nothing — M3 keeps no size scale; write the literal length in your own CSS, or use `<x-pane width>`/`<x-grid min-item>` where it fits';
|
|
||||||
|
|
||||||
expect($violations)->toBe([
|
expect($violations)->toBe([
|
||||||
"tailwind/sizing.blade.php:1 Tailwind sizing utility `w-full` {$hint}",
|
"tailwind/sizing.blade.php:1 Tailwind sizing utility `w-full` compiles to nothing — M3 keeps no size scale: `<x-pane width>` sets a content column's measure, `<x-icon size>` an icon's; anything else is a length in your own CSS",
|
||||||
"tailwind/sizing.blade.php:1 Tailwind sizing utility `h-screen` {$hint}",
|
"tailwind/sizing.blade.php:1 Tailwind sizing utility `h-screen` compiles to nothing — M3 keeps no size scale: `<x-pane width>` sets a content column's measure, `<x-icon size>` an icon's; anything else is a length in your own CSS",
|
||||||
"tailwind/sizing.blade.php:1 Tailwind sizing utility `min-w-0` {$hint}",
|
"tailwind/sizing.blade.php:1 Tailwind sizing utility `min-w-0` compiles to nothing — M3 keeps no size scale: `<x-pane width>` sets a content column's measure, `<x-icon size>` an icon's; anything else is a length in your own CSS",
|
||||||
"tailwind/sizing.blade.php:1 Tailwind sizing utility `max-w-md` {$hint}",
|
"tailwind/sizing.blade.php:1 Tailwind sizing utility `max-w-md` compiles to nothing — M3 keeps no size scale: `<x-pane width>` sets a content column's measure, `<x-icon size>` an icon's; anything else is a length in your own CSS",
|
||||||
"tailwind/sizing.blade.php:2 Tailwind sizing utility `size-4` {$hint}",
|
"tailwind/sizing.blade.php:2 Tailwind sizing utility `size-4` compiles to nothing — M3 keeps no size scale: `<x-pane width>` sets a content column's measure, `<x-icon size>` an icon's; anything else is a length in your own CSS",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -240,7 +235,7 @@ it('sends hidden to hide-below/hide-from and every other display utility to a pl
|
|||||||
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/display.blade.php'))->violations());
|
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/display.blade.php'))->violations());
|
||||||
|
|
||||||
expect($violations)->toBe([
|
expect($violations)->toBe([
|
||||||
"tailwind/display.blade.php:1 Tailwind's `hidden` compiles to nothing — use a layout component's `hide-below`/`hide-from` prop, or the reset's `[hidden]` attribute",
|
"tailwind/display.blade.php:1 Tailwind's `hidden` compiles to nothing — use a layout component's `hide-below`/`hide-from` prop, the `hidden` attribute (the reset keeps it hidden), or `x-show`",
|
||||||
'tailwind/display.blade.php:1 Tailwind display utility `block` compiles to nothing — write the `display` rule in your own CSS',
|
'tailwind/display.blade.php:1 Tailwind display utility `block` compiles to nothing — write the `display` rule in your own CSS',
|
||||||
'tailwind/display.blade.php:2 Tailwind display utility `inline-block` compiles to nothing — write the `display` rule in your own CSS',
|
'tailwind/display.blade.php:2 Tailwind display utility `inline-block` compiles to nothing — write the `display` rule in your own CSS',
|
||||||
'tailwind/display.blade.php:2 Tailwind display utility `invisible` compiles to nothing — write the `display` rule in your own CSS',
|
'tailwind/display.blade.php:2 Tailwind display utility `invisible` compiles to nothing — write the `display` rule in your own CSS',
|
||||||
@@ -264,7 +259,7 @@ it('exempts a class the application\'s own CSS declares, even when it is Tailwin
|
|||||||
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/exempt'))->violations());
|
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/exempt'))->violations());
|
||||||
|
|
||||||
expect($violations)->toBe([
|
expect($violations)->toBe([
|
||||||
"exempt/view.blade.php:1 Tailwind spacing utility `mt-2` compiles to nothing — use a layout component's `gap`/`padding` prop (`<x-stack gap>`, `<x-surface padding>`), or `var(--md-sys-measurement-space*)` in your own CSS",
|
"exempt/view.blade.php:1 Tailwind spacing utility `mt-2` compiles to nothing — space between siblings is a layout component's `gap=\"space100\"` (8px); any other margin is `var(--md-sys-measurement-space100)` in your own CSS",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -274,7 +269,9 @@ it('reports an arbitrary [] value once, whatever utility it modifies, and strips
|
|||||||
expect($violations)->toBe([
|
expect($violations)->toBe([
|
||||||
'tailwind/arbitrary.blade.php:1 Tailwind arbitrary value `top-[10px]` compiles to nothing — write the literal value in your own CSS, or use an M3 token',
|
'tailwind/arbitrary.blade.php:1 Tailwind arbitrary value `top-[10px]` compiles to nothing — write the literal value in your own CSS, or use an M3 token',
|
||||||
'tailwind/arbitrary.blade.php:1 Tailwind arbitrary value `grid-cols-[1fr_2fr]` compiles to nothing — write the literal value in your own CSS, or use an M3 token',
|
'tailwind/arbitrary.blade.php:1 Tailwind arbitrary value `grid-cols-[1fr_2fr]` compiles to nothing — write the literal value in your own CSS, or use an M3 token',
|
||||||
'tailwind/arbitrary.blade.php:2 Tailwind layout utility `flex` compiles to nothing — use `<x-row>`, `<x-grid>` or `<x-stack>` and their `gap`/`align`/`justify`/`columns` props',
|
'tailwind/arbitrary.blade.php:2 Tailwind layout utility `flex` compiles to nothing — use `<x-row>` (`gap`, `align`, `justify`, `wrap`, `stack-below`), or `<x-stack>` for a column',
|
||||||
|
'tailwind/arbitrary.blade.php:4 Tailwind arbitrary property `[--material-bottom-bar:5rem]` compiles to nothing — write `--material-bottom-bar` in your own CSS',
|
||||||
|
"tailwind/arbitrary.blade.php:4 Tailwind arbitrary property `[font-variation-settings:'ROND'_100]` compiles to nothing — write `font-variation-settings` in your own CSS",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -304,6 +301,76 @@ it('leaves every role alone until an application forbids one', function () {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('names the md-* class or token for a 1.x utility that compiled through the package\'s Tailwind theme', function () {
|
||||||
|
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/one-x.blade.php'))->violations());
|
||||||
|
|
||||||
|
expect($violations)->toBe([
|
||||||
|
'tailwind/one-x.blade.php:1 1.x utility `type-body-md` compiles to nothing — use `md-type-body-md` (text.css)',
|
||||||
|
'tailwind/one-x.blade.php:1 1.x utility `type-emphasized-title-lg` compiles to nothing — use `md-type-emphasized-title-lg` (text.css)',
|
||||||
|
'tailwind/one-x.blade.php:1 1.x utility `focus-ring` compiles to nothing — use `md-focus-ring` (interaction.css)',
|
||||||
|
'tailwind/one-x.blade.php:1 1.x utility `state-layer` compiles to nothing — use `md-state-layer` (interaction.css)',
|
||||||
|
'tailwind/one-x.blade.php:1 1.x utility `link` compiles to nothing — use `md-link` (interaction.css)',
|
||||||
|
'tailwind/one-x.blade.php:2 1.x utility `rounded-corner-lg` compiles to nothing — use `var(--md-sys-shape-corner-lg)` in your own CSS, or `<x-surface corner="lg">`',
|
||||||
|
'tailwind/one-x.blade.php:2 1.x utility `rounded-t-corner-xl-increased` compiles to nothing — use `var(--md-sys-shape-corner-xl-increased)` in your own CSS, or `<x-surface corner="xl-increased">`',
|
||||||
|
'tailwind/one-x.blade.php:2 1.x utility `shadow-elevation-2` compiles to nothing — use `var(--md-sys-elevation-2)` in your own CSS',
|
||||||
|
'tailwind/one-x.blade.php:2 1.x utility `ease-spatial-fast` compiles to nothing — pair `var(--md-sys-motion-spatial-fast)` with `var(--md-sys-motion-spatial-fast-duration)` in your own `transition`',
|
||||||
|
'tailwind/one-x.blade.php:2 1.x utility `ease-emphasized-decelerate` compiles to nothing — use `var(--md-sys-motion-easing-emphasized-decelerate)` in your own `transition`',
|
||||||
|
'tailwind/one-x.blade.php:2 Tailwind custom-property utility `duration-(--md-sys-motion-spatial-fast-duration)` compiles to nothing — write `var(--md-sys-motion-spatial-fast-duration)` in your own CSS',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports position, border, effect, interactivity and text utilities with the rule to write instead', function () {
|
||||||
|
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/utilities.blade.php'))->violations());
|
||||||
|
|
||||||
|
expect($violations)->toBe([
|
||||||
|
"tailwind/utilities.blade.php:1 Tailwind position utility `relative` compiles to nothing — write `position` in your own CSS (a FAB goes in `<x-scaffold>`'s `fab` slot)",
|
||||||
|
'tailwind/utilities.blade.php:1 Tailwind inset utility `inset-0` compiles to nothing — write the offset in your own CSS, from `var(--md-sys-measurement-space*)` where it is a spacing step',
|
||||||
|
'tailwind/utilities.blade.php:1 Tailwind inset utility `-top-2` compiles to nothing — write the offset in your own CSS, from `var(--md-sys-measurement-space*)` where it is a spacing step',
|
||||||
|
'tailwind/utilities.blade.php:1 Tailwind z-index utility `z-10` compiles to nothing — write `z-index` in your own CSS',
|
||||||
|
'tailwind/utilities.blade.php:1 Tailwind overflow utility `overflow-hidden` compiles to nothing — write `overflow` in your own CSS',
|
||||||
|
"tailwind/utilities.blade.php:1 Tailwind flex/grid item utility `shrink-0` compiles to nothing — the layout components arrange their children; an item's own `flex`, `order` or `grid-column` is a rule in your own CSS",
|
||||||
|
"tailwind/utilities.blade.php:1 Tailwind flex/grid item utility `col-start-2` compiles to nothing — the layout components arrange their children; an item's own `flex`, `order` or `grid-column` is a rule in your own CSS",
|
||||||
|
"tailwind/utilities.blade.php:1 Tailwind flex/grid item utility `justify-self-end` compiles to nothing — the layout components arrange their children; an item's own `flex`, `order` or `grid-column` is a rule in your own CSS",
|
||||||
|
"tailwind/utilities.blade.php:1 Tailwind's `container` compiles to nothing — use `<x-pane width>`, which sets M3's margins and a measure",
|
||||||
|
'tailwind/utilities.blade.php:2 Tailwind border utility `border` compiles to nothing — a line is `<x-divider>` or `<x-surface outlined>`; any other border is your own CSS, in `var(--md-sys-color-outline-variant)`',
|
||||||
|
'tailwind/utilities.blade.php:2 Tailwind border utility `border-t-2` compiles to nothing — a line is `<x-divider>` or `<x-surface outlined>`; any other border is your own CSS, in `var(--md-sys-color-outline-variant)`',
|
||||||
|
'tailwind/utilities.blade.php:2 Tailwind radius utility `rounded` compiles to nothing — use `var(--md-sys-shape-corner-xs)` in your own CSS, or `<x-surface corner="xs">`',
|
||||||
|
'tailwind/utilities.blade.php:2 Tailwind shadow utility `shadow` compiles to nothing — use `var(--md-sys-elevation-*)` in your own CSS',
|
||||||
|
"tailwind/utilities.blade.php:2 Tailwind outline utility `outline-none` compiles to nothing — M3's focus indicator is `md-focus-ring` (interaction.css); any other outline is your own CSS",
|
||||||
|
"tailwind/utilities.blade.php:2 Tailwind outline utility `ring-2` compiles to nothing — M3's focus indicator is `md-focus-ring` (interaction.css); any other outline is your own CSS",
|
||||||
|
'tailwind/utilities.blade.php:2 Tailwind motion utility `transition` compiles to nothing — pair `var(--md-sys-motion-spatial-*)`/`var(--md-sys-motion-effects-*)` with its `-duration` in your own `transition` or `animation`',
|
||||||
|
"tailwind/utilities.blade.php:2 Tailwind opacity utility `opacity-50` compiles to nothing — write `opacity` in your own CSS (M3's disabled content is 38 %)",
|
||||||
|
'tailwind/utilities.blade.php:2 Tailwind effect utility `-rotate-45` compiles to nothing — write the rule in your own CSS',
|
||||||
|
'tailwind/utilities.blade.php:2 Tailwind interactivity utility `cursor-pointer` compiles to nothing — write the rule in your own CSS',
|
||||||
|
'tailwind/utilities.blade.php:2 Tailwind interactivity utility `select-none` compiles to nothing — write the rule in your own CSS',
|
||||||
|
'tailwind/utilities.blade.php:3 Tailwind media utility `aspect-video` compiles to nothing — write `aspect-ratio`/`object-fit` in your own CSS',
|
||||||
|
'tailwind/utilities.blade.php:3 Tailwind media utility `object-cover` compiles to nothing — write `aspect-ratio`/`object-fit` in your own CSS',
|
||||||
|
'tailwind/utilities.blade.php:3 Tailwind text utility `font-mono` compiles to nothing — write the rule in your own CSS',
|
||||||
|
"tailwind/utilities.blade.php:3 Tailwind's `font-sans` compiles to nothing — the foundation already sets the brand typeface; any other `font-family` is your own CSS",
|
||||||
|
"tailwind/utilities.blade.php:3 Tailwind's `underline` compiles to nothing — `md-link` draws a link (interaction.css); any other decoration is your own CSS",
|
||||||
|
'tailwind/utilities.blade.php:3 Tailwind text utility `uppercase` compiles to nothing — write the rule in your own CSS',
|
||||||
|
"tailwind/utilities.blade.php:3 Tailwind's `text-end` compiles to nothing — use `md-text-end` (text.css)",
|
||||||
|
'tailwind/utilities.blade.php:3 Tailwind text utility `list-disc` compiles to nothing — write the rule in your own CSS',
|
||||||
|
'tailwind/utilities.blade.php:3 Tailwind display utility `table` compiles to nothing — write the `display` rule in your own CSS',
|
||||||
|
'tailwind/utilities.blade.php:3 Tailwind background utility `bg-clip-text` compiles to nothing — write the rule in your own CSS',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reads class lists in Alpine, Livewire and PHP bindings, but not a string compared in a condition', function () {
|
||||||
|
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/bindings.blade.php'))->violations());
|
||||||
|
|
||||||
|
expect($violations)->toBe([
|
||||||
|
"tailwind/bindings.blade.php:1 Tailwind's `hidden` compiles to nothing — use a layout component's `hide-below`/`hide-from` prop, the `hidden` attribute (the reset keeps it hidden), or `x-show`",
|
||||||
|
"tailwind/bindings.blade.php:1 Tailwind opacity utility `opacity-50` compiles to nothing — write `opacity` in your own CSS (M3's disabled content is 38 %)",
|
||||||
|
'tailwind/bindings.blade.php:2 Tailwind motion utility `transition` compiles to nothing — pair `var(--md-sys-motion-spatial-*)`/`var(--md-sys-motion-effects-*)` with its `-duration` in your own `transition` or `animation`',
|
||||||
|
'tailwind/bindings.blade.php:2 Tailwind effect utility `scale-95` compiles to nothing — write the rule in your own CSS',
|
||||||
|
'tailwind/bindings.blade.php:3 Tailwind spacing utility `gap-2` compiles to nothing — use `gap="space100"` (8px) on `<x-row>`, `<x-stack>`, `<x-grid>` or `<x-feed>`',
|
||||||
|
"tailwind/bindings.blade.php:4 Tailwind spacing utility `mt-2` compiles to nothing — space between siblings is a layout component's `gap=\"space100\"` (8px); any other margin is `var(--md-sys-measurement-space100)` in your own CSS",
|
||||||
|
'tailwind/bindings.blade.php:6 Tailwind layout utility `flex` compiles to nothing — use `<x-row>` (`gap`, `align`, `justify`, `wrap`, `stack-below`), or `<x-stack>` for a column',
|
||||||
|
'tailwind/bindings.blade.php:2 value outside the M3 scale `ease-out` — pair `var(--md-sys-motion-spatial-*)`/`var(--md-sys-motion-effects-*)` with its `-duration` in your own `transition`',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('bans any further pattern', function () {
|
it('bans any further pattern', function () {
|
||||||
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/views'))
|
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/views'))
|
||||||
->forbid('/\bfocus-ring\b/', 'a retired utility')
|
->forbid('/\bfocus-ring\b/', 'a retired utility')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="rounded-none rounded-xs rounded-sm rounded-md rounded-lg">
|
<div class="rounded-none rounded-xs rounded-sm rounded-md rounded-lg">
|
||||||
<span class="rounded-xl rounded-2xl rounded-3xl rounded-4xl rounded-full"></span>
|
<span class="rounded-xl rounded-2xl rounded-3xl rounded-4xl rounded-full"></span>
|
||||||
<span class="rounded-t-lg rounded-se-2xl"></span>
|
<span class="rounded-t-lg rounded-se-2xl"></span>
|
||||||
<span class="rounded-corner-lg rounded-corner-xxl rounded-corner-full">M3's own are fine</span>
|
<span class="md-type-body-md" style="border-radius: var(--md-sys-shape-corner-lg)">M3's own are fine</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="shadow-2xs shadow-xs shadow-sm shadow-md">
|
<div class="shadow-2xs shadow-xs shadow-sm shadow-md">
|
||||||
<span class="shadow-lg shadow-xl shadow-2xl"></span>
|
<span class="shadow-lg shadow-xl shadow-2xl"></span>
|
||||||
<span class="shadow-elevation-1 shadow-elevation-3 shadow-elevation-5">M3's own are fine</span>
|
<span class="md-type-body-md" style="box-shadow: var(--md-sys-elevation-1)">M3's own are fine</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="ease-linear ease-in ease-out ease-in-out">
|
<div class="ease-linear ease-in ease-out ease-in-out">
|
||||||
<span class="duration-75 duration-300 duration-1000"></span>
|
<span class="duration-75 duration-300 duration-1000"></span>
|
||||||
<span class="ease-standard ease-spatial-fast ease-effects-default duration-(--md-sys-motion-effects-fast-duration)">M3's own are fine</span>
|
<span class="md-type-body-md">M3's own are fine</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
<span class="font-semibold font-bold font-extrabold font-black"></span>
|
<span class="font-semibold font-bold font-extrabold font-black"></span>
|
||||||
<span class="leading-none leading-tight leading-snug leading-normal leading-relaxed leading-loose leading-6"></span>
|
<span class="leading-none leading-tight leading-snug leading-normal leading-relaxed leading-loose leading-6"></span>
|
||||||
<span class="tracking-tighter tracking-tight tracking-normal tracking-wide tracking-wider tracking-widest"></span>
|
<span class="tracking-tighter tracking-tight tracking-normal tracking-wide tracking-wider tracking-widest"></span>
|
||||||
<span class="type-body-md type-title-lg type-emphasized-label-lg">M3's own are fine</span>
|
<span class="md-type-body-md md-type-title-lg md-type-emphasized-label-lg">M3's own are fine</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
<div class="top-[10px] grid-cols-[1fr_2fr]">
|
<div class="top-[10px] grid-cols-[1fr_2fr]">
|
||||||
<span class="flex!"></span>
|
<span class="flex!"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="[--material-bottom-bar:5rem] [font-variation-settings:'ROND'_100]"></div>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<div x-data="{ view: 'grid' }" :class="{ 'is-open': open, hidden: !open, 'md-ink': view === 'grid' }" wire:loading.class="opacity-50"
|
||||||
|
x-transition:enter="transition ease-out" x-transition:enter-start="scale-95">
|
||||||
|
<ul @class(["gap-2", 'share-list', 'md-ink-quiet' => $status === 'hidden', 'md-ink' => isActive('block')])
|
||||||
|
{{ $attributes->merge(['class' => 'mt-2']) }}>
|
||||||
|
<li class="md-truncate
|
||||||
|
flex">Row</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<div class="type-body-md type-emphasized-title-lg focus-ring state-layer link">
|
||||||
|
<span class="rounded-corner-lg rounded-t-corner-xl-increased shadow-elevation-2 ease-spatial-fast ease-emphasized-decelerate duration-(--md-sys-motion-spatial-fast-duration)"></span>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="relative inset-0 -top-2 z-10 overflow-hidden shrink-0 col-start-2 justify-self-end container">
|
||||||
|
<span class="border border-t-2 rounded shadow outline-none ring-2 transition opacity-50 -rotate-45 cursor-pointer select-none"></span>
|
||||||
|
<span class="aspect-video object-cover font-mono font-sans underline uppercase text-end list-disc table bg-clip-text"></span>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user