diff --git a/src/components/Switch/Switch.stories.tsx b/src/components/Switch/Switch.stories.tsx new file mode 100644 index 00000000..6cac3b90 --- /dev/null +++ b/src/components/Switch/Switch.stories.tsx @@ -0,0 +1,92 @@ +// import { action } from '@storybook/addon-actions'; +import React from 'react'; +import Switch from './Switch'; + +export default { + title: 'Switch', + component: Switch, +}; + +export const DefaultSwitch = () => { + const [showOn, setShowOn] = React.useState(false); + const handleChange = () => { + setShowOn(!showOn); + }; + return ( + + ); +}; + +export const DiffSizeSwitch = () => { + const [showOn, setShowOn] = React.useState(false); + return ( +
+ setShowOn(!showOn)} + isChecked={showOn} + label="Small Switch with default color" + /> + + +
+ ); +}; + +export const DiffColorSwitch = () => { + const [showOnP, setShowOnP] = React.useState(false); + const [showOnS, setShowOnS] = React.useState(false); + + console.log(showOnP); + return ( +
+ { + setShowOnP(!showOnP); + }} + label="Primary color Switch" + isChecked={showOnP} + /> + + setShowOnS(!showOnS)} + isChecked={showOnS} + label="Secondary color Switch" + /> +
+ ); +}; + +export const DiffTypeSwitch = () => { + const [showOn, setShowOn] = React.useState(false); + const [defaultOn, setDefaultOn] = React.useState(true); + return ( +
+ { + setShowOn(!showOn); + }} + isChecked={showOn} + label="Default Switch" + /> + + + + { + setDefaultOn(!defaultOn); + }} + isChecked={defaultOn} + label="On as default switch" + /> + + console.log('clicked')} + /> +
+ ); +}; diff --git a/src/components/Switch/Switch.test.tsx b/src/components/Switch/Switch.test.tsx new file mode 100644 index 00000000..d4211f94 --- /dev/null +++ b/src/components/Switch/Switch.test.tsx @@ -0,0 +1,103 @@ +import React from 'react'; +import Switch, { PatSwitchProps } from './Switch'; +import { render, fireEvent, screen } from '@testing-library/react'; + +describe('Switch', () => { + it('should match snapshot', () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render default switch', () => { + render(); + const element = screen.getByTestId('switch-element'); + const checkbox = screen.getByRole('checkbox'); + const label = screen.getByText('Default Switch'); + const slider = screen.getByTestId('test-slider'); + + expect(element).toBeInTheDocument(); + expect(checkbox).toBeInTheDocument(); + expect(label).toBeInTheDocument(); + expect(slider).toBeInTheDocument(); + expect(slider).toHaveClass('switch-slider'); + expect(element).toHaveClass('switch switch-sm'); + expect(checkbox).not.toBeChecked(); + fireEvent.click(checkbox); + expect(checkbox).toBeChecked(); + expect(element).toHaveClass('switch-default'); + }); + + it('should render corrsonding button based on props', () => { + const switchLargePrimary: PatSwitchProps = { + switchColor: 'primary', + switchSize: 'lg', + className: 'test', + }; + + render( + + ); + const element = screen.getByTestId('switch-element'); + const checkbox = screen.getByRole('checkbox'); + const label = screen.getByText('Large Primary Switch'); + const slider = screen.getByTestId('test-slider'); + + expect(element).toBeInTheDocument(); + expect(checkbox).toBeInTheDocument(); + expect(label).toBeInTheDocument(); + expect(slider).toBeInTheDocument(); + expect(slider).toHaveClass('switch-slider'); + expect(element).toHaveClass('switch switch-lg test'); + expect(checkbox).not.toBeChecked(); + fireEvent.click(checkbox); + expect(checkbox).toBeChecked(); + }); + + it('should render on switch', () => { + const switchDefualtOn: PatSwitchProps = { + isChecked: true, + }; + + render(); + const element = screen.getByTestId('switch-element'); + const checkbox = screen.getByRole('checkbox'); + const label = screen.getByText('Default On Switch'); + const slider = screen.getByTestId('test-slider'); + + expect(element).toBeInTheDocument(); + expect(checkbox).toBeInTheDocument(); + expect(label).toBeInTheDocument(); + expect(slider).toBeInTheDocument(); + expect(slider).toHaveClass('switch-slider'); + expect(element).toHaveClass('switch switch-default switch-sm'); + fireEvent.click(checkbox); + expect(checkbox).toBeChecked(); + expect(checkbox).toHaveAttribute('checked', ''); + }); + + it('should render a disabled switch', () => { + const disabledSwitchProp: PatSwitchProps = { + disabled: true, + onClick: jest.fn(), + }; + render(); + const element = screen.getByTestId('switch-element'); + const checkbox = screen.getByRole('checkbox'); + const label = screen.getByText('Disabled Switch'); + const slider = screen.getByTestId('test-slider'); + expect(element).toBeInTheDocument(); + expect(checkbox).toBeInTheDocument(); + expect(label).toBeInTheDocument(); + expect(slider).toBeInTheDocument(); + expect(slider).toHaveClass('switch-slider'); + expect(element).toBeInTheDocument(); + expect(checkbox).toBeInTheDocument(); + expect(label).toBeInTheDocument(); + expect(slider).toBeInTheDocument(); + expect(element).toHaveClass('switch switch-default switch-sm'); + expect(disabledSwitchProp.onClick).toHaveBeenCalledTimes(0); + fireEvent.click(element); + expect(disabledSwitchProp.onClick).toHaveBeenCalledTimes(0); + expect(slider).toHaveClass('disabled'); + }); +}); diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx new file mode 100644 index 00000000..3801d0f3 --- /dev/null +++ b/src/components/Switch/Switch.tsx @@ -0,0 +1,85 @@ +import React, { FC, InputHTMLAttributes } from 'react'; + +import { classNames } from '../../utils/classNames'; + +export type SwitchSize = 'sm' | 'lg'; +export type SwitchColor = 'primary' | 'secondary' | 'default'; + +export interface ISwitchProps { + /** set class name */ + className?: string; + /** set switch size */ + switchSize?: SwitchSize; + /** set switch color */ + switchColor?: SwitchColor; + /** set disabled switch */ + disabled?: boolean; + /** set default to checked */ + isChecked?: boolean; + /** set switch label */ + label?: string; +} + +export type PatSwitchProps = ISwitchProps & + InputHTMLAttributes; + +/** + * Switches toggle the state of a single setting on or off. + * + * ```js + * import {Switch} from 'pat-ui' + * ``` + * + */ +const Switch: FC = (props) => { + const { + switchSize, + className, + disabled = false, + switchColor, + label, + isChecked, + ...rest + } = props; + let styleClasses = classNames('switch', { + [`switch-${isChecked ? switchColor : 'default'}`]: true, + [`switch-${switchSize}`]: !!switchSize, + disabled: !!disabled, + }); + if (className) { + styleClasses += ' ' + className; + } + + let swh = ( +
+ +

{label}

+
+ ); + + return swh; +}; + +Switch.defaultProps = { + switchColor: 'default', + disabled: false, + switchSize: 'sm', +}; + +export default Switch; diff --git a/src/components/Switch/__snapshots__/Switch.test.tsx.snap b/src/components/Switch/__snapshots__/Switch.test.tsx.snap new file mode 100644 index 00000000..27db9f91 --- /dev/null +++ b/src/components/Switch/__snapshots__/Switch.test.tsx.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Switch should match snapshot 1`] = ` + +
+ +

+ Snapshot Switch +

+
+
+`; diff --git a/src/components/Switch/index.tsx b/src/components/Switch/index.tsx new file mode 100644 index 00000000..ed80f23e --- /dev/null +++ b/src/components/Switch/index.tsx @@ -0,0 +1 @@ +export { default } from './Switch'; diff --git a/src/components/Switch/styles/_Switch.scss b/src/components/Switch/styles/_Switch.scss new file mode 100644 index 00000000..c68f34bf --- /dev/null +++ b/src/components/Switch/styles/_Switch.scss @@ -0,0 +1,123 @@ +@import './variables'; +@import './mixin'; +.switch { + margin: $switch-margin; + display: flex; + justify-content: center; + align-items: center; + + &-label { + @include switch-lable-style( + 16px, + 24px, + 0.15px, + $font-family-base, + 0.3em, + 0.3em + ); + } + + &-container { + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + width: 26px; + height: 10px; + } + + &-toggle { + opacity: 0; + width: 0; + height: 0; + } + + &-slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 5px; + } + + &-slider::before { + position: absolute; + content: ''; + @include switch-slider(16px, 16px, -3px, -2px, 0.4s, 50%); + } + &-slider:hover::before { + box-shadow: 0 0 0 3px rgba(222, 226, 230, 0.18); + } + + &-toggle:checked + &-slider::before { + transform: translateX(13px); + } +} + +.switch { + &-lg { + .switch-container { + width: 34px; + height: 14px; + opacity: 50%; + } + .switch-slider::before { + height: 20px; + width: 20px; + opacity: 100% !important; + } + .switch-slider { + border-radius: 7px; + } + + .switch-toggle:checked + .switch-slider::before { + transform: translateX(18px); + } + } +} + +.switch-primary { + .switch-slider { + background-color: $switch-primary; + } + + .switch-slider::before { + background-color: $primary; + } + + .switch-slider:hover::before { + box-shadow: $switch-primary-hover; + } +} + +.switch-secondary { + .switch-slider { + background-color: rgba(108, 117, 125, 0.8); + } + + .switch-slider::before { + background-color: $secondary; + } + .switch-slider:hover::before { + box-shadow: 0 0 0 3px rgba(108, 117, 125, 0.2); + } +} + +.switch-default { + .switch-slider { + background-color: rgba(173, 181, 189, 0.8); + } + + .switch-slider::before { + background-color: $gray-300; + } + .switch-slider:hover::before { + box-shadow: 0 0 0 3px rgba(222, 226, 230, 0.2); + } +} + +.disabled { + cursor: not-allowed !important; +} diff --git a/src/components/Switch/styles/_mixin.scss b/src/components/Switch/styles/_mixin.scss new file mode 100644 index 00000000..ebf37205 --- /dev/null +++ b/src/components/Switch/styles/_mixin.scss @@ -0,0 +1,24 @@ +@mixin switch-lable-style( + $size, + $height, + $spacing, + $font, + $margin-l, + $margin-r +) { + font-size: $size; + line-height: $height; + letter-spacing: $spacing; + font-family: $font; + margin-left: $margin-l; + margin-top: $margin-r; +} + +@mixin switch-slider($heigth, $width, $top, $left, $transition, $br) { + height: $heigth; + width: $width; + top: $top; + left: $left; + transition: $transition; + border-radius: $br; +} diff --git a/src/components/Switch/styles/_variables.scss b/src/components/Switch/styles/_variables.scss new file mode 100644 index 00000000..4ba25c44 --- /dev/null +++ b/src/components/Switch/styles/_variables.scss @@ -0,0 +1,4 @@ +$switch-primary: rgba(32, 201, 151, 0.8); +$switch-secondary: rgba(108, 117, 125, 0.8); +$switch-primary-hover: 0 0 0 3px rgba(32, 201, 151, 0.2); +$switch-margin: 1em; diff --git a/src/index.tsx b/src/index.tsx index 70aeb63c..ba5d50b6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,3 +5,4 @@ export { default as Message } from './components/Message'; export { default as Card } from './components/Card'; export { default as Dropdown } from './components/Dropdown'; export { default as Progress } from './components/Progress'; +export { default as Switch } from './components/Switch'; diff --git a/src/styles/index.scss b/src/styles/index.scss index 33fa969f..cb1ee7fc 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -12,3 +12,7 @@ @import '../components/Input/Input'; @import '../components/Card/Card'; @import '../components/Progress/Progress'; + +//Switch + +@import '../components/Switch/styles/Switch';