From 36ab4175866267b8b7132559bba0f389b6f3dd54 Mon Sep 17 00:00:00 2001 From: AssisrMatheus Date: Wed, 12 Apr 2023 10:40:12 -0400 Subject: [PATCH] feat: adds toggle button component --- package-lock.json | 140 +++--------------- package.json | 2 +- .../ToggleButton/ToggleButton.stories.tsx | 68 +++++++++ src/components/ToggleButton/index.tsx | 71 +++++++++ .../ToggleInput/ToggleInput.stories.tsx | 32 ++-- 5 files changed, 181 insertions(+), 132 deletions(-) create mode 100644 src/components/ToggleButton/ToggleButton.stories.tsx create mode 100644 src/components/ToggleButton/index.tsx diff --git a/package-lock.json b/package-lock.json index 384de73..6834978 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@perimetre/ui", - "version": "9.3.2", + "version": "9.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@perimetre/ui", - "version": "9.3.2", + "version": "9.4.0", "license": "MIT", "dependencies": { "@babel/core": "^7.12.13", @@ -45,7 +45,7 @@ "draft-js": "^0.11.7", "draftjs-to-html": "^0.9.1", "draftjs-utils": "^0.10.2", - "framer-motion": "^3.3.0", + "framer-motion": "^10.11.6", "hammerjs": "^2.0.8", "html-react-parser": "^1.2.4", "html-to-draftjs": "^1.5.0", @@ -18985,34 +18985,28 @@ } }, "node_modules/framer-motion": { - "version": "3.10.6", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-3.10.6.tgz", - "integrity": "sha512-OxOtKgQS4km9a8dm0IMBtNNp4f0DiHfQ/IzxKs818+Kg9V/Ve/pRUJ2dtWBb6+W4lIPNLgRSpbOwOACVj15XcQ==", - "dependencies": { - "framesync": "5.2.0", - "hey-listen": "^1.0.8", - "popmotion": "9.3.1", - "style-value-types": "4.1.1", - "tslib": "^1.10.0" + "version": "10.11.6", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.11.6.tgz", + "integrity": "sha512-QXfnUzPQqbJEnWpmtPaRB4OCuyH44uCys5Agg44LEQvItKTg0bou57WuhsNVuEyVCnMoAhrtRYiKeG/vAz6bFw==", + "dependencies": { + "tslib": "^2.4.0" }, "optionalDependencies": { "@emotion/is-prop-valid": "^0.8.2" }, "peerDependencies": { - "react": ">=16.8 || ^17.0.0", - "react-dom": ">=16.8 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, - "node_modules/framer-motion/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/framesync": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/framesync/-/framesync-5.2.0.tgz", - "integrity": "sha512-dcl92w5SHc0o6pRK3//VBVNvu6WkYkiXmHG6ZIXrVzmgh0aDYMDAaoA3p3LH71JIdN5qmhDcfONFA4Lmq22tNA==" - }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -19941,11 +19935,6 @@ "he": "bin/he" } }, - "node_modules/hey-listen": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", - "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" - }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -24612,22 +24601,6 @@ "node": ">=10" } }, - "node_modules/popmotion": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-9.3.1.tgz", - "integrity": "sha512-Qozvg8rz2OGeZwWuIjqlSXqqgWto/+QL24ll8sAAc0n71KY/wvN1W4sAASxTuHv8YWdDnk9u9IdadyPo2DGvDA==", - "dependencies": { - "framesync": "5.2.0", - "hey-listen": "^1.0.8", - "style-value-types": "4.1.1", - "tslib": "^1.10.0" - } - }, - "node_modules/popmotion/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/portfinder": { "version": "1.0.32", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", @@ -29879,20 +29852,6 @@ "inline-style-parser": "0.1.1" } }, - "node_modules/style-value-types": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-4.1.1.tgz", - "integrity": "sha512-cNLrl6jk+I1T18ZI2KIp/fcqKVuykcNELDrOz7y+TYZR97xmNdN0ewupURvVFnQxVrRJv98TMBq92VMsggq3kw==", - "dependencies": { - "hey-listen": "^1.0.8", - "tslib": "^1.10.0" - } - }, - "node_modules/style-value-types/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/styled-components": { "version": "5.3.6", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", @@ -47231,30 +47190,14 @@ } }, "framer-motion": { - "version": "3.10.6", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-3.10.6.tgz", - "integrity": "sha512-OxOtKgQS4km9a8dm0IMBtNNp4f0DiHfQ/IzxKs818+Kg9V/Ve/pRUJ2dtWBb6+W4lIPNLgRSpbOwOACVj15XcQ==", + "version": "10.11.6", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.11.6.tgz", + "integrity": "sha512-QXfnUzPQqbJEnWpmtPaRB4OCuyH44uCys5Agg44LEQvItKTg0bou57WuhsNVuEyVCnMoAhrtRYiKeG/vAz6bFw==", "requires": { "@emotion/is-prop-valid": "^0.8.2", - "framesync": "5.2.0", - "hey-listen": "^1.0.8", - "popmotion": "9.3.1", - "style-value-types": "4.1.1", - "tslib": "^1.10.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } + "tslib": "^2.4.0" } }, - "framesync": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/framesync/-/framesync-5.2.0.tgz", - "integrity": "sha512-dcl92w5SHc0o6pRK3//VBVNvu6WkYkiXmHG6ZIXrVzmgh0aDYMDAaoA3p3LH71JIdN5qmhDcfONFA4Lmq22tNA==" - }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -47960,11 +47903,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "hey-listen": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", - "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" - }, "highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -51486,24 +51424,6 @@ "@babel/runtime": "^7.17.8" } }, - "popmotion": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-9.3.1.tgz", - "integrity": "sha512-Qozvg8rz2OGeZwWuIjqlSXqqgWto/+QL24ll8sAAc0n71KY/wvN1W4sAASxTuHv8YWdDnk9u9IdadyPo2DGvDA==", - "requires": { - "framesync": "5.2.0", - "hey-listen": "^1.0.8", - "style-value-types": "4.1.1", - "tslib": "^1.10.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, "portfinder": { "version": "1.0.32", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", @@ -55350,22 +55270,6 @@ "inline-style-parser": "0.1.1" } }, - "style-value-types": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-4.1.1.tgz", - "integrity": "sha512-cNLrl6jk+I1T18ZI2KIp/fcqKVuykcNELDrOz7y+TYZR97xmNdN0ewupURvVFnQxVrRJv98TMBq92VMsggq3kw==", - "requires": { - "hey-listen": "^1.0.8", - "tslib": "^1.10.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, "styled-components": { "version": "5.3.6", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", diff --git a/package.json b/package.json index 12de3a5..9c89604 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "draft-js": "^0.11.7", "draftjs-to-html": "^0.9.1", "draftjs-utils": "^0.10.2", - "framer-motion": "^3.3.0", + "framer-motion": "^10.11.6", "hammerjs": "^2.0.8", "html-react-parser": "^1.2.4", "html-to-draftjs": "^1.5.0", diff --git a/src/components/ToggleButton/ToggleButton.stories.tsx b/src/components/ToggleButton/ToggleButton.stories.tsx new file mode 100644 index 0000000..a6477d6 --- /dev/null +++ b/src/components/ToggleButton/ToggleButton.stories.tsx @@ -0,0 +1,68 @@ +// also exported from '@storybook/react' if you can deal with breaking changes in 6.1 +import { Meta, Story } from '@storybook/react/types-6-0'; +import React, { useState } from 'react'; +import { ToggleButton, ToggleButtonProps } from '.'; +import { colorOptions } from '../../prebuiltTailwindTheme'; +import classnames from 'classnames'; +import { puiColorClassnameMap } from '../../storybookMappers'; + +export default { + title: 'Components/Inputs/ToggleButton', + component: ToggleButton, + argTypes: { + offLabel: { defaultValue: 'Off' }, + onLabel: { defaultValue: 'On' }, + color: { + defaultValue: 'pui-primary', + control: { + type: 'select', + options: colorOptions + } + }, + defaultChecked: { + control: { + type: 'boolean' + } + }, + disabled: { + control: { + type: 'boolean' + } + }, + className: { + control: { + type: 'text' + } + }, + onChange: { action: 'onChange' }, + onBlur: { action: 'onBlur' }, + onFocus: { action: 'onFocus' } + } +} as Meta; + +/** + * A story that displays a ToggleButton example + * + * @param props the story props + * @param props.color User selected color + */ +const Template: Story = ({ color, ...props }) => { + const [checked, setChecked] = useState(false); + + return ( + { + setChecked(e.target.checked); + if (props.onChange) props.onChange(e); + }} + /> + ); +}; + +export const Default = Template.bind({}); diff --git a/src/components/ToggleButton/index.tsx b/src/components/ToggleButton/index.tsx new file mode 100644 index 0000000..0eb00e7 --- /dev/null +++ b/src/components/ToggleButton/index.tsx @@ -0,0 +1,71 @@ +import classnames from 'classnames'; +import { motion } from 'framer-motion'; +import React from 'react'; + +const spring = { + type: 'spring', + stiffness: 700, + damping: 36 +}; + +export type ToggleButtonProps = Omit< + React.DetailedHTMLProps, HTMLInputElement>, + 'children' | 'type' +> & { + /** + * Label text for the button's off state + */ + offLabel: string; + /** + * Label text for the button's on state + */ + onLabel: string; +}; + +/** + * A toggle input component + * + * @param props the component props + * @param props.offLabel Label text for the button's off state + * @param props.onLabel Label text for the button's on state + */ +export const ToggleButton: React.FC = ({ offLabel, onLabel, ...props }) => ( + + {/* Hidden input so it fakes as a real html input and can be used with forms, it also gives us the full range of event inputs to listen to */} + + + {[offLabel, onLabel].map((label, index) => { + const selected = (!props.checked && index === 0) || (props.checked && index === 1); + return ( + // This defines the sizing that the background will copy from +
+ {/* Label text */} +

+ {label} +

+ + {/* This is the background that will get animated on toggle */} + {selected ? ( + + ) : null} +
+ ); + })} +
+); diff --git a/src/components/ToggleInput/ToggleInput.stories.tsx b/src/components/ToggleInput/ToggleInput.stories.tsx index a12279b..1335f22 100644 --- a/src/components/ToggleInput/ToggleInput.stories.tsx +++ b/src/components/ToggleInput/ToggleInput.stories.tsx @@ -3,9 +3,11 @@ import { Meta, Story } from '@storybook/react/types-6-0'; import React, { useState } from 'react'; import { ToggleInput, ToggleInputProps } from '.'; import { colorOptions } from '../../prebuiltTailwindTheme'; +import classnames from 'classnames'; +import { puiColorClassnameMap } from '../../storybookMappers'; export default { - title: 'Components/Inputs/Toggle', + title: 'Components/Inputs/ToggleInput', component: ToggleInput, argTypes: { label: { defaultValue: 'Input' }, @@ -27,21 +29,11 @@ export default { type: 'text' } }, - defaultValue: { - control: { - type: 'boolean' - } - }, disabled: { control: { type: 'boolean' } }, - readOnly: { - control: { - type: 'boolean' - } - }, className: { control: { type: 'text' @@ -57,11 +49,25 @@ export default { * A story that displays a ToggleInput example * * @param props the story props + * @param props.color User selected color */ -const Template: Story = (props) => { +const Template: Story = ({ color, ...props }) => { const [checked, setChecked] = useState(false); - return setChecked(e.target.checked)} />; + return ( + { + setChecked(e.target.checked); + if (props.onChange) props.onChange(e); + }} + /> + ); }; export const WithLabel = Template.bind({});