Skip to content

Commit 046ab55

Browse files
committed
adds PasswordInput component
1 parent 6333f54 commit 046ab55

File tree

6 files changed

+133
-0
lines changed

6 files changed

+133
-0
lines changed

app/components/layout.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ const components = [
104104
"Inline Code",
105105
"Input",
106106
"Media Object",
107+
"Password Input",
107108
"Popover",
108109
"Select",
109110
"Sheet",
@@ -123,6 +124,7 @@ const componentRouteLookup = {
123124
"Inline Code": "/components/inline-code",
124125
Input: "/components/input",
125126
"Media Object": "/components/media-object",
127+
"Password Input": "/components/password-input",
126128
Popover: "/components/popover",
127129
Select: "/components/select",
128130
Sheet: "/components/sheet",
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { code, CodeBlock, CodeBlockBody, CodeBlockCode, CodeBlockCopyButton } from "@/code-block";
2+
import { PasswordInput } from "@/password-input";
3+
import { Table, TableBody, TableHead, TableHeader, TableRow } from "@/table";
4+
import type { HeadersFunction, MetaFunction } from "@remix-run/node";
5+
import { Example } from "~/components/example";
6+
7+
export const meta: MetaFunction = () => {
8+
return [
9+
{ title: "@ngrok/mantle — PasswordInput" },
10+
{ name: "description", content: "mantle is ngrok's UI library and design system" },
11+
];
12+
};
13+
14+
export const headers: HeadersFunction = () => {
15+
return {
16+
"Cache-Control": "max-age=300, stale-while-revalidate=604800",
17+
};
18+
};
19+
20+
export default function Page() {
21+
return (
22+
<div>
23+
<h1 className="text-5xl font-medium">Password Input</h1>
24+
<p className="mt-4 text-xl text-gray-600">Fundamental component for password inputs.</p>
25+
26+
<Example className="mt-4 flex-col gap-4">
27+
<PasswordInput className="max-w-64" placeholder="Enter a username" />
28+
<PasswordInput className="max-w-64" placeholder="Enter a username" aria-invalid />
29+
</Example>
30+
<CodeBlock className="rounded-b-lg rounded-t-none">
31+
<CodeBlockBody>
32+
<CodeBlockCopyButton />
33+
<CodeBlockCode language="tsx">{code`
34+
<>
35+
<PasswordInput placeholder="Enter a username" />
36+
<PasswordInput placeholder="Enter a username" aria-invalid />
37+
</>
38+
`}</CodeBlockCode>
39+
</CodeBlockBody>
40+
</CodeBlock>
41+
42+
<h2 className="mt-16 text-3xl font-medium">API Reference</h2>
43+
<div className="z-10 mt-4 overflow-hidden rounded-lg border border-gray-300">
44+
<Table>
45+
<TableHeader>
46+
<TableRow>
47+
<TableHead>Prop</TableHead>
48+
<TableHead>Type</TableHead>
49+
<TableHead>Default</TableHead>
50+
</TableRow>
51+
</TableHeader>
52+
<TableBody className="font-mono text-xs text-gray-600">
53+
{/* <TableRow>
54+
<TableCell className="align-top font-medium">state</TableCell>
55+
<TableCell className="space-y-2 align-top text-xs">
56+
<p>default</p>
57+
<p>danger</p>
58+
</TableCell>
59+
<TableCell className="align-top">default</TableCell>
60+
</TableRow> */}
61+
</TableBody>
62+
</Table>
63+
</div>
64+
</div>
65+
);
66+
}

app/types/routes.ts

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const routePatterns = [
1212
"/components/inline-code",
1313
"/components/input",
1414
"/components/media-object",
15+
"/components/password-input",
1516
"/components/popover",
1617
"/components/select",
1718
"/components/sheet",
@@ -41,6 +42,7 @@ export const routes = [
4142
"/components/inline-code",
4243
"/components/input",
4344
"/components/media-object",
45+
"/components/password-input",
4446
"/components/popover",
4547
"/components/select",
4648
"/components/sheet",

components/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export * from "./core";
77
export * from "./inline-code";
88
export * from "./input";
99
export * from "./media-object";
10+
export * from "./password-input";
1011
export * from "./popover";
1112
export * from "./select";
1213
export * from "./sheet";

components/password-input/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { PasswordInput } from "./src/password-input";
2+
3+
export type { PasswordInputProps } from "./src/password-input";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Eye } from "@phosphor-icons/react/Eye";
2+
import { EyeClosed } from "@phosphor-icons/react/EyeClosed";
3+
import { cva } from "class-variance-authority";
4+
import { forwardRef, useState } from "react";
5+
import type { InputHTMLAttributes } from "react";
6+
import { cx } from "../../core";
7+
import type { AutoComplete, InputType } from "../../input";
8+
import { VariantProps } from "../../types/src/variant-props";
9+
10+
export type PasswordInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "autoComplete" | "type"> & {
11+
autoComplete?: AutoComplete;
12+
};
13+
14+
const passwordInputVariants = cva(
15+
"flex h-11 sm:h-9 w-full rounded-md border bg-white dark:bg-gray-50 px-3 py-2 file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-4 disabled:pointer-events-none disabled:opacity-50 sm:text-sm",
16+
{
17+
variants: {
18+
state: {
19+
default: "text-gray-900 border-gray-300 placeholder:text-gray-400 focus:border-blue-600 focus:ring-blue-500/25",
20+
danger: "border-red-600 focus:border-red-600 focus:ring-red-500/25",
21+
},
22+
},
23+
defaultVariants: {
24+
state: "default",
25+
},
26+
},
27+
);
28+
29+
type PasswordInputVariants = VariantProps<typeof passwordInputVariants>;
30+
31+
const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(({ className, style, ...inputProps }, ref) => {
32+
const [showPassword, setShowPassword] = useState<boolean>(false);
33+
const type: Extract<InputType, "text" | "password"> = showPassword ? "text" : "password";
34+
const state: PasswordInputVariants["state"] = inputProps["aria-invalid"] ? "danger" : "default";
35+
36+
return (
37+
<div className={cx(passwordInputVariants({ state }), className)} style={style}>
38+
<input
39+
ref={ref}
40+
className="m-0 flex-1 rounded bg-transparent p-0 focus:outline-none"
41+
type={type}
42+
{...inputProps}
43+
/>
44+
<button
45+
type="button"
46+
tabIndex={-1}
47+
className="ml-1 cursor-pointer bg-inherit p-0 text-size-inherit text-gray-600 hover:text-gray-900"
48+
onClick={() => {
49+
setShowPassword((s) => !s);
50+
}}
51+
>
52+
{showPassword ? <Eye /> : <EyeClosed />}
53+
</button>
54+
</div>
55+
);
56+
});
57+
PasswordInput.displayName = "PasswordInput";
58+
59+
export { PasswordInput };

0 commit comments

Comments
 (0)