PasswordField
Mẫu chuẩnÔ mật khẩu với nút hiện/ẩn nằm trong ô và autoComplete bắt buộc — quên nó là lỗi trình quản lý mật khẩu phổ biến nhất trên web, và ở đây trình biên dịch không cho quên.
Playground
Bấm nút hiện/ẩn thật, đổi autoComplete và ghi chú để xem cùng một PasswordField thích ứng.
autoComplete hiện tại: current-password
import { PasswordField } from "@nexobox/ui";
function Example() {
const [value, setValue] = useState("");
return (
<PasswordField
label="Mật khẩu"
value={value}
onChange={setValue}
autoComplete="current-password"
/>
);
}Bài toán
Một ô mật khẩu che chính thứ người dùng vừa gõ, nên tỉ lệ gõ sai cao và người dùng không có cách nào tự kiểm. Cách chữa đã thành chuẩn là một nút hiện/ẩn, nhưng nút đó cần một <button> thật để đến được bằng bàn phím, cần định vị tuyệt đối trong lòng ô, cần một aria-label đổi theo trạng thái, và cần lật type giữa password và text — tức nó phải sở hữu một mảnh state. Input là một primitive không state và không nên trở thành có state chỉ vì một ca dùng. Chỗ đúng cho nút đó là tầng trên cả Input lẫn Label, và đó cũng là lý do nó không thể là một variant của Field: Field không có state, composite này có.
Khi nào sử dụng
Nên dùng
Mọi ô nhập mật khẩu: đăng nhập, đăng ký, đặt lại mật khẩu, đổi mật khẩu trong trang bảo mật. Khi người dùng cần tự kiểm điều mình vừa gõ trước khi nộp.
Khi nào không dùng
Tránh dùng
| Không dùng cho | Dùng thay |
|---|---|
| Ô nhập văn bản thường có gợi ý và lỗi | Field |
| Mã OTP hoặc mã xác thực sáu chữ số | OtpInput — mã OTP không cần che, che một chuỗi dùng một lần chỉ gây gõ sai |
| Khoá API, token, secret trong trang cấu hình | Input cộng một nút sao chép — ngữ nghĩa ở đó là tiết lộ một giá trị đã lưu, không phải nhập một mật khẩu |
| Hai ô "mật khẩu" và "nhắc lại mật khẩu" cần so khớp | Hai PasswordField cộng luật so khớp ở host. Composite này không so hai giá trị |
Dựng từ
| Primitive tầng 1 | Vai trong composite |
|---|---|
| Label | Nhãn, htmlFor bằng id đã giải quyết. |
| Input | Ô nhập. type do composite lật, className bị composite đặt để chừa chỗ cho nút. |
| Icon (wrapper, không phải dòng catalogue) | Vẽ EyeIcon hoặc ViewOffIcon. |
| PasswordField thêm gì mà không primitive nào có |
|---|
Một <button type="button"> định vị tuyệt đối trong lòng ô, vuông theo component.field.height |
useState giữ trạng thái hiện/ẩn, và phép lật type giữa password và text |
aria-label đổi theo trạng thái: "Hiện mật khẩu" khi đang che, "Ẩn mật khẩu" khi đang hiện |
Một autoComplete bắt buộc, kiểu union hai giá trị |
Anatomy
| # | Phần | Ghi chú |
|---|---|---|
| 1 | Label | htmlFor bằng id. |
| 2 | Input | type lật theo state hiện/ẩn, padding-right bằng chiều cao ô. |
| 3 | Nút hiện/ẩn | Hình vuông cạnh bằng chiều cao ô, dính mép phải, vành tiêu điểm vẽ vào trong. |
| 4 | Ghi chú (p) | Chỉ render khi có error hoặc hint. |
Cạnh nút bằng chiều cao ô là một token dùng hai lần, không phải hai con số tình cờ bằng nhau — nên chữ trong ô không bao giờ chạy xuống dưới nút.
States
type="password", nhãn nút "Hiện mật khẩu".
Mật khẩu không đúng.
aria-invalid="true", gợi ý bị che.
Tối thiểu 8 ký tự.
autoComplete="new-password" cho ô đặt mật khẩu mới.
Nút được focus: viền tiêu điểm vẽ vào trong (outline-offset âm) vì nút dính mép ô. Trạng thái hiện/ẩn không đặt lại khi value đổi. Hover, disabled của ô: kế thừa từ Input.
Bàn phím & trợ năng
Seam — host sở hữu gì
PasswordField không đo độ mạnh mật khẩu, không áp luật độ dài, không so khớp hai ô, không nộp gì. Theo ADR-UI-003 clause 2 nó không fetch, không đọc router, không giữ session, không import gì từ next. Clause 6 cấm cụ thể hơn: dấu vân mật khẩu mô phỏng của bộ kit không được port, nên bất kỳ chỉ báo độ mạnh nào cũng phải là quyết định mới, không phải một thứ lặng lẽ xuất hiện ở đây.
| Host sở hữu | Cấp qua |
|---|---|
| Giá trị | value — controlled, bắt buộc, khác Field |
| Mỗi lần gõ | onChange(value: string) — nhận chuỗi, không nhận event |
| Luật độ mạnh, luật so khớp, và câu chữ của chúng | error / hint |
| Ngữ cảnh của ô: đang đăng nhập hay đang đặt mật khẩu mới | autoComplete, bắt buộc |
Trạng thái duy nhất composite tự giữ là hiện/ẩn — trạng thái trình bày, cố ý không lộ ra API: không có prop defaultVisible.
API & triển khai
| Prop | Mặc định | Quy ước |
|---|---|---|
| label | — | Bắt buộc. |
| value | — | Bắt buộc. Composite này luôn controlled. |
| onChange | — | Bắt buộc. Nhận chuỗi đã bóc khỏi event. |
| autoComplete | — | "current-password" | "new-password". Bắt buộc, không default. |
| hint | undefined | Bị error che. |
| error | undefined | Có giá trị ⇒ aria-invalid="true". |
| id | useId() | — |
| …InputHTMLAttributes | — | Trừ type, value, onChange -- ba prop này bị Omit vì composite sở hữu chúng. |
Design tokens
PasswordField đọc --nf-component-auth-field-gap cho khoảng cách dọc, --nf-component-field-height cho cạnh nút hiện/ẩn và padding-right của ô, --nf-component-focus-width / -focus-color cho viền tiêu điểm của nút, --nf-semantic-text-muted cho màu icon và gợi ý, --nf-semantic-status-danger-foreground cho màu lỗi. Cùng lỗ hổng đặt tên component.auth.fieldGap đã ghi ở Field, theo dõi bằng dòng ledger W10.19, áp cho cả trang này.
Do / Don't
Nên dùng
Truyền autoComplete="new-password" ở mọi ô đặt mật khẩu mới. Để composite sở hữu trạng thái hiện/ẩn. Đặt luật độ dài vào hint. Dùng hai composite cho "mật khẩu" và "nhắc lại".
Tránh dùng
Truyền "current-password" cho ô đăng ký — trình quản lý mật khẩu sẽ đề nghị mật khẩu cũ thay vì sinh một mật khẩu mới. Thêm một prop để mở sẵn. Trông đợi một thanh đo độ mạnh — nó không có. Trông đợi composite tự so khớp hai ô. Dùng PasswordField cho khoá API.