From b4c6bbb1918e0d3164ad28e09452be9ba0485e27 Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Wed, 1 Oct 2025 15:48:53 +0200 Subject: [PATCH] :lipstick: Remove css nesting --- .../ui/ds/controls/switcher/switcher.cljs | 18 +- .../ui/ds/controls/switcher/switcher.scss | 207 ++++++++---------- .../ds/controls/switcher/switcher.stories.jsx | 15 +- 3 files changed, 101 insertions(+), 139 deletions(-) diff --git a/frontend/src/app/main/ui/ds/controls/switcher/switcher.cljs b/frontend/src/app/main/ui/ds/controls/switcher/switcher.cljs index fe2e0bf304..38b18ba60e 100644 --- a/frontend/src/app/main/ui/ds/controls/switcher/switcher.cljs +++ b/frontend/src/app/main/ui/ds/controls/switcher/switcher.cljs @@ -87,11 +87,11 @@ :aria-disabled disabled :aria-label effective-aria-label :class (stl/css-case :switcher true - :is-checked current-checked - :is-disabled disabled - :switcher--sm (= size "sm") - :switcher--md (= size "md") - :switcher--lg (= size "lg")) + :switcher-checked current-checked + :switcher-disabled disabled + :switcher-sm (= size "sm") + :switcher-md (= size "md") + :switcher-lg (= size "lg")) :on-click handle-toggle :on-key-down handle-keydown})] @@ -99,13 +99,11 @@ (when has-label [:label {:for id :class (stl/css-case :switcher-label true - :is-disabled disabled) + :switcher-label-disabled disabled) :on-click handle-label-click} label]) [:> :div props [:div {:class (stl/css-case :switcher-track true - :is-checked current-checked - :is-disabled disabled)} + :switcher-track-disabled disabled)} [:div {:class (stl/css-case :switcher-thumb true - :is-checked current-checked - :is-disabled disabled)}]]]])) + :switcher-thumb-disabled disabled)}]]]])) diff --git a/frontend/src/app/main/ui/ds/controls/switcher/switcher.scss b/frontend/src/app/main/ui/ds/controls/switcher/switcher.scss index 47da45aad7..dc1d318925 100644 --- a/frontend/src/app/main/ui/ds/controls/switcher/switcher.scss +++ b/frontend/src/app/main/ui/ds/controls/switcher/switcher.scss @@ -24,36 +24,42 @@ $switcher-lg-thumb-size: $sz-24; $switcher-transition-duration: 0.2s; .switcher-wrapper { + --switcher-track-outline: none; + --switcher-track-outline-offset: 0; + display: flex; align-items: center; gap: var(--sp-s); padding: 0; - // Focus ring using DS tokens - on wrapper cascading to track &:focus-visible { - .switcher-track { - outline: $b-2 solid var(--color-accent-primary); - outline-offset: $b-2; - } + --switcher-track-outline: $b-2 solid var(--color-accent-primary); + --switcher-track-outline-offset: #{$b-2}; } } .switcher-label { - color: var(--color-foreground-primary); + --switcher-label-color: var(--color-foreground-secondary); + + color: var(--switcher-label-color); cursor: pointer; user-select: none; - - &:hover { - color: var(--color-foreground-primary); - } } -.switcher-label.is-disabled { +.switcher-label-disabled { cursor: not-allowed; color: var(--color-foreground-secondary); } .switcher { + --switcher-track-width: #{$switcher-md-track-width}; + --switcher-track-height: #{$switcher-md-track-height}; + --switcher-thumb-size: #{$switcher-md-thumb-size}; + --switcher-thumb-transform: translateX(0); + --switcher-track-bg: var(--color-background-quaternary); + --switcher-thumb-bg: var(--color-foreground-secondary); + --switcher-thumb-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); + position: relative; display: inline-block; cursor: pointer; @@ -62,131 +68,102 @@ $switcher-transition-duration: 0.2s; background: transparent; padding: 0; - - - // Size variants - Medium (default) - &.switcher--md { - .switcher-track { - width: $switcher-md-track-width; - height: $switcher-md-track-height; - } - - .switcher-thumb { - width: $switcher-md-thumb-size; - height: $switcher-md-thumb-size; - top: calc((#{$switcher-md-track-height} - #{$switcher-md-thumb-size}) / 2); - left: calc((#{$switcher-md-track-height} - #{$switcher-md-thumb-size}) / 2); - } - - &.is-checked .switcher-thumb { - transform: translateX(calc(#{$switcher-md-track-width} - #{$switcher-md-track-height})); - } + &:hover:not(.switcher-disabled) { + --switcher-thumb-bg: var(--color-foreground-primary); } +} + +// Size variants - Small +.switcher-sm { + --switcher-track-width: #{$switcher-sm-track-width}; + --switcher-track-height: #{$switcher-sm-track-height}; + --switcher-thumb-size: #{$switcher-sm-thumb-size}; +} + +// Size variants - Medium (default) +.switcher-md { + --switcher-track-width: #{$switcher-md-track-width}; + --switcher-track-height: #{$switcher-md-track-height}; + --switcher-thumb-size: #{$switcher-md-thumb-size}; +} + +// Size variants - Large +.switcher-lg { + --switcher-track-width: #{$switcher-lg-track-width}; + --switcher-track-height: #{$switcher-lg-track-height}; + --switcher-thumb-size: #{$switcher-lg-thumb-size}; +} + +// Checked state +.switcher-checked { + --switcher-track-bg: var(--color-accent-success); + --switcher-thumb-bg: var(--color-foreground-primary); + --switcher-thumb-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); - // Size variants - Small - &.switcher--sm { - .switcher-track { - width: $switcher-sm-track-width; - height: $switcher-sm-track-height; - } - - .switcher-thumb { - width: $switcher-sm-thumb-size; - height: $switcher-sm-thumb-size; - top: calc((#{$switcher-sm-track-height} - #{$switcher-sm-thumb-size}) / 2); - left: calc((#{$switcher-sm-track-height} - #{$switcher-sm-thumb-size}) / 2); - } - - &.is-checked .switcher-thumb { - transform: translateX(calc(#{$switcher-sm-track-width} - #{$switcher-sm-track-height})); - } + &:hover:not(.switcher-disabled) { + --switcher-track-bg: var(--color-accent-tertiary); } +} + +.switcher-checked.switcher-sm { + --switcher-thumb-transform: translateX(calc(#{$switcher-sm-track-width} - #{$switcher-sm-track-height})); +} + +.switcher-checked.switcher-md { + --switcher-thumb-transform: translateX(calc(#{$switcher-md-track-width} - #{$switcher-md-track-height})); +} + +.switcher-checked.switcher-lg { + --switcher-thumb-transform: translateX(calc(#{$switcher-lg-track-width} - #{$switcher-lg-track-height})); +} + +// Disabled state +.switcher-disabled { + cursor: not-allowed; - // Size variants - Large - &.switcher--lg { - .switcher-track { - width: $switcher-lg-track-width; - height: $switcher-lg-track-height; - } - - .switcher-thumb { - width: $switcher-lg-thumb-size; - height: $switcher-lg-thumb-size; - top: calc((#{$switcher-lg-track-height} - #{$switcher-lg-thumb-size}) / 2); - left: calc((#{$switcher-lg-track-height} - #{$switcher-lg-thumb-size}) / 2); - } - - &.is-checked .switcher-thumb { - transform: translateX(calc(#{$switcher-lg-track-width} - #{$switcher-lg-track-height})); - } + &:not(.switcher-checked) { + --switcher-track-bg: var(--color-background-tertiary); + --switcher-thumb-bg: var(--color-foreground-secondary); } } +.switcher-disabled.switcher-checked { + --switcher-track-bg: var(--color-background-quaternary); + --switcher-thumb-bg: var(--color-foreground-secondary); +} + .switcher-track { position: relative; - border-radius: $br-full; - background-color: var(--color-background-quaternary); + width: var(--switcher-track-width); + height: var(--switcher-track-height); + border-radius: $br-full; + background-color: var(--switcher-track-bg); transition: background-color $switcher-transition-duration ease-in-out; - - .switcher.is-checked & { - background-color: var(--color-accent-success); - } - - .switcher:not(.is-disabled):hover:not(.is-checked) & { - background-color: var(--color-background-quaternary); - } - - .switcher:not(.is-disabled):hover.is-checked & { - background-color: var(--color-accent-tertiary); - } + outline: var(--switcher-track-outline); + outline-offset: var(--switcher-track-outline-offset); +} + +.switcher-track-disabled { + opacity: 0.6; } .switcher-thumb { position: absolute; + width: var(--switcher-thumb-size); + height: var(--switcher-thumb-size); + top: calc((var(--switcher-track-height) - var(--switcher-thumb-size)) / 2); + left: calc((var(--switcher-track-height) - var(--switcher-thumb-size)) / 2); border-radius: 50%; - background-color: var(--color-foreground-secondary); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); + background-color: var(--switcher-thumb-bg); + box-shadow: var(--switcher-thumb-shadow); + transform: var(--switcher-thumb-transform); transition: transform $switcher-transition-duration ease-in-out, background-color $switcher-transition-duration ease-in-out; - - .switcher.is-checked & { - background-color: var(--color-foreground-primary); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); - } - - .switcher:not(.is-disabled):hover:not(.is-checked) & { - background-color: var(--color-foreground-primary); - } - - .switcher:not(.is-disabled):hover.is-checked & { - background-color: var(--color-foreground-primary); - } } -// Flat modifier-based selectors for states -.switcher.is-disabled { - cursor: not-allowed; -} - -.switcher-track.is-disabled { - background-color: var(--color-background-tertiary); - opacity: 0.6; -} - -.switcher-track.is-disabled.is-checked { - background-color: var(--color-background-quaternary); - opacity: 0.6; -} - -.switcher-thumb.is-disabled { - background-color: var(--color-foreground-secondary); +.switcher-thumb-disabled { opacity: 0.5; } -.switcher-thumb.is-disabled.is-checked { - background-color: var(--color-foreground-secondary); - opacity: 0.6; -} - @media (prefers-reduced-motion: reduce) { .switcher-track, .switcher-thumb { diff --git a/frontend/src/app/main/ui/ds/controls/switcher/switcher.stories.jsx b/frontend/src/app/main/ui/ds/controls/switcher/switcher.stories.jsx index fb494e8b41..678fac3ce7 100644 --- a/frontend/src/app/main/ui/ds/controls/switcher/switcher.stories.jsx +++ b/frontend/src/app/main/ui/ds/controls/switcher/switcher.stories.jsx @@ -13,10 +13,6 @@ export default { title: "Controls/Switcher", component: Switcher, argTypes: { - checked: { - control: { type: "boolean" }, - description: "Controlled checked state", - }, defaultChecked: { control: { type: "boolean" }, description: "Default checked state for uncontrolled mode", @@ -33,15 +29,7 @@ export default { options: ["sm", "md", "lg"], control: { type: "select" }, description: "Size variant of the switcher", - }, - "aria-label": { - control: { type: "text" }, - description: "Accessible label when no visible label is provided", - }, - onChange: { - action: "changed", - description: "Callback fired when the switcher state changes", - }, + } }, args: { disabled: false, @@ -100,7 +88,6 @@ export const WithLongLabel = { export const WithoutVisibleLabel = { args: { - "aria-label": "Toggle dark mode", defaultChecked: false, }, render: ({ ...args }) => (