@academix-admin/pin-input 0.1.0 → 0.2.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 +46 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +46 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -11,6 +11,7 @@ function PinInput({
|
|
|
11
11
|
onChange,
|
|
12
12
|
disabled = false,
|
|
13
13
|
error = false,
|
|
14
|
+
loading = false,
|
|
14
15
|
mask = false,
|
|
15
16
|
revealed = false,
|
|
16
17
|
autoFocus = true,
|
|
@@ -19,6 +20,7 @@ function PinInput({
|
|
|
19
20
|
inputProps
|
|
20
21
|
}) {
|
|
21
22
|
const inputs = Array(length).fill(0);
|
|
23
|
+
const isDisabled = disabled || loading;
|
|
22
24
|
const getFirstInvalidIndex = (val) => {
|
|
23
25
|
for (let i = 0; i < length; i++) {
|
|
24
26
|
if (!val[i]) return i;
|
|
@@ -82,7 +84,7 @@ function PinInput({
|
|
|
82
84
|
if (mask && !revealed) return value[index] ? "*" : "";
|
|
83
85
|
return value[index] || "";
|
|
84
86
|
};
|
|
85
|
-
const containerClass = `${className ?? classNames.container ?? ""}${error && classNames.containerError ? ` ${classNames.containerError}` : ""}`.trim() || void 0;
|
|
87
|
+
const containerClass = `${className ?? classNames.container ?? ""}${error && classNames.containerError ? ` ${classNames.containerError}` : ""}${loading && classNames.containerLoading ? ` ${classNames.containerLoading}` : ""}`.trim() || void 0;
|
|
86
88
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: containerClass, children: inputs.map((_, index) => /* @__PURE__ */ react.createElement(
|
|
87
89
|
"input",
|
|
88
90
|
{
|
|
@@ -96,7 +98,7 @@ function PinInput({
|
|
|
96
98
|
onChange: (e) => handleChange(e, index),
|
|
97
99
|
onKeyDown: (e) => handleKeyDown(e, index),
|
|
98
100
|
onPaste: handlePaste,
|
|
99
|
-
disabled,
|
|
101
|
+
disabled: isDisabled,
|
|
100
102
|
className: classNames.input,
|
|
101
103
|
autoFocus: autoFocus && index === 0 && !error,
|
|
102
104
|
onFocus: (e) => handleFocus(e, index)
|
|
@@ -110,6 +112,8 @@ function Keypad({
|
|
|
110
112
|
length = 6,
|
|
111
113
|
disabled = false,
|
|
112
114
|
error,
|
|
115
|
+
loading = false,
|
|
116
|
+
loadingIndicator,
|
|
113
117
|
showMaskToggle = false,
|
|
114
118
|
revealed = false,
|
|
115
119
|
onToggleReveal,
|
|
@@ -117,6 +121,7 @@ function Keypad({
|
|
|
117
121
|
classNames = {},
|
|
118
122
|
className
|
|
119
123
|
}) {
|
|
124
|
+
const busy = disabled || loading;
|
|
120
125
|
const handleDigitInput = (digit) => {
|
|
121
126
|
if (value.length < length) {
|
|
122
127
|
onChange(value + digit);
|
|
@@ -132,41 +137,45 @@ function Keypad({
|
|
|
132
137
|
}
|
|
133
138
|
};
|
|
134
139
|
const btn = classNames.button ?? "";
|
|
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
|
-
|
|
140
|
+
const wrapperClass = `${className ?? classNames.keypad ?? ""}${loading && classNames.loading ? ` ${classNames.loading}` : ""}`.trim() || void 0;
|
|
141
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClass, "aria-busy": loading || void 0, children: [
|
|
142
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames.grid, children: [
|
|
143
|
+
DIGITS.map((digit) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
144
|
+
"button",
|
|
145
|
+
{
|
|
146
|
+
type: "button",
|
|
147
|
+
onClick: () => handleDigitInput(digit),
|
|
148
|
+
disabled: busy,
|
|
149
|
+
className: btn || void 0,
|
|
150
|
+
children: digit
|
|
151
|
+
},
|
|
152
|
+
digit
|
|
153
|
+
)),
|
|
154
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
155
|
+
"button",
|
|
156
|
+
{
|
|
157
|
+
type: "button",
|
|
158
|
+
onClick: handleBackspace,
|
|
159
|
+
disabled: busy,
|
|
160
|
+
className: `${btn} ${classNames.backspace ?? ""}`.trim() || void 0,
|
|
161
|
+
children: backspaceLabel
|
|
162
|
+
}
|
|
163
|
+
),
|
|
164
|
+
showMaskToggle && /* @__PURE__ */ jsxRuntime.jsx(
|
|
165
|
+
"button",
|
|
166
|
+
{
|
|
167
|
+
type: "button",
|
|
168
|
+
onClick: () => onToggleReveal?.(!revealed),
|
|
169
|
+
disabled: busy,
|
|
170
|
+
className: `${btn} ${classNames.toggle ?? ""}`.trim() || void 0,
|
|
171
|
+
title: revealed ? "Hide" : "Show",
|
|
172
|
+
"aria-label": revealed ? "Hide" : "Show",
|
|
173
|
+
children: revealed ? /* @__PURE__ */ jsxRuntime.jsx(EyeOpen, {}) : /* @__PURE__ */ jsxRuntime.jsx(EyeClosed, {})
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
] }),
|
|
177
|
+
loading && loadingIndicator
|
|
178
|
+
] });
|
|
170
179
|
}
|
|
171
180
|
function EyeOpen() {
|
|
172
181
|
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: [
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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"]}
|
|
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 /** appended to the container while `loading` is true. */\n containerLoading?: 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 /** a verify/submit is in flight — disables the boxes and applies `classNames.containerLoading`. */\n loading?: 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 loading = 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 const isDisabled = disabled || loading;\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 }${loading && classNames.containerLoading ? ` ${classNames.containerLoading}` : ''}`.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={isDisabled}\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 /** appended to the outer wrapper while `loading` (style your overlay/dimming here). */\n loading?: string;\n}\n\nexport interface KeypadProps {\n value: string;\n onChange: (value: string) => void;\n length?: number;\n disabled?: boolean;\n error?: boolean;\n /** disables every key (a verify/submit in flight) and applies `classNames.loading`. */\n loading?: boolean;\n /** rendered inside the wrapper while `loading` (e.g. a spinner); position via `classNames.loading`. */\n loadingIndicator?: React.ReactNode;\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 loading = false,\n loadingIndicator,\n showMaskToggle = false,\n revealed = false,\n onToggleReveal,\n backspaceLabel = '✕',\n classNames = {},\n className,\n}: KeypadProps) {\n const busy = disabled || loading;\n\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 const wrapperClass = `${className ?? classNames.keypad ?? ''}${\n loading && classNames.loading ? ` ${classNames.loading}` : ''\n }`.trim() || undefined;\n\n return (\n <div className={wrapperClass} aria-busy={loading || undefined}>\n <div className={classNames.grid}>\n {DIGITS.map((digit) => (\n <button\n key={digit}\n type=\"button\"\n onClick={() => handleDigitInput(digit)}\n disabled={busy}\n className={btn || undefined}\n >\n {digit}\n </button>\n ))}\n <button\n type=\"button\"\n onClick={handleBackspace}\n disabled={busy}\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={busy}\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 {loading && loadingIndicator}\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
CHANGED
|
@@ -14,6 +14,8 @@ interface PinInputClassNames {
|
|
|
14
14
|
container?: string;
|
|
15
15
|
/** appended to the container while `error` is true. */
|
|
16
16
|
containerError?: string;
|
|
17
|
+
/** appended to the container while `loading` is true. */
|
|
18
|
+
containerLoading?: string;
|
|
17
19
|
/** each single-character box. */
|
|
18
20
|
input?: string;
|
|
19
21
|
}
|
|
@@ -23,6 +25,8 @@ interface PinInputProps {
|
|
|
23
25
|
onChange: (value: string) => void;
|
|
24
26
|
disabled?: boolean;
|
|
25
27
|
error?: boolean;
|
|
28
|
+
/** a verify/submit is in flight — disables the boxes and applies `classNames.containerLoading`. */
|
|
29
|
+
loading?: boolean;
|
|
26
30
|
/** render digits masked (as `*`) unless `revealed`. */
|
|
27
31
|
mask?: boolean;
|
|
28
32
|
/** when `mask`, show the real digits. */
|
|
@@ -35,7 +39,7 @@ interface PinInputProps {
|
|
|
35
39
|
/** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */
|
|
36
40
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;
|
|
37
41
|
}
|
|
38
|
-
declare function PinInput({ length, value, onChange, disabled, error, mask, revealed, autoFocus, classNames, className, inputProps, }: PinInputProps): React.JSX.Element;
|
|
42
|
+
declare function PinInput({ length, value, onChange, disabled, error, loading, mask, revealed, autoFocus, classNames, className, inputProps, }: PinInputProps): React.JSX.Element;
|
|
39
43
|
|
|
40
44
|
/**
|
|
41
45
|
* Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)
|
|
@@ -56,6 +60,8 @@ interface KeypadClassNames {
|
|
|
56
60
|
backspace?: string;
|
|
57
61
|
/** appended to the mask-toggle key. */
|
|
58
62
|
toggle?: string;
|
|
63
|
+
/** appended to the outer wrapper while `loading` (style your overlay/dimming here). */
|
|
64
|
+
loading?: string;
|
|
59
65
|
}
|
|
60
66
|
interface KeypadProps {
|
|
61
67
|
value: string;
|
|
@@ -63,6 +69,10 @@ interface KeypadProps {
|
|
|
63
69
|
length?: number;
|
|
64
70
|
disabled?: boolean;
|
|
65
71
|
error?: boolean;
|
|
72
|
+
/** disables every key (a verify/submit in flight) and applies `classNames.loading`. */
|
|
73
|
+
loading?: boolean;
|
|
74
|
+
/** rendered inside the wrapper while `loading` (e.g. a spinner); position via `classNames.loading`. */
|
|
75
|
+
loadingIndicator?: React.ReactNode;
|
|
66
76
|
/** render the reveal/mask (eye) toggle key. */
|
|
67
77
|
showMaskToggle?: boolean;
|
|
68
78
|
/** current reveal state (for the toggle icon/label). */
|
|
@@ -74,6 +84,6 @@ interface KeypadProps {
|
|
|
74
84
|
/** shortcut for `classNames.keypad`. */
|
|
75
85
|
className?: string;
|
|
76
86
|
}
|
|
77
|
-
declare function Keypad({ value, onChange, length, disabled, error, showMaskToggle, revealed, onToggleReveal, backspaceLabel, classNames, className, }: KeypadProps): React.JSX.Element;
|
|
87
|
+
declare function Keypad({ value, onChange, length, disabled, error, loading, loadingIndicator, showMaskToggle, revealed, onToggleReveal, backspaceLabel, classNames, className, }: KeypadProps): React.JSX.Element;
|
|
78
88
|
|
|
79
89
|
export { Keypad, type KeypadClassNames, type KeypadProps, PinInput, type PinInputClassNames, type PinInputProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ interface PinInputClassNames {
|
|
|
14
14
|
container?: string;
|
|
15
15
|
/** appended to the container while `error` is true. */
|
|
16
16
|
containerError?: string;
|
|
17
|
+
/** appended to the container while `loading` is true. */
|
|
18
|
+
containerLoading?: string;
|
|
17
19
|
/** each single-character box. */
|
|
18
20
|
input?: string;
|
|
19
21
|
}
|
|
@@ -23,6 +25,8 @@ interface PinInputProps {
|
|
|
23
25
|
onChange: (value: string) => void;
|
|
24
26
|
disabled?: boolean;
|
|
25
27
|
error?: boolean;
|
|
28
|
+
/** a verify/submit is in flight — disables the boxes and applies `classNames.containerLoading`. */
|
|
29
|
+
loading?: boolean;
|
|
26
30
|
/** render digits masked (as `*`) unless `revealed`. */
|
|
27
31
|
mask?: boolean;
|
|
28
32
|
/** when `mask`, show the real digits. */
|
|
@@ -35,7 +39,7 @@ interface PinInputProps {
|
|
|
35
39
|
/** spread onto every box (e.g. password-manager suppression attrs, incl. `data-*`). */
|
|
36
40
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement> & Record<`data-${string}`, unknown>;
|
|
37
41
|
}
|
|
38
|
-
declare function PinInput({ length, value, onChange, disabled, error, mask, revealed, autoFocus, classNames, className, inputProps, }: PinInputProps): React.JSX.Element;
|
|
42
|
+
declare function PinInput({ length, value, onChange, disabled, error, loading, mask, revealed, autoFocus, classNames, className, inputProps, }: PinInputProps): React.JSX.Element;
|
|
39
43
|
|
|
40
44
|
/**
|
|
41
45
|
* Keypad — an on-screen numeric keypad (0-9 + backspace, optional reveal/mask toggle)
|
|
@@ -56,6 +60,8 @@ interface KeypadClassNames {
|
|
|
56
60
|
backspace?: string;
|
|
57
61
|
/** appended to the mask-toggle key. */
|
|
58
62
|
toggle?: string;
|
|
63
|
+
/** appended to the outer wrapper while `loading` (style your overlay/dimming here). */
|
|
64
|
+
loading?: string;
|
|
59
65
|
}
|
|
60
66
|
interface KeypadProps {
|
|
61
67
|
value: string;
|
|
@@ -63,6 +69,10 @@ interface KeypadProps {
|
|
|
63
69
|
length?: number;
|
|
64
70
|
disabled?: boolean;
|
|
65
71
|
error?: boolean;
|
|
72
|
+
/** disables every key (a verify/submit in flight) and applies `classNames.loading`. */
|
|
73
|
+
loading?: boolean;
|
|
74
|
+
/** rendered inside the wrapper while `loading` (e.g. a spinner); position via `classNames.loading`. */
|
|
75
|
+
loadingIndicator?: React.ReactNode;
|
|
66
76
|
/** render the reveal/mask (eye) toggle key. */
|
|
67
77
|
showMaskToggle?: boolean;
|
|
68
78
|
/** current reveal state (for the toggle icon/label). */
|
|
@@ -74,6 +84,6 @@ interface KeypadProps {
|
|
|
74
84
|
/** shortcut for `classNames.keypad`. */
|
|
75
85
|
className?: string;
|
|
76
86
|
}
|
|
77
|
-
declare function Keypad({ value, onChange, length, disabled, error, showMaskToggle, revealed, onToggleReveal, backspaceLabel, classNames, className, }: KeypadProps): React.JSX.Element;
|
|
87
|
+
declare function Keypad({ value, onChange, length, disabled, error, loading, loadingIndicator, showMaskToggle, revealed, onToggleReveal, backspaceLabel, classNames, className, }: KeypadProps): React.JSX.Element;
|
|
78
88
|
|
|
79
89
|
export { Keypad, type KeypadClassNames, type KeypadProps, PinInput, type PinInputClassNames, type PinInputProps };
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ function PinInput({
|
|
|
9
9
|
onChange,
|
|
10
10
|
disabled = false,
|
|
11
11
|
error = false,
|
|
12
|
+
loading = false,
|
|
12
13
|
mask = false,
|
|
13
14
|
revealed = false,
|
|
14
15
|
autoFocus = true,
|
|
@@ -17,6 +18,7 @@ function PinInput({
|
|
|
17
18
|
inputProps
|
|
18
19
|
}) {
|
|
19
20
|
const inputs = Array(length).fill(0);
|
|
21
|
+
const isDisabled = disabled || loading;
|
|
20
22
|
const getFirstInvalidIndex = (val) => {
|
|
21
23
|
for (let i = 0; i < length; i++) {
|
|
22
24
|
if (!val[i]) return i;
|
|
@@ -80,7 +82,7 @@ function PinInput({
|
|
|
80
82
|
if (mask && !revealed) return value[index] ? "*" : "";
|
|
81
83
|
return value[index] || "";
|
|
82
84
|
};
|
|
83
|
-
const containerClass = `${className ?? classNames.container ?? ""}${error && classNames.containerError ? ` ${classNames.containerError}` : ""}`.trim() || void 0;
|
|
85
|
+
const containerClass = `${className ?? classNames.container ?? ""}${error && classNames.containerError ? ` ${classNames.containerError}` : ""}${loading && classNames.containerLoading ? ` ${classNames.containerLoading}` : ""}`.trim() || void 0;
|
|
84
86
|
return /* @__PURE__ */ jsx("div", { className: containerClass, children: inputs.map((_, index) => /* @__PURE__ */ createElement(
|
|
85
87
|
"input",
|
|
86
88
|
{
|
|
@@ -94,7 +96,7 @@ function PinInput({
|
|
|
94
96
|
onChange: (e) => handleChange(e, index),
|
|
95
97
|
onKeyDown: (e) => handleKeyDown(e, index),
|
|
96
98
|
onPaste: handlePaste,
|
|
97
|
-
disabled,
|
|
99
|
+
disabled: isDisabled,
|
|
98
100
|
className: classNames.input,
|
|
99
101
|
autoFocus: autoFocus && index === 0 && !error,
|
|
100
102
|
onFocus: (e) => handleFocus(e, index)
|
|
@@ -108,6 +110,8 @@ function Keypad({
|
|
|
108
110
|
length = 6,
|
|
109
111
|
disabled = false,
|
|
110
112
|
error,
|
|
113
|
+
loading = false,
|
|
114
|
+
loadingIndicator,
|
|
111
115
|
showMaskToggle = false,
|
|
112
116
|
revealed = false,
|
|
113
117
|
onToggleReveal,
|
|
@@ -115,6 +119,7 @@ function Keypad({
|
|
|
115
119
|
classNames = {},
|
|
116
120
|
className
|
|
117
121
|
}) {
|
|
122
|
+
const busy = disabled || loading;
|
|
118
123
|
const handleDigitInput = (digit) => {
|
|
119
124
|
if (value.length < length) {
|
|
120
125
|
onChange(value + digit);
|
|
@@ -130,41 +135,45 @@ function Keypad({
|
|
|
130
135
|
}
|
|
131
136
|
};
|
|
132
137
|
const btn = classNames.button ?? "";
|
|
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
|
-
|
|
138
|
+
const wrapperClass = `${className ?? classNames.keypad ?? ""}${loading && classNames.loading ? ` ${classNames.loading}` : ""}`.trim() || void 0;
|
|
139
|
+
return /* @__PURE__ */ jsxs("div", { className: wrapperClass, "aria-busy": loading || void 0, children: [
|
|
140
|
+
/* @__PURE__ */ jsxs("div", { className: classNames.grid, children: [
|
|
141
|
+
DIGITS.map((digit) => /* @__PURE__ */ jsx(
|
|
142
|
+
"button",
|
|
143
|
+
{
|
|
144
|
+
type: "button",
|
|
145
|
+
onClick: () => handleDigitInput(digit),
|
|
146
|
+
disabled: busy,
|
|
147
|
+
className: btn || void 0,
|
|
148
|
+
children: digit
|
|
149
|
+
},
|
|
150
|
+
digit
|
|
151
|
+
)),
|
|
152
|
+
/* @__PURE__ */ jsx(
|
|
153
|
+
"button",
|
|
154
|
+
{
|
|
155
|
+
type: "button",
|
|
156
|
+
onClick: handleBackspace,
|
|
157
|
+
disabled: busy,
|
|
158
|
+
className: `${btn} ${classNames.backspace ?? ""}`.trim() || void 0,
|
|
159
|
+
children: backspaceLabel
|
|
160
|
+
}
|
|
161
|
+
),
|
|
162
|
+
showMaskToggle && /* @__PURE__ */ jsx(
|
|
163
|
+
"button",
|
|
164
|
+
{
|
|
165
|
+
type: "button",
|
|
166
|
+
onClick: () => onToggleReveal?.(!revealed),
|
|
167
|
+
disabled: busy,
|
|
168
|
+
className: `${btn} ${classNames.toggle ?? ""}`.trim() || void 0,
|
|
169
|
+
title: revealed ? "Hide" : "Show",
|
|
170
|
+
"aria-label": revealed ? "Hide" : "Show",
|
|
171
|
+
children: revealed ? /* @__PURE__ */ jsx(EyeOpen, {}) : /* @__PURE__ */ jsx(EyeClosed, {})
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
] }),
|
|
175
|
+
loading && loadingIndicator
|
|
176
|
+
] });
|
|
168
177
|
}
|
|
169
178
|
function EyeOpen() {
|
|
170
179
|
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: [
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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 /** appended to the container while `loading` is true. */\n containerLoading?: 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 /** a verify/submit is in flight — disables the boxes and applies `classNames.containerLoading`. */\n loading?: 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 loading = 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 const isDisabled = disabled || loading;\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 }${loading && classNames.containerLoading ? ` ${classNames.containerLoading}` : ''}`.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={isDisabled}\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 /** appended to the outer wrapper while `loading` (style your overlay/dimming here). */\n loading?: string;\n}\n\nexport interface KeypadProps {\n value: string;\n onChange: (value: string) => void;\n length?: number;\n disabled?: boolean;\n error?: boolean;\n /** disables every key (a verify/submit in flight) and applies `classNames.loading`. */\n loading?: boolean;\n /** rendered inside the wrapper while `loading` (e.g. a spinner); position via `classNames.loading`. */\n loadingIndicator?: React.ReactNode;\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 loading = false,\n loadingIndicator,\n showMaskToggle = false,\n revealed = false,\n onToggleReveal,\n backspaceLabel = '✕',\n classNames = {},\n className,\n}: KeypadProps) {\n const busy = disabled || loading;\n\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 const wrapperClass = `${className ?? classNames.keypad ?? ''}${\n loading && classNames.loading ? ` ${classNames.loading}` : ''\n }`.trim() || undefined;\n\n return (\n <div className={wrapperClass} aria-busy={loading || undefined}>\n <div className={classNames.grid}>\n {DIGITS.map((digit) => (\n <button\n key={digit}\n type=\"button\"\n onClick={() => handleDigitInput(digit)}\n disabled={busy}\n className={btn || undefined}\n >\n {digit}\n </button>\n ))}\n <button\n type=\"button\"\n onClick={handleBackspace}\n disabled={busy}\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={busy}\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 {loading && loadingIndicator}\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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@academix-admin/pin-input",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Headless segmented code input (OTP / PIN) + on-screen numeric Keypad — accessible, device-proven focus/paste behavior, style every slot via classNames.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|