@verma-consulting/design-library 0.1.47 → 0.1.48

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/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Shared MUI-based React components for Verma Consulting applications. Use this package for consistent UI patterns, forms, and design tokens across projects.
4
4
 
5
+ ## Requirements
6
+
7
+ - **Node.js** `>=24.4.1` (see `engines` in `package.json`). For local development, use **24.4.1** via `.nvmrc`, `.node-version`, `.mise.toml`, or Volta (`volta` field in `package.json`).
8
+
5
9
  ## Installation
6
10
 
7
11
  ```bash
@@ -60,6 +64,7 @@ The library re-exports **all of `@mui/material`**, so you can use MUI components
60
64
  | [FormSnackBar](./docs/FormSnackBar.md) | Snackbar for form success/error messages. |
61
65
  | [SearchableSelect](./docs/SearchableSelect.md) | Searchable single/multi select with view/edit modes. |
62
66
  | [PhoneNumberField](./docs/PhoneNumberField.md) | Phone input with country code and E.164 output. |
67
+ | [OTPField](./docs/OTPField.md) | One-time code input (single-character cells, paste, mask). |
63
68
  | [InputFileUpload](./docs/InputFileUpload.md) | Button that opens a file picker. |
64
69
  | [ImageUploadAvatar](./docs/ImageUploadAvatar.md) | Avatar-style image upload with preview and clear. |
65
70
  | [TabPanel](./docs/TabPanel.md) | Panel content for MUI Tabs (by index). |
package/dist/index.d.mts CHANGED
@@ -2,7 +2,7 @@ import { DialogProps, DrawerProps } from '@mui/material';
2
2
  export * from '@mui/material';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import * as React from 'react';
5
- import React__default, { FC, KeyboardEvent } from 'react';
5
+ import React__default, { FC, KeyboardEvent, CSSProperties } from 'react';
6
6
  import MuiTablePagination, { LabelDisplayedRowsArgs } from '@mui/material/TablePagination';
7
7
  import { SxProps, Theme } from '@mui/material/styles';
8
8
  export { ThemeProvider, createTheme, styled, useTheme } from '@mui/material/styles';
@@ -210,4 +210,24 @@ interface CountrySelectProps {
210
210
  }
211
211
  declare const CountrySelect: React__default.FC<CountrySelectProps>;
212
212
 
213
- export { CountrySelect, type CountryType, EmptyState, FormDialog, FormDrawer, FormPopover, FormSnackBar, IOSSwitch, ImageUploadAvatar, InputFileUpload, Loader, Logo, PhoneNumberField, Pill, SearchableSelect, SkeletonBar, StatusPill, TabPanel, TablePagination, type TablePaginationDesignProps };
213
+ type OTPFieldProps = {
214
+ length: number;
215
+ initialValue?: string;
216
+ onChange: (value: string, index?: number) => void;
217
+ onComplete?: (value: string, index?: number) => void;
218
+ /** Use password inputs (no brief reveal; React 19–safe replacement for `react-pin-input` `secret`). */
219
+ secret?: boolean;
220
+ autoSelect?: boolean;
221
+ disabled?: boolean;
222
+ /** Applied to the focused cell’s outline (`fieldset`). */
223
+ inputFocusStyle?: CSSProperties;
224
+ /** Applied to the outer `Box` (MUI `sx`). */
225
+ sx?: SxProps<Theme>;
226
+ type?: string;
227
+ inputMode?: React__default.HTMLAttributes<HTMLInputElement>["inputMode"];
228
+ /** Optional per-character filter (same idea as `react-pin-input` `regexCriteria`). */
229
+ regexCriteria?: RegExp;
230
+ };
231
+ declare const OTPField: React__default.FC<OTPFieldProps>;
232
+
233
+ export { CountrySelect, type CountryType, EmptyState, FormDialog, FormDrawer, FormPopover, FormSnackBar, IOSSwitch, ImageUploadAvatar, InputFileUpload, Loader, Logo, OTPField, type OTPFieldProps, PhoneNumberField, Pill, SearchableSelect, SkeletonBar, StatusPill, TabPanel, TablePagination, type TablePaginationDesignProps };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { DialogProps, DrawerProps } from '@mui/material';
2
2
  export * from '@mui/material';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import * as React from 'react';
5
- import React__default, { FC, KeyboardEvent } from 'react';
5
+ import React__default, { FC, KeyboardEvent, CSSProperties } from 'react';
6
6
  import MuiTablePagination, { LabelDisplayedRowsArgs } from '@mui/material/TablePagination';
7
7
  import { SxProps, Theme } from '@mui/material/styles';
8
8
  export { ThemeProvider, createTheme, styled, useTheme } from '@mui/material/styles';
@@ -210,4 +210,24 @@ interface CountrySelectProps {
210
210
  }
211
211
  declare const CountrySelect: React__default.FC<CountrySelectProps>;
212
212
 
213
- export { CountrySelect, type CountryType, EmptyState, FormDialog, FormDrawer, FormPopover, FormSnackBar, IOSSwitch, ImageUploadAvatar, InputFileUpload, Loader, Logo, PhoneNumberField, Pill, SearchableSelect, SkeletonBar, StatusPill, TabPanel, TablePagination, type TablePaginationDesignProps };
213
+ type OTPFieldProps = {
214
+ length: number;
215
+ initialValue?: string;
216
+ onChange: (value: string, index?: number) => void;
217
+ onComplete?: (value: string, index?: number) => void;
218
+ /** Use password inputs (no brief reveal; React 19–safe replacement for `react-pin-input` `secret`). */
219
+ secret?: boolean;
220
+ autoSelect?: boolean;
221
+ disabled?: boolean;
222
+ /** Applied to the focused cell’s outline (`fieldset`). */
223
+ inputFocusStyle?: CSSProperties;
224
+ /** Applied to the outer `Box` (MUI `sx`). */
225
+ sx?: SxProps<Theme>;
226
+ type?: string;
227
+ inputMode?: React__default.HTMLAttributes<HTMLInputElement>["inputMode"];
228
+ /** Optional per-character filter (same idea as `react-pin-input` `regexCriteria`). */
229
+ regexCriteria?: RegExp;
230
+ };
231
+ declare const OTPField: React__default.FC<OTPFieldProps>;
232
+
233
+ export { CountrySelect, type CountryType, EmptyState, FormDialog, FormDrawer, FormPopover, FormSnackBar, IOSSwitch, ImageUploadAvatar, InputFileUpload, Loader, Logo, OTPField, type OTPFieldProps, PhoneNumberField, Pill, SearchableSelect, SkeletonBar, StatusPill, TabPanel, TablePagination, type TablePaginationDesignProps };
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ __export(index_exports, {
42
42
  InputFileUpload: () => InputFileUpload_default,
43
43
  Loader: () => Loader_default,
44
44
  Logo: () => Logo_default,
45
+ OTPField: () => OTPField_default,
45
46
  PhoneNumberField: () => PhoneNumberField_default,
46
47
  Pill: () => Pill_default,
47
48
  SearchableSelect: () => SearchableSelect_default,
@@ -2274,6 +2275,178 @@ var CountrySelect = ({
2274
2275
  );
2275
2276
  };
2276
2277
  var CountrySelect_default = CountrySelect;
2278
+
2279
+ // src/OTPField.tsx
2280
+ var import_react11 = require("react");
2281
+ var import_material17 = require("@mui/material");
2282
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2283
+ function toCells(s, len) {
2284
+ const slice = (s || "").slice(0, len);
2285
+ return Array.from({ length: len }, (_, i) => {
2286
+ var _a2;
2287
+ return (_a2 = slice[i]) != null ? _a2 : "";
2288
+ });
2289
+ }
2290
+ var OTPField = ({
2291
+ length,
2292
+ initialValue = "",
2293
+ onChange,
2294
+ onComplete,
2295
+ secret = false,
2296
+ autoSelect = false,
2297
+ disabled = false,
2298
+ inputFocusStyle,
2299
+ sx,
2300
+ type = "text",
2301
+ inputMode = "text",
2302
+ regexCriteria
2303
+ }) => {
2304
+ const [cells, setCells] = (0, import_react11.useState)(() => toCells(initialValue, length));
2305
+ const cellsRef = (0, import_react11.useRef)(cells);
2306
+ cellsRef.current = cells;
2307
+ const refs = (0, import_react11.useRef)([]);
2308
+ (0, import_react11.useEffect)(() => {
2309
+ setCells(toCells(initialValue, length));
2310
+ }, [initialValue, length]);
2311
+ const emit = (0, import_react11.useCallback)(
2312
+ (next, editedIndex) => {
2313
+ setCells(next);
2314
+ const joined = next.join("");
2315
+ onChange(joined, editedIndex);
2316
+ if (onComplete && joined.length === length && next.every(Boolean)) {
2317
+ onComplete(joined, length - 1);
2318
+ }
2319
+ },
2320
+ [length, onChange, onComplete]
2321
+ );
2322
+ (0, import_react11.useEffect)(() => {
2323
+ if (!autoSelect) return;
2324
+ const id = requestAnimationFrame(() => {
2325
+ var _a2;
2326
+ return (_a2 = refs.current[0]) == null ? void 0 : _a2.focus();
2327
+ });
2328
+ return () => cancelAnimationFrame(id);
2329
+ }, [autoSelect, length]);
2330
+ const charAllowed = (0, import_react11.useCallback)(
2331
+ (ch) => {
2332
+ if (!ch) return true;
2333
+ if (type === "numeric" && /\D/.test(ch)) return false;
2334
+ if (regexCriteria && !regexCriteria.test(ch)) return false;
2335
+ return true;
2336
+ },
2337
+ [regexCriteria, type]
2338
+ );
2339
+ const applyMany = (0, import_react11.useCallback)(
2340
+ (startIdx, raw) => {
2341
+ var _a2;
2342
+ const incoming = (type === "numeric" ? raw.replace(/\D/g, "") : raw).split("");
2343
+ const next = [...cellsRef.current];
2344
+ let i = startIdx;
2345
+ for (const ch of incoming) {
2346
+ if (i >= length) break;
2347
+ if (!charAllowed(ch)) return;
2348
+ next[i] = ch;
2349
+ i += 1;
2350
+ }
2351
+ const focusAt = Math.min(Math.max(i - 1, 0), length - 1);
2352
+ emit(next, focusAt);
2353
+ (_a2 = refs.current[focusAt]) == null ? void 0 : _a2.focus();
2354
+ },
2355
+ [charAllowed, emit, length, type]
2356
+ );
2357
+ const onFieldChange = (idx, e) => {
2358
+ var _a2;
2359
+ const raw = e.target.value;
2360
+ if (raw.length > 1) {
2361
+ applyMany(idx, raw);
2362
+ return;
2363
+ }
2364
+ if (raw && !charAllowed(raw)) return;
2365
+ const next = [...cellsRef.current];
2366
+ next[idx] = raw;
2367
+ emit(next, idx);
2368
+ if (raw && idx < length - 1) {
2369
+ (_a2 = refs.current[idx + 1]) == null ? void 0 : _a2.focus();
2370
+ }
2371
+ };
2372
+ const onKeyDown = (idx, e) => {
2373
+ var _a2, _b, _c;
2374
+ if (e.key === "Backspace") {
2375
+ const next = [...cellsRef.current];
2376
+ if (next[idx]) {
2377
+ next[idx] = "";
2378
+ emit(next, idx);
2379
+ } else if (idx > 0) {
2380
+ next[idx - 1] = "";
2381
+ emit(next, idx - 1);
2382
+ (_a2 = refs.current[idx - 1]) == null ? void 0 : _a2.focus();
2383
+ }
2384
+ e.preventDefault();
2385
+ } else if (e.key === "ArrowLeft" && idx > 0) {
2386
+ (_b = refs.current[idx - 1]) == null ? void 0 : _b.focus();
2387
+ e.preventDefault();
2388
+ } else if (e.key === "ArrowRight" && idx < length - 1) {
2389
+ (_c = refs.current[idx + 1]) == null ? void 0 : _c.focus();
2390
+ e.preventDefault();
2391
+ }
2392
+ };
2393
+ const onPasteFirst = (e) => {
2394
+ e.preventDefault();
2395
+ const text = e.clipboardData.getData("text").replace(/\s/g, "");
2396
+ if (!text) return;
2397
+ applyMany(0, text);
2398
+ };
2399
+ const inputType = secret ? "password" : type === "numeric" ? "tel" : "text";
2400
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2401
+ import_material17.Box,
2402
+ {
2403
+ sx: [
2404
+ {
2405
+ display: "flex",
2406
+ gap: 1,
2407
+ flexWrap: "nowrap",
2408
+ justifyContent: "center",
2409
+ alignItems: "center"
2410
+ },
2411
+ ...sx == null ? [] : Array.isArray(sx) ? sx : [sx]
2412
+ ],
2413
+ children: Array.from({ length }).map((_, idx) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2414
+ import_material17.TextField,
2415
+ {
2416
+ inputRef: (el) => {
2417
+ refs.current[idx] = el;
2418
+ },
2419
+ value: cells[idx],
2420
+ onChange: (e) => onFieldChange(idx, e),
2421
+ onKeyDown: (e) => onKeyDown(idx, e),
2422
+ onPaste: idx === 0 ? onPasteFirst : void 0,
2423
+ disabled,
2424
+ size: "small",
2425
+ type: inputType,
2426
+ sx: {
2427
+ width: 44,
2428
+ "& .MuiOutlinedInput-input": {
2429
+ textAlign: "center",
2430
+ py: 1,
2431
+ px: 0.5
2432
+ },
2433
+ ...inputFocusStyle ? {
2434
+ "& .MuiOutlinedInput-root.Mui-focused fieldset": inputFocusStyle
2435
+ } : {}
2436
+ },
2437
+ inputProps: {
2438
+ maxLength: 1,
2439
+ inputMode: inputMode != null ? inputMode : void 0,
2440
+ "aria-label": `Code character ${idx + 1} of ${length}`,
2441
+ autoComplete: "one-time-code"
2442
+ }
2443
+ },
2444
+ idx
2445
+ ))
2446
+ }
2447
+ );
2448
+ };
2449
+ var OTPField_default = OTPField;
2277
2450
  // Annotate the CommonJS export names for ESM import in node:
2278
2451
  0 && (module.exports = {
2279
2452
  CountrySelect,
@@ -2287,6 +2460,7 @@ var CountrySelect_default = CountrySelect;
2287
2460
  InputFileUpload,
2288
2461
  Loader,
2289
2462
  Logo,
2463
+ OTPField,
2290
2464
  PhoneNumberField,
2291
2465
  Pill,
2292
2466
  SearchableSelect,