From f16d53eb819b98a7013b6cbbab6609e6b77987b4 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 | 137 ++++++++++++++++++ packages/core/src/components/switch/switch.ts | 5 + packages/react/src/components/index.ts | 1 + .../src/components/switch/switch.stories.tsx | 12 ++ .../react/src/components/switch/switch.tsx | 36 +++++ 7 files changed, 195 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..c27d282ec --- /dev/null +++ b/packages/core/src/components/switch/switch.scss @@ -0,0 +1,137 @@ +@use '../../animations'; +@use '../../helpers'; +@use '../../mixins'; + +@mixin Switch() { + @if not mixins.includes('Switch') { + @include _Switch(); + } +} + +@mixin _Switch() { + .ods-switch-label { + align-items: center; + align-items: flex-start; + color: helpers.color('content-main'); + cursor: pointer; + display: inline-grid; + gap: helpers.space(1); + grid-template-columns: 1fr auto; + position: relative; + + input { + opacity: 0; + 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::after { + opacity: 1; + transform: translateY(-50%) translateX(helpers.space(1)) rotateZ(45deg) + scale(0.5); + } + + input:checked + .ods-switch-indicator:hover { + background-color: #5666f9; + border-color: #5666f9; + } + + input:checked + .ods-switch-indicator:hover::after { + 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::after, + input:disabled + .ods-switch-indicator:hover::after { + 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(0.5); + position: relative; + transition: var(--ods-transition-duration) ease; + transition-property: background-color, border-color; + 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: background-color linear 0.2s, + transform calc(var(--ods-transition-duration) * 2) ease; + width: 18px; + } + + // check icon + &::after { + border: solid helpers.color('border-input-selected'); + border-width: 0 helpers.space(0.5) helpers.space(0.5) 0; + content: ''; + display: inline-block; + left: helpers.space(1.625); + opacity: 0; + padding: helpers.space(1) helpers.space(0.5); + position: relative; + top: 50%; + transform: translateY(-50%) translateX(-100%) rotateZ(-45deg) scale(0); + transition: calc(var(--ods-transition-duration) * 2) ease; + transition-property: opacity, transform; + } + } +} diff --git a/packages/core/src/components/switch/switch.ts b/packages/core/src/components/switch/switch.ts new file mode 100644 index 000000000..1eadf58c7 --- /dev/null +++ b/packages/core/src/components/switch/switch.ts @@ -0,0 +1,5 @@ +export interface SwitchProps { + disabled?: boolean; + onChange?: (checked: boolean) => void; + className?: string; +} 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..79ab9a922 --- /dev/null +++ b/packages/react/src/components/switch/switch.stories.tsx @@ -0,0 +1,12 @@ +import { Switch, SwitchProps } from '@onfido/castor-react'; +import { Meta, Story } from '../../../../../docs'; + +export default { + title: 'React/Switch', + component: Switch, + argTypes: {}, + args: {}, + parameters: { display: 'flex' }, +} as Meta; + +export const Playground: Story = {}; diff --git a/packages/react/src/components/switch/switch.tsx b/packages/react/src/components/switch/switch.tsx new file mode 100644 index 000000000..25a780743 --- /dev/null +++ b/packages/react/src/components/switch/switch.tsx @@ -0,0 +1,36 @@ +import { c, classy, m } from '@onfido/castor'; +import React from 'react'; + +export interface SwitchProps extends Omit { + checked?: boolean; + disabled?: boolean; + onChange?: (checked: boolean) => void; +} + +export const Switch = ({ + id = `switch-${++idCount}`, + disabled, + className, + children, + onChange, + ...props +}: SwitchProps) => ( + +); + +let idCount = 0; + +type JsxInput = JSX.IntrinsicElements['input'];