remove v1 theme and components

This commit is contained in:
Alex Holliday
2026-02-20 18:10:43 +00:00
parent 8d104ee30a
commit ef774b44f2
40 changed files with 12 additions and 3419 deletions
+4 -4
View File
@@ -7,19 +7,19 @@ import { CssBaseline, GlobalStyles } from "@mui/material";
import { Routes } from "./Routes";
import AppLayout from "@/Components/v2/layout/AppLayout";
import type { RootState } from "@/Types/state";
import { lightTheme, darkTheme } from "@/Utils/Theme/v2Theme";
import { lightTheme, darkTheme } from "@/Utils/Theme/Theme";
function App() {
const mode = useSelector((state: RootState) => state.ui.mode);
const v2theme = mode === "light" ? lightTheme : darkTheme;
const theme = mode === "light" ? lightTheme : darkTheme;
return (
<ThemeProvider theme={v2theme}>
<ThemeProvider theme={theme}>
<CssBaseline />
<GlobalStyles
styles={{
body: {
backgroundColor: v2theme.palette.background.default,
backgroundColor: theme.palette.background.default,
},
}}
/>
@@ -1,29 +0,0 @@
import Icon from "../Icon";
import PropTypes from "prop-types";
const ArrowRight = ({ type, color = "#667085", ...props }) => {
if (type === "double") {
return (
<Icon
name="ChevronsRight"
color={color}
{...props}
/>
);
} else {
return (
<Icon
name="ChevronRight"
color={color}
{...props}
/>
);
}
};
ArrowRight.propTypes = {
type: PropTypes.oneOf(["double", "default"]),
color: PropTypes.string,
};
export default ArrowRight;
@@ -1,21 +0,0 @@
.MuiBreadcrumbs-root {
min-height: 34px;
}
.MuiBreadcrumbs-root svg {
width: 16px;
min-height: 16px;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-li a {
font-weight: 400;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-li:not(:last-child) {
cursor: pointer;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-li:last-child a {
font-weight: 500;
opacity: 1;
cursor: default;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-separator {
margin: 0;
}
@@ -1,78 +0,0 @@
import PropTypes from "prop-types";
import { Box, Breadcrumbs as MUIBreadcrumbs } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import ArrowRight from "../ArrowRight/index.jsx";
import "./index.css";
/**
* Breadcrumbs component that displays a list of breadcrumb items.
*
* @param {Object} props
* @param {Array} props.list - Array of breadcrumb items. Each item should have `name` and `path` properties.
* @param {string} props.list.name - The name to display for the breadcrumb.
* @param {string} props.list.path - The path to navigate to when the breadcrumb is clicked.
*
* @returns {JSX.Element} The rendered Breadcrumbs component.
*/
const Breadcrumbs = ({ list }) => {
const theme = useTheme();
const navigate = useNavigate();
return (
<MUIBreadcrumbs
separator={<ArrowRight />}
aria-label="breadcrumb"
px={theme.spacing(2)}
py={theme.spacing(3.5)}
width="fit-content"
backgroundColor={theme.palette.secondary.main}
borderRadius={theme.shape.borderRadius}
lineHeight="18px"
sx={{
"& .MuiBreadcrumbs-li a": {
transition: "background-color 0.2s ease-in-out, color 0.2s ease-in-out",
},
"& .MuiBreadcrumbs-li:not(:last-of-type):hover a": {
backgroundColor: theme.palette.secondary.contrastText,
color: theme.palette.secondary.main,
},
}}
>
{list.map((item, index) => {
return (
<Box
component="a"
key={`${item.name}-${index}`}
px={theme.spacing(4)}
pt={theme.spacing(2)}
pb={theme.spacing(3)}
borderRadius={theme.shape.borderRadius}
onClick={() => navigate(item.path)}
sx={{
opacity: 0.8,
textTransform: "capitalize",
"&, &:hover": {
color: theme.palette.secondary.contrastText,
},
}}
>
{item.name}
</Box>
);
})}
</MUIBreadcrumbs>
);
};
Breadcrumbs.propTypes = {
list: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
}).isRequired
).isRequired,
};
export default Breadcrumbs;
@@ -1,39 +0,0 @@
import { Stack, styled } from "@mui/material";
const ConfigBox = styled(Stack)(({ theme }) => ({
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.spacing(2),
"& > *": {
paddingTop: theme.spacing(15),
paddingBottom: theme.spacing(15),
},
"& > div:first-of-type": {
flex: 0.7,
borderRight: 1,
borderRightStyle: "solid",
borderRightColor: theme.palette.primary.lowContrast,
paddingRight: theme.spacing(15),
paddingLeft: theme.spacing(15),
backgroundColor: theme.palette.tertiary.background,
"& :is(h1, h2):first-of-type": {
fontWeight: 600,
marginBottom: theme.spacing(4),
},
},
"& > div:last-of-type": {
flex: 1,
paddingRight: theme.spacing(20),
paddingLeft: theme.spacing(20),
},
"& h1, & h2": {
color: theme.palette.primary.contrastTextSecondary,
},
}));
export default ConfigBox;
@@ -1,73 +0,0 @@
import { useId } from "react";
import PropTypes from "prop-types";
import { Modal, Stack, Typography } from "@mui/material";
const GenericDialog = ({ title, description, open, onClose, theme, children, width }) => {
const titleId = useId();
const descriptionId = useId();
const ariaDescribedBy = description?.length > 0 ? descriptionId : "";
return (
<Modal
aria-labelledby={titleId}
aria-describedby={ariaDescribedBy}
open={open}
onClose={onClose}
onClick={(e) => e.stopPropagation()}
>
<Stack
gap={theme.spacing(2)}
width={width}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
minWidth: 400,
bgcolor: theme.palette.primary.main,
border: 1,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
boxShadow: 24,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
>
<Typography
id={titleId}
component="h2"
fontSize={16}
color={theme.palette.primary.contrastText}
fontWeight={600}
marginBottom={theme.spacing(4)}
>
{title}
</Typography>
{description && (
<Typography
id={descriptionId}
color={theme.palette.primary.contrastTextTertiary}
marginBottom={theme.spacing(4)}
>
{description}
</Typography>
)}
{children}
</Stack>
</Modal>
);
};
GenericDialog.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string,
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node])
.isRequired,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
};
export { GenericDialog };
-63
View File
@@ -1,63 +0,0 @@
import PropTypes from "prop-types";
import { Button, Stack } from "@mui/material";
import { GenericDialog } from "./genericDialog.jsx";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
const Dialog = ({
title,
description,
open,
onCancel,
confirmationButtonLabel,
onConfirm,
isLoading,
}) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<GenericDialog
title={title}
description={description}
open={open}
onClose={onCancel}
theme={theme}
>
<Stack
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(12)}
justifyContent="flex-end"
>
<Button
variant="contained"
color="secondary"
onClick={onCancel}
>
{t("cancel", "Cancel")}
</Button>
<Button
variant="contained"
color="error"
loading={isLoading}
onClick={onConfirm}
>
{confirmationButtonLabel}
</Button>
</Stack>
</GenericDialog>
);
};
Dialog.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string,
open: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
confirmationButtonLabel: PropTypes.string.isRequired,
onConfirm: PropTypes.func.isRequired,
isLoading: PropTypes.bool,
};
export default Dialog;
-120
View File
@@ -1,120 +0,0 @@
/**
* Maps legacy icon names to Lucide icon names
* Use: import { iconMap } from './iconMap'; LucideIcons[iconMap['monitors']]
*/
export const iconMap = {
// Navigation/Sidebar icons
monitors: "Globe",
incidents: "AlertTriangle",
integrations: "Link",
"page-speed": "Gauge",
settings: "Settings",
notifications: "Bell",
maintenance: "Wrench",
"status-pages": "Wifi",
docs: "FileText",
discussions: "MessageCircle",
support: "HelpCircle",
changeLog: "Code",
logs: "Database",
// User icons
user: "User",
"user-two": "Users",
"user-edit": "UserPen",
groups: "Users",
lock: "Lock",
key: "Key",
logout: "LogOut",
// Action icons
edit: "Pencil",
"trash-bin": "Trash2",
search: "Search",
"close-icon": "X",
check: "Check",
"check-icon": "CheckCircle",
"check-outlined": "CheckCircle",
// Arrow icons
"left-arrow": "ChevronLeft",
"right-arrow": "ChevronRight",
"up-arrow": "ChevronUp",
"down-arrow": "ChevronDown",
"left-arrow-double": "ChevronsLeft",
"right-arrow-double": "ChevronsRight",
"left-arrow-long": "ArrowLeft",
"top-right-arrow": "ArrowUpRight",
"open-in-new-page": "ExternalLink",
"selector-vertical": "ChevronsUpDown",
// Status/Alert icons
"alert-icon": "Bell",
"warning-icon": "AlertCircle",
"history-icon": "History",
// Data/Chart icons
dashboard: "LayoutGrid",
"monitor-graph-line": "TrendingUp",
"response-time-icon": "TrendingUp",
"uptime-icon": "Activity",
"speedometer-icon": "Gauge",
"spedometer-icon": "Gauge",
"ruler-icon": "Ruler",
"performance-report": "Layers",
"average-response-icon": "BarChart3",
"distributed-uptime": "Network",
// Misc icons
calendar: "Calendar",
"calendar-check": "CalendarCheck",
"clock-snooze": "Clock",
mail: "Mail",
email: "Mail",
folder: "Folder",
"dots-vertical": "MoreVertical",
"pause-icon": "PauseCircle",
"resume-icon": "PlayCircle",
"checkbox-filled": "CheckSquare",
"checkbox-outline": "Square",
"radio-checked": "Circle",
"cpu-chip": "Cpu",
certificate: "Award",
"interval-check": "RefreshCw",
};
/**
* MUI icon to Lucide mapping
*/
export const muiToLucideMap = {
AddCircleOutline: "PlusCircle",
ArrowDownwardRounded: "ArrowDown",
ArrowUpwardRounded: "ArrowUp",
ArrowDropDown: "ChevronDown",
ArrowOutward: "ExternalLink",
CheckCircle: "CheckCircle2",
Clear: "X",
Close: "X",
CloseRounded: "X",
CloudUpload: "Upload",
Delete: "Trash2",
DeleteOutlineRounded: "Trash2",
Email: "Mail",
ErrorOutline: "AlertTriangle",
ErrorOutlineOutlined: "AlertTriangle",
Image: "ImageIcon",
InfoOutlined: "Info",
KeyboardArrowDown: "ChevronDown",
Menu: "Menu",
PauseCircleOutline: "PauseCircle",
PauseOutlined: "Pause",
PlayArrowOutlined: "Play",
PlayCircleOutlineRounded: "PlayCircle",
RadioButtonChecked: "Circle",
ReorderRounded: "GripVertical",
SettingsOutlined: "Settings",
Share: "Share2",
Visibility: "Eye",
VisibilityOff: "EyeOff",
WarningAmberOutlined: "AlertTriangle",
};
-198
View File
@@ -1,198 +0,0 @@
import { useTheme } from "@mui/material";
import PropTypes from "prop-types";
import {
Activity,
AlertCircle,
AlertTriangle,
ArrowDown,
ArrowLeft,
ArrowUp,
ArrowUpRight,
Bell,
Calendar,
Check,
CheckCircle,
CheckCircle2,
CheckSquare,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
Circle,
CircleDot,
Clock,
Code,
Cpu,
Database,
ExternalLink,
Eye,
EyeOff,
FileText,
Gauge,
Globe,
GripVertical,
HelpCircle,
History,
Image as ImageIcon,
Info,
Key,
Layers,
Link,
Lock,
LogOut,
Mail,
Menu,
MessageCircle,
MoreVertical,
Pause,
PauseCircle,
Play,
PlayCircle,
PlusCircle,
RefreshCw,
Ruler,
Search,
Settings,
Square,
Trash2,
TrendingUp,
Upload,
User,
Users,
Wifi,
Wrench,
X,
} from "lucide-react";
/**
* Map of icon names to Lucide icon components.
* Only icons explicitly imported here will be included in the bundle (tree-shaking).
* To add a new icon: import it above and add it to this object.
*/
const iconComponents = {
Activity,
AlertCircle,
AlertTriangle,
ArrowDown,
ArrowLeft,
ArrowUp,
ArrowUpRight,
Bell,
Calendar,
Check,
CheckCircle,
CheckCircle2,
CheckSquare,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
Circle,
CircleDot,
Clock,
Code,
Cpu,
Database,
ExternalLink,
Eye,
EyeOff,
FileText,
Gauge,
Globe,
GripVertical,
HelpCircle,
History,
Image: ImageIcon,
Info,
Key,
Layers,
Link,
Lock,
LogOut,
Mail,
Menu,
MessageCircle,
MoreVertical,
Pause,
PauseCircle,
Play,
PlayCircle,
PlusCircle,
RefreshCw,
Ruler,
Search,
Settings,
Square,
Trash2,
TrendingUp,
Upload,
User,
Users,
Wifi,
Wrench,
X,
};
/**
* Theme-aware icon component wrapping Lucide icons
*
* @param {string} name - Lucide icon name (e.g., "Bell", "Check", "ArrowLeft")
* @param {number} size - Icon size in pixels (default: 20)
* @param {string} color - Direct color value OR theme path like "primary.contrastText"
* @param {number} strokeWidth - Stroke width (default: 1.5)
* @param {string} fill - Fill color (default: "none")
*/
const Icon = ({ name, size = 20, color, strokeWidth = 1.5, fill = "none", ...props }) => {
const theme = useTheme();
// Resolve color from theme path or use direct value
const resolveColor = (colorValue) => {
if (!colorValue) {
return theme.palette.primary.contrastTextTertiary; // Default icon color
}
// If it's a theme path like "primary.contrastText"
if (typeof colorValue === "string" && colorValue.includes(".")) {
const parts = colorValue.split(".");
let resolved = theme.palette;
for (const part of parts) {
resolved = resolved?.[part];
}
return resolved || colorValue;
}
// Direct color value
return colorValue;
};
const LucideIcon = iconComponents[name];
if (!LucideIcon) {
console.warn(`Icon "${name}" not found in Icon component`);
return null;
}
return (
<LucideIcon
size={size}
color={resolveColor(color)}
strokeWidth={strokeWidth}
fill={fill}
{...props}
/>
);
};
Icon.propTypes = {
name: PropTypes.string.isRequired,
size: PropTypes.number,
color: PropTypes.string,
strokeWidth: PropTypes.number,
fill: PropTypes.string,
};
export default Icon;
@@ -1,116 +0,0 @@
import PropTypes from "prop-types";
import { FormControlLabel, Checkbox as MuiCheckbox } from "@mui/material";
import { useTheme } from "@emotion/react";
import Icon from "../../Icon";
/**
* Checkbox Component
*
* A customized checkbox component using Material-UI that supports custom sizing,
* disabled states, and custom icons.
*
* @component
* @param {Object} props - Component properties
* @param {string} props.id - Unique identifier for the checkbox input
* @param {string} [props.name] - Optional name attribute for the checkbox
* @param {(string|React.ReactNode)} props.label - Label text or node for the checkbox
* @param {('small'|'medium'|'large')} [props.size='medium'] - Size of the checkbox icon
* @param {boolean} props.isChecked - Current checked state of the checkbox
* @param {string} [props.value] - Optional value associated with the checkbox
* @param {Function} [props.onChange] - Callback function triggered when checkbox state changes
* @param {boolean} [props.isDisabled] - Determines if the checkbox is disabled
*
* @returns {React.ReactElement} Rendered Checkbox component
*
* @example
* // Basic usage
* <Checkbox
* id="terms-checkbox"
* label="I agree to terms"
* isChecked={agreed}
* onChange={handleAgree}
* />
*
* @example
* // With custom size and disabled state
* <Checkbox
* id="advanced-checkbox"
* label="Advanced Option"
* size="large"
* isChecked={isAdvanced}
* isDisabled={!canModify}
* onChange={handleAdvancedToggle}
* />
*/
const Checkbox = ({
id,
name,
label,
size = "medium",
isChecked,
value,
onChange,
isDisabled,
}) => {
/* TODO move sizes to theme */
const sizes = { small: "14px", medium: "16px", large: "18px" };
const theme = useTheme();
return (
<FormControlLabel
className="checkbox-wrapper"
control={
<MuiCheckbox
checked={isDisabled ? false : isChecked}
name={name}
value={value}
onChange={onChange}
icon={<Icon name="Square" />}
checkedIcon={<Icon name="CheckSquare" />}
inputProps={{
"aria-label": "controlled checkbox",
id: id,
}}
sx={{
"&:hover": { backgroundColor: "transparent" },
"& svg": { width: sizes[size], height: sizes[size] },
}}
/>
}
label={label}
disabled={isDisabled}
sx={{
borderRadius: theme.shape.borderRadius,
p: theme.spacing(2.5),
"& .MuiButtonBase-root": {
width: theme.spacing(10),
p: 0,
mr: theme.spacing(6),
},
"&:not(:has(.Mui-disabled)):hover": {
backgroundColor: theme.palette.tertiary.main,
},
"& span.MuiTypography-root": {
fontSize: 13,
color: theme.palette.primary.contrastTextTertiary,
},
".MuiFormControlLabel-label.Mui-disabled": {
color: theme.palette.primary.contrastTextTertiary,
opacity: 0.25,
},
}}
/>
);
};
Checkbox.propTypes = {
id: PropTypes.string,
name: PropTypes.string,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
size: PropTypes.oneOf(["small", "medium", "large"]),
isChecked: PropTypes.bool.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
onChange: PropTypes.func,
isDisabled: PropTypes.bool,
};
export default Checkbox;
@@ -1,63 +0,0 @@
import PropTypes from "prop-types";
import { Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { MuiColorInput } from "mui-color-input";
/**
*
* @param {*} id The ID of the component
* @param {*} value The color value of the component
* @param {*} error The error of the component
* @param {*} onChange The Change handler function
* @param {*} onBlur The Blur handler function
* @returns The ColorPicker component
* Example usage:
* <ColorPicker
* id="color"
* value={form.color}
* error={errors["color"]}
* onChange={handleColorChange}
* onBlur={handleBlur}
* >
* </ColorPicker>
*/
const ColorPicker = ({ id, name, value, error, onChange, onBlur }) => {
const theme = useTheme();
return (
<Stack gap={theme.spacing(4)}>
<MuiColorInput
format="hex"
name={name}
type="color-picker"
value={value}
id={id}
onChange={(color) => onChange({ target: { name, value: color } })}
onBlur={onBlur}
/>
{error && (
<Typography
component="span"
className="input-error"
color={theme.palette.error.main}
mt={theme.spacing(2)}
sx={{
opacity: 0.8,
}}
>
{error}
</Typography>
)}
</Stack>
);
};
ColorPicker.propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.string,
error: PropTypes.string,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
name: PropTypes.string,
};
export default ColorPicker;
@@ -1,51 +0,0 @@
import { Stack, Typography } from "@mui/material";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
const DEFAULT_GAP = 6;
const FieldWrapper = ({
label,
children,
gap,
labelMb,
labelFontWeight = 500,
labelVariant = "h3",
labelSx = {},
sx = {},
}) => {
const theme = useTheme();
return (
<Stack
gap={gap ?? theme.spacing(DEFAULT_GAP)}
sx={sx}
>
{label && (
<Typography
component={labelVariant}
color={theme.palette.primary.contrastTextSecondary}
fontWeight={labelFontWeight}
sx={{
...(labelMb !== undefined && { mb: theme.spacing(labelMb) }),
...labelSx,
}}
>
{label}
</Typography>
)}
{children}
</Stack>
);
};
FieldWrapper.propTypes = {
label: PropTypes.node,
children: PropTypes.node.isRequired,
gap: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
labelMb: PropTypes.number,
labelFontWeight: PropTypes.number,
labelVariant: PropTypes.string,
labelSx: PropTypes.object,
sx: PropTypes.object,
};
export default FieldWrapper;
@@ -1,249 +0,0 @@
// Components
import { Box, Stack, Typography } from "@mui/material";
import Icon from "../../Icon";
import Image from "../../Image/index.jsx";
import TextField from "@mui/material/TextField";
import IconButton from "@mui/material/IconButton";
import ProgressUpload from "../../ProgressBars/index.jsx";
// Utils
import PropTypes from "prop-types";
import { useCallback, useState, useRef, useEffect } from "react";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
/**
* ImageUpload component allows users to upload images with drag-and-drop functionality.
* It supports file size and format validation.
*
* @component
* @param {Object} props - Component props
* @param {boolean} [props.previewIsRound=false] - Determines if the image preview should be round
* @param {string} [props.src] - Source URL of the image to display
* @param {function} props.onChange - Callback function to handle file change, takes a file as an argument
* @param {number} [props.maxSize=3145728] - Maximum file size allowed in bytes (default is 3MB)
* @param {Array<string>} [props.accept=['jpg', 'jpeg', 'png']] - Array of accepted file formats
* @param {Object} [props.errors] - Object containing error messages
* @returns {JSX.Element} The rendered component
*/
const ImageUpload = ({
previewIsRound = false,
src,
onChange,
maxSize = 3 * 1024 * 1024,
accept = ["jpg", "jpeg", "png"],
error,
}) => {
const theme = useTheme();
const { t } = useTranslation();
const [uploadComplete, setUploadComplete] = useState(false);
const [completedFile, setCompletedFile] = useState(null);
const [file, setFile] = useState(null);
const [progress, setProgress] = useState({ value: 0, isLoading: false });
const intervalRef = useRef(null);
const [localError, setLocalError] = useState(null);
const [isDragging, setIsDragging] = useState(false);
const roundStyle = previewIsRound ? { borderRadius: "50%" } : {};
const handleImageChange = useCallback(
(file) => {
if (!file) return;
const isValidType = accept.some((type) => file.type.includes(type));
const isValidSize = file.size <= maxSize;
if (!isValidType) {
setLocalError(t("invalidFileFormat"));
return;
}
if (!isValidSize) {
setLocalError(t("invalidFileSize"));
return;
}
setLocalError(null);
const previewFile = {
src: URL.createObjectURL(file),
name: file.name,
file,
};
setFile(previewFile);
setProgress({ value: 0, isLoading: true });
intervalRef.current = setInterval(() => {
setProgress((prev) => {
const buffer = 12;
if (prev.value + buffer >= 100) {
clearInterval(intervalRef.current);
setUploadComplete(true);
setCompletedFile(previewFile);
return { value: 100, isLoading: false };
}
return { value: prev.value + buffer, isLoading: true };
});
}, 120);
},
[maxSize, accept]
);
useEffect(() => {
if (uploadComplete && completedFile) {
onChange?.(completedFile);
setUploadComplete(false);
setCompletedFile(null);
}
}, [uploadComplete, completedFile, onChange]);
return (
<>
{src ? (
<Stack
direction="row"
justifyContent="center"
>
<Image
alt="Uploaded preview"
src={src}
width="250px"
height="250px"
sx={{ ...roundStyle }}
/>
</Stack>
) : (
<>
<Box
className="image-field-wrapper"
mt={theme.spacing(8)}
onDragEnter={() => setIsDragging(true)}
onDragLeave={() => setIsDragging(false)}
onDrop={() => setIsDragging(false)}
sx={{
position: "relative",
height: "fit-content",
border: "dashed",
borderRadius: theme.shape.borderRadius,
borderColor: isDragging
? theme.palette.primary.main
: theme.palette.primary.lowContrast,
backgroundColor: isDragging ? "hsl(215, 87%, 51%, 0.05)" : "transparent",
borderWidth: "2px",
transition: "0.2s",
"&:hover": {
borderColor: theme.palette.primary.main,
backgroundColor: "hsl(215, 87%, 51%, 0.05)",
},
}}
>
<TextField
type="file"
onChange={(e) => handleImageChange(e?.target?.files?.[0])}
sx={{
width: "100%",
"& .MuiInputBase-input[type='file']": {
opacity: 0,
cursor: "pointer",
maxWidth: "500px",
minHeight: "175px",
zIndex: 1,
},
"& fieldset": {
padding: 0,
border: "none",
},
}}
/>
<Stack
alignItems="center"
gap="4px"
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
zIndex: 0,
width: "100%",
}}
>
<IconButton
sx={{
pointerEvents: "none",
borderRadius: theme.shape.borderRadius,
border: `solid ${theme.shape.borderThick}px ${theme.palette.primary.lowContrast}`,
boxShadow: theme.shape.boxShadow,
}}
>
<Icon
name="Upload"
size={24}
/>
</IconButton>
<Typography
component="h2"
color={theme.palette.primary.contrastTextTertiary}
>
<Typography
component="span"
fontSize="inherit"
color="info"
fontWeight={500}
>
{t("ClickUpload")}
</Typography>{" "}
or {t("DragandDrop")}
</Typography>
<Typography
component="p"
color={theme.palette.primary.contrastTextTertiary}
sx={{ opacity: 0.6 }}
>
({t("MaxSize")}: {Math.round(maxSize / 1024 / 1024)}MB)
</Typography>
</Stack>
</Box>
{(localError || progress.isLoading || progress.value !== 0) && (
<ProgressUpload
icon={
<Icon
name="Image"
size={20}
/>
}
label={file?.name || "Upload failed"}
size={file?.size}
progress={progress.value}
onClick={() => {
clearInterval(intervalRef.current);
setFile(null);
setProgress({ value: 0, isLoading: false });
setLocalError(null);
onChange(undefined);
}}
error={localError || error}
/>
)}
<Typography
component="p"
color={theme.palette.primary.contrastTextTertiary}
sx={{ opacity: 0.6 }}
>
{t("SupportedFormats")}: {accept.join(", ").toUpperCase()}
</Typography>
</>
)}
</>
);
};
ImageUpload.propTypes = {
previewIsRound: PropTypes.bool,
src: PropTypes.string,
onChange: PropTypes.func,
maxSize: PropTypes.number,
accept: PropTypes.array,
error: PropTypes.string,
};
export default ImageUpload;
@@ -1,117 +0,0 @@
import PropTypes from "prop-types";
import { FormControlLabel, Radio as MUIRadio, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import Icon from "../../Icon";
import "./index.css";
/**
* Radio component.
*
* @component
* @example
* // Usage:
* <Radio
* title="Radio Button Title"
* desc="Radio Button Description"
* size="small"
* />
*
* @param {Object} props - The component
* @param {string} id - The id of the radio button.
* @param {string} title - The title of the radio button.
* @param {string} [desc] - The description of the radio button.
* @param {string} [size="small"] - The size of the radio button.
* @returns {JSX.Element} - The rendered Radio component.
*/
const Radio = ({
name,
checked,
value,
id,
size,
title,
desc,
onChange,
labelSpacing,
}) => {
const theme = useTheme();
return (
<FormControlLabel
className="custom-radio-button"
name={name}
checked={checked}
value={value}
control={
<MUIRadio
id={id}
size={size}
checkedIcon={
<Icon
name="CircleDot"
size={16}
/>
}
sx={{
color: "transparent",
width: 16,
height: 16,
boxShadow: `inset 0 0 0 1px ${theme.palette.secondary.main}`,
"&:not(.Mui-checked)": {
boxShadow: `inset 0 0 0 1px ${theme.palette.primary.contrastText}70`, // Use theme text color for the outline
},
mt: theme.spacing(0.5),
}}
/>
}
onChange={onChange}
label={
<>
<Typography
component="p"
mb={
labelSpacing !== undefined ? theme.spacing(labelSpacing) : theme.spacing(2)
}
>
{title}
</Typography>
<Typography
component="h6"
mt={theme.spacing(1)}
color={theme.palette.primary.contrastTextSecondary}
>
{desc}
</Typography>
</>
}
labelPlacement="end"
sx={{
alignItems: "flex-start",
p: theme.spacing(2.5),
m: theme.spacing(-2.5),
borderRadius: theme.shape.borderRadius,
"&:hover": {
backgroundColor: theme.palette.tertiary.main,
},
"& .MuiButtonBase-root": {
p: 0,
mr: theme.spacing(6),
},
}}
/>
);
};
Radio.propTypes = {
title: PropTypes.string,
desc: PropTypes.string,
size: PropTypes.string,
name: PropTypes.string,
checked: PropTypes.bool,
value: PropTypes.string,
id: PropTypes.string,
onChange: PropTypes.func,
};
export default Radio;
@@ -1,315 +0,0 @@
import PropTypes from "prop-types";
import {
Box,
ListItem,
Autocomplete,
TextField,
Typography,
Checkbox,
} from "@mui/material";
import { useTheme } from "@emotion/react";
import Icon from "../../Icon";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import FieldWrapper from "../FieldWrapper/index.jsx";
/**
* Search component using Material UI's Autocomplete.
*
* @param {Object} props
* @param {string} props.id - Unique identifier for the Autocomplete component
* @param {Array<Object>} props.options - Options to display in the Autocomplete dropdown
* @param {string} props.filteredBy - Key to access the option label from the options
* @param {string} props.value - Current input value for the Autocomplete
* @param {Function} props.handleChange - Function to call when the input changes
* @param {Function} Prop.onBlur - Function to call when the input is blured
* @param {Object} props.sx - Additional styles to apply to the component
* @param {string} props.unit - Label to identify type of options
* @returns {JSX.Element} The rendered Search component
*/
const SearchAdornment = () => {
const theme = useTheme();
return (
<Box height={16}>
<Icon
name="Search"
size={16}
color="primary.contrastTextTertiary"
strokeWidth={1.2}
/>
</Box>
);
};
//TODO keep search state inside of component.
const Search = ({
label,
id,
options,
filteredBy,
secondaryLabel,
value,
inputValue,
handleInputChange,
handleChange,
sx,
multiple = false,
isAdorned = true,
error,
disabled,
startAdornment,
endAdornment,
onBlur,
//FieldWrapper's props
gap,
labelMb,
labelFontWeight,
labelVariant,
labelSx = {},
unit = "option",
maxWidth = "100%",
}) => {
const theme = useTheme();
const { t } = useTranslation();
const [selectAll, setSelectAll] = React.useState(false);
const [open, setOpen] = React.useState(false);
const enhancedOptions = React.useMemo(() => {
return multiple && isAdorned
? [
{
[filteredBy]: t("selectAll"),
isSelectAll: true,
_id: "select_all",
id: "select_all",
},
...options,
]
: options;
}, [multiple, isAdorned, options, filteredBy]);
const isOptionSelected = (option) => {
if (!multiple && !isAdorned) return false;
if (Array.isArray(value)) {
return value.some((item) => (item._id ?? item.id) === (option._id ?? option.id));
}
return false;
};
const handleSelectAll = (isSelectAll) => {
const newValue = isSelectAll ? [...options] : [];
handleChange(newValue);
setSelectAll(isSelectAll);
};
useEffect(() => {
const allSelected =
Array.isArray(value) && Array.isArray(options) && value.length === options.length;
if (selectAll !== allSelected) setSelectAll(allSelected);
}, [value, options]);
return (
<Autocomplete
onBlur={onBlur}
multiple={multiple}
id={id}
value={value}
open={open}
onOpen={() => setOpen(true)}
onClose={(event, reason) => {
if (reason === "blur" || reason === "escape") {
setOpen(false);
}
}}
inputValue={inputValue}
onInputChange={(_, newValue) => {
handleInputChange(newValue);
}}
onChange={(_, newValue) => {
if (multiple && isAdorned) {
const hasSelectAllSelected =
Array.isArray(newValue) && newValue.some((item) => item.isSelectAll);
if (hasSelectAllSelected) {
handleSelectAll(!selectAll);
} else {
handleChange(newValue);
setSelectAll(Array.isArray(newValue) && newValue.length === options.length);
}
} else {
handleChange(newValue);
setOpen(false);
}
}}
fullWidth
freeSolo
disabled={disabled}
disableClearable
options={enhancedOptions}
getOptionLabel={(option) => option[filteredBy]}
isOptionEqualToValue={(option, value) =>
(option._id ?? option.id) === (value?._id ?? value?.id)
} // Compare by unique identifier
renderInput={(params) => (
<FieldWrapper
label={label}
labelMb={labelMb}
labelVariant={labelVariant}
labelFontWeight={labelFontWeight}
labelSx={labelSx}
gap={gap}
sx={{
...sx,
}}
>
<TextField
{...params}
error={Boolean(error)}
placeholder="Type to search"
slotProps={{
input: {
...params.InputProps,
...(isAdorned && { startAdornment: <SearchAdornment /> }),
...(startAdornment && { startAdornment: startAdornment }),
...(endAdornment && { endAdornment: endAdornment }),
},
}}
sx={{}}
/>
{error && (
<Typography
component="span"
className="input-error"
color={theme.palette.error.main}
mt={theme.spacing(2)}
sx={{
opacity: 0.8,
}}
>
{error}
</Typography>
)}
</FieldWrapper>
)}
filterOptions={(options, { inputValue }) => {
if (inputValue.trim() === "" && multiple && isAdorned) {
return enhancedOptions;
}
const filtered = options.filter((option) =>
option[filteredBy].toLowerCase().includes(inputValue.toLowerCase())
);
if (filtered.length === 0) {
return [
{
[filteredBy]: t("general.noOptionsFound", { unit: unit }),
noOptions: true,
},
];
}
return filtered;
}}
getOptionKey={(option) => {
return option._id ?? option.id;
}}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
const hasSecondaryLabel = secondaryLabel && option[secondaryLabel] !== undefined;
const port = option["port"];
const selected = isOptionSelected(option);
return (
<ListItem
key={key}
{...optionProps}
sx={
option.noOptions
? {
pointerEvents: "none",
backgroundColor: theme.palette.primary.main,
}
: option.isSelectAll
? {
fontWeight: "bold",
backgroundColor: theme.palette.primary.light,
"&:hover": {
backgroundColor: theme.palette.primary.light,
},
}
: {}
}
>
{multiple && isAdorned && !option.noOptions && (
<Checkbox
checked={option.isSelectAll ? selectAll : selected}
sx={{
color: theme.palette.primary.contrastTextSecondary,
"&.Mui-checked": {
color: theme.palette.secondary.main,
},
padding: 0,
}}
/>
)}
{option[filteredBy] +
(hasSecondaryLabel
? ` (${option[secondaryLabel]}${port ? `: ${port}` : ""})`
: "")}
</ListItem>
);
}}
slotProps={{
popper: {
keepMounted: true,
sx: {
"& ul": { p: 2, backgroundColor: theme.palette.primary.main },
"& li.MuiAutocomplete-option": {
color: theme.palette.primary.contrastTextSecondary,
px: 4,
borderRadius: theme.shape.borderRadius,
},
"& .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'], & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'].Mui-focused, & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true']:hover":
{
backgroundColor: theme.palette.primary.lowContrast,
color: "red",
},
"& li.MuiAutocomplete-option:hover:not([aria-selected='true'])": {
color: theme.palette.secondary.contrastText,
backgroundColor: theme.palette.secondary.main,
},
"& .MuiAutocomplete-noOptions": {
px: theme.spacing(6),
py: theme.spacing(5),
},
},
},
}}
sx={{
/* height: 34,*/
"&.MuiAutocomplete-root .MuiAutocomplete-input": {
padding: `0 ${theme.spacing(5)}`,
},
...sx,
}}
/>
);
};
Search.propTypes = {
label: PropTypes.string,
id: PropTypes.string,
multiple: PropTypes.bool,
options: PropTypes.array.isRequired,
filteredBy: PropTypes.string.isRequired,
secondaryLabel: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
inputValue: PropTypes.string.isRequired,
handleInputChange: PropTypes.func.isRequired,
handleChange: PropTypes.func,
isAdorned: PropTypes.bool,
sx: PropTypes.object,
error: PropTypes.string,
disabled: PropTypes.bool,
startAdornment: PropTypes.object,
endAdornment: PropTypes.object,
onBlur: PropTypes.func,
unit: PropTypes.string,
};
export default Search;
@@ -1,7 +0,0 @@
.select-wrapper .select-component > .MuiSelect-select {
padding: 0 10px;
min-height: 34px;
display: flex;
align-items: center;
line-height: 1;
}
@@ -1,213 +0,0 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { MenuItem, Select as MuiSelect, Stack, Typography } from "@mui/material";
import Icon from "../../Icon";
import FieldWrapper from "../FieldWrapper/index.jsx";
import "./index.css";
/**
* @component
* @param {object} props
* @param {string} props.id - The ID attribute for the select element.
* @param {string} props.placeholder - The label of the select element.
* @param {string} props.placeholder - The placeholder text when no option is selected.
* @param {boolean} props.isHidden - Whether the placeholder should be hidden.
* @param {(string | number | boolean)} props.value - The currently selected value.
* @param {object[]} props.items - The array of items to populate in the select dropdown.
* @param {(string | number | boolean)} props.items._id - The unique identifier of each item.
* @param {string} props.items.name - The display name of each item.
* @param {function} props.onChange - The function to handle onChange event.
* @param {object} props.sx - The custom styles object for MUI Select component.
* @param {number} props.maxWidth - Maximum width in pixels for the select component. Enables responsive text truncation.
* @returns {JSX.Element}
*
* @example
* const frequencies = [
* { _id: 1, name: "1 minute" },
* { _id: 2, name: "2 minutes" },
* { _id: 3, name: "3 minutes" },
* ];
*
* <Select
* id="frequency-id"
* name="my-name"
* label="Check frequency"
* placeholder="Select frequency"
* value={value}
* onChange={handleChange}
* items={frequencies}
* />
*/
const Select = ({
id,
label,
placeholder,
isHidden,
value,
items,
onChange,
onBlur,
sx,
error = false,
name = "",
labelControlSpacing = 6,
maxWidth,
//FieldWrapper's props
labelMb,
labelFontWeight,
labelVariant,
labelSx = {},
fieldWrapperSx = {},
}) => {
const theme = useTheme();
const getItemValue = (item) => item?._id ?? item?.id;
const itemStyles = {
color: theme.palette.primary.contrastTextTertiary,
borderRadius: theme.shape.borderRadius,
margin: theme.spacing(2),
};
const responsiveMaxWidth = {
xs: `${maxWidth * 0.5}px`,
sm: `${maxWidth * 0.75}px`,
md: `${maxWidth * 0.9}px`,
lg: `${maxWidth}px`,
};
return (
<FieldWrapper
label={label}
labelMb={labelMb}
labelVariant={labelVariant}
labelFontWeight={labelFontWeight}
labelSx={labelSx}
gap={labelControlSpacing}
sx={{
...fieldWrapperSx,
}}
>
<MuiSelect
className="select-component"
value={value}
onChange={onChange}
onBlur={onBlur}
displayEmpty
error={error}
name={name}
inputProps={{ id: id }}
IconComponent={(props) => (
<Icon
name="ChevronDown"
size={20}
{...props}
/>
)}
MenuProps={{ disableScrollLock: true }}
sx={{
fontSize: 13,
minWidth: "125px",
...(maxWidth && { maxWidth: responsiveMaxWidth }),
"& fieldset": {
borderRadius: theme.shape.borderRadius,
borderColor: theme.palette.primary.lowContrast,
},
"&:not(.Mui-focused):hover fieldset": {
borderColor: theme.palette.primary.lowContrast,
},
"& svg path": {
fill: theme.palette.primary.contrastTextTertiary,
},
"& .MuiSelect-select": {
padding: "0",
minHeight: "34px",
display: "flex",
alignItems: "center",
lineHeight: 1,
},
...sx,
}}
renderValue={(selected) => {
const selectedItem = items.find((item) => getItemValue(item) === selected);
const displayName = selectedItem ? selectedItem.name : placeholder;
return (
<Typography
sx={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
title={displayName}
>
{displayName}
</Typography>
);
}}
>
{placeholder && (
<MenuItem
className="select-placeholder"
value="0"
sx={{
display: isHidden ? "none" : "flex",
visibility: isHidden ? "none" : "visible",
...itemStyles,
}}
>
{placeholder}
</MenuItem>
)}
{items
.map((item) => {
const itemValue = getItemValue(item);
if (itemValue === undefined || itemValue === null) {
return null;
}
return (
<MenuItem
value={itemValue}
key={`${id}-${itemValue}`}
sx={{
...itemStyles,
}}
>
{item.name}
</MenuItem>
);
})
.filter(Boolean)}
</MuiSelect>
</FieldWrapper>
);
};
Select.propTypes = {
id: PropTypes.string,
name: PropTypes.string,
label: PropTypes.string,
placeholder: PropTypes.string,
isHidden: PropTypes.bool,
error: PropTypes.bool,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
.isRequired,
items: PropTypes.arrayOf(
PropTypes.shape({
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
name: PropTypes.string.isRequired,
})
).isRequired,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
sx: PropTypes.object,
labelControlSpacing: PropTypes.number,
/**
* Maximum width in pixels. Used to control text truncation and element width.
* Responsive breakpoints will be calculated as percentages of this value.
*/
maxWidth: PropTypes.number,
};
export default Select;
@@ -1,74 +0,0 @@
import { Stack, Typography, InputAdornment, IconButton } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
import Icon from "../../../Icon";
export const HttpAdornment = ({ https }) => {
const theme = useTheme();
return (
<Stack
direction="row"
alignItems="center"
height="100%"
sx={{
borderRight: `solid 1px ${theme.palette.primary.lowContrast}`,
backgroundColor: theme.palette.tertiary.main,
pl: theme.spacing(6),
}}
>
<Typography
component="h5"
color={theme.palette.primary.contrastTextSecondary}
sx={{ lineHeight: 1, opacity: 0.8 }}
>
{https ? "https" : "http"}
</Typography>
</Stack>
);
};
HttpAdornment.propTypes = {
https: PropTypes.bool.isRequired,
prefix: PropTypes.string,
};
export const PasswordEndAdornment = ({ fieldType, setFieldType }) => {
const theme = useTheme();
return (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => setFieldType(fieldType === "password" ? "text" : "password")}
sx={{
color: theme.palette.primary.lowContrast,
padding: theme.spacing(1),
"&:focus-visible": {
outline: `2px solid ${theme.palette.primary.main}`,
outlineOffset: `2px`,
},
"& .MuiTouchRipple-root": {
pointerEvents: "none",
display: "none",
},
}}
>
{fieldType === "password" ? (
<Icon
name="EyeOff"
size={20}
/>
) : (
<Icon
name="Eye"
size={20}
/>
)}
</IconButton>
</InputAdornment>
);
};
PasswordEndAdornment.propTypes = {
fieldType: PropTypes.string,
setFieldType: PropTypes.func,
};
@@ -1,181 +0,0 @@
import { Stack, TextField, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { forwardRef, useState, cloneElement } from "react";
import PropTypes from "prop-types";
import FieldWrapper from "../FieldWrapper/index.jsx";
const getSx = (theme, type, maxWidth) => {
const sx = {
maxWidth: maxWidth,
"& .MuiFormHelperText-root": {
position: "absolute",
bottom: `-${theme.spacing(24)}`,
minHeight: theme.spacing(24),
},
};
if (type === "url") {
return {
...sx,
"& .MuiInputBase-root": { padding: 0 },
"& .MuiStack-root": {
borderTopLeftRadius: theme.shape.borderRadius,
borderBottomLeftRadius: theme.shape.borderRadius,
},
};
}
return sx;
};
const Required = () => {
const theme = useTheme();
return (
<Typography
component="span"
ml={theme.spacing(1)}
color={theme.palette.error.main}
>
*
</Typography>
);
};
const Optional = ({ optionalLabel }) => {
const theme = useTheme();
return (
<Typography
component="span"
fontSize="inherit"
fontWeight={400}
ml={theme.spacing(2)}
sx={{ opacity: 0.6 }}
>
{optionalLabel || "(optional)"}
</Typography>
);
};
Optional.propTypes = {
optionalLabel: PropTypes.string,
};
const TextInput = forwardRef(
(
{
id,
name,
type,
value,
placeholder,
isRequired,
isOptional,
optionalLabel,
onChange,
onBlur,
error = false,
helperText = null,
startAdornment = null,
endAdornment = null,
label = null,
maxWidth = "100%",
flex,
marginTop,
marginRight,
marginBottom,
marginLeft,
disabled = false,
hidden = false,
//FieldWrapper's props
gap,
labelMb,
labelFontWeight,
labelVariant,
labelSx = {},
sx = {},
},
ref
) => {
const [fieldType, setFieldType] = useState(type);
const theme = useTheme();
const labelContent = label && (
<>
{label}
{isRequired && <Required />}
{isOptional && <Optional optionalLabel={optionalLabel} />}
</>
);
return (
<FieldWrapper
label={labelContent}
labelMb={labelMb}
labelVariant={labelVariant}
labelFontWeight={labelFontWeight}
labelSx={labelSx}
gap={gap}
sx={{
flex,
display: hidden ? "none" : "",
mt: marginTop,
mr: marginRight,
mb: marginBottom,
ml: marginLeft,
...sx,
}}
>
<TextField
id={id}
name={name}
type={fieldType}
value={value}
placeholder={placeholder}
onChange={onChange}
onBlur={onBlur}
error={error}
helperText={helperText}
inputRef={ref}
sx={getSx(theme, type, maxWidth)}
slotProps={{
input: {
startAdornment: startAdornment,
endAdornment: endAdornment
? cloneElement(endAdornment, { fieldType, setFieldType })
: null,
},
}}
disabled={disabled}
/>
</FieldWrapper>
);
}
);
TextInput.displayName = "TextInput";
TextInput.propTypes = {
type: PropTypes.string,
id: PropTypes.string,
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
placeholder: PropTypes.string,
isRequired: PropTypes.bool,
isOptional: PropTypes.bool,
optionalLabel: PropTypes.string,
onChange: PropTypes.func,
onBlur: PropTypes.func,
error: PropTypes.bool,
helperText: PropTypes.string,
startAdornment: PropTypes.node,
endAdornment: PropTypes.node,
label: PropTypes.string,
maxWidth: PropTypes.string,
flex: PropTypes.number,
marginTop: PropTypes.string,
marginRight: PropTypes.string,
marginBottom: PropTypes.string,
marginLeft: PropTypes.string,
disabled: PropTypes.bool,
hidden: PropTypes.bool,
};
export default TextInput;
-73
View File
@@ -1,73 +0,0 @@
import { Link as MuiLink, useTheme } from "@mui/material";
import { Link as RouterLink } from "react-router-dom";
import PropTypes from "prop-types";
/**
* @component
* @param {Object} props
* @param {'primary' | 'secondary' | 'tertiary' | 'error'} props.level - The level of the link
* @param {string} props.label - The label of the link
* @param {string} props.url - The URL of the link
* @returns {JSX.Element}
*/
const Link = ({ level, label, url, external = true }) => {
const theme = useTheme();
const levelConfig = {
primary: {
color: theme.palette.primary.contrastTextTertiary,
sx: {
":hover": {
color: theme.palette.primary.contrastTextSecondary,
},
},
},
secondary: {
color: theme.palette.primary.contrastTextSecondary,
sx: {
":hover": {
color: theme.palette.primary.contrastTextSecondary,
},
},
},
tertiary: {
color: theme.palette.primary.contrastTextTertiary,
sx: {
textDecoration: "underline",
textDecorationStyle: "dashed",
textDecorationColor: theme.palette.primary.main,
textUnderlineOffset: "1px",
":hover": {
color: theme.palette.primary.contrastTextTertiary,
textDecorationColor: theme.palette.primary.main,
backgroundColor: theme.palette.primary.lowContrast,
},
},
},
error: {},
};
const { sx, color } = levelConfig[level];
return (
<MuiLink
component={external ? "a" : RouterLink}
to={external ? undefined : url}
href={external ? url : undefined}
sx={{ width: "fit-content", ...sx }}
color={color}
{...(external && { target: "_blank", rel: "noreferrer" })}
>
{label}
</MuiLink>
);
};
Link.propTypes = {
url: PropTypes.string.isRequired,
level: PropTypes.oneOf(["primary", "secondary", "tertiary", "error"]),
label: PropTypes.string.isRequired,
external: PropTypes.bool,
};
export default Link;
@@ -1,38 +0,0 @@
import Typography from "@mui/material/Typography";
import Stack from "@mui/material/Stack";
import Link from "@mui/material/Link";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Link as RouterLink } from "react-router-dom";
const TextLink = ({ text, linkText, href, state, target = "_self" }) => {
const theme = useTheme();
return (
<Stack
direction="row"
gap={theme.spacing(4)}
>
<Typography>{text}</Typography>
<Link
color="accent"
to={href}
component={RouterLink}
target={target}
state={state}
>
{linkText}
</Link>
</Stack>
);
};
TextLink.propTypes = {
text: PropTypes.string,
linkText: PropTypes.string,
href: PropTypes.string,
state: PropTypes.object,
target: PropTypes.string,
};
export default TextLink;
-34
View File
@@ -1,34 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
const ToastBody = ({ body }) => {
const theme = useTheme();
if (Array.isArray(body)) {
return (
<Stack gap={theme.spacing(2)}>
{body.map((item, idx) => (
<Typography
key={`item-${idx}`}
color={theme.palette.secondary.contrastText}
>
{item}
</Typography>
))}
</Stack>
);
} else if (typeof body === "string") {
return <Typography color={theme.palette.secondary.contrastText}>{body}</Typography>;
}
return null;
};
ToastBody.propTypes = {
body: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
};
export default ToastBody;
-114
View File
@@ -1,114 +0,0 @@
import Stack from "@mui/material/Stack";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import ToastBody from "./body.jsx";
import Icon from "../Icon";
// Utils
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
const icons = {
info: (
<Icon
name="Info"
size={24}
/>
),
error: (
<Icon
name="AlertCircle"
size={24}
/>
),
warning: (
<Icon
name="AlertTriangle"
size={24}
/>
),
};
const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
const theme = useTheme();
const icon = icons[variant];
return (
<Stack
gap={theme.spacing(2)}
paddingTop={theme.spacing(4)}
paddingRight={theme.spacing(8)}
paddingBottom={theme.spacing(4)}
paddingLeft={theme.spacing(8)}
backgroundColor={theme.palette.alert.main}
border={`solid 1px ${theme.palette.alert.contrastText}`}
borderRadius={theme.shape.borderRadius}
>
<Stack
direction="row"
gap={theme.spacing(8)}
justifyContent="space-between"
alignItems="center"
>
{hasIcon && icon}
{title && (
<Typography
fontWeight="700"
color={theme.palette.secondary.contrastText}
>
{title}
</Typography>
)}
{title && (
<IconButton onClick={onClick}>
<Icon
name="X"
size={20}
/>
</IconButton>
)}
</Stack>
<Stack
direction="row"
gap={theme.spacing(2)}
alignItems="center"
>
<ToastBody body={body} />
{!title && (
<IconButton onClick={onClick}>
<Icon
name="X"
size={20}
/>
</IconButton>
)}
</Stack>
{hasDismiss && (
<Button
variant="text"
color="info"
onClick={onClick}
sx={{
fontWeight: "600",
width: "fit-content",
}}
>
Dismiss
</Button>
)}
</Stack>
);
};
export default Toast;
Toast.propTypes = {
variant: PropTypes.string.isRequired,
title: PropTypes.string,
body: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
hasDismiss: PropTypes.bool,
hasIcon: PropTypes.bool,
onClick: PropTypes.func,
};
@@ -85,12 +85,7 @@ export const EmptyFallback = ({
zIndex={1}
alignItems="center"
>
<Typography
component="h1"
color={theme.palette.primary.contrastText}
>
{title}
</Typography>
<Typography component="h1">{title}</Typography>
<Stack
sx={{
flexWrap: "wrap",
@@ -5,7 +5,7 @@ import { PulseDot, Dot } from "@/Components/v2/design-elements";
import { getStatusColor, formatUrl } from "@/Utils/MonitorUtils";
import { useTheme } from "@mui/material/styles";
import prettyMilliseconds from "pretty-ms";
import { typographyLevels } from "@/Utils/Theme/v2Palette";
import { typographyLevels } from "@/Utils/Theme/Palette";
import useMediaQuery from "@mui/material/useMediaQuery";
export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
const theme = useTheme();
@@ -19,7 +19,6 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
<Typography
fontSize={typographyLevels.xl}
fontWeight={500}
color={theme.palette.primary.contrastText}
overflow={"hidden"}
textOverflow={"ellipsis"}
whiteSpace={"nowrap"}
@@ -4,7 +4,7 @@ import Box from "@mui/material/Box";
import { useTheme } from "@mui/material/styles";
import { lighten } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import type { PaletteKey } from "@/Utils/Theme/v2Theme";
import type { PaletteKey } from "@/Utils/Theme/Theme";
import { BaseBox, TooltipWithInfo } from "@/Components/v2/design-elements";
import type { SxProps } from "@mui/material";
@@ -1,6 +1,6 @@
import { MuiColorInput } from "mui-color-input";
import type { MuiColorInputProps } from "mui-color-input";
import { typographyLevels } from "@/Utils/Theme/v2Palette";
import { typographyLevels } from "@/Utils/Theme/Palette";
import { useTheme } from "@mui/material";
import Stack from "@mui/material/Stack";
import { FieldLabel } from "@/Components/v2/inputs/FieldLabel";
+1 -1
View File
@@ -5,7 +5,7 @@ import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import { Button } from "@/Components/v2/inputs";
import { typographyLevels } from "@/Utils/Theme/v2Palette";
import { typographyLevels } from "@/Utils/Theme/Palette";
import { useTranslation } from "react-i18next";
import type { ReactNode } from "react";
@@ -1,7 +1,7 @@
import { forwardRef } from "react";
import TextField from "@mui/material/TextField";
import type { TextFieldProps } from "@mui/material";
import { typographyLevels } from "@/Utils/Theme/v2Palette";
import { typographyLevels } from "@/Utils/Theme/Palette";
import { useTheme } from "@mui/material/styles";
import Stack from "@mui/material/Stack";
import { FieldLabel } from "./FieldLabel";
@@ -51,7 +51,6 @@ const AppLayout = ({ children }: AppLayoutProps) => {
backgroundSize: "100% 100%",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
color: theme.palette.primary.contrastText,
}}
>
<OfflineBanner visible={serverUnreachable} />
+1 -1
View File
@@ -1,5 +1,5 @@
import type { MonitorStatus, MonitorType } from "@/Types/Monitor";
import type { PaletteKey } from "@/Utils/Theme/v2Theme";
import type { PaletteKey } from "@/Utils/Theme/Theme";
import type { ValueType } from "@/Components/v2/design-elements/StatusLabel";
export const getMonitorPath = (type: MonitorType): string => {
@@ -1,5 +1,5 @@
import { createTheme } from "@mui/material";
import { lightPalette, darkPalette, typographyLevels } from "@/Utils/Theme/v2Palette";
import { lightPalette, darkPalette, typographyLevels } from "@/Utils/Theme/Palette";
import type { Theme } from "@mui/material/styles";
-291
View File
@@ -1,291 +0,0 @@
import { lighten } from "@mui/material/styles"; // CAIO_REVIEW
const typographyBase = 13;
/* TODO
Check for px in codebase. All font sizes should be in REM and should live here.
Rest should be checked on each case
*/
const typographyLevels = {
base: typographyBase,
xs: `${(typographyBase - 4) / 16}rem`,
s: `${(typographyBase - 2) / 16}rem`,
m: `${typographyBase / 16}rem`,
l: `${(typographyBase + 2) / 16}rem`,
xl: `${(typographyBase + 10) / 16}rem`,
};
/* TODO Review color palette and semantic colors */
const paletteColors = {
white: "#FFFFFF",
gray50: "#FEFEFE",
gray60: "#FEFDFE",
gray70: "#FDFDFD",
gray80: "#FDFCFD",
gray90: "#FCFCFD",
gray100: "#F4F4F4",
gray150: "#EFEFEF",
gray200: "#E3E3E3",
gray300: "#A2A3A3",
gray500: "#838C99",
gray600: "#454546",
gray750: "#36363E",
gray800: "#2D2D33",
gray850: "#131315",
gray860: "#111113",
gray870: "#0F0F11",
gray880: "#0C0C0E",
gray890: "#09090B",
blueGray20: "#E8F0FE",
blueGray150: "#667085",
blueGray200: "#475467",
blueGray400: "#344054",
blueBlueWave: "#1570EF",
blue700: "#4E5BA6",
purple300: "#664EFF",
purple400: "#3A1BFF",
green50: "#D4F4E1",
green150: "#45BB7A",
green400: "#079455",
green500: "#07B467",
green800: "#1C4428",
green900: "#12261E",
red50: "#F9ECED",
red100: "#FBD1D1",
red200: "#F04438",
red300: "#D32F2F",
red700: "#542426",
red800: "#912018",
orange50: "#FEF8EA",
orange100: "#FFECBC",
orange300: "#FDB022",
orange400: "#FF9F00",
orange500: "#E88C30",
orange600: "#DC6803",
orange800: "#624711",
};
const semanticColors = {
unresolved: {
main: {
light: paletteColors.blue700,
dark: paletteColors.purple300,
},
light: {
light: paletteColors.blueGray20,
dark: paletteColors.purple400,
},
bg: {
light: paletteColors.gray100,
dark: paletteColors.gray100,
},
},
};
const newColors = {
offWhite: "#FEFEFE",
offBlack: "#131315",
gray0: "#FDFDFD",
gray10: "#F4F4FF",
gray50: "#F9F9F9",
gray100: "#F3F3F3",
gray200: "#EFEFEF",
gray250: "#DADADA",
gray500: "#A2A3A3",
gray900: "#1c1c1c",
blueGray50: "#E8F0FE",
blueGray500: "#475467",
blueGray600: "#344054",
blueGray800: "#1C2130",
blueGray900: "#515151",
blueBlueWave: "#1570EF",
lightBlueWave: "#CDE2FF",
green100: "#67cd78",
green200: "#4B9B77",
green400: "#079455",
green700: "#026513",
orange100: "#FD8F22",
orange200: "#D69A5D",
orange600: "#9B734B",
orange700: "#884605",
red100: "#F27C7C",
red400: "#D92020",
red600: "#9B4B4B",
red700: "#980303",
};
/*
Structure:
main: background color
contrastText: color for main contrast text
contrastTextSecondary: if needed
contrastTextTertiary: if needed
lowContrast: if we need some low contrast for that color (for borders, and decorative elements). This should never be usend in text
*/
const newSemanticColors = {
accent: {
main: {
light: newColors.blueBlueWave,
dark: newColors.blueBlueWave,
},
light: {
light: lighten(newColors.blueBlueWave, 0.2), //CAIO_REVIEW
dark: lighten(newColors.blueBlueWave, 0.2), //CAIO_REVIEW
},
contrastText: {
light: newColors.offWhite,
dark: newColors.offWhite,
},
},
primary: {
main: {
light: newColors.offWhite,
dark: newColors.offBlack,
},
contrastText: {
light: newColors.blueGray800,
dark: newColors.blueGray50,
},
contrastTextSecondary: {
light: newColors.blueGray600,
dark: newColors.gray200,
},
contrastTextSecondaryDarkBg: {
light: newColors.gray200,
dark: newColors.gray200,
},
contrastTextTertiary: {
light: newColors.blueGray500,
dark: newColors.gray500,
},
lowContrast: {
light: newColors.gray250,
dark: newColors.blueGray600,
},
},
primaryBackground: {
main: {
light: newColors.gray0,
dark: "#000000",
},
},
secondary: {
main: {
light: newColors.gray200,
dark: "#313131" /* newColors.blueGray600 */,
},
light: {
light: newColors.lightBlueWave,
dark: newColors.lightBlueWave,
},
contrastText: {
light: newColors.blueGray600,
dark: newColors.gray200,
},
},
tertiary: {
main: {
light: newColors.gray100,
dark: newColors.blueGray800,
},
contrastText: {
light: newColors.blueGray800,
dark: newColors.gray100,
},
},
success: {
main: {
light: newColors.green700,
dark: newColors.green100,
},
contrastText: {
light: newColors.offWhite,
dark: newColors.offBlack,
},
lowContrast: {
light: newColors.green400,
dark: newColors.green200,
},
},
warning: {
main: {
light: newColors.orange700,
dark: newColors.orange200,
},
contrastText: {
light: newColors.offWhite,
dark: newColors.offBlack,
},
lowContrast: {
light: newColors.orange100,
dark: newColors.orange600,
},
},
/* Custom palette for PageSpeed warning box */
warningSecondary: {
main: {
light: paletteColors.orange50,
dark: paletteColors.orange800,
},
contrastText: {
light: paletteColors.orange600,
dark: paletteColors.orange100,
},
lowContrast: {
light: paletteColors.orange300,
dark: paletteColors.orange400,
},
},
error: {
main: {
light: newColors.red700,
dark: newColors.red100,
},
contrastText: {
light: newColors.offWhite,
dark: newColors.offBlack,
},
lowContrast: {
light: newColors.red400,
dark: newColors.red600,
},
},
/* These are temporary, just for everything not to break */
gradient: {
color1: {
light: paletteColors.gray90,
dark: paletteColors.gray890,
},
color2: {
light: paletteColors.gray80,
dark: paletteColors.gray880,
},
color3: {
light: paletteColors.gray70,
dark: paletteColors.gray870,
},
color4: {
light: paletteColors.gray60,
dark: paletteColors.gray860,
},
color5: {
light: paletteColors.gray50,
dark: paletteColors.gray850,
},
},
alert: {
main: {
light: newColors.gray200,
dark: newColors.gray900,
},
contrastText: {
light: newColors.blueGray600,
dark: newColors.blueGray900,
},
},
};
export { typographyLevels, semanticColors as colors, newSemanticColors };
-13
View File
@@ -1,13 +0,0 @@
import { createTheme } from "@mui/material";
import { baseTheme } from "./globalTheme";
import { /* colors, */ newSemanticColors } from "./constants";
import { extractThemeColors } from "./extractColorObject";
const palette = extractThemeColors("dark", newSemanticColors);
const darkTheme = createTheme({
palette,
...baseTheme(palette),
});
export default darkTheme;
@@ -1,25 +0,0 @@
function extractThemeColors(themeType, colorObject) {
if (!["light", "dark"].includes(themeType)) {
throw new Error('Invalid theme type. Use "light" or "dark".');
}
const extract = (obj) => {
if (typeof obj !== "object" || obj === null) {
return obj;
}
if ("light" in obj && "dark" in obj) {
// CAIO_REVIEW: This will break if the root object has light and dark properties
return obj[themeType]; // Return the value for the specified theme
}
// Reduce the object, keeping only the extracted themeType values
return Object.keys(obj).reduce((acc, key) => {
acc[key] = extract(obj[key]);
return acc;
}, {});
};
return extract(colorObject);
}
export { extractThemeColors };
-793
View File
@@ -1,793 +0,0 @@
import { typographyLevels } from "./constants";
const fontFamilyPrimary = '"Inter" , sans-serif';
import { darken } from "@mui/material/styles";
// const fontFamilySecondary = '"Avenir", sans-serif';
/* TODO take the color out from here */
const shadow =
"0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03)";
const baseTheme = (palette) => ({
typography: {
fontFamily: fontFamilyPrimary,
fontSize: typographyLevels.base,
h1: {
fontSize: typographyLevels.xl,
color: palette.primary.contrastText,
fontWeight: 500,
},
h2: {
fontSize: typographyLevels.l,
color: palette.primary.contrastTextSecondary,
fontWeight: 400,
},
// CAIO_REVIEW, need a brighter color for dark bg
h2DarkBg: {
fontSize: typographyLevels.l,
color: palette.primary.contrastTextSecondaryDarkBg,
fontWeight: 400,
},
body1: {
fontSize: typographyLevels.m,
color: palette.primary.contrastTextTertiary,
fontWeight: 400,
},
body2: {
fontSize: typographyLevels.s,
color: palette.primary.contrastTextTertiary,
fontWeight: 400,
},
label: {
color: palette.primary.contrastTextSecondary,
fontWeight: 500,
},
},
/* TODO change to 4 */
spacing: 2,
/* TODO we can skip using the callback functions on the next lines since we are already accessing it on line 10. That was the last thing I managed to do, so we are sort of doing it twice*/
/* TODO All these should live inside of a component*/
components: {
MuiButton: {
defaultProps: {
disableRipple: true,
},
styleOverrides: {
root: ({ theme }) => ({
variants: [
{
props: (props) => props.variant === "contained" && props.color === "accent",
backgroundColor: theme.palette.accent.main,
style: {
color: theme.palette.primary.contrastTextSecondaryDarkBg,
letterSpacing: "0.5px",
textShadow: "0 0 1px rgba(0, 0, 0, 0.15)",
"&:hover": {
backgroundColor: darken(theme.palette.accent.main, 0.25),
boxShadow: `0 2px 6px rgba(0, 0, 0, 0.1)`,
transition: "all 0.2s ease-in-out",
},
},
},
{
props: (props) => props.color === "accent",
style: {
"&:hover": {
backgroundColor: darken(theme.palette.accent.main, 0.2),
},
},
},
{
props: (props) => props.color === "error",
style: {
"&.Mui-disabled": {
backgroundColor: theme.palette.error.lowContrast,
},
"& .MuiButton-loadingIndicator": {
// styles for error variant loading indicator
color: theme.palette.error.contrastText,
},
},
},
{
props: (props) => props.variant === "group",
style: {
/* color: theme.palette.secondary.contrastText, */
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
},
},
{
props: (props) => props.variant === "group" && props.filled === "true",
style: {
backgroundColor: theme.palette.secondary.main,
},
},
/* {
props: (props) => props.variant === "contained",
style: {
backgroundColor: `${theme.palette.accent.main} !important`,
},
}, */
{
props: (props) =>
props.variant === "contained" && props.color === "secondary",
style: {
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
},
},
{
props: (props) => {
return (
props.variant === "contained" &&
props.disabled &&
props?.classes?.loadingIndicator === undefined // Do not apply to loading button
);
},
style: {
backgroundColor: `${theme.palette.secondary.main} !important`,
color: `${theme.palette.secondary.contrastText} !important`,
},
},
{
props: { variant: "text", color: "info" },
style: {
textDecoration: "underline",
color: theme.palette.primary.contrastText,
padding: 0,
margin: 0,
fontSize: typographyLevels.m,
fontWeight: theme.typography.body2.fontWeight,
backgroundColor: "transparent",
"&:hover": {
backgroundColor: "transparent",
textDecoration: "underline",
},
"&.Mui-disabled": {
backgroundColor: "transparent",
color: theme.palette.text.primary,
opacity: 0.5,
"&.MuiButton-text": {
backgroundColor: "transparent",
},
},
minWidth: 0,
boxShadow: "none",
border: "none",
},
},
],
height: 34,
fontWeight: 400,
borderRadius: 4,
boxShadow: "none",
textTransform: "none",
"&:focus": {
outline: "none",
},
"&:hover": {
boxShadow: "none",
},
"&.Mui-disabled": {
backgroundColor: theme.palette.secondary.main,
color: theme.palette.primary.contrastText,
},
"&.MuiButton-root": {
"&:disabled": {
backgroundColor: theme.palette.secondary.main,
color: theme.palette.primary.contrastText,
},
"&.MuiButton-colorAccent:hover": {
boxShadow: `0 0 0 1px ${theme.palette.accent.main}`, // CAIO_REVIEW, this should really have a solid BG color
},
},
"&.MuiButton-loading": {
"&:disabled": {
color: "transparent",
},
"& .MuiButton-loadingIndicator": {
color: theme.palette.primary.contrastText,
},
},
}),
},
},
MuiIconButton: {
styleOverrides: {
root: ({ theme }) => ({
padding: 4,
transition: "none",
"&:hover": {
backgroundColor: theme.palette.primary.lowContrast,
},
}),
},
},
MuiPaper: {
styleOverrides: {
root: ({ theme }) => {
return {
marginTop: 4,
padding: 0,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
borderRadius: 4,
boxShadow: shadow,
backgroundColor: theme.palette.primary.main,
backgroundImage: "none",
};
},
},
},
MuiList: {
styleOverrides: {
root: ({ theme }) => ({
padding: 0,
}),
},
},
MuiListItemButton: {
styleOverrides: {
root: ({ theme }) => ({
transition: "background-color .3s",
}),
},
},
MuiListItemText: {
styleOverrides: {
root: ({ theme }) => ({
"& .MuiTypography-root": {
color: theme.palette.primary.contrastText,
},
}),
},
},
MuiMenuItem: {
styleOverrides: {
root: ({ theme }) => ({
borderRadius: 4,
backgroundColor: "inherit",
padding: "4px 6px",
color: theme.palette.primary.contrastTextSecondary,
fontSize: 13,
margin: 2,
minWidth: 100,
"&:hover, &.Mui-selected, &.Mui-selected:hover, &.Mui-selected.Mui-focusVisible":
{
backgroundColor: theme.palette.primary.lowContrast,
},
}),
},
},
MuiTableCell: {
styleOverrides: {
root: ({ theme }) => ({
fontSize: typographyLevels.base,
borderBottomColor: theme.palette.primary.lowContrast,
}),
},
},
MuiTableHead: {
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.tertiary.main,
}),
},
},
MuiPagination: {
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
"& button": {
color: theme.palette.primary.contrastTextTertiary,
borderRadius: 4,
},
"& li:first-of-type button, & li:last-of-type button": {
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
},
}),
},
},
MuiPaginationItem: {
styleOverrides: {
root: ({ theme }) => ({
"&:not(.MuiPaginationItem-ellipsis):hover, &.Mui-selected": {
backgroundColor: theme.palette.primary.lowContrast,
},
}),
},
},
MuiSkeleton: {
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.primary.lowContrast,
}),
},
},
MuiTextField: {
styleOverrides: {
root: ({ theme }) => ({
"& fieldset": {
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
"& .MuiInputBase-input": {
padding: ".75em",
fontWeight: 400,
color: palette.primary.contrastTextSecondary,
"&.Mui-disabled": {
opacity: 0.3,
WebkitTextFillColor: "unset",
},
"& .Mui-focused": {
/* color: "#ff0000", */
/* borderColor: theme.palette.primary.contrastText, */
},
},
"& .MuiInputBase-input:-webkit-autofill": {
transition: "background-color 5000s ease-in-out 0s",
WebkitBoxShadow: `0 0 0px 1000px ${theme.palette.primary.main} inset`,
WebkitTextFillColor: theme.palette.primary.contrastText,
},
"& .MuiInputBase-input.MuiOutlinedInput-input": {
padding: `0 ${theme.spacing(5)}`,
},
"& .MuiOutlinedInput-root": {
color: theme.palette.primary.contrastTextSecondary,
borderRadius: 4,
},
"& .MuiOutlinedInput-notchedOutline": {
borderRadius: 4,
},
"& .MuiFormHelperText-root": {
color: palette.error.main,
opacity: 0.8,
fontSize: "var()",
marginLeft: 0,
},
"& .MuiFormHelperText-root.Mui-error": {
opacity: 0.8,
color: palette.error.main,
whiteSpace: "nowrap",
},
}),
},
},
MuiOutlinedInput: {
styleOverrides: {
root: {
"&.Mui-disabled .MuiOutlinedInput-notchedOutline": {
borderColor: palette.primary.contrastBorderDisabled,
},
"&.Mui-disabled:hover .MuiOutlinedInput-notchedOutline": {
borderColor: palette.primary.contrastBorderDisabled,
},
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: palette.primary.lowContrast, // Adjust hover border color
},
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
borderColor: palette.accent.main, // Adjust focus border color
},
color: palette.primary.contrastText,
},
},
},
MuiAutocomplete: {
styleOverrides: {
root: ({ theme }) => ({
"& .MuiOutlinedInput-root": {
paddingTop: 0,
paddingBottom: 0,
paddingRight: theme.spacing(5),
},
"& fieldset": {
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
"& .MuiOutlinedInput-root:hover:not(:has(input:focus)):not(:has(textarea:focus)) fieldset":
{
borderColor: theme.palette.primary.lowContrast,
},
"& .MuiAutocomplete-tag": {
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.lowContrast,
},
"& .MuiChip-deleteIcon": {
color: theme.palette.primary.contrastText, // CAIO_REVIEW
},
}),
},
},
MuiTab: {
styleOverrides: {
root: ({ theme }) => ({
color: theme.palette.tertiary.contrastText,
height: "34px",
minHeight: "34px",
borderRadius: 0,
textTransform: "none",
minWidth: "fit-content",
padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`,
fontWeight: 400,
"&:focus-visible": {
color: theme.palette.primary.contrastText,
borderColor: theme.palette.tertiary.contrastText,
borderRightColor: theme.palette.primary.lowContrast,
},
"&.Mui-selected": {
color: theme.palette.secondary.contrastText,
},
"&:hover": {
borderColor: theme.palette.primary.lowContrast,
},
}),
},
variants: [
{
props: { orientation: "vertical" },
style: ({ theme }) => ({
alignItems: "flex-start",
padding: `${theme.spacing(1)}px ${theme.spacing(2)}px ${theme.spacing(1)}px ${theme.spacing(6)}px`,
minHeight: theme.spacing(12),
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.main,
border: "none",
borderBottom: "none",
borderRight: "none",
borderRadius: theme.shape.borderRadius * 3,
margin: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
"&.Mui-selected": {
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.tertiary.main,
opacity: 1,
border: "none",
borderBottom: "none",
borderRight: "none",
borderRadius: theme.shape.borderRadius * 3,
minHeight: theme.spacing(14),
},
"&:hover": {
backgroundColor: theme.palette.tertiary.main,
border: "none",
borderRadius: theme.shape.borderRadius * 3,
minHeight: theme.spacing(14),
},
}),
},
],
},
MuiSvgIcon: {
styleOverrides: {
root: ({ theme }) => ({
color: theme.palette.primary.contrastTextTertiary,
}),
},
},
MuiTabs: {
styleOverrides: {
root: ({ theme }) => ({
height: "34px",
minHeight: "34px",
display: "inline-flex",
borderRadius: 0,
"& .MuiTabs-indicator": {
backgroundColor: theme.palette.tertiary.contrastText,
},
}),
},
variants: [
{
props: { orientation: "vertical" },
style: {
"& .MuiTabs-indicator": {
display: "none",
},
},
},
],
},
MuiSwitch: {
styleOverrides: {
root: ({ theme }) => ({
width: 42,
height: 26,
padding: 0,
"& .MuiSwitch-switchBase": {
padding: 0,
margin: 2,
transitionDuration: "300ms",
"&.Mui-checked": {
transform: "translateX(16px)",
color: "#fff",
"& + .MuiSwitch-track": {
backgroundColor: theme.palette.accent.main,
opacity: 1,
border: 0,
},
},
},
"& .MuiSwitch-thumb": {
boxSizing: "border-box",
width: 22,
height: 22,
},
"& .MuiSwitch-track": {
borderRadius: 13,
backgroundColor: theme.palette.secondary.light,
opacity: 1,
},
}),
},
},
MuiSelect: {
styleOverrides: {
root: ({ theme }) => ({
"& .MuiOutlinedInput-input": {
color: theme.palette.primary.contrastText,
},
"& .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
"& .MuiSelect-icon": {
color: theme.palette.primary.contrastTextSecondary, // Dropdown + color
},
"& .MuiSelect-select": {
display: "flex",
alignItems: "center",
},
"&:hover": {
backgroundColor: theme.palette.primary.main, // Background on hover
},
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.primary.lowContrast,
},
padding: `0 ${theme.spacing(5)}`,
minHeight: "34px",
display: "flex",
alignItems: "center",
lineHeight: 1,
}),
},
},
MuiButtonGroup: {
styleOverrides: {
root: ({ theme }) => ({
ml: "auto",
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
borderColor: theme.palette.primary.lowContrast,
width: "auto",
whiteSpace: "nowrap",
},
}),
},
},
// This code is added for clock in maintenance page
// code starts from here.
MuiClock: {
// Directly target the clock component
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.primary.main, // Alternative target
"& .MuiClock-clock": {
// Inner clock face
backgroundColor: theme.palette.secondary.main,
},
}),
},
},
MuiClockPicker: {
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.secondary.main, // Outer container background
"& .MuiClock-root": {
color: theme.palette.primary.lowContrast,
},
"& .MuiClock-clock": {
backgroundColor: theme.palette.background.default, // Clock face background
borderColor: theme.palette.secondary.lowContrast,
},
}),
},
},
// The clock pointer ( pointer to number like hour/minute hand)
MuiClockPointer: {
styleOverrides: {
root: ({ theme }) => ({
// Main pointer line color
backgroundColor: theme.palette.accent.main,
"& .MuiClockPointer-thumb": {
backgroundColor: theme.palette.grey[500], // Use your desired grey
},
}),
},
},
// This is for numbers in the clock (circular one's)
MuiClockNumber: {
styleOverrides: {
root: ({ theme }) => ({
color: theme.palette.primary.contrastText,
"&.Mui-selected": {
color: theme.palette.accent.contrastText,
backgroundColor: theme.palette.accent.main,
},
}),
},
},
// This is for 00:00 am and pm (top bar)
MuiTimePickerToolbar: {
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.secondary.lowContrast,
// General text color
"& .MuiTypography-root": {
color: theme.palette.primary.contrastTextTertiary,
},
// Selected time (hour/minute) color
"& .Mui-selected": {
color: `${theme.palette.accent.main} !important`,
},
// AM/PM buttons color
"& .MuiButtonBase-root": {
"&.Mui-selected": {
backgroundColor: theme.palette.accent.main,
},
},
}),
},
},
// left and right direction style
MuiPickersArrowSwitcher: {
styleOverrides: {
root: ({ theme }) => ({
"& .MuiIconButton-root": {
color: theme.palette.primary.contrastText,
},
}),
},
},
// cancel and okay actions style
MuiDialogActions: {
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.primary.main,
}),
},
},
// DatePicker calendar popup styling
MuiPickersPopper: {
styleOverrides: {
paper: ({ theme }) => ({
backgroundColor: theme.palette.primary.main,
}),
},
},
MuiDateCalendar: {
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.primary.main,
}),
},
},
MuiPickersCalendarHeader: {
styleOverrides: {
root: ({ theme }) => ({
"& .MuiPickersCalendarHeader-label": {
color: theme.palette.primary.contrastText,
},
}),
},
},
MuiDayCalendar: {
styleOverrides: {
weekDayLabel: ({ theme }) => ({
color: theme.palette.primary.contrastTextTertiary,
}),
},
},
MuiPickersDay: {
styleOverrides: {
root: ({ theme }) => ({
color: theme.palette.primary.contrastText,
"&:hover": {
backgroundColor: theme.palette.tertiary.main,
},
"&.Mui-selected": {
backgroundColor: theme.palette.accent.main,
color: theme.palette.accent.contrastText,
"&:hover": {
backgroundColor: theme.palette.accent.main,
},
},
"&.Mui-disabled": {
color: theme.palette.primary.contrastTextTertiary,
opacity: 0.5,
},
}),
},
},
// code ends here.
// For labels of input fields
MuiInputLabel: {
styleOverrides: {
root: ({ theme }) => ({
"&.Mui-focused": {
color: theme.palette.accent.main,
},
}),
},
},
MuiTypography: {
variants: [
{
props: { variant: "monitorName" },
style: {
fontSize: typographyLevels.xl,
color: palette.primary.contrastText,
fontWeight: 500,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
},
},
{
props: { variant: "monitorUrl" },
style: {
fontSize: typographyLevels.l,
color: palette.primary.contrastTextSecondary,
fontWeight: "bolder",
fontFamily: "monospace",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
},
},
],
},
MuiTooltip: {
styleOverrides: {
tooltip: () => ({
fontSize: typographyLevels.m,
}),
},
},
},
shape: {
borderRadius: 2,
borderThick: 2,
boxShadow: shadow,
},
});
export { baseTheme };
-12
View File
@@ -1,12 +0,0 @@
import { createTheme } from "@mui/material";
import { baseTheme } from "./globalTheme";
import { /* colors, */ newSemanticColors } from "./constants";
import { extractThemeColors } from "./extractColorObject";
const palette = extractThemeColors("light", newSemanticColors);
const lightTheme = createTheme({
palette,
...baseTheme(palette),
});
export default lightTheme;