-
-
Notifications
You must be signed in to change notification settings - Fork 300
/
Password.tsx
171 lines (153 loc) · 4.92 KB
/
Password.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import {
CSSProperties,
forwardRef,
isValidElement,
MouseEvent,
MouseEventHandler,
ReactNode,
useCallback,
useState,
} from "react";
import cn from "classnames";
import { Button } from "@react-md/button";
import { useIcon } from "@react-md/icon";
import { bem } from "@react-md/utils";
import { TextField, TextFieldProps } from "./TextField";
export interface ConfigurableVisibilityIcon {
/**
* The icon to display while the password is currently visible as plain text.
*/
visible: ReactNode;
/**
* The icon to display while the password is currently invisible as the
* password input.
*/
invisible: ReactNode;
}
export type GetVisibilityIcon = (type: "text" | "password") => ReactNode;
export interface PasswordProps extends Omit<TextFieldProps, "type"> {
/**
* The icon to use to toggle the visibility of the password by changing the
* input type to text temporarily. This can either be a renderable React node
* or an object for the `visible` and `invisible` states.
*/
visibilityIcon?: ReactNode | ConfigurableVisibilityIcon;
/**
* An optional style to apply to the visibility toggle button.
*/
visibilityStyle?: CSSProperties;
/**
* An optional classname to apply to the visibility toggle button.
*/
visibilityClassName?: string;
/**
* An optional `aria-label` to apply to the visibility toggle button.
*
* Note: The visibility button is being treated as a [toggle
* button](https://www.w3.org/TR/wai-aria-practices-1.1/#button) which means
* that the label **should not change** based on the visibility state and
* should not include the word "toggle" since it will be redundant.
*/
visibilityLabel?: string;
/**
* Boolean if the visibility toggle feature should be disabled.
*/
disableVisibility?: boolean;
/**
* An optional function to return the current icon to display within the
* visibility toggle button for additional control.
*
* Depending on the customization needs, it will probably be easier to just
* implement your own `Password` component using the native `TextField`.
*/
getVisibilityIcon?: GetVisibilityIcon;
/**
* An optional function to call when the visibility button has been clicked.
* This is only a simple `MouseEventHandler` for the button element.
*
* Depending on the customization needs, it will probably be easier to just
* implement your own `Password` component using the native `TextField`.
*/
onVisibilityClick?: MouseEventHandler<HTMLButtonElement>;
}
const block = bem("rmd-password");
function isConfigurableIcon(
icon: ReactNode | ConfigurableVisibilityIcon
): icon is ConfigurableVisibilityIcon {
return !!icon && !isValidElement(icon);
}
/**
* This component is a simple wrapper of the `TextField` that can only be
* rendered for password inputs. There is built-in functionality to be able to
* temporarily show the password's value by swapping the `type` to `"text"`.
*/
export const Password = forwardRef<HTMLInputElement, PasswordProps>(
function Password(
{
className,
inputClassName,
visibilityIcon: propVisibilityIcon,
visibilityStyle,
visibilityClassName,
visibilityLabel = "Show password",
onVisibilityClick,
getVisibilityIcon,
disableVisibility = false,
rightChildren: propRightChildren,
isRightAddon = disableVisibility,
...props
},
ref
) {
const { id } = props;
const [type, setType] = useState<"password" | "text">("password");
const toggle = useCallback(
(event: MouseEvent<HTMLButtonElement>) => {
if (onVisibilityClick) {
onVisibilityClick(event);
}
setType((prevType) => (prevType === "password" ? "text" : "password"));
},
[onVisibilityClick]
);
const visible = type === "text";
let visibilityIcon = useIcon("password", propVisibilityIcon);
if (isConfigurableIcon(propVisibilityIcon)) {
visibilityIcon = visible
? propVisibilityIcon.visible
: propVisibilityIcon.invisible;
}
let rightChildren: ReactNode = propRightChildren;
if (!disableVisibility) {
rightChildren = (
<Button
id={`${id}-password-toggle`}
aria-label={visibilityLabel}
aria-pressed={visible}
buttonType="icon"
onClick={toggle}
style={visibilityStyle}
className={cn(block("toggle"), visibilityClassName)}
>
{typeof getVisibilityIcon === "function"
? getVisibilityIcon(type)
: visibilityIcon}
</Button>
);
}
return (
<TextField
{...props}
className={cn(block({ offset: !disableVisibility }), className)}
inputClassName={cn(
block("input", { offset: !disableVisibility }),
inputClassName
)}
ref={ref}
type={type}
isRightAddon={isRightAddon}
rightChildren={rightChildren}
/>
);
}
);