An over-engineering audit of the whole tree, applied in five reviewed batches. Behaviour stays the same except where UPGRADE.md says otherwise. PHP: the showcase and error-page stylesheets are prebuilt into resources/dist by bin/stylesheets.mjs, through Vite's own postcss-import (first occurrence kept, the order an application's build gives), instead of Stylesheets::bundle() inlining imports on every request; only the import walk DesignGuard needs stays. SchemeStylesheet::withProfiles() replaces three copies of the scheme-plus-profiles loop, material:scheme leaves spec and contrast checks to the node script that already made them, and the error page's scheme cache, the hashed view namespace, the translations path with no lang/ folder and DesignGuard's 1.x-name hints are gone. JS: the androidx shape port progress.js and both bin scripts each carried lives once in resources/js/shapes.js (the generated SVGs are unchanged); util.js holds ringIndex(), ms(), reopenGuard() and remember(), which were written out several times; listeners are released through AbortController; tooltip.js's hoverPopover() serves the rich tooltip too. CSS: every rule for an element inside the navigation rail queries `--md-navigation-rail-value` instead of repeating the seven collapsed conditions under five media branches; badge, alert, progress, slider and button read one non-inheriting colour-role table (components/color.css); the dialog chrome, the submenu's popover chrome, the chip's state layer and touch target, and the visually-hidden inputs use the shared rules they copied; foundation/tokens.css is folded into foundation.css. Views: Support\Field and Support\Link replace the error-key, bound-value and link-attribute blocks copied into the fields and link components; the timepicker period group, the menu filter and the showcase head are partials; the datepicker's steppers and entry fields are loops; component docblocks no longer restate SKILL.md. Tests and tooling: one dataset-driven ComponentStylesheetsTest replaces four per-group files, DesignGuardTest and the layout-component tests use datasets, browser tests share one ready() helper, CSS parsing lives in ComponentStylesheet alone. docs/audits and the finding IDs citing it are removed, as are pestphp/pest-plugin-laravel, the unused composer scripts and check:font; the lint job runs in the feature job, which now installs node packages so the prebuilt-stylesheet staleness test runs in CI. Feature suite 1177 passed, Chrome browser suite 299 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
167 lines
7.8 KiB
CSS
167 lines
7.8 KiB
CSS
/*
|
|
* <x-button-group>: related buttons in a row, and the shapes a button takes from its place among
|
|
* its siblings. The connected form is also what `<x-group>` draws its segments with, which is why
|
|
* its stylesheet imports this one. A group never wraps: M3's rule, and the press expansion only
|
|
* reaches a neighbour on the same line anyway.
|
|
*
|
|
* Standard (`data-md-button-group="standard"`): the buttons stand apart — 18/12/8/8/8px by size
|
|
* (ButtonGroupXSmall…XLargeTokens.BetweenSpace) — and pressing a label button widens it and
|
|
* narrows its neighbours by the same amount, so the row keeps its length: Expressive's press
|
|
* expansion (ButtonGroupDefaults.ExpandedRatio is 15%; this uses a fixed step per size, 4/6/8/16/
|
|
* 20px, on the button's own padding of 16/16/24/48/64px). Icon buttons keep their width. A square
|
|
* standard group has no inner edges, so it hands its buttons the square corner scale
|
|
* `<x-button shape="square">` draws (12/12/16/28/28px, 8/8/12/16/16px under the finger), and a
|
|
* selected button in it turns round.
|
|
*
|
|
* Connected (`data-md-button-group="connected"`): 2px apart, the outer corners full, the inner
|
|
* corners small, smaller still while pressed, and a selected segment (`aria-pressed`, or a checked
|
|
* input inside it) fully round — ConnectedButtonGroupXSmall…XLargeTokens and the M3 Expressive
|
|
* connected button group spec: inner corners 4/8/8/16/20px, pressed 2/4/4/12/16px. At the two
|
|
* smallest sizes a segment is 32px or 40px tall, and M3 names both numbers it must not go below:
|
|
* "XS and S connected button groups have a 48dp target area and a 48dp minimum width".
|
|
*
|
|
* Shape (`data-md-shape="square"`, M3's "Default shape | Round, square" configuration):
|
|
* `--md-button-group-outer` is what the two ends of a group round to — half the height for a round
|
|
* group, and for a square one the corner its inner edges take, which is the square table M3
|
|
* publishes for connected groups (4/8/8/16/20dp by size). Selection morphs the other way: M3 has a
|
|
* toggle inside a group "swap shape square/round on selection", so a selected segment in a square
|
|
* group turns round, which is why the selected rule keeps `--md-button-group-full`.
|
|
*
|
|
* "Full" is half the size's height (16/20/28/48/68px), never `--md-sys-shape-corner-full`'s
|
|
* 9999px. One element mixes full outer corners with small inner ones, and when a box's radii add
|
|
* up to more than its side, CSS scales every radius by the same factor: a 9999px corner beside an
|
|
* 8px one shrank the 8px one to a hundredth of a pixel, so the inner corners drew square.
|
|
*
|
|
* <x-button>'s own corner rules carry no specificity, so these reshape the buttons in a group
|
|
* whatever selector they use; a pressed or selected segment changes one custom property and the
|
|
* outer corners stay put.
|
|
*/
|
|
|
|
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
|
|
|
@layer material.components {
|
|
[data-md-button-group] {
|
|
--md-button-group-inner: var(--md-sys-shape-corner-sm);
|
|
--md-button-group-inner-pressed: var(--md-sys-shape-corner-xs);
|
|
--md-button-group-full: 20px;
|
|
--md-button-group-square: var(--md-sys-shape-corner-md);
|
|
--md-button-group-square-pressed: var(--md-sys-shape-corner-sm);
|
|
--md-button-group-outer: var(--md-button-group-full);
|
|
--md-button-group-pad: var(--md-sys-measurement-space200);
|
|
--md-button-group-grow: 6px;
|
|
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
[data-md-button-group][data-md-size='xs'] {
|
|
--md-button-group-inner: var(--md-sys-shape-corner-xs);
|
|
--md-button-group-inner-pressed: 2px;
|
|
--md-button-group-full: 16px;
|
|
--md-button-group-grow: 4px;
|
|
}
|
|
|
|
[data-md-button-group][data-md-size='md'] {
|
|
--md-button-group-full: 28px;
|
|
--md-button-group-square: var(--md-sys-shape-corner-lg);
|
|
--md-button-group-square-pressed: var(--md-sys-shape-corner-md);
|
|
--md-button-group-pad: var(--md-sys-measurement-space300);
|
|
--md-button-group-grow: 8px;
|
|
}
|
|
|
|
[data-md-button-group][data-md-size='lg'] {
|
|
--md-button-group-inner: var(--md-sys-shape-corner-lg);
|
|
--md-button-group-inner-pressed: var(--md-sys-shape-corner-md);
|
|
--md-button-group-full: 48px;
|
|
--md-button-group-square: var(--md-sys-shape-corner-xl);
|
|
--md-button-group-square-pressed: var(--md-sys-shape-corner-lg);
|
|
--md-button-group-pad: var(--md-sys-measurement-space600);
|
|
--md-button-group-grow: 16px;
|
|
}
|
|
|
|
[data-md-button-group][data-md-size='xl'] {
|
|
--md-button-group-inner: var(--md-sys-shape-corner-lg-increased);
|
|
--md-button-group-inner-pressed: var(--md-sys-shape-corner-lg);
|
|
--md-button-group-full: 68px;
|
|
--md-button-group-square: var(--md-sys-shape-corner-xl);
|
|
--md-button-group-square-pressed: var(--md-sys-shape-corner-lg);
|
|
--md-button-group-pad: var(--md-sys-measurement-space800);
|
|
--md-button-group-grow: 20px;
|
|
}
|
|
|
|
[data-md-button-group='standard'] {
|
|
gap: var(--md-sys-measurement-space100);
|
|
}
|
|
|
|
[data-md-button-group='standard'][data-md-size='xs'] {
|
|
gap: 18px;
|
|
}
|
|
|
|
[data-md-button-group='standard'][data-md-size='sm'] {
|
|
gap: 12px;
|
|
}
|
|
|
|
/* A square standard group hands its buttons the square corner scale; a selected one rounds. */
|
|
[data-md-button-group='standard'][data-md-shape='square'] > * {
|
|
border-radius: var(--md-button-group-square);
|
|
}
|
|
|
|
[data-md-button-group='standard'][data-md-shape='square'] > :active:not(:disabled, [aria-disabled='true']) {
|
|
border-radius: var(--md-button-group-square-pressed);
|
|
}
|
|
|
|
[data-md-button-group='standard'][data-md-shape='square'] > [aria-pressed='true'] {
|
|
border-radius: var(--md-button-group-full);
|
|
}
|
|
|
|
/* Press expansion: the pressed label button grows, the label buttons beside it give way. */
|
|
[data-md-button-group='standard'] > :not([data-md-icon-button]):active:not(:disabled, [aria-disabled='true']) {
|
|
padding-inline: calc(var(--md-button-group-pad) + var(--md-button-group-grow));
|
|
}
|
|
|
|
[data-md-button-group='standard'] > :not([data-md-icon-button]):has(+ :not([data-md-icon-button]):active:not(:disabled, [aria-disabled='true'])) {
|
|
padding-inline-end: calc(var(--md-button-group-pad) - var(--md-button-group-grow));
|
|
}
|
|
|
|
[data-md-button-group='standard'] > :not([data-md-icon-button]):active:not(:disabled, [aria-disabled='true']) + :not([data-md-icon-button]) {
|
|
padding-inline-start: calc(var(--md-button-group-pad) - var(--md-button-group-grow));
|
|
}
|
|
|
|
/* A square group's ends take the corner its inner edges take. */
|
|
[data-md-button-group][data-md-shape='square'] {
|
|
--md-button-group-outer: var(--md-button-group-inner);
|
|
}
|
|
|
|
[data-md-button-group='connected'] {
|
|
gap: var(--md-sys-measurement-space25);
|
|
}
|
|
|
|
[data-md-button-group='connected']:is([data-md-size='xs'], [data-md-size='sm']) > * {
|
|
min-inline-size: var(--md-sys-measurement-space600);
|
|
}
|
|
|
|
[data-md-button-group='connected'] > * {
|
|
--md-button-group-corner: var(--md-button-group-inner);
|
|
|
|
border-radius: var(--md-button-group-corner);
|
|
}
|
|
|
|
[data-md-button-group='connected'] > :active {
|
|
--md-button-group-corner: var(--md-button-group-inner-pressed);
|
|
}
|
|
|
|
[data-md-button-group='connected'] > :is([aria-pressed='true'], :has(:checked)) {
|
|
--md-button-group-corner: var(--md-button-group-full);
|
|
}
|
|
|
|
[data-md-button-group='connected'] > :first-child {
|
|
border-start-start-radius: var(--md-button-group-outer);
|
|
border-end-start-radius: var(--md-button-group-outer);
|
|
}
|
|
|
|
[data-md-button-group='connected'] > :last-child {
|
|
border-start-end-radius: var(--md-button-group-outer);
|
|
border-end-end-radius: var(--md-button-group-outer);
|
|
}
|
|
}
|