From d39107a5da7eb96733938e090b911dc2fdc6e6d0 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Sat, 15 Jun 2024 14:27:32 -0400 Subject: [PATCH] Implemented Loading Button Spinner Component, #142 --- Client/package-lock.json | 42 ++++++- Client/package.json | 1 + Client/src/Components/ButtonSpinner/index.jsx | 105 ++++++++++++++++++ Client/src/Pages/Demo/Demo.jsx | 64 ++++++++++- 4 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 Client/src/Components/ButtonSpinner/index.jsx diff --git a/Client/package-lock.json b/Client/package-lock.json index 73edbf7fb..a4462bddf 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.11.5", "@fontsource/roboto": "^5.0.13", "@mui/icons-material": "^5.15.17", + "@mui/lab": "^5.0.0-alpha.170", "@mui/material": "^5.15.16", "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.3.2", @@ -1152,6 +1153,46 @@ } } }, + "node_modules/@mui/lab": { + "version": "5.0.0-alpha.170", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.170.tgz", + "integrity": "sha512-0bDVECGmrNjd3+bLdcLiwYZ0O4HP5j5WSQm5DV6iA/Z9kr8O6AnvZ1bv9ImQbbX7Gj3pX4o43EKwCutj3EQxQg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/system": "^5.15.15", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": ">=5.15.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "5.15.19", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.19.tgz", @@ -2647,7 +2688,6 @@ "integrity": "sha512-NglN/xprcM+SHD2XCli4oC6bWe6kHoytcyLKCWXmRL854F0qhPhaYgUswUsglnPxYaNQIg2uMY4BvaomIf3kLA==", "dev": true, "license": "ISC" - }, "node_modules/error-ex": { "version": "1.3.2", diff --git a/Client/package.json b/Client/package.json index 505fe25d8..f5df1b5f8 100644 --- a/Client/package.json +++ b/Client/package.json @@ -14,6 +14,7 @@ "@emotion/styled": "^11.11.5", "@fontsource/roboto": "^5.0.13", "@mui/icons-material": "^5.15.17", + "@mui/lab": "^5.0.0-alpha.170", "@mui/material": "^5.15.16", "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.3.2", diff --git a/Client/src/Components/ButtonSpinner/index.jsx b/Client/src/Components/ButtonSpinner/index.jsx new file mode 100644 index 000000000..2cfdaad5d --- /dev/null +++ b/Client/src/Components/ButtonSpinner/index.jsx @@ -0,0 +1,105 @@ +import LoadingButton from "@mui/lab/LoadingButton"; +import PropTypes from "prop-types"; + +const levelConfig = { + primary: { + variant: "contained", + color: "primary", + }, + secondary: { + variant: "outlined", + color: "secondary", + }, + tertiary: { + variant: "text", + color: "tertiary", + }, + error: { + variant: "contained", + color: "error", + }, + imagePrimary: { + color: "primary", + variant: "contained", + }, + imageSecondary: { + color: "secondary", + variant: "outlined", + }, + imageTertiary: { + color: "tertiary", + variant: "text", + }, +}; + +/** + * ButtonSpinner component displays a button with loading loadingText capability. + * @component + * @param {Object} props + * @param {'primary' | 'secondary' | 'tertiary' | 'error' | 'imagePrimary' | 'imageSecondary' | 'imageTertiary'} props.level - The style level of the button. + * @param {string} props.label - The label text displayed on the button. + * @param {React.ReactNode} [props.img] - Icon or image element to display within the button. + * @param {boolean} [props.disabled=false] - Determines if the button is disabled. + * @param {string} [props.loadingText] - Text displayed when the button is in a loading state. + * @param {'start' | 'end'} [props.position] - Specifies where the icon or loading loadingText should be positioned relative to the label. + * @param {function} props.onClick - Callback function invoked when the button is clicked. + * @param {boolean} props.isLoading - Indicates if the button is in a loading state. + * @param {Object} [props.sx] - Additional styles to apply to the button. + * @returns {JSX.Element} + * @example + * // Render a primary button with an icon at the end + * } + * position="end" + * onClick={handleSaveClick} + * isLoading={loading} + * /> + */ + +const ButtonSpinner = ({ + level, + label, + disabled, + loadingText, + position, + img, + onClick, + isLoading, + sx, +}) => { + const { variant, color } = levelConfig[level]; + //if both a loadingPosition and loadingIndicator are provided, the spinner overlaps with the text and it breaks + if (position && loadingText) loadingText = null; + return ( + + {label} + + ); +}; + +ButtonSpinner.propTypes = { + level: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + loadingText: PropTypes.string, + position: PropTypes.oneOf(["start", "end"]), + img: PropTypes.node, + onClick: PropTypes.func.isRequired, + isLoading: PropTypes.bool.isRequired, + sx: PropTypes.object, + disabled: PropTypes.bool, +}; + +export default ButtonSpinner; diff --git a/Client/src/Pages/Demo/Demo.jsx b/Client/src/Pages/Demo/Demo.jsx index f2af76fb4..583f8bda0 100644 --- a/Client/src/Pages/Demo/Demo.jsx +++ b/Client/src/Pages/Demo/Demo.jsx @@ -1,5 +1,6 @@ -import React from "react"; +import React, { useState } from "react"; import Button from "../../Components/Button/"; +import ButtonSpinner from "../../Components/ButtonSpinner"; import Link from "../../Components/Link"; import { useTheme, @@ -13,6 +14,7 @@ import { Tab, Tabs, Typography, + Stack, } from "@mui/material"; import { ColoredLabel, StatusLabel } from "../../Components/Label/"; import Avatar from "../../Components/Avatar/"; @@ -25,6 +27,8 @@ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import AddIcon from "@mui/icons-material/Add"; import Divider from "@mui/material/Divider"; +import UploadIcon from '@mui/icons-material/Upload'; +import SendIcon from '@mui/icons-material/Send'; // Redux import { useSelector } from "react-redux"; @@ -181,6 +185,16 @@ const Demo = () => { // ********************************* const monitorState = useSelector((state) => state.monitors); const { monitors, msg } = monitorState; + + //Button Spinner state + const [isLoading, setIsLoading] = useState(false); + const handleButtonSpinnerClick = () => { + setIsLoading(true); + + setTimeout(() => { + setIsLoading(false); + }, 4000); + }; return (
@@ -352,6 +366,54 @@ const Demo = () => {

Section

+ +

Loading Buttons

+ + + + + } + onClick={handleButtonSpinnerClick} + isLoading={isLoading} + /> + } + onClick={handleButtonSpinnerClick} + isLoading={isLoading} + /> + + +
); };