somewhat set text fields theme
Change-Id: I624d78fdb8c7c96d19713a72bf691c910b353e5e
diff --git a/client/src/components/buttons.js b/client/src/components/buttons.js
index 1233369..0919c69 100644
--- a/client/src/components/buttons.js
+++ b/client/src/components/buttons.js
@@ -1,8 +1,9 @@
+import { QuestionMark } from "@mui/icons-material";
import { IconButton } from "@mui/material";
import { styled } from "@mui/styles";
-import { CameraIcon, CancelIcon, FolderIcon, PenIcon } from "./svgIcons";
+import { CameraIcon, CancelIcon, CrossedEyeIcon, EyeIcon, FolderIcon, InfoIcon, PenIcon } from "./svgIcons";
-const CustomisePictureButton = styled(
+const RoundButton = styled(
({Icon, ...props}) => (
<IconButton {...props} disableRipple={true}>
<Icon fontSize="inherit"/>
@@ -11,8 +12,6 @@
)(({theme}) => ({
border: `1px solid ${theme.palette.primary.dark}`,
color: theme.palette.primary.dark,
- height: "53px",
- width: "53px",
fontSize: "15px",
"&:hover": {
background: theme.palette.primary.light,
@@ -21,40 +20,95 @@
color: "#FFF",
background: theme.palette.primary.dark,
},
+ "&.MuiIconButton-sizeSmall": {
+ height: "15px",
+ width: "15px",
+ },
+ "&.MuiIconButton-sizeMedium": {
+ height: "30px",
+ width: "30px",
+ },
+ "&.MuiIconButton-sizeLarge": {
+ height: "53px",
+ width: "53px",
+ }
}));
-export const RemovePictureButton = (props) => {
+export const CancelButton = (props) => {
return (
- <CustomisePictureButton {...props}
+ <RoundButton {...props}
aria-label="remove picture"
Icon={CancelIcon}
+ size="large"
/>
)
}
-export const EditPictureButton = (props) => {
+export const EditButton = (props) => {
return (
- <CustomisePictureButton {...props}
+ <RoundButton {...props}
aria-label="edit picture"
Icon={PenIcon}
+ size="large"
/>
)
}
-export const UploadPictureButton = (props) => {
+export const UploadButton = (props) => {
return (
- <CustomisePictureButton {...props}
+ <RoundButton {...props}
aria-label="upload picture"
Icon={FolderIcon}
+ size="large"
/>
)
}
export const TakePictureButton = (props) => {
return (
- <CustomisePictureButton {...props}
+ <RoundButton {...props}
aria-label="take picture"
Icon={CameraIcon}
+ size="large"
/>
)
}
+
+export const InfoButton = (props) => {
+ return (
+ <RoundButton {...props}
+ aria-label="informations"
+ Icon={InfoIcon}
+ size="small"
+ />
+ )
+}
+
+export const TipButton = (props) => {
+ return (
+ <RoundButton {...props}
+ aria-label="informations"
+ Icon={QuestionMark}
+ size="medium"
+ />
+ )
+}
+
+export const ToggleVisibilityButton = styled(
+ ({visible, ...props}) => {
+ const Icon = visible ? CrossedEyeIcon : EyeIcon
+ return (
+ <IconButton {...props} disableRipple={true}>
+ <Icon fontSize="inherit"/>
+ </IconButton>
+ )
+ }
+)(({theme}) => ({
+ color: theme.palette.primary.dark,
+ fontSize: "15px",
+ height: "15px",
+ width: "15px",
+ "&:hover": {
+ background: theme.palette.primary.light,
+ },
+}));
\ No newline at end of file
diff --git a/client/src/components/inputs.js b/client/src/components/inputs.js
new file mode 100644
index 0000000..f7e5824
--- /dev/null
+++ b/client/src/components/inputs.js
@@ -0,0 +1,174 @@
+import { Stack, TextField } from "@mui/material"
+import { styled } from "@mui/styles"
+import React from "react"
+import { InfoButton, ToggleVisibilityButton } from "./buttons"
+import { CheckedIcon, CrossIcon, LockIcon, PenIcon, PersonIcon } from "./svgIcons"
+
+const iconsHeight = "16px"
+const StyledCheckedIconSuccess = styled(CheckedIcon)(({theme}) => ({height: iconsHeight, color: theme.palette.success.main}))
+const StyledCrossIconError = styled(CrossIcon)(({theme}) => ({height: iconsHeight, color: theme.palette.error.main}))
+const StyledPenIconLight = styled(PenIcon)({height: iconsHeight, color: "#03B9E9"})
+const StyledPenIconDark = styled(PenIcon)(({theme}) => ({height: iconsHeight, color: theme.palette.primary.dark}))
+const StyledPersonIconLight = styled(PersonIcon)({height: iconsHeight, color: "#03B9E9"})
+const StyledLockIcon = styled(LockIcon)({height: iconsHeight, color: "#03B9E9"})
+
+export const UsernameInput = ({infoButtonProps, ...props}) => {
+ const [isSelected, setIsSelected] = React.useState(false);
+ const [input, setInput] = React.useState();
+ const [startAdornment, setStartAdornment] = React.useState()
+
+ const onChange = React.useCallback((event) => {
+ setInput(event.target.value)
+ props.onChange?.(event)
+ }, [props.onChange])
+
+ React.useEffect(() => {
+ /* Handle startAdornment */
+ let Icon = StyledPersonIconLight
+ let visibility = "visible"
+ if (props.error) {
+ Icon = StyledCrossIconError
+ }
+ else if (props.success) {
+ Icon = StyledCheckedIconSuccess
+ }
+ else if (!isSelected && !input) {
+ visibility = "hidden" // keep icon's space so text does not move
+ }
+ setStartAdornment(<Icon sx={{visibility}}/>)
+ }, [props.error, props.success, isSelected, input])
+
+ return (
+ <TextField
+ {...props}
+ label="Choose an identifier"
+ variant="standard"
+ InputLabelProps={{ shrink: !!(isSelected || input) }}
+ onChange={onChange}
+ InputProps={{
+ startAdornment,
+ endAdornment: <InfoButton {...infoButtonProps}/>,
+ }}
+ onFocus={() => setIsSelected(true)}
+ onBlur={() => setIsSelected(false)}
+ />
+ )
+}
+
+export const PasswordInput = ({infoButtonProps, ...props}) => {
+ const [showPassword, setShowPassword] = React.useState(false);
+ const [isSelected, setIsSelected] = React.useState(false);
+ const [input, setInput] = React.useState();
+ const [startAdornment, setStartAdornment] = React.useState()
+
+ const toggleShowPassword = () => {
+ setShowPassword((showPassword) => !showPassword);
+ }
+
+ const onChange = React.useCallback((event) => {
+ setInput(event.target.value)
+ props.onChange?.(event)
+ }, [props.onChange])
+
+ React.useEffect(() => {
+ /* Handle startAdornment */
+ let Icon = StyledLockIcon
+ let visibility = "visible"
+ if (props.error) {
+ Icon = StyledCrossIconError
+ }
+ else if (props.success) {
+ Icon = StyledCheckedIconSuccess
+ }
+ else if (!isSelected && !input) {
+ visibility = "hidden" // keep icon's space so text does not move
+ }
+ setStartAdornment(<Icon sx={{visibility}}/>)
+ }, [props.error, props.success, isSelected, input])
+
+ return (
+ <TextField
+ {...props}
+ label="Password"
+ type={showPassword ? "text" : "password"}
+ variant="standard"
+ autoComplete="current-password"
+ InputLabelProps={{ shrink: !!(isSelected || input) }}
+ onChange={onChange}
+ InputProps={{
+ startAdornment,
+ endAdornment: <Stack direction="row" spacing="14px">
+ <InfoButton {...infoButtonProps}/>
+ <ToggleVisibilityButton
+ visible={showPassword}
+ onClick={toggleShowPassword}
+ />
+ </Stack>,
+ }}
+ onFocus={() => setIsSelected(true)}
+ onBlur={() => setIsSelected(false)}
+ />
+ )
+}
+
+export const NickNameInput = (props) => {
+ const [isSelected, setIsSelected] = React.useState(false);
+ const [input, setInput] = React.useState();
+ const [startAdornmentVisibility, setStartAdornmentVisibility] = React.useState()
+
+ const onChange = React.useCallback((event) => {
+ setInput(event.target.value)
+ props.onChange?.(event)
+ }, [props.onChange])
+
+ React.useEffect(() => {
+ setStartAdornmentVisibility((isSelected || input) ? "visible" : "hidden")
+ }, [isSelected, input])
+
+ return (
+ <TextField
+ {...props}
+ label="Nickname, surname..."
+ variant="standard"
+ InputLabelProps={{ shrink: !!(isSelected || input) }}
+ onChange={onChange}
+ InputProps={{
+ startAdornment: <StyledPenIconLight sx={{visibility: startAdornmentVisibility}}/>,
+ }}
+ onFocus={() => setIsSelected(true)}
+ onBlur={() => setIsSelected(false)}
+ />
+ )
+}
+
+export const RegularInput = (props) => {
+ const [isSelected, setIsSelected] = React.useState(false);
+ const [input, setInput] = React.useState();
+ const [startAdornmentVisibility, setStartAdornmentVisibility] = React.useState()
+ const [endAdornmentVisibility, setEndAdornmentVisibility] = React.useState()
+
+ const onChange = React.useCallback((event) => {
+ setInput(event.target.value)
+ props.onChange?.(event)
+ }, [props.onChange])
+
+ React.useEffect(() => {
+ setStartAdornmentVisibility((isSelected || input) ? "visible" : "hidden")
+ setEndAdornmentVisibility((isSelected || input) ? "hidden" : "visible")
+ }, [isSelected, input])
+
+ return (
+ <TextField
+ {...props}
+ variant="standard"
+ InputLabelProps={{ shrink: !!(isSelected || input) }}
+ onChange={onChange}
+ InputProps={{
+ startAdornment: <StyledPenIconLight sx={{visibility: startAdornmentVisibility}}/>,
+ endAdornment: <StyledPenIconDark sx={{visibility: endAdornmentVisibility}}/>,
+ }}
+ onFocus={() => setIsSelected(true)}
+ onBlur={() => setIsSelected(false)}
+ />
+ )
+}
diff --git a/client/src/components/svgIcons.js b/client/src/components/svgIcons.js
index f7350b6..0fe7d85 100644
--- a/client/src/components/svgIcons.js
+++ b/client/src/components/svgIcons.js
@@ -1,9 +1,19 @@
import { SvgIcon } from "@mui/material"
+/*
+ We use SvgIcon so the icons can be handled more easily by Material ui components.
+ Here some tips to add an SvgIcon in case you too struggle to find informations online:
+ - Open the svg with https://jakearchibald.github.io/svgomg/ in order to clean it from useless information.
+ - Replace the <svg> tag for <SvgIcon>.
+ - Try removing "style" attributes. They are often uncessary and cause errors.
+ - If some "style" attributes are necessary, convert them to the React inline style syntax (https://reactjs.org/docs/dom-elements.html#style).
+ - Play with the viewBox attribute in order to center the icon and make it uses all available space. Adding a temporary border with inline style might help.
+*/
+
export const CameraIcon = (props) => {
return (
<SvgIcon {...props} viewBox="2 3 20 19">
- <path d="M3.6 20.3c-.4 0-.8-.2-1.1-.5-.2-.2-.4-.6-.4-.9V7.7c-.1-.3.1-.7.4-1 .2-.3.5-.4.8-.5H7.9l1.2-2.5h5.7L16 6.2h4.3c.4 0 .8.2 1.1.5.2.2.4.6.4.9v11.2c0 .4-.2.8-.5 1.1-.2.2-.6.4-.9.4H3.6zm0-12.6-.1 11v.1h17.1V7.7h-5.3L14 5.2h-4L8.8 7.7H3.6zm8.4 9.7c-1.2 0-2.3-.5-3.2-1.3-.8-.8-1.3-2-1.3-3.2 0-1.2.5-2.3 1.3-3.2.8-.8 2-1.3 3.2-1.3 1.2 0 2.3.5 3.2 1.3.8.8 1.3 2 1.3 3.2s-.5 2.3-1.3 3.2c-.9.8-2 1.3-3.2 1.3zm0-7.5c-.8 0-1.6.3-2.1.9S9 12.1 9 12.9s.3 1.6.9 2.1c1.1 1.1 3.1 1.1 4.3 0 .6-.6.9-1.3.9-2.1s-.3-1.6-.9-2.1c-.6-.6-1.4-.9-2.2-.9z"/>
+ <path d="M3.6 20.3c-.4 0-.8-.2-1.1-.5-.2-.2-.4-.6-.4-.9V7.7c-.1-.3.1-.7.4-1 .2-.3.5-.4.8-.5H7.9l1.2-2.5h5.7L16 6.2h4.3c.4 0 .8.2 1.1.5.2.2.4.6.4.9v11.2c0 .4-.2.8-.5 1.1-.2.2-.6.4-.9.4H3.6zm0-12.6-.1 11v.1h17.1V7.7h-5.3L14 5.2h-4L8.8 7.7H3.6zm8.4 9.7c-1.2 0-2.3-.5-3.2-1.3-.8-.8-1.3-2-1.3-3.2 0-1.2.5-2.3 1.3-3.2.8-.8 2-1.3 3.2-1.3 1.2 0 2.3.5 3.2 1.3.8.8 1.3 2 1.3 3.2s-.5 2.3-1.3 3.2c-.9.8-2 1.3-3.2 1.3zm0-7.5c-.8 0-1.6.3-2.1.9S9 12.1 9 12.9s.3 1.6.9 2.1c1.1 1.1 3.1 1.1 4.3 0 .6-.6.9-1.3.9-2.1s-.3-1.6-.9-2.1c-.6-.6-1.4-.9-2.2-.9z"/>
</SvgIcon>
)
}
@@ -16,6 +26,46 @@
)
}
+export const CheckedIcon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 16 16">
+ <path d="M11.138 5.152 6.802 9.486l-1.936-1.94a.64205296.64205296 0 0 0-.908.908l2.39 2.394a.642.642 0 0 0 .908 0l4.79-4.785a.6431145.6431145 0 0 0-.908-.911Z"/>
+ <path d="M8 16a8 8 0 1 1 8-8 8.009 8.009 0 0 1-8 8ZM8 1.284A6.716 6.716 0 1 0 14.716 8 6.723 6.723 0 0 0 8 1.284Z"/>
+ </SvgIcon>
+ )
+}
+
+export const CrossedEyeIcon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 15.931 12.145">
+ <path d="M7.933 10.41a7.081 7.081 0 0 1-3.7-1.292 12.409 12.409 0 0 1-2.874-2.717.237.237 0 0 1 0-.366 14.122 14.122 0 0 1 2.429-2.372L3 2.873a14.6 14.6 0 0 0-2.836 2.93.629.629 0 0 0 .019.87 13.62 13.62 0 0 0 4.222 3.834 7.4 7.4 0 0 0 3.547 1 7.067 7.067 0 0 0 2.948-.711l-.848-.848a5.577 5.577 0 0 1-2.119.462ZM15.74 5.784a13.154 13.154 0 0 0-4.26-3.856A7.284 7.284 0 0 0 8.145.941a6.436 6.436 0 0 0-2.892.6l.848.848a5.691 5.691 0 0 1 1.793-.348 5.788 5.788 0 0 1 2.583.617 11.437 11.437 0 0 1 3.586 2.783c.193.212.347.424.54.636a.209.209 0 0 1 .019.289 13.993 13.993 0 0 1-2.256 2.275l.79.79a14.6 14.6 0 0 0 2.6-2.737.658.658 0 0 0-.016-.91Z"/>
+ <path d="m9.687 5.974 1 1a3.349 3.349 0 0 0 .1-.752 2.867 2.867 0 0 0-2.835-2.848 2.576 2.576 0 0 0-.771.116l1.022 1.021a1.738 1.738 0 0 1 1.484 1.463ZM5.311 5.205a2.6 2.6 0 0 0-.193 1.022A2.867 2.867 0 0 0 7.971 9.06a3.005 3.005 0 0 0 1.022-.193l-.906-.906h-.135a1.749 1.749 0 0 1-1.734-1.773v-.077ZM2.882.173A.514.514 0 0 0 2.493 0a.659.659 0 0 0-.556.386.49.49 0 0 0 .135.578l11.007 11.007a.514.514 0 0 0 .386.173.659.659 0 0 0 .559-.386.49.49 0 0 0-.131-.577Z"/>
+ </SvgIcon>
+ )
+}
+
+export const CrossIcon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 16 16">
+ <path d="M8 16a8 8 0 1 1 8-8 8.009 8.009 0 0 1-8 8ZM8 .888A7.112 7.112 0 1 0 15.112 8 7.12 7.12 0 0 0 8 .888Z"/>
+ <path d="M10.837 5.167a.444.444 0 0 0-.628 0l-2.2 2.2-2.214-2.2a.44406306.44406306 0 0 0-.628.628l2.2 2.2-2.2 2.2a.44904009.44904009 0 0 0 .628.642l2.2-2.2 2.2 2.2a.4507918.4507918 0 1 0 .642-.633l-2.2-2.2 2.2-2.209a.445.445 0 0 0 0-.628Z"/>
+ </SvgIcon>
+ )
+}
+
+export const EyeIcon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 15.931 10.568">
+ <path d="M7.933 9.469a7.081 7.081 0 0 1-3.7-1.292A12.409 12.409 0 0 1 1.359 5.46a.237.237 0 0 1 0-.366c.733-.892 3.322-3.276 4.685-3.702l-.791-.79a18.682 18.682 0 0 0-5.089 4.26.629.629 0 0 0 .019.867 13.62 13.62 0 0 0 4.222 3.837 7.4 7.4 0 0 0 3.547 1 7.067 7.067 0 0 0 2.948-.711l-.847-.853a5.577 5.577 0 0 1-2.12.467Z"/>
+ <path d="M15.74 4.843A13.154 13.154 0 0 0 11.48.987 7.284 7.284 0 0 0 8.145 0a6.436 6.436 0 0 0-2.892.6l.848.848A5.691 5.691 0 0 1 7.894 1.1a5.788 5.788 0 0 1 2.583.617A11.437 11.437 0 0 1 14.063 4.5c.193.212.347.424.54.636a.209.209 0 0 1 .019.289 17.151 17.151 0 0 1-4.627 3.6l.79.79a21.4 21.4 0 0 0 4.973-4.067.658.658 0 0 0-.018-.905Z"/>
+ <g transform="translate(4.952 1.963)" style={{"stroke": "#005699", "fill": "none"}}>
+ <circle cx="3" cy="3" r="3" style={{"stroke": "none"}}/>
+ <circle cx="3" cy="3" r="2.5"/>
+ </g>
+ </SvgIcon>
+ )
+}
+
export const FolderIcon = (props) => {
return (
<SvgIcon {...props} viewBox="0 0 17.504 14.812">
@@ -24,6 +74,24 @@
)
}
+export const InfoIcon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="2 2 20 20">
+ <path d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
+ </SvgIcon>
+ )
+}
+
+export const LockIcon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 12.727 15.636">
+ <path d="M10.727 15.636H2a2 2 0 0 1-2-2V7.454a2 2 0 0 1 2-2h8.727a2 2 0 0 1 2 2v6.182a2 2 0 0 1-2 2ZM2 6.545a.91.91 0 0 0-.909.909v6.182a.91.91 0 0 0 .909.909h8.727a.908.908 0 0 0 .909-.909V7.454a.908.908 0 0 0-.909-.909Z"/>
+ <path d="M10.363 6.546h-8A.546.546 0 0 1 1.818 6V4.181a4.048 4.048 0 0 1 1.35-2.974A4.73 4.73 0 0 1 6.364 0a4.729 4.729 0 0 1 3.195 1.207 4.048 4.048 0 0 1 1.35 2.974V6a.546.546 0 0 1-.546.546Zm-4-5.455a3.645 3.645 0 0 0-2.462.923 2.918 2.918 0 0 0-.993 2.167v1.274h6.91V4.181a2.918 2.918 0 0 0-.993-2.167 3.644 3.644 0 0 0-2.461-.923ZM6.363 11.272a1.636 1.636 0 1 1 1.636-1.636 1.638 1.638 0 0 1-1.636 1.636Zm0-2.182a.545.545 0 1 0 .545.545.546.546 0 0 0-.545-.544Z"/>
+ <path d="M5.818 10.727v1.819a.5455.5455 0 1 0 1.091 0v-1.819a.5455.5455 0 0 0-1.091 0Z"/>
+ </SvgIcon>
+ )
+}
+
export const PenIcon = (props) => {
return (
<SvgIcon {...props} viewBox="0 0 14.863 14.863">
@@ -31,3 +99,13 @@
</SvgIcon>
)
}
+
+export const PersonIcon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 24 24">
+ <g stroke="#03B9E9" strokeWidth="1.75" fill="none" fillRule="evenodd" strokeLinejoin="round">
+ <path d="M17 6.5c0 2.48522308-2.0147769 4.5-4.5 4.5C10.01477692 11 8 8.98522308 8 6.5 8 4.0147769 10.01477692 2 12.5 2 14.9852231 2 17 4.0147769 17 6.5ZM3 22c0-5.5228267 4.02947764-10 9.00005436-10C16.9705224 12 21 16.4771733 21 22"/>
+ </g>
+ </SvgIcon>
+ )
+}
diff --git a/client/src/themes/default.js b/client/src/themes/default.js
index 4de4f94..1e4f0ac 100644
--- a/client/src/themes/default.js
+++ b/client/src/themes/default.js
@@ -69,6 +69,7 @@
fontSize: "15px",
lineHeight: "17px",
textTransform: "none",
+ borderRadius: "5px",
},
sizeSmall: {
height: "36px",
@@ -140,6 +141,70 @@
},
},
},
+ MuiInput: {
+ styleOverrides: {
+ root: {
+ color: theme.palette.primary.dark,
+ "&.Mui-error": {
+ color: theme.palette.error.main,
+ },
+ },
+ underline: {
+ /*
+ Material UI uses "before" for the regular underline.
+ There is a second underline called "after" placed over "before"
+ */
+ "&:before": {
+ borderBottom: `2px solid ${theme.palette.primary.dark}`,
+ },
+ "&:hover:not(.Mui-disabled, .Mui-error):before": {
+ borderBottom: "2px solid #03B9E9",
+ },
+ "&:after": {
+ borderBottom: "2px solid #03B9E9",
+ },
+ "&:hover:not(.Mui-error):after": {
+ borderBottom: "2px solid #03B9E9",
+ },
+ },
+ }
+ },
+ MuiFormControl: {
+ styleOverrides: {
+ root: {
+ height: "90px",
+ },
+ },
+ },
+ MuiFormHelperText: {
+ styleOverrides: {
+ root: {
+ position: "absolute",
+ bottom: "0px",
+ fontSize: "15px",
+ },
+ },
+ },
+ MuiInputLabel: {
+ styleOverrides: {
+ root: {
+ color: "black",
+ fontSize: "15px",
+ left: "50%",
+ transform: "translate(-50%, 20px)",
+ transition: "left .2s, transform .2s",
+ },
+ shrink: {
+ color: "black",
+ left: 0,
+ transform: "translate(0, 50px) scale(0.75)",
+ transition: "left .2s, transform .2s",
+ "&.Mui-focused, &.Mui-error": {
+ color: "black",
+ },
+ },
+ },
+ },
MuiCssBaseline: {
styleOverrides: `
@font-face {