Skip to content

Commit

Permalink
hidden properties
Browse files Browse the repository at this point in the history
password reveal icon
pass type
resolves #1395
  • Loading branch information
Fred Lefévère-Laoide authored and Fred Lefévère-Laoide committed Jul 9, 2024
1 parent 52cb9bb commit ccc70a1
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 98 deletions.
155 changes: 98 additions & 57 deletions frontend/taipy-gui/src/components/Taipy/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
* specific language governing permissions and limitations under the License.
*/

import React, { useState, useEffect, useCallback, useRef, KeyboardEvent, useMemo } from "react";
import React, { useState, useEffect, useCallback, useRef, KeyboardEvent, useMemo, CSSProperties } from "react";
import IconButton from "@mui/material/IconButton";
import InputAdornment from "@mui/material/InputAdornment";
import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import IconButton from "@mui/material/IconButton";
import Visibility from "@mui/icons-material/Visibility";
import VisibilityOff from "@mui/icons-material/VisibilityOff";
import ArrowDropUpIcon from "@mui/icons-material/ArrowDropUp";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";

Expand All @@ -36,6 +39,20 @@ const getActionKeys = (keys?: string): string[] => {
return ak.length > 0 ? ak : [AUTHORIZED_KEYS[0]];
};

const numberSx = {
"& input[type=number]::-webkit-outer-spin-button, & input[type=number]::-webkit-inner-spin-button": {
display: "none",
},
"& input[type=number]": {
MozAppearance: "textfield",
},
};
const verticalDivStyle: CSSProperties = {
display: "flex",
flexDirection: "column",
gap: 0,
};

const Input = (props: TaipyInputProps) => {
const {
type,
Expand All @@ -48,6 +65,7 @@ const Input = (props: TaipyInputProps) => {
multiline = false,
linesShown = 5,
} = props;

const [value, setValue] = useState(defaultValue);
const dispatch = useDispatch();
const delayCall = useRef(-1);
Expand Down Expand Up @@ -81,7 +99,7 @@ const Input = (props: TaipyInputProps) => {
}, changeDelay);
}
},
[updateVarName, dispatch, propagate, onChange, changeDelay, module],
[updateVarName, dispatch, propagate, onChange, changeDelay, module]
);

const handleAction = useCallback(
Expand Down Expand Up @@ -134,7 +152,7 @@ const Input = (props: TaipyInputProps) => {
updateVarName,
onChange,
propagate,
],
]
);

const roundBasedOnStep = useMemo(() => {
Expand All @@ -160,7 +178,7 @@ const Input = (props: TaipyInputProps) => {
step || 1,
stepMultiplier || 10,
event.shiftKey,
increment,
increment
);
if (min !== undefined && Number(newValue) < min) {
return min.toString();
Expand All @@ -171,21 +189,89 @@ const Input = (props: TaipyInputProps) => {
return newValue;
});
},
[min, max, step, stepMultiplier, calculateNewValue],
[min, max, step, stepMultiplier, calculateNewValue]
);

const handleUpStepperMouseDown = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
handleStepperMouseDown(event, true);
},
[handleStepperMouseDown],
[handleStepperMouseDown]
);

const handleDownStepperMouseDown = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
handleStepperMouseDown(event, false);
},
[handleStepperMouseDown],
[handleStepperMouseDown]
);

// password
const [showPassword, setShowPassword] = useState(false);
const handleClickShowPassword = useCallback(() => setShowPassword((show) => !show), []);
const handleMouseDownPassword = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => event.preventDefault(),
[]
);
const muiInputProps = useMemo(
() =>
type == "password"
? {
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}
: type == "number"
? {
endAdornment: (
<div style={verticalDivStyle}>
<IconButton
data-testid="stepper-up-spinner"
size="small"
onMouseDown={handleUpStepperMouseDown}
>
<ArrowDropUpIcon fontSize="inherit" />
</IconButton>
<IconButton
data-testid="stepper-down-spinner"
size="small"
onMouseDown={handleDownStepperMouseDown}
>
<ArrowDropDownIcon fontSize="inherit" />
</IconButton>
</div>
),
}
: undefined,
[
type,
showPassword,
handleClickShowPassword,
handleMouseDownPassword,
handleUpStepperMouseDown,
handleDownStepperMouseDown,
]
);

const inputProps = useMemo(
() =>
type == "number"
? {
step: step ? step : 1,
min: min,
max: max,
}
: undefined,
[type, step, min, max]
);

useEffect(() => {
Expand All @@ -197,60 +283,15 @@ const Input = (props: TaipyInputProps) => {
return (
<Tooltip title={hover || ""}>
<TextField
sx={{
"& input[type=number]::-webkit-outer-spin-button, & input[type=number]::-webkit-inner-spin-button":
{
display: "none",
},
"& input[type=number]": {
MozAppearance: "textfield",
},
}}
sx={numberSx}
margin="dense"
hiddenLabel
value={value ?? ""}
className={className}
type={type}
type={showPassword && type == "password" ? "text" : type}
id={id}
inputProps={
type !== "text"
? {
step: step ? step : 1,
min: min,
max: max,
}
: {}
}
InputProps={
type !== "text"
? {
endAdornment: (
<div
style={{
display: "flex",
flexDirection: "column",
gap: 0,
}}
>
<IconButton
data-testid="stepper-up-spinner"
size="small"
onMouseDown={handleUpStepperMouseDown}
>
<ArrowDropUpIcon fontSize="inherit" />
</IconButton>
<IconButton
data-testid="stepper-down-spinner"
size="small"
onMouseDown={handleDownStepperMouseDown}
>
<ArrowDropDownIcon fontSize="inherit" />
</IconButton>
</div>
),
}
: {}
}
inputProps={inputProps}
InputProps={muiInputProps}
label={props.label}
onChange={handleInput}
disabled={!active}
Expand Down
77 changes: 43 additions & 34 deletions frontend/taipy-gui/src/components/Taipy/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,15 @@
* specific language governing permissions and limitations under the License.
*/

import React, { ChangeEvent, KeyboardEvent, MouseEvent, useCallback, useEffect, useState } from "react";
import React, { ChangeEvent, KeyboardEvent, MouseEvent, useCallback, useEffect, useMemo, useState } from "react";
import Button from "@mui/material/Button";
import CircularProgress from "@mui/material/CircularProgress";
import DialogTitle from "@mui/material/DialogTitle";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import FormControl from "@mui/material/FormControl";
import InputAdornment from "@mui/material/InputAdornment";
import InputLabel from "@mui/material/InputLabel";
import OutlinedInput from "@mui/material/OutlinedInput";
import TextField from "@mui/material/TextField";
import IconButton from "@mui/material/IconButton";
import CloseIcon from "@mui/icons-material/Close";
Expand Down Expand Up @@ -51,6 +48,8 @@ const closeSx: SxProps<Theme> = {
alignSelf: "start",
};
const titleSx = { m: 0, p: 2, display: "flex", paddingRight: "0.1em" };
const userProps = { autocomplete: "username" };
const pwdProps = { autocomplete: "current-password" };

const Login = (props: LoginProps) => {
const { id, title = "Log-in", onAction = "on_login", message, defaultMessage } = props;
Expand Down Expand Up @@ -81,13 +80,6 @@ const Login = (props: LoginProps) => {
input == "user" ? setUser(evt.currentTarget.value) : setPassword(evt.currentTarget.value);
}, []);

const handleClickShowPassword = useCallback(() => setShowPassword((show) => !show), []);

const handleMouseDownPassword = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => event.preventDefault(),
[]
);

const handleEnter = useCallback(
(evt: KeyboardEvent<HTMLInputElement>) => {
if (!evt.shiftKey && !evt.ctrlKey && !evt.altKey && evt.key == "Enter") {
Expand All @@ -98,6 +90,30 @@ const Login = (props: LoginProps) => {
[handleAction]
);

// password
const handleClickShowPassword = useCallback(() => setShowPassword((show) => !show), []);
const handleMouseDownPassword = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => event.preventDefault(),
[]
);
const passwordProps = useMemo(
() => ({
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}),
[showPassword, handleClickShowPassword, handleMouseDownPassword]
);

useEffect(() => {
nbLogins++;
if (nbLogins === 1) {
Expand Down Expand Up @@ -129,30 +145,23 @@ const Login = (props: LoginProps) => {
onChange={changeInput}
data-input="user"
onKeyDown={handleEnter}
inputProps={userProps}
></TextField>
<FormControl variant="outlined" data-input="password" required>
<InputLabel htmlFor="taipy-login-password">Password</InputLabel>
<OutlinedInput
id="taipy-login-password"
type={showPassword ? "text" : "password"}
value={password}
onChange={changeInput}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
}
label="Password"
onKeyDown={handleEnter}
/>
</FormControl>
<TextField
variant="outlined"
label="Password"
required
fullWidth
margin="dense"
className={getSuffixedClassNames(className, "-password")}
type={showPassword ? "text" : "password"}
value={password}
onChange={changeInput}
data-input="password"
onKeyDown={handleEnter}
inputProps={pwdProps}
InputProps={passwordProps}
/>
<DialogContentText>{message || defaultMessage}</DialogContentText>
</DialogContent>
<DialogActions>
Expand Down
6 changes: 3 additions & 3 deletions taipy/gui/_renderers/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def _get_variable_hash_names(
if hashname is None:
if callable(v):
if v.__name__ == "<lambda>":
hashname = _get_expr_var_name(v.__code__)
hashname = f"__lambda_{id(v)}"
gui._bind_var_val(hashname, v)
else:
hashname = _get_expr_var_name(v.__name__)
Expand Down Expand Up @@ -404,7 +404,7 @@ def _get_lov_adapter(self, var_name: str, property_name: t.Optional[str] = None,
adapter = self.__gui._get_adapter_for_type(var_type)
elif var_type == str.__name__ and callable(adapter):
var_type += (
_get_expr_var_name(str(adapter.__code__))
f"__lambda_{id(adapter)}"
if adapter.__name__ == "<lambda>"
else _get_expr_var_name(adapter.__name__)
)
Expand Down Expand Up @@ -888,7 +888,7 @@ def _set_table_pagesize_options(self, default_size=None):
def _set_input_type(self, type_name: str, allow_password=False):
if allow_password and self.__get_boolean_attribute("password", False):
return self.set_attribute("type", "password")
return self.set_attribute("type", type_name)
return self.set_attribute("type", self.__attributes.get("type", type_name))

def _set_kind(self):
if self.__attributes.get("theme", False):
Expand Down
6 changes: 6 additions & 0 deletions taipy/gui/viselements.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@
"type": "int",
"default_value": "5",
"doc": "The height of the displayed element if multiline is True."
},
{
"name": "type",
"type": "str",
"default_value": "text",
"doc": "The type of input: text, tel, email ..."
}
]
}
Expand Down
Loading

0 comments on commit ccc70a1

Please sign in to comment.