From 64c48a45261f0c2524dffb00b0a2c10ba8c9c5e7 Mon Sep 17 00:00:00 2001 From: Yusen Jiang Date: Sun, 18 Sep 2022 13:10:00 -0700 Subject: [PATCH 01/17] UI for switch component --- src/components/Switch/Switch.tsx | 36 +++++++++++++++++++++++++++ src/components/Switch/_Switch.scss | 40 ++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 src/components/Switch/Switch.tsx create mode 100644 src/components/Switch/_Switch.scss diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx new file mode 100644 index 00000000..45a97857 --- /dev/null +++ b/src/components/Switch/Switch.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { classNames } from '../../utils/classNames'; + +export type SwitchSize = 'sm' | 'lg'; +export type SwitchColor = 'primary' | 'secondary'; + +export interface ISwitchProps { + className?: string; + switchSize?: SwitchSize; + switchColor?: SwitchColor; + disabled?: boolean; + label?: string; +} + +const Switch: React.FC = (props) => { + // const { switchSize, className, disabled, switchColor, label } = props; + + // let styleClasses = classNames('btn', { + // [`btn-${switchColor}`]: true, + // [`btn-${switchSize}`]: !!switchSize, + // }); + // if (className) { + // styleClasses += ' ' + className; + // } + + return ( + <> + + + ); +}; + +export default Switch; diff --git a/src/components/Switch/_Switch.scss b/src/components/Switch/_Switch.scss new file mode 100644 index 00000000..c6ffa7eb --- /dev/null +++ b/src/components/Switch/_Switch.scss @@ -0,0 +1,40 @@ +.switch-label { + margin-top: 50px; + margin-left: 50px; + position: relative; + display: inline-block; + width: 26px; + height: 10px; +} + +.switch-toggle { + opacity: 0; + width: 0; + height: 0; +} + +.switch-slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + border-radius: 5px; +} + +.switch-slider::before { + position: absolute; + content: ''; + height: 16px; + width: 16px; + top: -3px; + background-color: greenyellow; + transition: 0.4s; + border-radius: 50%; +} + +.switch-toggle:checked + .switch-slider::before { + transform: translateX(11px); +} From 784d2797bcfbd5c565e9bed7893ab4b6be892959 Mon Sep 17 00:00:00 2001 From: Yusen Jiang Date: Sun, 18 Sep 2022 18:04:56 -0700 Subject: [PATCH 02/17] finish switch component --- src/components/Switch/Switch.tsx | 47 ++++++--- src/components/Switch/_Switch.scss | 147 +++++++++++++++++++++++------ 2 files changed, 149 insertions(+), 45 deletions(-) diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx index 45a97857..8677bd1f 100644 --- a/src/components/Switch/Switch.tsx +++ b/src/components/Switch/Switch.tsx @@ -2,34 +2,53 @@ import React from 'react'; import { classNames } from '../../utils/classNames'; export type SwitchSize = 'sm' | 'lg'; -export type SwitchColor = 'primary' | 'secondary'; +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 switch label label?: string; + //set action when clicked + onClick?: () => void; } const Switch: React.FC = (props) => { - // const { switchSize, className, disabled, switchColor, label } = props; + const { switchSize, className, disabled, switchColor, label } = props; - // let styleClasses = classNames('btn', { - // [`btn-${switchColor}`]: true, - // [`btn-${switchSize}`]: !!switchSize, - // }); - // if (className) { - // styleClasses += ' ' + className; - // } + let styleClasses = classNames('switch', { + [`switch-${switchColor}`]: true, + [`switch-${switchSize}`]: !!switchSize, + disabled: !!disabled, + }); + if (className) { + styleClasses += ' ' + className; + } return ( - <> - -

{label}

+

{label || props.children}

); diff --git a/src/styles/index.scss b/src/styles/index.scss index 33fa969f..ba400b5d 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/Switch'; From b30af27d4575b91ac07ba65c5ad6288efa8e4a54 Mon Sep 17 00:00:00 2001 From: Yusen Jiang Date: Sun, 18 Sep 2022 21:32:51 -0700 Subject: [PATCH 05/17] allow user set the default behavior(on/off) --- src/components/Switch/Switch.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx index 615ad28c..4a50e27b 100644 --- a/src/components/Switch/Switch.tsx +++ b/src/components/Switch/Switch.tsx @@ -13,6 +13,8 @@ export interface ISwitchProps { switchColor?: SwitchColor; //set disabled switch disabled?: boolean; + //set default to checked + checked?: boolean; //set switch label label?: string; //set action when clicked @@ -20,7 +22,18 @@ export interface ISwitchProps { } const Switch: React.FC = (props) => { - const { switchSize, className, disabled, switchColor, label } = props; + const { + switchSize, + className, + disabled, + switchColor, + label, + checked, + } = props; + + //controls default bahavior (on or off) of the switch + const defaultChecked = checked ? checked : false; + const [isChecked, setIsChecked] = React.useState(defaultChecked); let styleClasses = classNames('switch', { [`switch-${switchColor}`]: true, @@ -37,8 +50,10 @@ const Switch: React.FC = (props) => { e.preventDefault() : props.onClick} + onChange={() => setIsChecked((prev) => !prev)} /> Date: Sun, 18 Sep 2022 21:35:06 -0700 Subject: [PATCH 06/17] add comment to the onchange function --- src/components/Switch/Switch.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx index 4a50e27b..408bbf12 100644 --- a/src/components/Switch/Switch.tsx +++ b/src/components/Switch/Switch.tsx @@ -53,6 +53,7 @@ const Switch: React.FC = (props) => { checked={isChecked} //avoid any action when disabled onClick={disabled ? (e) => e.preventDefault() : props.onClick} + //set state to control on/off on the switch onChange={() => setIsChecked((prev) => !prev)} /> Date: Mon, 19 Sep 2022 09:37:40 -0700 Subject: [PATCH 07/17] add storybook --- src/components/Switch/Switch.stories.tsx | 56 ++++++++++++++++++++++++ src/components/Switch/Switch.tsx | 19 ++++---- 2 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 src/components/Switch/Switch.stories.tsx diff --git a/src/components/Switch/Switch.stories.tsx b/src/components/Switch/Switch.stories.tsx new file mode 100644 index 00000000..7c91f2de --- /dev/null +++ b/src/components/Switch/Switch.stories.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { action } from '@storybook/addon-actions'; +import Switch from './Switch'; + +export default { + title: 'Switch', + component: Switch, +}; + +export const DefaultSwitch = () => ( + Default Switch +); + +export const DiffSizeSwitch = () => ( +
+ + Small Switch + + + + Small Switch + +
+); + +export const DiffColorSwitch = () => ( +
+ + Primary color Switch + + + + Secondary color Switch + +
+); + +export const DiffTypeSwitch = () => ( +
+ Default Switch + + + On as default switch + + + + Defualt disabled switch + +
+); diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx index 408bbf12..fd18c5ea 100644 --- a/src/components/Switch/Switch.tsx +++ b/src/components/Switch/Switch.tsx @@ -1,27 +1,28 @@ import React from 'react'; +import { FC, useState } from 'react'; import { classNames } from '../../utils/classNames'; export type SwitchSize = 'sm' | 'lg'; export type SwitchColor = 'primary' | 'secondary' | 'default'; export interface ISwitchProps { - // set class name + /** set class name */ className?: string; - //set switch size + /** set switch size */ switchSize?: SwitchSize; - //set switch color + /** set switch color */ switchColor?: SwitchColor; - //set disabled switch + /** set disabled switch */ disabled?: boolean; - //set default to checked + /** set default to checked */ checked?: boolean; - //set switch label + /** set switch label */ label?: string; - //set action when clicked + /** set action when clicked */ onClick?: () => void; } -const Switch: React.FC = (props) => { +export const Switch: FC = (props) => { const { switchSize, className, @@ -33,7 +34,7 @@ const Switch: React.FC = (props) => { //controls default bahavior (on or off) of the switch const defaultChecked = checked ? checked : false; - const [isChecked, setIsChecked] = React.useState(defaultChecked); + const [isChecked, setIsChecked] = useState(defaultChecked); let styleClasses = classNames('switch', { [`switch-${switchColor}`]: true, From 1e04dcb1fc827cdc50ec7baa0725433bfc46e2db Mon Sep 17 00:00:00 2001 From: Yusen Jiang Date: Mon, 19 Sep 2022 09:46:47 -0700 Subject: [PATCH 08/17] add comments in Switch.tsx --- src/components/Switch/Switch.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx index fd18c5ea..32669965 100644 --- a/src/components/Switch/Switch.tsx +++ b/src/components/Switch/Switch.tsx @@ -22,6 +22,14 @@ export interface ISwitchProps { onClick?: () => void; } +/** + * Switches toggle the state of a single setting on or off. + * + * ```js + * import {Switch} from 'pat-ui' + * ``` + * + */ export const Switch: FC = (props) => { const { switchSize, From e2519a9a4d305f28da478f09f717b45686775913 Mon Sep 17 00:00:00 2001 From: Yusen Jiang Date: Tue, 20 Sep 2022 00:56:33 -0700 Subject: [PATCH 09/17] allow user to control the behaviors on the switch --- src/components/Switch/Switch.stories.tsx | 121 +++++++++++++++-------- src/components/Switch/Switch.tsx | 21 ++-- 2 files changed, 90 insertions(+), 52 deletions(-) diff --git a/src/components/Switch/Switch.stories.tsx b/src/components/Switch/Switch.stories.tsx index 7c91f2de..7eb70572 100644 --- a/src/components/Switch/Switch.stories.tsx +++ b/src/components/Switch/Switch.stories.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { action } from '@storybook/addon-actions'; import Switch from './Switch'; export default { @@ -7,50 +6,88 @@ export default { component: Switch, }; -export const DefaultSwitch = () => ( - Default Switch -); +export const DefaultSwitch = () => { + const [showOn, setShowOn] = React.useState(false); -export const DiffSizeSwitch = () => ( -
- - Small Switch + //why use switch + //With a controlled component, the input’s(switch) value is always driven by the React state. + return ( + setShowOn(!showOn)} isChecked={showOn}> + Default Switch + ); +}; - - Small Switch - -
-); +export const DiffSizeSwitch = () => { + const [showOn, setShowOn] = React.useState(false); + return ( +
+ setShowOn(!showOn)} + isChecked={showOn} + > + Small Switch with default color + -export const DiffColorSwitch = () => ( -
- - Primary color Switch - + Large Switch with default color +
+ ); +}; - - Secondary color Switch - -
-); - -export const DiffTypeSwitch = () => ( -
- Default Switch - - - On as default switch - +export const DiffColorSwitch = () => { + const [showOnP, setShowOnP] = React.useState(false); + const [showOnS, setShowOnS] = React.useState(false); - - Defualt disabled switch - -
-); + return ( +
+ { + setShowOnP(!showOnP); + }} + isChecked={showOnP} + > + Primary color Switch + + + setShowOnS(!showOnS)} + isChecked={showOnS} + > + Secondary color Switch + +
+ ); +}; + +export const DiffTypeSwitch = () => { + const [showOn, setShowOn] = React.useState(false); + const [defaultOn, setDefaultOn] = React.useState(true); + return ( +
+ { + setShowOn(!showOn); + }} + isChecked={showOn} + > + Default Switch + + + + + { + setDefaultOn(!defaultOn); + }} + isChecked={defaultOn} + > + On as default switch + + + Defualt disabled switch +
+ ); +}; diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx index 32669965..cad2410d 100644 --- a/src/components/Switch/Switch.tsx +++ b/src/components/Switch/Switch.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { FC, useState } from 'react'; +import React, { FC } from 'react'; + import { classNames } from '../../utils/classNames'; export type SwitchSize = 'sm' | 'lg'; @@ -15,7 +15,7 @@ export interface ISwitchProps { /** set disabled switch */ disabled?: boolean; /** set default to checked */ - checked?: boolean; + isChecked?: boolean; /** set switch label */ label?: string; /** set action when clicked */ @@ -37,15 +37,15 @@ export const Switch: FC = (props) => { disabled, switchColor, label, - checked, + isChecked, } = props; //controls default bahavior (on or off) of the switch - const defaultChecked = checked ? checked : false; - const [isChecked, setIsChecked] = useState(defaultChecked); + // const defaultChecked = checked ? checked : false; + // const [isChecked, setIsChecked] = useState(defaultChecked); let styleClasses = classNames('switch', { - [`switch-${switchColor}`]: true, + [`switch-${isChecked ? switchColor : 'default'}`]: true, [`switch-${switchSize}`]: !!switchSize, disabled: !!disabled, }); @@ -59,11 +59,12 @@ export const Switch: FC = (props) => { e.preventDefault() : props.onClick} - //set state to control on/off on the switch - onChange={() => setIsChecked((prev) => !prev)} + // //set state to control on/off on the switch + // onChange={() => setIsChecked((prev) => !prev)} + onChange={props.onClick} + checked={isChecked} /> Date: Tue, 20 Sep 2022 12:31:25 -0700 Subject: [PATCH 10/17] finished switch storybook --- src/components/Switch/Switch.stories.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Switch/Switch.stories.tsx b/src/components/Switch/Switch.stories.tsx index 7eb70572..34d32a7d 100644 --- a/src/components/Switch/Switch.stories.tsx +++ b/src/components/Switch/Switch.stories.tsx @@ -10,9 +10,13 @@ export const DefaultSwitch = () => { const [showOn, setShowOn] = React.useState(false); //why use switch - //With a controlled component, the input’s(switch) value is always driven by the React state. + //in controlled component, the input’s(switch) value is always driven by the React state. + const handleClick = () => { + setShowOn(!showOn); + }; + console.log(showOn, showOn ? 'on' : 'off'); return ( - setShowOn(!showOn)} isChecked={showOn}> + Default Switch ); From ea7b69e379849e8f158942770dd937c72e1fe071 Mon Sep 17 00:00:00 2001 From: Yusen Jiang Date: Tue, 20 Sep 2022 14:31:31 -0700 Subject: [PATCH 11/17] finished switch tests --- src/components/Switch/Switch.test.tsx | 86 +++++++++++++++++++ src/components/Switch/Switch.tsx | 5 +- .../Switch/__snapshots__/Switch.test.tsx.snap | 30 +++++++ src/components/Switch/index.tsx | 1 + src/index.tsx | 1 + 5 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/components/Switch/Switch.test.tsx create mode 100644 src/components/Switch/__snapshots__/Switch.test.tsx.snap create mode 100644 src/components/Switch/index.tsx diff --git a/src/components/Switch/Switch.test.tsx b/src/components/Switch/Switch.test.tsx new file mode 100644 index 00000000..7de6c22c --- /dev/null +++ b/src/components/Switch/Switch.test.tsx @@ -0,0 +1,86 @@ +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( Snapshot Switch ); + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render default switch', () => { + render(Defualt Switch); + const element = screen.getByTestId('switch-element'); + const checkbox = screen.getByRole('checkbox'); + + expect(element).toBeInTheDocument(); + expect(checkbox).toBeInTheDocument(); + expect(element).toHaveClass('switch switch-default switch-sm'); + expect(checkbox).not.toBeChecked(); + fireEvent.click(checkbox); + expect(checkbox).toBeChecked(); + }); + + it('should render corrsonding button based on props', () => { + const switchLargePrimary: PatSwitchProps = { + switchColor: 'primary', + switchSize: 'lg', + }; + + render(); + const element = screen.getByTestId('switch-element'); + const checkbox = screen.getByRole('checkbox'); + expect(element).toBeInTheDocument(); + expect(checkbox).toBeInTheDocument(); + expect(element).toHaveClass('switch switch-default switch-lg'); + expect(checkbox).not.toBeChecked(); + fireEvent.click(checkbox); + expect(checkbox).toBeChecked(); + }); + + it('should render on switch', () => { + const switchDefualtOn: PatSwitchProps = { + isChecked: true, + onClick: jest.fn(), + }; + + render(); + const element = screen.getByTestId('switch-element'); + const checkbox = screen.getByRole('checkbox'); + expect(element).toBeInTheDocument(); + expect(checkbox).toBeInTheDocument(); + expect(element).toHaveClass('switch switch-default switch-sm'); + expect(checkbox).toBeChecked(); + }); + + it('should render on switch', () => { + const switchDefualtOn: PatSwitchProps = { + isChecked: true, + onClick: jest.fn(), + }; + + render(); + const element = screen.getByTestId('switch-element'); + const checkbox = screen.getByRole('checkbox'); + expect(element).toBeInTheDocument(); + expect(checkbox).toBeInTheDocument(); + expect(element).toHaveClass('switch switch-default switch-sm'); + expect(checkbox).toBeChecked(); + }); + + 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'); + expect(element).toBeInTheDocument(); + expect(checkbox).toBeInTheDocument(); + expect(element).toHaveClass('switch switch-default switch-sm'); + expect(disabledSwitchProp.onClick).toHaveBeenCalledTimes(0); + fireEvent.click(element); + expect(disabledSwitchProp.onClick).toHaveBeenCalledTimes(0); + }); +}); diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx index cad2410d..edb58b8c 100644 --- a/src/components/Switch/Switch.tsx +++ b/src/components/Switch/Switch.tsx @@ -22,6 +22,8 @@ export interface ISwitchProps { onClick?: () => void; } +export type PatSwitchProps = ISwitchProps; + /** * Switches toggle the state of a single setting on or off. * @@ -53,12 +55,13 @@ export const Switch: FC = (props) => { styleClasses += ' ' + className; } let swh = ( -
+

- Snapshot Switch + Snapshot Switch

From f74418f980188f41e728db34310cd938b9641c51 Mon Sep 17 00:00:00 2001 From: Yusen Jiang Date: Wed, 21 Sep 2022 08:19:47 -0700 Subject: [PATCH 13/17] bug fixes --- src/components/Switch/Switch.stories.tsx | 14 ++++++++++---- src/components/Switch/Switch.tsx | 13 +++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/Switch/Switch.stories.tsx b/src/components/Switch/Switch.stories.tsx index 04e479e1..aabb1967 100644 --- a/src/components/Switch/Switch.stories.tsx +++ b/src/components/Switch/Switch.stories.tsx @@ -1,6 +1,7 @@ -import { action } from '@storybook/addon-actions'; +// import { action } from '@storybook/addon-actions'; import React from 'react'; import Switch from './Switch'; +import Button from '../Button'; export default { title: 'Switch', @@ -13,10 +14,10 @@ export const DefaultSwitch = () => { //why use switch //in controlled component, the input’s(switch) value is always driven by the React state. - const handleChange = (e: any) => { + const handleChange = (e: React.ChangeEvent) => { // setShowOn((prev) => !prev); setShowOn(!showOn); - action(showOn ? 'off' : 'on')(e); + // action(showOn ? 'off' : 'on')(e); }; return ( @@ -48,6 +49,7 @@ export const DiffColorSwitch = () => { const [showOnP, setShowOnP] = React.useState(false); const [showOnS, setShowOnS] = React.useState(false); + console.log(showOnP); return (
{ label="On as default switch" /> - + console.log('clicked')} + />
); }; diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx index 1c475440..4d71ad27 100644 --- a/src/components/Switch/Switch.tsx +++ b/src/components/Switch/Switch.tsx @@ -55,6 +55,15 @@ const Switch: FC = (props) => { styleClasses += ' ' + className; } + //avoid actions when disabled = true + if (disabled) { + rest.onClick = (e) => { + e.preventDefault(); + }; + rest.onChange = (e) => { + e.preventDefault(); + }; + } let swh = (