Skip to content

Commit

Permalink
feat: add switch component
Browse files Browse the repository at this point in the history
  • Loading branch information
itupix committed Jun 20, 2024
1 parent 5a8273f commit bcbf178
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 0 deletions.
3 changes: 3 additions & 0 deletions packages/core/src/components/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -69,4 +71,5 @@
@include tooltip.Tooltip();
@include validation.Validation();
@include accordion.Accordion();
@include switch.Switch();
}
1 change: 1 addition & 0 deletions packages/core/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
136 changes: 136 additions & 0 deletions packages/core/src/components/switch/switch.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
@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-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.5);
opacity: 0;
padding: helpers.space(1) helpers.space(0.5);
position: relative;
top: calc(50% - 1px);
transform: translateY(-50%) translateX(-100%) rotateZ(-45deg) scale(0);
transition: calc(var(--ods-transition-duration) * 2) ease;
transition-property: opacity, transform;
}
}
}
6 changes: 6 additions & 0 deletions packages/core/src/components/switch/switch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface SwitchProps {
id?: string;
disabled?: boolean;
onChange?: (checked: boolean) => void;
className?: string;
}
1 change: 1 addition & 0 deletions packages/react/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
54 changes: 54 additions & 0 deletions packages/react/src/components/switch/switch.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { color, type SwitchProps } from '@onfido/castor';
import { Field, FieldLabel, Fieldset, Switch } from '@onfido/castor-react';
import React, { useState } from 'react';
import { Meta, Story } from '../../../../../docs';

export default {
title: 'React/Switch',
component: Switch,
argTypes: {
disabled: {
table: { type: { summary: 'boolean' } },
},
},
args: {
disabled: false,
},
parameters: {},
} as Meta<SwitchProps>;

export const Playground: Story<SwitchProps> = {};

export const Examples: Story<SwitchProps> = {
render: () => {
const [switchState, setSwitchState] = useState(true);
const handleOnChange = (checked) => {

Check failure on line 25 in packages/react/src/components/switch/switch.stories.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Parameter 'checked' implicitly has an 'any' type.
setSwitchState(checked);
};
return (
<Fieldset>
<Field>
<Switch>
<FieldLabel style={{ width: 250 }}>Label</FieldLabel>
</Switch>
</Field>
<Field>
<Switch disabled>
<FieldLabel
style={{ width: 250, color: color('content-disabled') }}
>
Label with disabled switch
</FieldLabel>
</Switch>
</Field>
<Field>
<Switch checked={switchState} onChange={handleOnChange}>
<FieldLabel style={{ width: 250 }}>
Label with checked switch
</FieldLabel>
</Switch>
</Field>
</Fieldset>
);
},
};
32 changes: 32 additions & 0 deletions packages/react/src/components/switch/switch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { c, classy, m, SwitchProps as BaseProps } from '@onfido/castor';
import React, { type FC } from 'react';

export const Switch: FC<SwitchProps> = ({
id = `switch-${++idCount}`,
disabled,
className,
children,
onChange,
...props
}) => (
<label
htmlFor={id}
className={classy(className, c('switch-label'), m({ disabled }))}
>
{children && <span>{children}</span>}
<input
{...props}
type="checkbox"
id={id}
disabled={disabled}
onChange={(ev) => onChange?.(ev.currentTarget.checked)}
/>
<span className={classy(c('switch-indicator'), m({ disabled }))}></span>
</label>
);

export type SwitchProps = BaseProps & Omit<SwitchElementProps, 'children'>;

type SwitchElementProps = JSX.IntrinsicElements['input'];

let idCount = 0;

0 comments on commit bcbf178

Please sign in to comment.