Compare commits

...

12 Commits

Author SHA1 Message Date
elhombretecla
c565915007 💄 Add new tier two color tokens for ui elements 2025-10-06 09:53:11 +02:00
Eva Marco
2de6b6460e ♻️ Review on switcher component 2025-10-03 15:02:31 +02:00
elhombretecla
f905dfc699 💄 Fix linter errors 2025-10-02 11:05:53 +02:00
elhombretecla
c79f110177 💄 Fix cli errors 2025-10-02 11:05:34 +02:00
elhombretecla
f644b3744a 💄 Fix light theme styles 2025-10-02 11:05:34 +02:00
elhombretecla
0722af3a2f 🎉 Add translation string to switcher label 2025-10-02 11:05:34 +02:00
elhombretecla
b4c6bbb191 💄 Remove css nesting 2025-10-02 11:05:34 +02:00
Eva Marco
cad9d03ca1 🎉 Some fixes 2025-10-02 11:05:34 +02:00
elhombretecla
1d6389a3eb 🎉 Apply fixes and new doc structure 2025-10-02 11:05:34 +02:00
elhombretecla
913a8d3148 🎉 Fix label color and scss variables 2025-10-02 11:05:34 +02:00
elhombretecla
34e3453f24 Use proper classes name convention 2025-10-02 11:05:34 +02:00
elhombretecla
6f362f9211 🎉 Add new component switcher structure 2025-10-02 11:05:34 +02:00
10 changed files with 405 additions and 0 deletions

View File

@@ -13,6 +13,7 @@
[app.main.ui.ds.controls.input :refer [input*]]
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
[app.main.ui.ds.controls.select :refer [select*]]
[app.main.ui.ds.controls.switcher.switcher :refer [switcher*]]
[app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]]
[app.main.ui.ds.controls.utilities.input-field :refer [input-field*]]
[app.main.ui.ds.controls.utilities.label :refer [label*]]
@@ -60,6 +61,7 @@
:Loader loader*
:RawSvg raw-svg*
:Select select*
:Switcher switcher*
:Combobox combobox*
:Text text*
:TabSwitcher tab-switcher*

View File

@@ -11,6 +11,7 @@ $br-8: px2rem(8);
$br-4: px2rem(4);
$br-6: px2rem(6);
$br-circle: 50%;
$br-full: px2rem(9999);
$b-1: px2rem(1);
$b-2: px2rem(2);

View File

@@ -79,6 +79,9 @@ $grayish-red: #bfbfbf;
--color-accent-select: #{$purple-600-10};
--color-accent-action: #{$purple-400};
--color-accent-action-hover: #{$purple-500};
--color-accent-ui-base: #{$purple-200};
--color-accent-ui-hover: #{$purple-600};
--color-accent-ui-active: #{$gray-200};
--color-accent-success: #{$green-500};
--color-background-success: #{$green-200};
@@ -127,6 +130,9 @@ $grayish-red: #bfbfbf;
--color-accent-select: #{$mint-250-10};
--color-accent-action: #{$purple-400};
--color-accent-action-hover: #{$purple-500};
--color-accent-ui-base: #{$green-500};
--color-accent-ui-hover: #{$mint-250};
--color-accent-ui-active: #{$gray-800};
--color-accent-success: #{$green-500};
--color-background-success: #{$green-950};

View File

@@ -0,0 +1,14 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.controls.switcher
(:require
[app.common.data.macros :as dm]
[app.main.ui.ds.controls.switcher.switcher :as impl]))
(dm/export impl/switcher*)

View File

@@ -0,0 +1,87 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.controls.switcher.switcher
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.util.dom :as dom]
[app.util.keyboard :as kbd]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(def ^:private schema:switcher
[:and
[:map
[:id {:optional true} :string]
[:label {:optional true} [:maybe :string]]
[:aria-label {:optional true} [:maybe :string]]
[:default-checked {:optional true} :boolean]
[:on-change {:optional true} [:maybe fn?]]
[:disabled {:optional true} :boolean]
[:size {:optional true} [:enum "sm" "md" "lg"]]
[:class {:optional true} :string]]
[:fn {:error/message "Invalid Props"}
(fn [props]
(or (contains? props :label)
(contains? props :aria-label)))]])
(mf/defc switcher*
{::mf/schema schema:switcher}
[{:keys [id label default-checked on-change disabled size aria-label class] :rest props}]
(let [id (or id (mf/use-id))
size (d/nilv size " md ")
disabled (d/nilv disabled false)
is-checked* (mf/use-state (d/nilv default-checked false))
is-checked (deref is-checked*)
;; Toggle handler
handle-toggle
(mf/use-fn
(mf/deps on-change is-checked* disabled)
(fn []
(when-not disabled
(let [new-checked (not is-checked)]
(reset! is-checked* new-checked)
(when on-change
(on-change new-checked))))))
;; Keyboard events
handle-keydown
(mf/use-fn
(mf/deps handle-toggle)
(fn [event]
(when (or (kbd/space? event) (kbd/enter? event))
(dom/prevent-default event)
(handle-toggle event))))
has-label (not (str/blank? label))
props (mf/spread-props props {:id id
:role "switch"
:aria-label (when-not has-label
aria-label)
:class [class (stl/css :switcher)]
:aria-checked is-checked
:disabled disabled
:on-click handle-toggle
:on-key-down handle-keydown
:tab-index (if disabled -1 0)})]
[:> :div props
(when has-label
[:label {:for id
:class (stl/css-case :switcher-label true
:switcher-label-disabled disabled)}
label])
[:div {:class (stl/css-case :switcher-track true
:switcher-checked is-checked
:switcher-sm (= size "sm")
:switcher-md (= size "md")
:switcher-lg (= size "lg")
:switcher-track-disabled-checked (and is-checked disabled))}
[:div {:class (stl/css-case :switcher-thumb true
:switcher-thumb-disabled disabled)}]]]))

View File

@@ -0,0 +1,61 @@
{ /* This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Copyright (c) KALEIDOS INC */ }
import { Canvas, Meta } from '@storybook/blocks';
import * as Switcher from "./switcher.stories";
<Meta title="Controls/Switcher" />
# Switcher
The `switcher*` component is a toggle control that allows users to switch between two states (on/off).
<Canvas of={Switcher.Default} />
## Anatomy
The switcher component consists of three main parts:
- **Label** (optional): Text that describes what the switcher controls
- **Track**: The pill-shaped background that indicates the current state
- **Thumb**: The circular knob that moves between positions
## Variants
### Different Sizes
The switcher supports three size variants: `"sm"`, `"md"` (default), and `"lg"`.
```clj
[:> switcher* {:size "sm" :label "Small"}]
[:> switcher* {:size "md" :label "Medium"}]
[:> switcher* {:size "lg" :label "Large"}]
```
### Without Visible Label
When no visible label is provided, use `aria-label` for accessibility.
```clj
[:> switcher* {:aria-label "Toggle dark mode"
:default-checked false}]
```
<Canvas of={Switcher.WithoutVisibleLabel} />
## Usage Guidelines
### When to Use
- For binary settings that take effect immediately
- In preference panels and configuration screens
### When Not to Use
- For actions that require confirmation (use buttons instead)
- For multiple choice selections (use radio buttons or select)
- For temporary states that need explicit "Apply" action

View File

@@ -0,0 +1,159 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "ds/colors.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_borders.scss" as *;
@use "ds/spacing.scss" as *;
$switcher-sm-track-width: $sz-32;
$switcher-sm-track-height: $sz-16;
$switcher-sm-thumb-size: $sz-12;
$switcher-md-track-width: $sz-40;
$switcher-md-track-height: $sz-24;
$switcher-md-thumb-size: $sz-16;
$switcher-lg-track-width: $sz-48;
$switcher-lg-track-height: $sz-28;
$switcher-lg-thumb-size: $sz-24;
$switcher-transition-duration: 0.2s;
.switcher {
--switcher-track-outline-color: transparent;
--switcher-track-outline-offset: 0;
--switcher-track-width: #{$switcher-md-track-width};
--switcher-track-height: #{$switcher-md-track-height};
--switcher-track-bg: var(--color-accent-ui-active);
--switcher-track-opacity: 1;
--switcher-thumb-transform: translateX(0);
--switcher-thumb-size: #{$switcher-md-thumb-size};
--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);
--switcher-thumb-opacity: 1;
--switcher-label-foreground-color: var(--color-foreground-secondary);
--switcher-cursor: pointer;
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: var(--sp-s);
padding: 0;
inline-size: fit-content;
outline: none;
cursor: var(--switcher-cursor);
&:focus-visible {
--switcher-track-outline: $b-2 solid var(--color-accent-primary);
--switcher-track-outline-offset: #{$b-2};
--switcher-track-outline-color: var(--color-accent-primary);
}
&:disabled,
&[disabled] {
--switcher-label-foreground-color: var(--color-foreground-secondary);
--switcher-cursor: not-allowed;
--switcher-track-opacity: 0.6;
--switcher-thumb-opacity: 0.5;
--switcher-track-background: var(--color-background-tertiary);
}
}
.switcher-label {
color: var(--switcher-label-foreground-color);
user-select: none;
}
.switcher-track {
position: relative;
inline-size: var(--switcher-track-width);
block-size: var(--switcher-track-height);
border-radius: $br-full;
background-color: var(--switcher-track-bg);
transition: background-color $switcher-transition-duration ease-in-out;
outline: $b-2 solid var(--switcher-track-outline-color);
outline-offset: var(--switcher-track-outline-offset);
opacity: var(--switcher-track-opacity);
&:hover {
--switcher-thumb-bg: var(--color-static-white);
}
}
.switcher-thumb {
position: absolute;
inline-size: var(--switcher-thumb-size);
block-size: var(--switcher-thumb-size);
inset-block-start: calc((var(--switcher-track-height) - var(--switcher-thumb-size)) / 2);
inset-inline-start: calc((var(--switcher-track-height) - var(--switcher-thumb-size)) / 2);
border-radius: 50%;
background-color: var(--switcher-thumb-bg);
opacity: var(--switcher-thumb-opacity);
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;
}
// 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-ui-base);
--switcher-thumb-bg: var(--color-static-white);
--switcher-thumb-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);
&:hover {
--switcher-track-bg: var(--color-accent-ui-hover);
}
}
.switcher-track-disabled-checked {
--switcher-track-bg: var(--color-background-tertiary);
&:hover {
--switcher-track-bg: var(--color-background-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}));
}
@media (prefers-reduced-motion: reduce) {
.switcher-track,
.switcher-thumb {
transition: none;
}
}

View File

@@ -0,0 +1,67 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
import * as React from "react";
import Components from "@target/components";
const { Switcher } = Components;
export default {
title: "Controls/Switcher",
component: Switcher,
argTypes: {
defaultChecked: {
control: { type: "boolean" },
description: "Default checked state for uncontrolled mode",
},
label: {
control: { type: "text" },
description: "Label text displayed next to the switcher",
},
disabled: {
control: { type: "boolean" },
description: "Whether the switcher is disabled",
},
size: {
options: ["sm", "md", "lg"],
control: { type: "select" },
description: "Size variant of the switcher",
}
},
args: {
disabled: false,
size: "md",
defaultChecked: false
},
parameters: {
controls: { exclude: ["id", "class", "dataTestid", "on-change"] },
},
render: ({ onChange, ...args }) => (
<Switcher {...args} onChange={onChange} label="Enable notifications"/>
),
};
export const Default = {};
export const WithLongLabel = {
args: {
label: "This is a very long label that demonstrates how the switcher component handles text wrapping and layout when the label content is extensive",
defaultChecked: true,
},
render: ({ ...args }) => (
<div style={{ maxWidth: "300px" }}>
<Switcher {...args}/>
</div>
),
};
export const WithoutVisibleLabel = {
args: {
defaultChecked: false,
},
render: ({ ...args }) => (
<Switcher {...args} aria-label="Enable notification"/>
),
};

View File

@@ -1186,6 +1186,10 @@ msgstr "Detach token"
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "This token is not available in any active set or theme."
#: src/app/main/ui/ds/controls/switcher/switcher.cljs:78
msgid "ds.switcher.aria-label"
msgstr "Switcher label"
#: src/app/main/data/auth.cljs:314
msgid "errors.auth-provider-not-allowed"
msgstr "Auth provider not allowed for this profile"

View File

@@ -1198,6 +1198,10 @@ msgstr "Desvincular token"
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "Este token no está disponible en ningún set ni tema activo."
#: src/app/main/ui/ds/controls/switcher/switcher.cljs:78
msgid "ds.switcher.aria-label"
msgstr "Etiqueta del switcher"
#: src/app/main/data/auth.cljs:314
msgid "errors.auth-provider-not-allowed"
msgstr "El proveedor de autenticación no permitido para este perfil de usuario"