From aba2a30b1202c19fa7b77f0647d5031272031405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Boukorras?= Date: Mon, 27 May 2024 15:06:22 +0200 Subject: [PATCH] feat: add switch component --- packages/core/src/components/index.scss | 3 + packages/core/src/components/index.ts | 1 + .../core/src/components/switch/switch.scss | 132 ++++++++++++++++++ packages/core/src/components/switch/switch.ts | 4 + packages/react/src/components/index.ts | 1 + .../src/components/switch/switch.stories.tsx | 62 ++++++++ .../react/src/components/switch/switch.tsx | 28 ++++ 7 files changed, 231 insertions(+) create mode 100644 packages/core/src/components/switch/switch.scss create mode 100644 packages/core/src/components/switch/switch.ts create mode 100644 packages/react/src/components/switch/switch.stories.tsx create mode 100644 packages/react/src/components/switch/switch.tsx diff --git a/packages/core/src/components/index.scss b/packages/core/src/components/index.scss index fc1dd84aa..93892b2e6 100644 --- a/packages/core/src/components/index.scss +++ b/packages/core/src/components/index.scss @@ -44,6 +44,8 @@ @forward './validation/validation'; @use './accordion/accordion.scss'; @forward './accordion/accordion.scss'; +@use './switch/switch.scss'; +@forward './switch/switch.scss'; @mixin components() { @include asterisk.Asterisk(); @@ -69,4 +71,5 @@ @include tooltip.Tooltip(); @include validation.Validation(); @include accordion.Accordion(); + @include switch.Switch(); } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index cf9e912c1..faa986538 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -12,6 +12,7 @@ export * from './progress/progress'; export * from './radio/radio'; export * from './select/select'; export * from './spinner/spinner'; +export * from './switch/switch'; export * from './textarea/textarea'; export * from './tooltip/tooltip'; export * from './validation/validation'; diff --git a/packages/core/src/components/switch/switch.scss b/packages/core/src/components/switch/switch.scss new file mode 100644 index 000000000..3dea19757 --- /dev/null +++ b/packages/core/src/components/switch/switch.scss @@ -0,0 +1,132 @@ +/* stylelint-disable selector-max-compound-selectors */ + +@use '../../animations'; +@use '../../helpers'; +@use '../../mixins'; + +@mixin Switch() { + @if not mixins.includes('Switch') { + @include _Switch(); + } +} + +@mixin _Switch() { + .ods-switch-label { + align-items: center; + color: helpers.color('content-main'); + cursor: pointer; + display: inline-flex; + gap: helpers.space(1); + position: relative; + + input { + opacity: 0; + pointer-events: none; + position: absolute; + } + + // Hover + input + .ods-switch-indicator:hover { + border-color: #636670; + } + + input + .ods-switch-indicator:hover::before { + background-color: #636670; + } + + // Checked + input:checked + .ods-switch-indicator { + background-color: helpers.color('background-input-selected'); + border-color: helpers.color('border-input-selected'); + } + + input:checked + .ods-switch-indicator::before { + background-color: helpers.color('background-input'); + transform: translateY(-50%) translateX(helpers.space(2)); + } + + input:checked + .ods-switch-indicator .ods-icon { + opacity: 1; + transform: translateY(-50%) translateX(helpers.space(2)); + } + + input:checked + .ods-switch-indicator:hover { + background-color: #5666f9; + border-color: #5666f9; + } + + input:checked + .ods-switch-indicator:hover .ods-icon { + border-color: #5666f9; + } + + // Focus + input:focus + .ods-switch-indicator { + box-shadow: // + 0 0 0 helpers.space(0.25) helpers.color('border-focus-inner'), + 0 0 0 helpers.space(0.5) helpers.color('border-action-focus'); + } + + // Disabled + input:disabled + .ods-switch-indicator { + cursor: not-allowed; + } + + input:disabled + .ods-switch-indicator, + input:disabled + .ods-switch-indicator:hover, + input:disabled + .ods-switch-indicator .ods-icon, + input:disabled + .ods-switch-indicator:hover .ods-icon { + border-color: helpers.color('border-disabled'); + } + + input:disabled:checked + .ods-switch-indicator, + input:disabled + .ods-switch-indicator::before { + background-color: helpers.color('background-disabled'); + } + + input:disabled:checked + .ods-switch-indicator::before { + background-color: #fff; + } + } + + .ods-switch-indicator { + $border-width: helpers.space(0.25); + + background-color: helpers.color('background-input'); + border: $border-width solid helpers.color('border-input'); + border-radius: helpers.border-radius('full'); + box-sizing: border-box; + height: helpers.space(3); + margin: helpers.space(1.5) helpers.space(1); + position: relative; + width: helpers.space(5); + + // slider + &::before { + background-color: #828893; + border-radius: helpers.border-radius('full'); + content: ''; + display: inline-block; + height: 18px; + left: 1px; + position: absolute; + top: 50%; + transform: translateY(-50%); + transition: transform calc(var(--ods-transition-duration) * 2) ease-out; + width: 18px; + } + + // check icon + .ods-icon { + content: ''; + fill: helpers.color('border-input-selected'); + height: helpers.space(2); + left: 2px; + opacity: 0; + position: absolute; + top: calc(50% - 1px); + transform: translateY(-50%) translateX(0); + transition: transform calc(var(--ods-transition-duration) * 2) ease-out; + width: helpers.space(2); + } + } +} diff --git a/packages/core/src/components/switch/switch.ts b/packages/core/src/components/switch/switch.ts new file mode 100644 index 000000000..1403d1ed3 --- /dev/null +++ b/packages/core/src/components/switch/switch.ts @@ -0,0 +1,4 @@ +export interface SwitchProps { + disabled?: boolean; + checked?: boolean; +} diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index b19eb3447..7c2efbe88 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -20,6 +20,7 @@ export * from './select/option'; export * from './select/option-group'; export * from './select/select'; export * from './spinner/spinner'; +export * from './switch/switch'; export * from './textarea/textarea'; export * from './tooltip/tooltip'; export * from './validation/validation'; diff --git a/packages/react/src/components/switch/switch.stories.tsx b/packages/react/src/components/switch/switch.stories.tsx new file mode 100644 index 000000000..9a6d6dbed --- /dev/null +++ b/packages/react/src/components/switch/switch.stories.tsx @@ -0,0 +1,62 @@ +import { color, type SwitchProps } from '@onfido/castor'; +import { Field, FieldLabel, Fieldset, Switch } from '@onfido/castor-react'; +import React, { useState, type ChangeEvent } from 'react'; +import { Meta, Story } from '../../../../../docs'; + +export default { + title: 'React/Switch', + component: Switch, + argTypes: { + checked: { + type: 'boolean', + }, + children: { description: 'Acts as a label for the ``.' }, + disabled: { + type: 'boolean', + }, + }, + args: { + children: '', + }, + parameters: {}, +} as Meta; + +export const Playground: Story = {}; + +export const Examples: Story = { + render: () => { + const [switchState, setSwitchState] = useState(true); + const handleOnChange = (event: ChangeEvent) => { + setSwitchState(event.target.checked); + }; + + return ( +
+ + + + Label + + + + + + + Label with disabled switch + + + + + + + Label with checked switch + + + +
+ ); + }, +}; diff --git a/packages/react/src/components/switch/switch.tsx b/packages/react/src/components/switch/switch.tsx new file mode 100644 index 000000000..e8c5f2825 --- /dev/null +++ b/packages/react/src/components/switch/switch.tsx @@ -0,0 +1,28 @@ +import { c, classy, m, type SwitchProps as BaseProps } from '@onfido/castor'; +import React, { type FC } from 'react'; +import { Icon } from '../icon/icon'; + +let idCount = 0; + +export const Switch: FC = ({ + id = `castor_switch_${++idCount}`, + disabled, + className, + children, + ...props +}) => ( + +); + +export type SwitchProps = BaseProps & Omit; + +type SwitchElementProps = JSX.IntrinsicElements['input'];