@byline/ui 6.3.0 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/inputs/input-password.d.ts +17 -1
- package/dist/components/inputs/input-password.js +19 -11
- package/dist/components/inputs/input-password.module.js +5 -0
- package/dist/components/inputs/input-password_module.css +21 -0
- package/package.json +7 -7
- package/src/components/inputs/input-password.module.css +43 -0
- package/src/components/inputs/input-password.tsx +49 -7
|
@@ -1,2 +1,18 @@
|
|
|
1
1
|
import type { InputProps } from './input.js';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* `type` and `endAdornment` are the two props this component exists to
|
|
4
|
+
* control, so they are removed from the public surface rather than left
|
|
5
|
+
* to be overridden.
|
|
6
|
+
*
|
|
7
|
+
* They used to be overridable by accident: props were spread AFTER them,
|
|
8
|
+
* so a caller passing the obvious `type="password"` silently defeated the
|
|
9
|
+
* toggle — the icon swapped, the field stayed masked. Omitting them turns
|
|
10
|
+
* that into a compile error instead of a bug someone has to notice.
|
|
11
|
+
*/
|
|
12
|
+
export interface InputPasswordProps extends Omit<InputProps, 'type' | 'endAdornment'> {
|
|
13
|
+
/** Accessible name for the reveal control while the password is hidden. */
|
|
14
|
+
showPasswordLabel?: string;
|
|
15
|
+
/** Accessible name for the reveal control while the password is shown. */
|
|
16
|
+
hidePasswordLabel?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function InputPassword({ showPasswordLabel, hidePasswordLabel, ...props }: InputPasswordProps): import("react").JSX.Element;
|
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
+
import clsx from "clsx";
|
|
3
4
|
import { EyeClosedIcon } from "../../icons/eye-closed-icon.js";
|
|
4
5
|
import { EyeOpenIcon } from "../../icons/eye-open-icon.js";
|
|
5
6
|
import { Input } from "./input.js";
|
|
6
|
-
|
|
7
|
+
import input_password_module from "./input-password.module.js";
|
|
8
|
+
function InputPassword({ showPasswordLabel = 'Show password', hidePasswordLabel = 'Hide password', ...props }) {
|
|
7
9
|
const [visible, setVisible] = useState(false);
|
|
8
10
|
return /*#__PURE__*/ jsx(Input, {
|
|
11
|
+
...props,
|
|
9
12
|
type: visible ? 'text' : 'password',
|
|
10
|
-
endAdornment:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
endAdornment: /*#__PURE__*/ jsx("button", {
|
|
14
|
+
type: "button",
|
|
15
|
+
className: clsx('byline-input-password-toggle', input_password_module.toggle),
|
|
16
|
+
"aria-label": visible ? hidePasswordLabel : showPasswordLabel,
|
|
17
|
+
"aria-pressed": visible,
|
|
18
|
+
onMouseDown: (event)=>event.preventDefault(),
|
|
19
|
+
onClick: ()=>setVisible((current)=>!current),
|
|
20
|
+
children: visible ? /*#__PURE__*/ jsx(EyeOpenIcon, {
|
|
21
|
+
width: "18px",
|
|
22
|
+
height: "18px"
|
|
23
|
+
}) : /*#__PURE__*/ jsx(EyeClosedIcon, {
|
|
24
|
+
width: "18px",
|
|
25
|
+
height: "18px"
|
|
26
|
+
})
|
|
27
|
+
})
|
|
20
28
|
});
|
|
21
29
|
}
|
|
22
30
|
export { InputPassword };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
@layer byline-components {
|
|
2
|
+
:is(.toggle-bNQhst, .byline-input-password-toggle) {
|
|
3
|
+
color: inherit;
|
|
4
|
+
cursor: pointer;
|
|
5
|
+
background: none;
|
|
6
|
+
border: none;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
align-items: center;
|
|
9
|
+
min-width: 1.375rem;
|
|
10
|
+
min-height: 1.375rem;
|
|
11
|
+
padding: 0;
|
|
12
|
+
display: flex;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:is(.toggle-bNQhst:focus-visible, .byline-input-password-toggle:focus-visible) {
|
|
16
|
+
outline: 1px solid oklch(from var(--field-ring, var(--blue-500)) l c h / .6);
|
|
17
|
+
outline-offset: 1px;
|
|
18
|
+
border-radius: var(--border-radius-sm, 3px);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "6.
|
|
6
|
+
"version": "6.5.0",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20.9.0"
|
|
9
9
|
},
|
|
@@ -55,19 +55,19 @@
|
|
|
55
55
|
"@dnd-kit/sortable": "^10.0.0",
|
|
56
56
|
"clsx": "^2.1.1",
|
|
57
57
|
"date-fns": "^4.4.0",
|
|
58
|
-
"motion": "^13.
|
|
58
|
+
"motion": "^13.4.0",
|
|
59
59
|
"react-day-picker": "^10.0.1",
|
|
60
|
-
"zod": "^4.6.
|
|
60
|
+
"zod": "^4.6.5"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"react": "^19.0.0",
|
|
64
64
|
"react-dom": "^19.0.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@biomejs/biome": "2.5.
|
|
67
|
+
"@biomejs/biome": "2.5.14",
|
|
68
68
|
"@rsbuild/plugin-react": "^2.1.0",
|
|
69
|
-
"@rslib/core": "^1.0.
|
|
70
|
-
"@types/node": "^26.
|
|
69
|
+
"@rslib/core": "^1.0.1",
|
|
70
|
+
"@types/node": "^26.6.2",
|
|
71
71
|
"@types/react": "19.3.0",
|
|
72
72
|
"@types/react-dom": "19.3.0",
|
|
73
73
|
"@vitejs/plugin-react": "^6.1.1",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"typescript": "^7.0.2",
|
|
80
80
|
"typescript-plugin-css-modules": "^5.2.0",
|
|
81
81
|
"vite": "^8.3.0",
|
|
82
|
-
"vitest": "^5.0.
|
|
82
|
+
"vitest": "^5.0.1"
|
|
83
83
|
},
|
|
84
84
|
"publishConfig": {
|
|
85
85
|
"access": "public",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InputPassword — the reveal toggle inside the input's end adornment.
|
|
3
|
+
*
|
|
4
|
+
* Override handle:
|
|
5
|
+
* .byline-input-password-toggle — the reveal/hide button
|
|
6
|
+
*
|
|
7
|
+
* It is a `<button>` so it is keyboard reachable and announced, which
|
|
8
|
+
* means the browser's default button chrome has to be stripped back to
|
|
9
|
+
* what the bare icon used to look like.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
@layer byline-components {
|
|
13
|
+
.toggle,
|
|
14
|
+
:global(.byline-input-password-toggle) {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
padding: 0;
|
|
19
|
+
border: none;
|
|
20
|
+
background: none;
|
|
21
|
+
color: inherit;
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
/* The icon is 18px; the box is padded out to a comfortable target
|
|
24
|
+
without changing where the icon sits. */
|
|
25
|
+
min-width: 1.375rem;
|
|
26
|
+
min-height: 1.375rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* A hairline ring at reduced opacity. The control is a 22px box sitting
|
|
31
|
+
* inside a 36px field, so the 2px ring this started as, at full
|
|
32
|
+
* `--primary-500`, read as heavier than the input's own border and drew
|
|
33
|
+
* the eye away from the field. Thickness and offset both drop to 1px,
|
|
34
|
+
* which pulls the outer edge in by 4px without touching the button box —
|
|
35
|
+
* so the 18px icon and the click target are unchanged.
|
|
36
|
+
*/
|
|
37
|
+
.toggle:focus-visible,
|
|
38
|
+
:global(.byline-input-password-toggle):focus-visible {
|
|
39
|
+
outline: 1px solid oklch(from var(--field-ring, var(--blue-500)) l c h / 0.6);
|
|
40
|
+
outline-offset: 1px;
|
|
41
|
+
border-radius: var(--border-radius-sm, 3px);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,24 +1,66 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
|
|
3
|
+
import cx from 'clsx'
|
|
4
|
+
|
|
3
5
|
import { EyeClosedIcon } from '../../icons/eye-closed-icon.jsx'
|
|
4
6
|
import { EyeOpenIcon } from '../../icons/eye-open-icon.jsx'
|
|
5
7
|
import { Input } from './input.jsx'
|
|
8
|
+
import styles from './input-password.module.css'
|
|
6
9
|
import type { InputProps } from './input.jsx'
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
/**
|
|
12
|
+
* `type` and `endAdornment` are the two props this component exists to
|
|
13
|
+
* control, so they are removed from the public surface rather than left
|
|
14
|
+
* to be overridden.
|
|
15
|
+
*
|
|
16
|
+
* They used to be overridable by accident: props were spread AFTER them,
|
|
17
|
+
* so a caller passing the obvious `type="password"` silently defeated the
|
|
18
|
+
* toggle — the icon swapped, the field stayed masked. Omitting them turns
|
|
19
|
+
* that into a compile error instead of a bug someone has to notice.
|
|
20
|
+
*/
|
|
21
|
+
export interface InputPasswordProps extends Omit<InputProps, 'type' | 'endAdornment'> {
|
|
22
|
+
/** Accessible name for the reveal control while the password is hidden. */
|
|
23
|
+
showPasswordLabel?: string
|
|
24
|
+
/** Accessible name for the reveal control while the password is shown. */
|
|
25
|
+
hidePasswordLabel?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function InputPassword({
|
|
29
|
+
showPasswordLabel = 'Show password',
|
|
30
|
+
hidePasswordLabel = 'Hide password',
|
|
31
|
+
...props
|
|
32
|
+
}: InputPasswordProps) {
|
|
9
33
|
const [visible, setVisible] = useState(false)
|
|
10
34
|
|
|
11
35
|
return (
|
|
12
36
|
<Input
|
|
37
|
+
// Spread first: `type` below is the whole point of the component and
|
|
38
|
+
// must win over anything a caller passes.
|
|
39
|
+
{...props}
|
|
13
40
|
type={visible ? 'text' : 'password'}
|
|
14
41
|
endAdornment={
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
42
|
+
<button
|
|
43
|
+
type="button"
|
|
44
|
+
className={cx('byline-input-password-toggle', styles.toggle)}
|
|
45
|
+
// A real button, not a div: the control has to be reachable by
|
|
46
|
+
// keyboard and announced by a screen reader. The icons are
|
|
47
|
+
// `aria-hidden`, so without a name here the control is silent.
|
|
48
|
+
aria-label={visible ? hidePasswordLabel : showPasswordLabel}
|
|
49
|
+
aria-pressed={visible}
|
|
50
|
+
// Clicking the toggle must not blur the field. Beyond keeping the
|
|
51
|
+
// caret where the user left it, a blur fires the form's validation
|
|
52
|
+
// and the re-render that follows can replace this element between
|
|
53
|
+
// mousedown and mouseup — which loses the click entirely.
|
|
54
|
+
onMouseDown={(event) => event.preventDefault()}
|
|
55
|
+
onClick={() => setVisible((current) => !current)}
|
|
56
|
+
>
|
|
57
|
+
{visible ? (
|
|
58
|
+
<EyeOpenIcon width="18px" height="18px" />
|
|
59
|
+
) : (
|
|
60
|
+
<EyeClosedIcon width="18px" height="18px" />
|
|
61
|
+
)}
|
|
62
|
+
</button>
|
|
20
63
|
}
|
|
21
|
-
{...props}
|
|
22
64
|
/>
|
|
23
65
|
)
|
|
24
66
|
}
|