@academix-admin/pin-input 0.1.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/index.cjs +187 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +79 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +184 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
|
|
7
|
+
// src/PinInput.tsx
|
|
8
|
+
function PinInput({
|
|
9
|
+
length = 6,
|
|
10
|
+
value,
|
|
11
|
+
onChange,
|
|
12
|
+
disabled = false,
|
|
13
|
+
error = false,
|
|
14
|
+
mask = false,
|
|
15
|
+
revealed = false,
|
|
16
|
+
autoFocus = true,
|
|
17
|
+
classNames = {},
|
|
18
|
+
className,
|
|
19
|
+
inputProps
|
|
20
|
+
}) {
|
|
21
|
+
const inputs = Array(length).fill(0);
|
|
22
|
+
const getFirstInvalidIndex = (val) => {
|
|
23
|
+
for (let i = 0; i < length; i++) {
|
|
24
|
+
if (!val[i]) return i;
|
|
25
|
+
}
|
|
26
|
+
return length - 1;
|
|
27
|
+
};
|
|
28
|
+
const handleChange = (e, index) => {
|
|
29
|
+
const digit = e.target.value.replace(/\D/g, "").slice(-1);
|
|
30
|
+
const otpArray = value.split("");
|
|
31
|
+
otpArray[index] = digit || "";
|
|
32
|
+
const nextValue = otpArray.join("");
|
|
33
|
+
onChange(nextValue);
|
|
34
|
+
const allowedIndex = getFirstInvalidIndex(nextValue);
|
|
35
|
+
if (digit && index < length - 1 && index < allowedIndex) {
|
|
36
|
+
const next = e.target.nextElementSibling;
|
|
37
|
+
next?.focus();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const handleKeyDown = (e, index) => {
|
|
41
|
+
if (e.key !== "Backspace") return;
|
|
42
|
+
if (error && value.length === length) {
|
|
43
|
+
onChange("");
|
|
44
|
+
const els2 = e.currentTarget.parentElement.querySelectorAll("input");
|
|
45
|
+
els2[0].focus();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const els = e.currentTarget.parentElement.querySelectorAll("input");
|
|
49
|
+
const domValues = Array.from(els).map((input) => input.value);
|
|
50
|
+
if (!domValues[index] && index > 0) {
|
|
51
|
+
const prev = els[index - 1];
|
|
52
|
+
prev.focus();
|
|
53
|
+
}
|
|
54
|
+
const otpArray = value.split("");
|
|
55
|
+
for (let i = index; i < otpArray.length; i++) {
|
|
56
|
+
otpArray[i] = "";
|
|
57
|
+
}
|
|
58
|
+
onChange(otpArray.join(""));
|
|
59
|
+
};
|
|
60
|
+
const handlePaste = (e) => {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
const pasted = e.clipboardData.getData("text").replace(/\D/g, "");
|
|
63
|
+
if (pasted) {
|
|
64
|
+
onChange(pasted.slice(0, length));
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const handleFocus = (e, index) => {
|
|
68
|
+
const els = e.currentTarget.parentElement.querySelectorAll("input");
|
|
69
|
+
let allowedIndex = 0;
|
|
70
|
+
for (let i = 0; i < length; i++) {
|
|
71
|
+
if (els[i].value === "") {
|
|
72
|
+
allowedIndex = i;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
if (i === length - 1) allowedIndex = length - 1;
|
|
76
|
+
}
|
|
77
|
+
if (index > allowedIndex) {
|
|
78
|
+
els[allowedIndex].focus();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const display = (index) => {
|
|
82
|
+
if (mask && !revealed) return value[index] ? "*" : "";
|
|
83
|
+
return value[index] || "";
|
|
84
|
+
};
|
|
85
|
+
const containerClass = `${className ?? classNames.container ?? ""}${error && classNames.containerError ? ` ${classNames.containerError}` : ""}`.trim() || void 0;
|
|
86
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: containerClass, children: inputs.map((_, index) => /* @__PURE__ */ react.createElement(
|
|
87
|
+
"input",
|
|
88
|
+
{
|
|
89
|
+
...inputProps,
|
|
90
|
+
key: index,
|
|
91
|
+
type: "text",
|
|
92
|
+
inputMode: "numeric",
|
|
93
|
+
pattern: "[0-9]*",
|
|
94
|
+
maxLength: 1,
|
|
95
|
+
value: display(index),
|
|
96
|
+
onChange: (e) => handleChange(e, index),
|
|
97
|
+
onKeyDown: (e) => handleKeyDown(e, index),
|
|
98
|
+
onPaste: handlePaste,
|
|
99
|
+
disabled,
|
|
100
|
+
className: classNames.input,
|
|
101
|
+
autoFocus: autoFocus && index === 0 && !error,
|
|
102
|
+
onFocus: (e) => handleFocus(e, index)
|
|
103
|
+
}
|
|
104
|
+
)) });
|
|
105
|
+
}
|
|
106
|
+
var DIGITS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
|
|
107
|
+
function Keypad({
|
|
108
|
+
value,
|
|
109
|
+
onChange,
|
|
110
|
+
length = 6,
|
|
111
|
+
disabled = false,
|
|
112
|
+
error,
|
|
113
|
+
showMaskToggle = false,
|
|
114
|
+
revealed = false,
|
|
115
|
+
onToggleReveal,
|
|
116
|
+
backspaceLabel = "\u2715",
|
|
117
|
+
classNames = {},
|
|
118
|
+
className
|
|
119
|
+
}) {
|
|
120
|
+
const handleDigitInput = (digit) => {
|
|
121
|
+
if (value.length < length) {
|
|
122
|
+
onChange(value + digit);
|
|
123
|
+
} else if (!!error && value.length === length) {
|
|
124
|
+
onChange(`${digit}`);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const handleBackspace = () => {
|
|
128
|
+
if (value.length > 0 && !error) {
|
|
129
|
+
onChange(value.slice(0, -1));
|
|
130
|
+
} else if (!!error && value.length === length) {
|
|
131
|
+
onChange("");
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const btn = classNames.button ?? "";
|
|
135
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: className ?? classNames.keypad, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames.grid, children: [
|
|
136
|
+
DIGITS.map((digit) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
137
|
+
"button",
|
|
138
|
+
{
|
|
139
|
+
type: "button",
|
|
140
|
+
onClick: () => handleDigitInput(digit),
|
|
141
|
+
disabled,
|
|
142
|
+
className: btn || void 0,
|
|
143
|
+
children: digit
|
|
144
|
+
},
|
|
145
|
+
digit
|
|
146
|
+
)),
|
|
147
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
148
|
+
"button",
|
|
149
|
+
{
|
|
150
|
+
type: "button",
|
|
151
|
+
onClick: handleBackspace,
|
|
152
|
+
disabled,
|
|
153
|
+
className: `${btn} ${classNames.backspace ?? ""}`.trim() || void 0,
|
|
154
|
+
children: backspaceLabel
|
|
155
|
+
}
|
|
156
|
+
),
|
|
157
|
+
showMaskToggle && /* @__PURE__ */ jsxRuntime.jsx(
|
|
158
|
+
"button",
|
|
159
|
+
{
|
|
160
|
+
type: "button",
|
|
161
|
+
onClick: () => onToggleReveal?.(!revealed),
|
|
162
|
+
disabled,
|
|
163
|
+
className: `${btn} ${classNames.toggle ?? ""}`.trim() || void 0,
|
|
164
|
+
title: revealed ? "Hide" : "Show",
|
|
165
|
+
"aria-label": revealed ? "Hide" : "Show",
|
|
166
|
+
children: revealed ? /* @__PURE__ */ jsxRuntime.jsx(EyeOpen, {}) : /* @__PURE__ */ jsxRuntime.jsx(EyeClosed, {})
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
] }) });
|
|
170
|
+
}
|
|
171
|
+
function EyeOpen() {
|
|
172
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: [
|
|
173
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
174
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "3", stroke: "currentColor", strokeWidth: "2" })
|
|
175
|
+
] });
|
|
176
|
+
}
|
|
177
|
+
function EyeClosed() {
|
|
178
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: [
|
|
179
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
180
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
181
|
+
] });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
exports.Keypad = Keypad;
|
|
185
|
+
exports.PinInput = PinInput;
|
|
186
|
+
//# sourceMappingURL=index.cjs.map
|
|
187
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/PinInput.tsx","../src/Keypad.tsx"],"names":["els","jsx","createElement","jsxs"],"mappings":"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","file":"index.cjs","sourcesContent":["'use client';\n\nimport React from 'react';\n\n/**\n * PinInput — a segmented numeric code input (OTP / PIN). Headless: it renders accessible\n * markup and you style every slot via `classNames` (bring your own classes). React-only,\n * no app/theme coupling, so it is safe for any consumer.\n *\n * The focus / auto-advance / backspace-cascade / paste / focus-guard behavior is the\n * device-proven academix logic, preserved exactly. Set `mask` for a hidden PIN (digits\n * render as `*` unless `revealed`); leave it off for a visible OTP.\n */\n\nexport interface PinInputClassNames {\n /** the boxes wrapper. */\n container?: string;\n /** appended to the container while `error` is true. */\n containerError?: string;\n /** each single-character box. */\n input?: string;\n}\n\nexport interface PinInputProps {\n length?: number;\n value: string;\n onChange: (value: string) => void;\n disabled?: boolean;\n error?: boolean;\n /** render digits masked (as `*`) unless `revealed`. */\n mask?: boolean;\n /** when `mask`, show the real digits. */\n revealed?: boolean;\n /** autofocus the first box on mount (when not in error). Default true. */\n autoFocus?: boolean;\n classNames?: PinInputClassNames;\n /** shortcut for `classNames.container`. */\n className?: string;\n /** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */\n inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;\n}\n\nexport function PinInput({\n length = 6,\n value,\n onChange,\n disabled = false,\n error = false,\n mask = false,\n revealed = false,\n autoFocus = true,\n classNames = {},\n className,\n inputProps,\n}: PinInputProps) {\n const inputs = Array(length).fill(0);\n\n const getFirstInvalidIndex = (val: string) => {\n for (let i = 0; i < length; i++) {\n if (!val[i]) return i;\n }\n return length - 1; // all full → last field\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>, index: number) => {\n const digit = e.target.value.replace(/\\D/g, '').slice(-1);\n\n const otpArray = value.split('');\n otpArray[index] = digit || '';\n const nextValue = otpArray.join('');\n\n onChange(nextValue);\n\n // compute allowed index from the new value\n const allowedIndex = getFirstInvalidIndex(nextValue);\n\n // Auto-advance only if digit is valid and index < allowed position\n if (digit && index < length - 1 && index < allowedIndex) {\n const next = e.target.nextElementSibling as HTMLInputElement | null;\n next?.focus();\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, index: number) => {\n if (e.key !== 'Backspace') return;\n\n // Error mode: clear everything\n if (error && value.length === length) {\n onChange('');\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n (els[0] as HTMLInputElement).focus();\n return;\n }\n\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Build a snapshot of the current actual values from DOM, not from state.\n const domValues = Array.from(els).map((input) => (input as HTMLInputElement).value);\n\n // If current field already empty → move back\n if (!domValues[index] && index > 0) {\n const prev = els[index - 1] as HTMLInputElement;\n prev.focus();\n }\n\n // Clear current char (and everything after) and update parent state\n const otpArray = value.split('');\n for (let i = index; i < otpArray.length; i++) {\n otpArray[i] = '';\n }\n onChange(otpArray.join(''));\n };\n\n const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n e.preventDefault();\n const pasted = e.clipboardData.getData('text').replace(/\\D/g, '');\n if (pasted) {\n onChange(pasted.slice(0, length));\n }\n };\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>, index: number) => {\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Determine focusable index based on ACTUAL input values\n let allowedIndex = 0;\n for (let i = 0; i < length; i++) {\n if ((els[i] as HTMLInputElement).value === '') {\n allowedIndex = i;\n break;\n }\n if (i === length - 1) allowedIndex = length - 1;\n }\n\n // Prevent skipping ahead\n if (index > allowedIndex) {\n (els[allowedIndex] as HTMLInputElement).focus();\n }\n };\n\n const display = (index: number) => {\n if (mask && !revealed) return value[index] ? '*' : '';\n return value[index] || '';\n };\n\n const containerClass = `${className ?? classNames.container ?? ''}${\n error && classNames.containerError ? ` ${classNames.containerError}` : ''\n }`.trim() || undefined;\n\n return (\n <div className={containerClass}>\n {inputs.map((_, index) => (\n <input\n {...inputProps}\n key={index}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={1}\n value={display(index)}\n onChange={(e) => handleChange(e, index)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n onPaste={handlePaste}\n disabled={disabled}\n className={classNames.input}\n autoFocus={autoFocus && index === 0 && !error}\n onFocus={(e) => handleFocus(e, index)}\n />\n ))}\n </div>\n );\n}\n","'use client';\n\nimport React from 'react';\n\n/**\n * Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)\n * that drives a `PinInput`'s value. Headless: style each slot via `classNames`. React-only.\n *\n * Behavior preserved from academix: append up to `length` digits; while `error` and the\n * value is full, the next digit replaces it and backspace clears it. Set `showMaskToggle`\n * for the PIN eye button (calls `onToggleReveal`); omit it for a plain OTP keypad.\n */\n\nexport interface KeypadClassNames {\n /** outer wrapper. */\n keypad?: string;\n /** the button grid. */\n grid?: string;\n /** every key (digits, backspace, toggle). */\n button?: string;\n /** appended to the backspace key. */\n backspace?: string;\n /** appended to the mask-toggle key. */\n toggle?: string;\n}\n\nexport interface KeypadProps {\n value: string;\n onChange: (value: string) => void;\n length?: number;\n disabled?: boolean;\n error?: boolean;\n /** render the reveal/mask (eye) toggle key. */\n showMaskToggle?: boolean;\n /** current reveal state (for the toggle icon/label). */\n revealed?: boolean;\n onToggleReveal?: (revealed: boolean) => void;\n /** backspace key content. Default `✕`. */\n backspaceLabel?: React.ReactNode;\n classNames?: KeypadClassNames;\n /** shortcut for `classNames.keypad`. */\n className?: string;\n}\n\nconst DIGITS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];\n\nexport function Keypad({\n value,\n onChange,\n length = 6,\n disabled = false,\n error,\n showMaskToggle = false,\n revealed = false,\n onToggleReveal,\n backspaceLabel = '✕',\n classNames = {},\n className,\n}: KeypadProps) {\n const handleDigitInput = (digit: number) => {\n if (value.length < length) {\n onChange(value + digit);\n } else if (!!error && value.length === length) {\n onChange(`${digit}`);\n }\n };\n\n const handleBackspace = () => {\n if (value.length > 0 && !error) {\n onChange(value.slice(0, -1));\n } else if (!!error && value.length === length) {\n onChange('');\n }\n };\n\n const btn = classNames.button ?? '';\n\n return (\n <div className={className ?? classNames.keypad}>\n <div className={classNames.grid}>\n {DIGITS.map((digit) => (\n <button\n key={digit}\n type=\"button\"\n onClick={() => handleDigitInput(digit)}\n disabled={disabled}\n className={btn || undefined}\n >\n {digit}\n </button>\n ))}\n <button\n type=\"button\"\n onClick={handleBackspace}\n disabled={disabled}\n className={`${btn} ${classNames.backspace ?? ''}`.trim() || undefined}\n >\n {backspaceLabel}\n </button>\n {showMaskToggle && (\n <button\n type=\"button\"\n onClick={() => onToggleReveal?.(!revealed)}\n disabled={disabled}\n className={`${btn} ${classNames.toggle ?? ''}`.trim() || undefined}\n title={revealed ? 'Hide' : 'Show'}\n aria-label={revealed ? 'Hide' : 'Show'}\n >\n {revealed ? <EyeOpen /> : <EyeClosed />}\n </button>\n )}\n </div>\n </div>\n );\n}\n\nfunction EyeOpen() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" stroke=\"currentColor\" strokeWidth=\"2\" />\n </svg>\n );\n}\nfunction EyeClosed() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PinInput — a segmented numeric code input (OTP / PIN). Headless: it renders accessible
|
|
5
|
+
* markup and you style every slot via `classNames` (bring your own classes). React-only,
|
|
6
|
+
* no app/theme coupling, so it is safe for any consumer.
|
|
7
|
+
*
|
|
8
|
+
* The focus / auto-advance / backspace-cascade / paste / focus-guard behavior is the
|
|
9
|
+
* device-proven academix logic, preserved exactly. Set `mask` for a hidden PIN (digits
|
|
10
|
+
* render as `*` unless `revealed`); leave it off for a visible OTP.
|
|
11
|
+
*/
|
|
12
|
+
interface PinInputClassNames {
|
|
13
|
+
/** the boxes wrapper. */
|
|
14
|
+
container?: string;
|
|
15
|
+
/** appended to the container while `error` is true. */
|
|
16
|
+
containerError?: string;
|
|
17
|
+
/** each single-character box. */
|
|
18
|
+
input?: string;
|
|
19
|
+
}
|
|
20
|
+
interface PinInputProps {
|
|
21
|
+
length?: number;
|
|
22
|
+
value: string;
|
|
23
|
+
onChange: (value: string) => void;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
error?: boolean;
|
|
26
|
+
/** render digits masked (as `*`) unless `revealed`. */
|
|
27
|
+
mask?: boolean;
|
|
28
|
+
/** when `mask`, show the real digits. */
|
|
29
|
+
revealed?: boolean;
|
|
30
|
+
/** autofocus the first box on mount (when not in error). Default true. */
|
|
31
|
+
autoFocus?: boolean;
|
|
32
|
+
classNames?: PinInputClassNames;
|
|
33
|
+
/** shortcut for `classNames.container`. */
|
|
34
|
+
className?: string;
|
|
35
|
+
/** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */
|
|
36
|
+
inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;
|
|
37
|
+
}
|
|
38
|
+
declare function PinInput({ length, value, onChange, disabled, error, mask, revealed, autoFocus, classNames, className, inputProps, }: PinInputProps): React.JSX.Element;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)
|
|
42
|
+
* that drives a `PinInput`'s value. Headless: style each slot via `classNames`. React-only.
|
|
43
|
+
*
|
|
44
|
+
* Behavior preserved from academix: append up to `length` digits; while `error` and the
|
|
45
|
+
* value is full, the next digit replaces it and backspace clears it. Set `showMaskToggle`
|
|
46
|
+
* for the PIN eye button (calls `onToggleReveal`); omit it for a plain OTP keypad.
|
|
47
|
+
*/
|
|
48
|
+
interface KeypadClassNames {
|
|
49
|
+
/** outer wrapper. */
|
|
50
|
+
keypad?: string;
|
|
51
|
+
/** the button grid. */
|
|
52
|
+
grid?: string;
|
|
53
|
+
/** every key (digits, backspace, toggle). */
|
|
54
|
+
button?: string;
|
|
55
|
+
/** appended to the backspace key. */
|
|
56
|
+
backspace?: string;
|
|
57
|
+
/** appended to the mask-toggle key. */
|
|
58
|
+
toggle?: string;
|
|
59
|
+
}
|
|
60
|
+
interface KeypadProps {
|
|
61
|
+
value: string;
|
|
62
|
+
onChange: (value: string) => void;
|
|
63
|
+
length?: number;
|
|
64
|
+
disabled?: boolean;
|
|
65
|
+
error?: boolean;
|
|
66
|
+
/** render the reveal/mask (eye) toggle key. */
|
|
67
|
+
showMaskToggle?: boolean;
|
|
68
|
+
/** current reveal state (for the toggle icon/label). */
|
|
69
|
+
revealed?: boolean;
|
|
70
|
+
onToggleReveal?: (revealed: boolean) => void;
|
|
71
|
+
/** backspace key content. Default `✕`. */
|
|
72
|
+
backspaceLabel?: React.ReactNode;
|
|
73
|
+
classNames?: KeypadClassNames;
|
|
74
|
+
/** shortcut for `classNames.keypad`. */
|
|
75
|
+
className?: string;
|
|
76
|
+
}
|
|
77
|
+
declare function Keypad({ value, onChange, length, disabled, error, showMaskToggle, revealed, onToggleReveal, backspaceLabel, classNames, className, }: KeypadProps): React.JSX.Element;
|
|
78
|
+
|
|
79
|
+
export { Keypad, type KeypadClassNames, type KeypadProps, PinInput, type PinInputClassNames, type PinInputProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PinInput — a segmented numeric code input (OTP / PIN). Headless: it renders accessible
|
|
5
|
+
* markup and you style every slot via `classNames` (bring your own classes). React-only,
|
|
6
|
+
* no app/theme coupling, so it is safe for any consumer.
|
|
7
|
+
*
|
|
8
|
+
* The focus / auto-advance / backspace-cascade / paste / focus-guard behavior is the
|
|
9
|
+
* device-proven academix logic, preserved exactly. Set `mask` for a hidden PIN (digits
|
|
10
|
+
* render as `*` unless `revealed`); leave it off for a visible OTP.
|
|
11
|
+
*/
|
|
12
|
+
interface PinInputClassNames {
|
|
13
|
+
/** the boxes wrapper. */
|
|
14
|
+
container?: string;
|
|
15
|
+
/** appended to the container while `error` is true. */
|
|
16
|
+
containerError?: string;
|
|
17
|
+
/** each single-character box. */
|
|
18
|
+
input?: string;
|
|
19
|
+
}
|
|
20
|
+
interface PinInputProps {
|
|
21
|
+
length?: number;
|
|
22
|
+
value: string;
|
|
23
|
+
onChange: (value: string) => void;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
error?: boolean;
|
|
26
|
+
/** render digits masked (as `*`) unless `revealed`. */
|
|
27
|
+
mask?: boolean;
|
|
28
|
+
/** when `mask`, show the real digits. */
|
|
29
|
+
revealed?: boolean;
|
|
30
|
+
/** autofocus the first box on mount (when not in error). Default true. */
|
|
31
|
+
autoFocus?: boolean;
|
|
32
|
+
classNames?: PinInputClassNames;
|
|
33
|
+
/** shortcut for `classNames.container`. */
|
|
34
|
+
className?: string;
|
|
35
|
+
/** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */
|
|
36
|
+
inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;
|
|
37
|
+
}
|
|
38
|
+
declare function PinInput({ length, value, onChange, disabled, error, mask, revealed, autoFocus, classNames, className, inputProps, }: PinInputProps): React.JSX.Element;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)
|
|
42
|
+
* that drives a `PinInput`'s value. Headless: style each slot via `classNames`. React-only.
|
|
43
|
+
*
|
|
44
|
+
* Behavior preserved from academix: append up to `length` digits; while `error` and the
|
|
45
|
+
* value is full, the next digit replaces it and backspace clears it. Set `showMaskToggle`
|
|
46
|
+
* for the PIN eye button (calls `onToggleReveal`); omit it for a plain OTP keypad.
|
|
47
|
+
*/
|
|
48
|
+
interface KeypadClassNames {
|
|
49
|
+
/** outer wrapper. */
|
|
50
|
+
keypad?: string;
|
|
51
|
+
/** the button grid. */
|
|
52
|
+
grid?: string;
|
|
53
|
+
/** every key (digits, backspace, toggle). */
|
|
54
|
+
button?: string;
|
|
55
|
+
/** appended to the backspace key. */
|
|
56
|
+
backspace?: string;
|
|
57
|
+
/** appended to the mask-toggle key. */
|
|
58
|
+
toggle?: string;
|
|
59
|
+
}
|
|
60
|
+
interface KeypadProps {
|
|
61
|
+
value: string;
|
|
62
|
+
onChange: (value: string) => void;
|
|
63
|
+
length?: number;
|
|
64
|
+
disabled?: boolean;
|
|
65
|
+
error?: boolean;
|
|
66
|
+
/** render the reveal/mask (eye) toggle key. */
|
|
67
|
+
showMaskToggle?: boolean;
|
|
68
|
+
/** current reveal state (for the toggle icon/label). */
|
|
69
|
+
revealed?: boolean;
|
|
70
|
+
onToggleReveal?: (revealed: boolean) => void;
|
|
71
|
+
/** backspace key content. Default `✕`. */
|
|
72
|
+
backspaceLabel?: React.ReactNode;
|
|
73
|
+
classNames?: KeypadClassNames;
|
|
74
|
+
/** shortcut for `classNames.keypad`. */
|
|
75
|
+
className?: string;
|
|
76
|
+
}
|
|
77
|
+
declare function Keypad({ value, onChange, length, disabled, error, showMaskToggle, revealed, onToggleReveal, backspaceLabel, classNames, className, }: KeypadProps): React.JSX.Element;
|
|
78
|
+
|
|
79
|
+
export { Keypad, type KeypadClassNames, type KeypadProps, PinInput, type PinInputClassNames, type PinInputProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { createElement } from 'react';
|
|
4
|
+
|
|
5
|
+
// src/PinInput.tsx
|
|
6
|
+
function PinInput({
|
|
7
|
+
length = 6,
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
disabled = false,
|
|
11
|
+
error = false,
|
|
12
|
+
mask = false,
|
|
13
|
+
revealed = false,
|
|
14
|
+
autoFocus = true,
|
|
15
|
+
classNames = {},
|
|
16
|
+
className,
|
|
17
|
+
inputProps
|
|
18
|
+
}) {
|
|
19
|
+
const inputs = Array(length).fill(0);
|
|
20
|
+
const getFirstInvalidIndex = (val) => {
|
|
21
|
+
for (let i = 0; i < length; i++) {
|
|
22
|
+
if (!val[i]) return i;
|
|
23
|
+
}
|
|
24
|
+
return length - 1;
|
|
25
|
+
};
|
|
26
|
+
const handleChange = (e, index) => {
|
|
27
|
+
const digit = e.target.value.replace(/\D/g, "").slice(-1);
|
|
28
|
+
const otpArray = value.split("");
|
|
29
|
+
otpArray[index] = digit || "";
|
|
30
|
+
const nextValue = otpArray.join("");
|
|
31
|
+
onChange(nextValue);
|
|
32
|
+
const allowedIndex = getFirstInvalidIndex(nextValue);
|
|
33
|
+
if (digit && index < length - 1 && index < allowedIndex) {
|
|
34
|
+
const next = e.target.nextElementSibling;
|
|
35
|
+
next?.focus();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const handleKeyDown = (e, index) => {
|
|
39
|
+
if (e.key !== "Backspace") return;
|
|
40
|
+
if (error && value.length === length) {
|
|
41
|
+
onChange("");
|
|
42
|
+
const els2 = e.currentTarget.parentElement.querySelectorAll("input");
|
|
43
|
+
els2[0].focus();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const els = e.currentTarget.parentElement.querySelectorAll("input");
|
|
47
|
+
const domValues = Array.from(els).map((input) => input.value);
|
|
48
|
+
if (!domValues[index] && index > 0) {
|
|
49
|
+
const prev = els[index - 1];
|
|
50
|
+
prev.focus();
|
|
51
|
+
}
|
|
52
|
+
const otpArray = value.split("");
|
|
53
|
+
for (let i = index; i < otpArray.length; i++) {
|
|
54
|
+
otpArray[i] = "";
|
|
55
|
+
}
|
|
56
|
+
onChange(otpArray.join(""));
|
|
57
|
+
};
|
|
58
|
+
const handlePaste = (e) => {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
const pasted = e.clipboardData.getData("text").replace(/\D/g, "");
|
|
61
|
+
if (pasted) {
|
|
62
|
+
onChange(pasted.slice(0, length));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const handleFocus = (e, index) => {
|
|
66
|
+
const els = e.currentTarget.parentElement.querySelectorAll("input");
|
|
67
|
+
let allowedIndex = 0;
|
|
68
|
+
for (let i = 0; i < length; i++) {
|
|
69
|
+
if (els[i].value === "") {
|
|
70
|
+
allowedIndex = i;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
if (i === length - 1) allowedIndex = length - 1;
|
|
74
|
+
}
|
|
75
|
+
if (index > allowedIndex) {
|
|
76
|
+
els[allowedIndex].focus();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const display = (index) => {
|
|
80
|
+
if (mask && !revealed) return value[index] ? "*" : "";
|
|
81
|
+
return value[index] || "";
|
|
82
|
+
};
|
|
83
|
+
const containerClass = `${className ?? classNames.container ?? ""}${error && classNames.containerError ? ` ${classNames.containerError}` : ""}`.trim() || void 0;
|
|
84
|
+
return /* @__PURE__ */ jsx("div", { className: containerClass, children: inputs.map((_, index) => /* @__PURE__ */ createElement(
|
|
85
|
+
"input",
|
|
86
|
+
{
|
|
87
|
+
...inputProps,
|
|
88
|
+
key: index,
|
|
89
|
+
type: "text",
|
|
90
|
+
inputMode: "numeric",
|
|
91
|
+
pattern: "[0-9]*",
|
|
92
|
+
maxLength: 1,
|
|
93
|
+
value: display(index),
|
|
94
|
+
onChange: (e) => handleChange(e, index),
|
|
95
|
+
onKeyDown: (e) => handleKeyDown(e, index),
|
|
96
|
+
onPaste: handlePaste,
|
|
97
|
+
disabled,
|
|
98
|
+
className: classNames.input,
|
|
99
|
+
autoFocus: autoFocus && index === 0 && !error,
|
|
100
|
+
onFocus: (e) => handleFocus(e, index)
|
|
101
|
+
}
|
|
102
|
+
)) });
|
|
103
|
+
}
|
|
104
|
+
var DIGITS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
|
|
105
|
+
function Keypad({
|
|
106
|
+
value,
|
|
107
|
+
onChange,
|
|
108
|
+
length = 6,
|
|
109
|
+
disabled = false,
|
|
110
|
+
error,
|
|
111
|
+
showMaskToggle = false,
|
|
112
|
+
revealed = false,
|
|
113
|
+
onToggleReveal,
|
|
114
|
+
backspaceLabel = "\u2715",
|
|
115
|
+
classNames = {},
|
|
116
|
+
className
|
|
117
|
+
}) {
|
|
118
|
+
const handleDigitInput = (digit) => {
|
|
119
|
+
if (value.length < length) {
|
|
120
|
+
onChange(value + digit);
|
|
121
|
+
} else if (!!error && value.length === length) {
|
|
122
|
+
onChange(`${digit}`);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const handleBackspace = () => {
|
|
126
|
+
if (value.length > 0 && !error) {
|
|
127
|
+
onChange(value.slice(0, -1));
|
|
128
|
+
} else if (!!error && value.length === length) {
|
|
129
|
+
onChange("");
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const btn = classNames.button ?? "";
|
|
133
|
+
return /* @__PURE__ */ jsx("div", { className: className ?? classNames.keypad, children: /* @__PURE__ */ jsxs("div", { className: classNames.grid, children: [
|
|
134
|
+
DIGITS.map((digit) => /* @__PURE__ */ jsx(
|
|
135
|
+
"button",
|
|
136
|
+
{
|
|
137
|
+
type: "button",
|
|
138
|
+
onClick: () => handleDigitInput(digit),
|
|
139
|
+
disabled,
|
|
140
|
+
className: btn || void 0,
|
|
141
|
+
children: digit
|
|
142
|
+
},
|
|
143
|
+
digit
|
|
144
|
+
)),
|
|
145
|
+
/* @__PURE__ */ jsx(
|
|
146
|
+
"button",
|
|
147
|
+
{
|
|
148
|
+
type: "button",
|
|
149
|
+
onClick: handleBackspace,
|
|
150
|
+
disabled,
|
|
151
|
+
className: `${btn} ${classNames.backspace ?? ""}`.trim() || void 0,
|
|
152
|
+
children: backspaceLabel
|
|
153
|
+
}
|
|
154
|
+
),
|
|
155
|
+
showMaskToggle && /* @__PURE__ */ jsx(
|
|
156
|
+
"button",
|
|
157
|
+
{
|
|
158
|
+
type: "button",
|
|
159
|
+
onClick: () => onToggleReveal?.(!revealed),
|
|
160
|
+
disabled,
|
|
161
|
+
className: `${btn} ${classNames.toggle ?? ""}`.trim() || void 0,
|
|
162
|
+
title: revealed ? "Hide" : "Show",
|
|
163
|
+
"aria-label": revealed ? "Hide" : "Show",
|
|
164
|
+
children: revealed ? /* @__PURE__ */ jsx(EyeOpen, {}) : /* @__PURE__ */ jsx(EyeClosed, {})
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
] }) });
|
|
168
|
+
}
|
|
169
|
+
function EyeOpen() {
|
|
170
|
+
return /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: [
|
|
171
|
+
/* @__PURE__ */ jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
172
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3", stroke: "currentColor", strokeWidth: "2" })
|
|
173
|
+
] });
|
|
174
|
+
}
|
|
175
|
+
function EyeClosed() {
|
|
176
|
+
return /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: [
|
|
177
|
+
/* @__PURE__ */ jsx("path", { d: "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
178
|
+
/* @__PURE__ */ jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
179
|
+
] });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { Keypad, PinInput };
|
|
183
|
+
//# sourceMappingURL=index.js.map
|
|
184
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/PinInput.tsx","../src/Keypad.tsx"],"names":["els","jsx"],"mappings":"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","file":"index.js","sourcesContent":["'use client';\n\nimport React from 'react';\n\n/**\n * PinInput — a segmented numeric code input (OTP / PIN). Headless: it renders accessible\n * markup and you style every slot via `classNames` (bring your own classes). React-only,\n * no app/theme coupling, so it is safe for any consumer.\n *\n * The focus / auto-advance / backspace-cascade / paste / focus-guard behavior is the\n * device-proven academix logic, preserved exactly. Set `mask` for a hidden PIN (digits\n * render as `*` unless `revealed`); leave it off for a visible OTP.\n */\n\nexport interface PinInputClassNames {\n /** the boxes wrapper. */\n container?: string;\n /** appended to the container while `error` is true. */\n containerError?: string;\n /** each single-character box. */\n input?: string;\n}\n\nexport interface PinInputProps {\n length?: number;\n value: string;\n onChange: (value: string) => void;\n disabled?: boolean;\n error?: boolean;\n /** render digits masked (as `*`) unless `revealed`. */\n mask?: boolean;\n /** when `mask`, show the real digits. */\n revealed?: boolean;\n /** autofocus the first box on mount (when not in error). Default true. */\n autoFocus?: boolean;\n classNames?: PinInputClassNames;\n /** shortcut for `classNames.container`. */\n className?: string;\n /** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */\n inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;\n}\n\nexport function PinInput({\n length = 6,\n value,\n onChange,\n disabled = false,\n error = false,\n mask = false,\n revealed = false,\n autoFocus = true,\n classNames = {},\n className,\n inputProps,\n}: PinInputProps) {\n const inputs = Array(length).fill(0);\n\n const getFirstInvalidIndex = (val: string) => {\n for (let i = 0; i < length; i++) {\n if (!val[i]) return i;\n }\n return length - 1; // all full → last field\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>, index: number) => {\n const digit = e.target.value.replace(/\\D/g, '').slice(-1);\n\n const otpArray = value.split('');\n otpArray[index] = digit || '';\n const nextValue = otpArray.join('');\n\n onChange(nextValue);\n\n // compute allowed index from the new value\n const allowedIndex = getFirstInvalidIndex(nextValue);\n\n // Auto-advance only if digit is valid and index < allowed position\n if (digit && index < length - 1 && index < allowedIndex) {\n const next = e.target.nextElementSibling as HTMLInputElement | null;\n next?.focus();\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, index: number) => {\n if (e.key !== 'Backspace') return;\n\n // Error mode: clear everything\n if (error && value.length === length) {\n onChange('');\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n (els[0] as HTMLInputElement).focus();\n return;\n }\n\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Build a snapshot of the current actual values from DOM, not from state.\n const domValues = Array.from(els).map((input) => (input as HTMLInputElement).value);\n\n // If current field already empty → move back\n if (!domValues[index] && index > 0) {\n const prev = els[index - 1] as HTMLInputElement;\n prev.focus();\n }\n\n // Clear current char (and everything after) and update parent state\n const otpArray = value.split('');\n for (let i = index; i < otpArray.length; i++) {\n otpArray[i] = '';\n }\n onChange(otpArray.join(''));\n };\n\n const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n e.preventDefault();\n const pasted = e.clipboardData.getData('text').replace(/\\D/g, '');\n if (pasted) {\n onChange(pasted.slice(0, length));\n }\n };\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>, index: number) => {\n const els = e.currentTarget.parentElement!.querySelectorAll('input');\n\n // Determine focusable index based on ACTUAL input values\n let allowedIndex = 0;\n for (let i = 0; i < length; i++) {\n if ((els[i] as HTMLInputElement).value === '') {\n allowedIndex = i;\n break;\n }\n if (i === length - 1) allowedIndex = length - 1;\n }\n\n // Prevent skipping ahead\n if (index > allowedIndex) {\n (els[allowedIndex] as HTMLInputElement).focus();\n }\n };\n\n const display = (index: number) => {\n if (mask && !revealed) return value[index] ? '*' : '';\n return value[index] || '';\n };\n\n const containerClass = `${className ?? classNames.container ?? ''}${\n error && classNames.containerError ? ` ${classNames.containerError}` : ''\n }`.trim() || undefined;\n\n return (\n <div className={containerClass}>\n {inputs.map((_, index) => (\n <input\n {...inputProps}\n key={index}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={1}\n value={display(index)}\n onChange={(e) => handleChange(e, index)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n onPaste={handlePaste}\n disabled={disabled}\n className={classNames.input}\n autoFocus={autoFocus && index === 0 && !error}\n onFocus={(e) => handleFocus(e, index)}\n />\n ))}\n </div>\n );\n}\n","'use client';\n\nimport React from 'react';\n\n/**\n * Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)\n * that drives a `PinInput`'s value. Headless: style each slot via `classNames`. React-only.\n *\n * Behavior preserved from academix: append up to `length` digits; while `error` and the\n * value is full, the next digit replaces it and backspace clears it. Set `showMaskToggle`\n * for the PIN eye button (calls `onToggleReveal`); omit it for a plain OTP keypad.\n */\n\nexport interface KeypadClassNames {\n /** outer wrapper. */\n keypad?: string;\n /** the button grid. */\n grid?: string;\n /** every key (digits, backspace, toggle). */\n button?: string;\n /** appended to the backspace key. */\n backspace?: string;\n /** appended to the mask-toggle key. */\n toggle?: string;\n}\n\nexport interface KeypadProps {\n value: string;\n onChange: (value: string) => void;\n length?: number;\n disabled?: boolean;\n error?: boolean;\n /** render the reveal/mask (eye) toggle key. */\n showMaskToggle?: boolean;\n /** current reveal state (for the toggle icon/label). */\n revealed?: boolean;\n onToggleReveal?: (revealed: boolean) => void;\n /** backspace key content. Default `✕`. */\n backspaceLabel?: React.ReactNode;\n classNames?: KeypadClassNames;\n /** shortcut for `classNames.keypad`. */\n className?: string;\n}\n\nconst DIGITS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];\n\nexport function Keypad({\n value,\n onChange,\n length = 6,\n disabled = false,\n error,\n showMaskToggle = false,\n revealed = false,\n onToggleReveal,\n backspaceLabel = '✕',\n classNames = {},\n className,\n}: KeypadProps) {\n const handleDigitInput = (digit: number) => {\n if (value.length < length) {\n onChange(value + digit);\n } else if (!!error && value.length === length) {\n onChange(`${digit}`);\n }\n };\n\n const handleBackspace = () => {\n if (value.length > 0 && !error) {\n onChange(value.slice(0, -1));\n } else if (!!error && value.length === length) {\n onChange('');\n }\n };\n\n const btn = classNames.button ?? '';\n\n return (\n <div className={className ?? classNames.keypad}>\n <div className={classNames.grid}>\n {DIGITS.map((digit) => (\n <button\n key={digit}\n type=\"button\"\n onClick={() => handleDigitInput(digit)}\n disabled={disabled}\n className={btn || undefined}\n >\n {digit}\n </button>\n ))}\n <button\n type=\"button\"\n onClick={handleBackspace}\n disabled={disabled}\n className={`${btn} ${classNames.backspace ?? ''}`.trim() || undefined}\n >\n {backspaceLabel}\n </button>\n {showMaskToggle && (\n <button\n type=\"button\"\n onClick={() => onToggleReveal?.(!revealed)}\n disabled={disabled}\n className={`${btn} ${classNames.toggle ?? ''}`.trim() || undefined}\n title={revealed ? 'Hide' : 'Show'}\n aria-label={revealed ? 'Hide' : 'Show'}\n >\n {revealed ? <EyeOpen /> : <EyeClosed />}\n </button>\n )}\n </div>\n </div>\n );\n}\n\nfunction EyeOpen() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" stroke=\"currentColor\" strokeWidth=\"2\" />\n </svg>\n );\n}\nfunction EyeClosed() {\n return (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden>\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@academix-admin/pin-input",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless segmented code input (OTP / PIN) + on-screen numeric Keypad — accessible, device-proven focus/paste behavior, style every slot via classNames.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"otp",
|
|
8
|
+
"pin",
|
|
9
|
+
"code-input",
|
|
10
|
+
"one-time-password",
|
|
11
|
+
"keypad",
|
|
12
|
+
"headless"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Academix",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/academix-admin/academix-ui.git",
|
|
19
|
+
"directory": "packages/pin-input"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/academix-admin/academix-ui/tree/main/packages/pin-input#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/academix-admin/academix-ui/issues"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"main": "./dist/index.cjs",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"import": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"default": "./dist/index.js"
|
|
40
|
+
},
|
|
41
|
+
"require": {
|
|
42
|
+
"types": "./dist/index.d.cts",
|
|
43
|
+
"default": "./dist/index.cjs"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"./package.json": "./package.json"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"clean": "rimraf dist",
|
|
51
|
+
"dev": "tsup --watch",
|
|
52
|
+
"typecheck": "tsc --noEmit"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": ">=18.0.0",
|
|
56
|
+
"react-dom": ">=18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/react": "^19.0.0",
|
|
60
|
+
"@types/react-dom": "^19.0.0",
|
|
61
|
+
"react": "^18.3.1",
|
|
62
|
+
"react-dom": "^18.3.1",
|
|
63
|
+
"rimraf": "^6.0.1",
|
|
64
|
+
"tsup": "^8.3.5",
|
|
65
|
+
"typescript": "^5.9.3"
|
|
66
|
+
},
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"access": "public"
|
|
69
|
+
}
|
|
70
|
+
}
|