diff --git a/resources/css/components.css b/resources/css/components.css index 16372094..cd0fe72f 100644 --- a/resources/css/components.css +++ b/resources/css/components.css @@ -21,6 +21,9 @@ @import './components/textarea.css'; @import './components/select.css'; @import './components/file.css'; +@import './components/checkbox.css'; +@import './components/radio.css'; +@import './components/toggle.css'; /* Containment */ diff --git a/resources/css/components/checkbox.css b/resources/css/components/checkbox.css new file mode 100644 index 00000000..a1e7dfe2 --- /dev/null +++ b/resources/css/components/checkbox.css @@ -0,0 +1,114 @@ +/* + * : M3's checkbox (resources/views/components/checkbox.blade.php), on the row the + * selection controls share (components/selection.css). + * + * CheckboxTokens (androidx Compose Material 3, Apache-2.0): an 18px box with a 2px on-surface-variant + * outline and 2px corners, filled with primary and holding an on-primary tick (or dash, for the + * mixed state) at the 18px icon size once it is ticked; the state layer a 40px circle around it, + * 11px past each edge. The tick grows in and fades in on the fast spatial spring. With the server's + * error the outline, the fill and the state layer are error and the tick on-error. Disabled, the + * outline and the fill are on-surface at 38% and the tick is surface. Beside a hint the box drops + * 3px, to sit on the label's first line. + * + * [data-md-checkbox] the root: the row and its errors; `class` and `style` land here + * [data-md-selection-row] + * [data-md-checkbox-box] the box: input, [data-md-checkbox-check], [data-md-checkbox-mixed] + * [data-md-selection-support] the errors, 4px under the row + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@import './selection.css'; +@import './icon.css'; + +@layer material.components { + [data-md-checkbox] { + min-width: 0; + } + + [data-md-checkbox-box] { + position: relative; + display: grid; + flex: none; + place-items: center; + width: 18px; + height: 18px; + } + + [data-md-checkbox-box] > * { + grid-area: 1 / 1; + } + + [data-md-selection-row]:has([data-md-selection-hint]) [data-md-checkbox-box] { + margin-top: 3px; + } + + [data-md-checkbox-box]::before { + inset: -11px; + } + + [data-md-checkbox-box]:has(input:focus-visible)::before { + outline: 3px solid var(--md-sys-color-secondary); + } + + [data-md-checkbox-box] input { + width: 18px; + height: 18px; + border: 2px solid var(--md-sys-color-on-surface-variant); + border-radius: 2px; + } + + [data-md-checkbox-box] input:is(:checked, :indeterminate) { + border-color: var(--md-sys-color-primary); + background-color: var(--md-sys-color-primary); + } + + [data-md-checkbox-box] :is([data-md-checkbox-check], [data-md-checkbox-mixed]) { + color: var(--md-sys-color-on-primary); + opacity: 0; + scale: 0.5; + pointer-events: none; + transition-property: opacity, scale; + transition-duration: var(--md-sys-motion-spatial-fast-duration); + transition-timing-function: var(--md-sys-motion-spatial-fast); + } + + [data-md-checkbox-box] input:checked:not(:indeterminate) ~ [data-md-checkbox-check], + [data-md-checkbox-box] input:indeterminate ~ [data-md-checkbox-mixed] { + opacity: 1; + scale: 1; + } + + [data-md-selection-row][data-md-invalid] [data-md-checkbox-box] input { + border-color: var(--md-sys-color-error); + } + + [data-md-selection-row][data-md-invalid] [data-md-checkbox-box] input:is(:checked, :indeterminate) { + background-color: var(--md-sys-color-error); + } + + [data-md-selection-row][data-md-invalid] [data-md-checkbox-box] :is([data-md-checkbox-check], [data-md-checkbox-mixed]) { + color: var(--md-sys-color-on-error); + } + + [data-md-checkbox-box] input:disabled { + border-color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent); + } + + [data-md-checkbox-box] input:disabled:is(:checked, :indeterminate) { + border-color: transparent; + background-color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent); + } + + [data-md-checkbox-box] input:disabled ~ :is([data-md-checkbox-check], [data-md-checkbox-mixed]) { + color: var(--md-sys-color-surface); + } + + [data-md-checkbox] [data-md-selection-support] { + margin-top: var(--md-sys-measurement-space50); + color: var(--md-sys-color-error); + font: var(--md-sys-typescale-body-sm); + letter-spacing: var(--md-sys-typescale-body-sm-tracking); + font-variation-settings: normal; + } +} diff --git a/resources/css/components/radio.css b/resources/css/components/radio.css new file mode 100644 index 00000000..15ffe431 --- /dev/null +++ b/resources/css/components/radio.css @@ -0,0 +1,79 @@ +/* + * : M3's radio buttons (resources/views/components/radio.blade.php), on the row the + * selection controls share (components/selection.css). + * + * RadioButtonTokens (androidx Compose Material 3, Apache-2.0): a 20px ring with a 2px + * on-surface-variant outline, primary once chosen, and a 10px primary dot that grows in on the fast + * spatial spring; the state layer a 40px circle around it. With the server's error the ring and + * the dot are error. Disabled, both are on-surface at 38%. + * + * [data-md-radio-button] the ring: input, [data-md-radio-dot] + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@import './selection.css'; + +@layer material.components { + [data-md-radio-button] { + position: relative; + display: grid; + flex: none; + place-items: center; + width: 20px; + height: 20px; + } + + [data-md-radio-button] > * { + grid-area: 1 / 1; + } + + [data-md-radio-button]::before { + inset: -10px; + } + + [data-md-radio-button]:has(input:focus-visible)::before { + outline: 3px solid var(--md-sys-color-secondary); + } + + [data-md-radio-button] input { + width: 20px; + height: 20px; + border: 2px solid var(--md-sys-color-on-surface-variant); + border-radius: var(--md-sys-shape-corner-full); + } + + [data-md-radio-button] [data-md-radio-dot] { + width: 10px; + height: 10px; + border-radius: var(--md-sys-shape-corner-full); + background-color: var(--md-sys-color-primary); + scale: 0; + pointer-events: none; + transition: scale var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast); + } + + [data-md-radio-button] input:checked { + border-color: var(--md-sys-color-primary); + } + + [data-md-radio-button] input:checked ~ [data-md-radio-dot] { + scale: 1; + } + + [data-md-selection-row][data-md-invalid] [data-md-radio-button] input { + border-color: var(--md-sys-color-error); + } + + [data-md-selection-row][data-md-invalid] [data-md-radio-button] [data-md-radio-dot] { + background-color: var(--md-sys-color-error); + } + + [data-md-radio-button] input:disabled { + border-color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent); + } + + [data-md-radio-button] input:disabled ~ [data-md-radio-dot] { + background-color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent); + } +} diff --git a/resources/css/components/selection.css b/resources/css/components/selection.css index 63306638..e423fdc6 100644 --- a/resources/css/components/selection.css +++ b/resources/css/components/selection.css @@ -1,70 +1,90 @@ /* - * M3's selection controls: the checkbox, the radio button and the switch (CheckboxTokens, - * RadioButtonTokens, SwitchTokens, androidx Compose Material 3, Apache-2.0). + * What M3's three selection controls share: the checkbox (components/checkbox.css), the radio button + * (components/radio.css) and the switch (components/toggle.css) — CheckboxTokens, RadioButtonTokens, + * SwitchTokens, androidx Compose Material 3, Apache-2.0. Each control's stylesheet imports this one. * * Each is a real `` with its appearance removed and drawn over, so focus, the keyboard * (Space; the arrow keys between radios), the form value, `wire:model` and what a screen reader - * announces stay the browser's. The row around it is a `