Add helper components for UI
Changes:
- Extracted Copyright from LoginDialog file to its own
- Added some helper components
- Removed unnecessary setup from welcome animation
- Added custom React type for svg element
- Fixed ThemeDemonstrator
GitLab: #12
Change-Id: Ie7158520983dab5c7069f179b6f9531b5106ba85
diff --git a/client/src/components/Input.tsx b/client/src/components/Input.tsx
index 47ad45b..e867e26 100644
--- a/client/src/components/Input.tsx
+++ b/client/src/components/Input.tsx
@@ -15,11 +15,22 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
-import { IconButtonProps, Stack, TextField, TextFieldProps } from '@mui/material';
+import { GppMaybe, Warning } from '@mui/icons-material';
+import {
+ IconButtonProps,
+ List,
+ ListItem,
+ ListItemIcon,
+ Stack,
+ TextField,
+ TextFieldProps,
+ Typography,
+} from '@mui/material';
import { styled } from '@mui/material/styles';
import { ChangeEvent, ReactElement, useCallback, useEffect, useState } from 'react';
import { InfoButton, ToggleVisibilityButton } from './Button';
+import RulesDialog from './RulesDialog';
import { CheckedIcon, LockIcon, PenIcon, PersonIcon, RoundSaltireIcon } from './SvgIcon';
const iconsHeight = '16px';
@@ -36,15 +47,17 @@
const StyledPersonIconLight = styled(PersonIcon)({ height: iconsHeight, color: '#03B9E9' });
const StyledLockIcon = styled(LockIcon)({ height: iconsHeight, color: '#03B9E9' });
-type InputProps = TextFieldProps & {
+export type InputProps = TextFieldProps & {
infoButtonProps?: IconButtonProps;
success?: boolean;
+ tooltipTitle: string;
};
-export const UsernameInput = ({ infoButtonProps, onChange: _onChange, ...props }: InputProps) => {
+export const UsernameInput = ({ infoButtonProps, onChange: _onChange, tooltipTitle, ...props }: InputProps) => {
const [isSelected, setIsSelected] = useState(false);
const [input, setInput] = useState(props.defaultValue);
const [startAdornment, setStartAdornment] = useState<ReactElement | undefined>();
+ const [isDialogOpened, setIsDialogOpened] = useState<boolean>(false);
const onChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
@@ -69,27 +82,35 @@
}, [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)}
- />
+ <>
+ <RulesDialog openDialog={isDialogOpened} title="Username rules :" closeDialog={() => setIsDialogOpened(false)}>
+ <UsernameRules />
+ </RulesDialog>
+ <TextField
+ {...props}
+ label={'Choose an identifier'}
+ variant="standard"
+ InputLabelProps={{ shrink: !!(isSelected || input) }}
+ onChange={onChange}
+ InputProps={{
+ startAdornment,
+ endAdornment: (
+ <InfoButton tooltipTitle={tooltipTitle} {...infoButtonProps} onClick={() => setIsDialogOpened(true)} />
+ ),
+ }}
+ onFocus={() => setIsSelected(true)}
+ onBlur={() => setIsSelected(false)}
+ />
+ </>
);
};
-export const PasswordInput = ({ infoButtonProps, onChange: _onChange, ...props }: InputProps) => {
+export const PasswordInput = ({ infoButtonProps, onChange: _onChange, tooltipTitle, ...props }: InputProps) => {
const [showPassword, setShowPassword] = useState(false);
const [isSelected, setIsSelected] = useState(false);
const [input, setInput] = useState(props.defaultValue);
const [startAdornment, setStartAdornment] = useState<ReactElement | undefined>();
+ const [isDialogOpened, setIsDialogOpened] = useState<boolean>(false);
const toggleShowPassword = () => {
setShowPassword((showPassword) => !showPassword);
@@ -118,26 +139,31 @@
}, [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)}
- />
+ <>
+ <RulesDialog openDialog={isDialogOpened} title="Password rules :" closeDialog={() => setIsDialogOpened(false)}>
+ <PasswordRules />
+ </RulesDialog>
+ <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" alignItems="center">
+ <InfoButton tooltipTitle={tooltipTitle} {...infoButtonProps} onClick={() => setIsDialogOpened(true)} />
+ <ToggleVisibilityButton visible={showPassword} onClick={toggleShowPassword} />
+ </Stack>
+ ),
+ }}
+ onFocus={() => setIsSelected(true)}
+ onBlur={() => setIsSelected(false)}
+ />
+ </>
);
};
@@ -208,3 +234,82 @@
/>
);
};
+
+export function inputColor(
+ error?: boolean,
+ success?: boolean
+): 'success' | 'error' | 'primary' | 'secondary' | 'info' | 'warning' | undefined {
+ return error ? 'error' : success ? 'success' : 'primary';
+}
+
+const PasswordRules = () => {
+ return (
+ <Typography variant="body1">
+ <List>
+ <ListItem>
+ <ListItemIcon>
+ <GppMaybe />
+ </ListItemIcon>
+ The password must contain at least 1 lowercase alphabetical character.
+ </ListItem>
+ <ListItem>
+ <ListItemIcon>
+ <GppMaybe />
+ </ListItemIcon>
+ The password must contain at least 1 uppercase alphabetical character.
+ </ListItem>
+ <ListItem>
+ <ListItemIcon>
+ <GppMaybe />
+ </ListItemIcon>
+ The password must contain at least 1 numeric character.
+ </ListItem>
+ <ListItem>
+ <ListItemIcon>
+ <GppMaybe />
+ </ListItemIcon>
+ The password must contain at least one special character.
+ </ListItem>
+ <ListItem>
+ <ListItemIcon>
+ <GppMaybe />
+ </ListItemIcon>
+ The password must be eight characters or longer for Strong strength.
+ </ListItem>
+ </List>
+ </Typography>
+ );
+};
+
+const UsernameRules = () => {
+ return (
+ <Typography variant="body1">
+ <List>
+ <ListItem>
+ <ListItemIcon>
+ <Warning />
+ </ListItemIcon>
+ The username must be 3 to 32 characters long.
+ </ListItem>
+ <ListItem>
+ <ListItemIcon>
+ <Warning />
+ </ListItemIcon>
+ The username may contain lowercase and uppercase alphabetical characters.
+ </ListItem>
+ <ListItem>
+ <ListItemIcon>
+ <Warning />
+ </ListItemIcon>
+ The username may contain hyphens {'(-)'}.
+ </ListItem>
+ <ListItem>
+ <ListItemIcon>
+ <Warning />
+ </ListItemIcon>
+ The username may contain underscores {'(_)'}.
+ </ListItem>
+ </List>
+ </Typography>
+ );
+};