Merge pull request #322 from bluewave-labs/feat/alert-refactor

Refactored toast alerts
This commit is contained in:
Alexander Holliday authored and GitHub committed 2024-07-12 10:04:17 -07:00
commit 87ac3c3a39
20 files changed
+211 -546

No files matched your search

+3 -5
View File
@@ -1,9 +1,7 @@
.alert {
margin: 0;
width: fit-content;
padding: var(--env-var-spacing-1-plus);
}
.alert, .alert button{
font-size: var(--env-var-font-size-medium);
}
.alert .MuiStack-root {
max-width: 85%;
}
}
+66 -3
View File
@@ -1,10 +1,12 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Stack } from "@mui/material";
import { Box, IconButton, Stack } from "@mui/material";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
import CloseIcon from "@mui/icons-material/Close";
import "./index.css";
import Button from "../Button";
/**
* Icons mapping for different alert variants.
@@ -22,10 +24,13 @@ const icons = {
* @param {'info' | 'error' | 'warning'} props.variant - The type of alert.
* @param {string} [props.title] - The title of the alert.
* @param {string} [props.body] - The body text of the alert.
* @param {boolean} [props.isToast] - Indicates if the alert is used as a toast notification.
* @param {boolean} [props.hasIcon] - Whether to display an icon in the alert.
* @param {function} props.onClick - Toast dismiss function.
* @returns {JSX.Element}
*/
const Alert = ({ variant, title, body }) => {
const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
const theme = useTheme();
const { bg, border, color } = theme.alert[variant];
const icon = icons[variant];
@@ -34,19 +39,67 @@ const Alert = ({ variant, title, body }) => {
<Stack
direction="row"
justifyContent="flex-start"
alignItems={hasIcon ? "" : "center"}
className="alert row-stack"
gap={theme.gap.ml}
sx={{
padding: hasIcon ? theme.gap.ml : `${theme.gap.small} ${theme.gap.ml}`,
backgroundColor: bg,
border: `solid 1px ${border}`,
borderRadius: `${theme.shape.borderRadius}px`,
}}
>
{icon && <Box sx={{ color: color }}>{icon}</Box>}
{hasIcon && <Box sx={{ color: color }}>{icon}</Box>}
<Stack direction="column" gap="2px" sx={{ flex: 1, color: color }}>
{title && <Box sx={{ fontWeight: "700" }}>{title}</Box>}
{body && <Box sx={{ fontWeight: "400" }}>{body}</Box>}
{hasIcon && isToast && (
<Button
level="tertiary"
label="Dismiss"
onClick={onClick}
sx={{
fontWeight: "600",
width: "fit-content",
mt: theme.gap.small,
padding: 0,
minWidth: 0,
"&:hover": {
backgroundColor: "transparent",
},
"& .MuiTouchRipple-root": {
pointerEvents: "none",
display: "none",
},
}}
></Button>
)}
</Stack>
{isToast && (
<IconButton
onClick={onClick}
sx={{
alignSelf: "flex-start",
ml: "auto",
mr: "-5px",
mt: hasIcon ? "-5px" : 0,
padding: "5px",
"&:focus": {
outline: "none",
},
"& .MuiTouchRipple-root": {
pointerEvents: "none",
display: "none",
},
}}
>
<CloseIcon
sx={{
fontSize: "20px",
}}
/>
</IconButton>
)}
</Stack>
);
};
@@ -55,6 +108,16 @@ Alert.propTypes = {
variant: PropTypes.oneOf(["info", "error", "warning"]).isRequired,
title: PropTypes.string,
body: PropTypes.string,
isToast: PropTypes.bool,
hasIcon: PropTypes.bool,
onClick: function (props, propName, componentName) {
if (props.isToast && !props[propName]) {
return new Error(
`Prop '${propName}' is required when 'isToast' is true in '${componentName}'.`
);
}
return null;
},
};
export default Alert;
@@ -8,6 +8,7 @@ import { editPasswordValidation } from "../../../Validation/validation";
import Alert from "../../Alert";
import { update } from "../../../Features/Auth/authSlice";
import { useDispatch, useSelector } from "react-redux";
import { createToast } from "../../../Utils/toastUtils";
/**
* PasswordPanel component manages the form for editing password.
@@ -78,15 +79,23 @@ const PasswordPanel = () => {
} else {
const action = await dispatch(update({ authToken, localData }));
if (action.payload.success) {
// TODO: Add toast/notification for password update success
createToast({
variant: "info",
body: "Your password was changed successfully.",
hasIcon: false,
});
setLocalData({
password: "",
newPassword: "",
confirm: "",
});
} else {
// TODO: Add toast/notification for password update rejected
// TODO: Check for other errors?
createToast({
variant: "info",
body: "Your password input was incorrect.",
hasIcon: false,
});
setErrors({ password: "*" + action.payload.msg + "." });
}
}
@@ -22,6 +22,7 @@ import ImageIcon from "@mui/icons-material/Image";
import ProgressUpload from "../../ProgressBars";
import { formatBytes } from "../../../Utils/fileUtils";
import { clearMonitorState } from "../../../Features/Monitors/monitorsSlice";
import { createToast } from "../../../Utils/toastUtils";
/**
* ProfilePanel component displays a form for editing user profile information
@@ -58,6 +59,7 @@ const ProfilePanel = () => {
// Handles input field changes and performs validation
const handleChange = (event) => {
errors["unchanged"] && clearError("unchanged");
const { value, id } = event.target;
const name = idToName[id];
setLocalData((prev) => ({
@@ -85,7 +87,7 @@ const ProfilePanel = () => {
delete: false,
});
//TODO - potentitally remove once image compression functions are implemented
//TODO - potentitally remove, will revisit in the future
intervalRef.current = setInterval(() => {
const buffer = 12;
setProgress((prev) => {
@@ -151,14 +153,11 @@ const ProfilePanel = () => {
deleteProfileImage: false,
}));
setIsOpen("");
errors["unchanged"] && clearError("unchanged");
};
console.log(
localData.deleteProfileImage === false && localData.file === undefined
);
// Handles form submission to update user profile
const handleSaveProfile = (event) => {
const handleSaveProfile = async (event) => {
event.preventDefault();
if (
localData.firstname === user.firstname &&
@@ -166,12 +165,29 @@ const ProfilePanel = () => {
localData.deleteProfileImage === undefined &&
localData.file === undefined
) {
// TODO: Add toast/notification for unchanged profile data
createToast({
variant: "warning",
body: "Unable to update profile: No changes detected.",
hasIcon: false,
});
setErrors({ unchanged: "unable to update profile" });
return;
}
dispatch(update({ authToken, localData }));
// TODO: Add toast/notification for profile update success
const action = await dispatch(update({ authToken, localData }));
if (action.payload.success) {
createToast({
variant: "info",
body: "Your profile data was changed successfully.",
hasIcon: false,
});
} else {
createToast({
variant: "error",
body: "There was an error updating your profile data.",
hasIcon: false,
});
}
};
// Removes current profile image from UI
@@ -180,6 +196,7 @@ const ProfilePanel = () => {
...prev,
deleteProfileImage: true,
}));
errors["unchanged"] && clearError("unchanged");
};
// Initiates the account deletion process
@@ -1,71 +0,0 @@
import "./announcementUpdateSubscription.css";
import React from "react";
import PropTypes from "prop-types";
import { useTheme } from "@mui/material";
/**
* @component
* @param {Object} props
* @param {string} props.title - Title text for the update subscription dialog (required)
* @param {string} props.text - Body text for the update subscription dialog (required)
* @param {string} props.cancel - Text for the negative (cancel) button (required)
* @param {string} props.positive - Text for the positive button (required)
* @param {string} props.header - Title text for the email subscription section (required)
* @param {string} props.button - Text for the subscribe button (required)
* @returns {JSX.Element} - Renders the announcement update subscription component
*/
const AnnouncementUpdateSubscription = ({
title,
text,
cancel,
positive,
header,
button,
}) => {
const theme = useTheme();
const fontLookup = {
default: theme.font.default.font,
};
const fontFamily = fontLookup["default"];
return (
<div className="update-subscription" style={{ fontFamily: fontFamily }}>
<div className="update-subscription-dialog">
<div className="subscription-dialog-title">{title}</div>
<div className="v-spacing-small"></div>
<div className="subscription-dialog-text">{text}</div>
<div className="v-spacing-medium"></div>
<div className="subscription-dialog-controls">
<button className="controls-negative">{cancel}</button>
<button className="controls-positive">{positive}</button>
</div>
<div className="v-spacing-medium"></div>
</div>
<div className="v-spacing-medium"></div>
<div className="update-subscription-title">{header}</div>
<div className="subscription-email">
<input
className="subscription-email-field"
type="text"
placeholder="olivia@untitledui.com"
/>
<button className="subscribe-button">{button}</button>
</div>
<div className="v-spacing-medium"></div>
</div>
);
};
AnnouncementUpdateSubscription.propTypes = {
title: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
cancel: PropTypes.string.isRequired,
positive: PropTypes.string.isRequired,
header: PropTypes.string.isRequired,
button: PropTypes.string.isRequired,
};
export default AnnouncementUpdateSubscription;
@@ -1,86 +0,0 @@
.update-subscription {
margin: 20px;
}
.update-subscription,
.update-subscription-dialog {
max-width: 770px;
border-radius: 4px;
border: 1px solid #eaecf0;
}
.update-subscription-dialog {
padding: 15px 30px;
}
.subscription-dialog-title {
font-size: 1rem;
font-weight: 700;
color: var(--env-var-color-1);
}
.v-spacing-small {
height: 10px;
}
.v-spacing-medium {
height: 15px;
}
.subscription-dialog-text {
font-size: 13px;
color: var(--env-var-color-2);
}
.subscription-dialog-controls {
display: flex;
}
.controls-negative {
padding: 12px 16px;
margin-right: 12px;
border-radius: var(--env-var-radius-1);
border: 1px solid var(--env-var-color-6);
background-color: var(--env-var-color-8);
font-size: var(--env-var-font-size-medium);
}
.controls-positive,
.subscribe-button {
padding: 12px 16px;
margin-right: 12px;
border-radius: var(--env-var-radius-1);
border: 1px solid var(--env-var-color-7);
background-color: var(--env-var-color-3);
color: var(--env-var-color-8);
font-size: var(--env-var-font-size-medium);
}
.update-subscription-title {
color: var(--env-var-color-5);
padding: 0 20px;
font-weight: 700;
font-size: 13px;
margin-bottom: 10px;
}
.subscription-email {
display: flex;
padding: 0 20px;
}
.subscription-email-field {
width: 290px;
height: 24px;
border-radius: var(--env-var-radius-1);
border: 1px solid var(--env-var-color-4);
box-shadow: 0px 1px 2px 0px rgba(101, 182, 28, 0.05);
padding: 5px 15px;
margin-right: 20px;
}
.subscribe-button {
width: 100px;
height: 34px;
padding: 2px 15px;
}
@@ -1,55 +0,0 @@
import "./announcementsDualButton.css";
import React from "react";
import CloseIcon from "@mui/icons-material/Close";
import PropTypes from "prop-types";
import { useTheme } from "@mui/material";
/**
* @component
* @param {Object} props
* @param {string} props.subject - The announcement subject text
* @param {string} props.body - The announcement body text content
* @param {string} props.esc - The text for the escape button
* @param {string} props.primary - The text for the primary button
* @returns {JSX.Element} - Renders the announcement dual button component
*/
const AnnouncementsDualButton = ({ subject, body, esc, primary }) => {
const theme = useTheme();
const fontLookup = {
default: theme.font.default.font,
};
const fontFamily = fontLookup["default"];
return (
<div
className="announcement-without-tile"
style={{ fontFamily: fontFamily }}
>
<div className="announcement-without-content">
<div className="announcement-without-content-subject">{subject}</div>
<div className="v-spacing"></div>
<div className="announcement-without-content-body">{body}</div>
<div className="announcement-content-controllers">
<div className="controllers-button-esc">{esc}</div>
<div className="controllers-button-primary">{primary}</div>
</div>
</div>
<div className="h-spacing"></div>
<div className="announcement-close">
<CloseIcon style={{ fill: "#98A2B3" }} />
</div>
</div>
);
};
AnnouncementsDualButton.propTypes = {
subject: PropTypes.string.isRequired,
body: PropTypes.string.isRequired,
esc: PropTypes.string.isRequired,
primary: PropTypes.string.isRequired,
};
export default AnnouncementsDualButton;
@@ -1,25 +0,0 @@
.announcement-without-tile {
margin: 20px;
max-width: 343px;
width: fit-content;
display: flex;
border: 1px solid var(--env-var-color-4);
padding: 16px;
border-radius: var(--env-var-radius-1);
}
.h-spacing {
width: 20px;
}
.announcement-without-content-subject {
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-5);
font-weight: 700;
margin-bottom: 10px;
}
.announcement-without-content-body {
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-2);
}
@@ -1,71 +0,0 @@
import "./announcementsDualButtonWithIcon.css";
import CloseIcon from "@mui/icons-material/Close";
import PropTypes from "prop-types";
import { useTheme } from "@mui/material";
/**
* @component
* @param {Object} props
* @param {React.ReactNode} props.icon - Optional React node for an announcement icon
* @param {string} props.subject - The announcement subject text
* @param {string} props.body - The announcement body text content
* @param {string} props.esc - The text for the escape button (usually "Close")
* @param {string} props.primary - The text for the primary button
* @param {function} props.closeToast - Function to close the toast notification
* @returns {JSX.Element} - Renders the announcement dual button with icon component
*/
const AnnouncementsDualButtonWithIcon = ({
icon,
subject,
body,
esc,
primary,
closeToast,
}) => {
const theme = useTheme();
const fontLookup = {
default: theme.font.default.font,
};
const fontFamily = fontLookup["default"];
return (
<div className="announcement-tile" style={{ fontFamily: fontFamily }}>
{icon && <div className="announcement-icon">{icon}</div>}
<div className="announcement-content">
{subject && (
<div className="announcement-content-subject">{subject}</div>
)}
{body && <div className="announcement-content-body">{body}</div>}
{(esc || primary) && (
<div className="announcement-content-controllers">
{esc && (
<div className="controllers-button-esc" onClick={closeToast}>
{esc}
</div>
)}
{primary && (
<div className="controllers-button-primary">{primary}</div>
)}
</div>
)}
</div>
<div className="announcement-close" onClick={closeToast}>
<CloseIcon style={{ fill: "#98A2B3" }} />
</div>
</div>
);
};
AnnouncementsDualButtonWithIcon.propTypes = {
icon: PropTypes.object,
subject: PropTypes.string,
body: PropTypes.string,
esc: PropTypes.string,
primary: PropTypes.string,
closeToast: PropTypes.func.isRequired,
};
export default AnnouncementsDualButtonWithIcon;
@@ -1,58 +0,0 @@
.announcement-tile {
margin: 20px;
width: fit-content;
display: flex;
border: 1px solid var(--env-var-color-4);
padding: 16px;
border-radius: var(--env-var-radius-1);
}
.info-icon-frame {
display: flex;
width: var(--env-var-default-2);
height: var(--env-var-default-2);
border: 1px solid var(--env-var-color-6);
border-radius: var(--env-var-radius-2);
justify-content: center;
align-items: center;
}
.announcement-content {
padding: 0 16px;
}
.announcement-content-subject {
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-5);
font-weight: 700;
margin-bottom: 10px;
}
.announcement-content-body {
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-2);
font-weight: 300;
}
.announcement-content-controllers {
display: flex;
font-size: var(--env-var-font-size-medium);
margin-top: 15px;
}
.controllers-button-esc {
font-size: var(--env-var-font-size-medium);
font-weight: 600;
margin-right: 12px;
}
.controllers-button-primary {
font-size: var(--env-var-font-size-medium);
font-weight: 600;
color: var(--env-var-color-3);
}
.announcement-close {
width: var(--env-var-default-1);
height: var(--env-var-default-1);
}
@@ -1,36 +0,0 @@
import "./announcementsMessageBar.css";
import React from "react";
import CloseIcon from "@mui/icons-material/Close";
import PropTypes from "prop-types";
import { useTheme } from "@mui/material";
/**
* @component
* @param {Object} props
* @param {string} props.message - The announcement message text content (required)
* @returns {JSX.Element} - Renders the announcement message bar component
*/
const AnnouncementsMessageBar = ({ message }) => {
const theme = useTheme();
const fontLookup = {
default: theme.font.default.font,
};
const fontFamily = fontLookup["default"];
return (
<div className="announcement-tile" style={{ fontFamily: fontFamily }}>
<div className="announcement-messagebar-body">{message}</div>
<div className="announcement-close">
<CloseIcon style={{ fill: "#98A2B3" }} />
</div>
</div>
);
};
AnnouncementsMessageBar.propTypes = {
message: PropTypes.string.isRequired,
};
export default AnnouncementsMessageBar;
@@ -1,8 +0,0 @@
.announcement-messagebar-body {
margin: 20px;
font-size: var(--env-var-font-size-medium);
padding: 0 16px 0 0;
justify-content: center;
align-items: center;
margin: auto;
}
-3
View File
@@ -1,3 +0,0 @@
.Toastify__toast-container {
width: auto;
}
-52
View File
@@ -1,52 +0,0 @@
import PropTypes from "prop-types";
import ComplexAlert from "../../Components/Icons/ComplexAlert/ComplexAlert";
import AnnouncementsDualButtonWithIcon from "./AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
import "react-toastify/dist/ReactToastify.css";
import { toast, ToastContainer } from "react-toastify";
import "./index.css";
/**
* ToastComponent displays a notification that is triggered by error messages.
*
* @component
* @param {Object} props - The component props
* @param {string} props.subject - The subject or title of the toast message
* @param {string} props.body - The body content of the toast message
* @param {string} props.esc - The text for the dismiss action button
* @param {string} props.primary - The text for the primary action button
* @returns {JSX.Element} JSX element representing the ToastComponent
*/
const ToastComponent = ({ subject, body, esc, primary }) => {
const displayMsg = () => {
toast(
({ closeToast, toastProps }) => (
<AnnouncementsDualButtonWithIcon
icon={<ComplexAlert theme="red" />}
subject={subject}
body={body}
esc={esc}
primary={primary}
closeToast={closeToast}
/>
),
{ closeButton: false }
);
};
return (
<div>
<button onClick={displayMsg}>Click me</button>
<ToastContainer className="Toastify__toast-container" />
</div>
);
};
ToastComponent.propTypes = {
subject: PropTypes.string.isRequired,
body: PropTypes.string.isRequired,
esc: PropTypes.string.isRequired,
primary: PropTypes.string.isRequired,
};
export default ToastComponent;
+3
View File
@@ -8,6 +8,8 @@ import ProfilePanel from "../../Components/TabPanels/Account/ProfilePanel";
import PasswordPanel from "../../Components/TabPanels/Account/PasswordPanel";
import TeamPanel from "../../Components/TabPanels/Account/TeamPanel";
import { useNavigate } from "react-router";
import "react-toastify/dist/ReactToastify.css";
import { ToastContainer } from "react-toastify";
/**
* Account component renders a settings page with tabs for Profile, Password, and Team settings.
@@ -73,6 +75,7 @@ const Account = ({ open = "profile" }) => {
<PasswordPanel />
<TeamPanel />
</TabContext>
<ToastContainer />
</Box>
);
};
+43 -2
View File
@@ -40,6 +40,8 @@ import { getMonitorsByUserId } from "../../Features/Monitors/monitorsSlice";
import ImageField from "../../Components/TextFields/Image";
import ProgressUpload from "../../Components/ProgressBars";
import Alert from "../../Components/Alert";
import { createToast } from "../../Utils/toastUtils";
import { ToastContainer } from "react-toastify";
const cols = [
{
@@ -421,11 +423,20 @@ const Demo = () => {
</Stack>
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
<Stack justifyContent="center" alignItems="center">
<ImageField />
<ImageField
id="test-image-field"
onChange={() => console.log("changed")}
/>
</Stack>
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
<Stack justifyContent="center" alignItems="center">
<ProgressUpload icon={<ImageIcon />} label="image.jpg" size="2 MB" />
<ProgressUpload
icon={<ImageIcon />}
label="image.jpg"
size="2 MB"
progress={50}
onClick={() => console.log("click")}
/>
</Stack>
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
<Stack justifyContent="center" alignItems="center">
@@ -449,8 +460,38 @@ const Demo = () => {
body="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
/>
</Box>
<Box width="500px" mt="5px">
<Alert
variant="info"
title="We've just released a new feature"
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid pariatur, ipsum dolor."
isToast={true}
/>
</Box>
<Box width="500px" mt="5px">
<Alert
variant="info"
body="Your password is incorrect."
isToast={true}
hasIcon={false}
/>
</Box>
<Box width="500px" mt="5px">
<Button
level="primary"
label="Toast"
onClick={() =>
createToast({
variant: "info",
body: "Your password is incorrect.",
hasIcon: false,
})
}
/>
</Box>
</Stack>
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
<ToastContainer />
</div>
);
};
@@ -4,16 +4,9 @@ import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
import WebsiteTextField from "../../Components/TextFields/Website/WebsiteTextField";
import DescriptionTextField from "../../Components/TextFields/Description/DescriptionTextField";
import AnnouncementsDualButton from "../../Components/Toast/AnnouncementsDualButton/AnnouncementsDualButton";
import AnnouncementsDualButtonWithIcon from "../../Components/Toast/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import ComplexAlert from "../../Components/Icons/ComplexAlert/ComplexAlert";
import AnnouncementsMessageBar from "../../Components/Toast/AnnouncementsMessageBar/AnnouncementsMessageBar";
import AnnouncementUpdateSubscription from "../../Components/Toast/AnnouncementUpdateSubscription/AnnouncementUpdateSubscription";
import PlayGroundCharts from "./PlayGround-Charts";
import PlayGroundPopupModals from "./PlayGround-Popup-Modals";
import PlayGroundTooltips from "./PlayGround-Tooltips";
import Toast from "../../Components/Toast";
// This Component is just for the development and test
// purposes and just to see what my components look like while development
@@ -53,7 +46,6 @@ function PlayGround() {
hasCopyButton={true}
hintText="This is a hint text to help user."
/>
<Toast />
<hr />
{/* Now, illustration of the Description text fields */}
@@ -66,55 +58,6 @@ function PlayGround() {
hasError={true}
hintText="This is a hint text to help user."
/>
<AnnouncementsDualButtonWithIcon
icon={
<div className="info-icon-frame">
<InfoOutlinedIcon style={{ fill: "#344054" }} />
</div>
}
subject="We’ve just released a new feature"
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
pariatur, ipsum dolor."
esc="Dismiss"
primary="View changes"
/>
<AnnouncementsDualButtonWithIcon
icon={<ComplexAlert theme="red" />}
subject="There was a problem with that action"
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
pariatur, ipsum dolor."
esc="Dismiss"
primary="Learn more"
/>
<AnnouncementsDualButtonWithIcon
icon={<ComplexAlert theme="green" />}
subject="Successfully updated profile"
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
pariatur, ipsum dolor."
esc="Dismiss"
primary="Learn more"
/>
<AnnouncementsDualButton
subject="We’ve just released a new feature"
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
pariatur, ipsum dolor."
esc="Dismiss"
primary="View changes"
/>
<AnnouncementsMessageBar message="New employee created successfully" />
<AnnouncementUpdateSubscription
title="We’ve just released a new update!"
text="Check out the all new dashboard view. Pages and now load faster."
cancel="Dismiss"
positive="Changelog"
header="Subscribe to updates"
button="Subscribe"
/>
<hr />
<PlayGroundCharts />
<hr />
+3 -3
View File
@@ -109,9 +109,9 @@ const theme = createTheme({
border: "#f04438",
},
warning: {
color: "#f79009",
bg: "#fffaef",
border: "#fecf60"
color: "#DC6803",
bg: "#fffcf5",
border: "#fec84b"
},
},
});
+44
View File
@@ -0,0 +1,44 @@
import PropTypes from "prop-types";
import { toast } from "react-toastify";
import Alert from "../Components/Alert";
/**
* @param {object} props
* @param {'info' | 'error' | 'warning'} - The variant of the alert (e.g., "info", "error").
* @param {string} props.title - The title of the alert.
* @param {string} props.body - The body/content of the alert.
* @param {boolean} props.hasIcon - Whether the alert should include an icon.
* @param {object} [props.config] - Additional configuration props for the toast.
*/
export const createToast = ({ variant, title, body, hasIcon, config = {} }) => {
const toastConfig = {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: true,
closeButton: false,
...config,
};
toast(
({ closeToast }) => (
<Alert
variant={variant}
title={title}
body={body}
isToast={true}
hasIcon={hasIcon}
onClick={closeToast}
/>
),
toastConfig
);
};
createToast.propTypes = {
variant: PropTypes.oneOf(["info", "error", "warning"]).isRequired,
title: PropTypes.string,
body: PropTypes.string,
hasIcon: PropTypes.bool,
config: PropTypes.object,
};
+12
View File
@@ -124,6 +124,18 @@ button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.Toastify__toast-container {
width: auto;
}
.Toastify [class^="Toastify__toast"] {
padding: 0;
margin: 0;
}
.Toastify__toast{
min-height: 0;
border-radius: 4px;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;