blob: 47ad45b1f2023965ee8ded5ef1a53498ceb7bb8f [file] [log] [blame]
simon26e79f72022-10-05 22:16:08 -04001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
simon35378692022-10-02 23:25:57 -040018import { IconButtonProps, Stack, TextField, TextFieldProps } from '@mui/material';
simond47ef9e2022-09-28 22:24:28 -040019import { styled } from '@mui/material/styles';
simon35378692022-10-02 23:25:57 -040020import { ChangeEvent, ReactElement, useCallback, useEffect, useState } from 'react';
simon07b4eb02022-09-29 17:50:26 -040021
simon35378692022-10-02 23:25:57 -040022import { InfoButton, ToggleVisibilityButton } from './Button';
23import { CheckedIcon, LockIcon, PenIcon, PersonIcon, RoundSaltireIcon } from './SvgIcon';
idillon-sfl37c18df2022-08-26 18:44:27 -040024
simond47ef9e2022-09-28 22:24:28 -040025const iconsHeight = '16px';
26const StyledCheckedIconSuccess = styled(CheckedIcon)(({ theme }) => ({
27 height: iconsHeight,
28 color: theme.palette.success.main,
29}));
30const StyledRoundSaltireIconError = styled(RoundSaltireIcon)(({ theme }) => ({
31 height: iconsHeight,
32 color: theme.palette.error.main,
33}));
34const StyledPenIconLight = styled(PenIcon)({ height: iconsHeight, color: '#03B9E9' });
35const StyledPenIconDark = styled(PenIcon)(({ theme }) => ({ height: iconsHeight, color: theme.palette.primary.dark }));
36const StyledPersonIconLight = styled(PersonIcon)({ height: iconsHeight, color: '#03B9E9' });
37const StyledLockIcon = styled(LockIcon)({ height: iconsHeight, color: '#03B9E9' });
idillon-sfl37c18df2022-08-26 18:44:27 -040038
simon35378692022-10-02 23:25:57 -040039type InputProps = TextFieldProps & {
40 infoButtonProps?: IconButtonProps;
41 success?: boolean;
42};
43
44export const UsernameInput = ({ infoButtonProps, onChange: _onChange, ...props }: InputProps) => {
simond47ef9e2022-09-28 22:24:28 -040045 const [isSelected, setIsSelected] = useState(false);
46 const [input, setInput] = useState(props.defaultValue);
simon35378692022-10-02 23:25:57 -040047 const [startAdornment, setStartAdornment] = useState<ReactElement | undefined>();
idillon-sfl37c18df2022-08-26 18:44:27 -040048
simond47ef9e2022-09-28 22:24:28 -040049 const onChange = useCallback(
simon35378692022-10-02 23:25:57 -040050 (event: ChangeEvent<HTMLInputElement>) => {
simond47ef9e2022-09-28 22:24:28 -040051 setInput(event.target.value);
simon80b7b3b2022-09-28 17:50:10 -040052 _onChange?.(event);
simond47ef9e2022-09-28 22:24:28 -040053 },
simon80b7b3b2022-09-28 17:50:10 -040054 [_onChange]
simond47ef9e2022-09-28 22:24:28 -040055 );
idillon-sfl37c18df2022-08-26 18:44:27 -040056
simond47ef9e2022-09-28 22:24:28 -040057 useEffect(() => {
58 /* Handle startAdornment */
59 let Icon = StyledPersonIconLight;
60 let visibility = 'visible';
61 if (props.error) {
62 Icon = StyledRoundSaltireIconError;
63 } else if (props.success) {
64 Icon = StyledCheckedIconSuccess;
65 } else if (!isSelected && !input) {
66 visibility = 'hidden'; // keep icon's space so text does not move
idillon-sfl37c18df2022-08-26 18:44:27 -040067 }
simond47ef9e2022-09-28 22:24:28 -040068 setStartAdornment(<Icon sx={{ visibility }} />);
69 }, [props.error, props.success, isSelected, input]);
idillon-sfl37c18df2022-08-26 18:44:27 -040070
simond47ef9e2022-09-28 22:24:28 -040071 return (
72 <TextField
73 {...props}
74 label="Choose an identifier"
75 variant="standard"
76 InputLabelProps={{ shrink: !!(isSelected || input) }}
77 onChange={onChange}
78 InputProps={{
79 startAdornment,
80 endAdornment: <InfoButton {...infoButtonProps} />,
81 }}
82 onFocus={() => setIsSelected(true)}
83 onBlur={() => setIsSelected(false)}
84 />
85 );
86};
idillon-sfl37c18df2022-08-26 18:44:27 -040087
simon35378692022-10-02 23:25:57 -040088export const PasswordInput = ({ infoButtonProps, onChange: _onChange, ...props }: InputProps) => {
simond47ef9e2022-09-28 22:24:28 -040089 const [showPassword, setShowPassword] = useState(false);
90 const [isSelected, setIsSelected] = useState(false);
91 const [input, setInput] = useState(props.defaultValue);
simon35378692022-10-02 23:25:57 -040092 const [startAdornment, setStartAdornment] = useState<ReactElement | undefined>();
idillon-sfl37c18df2022-08-26 18:44:27 -040093
simond47ef9e2022-09-28 22:24:28 -040094 const toggleShowPassword = () => {
95 setShowPassword((showPassword) => !showPassword);
96 };
97
98 const onChange = useCallback(
simon35378692022-10-02 23:25:57 -040099 (event: ChangeEvent<HTMLInputElement>) => {
simond47ef9e2022-09-28 22:24:28 -0400100 setInput(event.target.value);
simon80b7b3b2022-09-28 17:50:10 -0400101 _onChange?.(event);
simond47ef9e2022-09-28 22:24:28 -0400102 },
simon80b7b3b2022-09-28 17:50:10 -0400103 [_onChange]
simond47ef9e2022-09-28 22:24:28 -0400104 );
105
106 useEffect(() => {
107 /* Handle startAdornment */
108 let Icon = StyledLockIcon;
109 let visibility = 'visible';
110 if (props.error) {
111 Icon = StyledRoundSaltireIconError;
112 } else if (props.success) {
113 Icon = StyledCheckedIconSuccess;
114 } else if (!isSelected && !input) {
115 visibility = 'hidden'; // keep icon's space so text does not move
116 }
117 setStartAdornment(<Icon sx={{ visibility }} />);
118 }, [props.error, props.success, isSelected, input]);
119
120 return (
121 <TextField
122 {...props}
123 label="Password"
124 type={showPassword ? 'text' : 'password'}
125 variant="standard"
126 autoComplete="current-password"
127 InputLabelProps={{ shrink: !!(isSelected || input) }}
128 onChange={onChange}
129 InputProps={{
130 startAdornment,
131 endAdornment: (
132 <Stack direction="row" spacing="14px">
133 <InfoButton {...infoButtonProps} />
134 <ToggleVisibilityButton visible={showPassword} onClick={toggleShowPassword} />
135 </Stack>
136 ),
137 }}
138 onFocus={() => setIsSelected(true)}
139 onBlur={() => setIsSelected(false)}
140 />
141 );
142};
idillon-sfl37c18df2022-08-26 18:44:27 -0400143
simon35378692022-10-02 23:25:57 -0400144export const NickNameInput = ({ onChange: _onChange, ...props }: TextFieldProps) => {
simond47ef9e2022-09-28 22:24:28 -0400145 const [isSelected, setIsSelected] = useState(false);
146 const [input, setInput] = useState(props.defaultValue);
simon35378692022-10-02 23:25:57 -0400147 const [startAdornmentVisibility, setStartAdornmentVisibility] = useState<'visible' | 'hidden'>('hidden');
idillon-sfl37c18df2022-08-26 18:44:27 -0400148
simond47ef9e2022-09-28 22:24:28 -0400149 const onChange = useCallback(
simon35378692022-10-02 23:25:57 -0400150 (event: ChangeEvent<HTMLInputElement>) => {
simond47ef9e2022-09-28 22:24:28 -0400151 setInput(event.target.value);
simon80b7b3b2022-09-28 17:50:10 -0400152 _onChange?.(event);
simond47ef9e2022-09-28 22:24:28 -0400153 },
simon80b7b3b2022-09-28 17:50:10 -0400154 [_onChange]
simond47ef9e2022-09-28 22:24:28 -0400155 );
idillon-sfl37c18df2022-08-26 18:44:27 -0400156
simond47ef9e2022-09-28 22:24:28 -0400157 useEffect(() => {
158 setStartAdornmentVisibility(isSelected || input ? 'visible' : 'hidden');
159 }, [isSelected, input]);
idillon-sfl37c18df2022-08-26 18:44:27 -0400160
simond47ef9e2022-09-28 22:24:28 -0400161 return (
162 <TextField
163 {...props}
164 label="Nickname, surname..."
165 variant="standard"
166 InputLabelProps={{ shrink: !!(isSelected || input) }}
167 onChange={onChange}
168 InputProps={{
169 startAdornment: <StyledPenIconLight sx={{ visibility: startAdornmentVisibility }} />,
170 }}
171 onFocus={() => setIsSelected(true)}
172 onBlur={() => setIsSelected(false)}
173 />
174 );
175};
idillon-sfl37c18df2022-08-26 18:44:27 -0400176
simon35378692022-10-02 23:25:57 -0400177export const RegularInput = ({ onChange: _onChange, ...props }: TextFieldProps) => {
simond47ef9e2022-09-28 22:24:28 -0400178 const [isSelected, setIsSelected] = useState(false);
179 const [input, setInput] = useState(props.defaultValue);
simon35378692022-10-02 23:25:57 -0400180 const [startAdornmentVisibility, setStartAdornmentVisibility] = useState<'visible' | 'hidden'>('hidden');
181 const [endAdornmentVisibility, setEndAdornmentVisibility] = useState<'visible' | 'hidden'>('visible');
idillon-sfl37c18df2022-08-26 18:44:27 -0400182
simond47ef9e2022-09-28 22:24:28 -0400183 const onChange = useCallback(
simon35378692022-10-02 23:25:57 -0400184 (event: ChangeEvent<HTMLInputElement>) => {
simond47ef9e2022-09-28 22:24:28 -0400185 setInput(event.target.value);
simon80b7b3b2022-09-28 17:50:10 -0400186 _onChange?.(event);
simond47ef9e2022-09-28 22:24:28 -0400187 },
simon80b7b3b2022-09-28 17:50:10 -0400188 [_onChange]
simond47ef9e2022-09-28 22:24:28 -0400189 );
idillon-sfl37c18df2022-08-26 18:44:27 -0400190
simond47ef9e2022-09-28 22:24:28 -0400191 useEffect(() => {
192 setStartAdornmentVisibility(isSelected || input ? 'visible' : 'hidden');
193 setEndAdornmentVisibility(isSelected || input ? 'hidden' : 'visible');
194 }, [isSelected, input]);
idillon-sfl37c18df2022-08-26 18:44:27 -0400195
simond47ef9e2022-09-28 22:24:28 -0400196 return (
197 <TextField
198 {...props}
199 variant="standard"
200 InputLabelProps={{ shrink: !!(isSelected || input) }}
201 onChange={onChange}
202 InputProps={{
203 startAdornment: <StyledPenIconLight sx={{ visibility: startAdornmentVisibility }} />,
204 endAdornment: <StyledPenIconDark sx={{ visibility: endAdornmentVisibility }} />,
205 }}
206 onFocus={() => setIsSelected(true)}
207 onBlur={() => setIsSelected(false)}
208 />
209 );
210};