Draw the checkbox and the selection controls' row without Tailwind
<x-checkbox> renders data-md-checkbox, its box, tick and dash, and its row, text and errors as data-md-* attributes; the 18px tick takes a size prop and a label-less box md-touch-target (plan step 36). selection.css moves into material.components as the row, text and state layer the three controls share, and each control's drawing goes to its own file: checkbox.css, radio.css, toggle.css, on the state tokens. The radio and the switch take the renamed shared hooks now and lose their layout classes in their own commits. field.js follows data-md-indeterminate. Renaming the switch's data-handle also stops selection.css matching the slider's handles, which it drew 24px above the track. 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
3083fe28b2
commit
f86afc0542
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* <x-checkbox>: 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* <x-radio>: 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);
|
||||
}
|
||||
}
|
||||
@@ -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 `<input>` 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 `<label>`, so its text is pressable too.
|
||||
* Hooks are data attributes, not class names: `checkbox`, `radio` and `toggle` are daisyUI's, which
|
||||
* the design guard rejects.
|
||||
* announces stay the browser's. The row around it is a `<label>`, so its text is pressable too:
|
||||
*
|
||||
* [data-selection] the <label> row; `data-invalid` when the server has an error for it
|
||||
* [data-checkbox] the 18px box, its state layer a 40px circle around it
|
||||
* input, [data-check], [data-mixed]
|
||||
* [data-radio] the 20px ring and its 10px dot
|
||||
* input, [data-dot]
|
||||
* [data-switch] the 52×32 track; `data-icons` = "both" or "selected"
|
||||
* input, [data-handle] (with [data-on] and [data-off] icons)
|
||||
* [data-md-selection-row] the <label>: the control and its text, 16px apart; with a hint
|
||||
* the control aligns to the first line; `data-md-right` puts the
|
||||
* control at the end; `data-md-invalid` when the server has an error
|
||||
* [data-md-checkbox-box] | [data-md-radio-button] | [data-md-switch] the control
|
||||
* [data-md-selection-text] the label (body-large, on-surface) and the hint under it
|
||||
* [data-md-selection-label], [data-md-selection-hint]
|
||||
*
|
||||
* Every state layer is on-surface when unselected and primary when selected, 8% on hover (only
|
||||
* where a pointer can hover) and 10% on focus and press; the focus indicator is the package's
|
||||
* secondary ring. A disabled control is on-surface at 38% and has no state layer.
|
||||
* Every state layer is on-surface when unselected and primary when selected, at M3's hover opacity
|
||||
* (only where a pointer can hover) and its focus and press opacities (tokens/state.css); the focus
|
||||
* indicator is the package's secondary ring. A disabled control is on-surface at 38% and has no
|
||||
* state layer.
|
||||
*
|
||||
* The state layer is a ::before with no pointer events, so it is not a target: a labelled control
|
||||
* is pressed anywhere along its row, and a control with no label wears the `touch-target` utility
|
||||
* (resources/css/tokens/state.css) so its box catches presses over M3's 48px minimum.
|
||||
* is pressed anywhere along its row, and a control with no label wears `md-touch-target`
|
||||
* (foundation/interaction.css) so its box catches presses over M3's 48px minimum.
|
||||
*/
|
||||
|
||||
@layer components {
|
||||
[data-selection] {
|
||||
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
||||
|
||||
@layer material.components {
|
||||
[data-md-selection-row] {
|
||||
/* The state layer's tint. */
|
||||
--selection-ink: var(--md-sys-color-on-surface);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--md-sys-measurement-space200);
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
[data-selection]:has(input:disabled) {
|
||||
[data-md-selection-row]:has([data-md-selection-hint]) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
[data-md-selection-row][data-md-right] {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
[data-md-selection-row]:has(input:disabled) {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
[data-selection]:has(input:disabled) [data-selection-label] {
|
||||
color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||||
[data-md-selection-text] {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-checkbox],
|
||||
[data-radio] {
|
||||
position: relative;
|
||||
display: grid;
|
||||
flex: none;
|
||||
place-items: center;
|
||||
[data-md-selection-label] {
|
||||
display: block;
|
||||
color: var(--md-sys-color-on-surface);
|
||||
font: var(--md-sys-typescale-body-lg);
|
||||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
[data-checkbox] > *,
|
||||
[data-radio] > * {
|
||||
grid-area: 1 / 1;
|
||||
[data-md-selection-hint] {
|
||||
display: block;
|
||||
margin-top: var(--md-sys-measurement-space25);
|
||||
color: var(--md-sys-color-on-surface-variant);
|
||||
font: var(--md-sys-typescale-body-sm);
|
||||
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
[data-checkbox]::before,
|
||||
[data-radio]::before,
|
||||
[data-handle]::before {
|
||||
content: "";
|
||||
[data-md-selection-row]:has(input:disabled) [data-md-selection-label] {
|
||||
color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
|
||||
}
|
||||
|
||||
:is([data-md-checkbox-box], [data-md-radio-button], [data-md-switch-handle])::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
pointer-events: none;
|
||||
transition: background-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||||
}
|
||||
|
||||
[data-checkbox] input,
|
||||
[data-radio] input,
|
||||
[data-switch] input {
|
||||
:is([data-md-checkbox-box], [data-md-radio-button], [data-md-switch]) input {
|
||||
margin: 0;
|
||||
appearance: none;
|
||||
cursor: inherit;
|
||||
@@ -74,275 +94,26 @@
|
||||
transition-timing-function: var(--md-sys-motion-effects-fast);
|
||||
}
|
||||
|
||||
/* The state layer. `--selection-ink` is what it is tinted with. */
|
||||
[data-selection] {
|
||||
--selection-ink: var(--md-sys-color-on-surface);
|
||||
}
|
||||
|
||||
[data-selection]:has(input:checked),
|
||||
[data-selection]:has(input:indeterminate) {
|
||||
[data-md-selection-row]:has(input:checked),
|
||||
[data-md-selection-row]:has(input:indeterminate) {
|
||||
--selection-ink: var(--md-sys-color-primary);
|
||||
}
|
||||
|
||||
[data-selection][data-invalid] {
|
||||
[data-md-selection-row][data-md-invalid] {
|
||||
--selection-ink: var(--md-sys-color-error);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
[data-selection]:hover:not(:has(input:disabled)) :is([data-checkbox], [data-radio], [data-handle])::before {
|
||||
background-color: color-mix(in srgb, var(--selection-ink) 8%, transparent);
|
||||
[data-md-selection-row]:hover:not(:has(input:disabled)) :is([data-md-checkbox-box], [data-md-radio-button], [data-md-switch-handle])::before {
|
||||
background-color: color-mix(in srgb, var(--selection-ink) calc(var(--md-sys-state-hover-state-layer-opacity) * 100%), transparent);
|
||||
}
|
||||
}
|
||||
|
||||
[data-selection]:has(input:focus-visible) :is([data-checkbox], [data-radio], [data-handle])::before,
|
||||
[data-selection]:not(:has(input:disabled)):active :is([data-checkbox], [data-radio], [data-handle])::before {
|
||||
background-color: color-mix(in srgb, var(--selection-ink) 10%, transparent);
|
||||
[data-md-selection-row]:has(input:focus-visible) :is([data-md-checkbox-box], [data-md-radio-button], [data-md-switch-handle])::before {
|
||||
background-color: color-mix(in srgb, var(--selection-ink) calc(var(--md-sys-state-focus-state-layer-opacity) * 100%), transparent);
|
||||
}
|
||||
|
||||
[data-checkbox]:has(input:focus-visible)::before,
|
||||
[data-radio]:has(input:focus-visible)::before {
|
||||
outline: 3px solid var(--md-sys-color-secondary);
|
||||
}
|
||||
|
||||
/* ---- Checkbox ---------------------------------------------------------------------------- */
|
||||
|
||||
[data-checkbox] {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
[data-checkbox]::before {
|
||||
inset: -0.6875rem;
|
||||
}
|
||||
|
||||
[data-checkbox] input {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
border: 2px solid var(--md-sys-color-on-surface-variant);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
[data-checkbox] input:is(:checked, :indeterminate) {
|
||||
border-color: var(--md-sys-color-primary);
|
||||
background-color: var(--md-sys-color-primary);
|
||||
}
|
||||
|
||||
[data-checkbox] :is([data-check], [data-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-checkbox] input:checked:not(:indeterminate) ~ [data-check],
|
||||
[data-checkbox] input:indeterminate ~ [data-mixed] {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
}
|
||||
|
||||
[data-selection][data-invalid] [data-checkbox] input {
|
||||
border-color: var(--md-sys-color-error);
|
||||
}
|
||||
|
||||
[data-selection][data-invalid] [data-checkbox] input:is(:checked, :indeterminate) {
|
||||
background-color: var(--md-sys-color-error);
|
||||
}
|
||||
|
||||
[data-selection][data-invalid] [data-checkbox] :is([data-check], [data-mixed]) {
|
||||
color: var(--md-sys-color-on-error);
|
||||
}
|
||||
|
||||
[data-checkbox] input:disabled {
|
||||
border-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||||
}
|
||||
|
||||
[data-checkbox] input:disabled:is(:checked, :indeterminate) {
|
||||
border-color: transparent;
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||||
}
|
||||
|
||||
[data-checkbox] input:disabled ~ :is([data-check], [data-mixed]) {
|
||||
color: var(--md-sys-color-surface);
|
||||
}
|
||||
|
||||
/* ---- Radio button ------------------------------------------------------------------------ */
|
||||
|
||||
[data-radio] {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
[data-radio]::before {
|
||||
inset: -0.625rem;
|
||||
}
|
||||
|
||||
[data-radio] input {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border: 2px solid var(--md-sys-color-on-surface-variant);
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
}
|
||||
|
||||
[data-radio] [data-dot] {
|
||||
width: 0.625rem;
|
||||
height: 0.625rem;
|
||||
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-radio] input:checked {
|
||||
border-color: var(--md-sys-color-primary);
|
||||
}
|
||||
|
||||
[data-radio] input:checked ~ [data-dot] {
|
||||
scale: 1;
|
||||
}
|
||||
|
||||
[data-selection][data-invalid] [data-radio] input {
|
||||
border-color: var(--md-sys-color-error);
|
||||
}
|
||||
|
||||
[data-selection][data-invalid] [data-radio] [data-dot] {
|
||||
background-color: var(--md-sys-color-error);
|
||||
}
|
||||
|
||||
[data-radio] input:disabled {
|
||||
border-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||||
}
|
||||
|
||||
[data-radio] input:disabled ~ [data-dot] {
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||||
}
|
||||
|
||||
/* ---- Switch -------------------------------------------------------------------------------
|
||||
*
|
||||
* The handle's centre is 16px from the track's start unselected and 36px selected; it is 16px
|
||||
* across unselected, 24px with an icon or selected, and 28px while pressed. The positions below
|
||||
* are those centres less half the size, measured inside the track's 2px outline. */
|
||||
|
||||
[data-switch] {
|
||||
position: relative;
|
||||
display: grid;
|
||||
flex: none;
|
||||
width: 3.25rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
[data-switch] input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 2px solid var(--md-sys-color-outline);
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
background-color: var(--md-sys-color-surface-container-highest);
|
||||
}
|
||||
|
||||
[data-switch] input:focus-visible {
|
||||
outline: 3px solid var(--md-sys-color-secondary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
[data-handle] {
|
||||
--handle: 1rem;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
inset-inline-start: calc(1rem - var(--handle) / 2);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: var(--handle);
|
||||
height: var(--handle);
|
||||
translate: 0 -50%;
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
background-color: var(--md-sys-color-outline);
|
||||
color: var(--md-sys-color-surface-container-highest);
|
||||
pointer-events: none;
|
||||
transition-property: inset-inline-start, width, height, background-color;
|
||||
transition-duration: var(--md-sys-motion-spatial-fast-duration);
|
||||
transition-timing-function: var(--md-sys-motion-spatial-fast);
|
||||
}
|
||||
|
||||
[data-handle]::before {
|
||||
inset: 50% auto auto 50%;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
translate: -50% -50%;
|
||||
}
|
||||
|
||||
[data-handle] > svg {
|
||||
grid-area: 1 / 1;
|
||||
opacity: 0;
|
||||
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||||
}
|
||||
|
||||
[data-switch][data-icons="both"] [data-handle] {
|
||||
--handle: 1.5rem;
|
||||
}
|
||||
|
||||
[data-switch][data-icons="both"] input:not(:checked) ~ [data-handle] [data-off],
|
||||
[data-switch][data-icons] input:checked ~ [data-handle] [data-on] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[data-switch] input:checked {
|
||||
border-color: var(--md-sys-color-primary);
|
||||
background-color: var(--md-sys-color-primary);
|
||||
}
|
||||
|
||||
[data-switch] input:checked ~ [data-handle] {
|
||||
--handle: 1.5rem;
|
||||
|
||||
inset-inline-start: calc(2.25rem - var(--handle) / 2);
|
||||
background-color: var(--md-sys-color-on-primary);
|
||||
color: var(--md-sys-color-on-primary-container);
|
||||
}
|
||||
|
||||
[data-selection]:active [data-switch] input:not(:disabled) ~ [data-handle] {
|
||||
--handle: 1.75rem;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
[data-selection]:hover [data-switch] input:not(:disabled) ~ [data-handle] {
|
||||
background-color: var(--md-sys-color-on-surface-variant);
|
||||
}
|
||||
|
||||
[data-selection]:hover [data-switch] input:checked:not(:disabled) ~ [data-handle] {
|
||||
background-color: var(--md-sys-color-primary-container);
|
||||
}
|
||||
}
|
||||
|
||||
[data-switch] input:focus-visible ~ [data-handle],
|
||||
[data-selection]:active [data-switch] input:not(:disabled) ~ [data-handle] {
|
||||
background-color: var(--md-sys-color-on-surface-variant);
|
||||
}
|
||||
|
||||
[data-switch] input:checked:focus-visible ~ [data-handle],
|
||||
[data-selection]:active [data-switch] input:checked:not(:disabled) ~ [data-handle] {
|
||||
background-color: var(--md-sys-color-primary-container);
|
||||
}
|
||||
|
||||
[data-switch] input:disabled {
|
||||
border-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-surface-container-highest) 12%, transparent);
|
||||
}
|
||||
|
||||
[data-switch] input:disabled ~ [data-handle] {
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||||
color: color-mix(in srgb, var(--md-sys-color-surface-container-highest) 38%, transparent);
|
||||
}
|
||||
|
||||
[data-switch] input:disabled:checked {
|
||||
border-color: transparent;
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
|
||||
}
|
||||
|
||||
[data-switch] input:disabled:checked ~ [data-handle] {
|
||||
background-color: var(--md-sys-color-surface);
|
||||
color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||||
[data-md-selection-row]:not(:has(input:disabled)):active :is([data-md-checkbox-box], [data-md-radio-button], [data-md-switch-handle])::before {
|
||||
background-color: color-mix(in srgb, var(--selection-ink) calc(var(--md-sys-state-pressed-state-layer-opacity) * 100%), transparent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* <x-toggle>: M3's switch (resources/views/components/toggle.blade.php), on the row the selection
|
||||
* controls share (components/selection.css). The hooks say `switch` because `toggle` is daisyUI's
|
||||
* class, which the design guard rejects.
|
||||
*
|
||||
* SwitchTokens (androidx Compose Material 3, Apache-2.0): a 52×32 track with a 2px outline, in
|
||||
* surface-container-highest when off and primary when on. The handle's centre is 16px from the
|
||||
* track's start off and 36px on; it is 16px across off, 24px with an icon or on, and 28px while
|
||||
* pressed, moving and growing on the fast spatial spring. Off it is outline, on-surface-variant
|
||||
* while hovered, focused or pressed; on it is on-primary, primary-container in those states. The
|
||||
* state layer is a 40px circle around the handle. Its icons are 16px, on-primary-container when on.
|
||||
* Disabled, the track is on-surface at 12% and the handle on-surface at 38% (surface when on).
|
||||
*
|
||||
* [data-md-switch] the track; `data-md-icons` = "both" or "selected"
|
||||
* input, [data-md-switch-handle] (with [data-md-switch-on] and [data-md-switch-off] icons)
|
||||
*/
|
||||
|
||||
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
||||
|
||||
@import './selection.css';
|
||||
@import './icon.css';
|
||||
|
||||
@layer material.components {
|
||||
/* The positions below are the handle's centres less half its size, measured inside the track's
|
||||
2px outline. */
|
||||
[data-md-switch] {
|
||||
position: relative;
|
||||
display: grid;
|
||||
flex: none;
|
||||
width: 52px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
[data-md-switch] input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 2px solid var(--md-sys-color-outline);
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
background-color: var(--md-sys-color-surface-container-highest);
|
||||
}
|
||||
|
||||
[data-md-switch] input:focus-visible {
|
||||
outline: 3px solid var(--md-sys-color-secondary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
[data-md-switch-handle] {
|
||||
--handle: 16px;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
inset-inline-start: calc(16px - var(--handle) / 2);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: var(--handle);
|
||||
height: var(--handle);
|
||||
translate: 0 -50%;
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
background-color: var(--md-sys-color-outline);
|
||||
color: var(--md-sys-color-surface-container-highest);
|
||||
pointer-events: none;
|
||||
transition-property: inset-inline-start, width, height, background-color;
|
||||
transition-duration: var(--md-sys-motion-spatial-fast-duration);
|
||||
transition-timing-function: var(--md-sys-motion-spatial-fast);
|
||||
}
|
||||
|
||||
[data-md-switch-handle]::before {
|
||||
inset: 50% auto auto 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
translate: -50% -50%;
|
||||
}
|
||||
|
||||
[data-md-switch-handle] > svg {
|
||||
grid-area: 1 / 1;
|
||||
opacity: 0;
|
||||
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||||
}
|
||||
|
||||
[data-md-switch][data-md-icons='both'] [data-md-switch-handle] {
|
||||
--handle: 24px;
|
||||
}
|
||||
|
||||
[data-md-switch][data-md-icons='both'] input:not(:checked) ~ [data-md-switch-handle] [data-md-switch-off],
|
||||
[data-md-switch][data-md-icons] input:checked ~ [data-md-switch-handle] [data-md-switch-on] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[data-md-switch] input:checked {
|
||||
border-color: var(--md-sys-color-primary);
|
||||
background-color: var(--md-sys-color-primary);
|
||||
}
|
||||
|
||||
[data-md-switch] input:checked ~ [data-md-switch-handle] {
|
||||
--handle: 24px;
|
||||
|
||||
inset-inline-start: calc(36px - var(--handle) / 2);
|
||||
background-color: var(--md-sys-color-on-primary);
|
||||
color: var(--md-sys-color-on-primary-container);
|
||||
}
|
||||
|
||||
[data-md-selection-row]:active [data-md-switch] input:not(:disabled) ~ [data-md-switch-handle] {
|
||||
--handle: 28px;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
[data-md-selection-row]:hover [data-md-switch] input:not(:disabled) ~ [data-md-switch-handle] {
|
||||
background-color: var(--md-sys-color-on-surface-variant);
|
||||
}
|
||||
|
||||
[data-md-selection-row]:hover [data-md-switch] input:checked:not(:disabled) ~ [data-md-switch-handle] {
|
||||
background-color: var(--md-sys-color-primary-container);
|
||||
}
|
||||
}
|
||||
|
||||
[data-md-switch] input:focus-visible ~ [data-md-switch-handle],
|
||||
[data-md-selection-row]:active [data-md-switch] input:not(:disabled) ~ [data-md-switch-handle] {
|
||||
background-color: var(--md-sys-color-on-surface-variant);
|
||||
}
|
||||
|
||||
[data-md-switch] input:checked:focus-visible ~ [data-md-switch-handle],
|
||||
[data-md-selection-row]:active [data-md-switch] input:checked:not(:disabled) ~ [data-md-switch-handle] {
|
||||
background-color: var(--md-sys-color-primary-container);
|
||||
}
|
||||
|
||||
[data-md-switch] input:disabled {
|
||||
border-color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-container-opacity) * 100%), transparent);
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-surface-container-highest) calc(var(--md-sys-state-disabled-container-opacity) * 100%), transparent);
|
||||
}
|
||||
|
||||
[data-md-switch] input:disabled ~ [data-md-switch-handle] {
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
|
||||
color: color-mix(in srgb, var(--md-sys-color-surface-container-highest) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
|
||||
}
|
||||
|
||||
[data-md-switch] input:disabled:checked {
|
||||
border-color: transparent;
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-container-opacity) * 100%), transparent);
|
||||
}
|
||||
|
||||
[data-md-switch] input:disabled:checked ~ [data-md-switch-handle] {
|
||||
background-color: var(--md-sys-color-surface);
|
||||
color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user