Files
2026-07-13 12:12:04 +08:00

243 lines
6.8 KiB
CSS

/* Switch base styles */
.switch {
@apply flex flex-col items-start gap-1 no-highlight;
/* Cursor */
cursor: var(--cursor-interactive);
/* Default tokens for switch control states */
--switch-control-bg: var(--default);
--switch-control-bg-hover: color-mix(in oklab, var(--switch-control-bg), transparent 20%);
--switch-control-bg-pressed: var(--switch-control-bg-hover);
--switch-control-bg-checked: var(--accent);
--switch-control-bg-checked-hover: var(--accent-hover);
/* Disabled state */
&[data-disabled="true"] {
@apply status-disabled;
[data-slot="description"],
[data-slot="field-error"] {
@apply status-disabled;
}
.switch__thumb {
@apply bg-default-foreground/20;
}
}
/* Field-level help text below the control + label row */
& > [data-slot="description"] {
@apply w-full min-w-0 ps-[calc(2.5rem+0.75rem)];
}
& > [data-slot="field-error"] {
@apply w-full min-w-0 ps-[calc(2.5rem+0.75rem)] text-xs text-muted;
}
&.switch--sm > [data-slot="description"] {
@apply ps-[calc(2rem+0.75rem)];
}
&.switch--sm > [data-slot="field-error"] {
@apply ps-[calc(2rem+0.75rem)];
}
&.switch--lg > [data-slot="description"] {
@apply ps-[calc(3rem+0.75rem)];
}
&.switch--lg > [data-slot="field-error"] {
@apply ps-[calc(3rem+0.75rem)];
}
/* Disabled + Selected state */
&:disabled[aria-checked="true"],
&:disabled[data-selected="true"],
&[data-disabled="true"][aria-checked="true"],
&[data-disabled="true"][data-selected="true"],
&[aria-disabled="true"][aria-checked="true"],
&[aria-disabled="true"][data-selected="true"] {
.switch__thumb {
opacity: 0.4;
}
}
}
/* Size variants - default is medium */
.switch--sm {
/* Small size modifier */
}
.switch--md {
/* Medium size - no styles as this is the default */
}
.switch--lg {
/* Large size modifier */
}
/* Switch control (track) */
.switch__control {
/* Default size (medium) */
@apply relative flex shrink-0 items-center overflow-hidden rounded-xl;
height: 1.25rem; /* 20px on desktop (14px base), 20px on mobile (16px base) - scales with font-size */
width: 2.5rem; /* 35px on desktop, 40px on mobile - scales with font-size */
background-color: var(--switch-control-bg);
/**
* Transitions
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
*/
transition:
background-color 250ms var(--ease-smooth),
box-shadow 150ms var(--ease-out);
@apply motion-reduce:transition-none;
/* Focus states */
.switch:focus-visible &,
.switch:has([data-slot="switch-content"][data-focus-visible="true"]) &,
.switch [data-slot="switch-content"][data-focus-visible="true"] & {
@apply status-focused;
}
/* Hover states */
.switch:hover &,
.switch:has([data-slot="switch-content"][data-hovered="true"]) &,
.switch [data-slot="switch-content"][data-hovered="true"] & {
background-color: var(--switch-control-bg-hover);
}
/* Pressed states */
.switch:active &,
.switch:has([data-slot="switch-content"][data-pressed="true"]) &,
.switch [data-slot="switch-content"][data-pressed="true"] & {
background-color: var(--switch-control-bg-pressed);
@apply motion-reduce:transform-none;
}
/* Checked states */
.switch[aria-checked="true"] &,
.switch[data-selected="true"] & {
background-color: var(--switch-control-bg-checked);
}
/* Checked + Hover states */
.switch[aria-checked="true"]:hover &,
.switch[data-selected="true"]:hover &,
.switch[aria-checked="true"][data-hovered="true"] &,
.switch[data-selected="true"][data-hovered="true"] &,
.switch:has([data-slot="switch-content"][data-hovered="true"])[data-selected="true"] & {
background-color: var(--switch-control-bg-checked-hover);
}
/* Checked + Pressed states */
.switch[aria-checked="true"]:active &,
.switch[data-selected="true"]:active &,
.switch[aria-checked="true"][data-pressed="true"] &,
.switch[data-selected="true"][data-pressed="true"] &,
.switch:has([data-slot="switch-content"][data-pressed="true"])[data-selected="true"] & {
background-color: var(--switch-control-bg-checked-hover);
}
}
/* Switch content — the clickable label (control + label text) */
.switch__content {
@apply relative inline-flex cursor-[inherit] items-center gap-3 text-sm font-medium text-foreground outline-none select-none no-highlight;
}
/* Size-specific control dimensions */
.switch--sm .switch__control {
@apply rounded-lg;
height: 1rem; /* ~14px on desktop, ~16px on mobile */
width: 2rem; /* ~32px on desktop, ~36px on mobile */
}
.switch--md .switch__control {
/* No styles as this is the default size */
}
.switch--lg .switch__control {
height: 1.5rem; /* ~21px on desktop, ~24px on mobile */
width: 3rem; /* ~48px on desktop, ~56px on mobile */
}
/* Switch thumb */
.switch__thumb {
/* Default size (medium) with margin-based positioning */
@apply ms-0.5 flex origin-center rounded-lg bg-white text-black shadow-field;
height: 1rem; /* ~14px on desktop, ~16px on mobile */
width: 1.375rem; /* ~19px on desktop, ~22px on mobile */
/**
* Transitions
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
*/
transition:
margin 300ms var(--ease-out-fluid),
background-color 200ms var(--ease-out);
@apply motion-reduce:transition-none;
/* Selected/checked state - use margin instead of translate */
.switch[aria-checked="true"] &,
.switch[data-selected="true"] & {
@apply ms-[calc(100%-1.5rem)] bg-accent-foreground text-accent;
box-shadow:
0px 0px 5px 0px rgb(0 0 0 / 0.02),
0px 2px 10px 0px rgb(0 0 0 / 0.06),
0px 0px 1px 0px rgb(0 0 0 / 0.3);
}
.switch:active &,
.switch[data-pressed="true"] & {
}
}
/* Size-specific thumb dimensions */
.switch--sm .switch__thumb {
@apply rounded-md;
height: 0.75rem; /* ~10.5px on desktop, ~12px on mobile */
width: 1.03125rem; /* ~14.4px on desktop, ~16.5px on mobile - maintains 1.375:1 ratio */
/* Small selected state positioning */
.switch[aria-checked="true"] &,
.switch[data-selected="true"] & {
@apply ms-[calc(100%-1.15625rem)];
}
}
.switch--lg .switch__thumb {
@apply rounded-xl;
height: 1.25rem; /* ~17.5px on desktop, ~20px on mobile */
width: 1.71875rem; /* ~24px on desktop, ~27.5px on mobile - maintains 1.375:1 ratio */
/* Large selected state positioning */
.switch[aria-checked="true"] &,
.switch[data-selected="true"] & {
@apply ms-[calc(100%-1.84375rem)];
}
}
/* Switch thumb content container */
.switch__thumb > * {
@apply flex size-full items-center justify-center;
}
/* Switch label */
.switch__label {
@apply text-base font-medium text-foreground;
}
.switch [data-slot="label"] {
@apply select-none;
}
.switch__content [data-slot="label"] {
cursor: var(--cursor-interactive);
}
.switch [data-slot="description"] {
@apply cursor-default select-none;
}