Skip to content

Commit

Permalink
Merge pull request #6 from Bandmators/feature/switch
Browse files Browse the repository at this point in the history
feat(common): switch (#5)
  • Loading branch information
kyechan99 authored Jan 29, 2024
2 parents e59a264 + 77930c8 commit bf420a6
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 6 deletions.
7 changes: 3 additions & 4 deletions src/components/common/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import styled from '@emotion/styled';
import React, { ComponentPropsWithoutRef } from 'react';

import { SizeType } from '@/types/size';
import { SpecialVariantType } from '@/types/variant';

type ButtonVariantType = 'primary' | 'secondary' | 'warning' | 'danger' | 'outline' | 'ghost';

const ButtonVariantStyles = ({ theme, variant }: { theme: Theme; variant: ButtonVariantType }) => {
const ButtonVariantStyles = ({ theme, variant }: { theme: Theme; variant: SpecialVariantType }) => {
switch (variant) {
case 'secondary':
return css`
Expand Down Expand Up @@ -104,7 +103,7 @@ export interface ButtonVariantProps {
/*
Button variant
*/
variant?: ButtonVariantType;
variant?: SpecialVariantType;
/*
Button size
*/
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/Checkbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
)}
</StyledCheckbox>
{label && <CheckboxLabel>{label}</CheckboxLabel>}
{children && <div>{children}</div>}
{children}
</CheckboxContainer>
);
},
Expand Down
213 changes: 213 additions & 0 deletions src/components/common/Switch/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { Theme, css } from '@emotion/react';
import styled from '@emotion/styled';
import * as React from 'react';

import { composeEventHandlers } from '@/libs/event';
import { SpecialSizeType } from '@/types/size';
import { VariantType } from '@/types/variant';

interface SwitchProps extends Omit<React.ComponentPropsWithoutRef<'input'>, 'size'> {
/*
Switch checked value
*/
checked?: boolean;
/*
Switch Label (Like Description)
*/
label?: string;
/*
Switch variant
*/
variant?: VariantType;
/*
Switch size
*/
size?: SpecialSizeType;
/*
Switch align style (css 'algin-items')
*/
align?: React.CSSProperties['alignItems'];
}

export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
(
{
className,
label,
checked = false,
variant = 'primary',
size = 'md',
align = 'center',
disabled = false,
onChange,
id,
children,
...props
},
ref,
) => {
const [chk, setChk] = React.useState<boolean>(checked);

React.useEffect(() => {
setChk(checked);
}, [checked]);

const onChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setChk(e.target.checked);
};

return (
<SwitchContainer align={align} disabled={disabled} className={className}>
<HiddenSwitch
ref={ref}
id={id}
type="checkbox"
checked={chk}
onChange={composeEventHandlers(onChange, onChangeHandler)}
disabled={disabled}
{...props}
/>
<StyledSwitch variant={variant} checked={chk} size={size} disabled={disabled} />
{label && <SwitchLabel>{label}</SwitchLabel>}
{children}
</SwitchContainer>
);
},
);
Switch.displayName = 'Switch';

const SwitchContainer = styled.label<{ align: React.CSSProperties['alignItems']; disabled: boolean }>`
position: relative;
display: inline-flex;
align-items: ${props => props.align};
line-height: 1;
${props => props.disabled && 'opacity: 0.5;'}
`;

const HiddenSwitch = styled.input`
clip: rect(0 0 0 0);
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0px;
border: none;
position: absolute;
white-space: nowrap;
`;

const SwitchLabel = styled.span`
font-weight: 500;
margin-left: 1rem;
`;

const SwitchVariantStyles = ({ theme, variant, checked }: { theme: Theme; variant: VariantType; checked: boolean }) => {
if (!checked) return;

switch (variant) {
case 'secondary':
return css`
background-color: ${theme.colors.secondary};
`;
case 'danger':
return css`
color: ${theme.colors.white};
background-color: ${theme.colors.danger};
`;
case 'warning':
return css`
color: ${theme.colors.white};
background-color: ${theme.colors.warning};
`;
case 'primary':
default:
return css`
color: white;
background-color: ${theme.colors.primary};
`;
}
};

const SwitchSizeStyles = ({ size, checked }: { size: SpecialSizeType; checked: boolean }) => {
switch (size) {
case 'sm':
return css`
width: 2rem;
height: 1rem;
&:after {
top: 0.1rem;
left: 0.1rem;
width: 0.8rem;
height: 0.8rem;
${checked && `left: calc(100% - 0.1rem);`}
}
`;
case 'lg':
return css`
width: 4rem;
height: 2rem;
&:after {
top: 0.2rem;
left: 0.2rem;
width: 1.6rem;
height: 1.6rem;
${checked && `left: calc(100% - 0.2rem);`}
}
`;
case 'xl':
return css`
width: 6rem;
height: 3rem;
&:after {
top: 0.3rem;
left: 0.3rem;
width: 2.4rem;
height: 2.4rem;
${checked && `left: calc(100% - 0.4rem);`}
}
`;
case 'md':
default:
return css`
width: 3rem;
height: 1.5rem;
&:after {
top: 0.15rem;
left: 0.15rem;
width: 1.2rem;
height: 1.2rem;
${checked && `left: calc(100% - 0.15rem);`}
}
`;
}
};

const StyledSwitch = styled.div<SwitchProps>`
position: relative;
display: inline-block;
border-radius: 50rem;
background-color: ${({ theme }) => theme.colors.gray['200']};
transition: background-color ease 0.2s;
cursor: pointer;
text-indent: -9999px;
&:after {
content: '';
position: absolute;
background: #fff;
border-radius: 50rem;
transition: 0.2s;
}
${props =>
props.checked &&
css`
&:after {
transform: translateX(-100%);
}
`}
${({ theme, variant, checked = false }) => variant && SwitchVariantStyles({ theme, variant, checked })}
${({ size, checked = false }) => size && SwitchSizeStyles({ size, checked })}
${props => props.disabled && 'cursor: not-allowed;'}
`;
1 change: 1 addition & 0 deletions src/components/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './Label';
export * from './Textarea';
export * from './Tooltip';
export * from './Checkbox';
export * from './Switch';
58 changes: 58 additions & 0 deletions src/stories/common/Switch.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';

import { Label, Switch } from '../..';

const meta = {
title: 'common/Switch',
component: Switch,
tags: ['autodocs'],
argTypes: {},
parameters: {
componentSubtitle: 'Base Switch',
},
} satisfies Meta<typeof Switch>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
checked: true,
label: 'Switch',
},
};

export const WithChildren: Story = {
args: {
id: 'switch-with-child',
label: 'With Children',
},
};

export const WithLabel: Story = {
args: {
id: 'switch-with-text',
},
decorators: [
Story => {
return (
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center', lineHeight: '1' }}>
<Story />
<Label htmlFor="switch-with-text" style={{ marginLeft: '0px' }}>
With Label
</Label>
</div>
);
},
],
};

export const Disabled: Story = {
args: {
checked: true,
label: 'Disabled',
disabled: true,
},
};
2 changes: 1 addition & 1 deletion src/styles/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const BMateColors = {
white: '#FAFAFA',
black: '#212121',
primary: '#212121',
secondary: '#EEEEEE',
secondary: '#E0E0E0',
gray: {
50: '#FAFAFA',
100: '#F5F5F5',
Expand Down
1 change: 1 addition & 0 deletions src/types/size.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export type SizeType = 'sm' | 'md' | 'lg';
export type SpecialSizeType = SizeType | 'xl';
2 changes: 2 additions & 0 deletions src/types/variant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type VariantType = 'primary' | 'secondary' | 'warning' | 'danger';
export type SpecialVariantType = VariantType | 'outline' | 'ghost';

0 comments on commit bf420a6

Please sign in to comment.