-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat(web): Introduce UNSTABLE Toggle component #DS-1345
- Loading branch information
Showing
10 changed files
with
540 additions
and
1 deletion.
There are no files selected for viewing
193 changes: 193 additions & 0 deletions
193
packages/web/src/scss/components/UNSTABLE_Toggle/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
# UNSTABLE Toggle | ||
|
||
> ⚠️ This component is UNSTABLE. It may significantly change at any point in the future. | ||
> Please use it with caution. | ||
Toggle is a form control that allows users to switch between two states. | ||
|
||
## Basic Usage | ||
|
||
The Toggle component implements the HTML [checkbox input][mdn-checkbox] element. It uses | ||
the native input element and styles it to look like a toggle switch. | ||
|
||
```html | ||
<label for="toggle-default" class="UNSTABLE_Toggle"> | ||
<span class="UNSTABLE_Toggle__text"> | ||
<span class="UNSTABLE_Toggle__label">Toggle Label</span> | ||
</span> | ||
<input type="checkbox" id="toggle-default" class="UNSTABLE_Toggle__input" name="default" /> | ||
</label> | ||
``` | ||
|
||
## Indicators | ||
|
||
If you need to indicate the state of the toggle, you can add the `UNSTABLE_Toggle__input--indicators` | ||
modifier class to the input. This will add a visual indicators to the toggle switch. | ||
|
||
```html | ||
<label for="toggle-indicators" class="UNSTABLE_Toggle"> | ||
<span class="UNSTABLE_Toggle__text"> | ||
<span class="UNSTABLE_Toggle__label">Toggle Label</span> | ||
</span> | ||
<input | ||
type="checkbox" | ||
id="toggle-indicators" | ||
class="UNSTABLE_Toggle__input UNSTABLE_Toggle__input--indicators" | ||
name="default" | ||
/> | ||
</label> | ||
``` | ||
|
||
## Required | ||
|
||
Add the `required` attribute to the input to mark it as required and add the | ||
`UNSTABLE_Toggle__label--required` modifier class to the label to indicate the state. | ||
|
||
```html | ||
<label for="toggle-required" class="UNSTABLE_Toggle"> | ||
<span class="UNSTABLE_Toggle__text"> | ||
<span class="UNSTABLE_Toggle__label UNSTABLE_Toggle__label--required">Toggle Label</span> | ||
</span> | ||
<input type="checkbox" id="toggle-required" class="UNSTABLE_Toggle__input" name="required" required /> | ||
</label> | ||
``` | ||
|
||
## Hidden Label | ||
|
||
```html | ||
<label for="toggle-hidden-label" class="UNSTABLE_Toggle"> | ||
<span class="UNSTABLE_Toggle__text"> | ||
<span class="UNSTABLE_Toggle__label UNSTABLE_Toggle__label--hidden">Toggle Label</span> | ||
</span> | ||
<input type="checkbox" id="toggle-hidden-label" class="UNSTABLE_Toggle__input" name="hidden-label" /> | ||
</label> | ||
``` | ||
|
||
## Fluid | ||
|
||
```html | ||
<label for="toggle-fluid" class="UNSTABLE_Toggle UNSTABLE_Toggle--fluid"> | ||
<span class="UNSTABLE_Toggle__text"> | ||
<span class="UNSTABLE_Toggle__label">Toggle Label</span> | ||
</span> | ||
<input type="checkbox" id="toggle-fluid" class="UNSTABLE_Toggle__input" name="fluid" /> | ||
</label> | ||
``` | ||
|
||
## Helper Text | ||
|
||
```html | ||
<label for="toggle-helper-text" class="UNSTABLE_Toggle"> | ||
<span class="UNSTABLE_Toggle__text"> | ||
<span class="UNSTABLE_Toggle__label">Toggle Label</span> | ||
<span class="UNSTABLE_Toggle__helperText" id="toggle-helper-text-helper-text">Helper text</span> | ||
</span> | ||
<input | ||
type="checkbox" | ||
id="toggle-helper-text" | ||
class="UNSTABLE_Toggle__input" | ||
name="helper-text" | ||
aria-describedby="toggle-helper-text-helper-text" | ||
/> | ||
</label> | ||
``` | ||
|
||
## Validation States | ||
|
||
Validation states can be presented either by adding a CSS modifier class | ||
(`UNSTABLE_Toggle--success`, `UNSTABLE_Toggle--warning`, `UNSTABLE_Toggle--danger`), or by adding | ||
a JS interaction class when controlled by JavaScript (`has-success`, | ||
`has-warning`, `has-danger`). See Validation state [dictionary][dictionary-validation]. | ||
|
||
```html | ||
<label for="toggle-success" class="UNSTABLE_Toggle UNSTABLE_Toggle--success"> | ||
<span class="UNSTABLE_Toggle__text"> | ||
<span class="UNSTABLE_Toggle__label">Toggle Label</span> | ||
</span> | ||
<input type="checkbox" id="toggle-success" class="UNSTABLE_Toggle__input" name="default" /> | ||
</label> | ||
|
||
<label for="toggle-warning" class="UNSTABLE_Toggle UNSTABLE_Toggle--warning"> | ||
<span class="UNSTABLE_Toggle__text"> | ||
<span class="UNSTABLE_Toggle__label">Toggle Label</span> | ||
<span class="UNSTABLE_Toggle__validationText" id="toggle-warning-validation-text">Validation text</span> | ||
</span> | ||
<input | ||
type="checkbox" | ||
id="toggle-warning" | ||
class="UNSTABLE_Toggle__input" | ||
name="default" | ||
aria-describedby="toggle-warning-validation-text" | ||
checked | ||
/> | ||
</label> | ||
|
||
<div class="UNSTABLE_Toggle UNSTABLE_Toggle--danger"> | ||
<div class="UNSTABLE_Toggle__text"> | ||
<label for="toggle-danger" class="UNSTABLE_Toggle__label">Toggle Label</label> | ||
<ul class="UNSTABLE_Toggle__validationText" id="toggle-danger-validation-text"> | ||
<li>First validation text</li> | ||
<li>Second validation text</li> | ||
</ul> | ||
</div> | ||
<input | ||
type="checkbox" | ||
id="toggle-danger" | ||
class="UNSTABLE_Toggle__input" | ||
name="default" | ||
aria-describedby="toggle-danger-validation-text" | ||
/> | ||
</div> | ||
``` | ||
|
||
### JavaScript-Controlled Validation Text | ||
|
||
When implementing client-side form validation, use JS interaction state classes | ||
(`has-success`, `has-warning`, `has-danger`) on the wrapping `<div>` element and | ||
render validation texts in a `<div>` or `<ul>` with `data-spirit-element="validation_text"` | ||
attribute. This way your JS remains disconnected from CSS that may or may not be | ||
[prefixed][prefixed]. | ||
|
||
**Remember this approach is only valid for vanilla JS implementation. React | ||
components mix CSS with JS by design and handle prefixes their own way.** | ||
|
||
```html | ||
<label for="toggle-success" class="UNSTABLE_Toggle has-success"> | ||
<span class="UNSTABLE_Toggle__text"> | ||
<span class="UNSTABLE_Toggle__label">Toggle Label</span> | ||
<div | ||
class="UNSTABLE_Toggle__validationText" | ||
id="toggle-success-validation-text" | ||
data-spirit-element="validation_text" | ||
> | ||
Validation text | ||
</div> | ||
</span> | ||
<input | ||
type="checkbox" | ||
id="toggle-success" | ||
class="UNSTABLE_Toggle__input" | ||
name="default" | ||
aria-describedby="toggle-success-validation-text" | ||
/> | ||
</label> | ||
``` | ||
|
||
## Disabled State | ||
|
||
On top of adding the `disabled` attribute to the input, disabled Toggle needs to | ||
be marked by adding `UNSTABLE_Toggle--disabled` modifier class, or with `is-disabled` | ||
JS interaction class when controlled by JavaScript: | ||
|
||
```html | ||
<label for="toggle-disabled" class="UNSTABLE_Toggle UNSTABLE_Toggle--disabled"> | ||
<span class="UNSTABLE_Toggle__text"> | ||
<span class="UNSTABLE_Toggle__label">Toggle Label</span> | ||
</span> | ||
<input type="checkbox" id="toggle-disabled" class="UNSTABLE_Toggle__input" name="default" disabled /> | ||
</label> | ||
``` | ||
|
||
[dictionary-validation]: https://github.com/lmc-eu/spirit-design-system/blob/main/docs/DICTIONARIES.md#validation | ||
[mdn-checkbox]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox | ||
[prefixed]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web/README.md#prefixing-css-class-names |
128 changes: 128 additions & 0 deletions
128
packages/web/src/scss/components/UNSTABLE_Toggle/_UNSTABLE_Toggle.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
@use 'theme'; | ||
@use '../../settings/cursors'; | ||
@use '../../theme/form-fields' as form-fields-theme; | ||
@use '../../tools/form-fields' as form-fields-tools; | ||
@use '../../tools/svg'; | ||
|
||
$_field-name: 'UNSTABLE_Toggle'; | ||
|
||
.UNSTABLE_Toggle { | ||
display: flex; | ||
align-items: start; | ||
justify-content: space-between; | ||
max-width: theme.$max-width; | ||
margin-block: form-fields-theme.$gap; | ||
cursor: cursors.$form-fields; | ||
} | ||
|
||
.UNSTABLE_Toggle--fluid { | ||
max-width: none; | ||
} | ||
|
||
.UNSTABLE_Toggle__text { | ||
margin-right: form-fields-theme.$gap; | ||
} | ||
|
||
.UNSTABLE_Toggle__label { | ||
@include form-fields-tools.inline-field-label(); | ||
} | ||
|
||
.UNSTABLE_Toggle__label--hidden { | ||
@include form-fields-tools.label-hidden(); | ||
} | ||
|
||
.UNSTABLE_Toggle__label--required::after { | ||
@include form-fields-tools.label-required(); | ||
} | ||
|
||
.UNSTABLE_Toggle__input { | ||
@include form-fields-tools.inline-field-input(); | ||
|
||
flex-shrink: 0; | ||
width: theme.$input-width; | ||
height: theme.$input-height; | ||
border: theme.$input-border-width solid form-fields-theme.$inline-field-input-color-unchecked; | ||
border-radius: theme.$input-border-radius; | ||
background-image: svg.escape(theme.$input-mark); | ||
background-position-x: theme.$input-mark-position-x; | ||
background-position-y: theme.$input-mark-position-y; | ||
background-size: theme.$input-mark-width theme.$input-mark-height; | ||
background-repeat: no-repeat; | ||
background-color: form-fields-theme.$inline-field-input-color-unchecked; | ||
|
||
@media (prefers-reduced-motion: no-preference) { | ||
transition-property: border-color, background-color, background-position; | ||
transition-duration: theme.$input-transition-duration; | ||
transition-timing-function: theme.$input-transition-timing; | ||
} | ||
|
||
&:focus { | ||
background-color: form-fields-theme.$inline-field-input-color-unchecked-active; | ||
box-shadow: theme.$input-focus-shadow; | ||
} | ||
|
||
&:checked { | ||
border-color: form-fields-theme.$inline-field-input-color-checked; | ||
background-position-x: theme.$input-mark-position-x-checked; | ||
background-color: form-fields-theme.$inline-field-input-color-checked; | ||
} | ||
|
||
@media (hover: hover) { | ||
&:hover { | ||
background-color: form-fields-theme.$inline-field-input-color-unchecked-hover; | ||
} | ||
|
||
&:checked:hover { | ||
background-color: form-fields-theme.$inline-field-input-color-checked-hover; | ||
} | ||
} | ||
} | ||
|
||
.UNSTABLE_Toggle__input--indicators { | ||
background-image: svg.escape(theme.$input-mark-indicators); | ||
} | ||
|
||
.UNSTABLE_Toggle__validationText, | ||
.UNSTABLE_Toggle > .UNSTABLE_Toggle__text > [data-spirit-element='validation_text'] { | ||
@include form-fields-tools.validation-text(); | ||
} | ||
|
||
.UNSTABLE_Toggle__helperText { | ||
@include form-fields-tools.helper-text(); | ||
} | ||
|
||
@include form-fields-tools.input-field-validation-states($_field-name); | ||
|
||
.UNSTABLE_Toggle--disabled { | ||
@include form-fields-tools.inline-field-root-disabled(); | ||
} | ||
|
||
.UNSTABLE_Toggle--disabled .UNSTABLE_Toggle__label { | ||
@include form-fields-tools.label-disabled(); | ||
} | ||
|
||
.UNSTABLE_Toggle--disabled > .UNSTABLE_Toggle__input, | ||
.UNSTABLE_Toggle > .UNSTABLE_Toggle__input:disabled { | ||
@include form-fields-tools.input-disabled(); | ||
|
||
border-color: theme.$input-border-color-disabled; | ||
background-image: svg.escape(theme.$input-mark-disabled); | ||
background-color: transparent; | ||
} | ||
|
||
.UNSTABLE_Toggle__input:checked:focus { | ||
background-color: form-fields-theme.$inline-field-input-color-checked-active; | ||
} | ||
|
||
.UNSTABLE_Toggle--disabled > .UNSTABLE_Toggle__input:checked, | ||
.UNSTABLE_Toggle > .UNSTABLE_Toggle__input:disabled:checked { | ||
background-image: svg.escape(theme.$input-mark-disabled-checked); | ||
} | ||
|
||
.UNSTABLE_Toggle--disabled .UNSTABLE_Toggle__validationText { | ||
@include form-fields-tools.validation-text-disabled(); | ||
} | ||
|
||
.UNSTABLE_Toggle--disabled .UNSTABLE_Toggle__helperText { | ||
@include form-fields-tools.helper-text-disabled(); | ||
} |
21 changes: 21 additions & 0 deletions
21
packages/web/src/scss/components/UNSTABLE_Toggle/_theme.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
@use '@tokens' as tokens; | ||
@use '../../settings/transitions'; | ||
|
||
$max-width: 21rem; | ||
$input-border-width: tokens.$border-width-100; | ||
$input-border-radius: 12px; | ||
$input-width: 44px; | ||
$input-height: 24px; | ||
$input-mark-height: 44px; | ||
$input-mark-width: 52px; | ||
$input-mark-position-y: -7px; // Position of the mark is offset because it has a shadow | ||
$input-mark-position-x: -15px; // Position of the mark in non-checked state | ||
$input-mark-position-x-checked: $input-mark-position-x + 20px; // Position of the mark in checked state | ||
$input-mark: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="#{$input-mark-width}" height="#{$input-mark-height}" viewBox="0 0 52 44" fill="none"><g xmlns="http://www.w3.org/2000/svg" filter="url(#a)"><circle xmlns="http://www.w3.org/2000/svg" cx="26" cy="18" r="10" fill="#{tokens.$background-basic}"/></g><defs><filter id="a" width="44" height="44" x="4" y="0" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="4"/><feGaussianBlur stdDeviation="6"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_1_81"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_1_81" result="shape"/></filter></defs></svg>'); | ||
$input-mark-indicators: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="#{$input-mark-width}" height="#{$input-mark-height}" viewBox="0 0 52 44" fill="none"><path fill="#{tokens.$background-basic}" d="M48.675 14.33a.58.58 0 0 0-.822 0L45 17.178l-2.852-2.852a.58.58 0 1 0-.823.823L44.177 18l-2.852 2.852a.58.58 0 1 0 .822.823L45 18.823l2.852 2.852a.58.58 0 1 0 .823-.823L45.823 18l2.852-2.852a.585.585 0 0 0 0-.817ZM5.25 20.651 3.208 18.61a.574.574 0 0 0-.816 0 .574.574 0 0 0 0 .817l2.444 2.444a.58.58 0 0 0 .822 0l6.184-6.177a.574.574 0 0 0 0-.817.574.574 0 0 0-.817 0L5.25 20.651Z"/><g xmlns="http://www.w3.org/2000/svg" filter="url(#a)"><circle xmlns="http://www.w3.org/2000/svg" cx="26" cy="18" r="10" fill="#{tokens.$background-basic}"/></g><defs><filter id="a" width="44" height="44" x="4" y="0" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="4"/><feGaussianBlur stdDeviation="6"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_1_81"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_1_81" result="shape"/></filter></defs></svg>'); | ||
$input-mark-disabled: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="#{$input-mark-width}" height="#{$input-mark-height}" viewBox="0 0 52 44" fill="none"><circle xmlns="http://www.w3.org/2000/svg" cx="26" cy="18" r="10" fill="#{tokens.$action-unselected-disabled}"/></svg>'); | ||
$input-mark-disabled-checked: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="#{$input-mark-width}" height="#{$input-mark-height}" viewBox="0 0 52 44" fill="none"><circle xmlns="http://www.w3.org/2000/svg" cx="26" cy="18" r="10" fill="#{tokens.$action-selected-disabled}"/></svg>'); | ||
$input-transition-duration: transitions.$duration-100; | ||
$input-transition-timing: transitions.$timing-eased-in-out; | ||
$input-border-color-disabled: tokens.$border-primary-disabled; | ||
$input-focus-shadow: tokens.$focus; |
Oops, something went wrong.