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/Button.tsx b/client/src/components/Button.tsx
index 25ddf39..0d401a2 100644
--- a/client/src/components/Button.tsx
+++ b/client/src/components/Button.tsx
@@ -49,6 +49,7 @@
VideoCameraIcon,
VolumeIcon,
} from './SvgIcon';
+import CustomTooltip from './Tooltip';
type ShapedButtonProps = IconButtonProps & {
Icon: ComponentType<SvgIconProps>;
@@ -99,8 +100,15 @@
return <RoundButton {...props} aria-label="take picture" Icon={CameraIcon} size="large" />;
};
-export const InfoButton = (props: IconButtonProps) => {
- return <RoundButton {...props} aria-label="informations" Icon={InfoIcon} size="small" />;
+type InfoButtonProps = IconButtonProps & {
+ tooltipTitle: string;
+};
+export const InfoButton = ({ tooltipTitle, ...props }: InfoButtonProps) => {
+ return (
+ <CustomTooltip className="tooltip" title={tooltipTitle}>
+ <RoundButton {...props} aria-label="informations" Icon={InfoIcon} size="small" />
+ </CustomTooltip>
+ );
};
export const TipButton = (props: IconButtonProps) => {
diff --git a/client/src/components/Copyright.tsx b/client/src/components/Copyright.tsx
new file mode 100644
index 0000000..a89855f
--- /dev/null
+++ b/client/src/components/Copyright.tsx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { Link } from '@mui/material';
+import Typography from '@mui/material/Typography';
+
+// TODO: Choose on which pages to put this.
+export default function Copyright() {
+ return (
+ <Typography variant="body2" color="textSecondary" align="center" sx={{ position: 'absolute', bottom: 0, left: 0 }}>
+ {'Copyright © 2016-'}
+ {new Date().getFullYear()}
+ {' Savoir-faire Linux Inc.'}
+ <Link color="inherit" href="https://jami.net/">
+ Jami.net
+ </Link>{' '}
+ {'.'}
+ </Typography>
+ );
+}
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>
+ );
+};
diff --git a/client/src/components/JamiWelcomeLogo.tsx b/client/src/components/JamiWelcomeLogo.tsx
new file mode 100644
index 0000000..8002aab
--- /dev/null
+++ b/client/src/components/JamiWelcomeLogo.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { Box, SxProps, Typography } from '@mui/material';
+
+import { ReactComponent as JamiLogo } from '../icons/jami-logo-icon.svg';
+import { jamiLogoDefaultSize } from '../utils/constants';
+
+interface WelcomeLogoProps {
+ logoWidth?: string;
+ logoHeight?: string;
+ boxSxProps?: SxProps;
+}
+
+export default function JamiWelcomeLogo(props: WelcomeLogoProps) {
+ return (
+ <Box
+ sx={{
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ textAlign: 'center',
+ flexDirection: 'column',
+ ...props.boxSxProps,
+ }}
+ >
+ <JamiLogo width={props.logoWidth ?? jamiLogoDefaultSize} height={props.logoHeight ?? jamiLogoDefaultSize} />
+ <Typography variant="h1">Welcome to Jami!</Typography>
+ </Box>
+ );
+}
diff --git a/client/src/components/ProcessingRequest.tsx b/client/src/components/ProcessingRequest.tsx
new file mode 100644
index 0000000..c7da9a4
--- /dev/null
+++ b/client/src/components/ProcessingRequest.tsx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import Backdrop from '@mui/material/Backdrop';
+import CircularProgress from '@mui/material/CircularProgress';
+
+export default function ProcessingRequest(props: { open: boolean }) {
+ return (
+ <Backdrop sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }} open={props.open} onClick={() => {}}>
+ <CircularProgress color="inherit" />
+ </Backdrop>
+ );
+}
diff --git a/client/src/components/RulesDialog.tsx b/client/src/components/RulesDialog.tsx
new file mode 100644
index 0000000..5e1e929
--- /dev/null
+++ b/client/src/components/RulesDialog.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
+
+interface RulesDialogProps {
+ openDialog: boolean;
+ title: string;
+ closeDialog: () => void;
+ children: React.ReactNode;
+}
+
+export default function RulesDialog(props: RulesDialogProps) {
+ return (
+ <Dialog open={props.openDialog} onClose={props.closeDialog}>
+ <DialogTitle>
+ {props.title}
+ <br />
+ </DialogTitle>
+ <DialogContent>{props.children}</DialogContent>
+ <DialogActions>
+ <Button onClick={props.closeDialog} autoFocus>
+ Close
+ </Button>
+ </DialogActions>
+ </Dialog>
+ );
+}
diff --git a/client/src/components/Tooltip.tsx b/client/src/components/Tooltip.tsx
new file mode 100644
index 0000000..a6ea84b
--- /dev/null
+++ b/client/src/components/Tooltip.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { Stack } from '@mui/material';
+import { styled } from '@mui/material/styles';
+import Tooltip, { tooltipClasses, TooltipProps } from '@mui/material/Tooltip';
+
+export default styled(({ className, title, ...props }: TooltipProps) => (
+ <Tooltip {...props} classes={{ popper: className }} title={<span style={{ whiteSpace: 'pre-line' }}>{title}</span>}>
+ <Stack>{props.children}</Stack>
+ </Tooltip>
+))(({ theme }) => ({
+ [`& .${tooltipClasses.tooltip}`]: {
+ backgroundColor: theme.InfoTooltip.backgroundColor.main,
+ color: theme.InfoTooltip.color.main,
+ maxWidth: 220,
+ fontSize: theme.typography.pxToRem(15),
+ boxShadow: '3px 3px 7px #00000029',
+ borderRadius: '5px',
+ },
+}));
diff --git a/client/src/components/welcome.jsx b/client/src/components/welcome.jsx
index 39e222b..9f3978d 100644
--- a/client/src/components/welcome.jsx
+++ b/client/src/components/welcome.jsx
@@ -15,10 +15,12 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
-import { Button, Container } from '@mui/material';
+import { Container } from '@mui/material';
import { AnimatePresence, motion } from 'framer-motion';
import { useState } from 'react';
+import { ReactComponent as JamiLogo } from '../icons/jami-logo-icon.svg';
+
const list = {
hidden: { opacity: 0 },
visible: {
@@ -49,29 +51,15 @@
onAnimationComplete={(a) => {
if (a === 'hidden') {
props.onComplete();
- } else if (!props.showSetup) {
+ } else {
setVisible(false);
}
}}
>
<motion.div variants={item}>
- <img
- src="/jami-logo-icon.svg"
- style={{
- width: '32',
- height: '32',
- }}
- alt="jami n/logo"
- />
+ <JamiLogo width="95%" />
</motion.div>
<motion.h1 variants={item}>Welcome to Jami</motion.h1>
- {props.showSetup && (
- <motion.div variants={item}>
- <Button variant="outlined" onClick={() => setVisible(false)}>
- Start setup
- </Button>
- </motion.div>
- )}
</motion.div>
)}
</AnimatePresence>
diff --git a/client/public/jami-logo-icon.svg b/client/src/icons/jami-logo-icon.svg
similarity index 100%
rename from client/public/jami-logo-icon.svg
rename to client/src/icons/jami-logo-icon.svg
diff --git a/client/src/pages/LoginDialog.tsx b/client/src/pages/LoginDialog.tsx
index fb01ccd..ee990bc 100644
--- a/client/src/pages/LoginDialog.tsx
+++ b/client/src/pages/LoginDialog.tsx
@@ -22,27 +22,11 @@
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import FormControlLabel from '@mui/material/FormControlLabel';
-import Link from '@mui/material/Link';
import TextField from '@mui/material/TextField';
-import Typography from '@mui/material/Typography';
import { ChangeEvent, Component, MouseEvent } from 'react';
import authManager from '../AuthManager';
-function Copyright() {
- return (
- <Typography variant="body2" color="textSecondary" align="center">
- {'Copyright © 2016-'}
- {new Date().getFullYear()}
- {' Savoir-faire Linux Inc.'}
- <Link color="inherit" href="https://jami.net/">
- Jami.net
- </Link>{' '}
- {'.'}
- </Typography>
- );
-}
-
type SignInPageProps = {
open: boolean;
};
diff --git a/client/src/themes/Default.ts b/client/src/themes/Default.ts
index 3ba20ab..c0f5884 100644
--- a/client/src/themes/Default.ts
+++ b/client/src/themes/Default.ts
@@ -15,7 +15,23 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
-import { createTheme } from '@mui/material';
+import { createTheme, SimplePaletteColorOptions } from '@mui/material';
+
+declare module '@mui/material/styles' {
+ interface Theme {
+ InfoTooltip: {
+ backgroundColor: SimplePaletteColorOptions;
+ color: SimplePaletteColorOptions;
+ };
+ }
+
+ interface ThemeOptions {
+ InfoTooltip: {
+ backgroundColor: SimplePaletteColorOptions;
+ color: SimplePaletteColorOptions;
+ };
+ }
+}
const theme = createTheme({
palette: {
@@ -31,6 +47,14 @@
main: '#009980',
},
},
+ InfoTooltip: {
+ backgroundColor: {
+ main: '#FFFFFF',
+ },
+ color: {
+ main: '#292929',
+ },
+ },
typography: {
fontFamily: 'Ubuntu',
allVariants: {
diff --git a/client/src/themes/ThemeDemonstrator.tsx b/client/src/themes/ThemeDemonstrator.tsx
index 8b3ceb1..dd82953 100644
--- a/client/src/themes/ThemeDemonstrator.tsx
+++ b/client/src/themes/ThemeDemonstrator.tsx
@@ -59,7 +59,7 @@
<TakePictureButton />
</Stack>
<Stack direction="row" spacing="5px">
- <InfoButton />
+ <InfoButton tooltipTitle={''} />
<TipButton />
</Stack>
<Stack direction="row" spacing="5px">
@@ -70,10 +70,10 @@
<Switch />
</Stack>
<Stack padding="5px" width="300px">
- <UsernameInput />
- <UsernameInput error defaultValue="Cyrille" />
- <PasswordInput />
- <PasswordInput error defaultValue="SavoirFaireLinux" />
+ <UsernameInput onChange={() => {}} tooltipTitle={'Test'} />
+ <UsernameInput onChange={() => {}} tooltipTitle={'Test'} error defaultValue="Cyrille" />
+ <PasswordInput onChange={() => {}} tooltipTitle={'Test'} />
+ <PasswordInput onChange={() => {}} tooltipTitle={'Test'} error defaultValue="SavoirFaireLinux" />
<NickNameInput />
<RegularInput />
</Stack>