Merge pull request #1174 from bluewave-labs/feat/fe/refactor/input

Feat/fe/refactor/input
This commit is contained in:
Alexander Holliday authored and GitHub committed 2024-11-25 19:38:08 -08:00
commit 7e553a73e8
8 files changed
+584 -20

No files matched your search

+153 -18
View File
@@ -13,7 +13,7 @@
"@fontsource/roboto": "^5.0.13",
"@mui/icons-material": "^5.15.17",
"@mui/lab": "^5.0.0-alpha.170",
"@mui/material": "^5.16.7",
"@mui/material": "6.1.8",
"@mui/x-charts": "^7.5.1",
"@mui/x-data-grid": "7.22.3",
"@mui/x-date-pickers": "7.22.3",
@@ -1135,9 +1135,9 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
"version": "5.16.7",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz",
"integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==",
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.8.tgz",
"integrity": "sha512-TGAvzwUg9hybDacwfIGFjI2bXYXrIqky+vMfaeay8rvT56/PNAlvIDUJ54kpT5KRc9AWAihOvtDI7/LJOThOmQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -1212,26 +1212,26 @@
}
},
"node_modules/@mui/material": {
"version": "5.16.7",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.7.tgz",
"integrity": "sha512-cwwVQxBhK60OIOqZOVLFt55t01zmarKJiJUWbk0+8s/Ix5IaUzAShqlJchxsIQ4mSrWqgcKCCXKtIlG5H+/Jmg==",
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.8.tgz",
"integrity": "sha512-QZdQFnXct+7NXIzHgT3qt+sQiO7HYGZU2vymP9Xl9tUMXEOA/S1mZMMb7+WGZrk5TzNlU/kP/85K0da5V1jXoQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.9",
"@mui/core-downloads-tracker": "^5.16.7",
"@mui/system": "^5.16.7",
"@mui/types": "^7.2.15",
"@mui/utils": "^5.16.6",
"@babel/runtime": "^7.26.0",
"@mui/core-downloads-tracker": "^6.1.8",
"@mui/system": "^6.1.8",
"@mui/types": "^7.2.19",
"@mui/utils": "^6.1.8",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.10",
"clsx": "^2.1.0",
"@types/react-transition-group": "^4.4.11",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1",
"react-is": "^18.3.1",
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=12.0.0"
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
@@ -1240,9 +1240,10 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
"@mui/material-pigment-css": "^6.1.8",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
@@ -1251,6 +1252,140 @@
"@emotion/styled": {
"optional": true
},
"@mui/material-pigment-css": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/material/node_modules/@mui/private-theming": {
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.8.tgz",
"integrity": "sha512-TuKl7msynCNCVvhX3c0ef1sF0Qb3VHcPs8XOGB/8bdOGBr/ynmIG1yTMjZeiFQXk8yN9fzK/FDEKMFxILNn3wg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/utils": "^6.1.8",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/material/node_modules/@mui/styled-engine": {
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.8.tgz",
"integrity": "sha512-ZvEoT0U2nPLSLI+B4by4cVjaZnPT2f20f4JUPkyHdwLv65ZzuoHiTlwyhqX1Ch63p8bcJzKTHQVGisEoMK6PGA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@emotion/cache": "^11.13.1",
"@emotion/serialize": "^1.3.2",
"@emotion/sheet": "^1.4.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
}
}
},
"node_modules/@mui/material/node_modules/@mui/system": {
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.8.tgz",
"integrity": "sha512-i1kLfQoWxzFpXTBQIuPoA3xKnAnP3en4I2T8xIolovSolGQX5k8vGjw1JaydQS40td++cFsgCdEU458HDNTGUA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/private-theming": "^6.1.8",
"@mui/styled-engine": "^6.1.8",
"@mui/types": "^7.2.19",
"@mui/utils": "^6.1.8",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/material/node_modules/@mui/utils": {
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.8.tgz",
"integrity": "sha512-O2DWb1kz8hiANVcR7Z4gOB3SvPPsSQGUmStpyBDzde6dJIfBzgV9PbEQOBZd3EBsd1pB+Uv1z5LAJAbymmawrA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/types": "^7.2.19",
"@types/prop-types": "^15.7.13",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"react-is": "^18.3.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
+1 -1
View File
@@ -16,7 +16,7 @@
"@fontsource/roboto": "^5.0.13",
"@mui/icons-material": "^5.15.17",
"@mui/lab": "^5.0.0-alpha.170",
"@mui/material": "^5.16.7",
"@mui/material": "6.1.8",
"@mui/x-charts": "^7.5.1",
"@mui/x-data-grid": "7.22.3",
"@mui/x-date-pickers": "7.22.3",
+6
View File
@@ -41,6 +41,7 @@ import { logger } from "./Utils/Logger"; // Import the logger
import { networkService } from "./main";
import { Infrastructure } from "./Pages/Infrastructure";
import InfrastructureDetails from "./Pages/Infrastructure/Details";
import Test from "./Pages/test";
function App() {
const AdminCheckedRegister = withAdminCheck(Register);
const MonitorsWithAdminProp = withAdminProp(Monitors);
@@ -89,6 +90,11 @@ function App() {
path="/"
element={<HomeLayout />}
>
<Route
path="/test"
element={<Test />}
/>
<Route
exact
path="/"
+5 -1
View File
@@ -4,6 +4,10 @@
.field-infrastructure-alert{
max-width: var(--env-var-width-4);
}
.field-infrastructure-alert .MuiInputBase-root:has(input) {
/* height: var(--env-var-height-2); */
min-width: unset;
}
@@ -20,7 +24,7 @@
padding-right: var(--env-var-spacing-1-minus);
}
.field .MuiInputBase-root:has(input) {
height: var(--env-var-height-2);
/* height: var(--env-var-height-2); */
}
.field .MuiInputBase-root:has(.MuiInputAdornment-root) {
padding-right: var(--env-var-spacing-1-minus);
@@ -0,0 +1,60 @@
import { Stack, Typography, InputAdornment, IconButton } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { useState } from "react";
import PropTypes from "prop-types";
import VisibilityOff from "@mui/icons-material/VisibilityOff";
import Visibility from "@mui/icons-material/Visibility";
export const HttpAdornment = ({ https }) => {
const theme = useTheme();
return (
<Stack
direction="row"
alignItems="center"
height="100%"
sx={{
borderRight: `solid 1px ${theme.palette.border.dark}`,
backgroundColor: theme.palette.background.accent,
pl: theme.spacing(6),
}}
>
<Typography
component="h5"
paddingRight={"var(--env-var-spacing-1-minus)"}
color={theme.palette.text.secondary}
sx={{ lineHeight: 1, opacity: 0.8 }}
>
{https ? "https" : "http"}://
</Typography>
</Stack>
);
};
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.border.dark,
padding: theme.spacing(1),
"&:focus-visible": {
outline: `2px solid ${theme.palette.primary.main}`,
outlineOffset: `2px`,
},
"& .MuiTouchRipple-root": {
pointerEvents: "none",
display: "none",
},
}}
>
{fieldType === "password" ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
);
};
HttpAdornment.propTypes = {
https: PropTypes.bool.isRequired,
};
@@ -0,0 +1,130 @@
import { Stack, TextField, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { forwardRef, useState, cloneElement } from "react";
import PropTypes from "prop-types";
const getSx = (theme, type, maxWidth) => {
const sx = {
maxWidth: maxWidth,
};
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(
(
{
type,
value,
placeholder,
isRequired,
isOptional,
optionalLabel,
onChange,
error = false,
helperText = null,
startAdornment = null,
endAdornment = null,
label = null,
maxWidth = "100%",
},
ref
) => {
const [fieldType, setFieldType] = useState(type);
const theme = useTheme();
return (
<Stack>
<Typography
component="h3"
fontSize={"var(--env-var-font-size-medium)"}
color={theme.palette.text.secondary}
fontWeight={500}
>
{label}
{isRequired && <Required />}
{isOptional && <Optional optionalLabel={optionalLabel} />}
</Typography>
<TextField
type={fieldType}
value={value}
placeholder={placeholder}
onChange={onChange}
error={error}
helperText={helperText}
inputRef={ref}
sx={getSx(theme, type, maxWidth)}
slotProps={{
input: {
startAdornment: startAdornment,
endAdornment: endAdornment
? cloneElement(endAdornment, { fieldType, setFieldType })
: null,
},
}}
/>
</Stack>
);
}
);
TextInput.displayName = "TextInput";
TextInput.propTypes = {
type: PropTypes.string,
value: PropTypes.string,
placeholder: PropTypes.string,
isRequired: PropTypes.bool,
isOptional: PropTypes.bool,
optionalLabel: PropTypes.string,
onChange: PropTypes.func,
error: PropTypes.bool,
helperText: PropTypes.string,
startAdornment: PropTypes.node,
endAdornment: PropTypes.node,
label: PropTypes.string,
maxWidth: PropTypes.string,
};
export default TextInput;
+192
View File
@@ -0,0 +1,192 @@
import { Stack, Typography } from "@mui/material";
import Field from "../Components/Inputs/Field";
import TextInput from "../Components/Inputs/TextInput";
import { useState, useEffect, useRef } from "react";
import { HttpAdornment } from "../Components/Inputs/TextInput/Adornments";
import { PasswordEndAdornment } from "../Components/Inputs/TextInput/Adornments";
const Test = () => {
const [originalValue, setOriginalValue] = useState("");
const [originalError, setOriginalError] = useState("");
const [newValue, setNewValue] = useState("");
const [newError, setNewError] = useState("");
const [thresholdValue, setThresholdValue] = useState(20);
const [thresholdError, setThresholdError] = useState("");
const [thresholdValue2, setThresholdValue2] = useState(20);
const [thresholdError2, setThresholdError2] = useState("");
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
const checkError = (value) => {
if (value !== "clear") {
return "This is an error";
}
return "";
};
const checkThresholdError = (value) => {
if (value !== 99) {
return "This is a threshold error";
}
return "";
};
const checkThresholdError2 = (value) => {
if (value !== 99) {
return "This is a threshold error 2";
}
return "";
};
const handleOriginalValue = (e) => {
setOriginalError(checkError(e.target.value));
setOriginalValue(e.target.value);
};
const handleNewValue = (e) => {
setNewError(checkError(e.target.value));
setNewValue(e.target.value);
};
const handleThresholdValue = (e) => {
const parsedVal = parseInt(e.target.value);
setThresholdError(checkThresholdError(parsedVal));
setThresholdValue(parsedVal);
};
const handleThresholdValue2 = (e) => {
const parsedVal = parseInt(e.target.value);
setThresholdError2(checkThresholdError2(parsedVal));
setThresholdValue2(parsedVal);
};
return (
<Stack
gap={8}
direction="column"
border="1px dashed blue"
padding="1rem"
>
<Typography>
This is a test page for the TextInput component. It is a rationalized Input
component.
</Typography>
<Typography>Type anything for an error.</Typography>
<Typography>Typing "clear" will clear the error for text based input</Typography>
<Typography>Typing "99" will clear the error for threshold based input</Typography>
<Field
id="original-field"
onChange={handleOriginalValue}
type="text"
value={originalValue}
error={originalError}
/>
<TextInput
value={newValue}
onChange={handleNewValue}
error={newError !== ""}
helperText={newError}
/>
<Field
type={"url"}
id="monitor-url"
label={"URL to monitor"}
https={true}
placeholder={""}
value={originalValue}
onChange={handleOriginalValue}
error={originalError}
/>
<TextInput
type={"url"}
id="monitor-url"
label={"URL to monitor"}
placeholder={""}
value={newValue}
startAdornment={<HttpAdornment https={true} />}
onChange={handleNewValue}
error={newError !== ""}
helperText={newError}
/>
<Field
type="password"
id="login-password-input"
label="Password"
isRequired={true}
placeholder="••••••••••"
autoComplete="current-password"
value={originalValue}
onChange={handleOriginalValue}
error={originalError}
ref={inputRef}
/>
<TextInput
type="password"
id="login-password-input"
label="Password"
isRequired={true}
placeholder="••••••••••"
autoComplete="current-password"
value={newValue}
endAdornment={<PasswordEndAdornment />}
onChange={handleNewValue}
error={newError !== ""}
helperText={newError}
ref={inputRef}
/>
<Field
id="ttl"
label="The days you want to keep monitoring history."
isOptional={true}
optionalLabel="0 for infinite"
value={originalValue}
onChange={handleOriginalValue}
error={originalError}
/>
<TextInput
id="ttl"
label="The days you want to keep monitoring history."
isOptional={true}
optionalLabel="0 for infinite"
value={newValue}
onChange={handleNewValue}
error={newError !== ""}
helperText={newError}
/>
<Typography>Short field for threshold. Easily show/hide error text</Typography>
<TextInput
maxWidth="var(--env-var-width-4)"
id="threshold"
type="number"
value={thresholdValue.toString()}
onChange={handleThresholdValue}
error={thresholdError !== ""}
/>
<TextInput
maxWidth="var(--env-var-width-4)"
id="threshold"
type="number"
value={thresholdValue2.toString()}
onChange={handleThresholdValue2}
error={thresholdError2 !== ""}
/>
<Typography sx={{ color: "red" }}>
{thresholdError} {thresholdError2}
</Typography>
</Stack>
);
};
export default Test;
+37
View File
@@ -209,6 +209,43 @@ const baseTheme = (palette) => ({
}),
},
},
MuiTextField: {
styleOverrides: {
root: ({ theme }) => ({
"& fieldset": {
borderColor: theme.palette.border.dark,
borderRadius: theme.shape.borderRadius,
},
"& .MuiInputBase-input": {
height: "var(--env-var-height-2)",
fontSize: "var(--env-var-font-size-medium)",
fontWeight: 400,
color: palette.text.secondary, // or any color from your palette
},
"& .MuiInputBase-input.MuiOutlinedInput-input": {
padding: "0 var(--env-var-spacing-1-minus) !important",
},
"& .MuiOutlinedInput-root": {
borderRadius: 4,
},
"& .MuiOutlinedInput-notchedOutline": {
borderRadius: 4,
},
"& .MuiFormHelperText-root": {
color: palette.error.main,
opacity: 0.8,
fontSize: "var(--env-var-font-size-medium)",
marginLeft: 0,
},
"& .MuiFormHelperText-root.Mui-error": {
opacity: 0.8,
fontSize: "var(--env-var-font-size-medium)",
color: palette.error.main,
},
}),
},
},
},
shape: {
borderRadius: 2,