@verma-consulting/design-library 0.1.47 → 0.1.49
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 +5 -0
- package/dist/index.d.mts +22 -2
- package/dist/index.d.ts +22 -2
- package/dist/index.js +174 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +178 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -2
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
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -1653,7 +1654,6 @@ var FormDrawer = ({
|
|
|
1653
1654
|
position: "relative",
|
|
1654
1655
|
flexShrink: 0,
|
|
1655
1656
|
p: 3,
|
|
1656
|
-
borderBottom: (t) => `1px solid ${(0, import_styles14.alpha)(t.palette.divider, 0.36)}`,
|
|
1657
1657
|
bgcolor: "background.paper"
|
|
1658
1658
|
},
|
|
1659
1659
|
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_material14.Grid, { container: true, children: [
|
|
@@ -2274,6 +2274,178 @@ var CountrySelect = ({
|
|
|
2274
2274
|
);
|
|
2275
2275
|
};
|
|
2276
2276
|
var CountrySelect_default = CountrySelect;
|
|
2277
|
+
|
|
2278
|
+
// src/OTPField.tsx
|
|
2279
|
+
var import_react11 = require("react");
|
|
2280
|
+
var import_material17 = require("@mui/material");
|
|
2281
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
2282
|
+
function toCells(s, len) {
|
|
2283
|
+
const slice = (s || "").slice(0, len);
|
|
2284
|
+
return Array.from({ length: len }, (_, i) => {
|
|
2285
|
+
var _a2;
|
|
2286
|
+
return (_a2 = slice[i]) != null ? _a2 : "";
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
var OTPField = ({
|
|
2290
|
+
length,
|
|
2291
|
+
initialValue = "",
|
|
2292
|
+
onChange,
|
|
2293
|
+
onComplete,
|
|
2294
|
+
secret = false,
|
|
2295
|
+
autoSelect = false,
|
|
2296
|
+
disabled = false,
|
|
2297
|
+
inputFocusStyle,
|
|
2298
|
+
sx,
|
|
2299
|
+
type = "text",
|
|
2300
|
+
inputMode = "text",
|
|
2301
|
+
regexCriteria
|
|
2302
|
+
}) => {
|
|
2303
|
+
const [cells, setCells] = (0, import_react11.useState)(() => toCells(initialValue, length));
|
|
2304
|
+
const cellsRef = (0, import_react11.useRef)(cells);
|
|
2305
|
+
cellsRef.current = cells;
|
|
2306
|
+
const refs = (0, import_react11.useRef)([]);
|
|
2307
|
+
(0, import_react11.useEffect)(() => {
|
|
2308
|
+
setCells(toCells(initialValue, length));
|
|
2309
|
+
}, [initialValue, length]);
|
|
2310
|
+
const emit = (0, import_react11.useCallback)(
|
|
2311
|
+
(next, editedIndex) => {
|
|
2312
|
+
setCells(next);
|
|
2313
|
+
const joined = next.join("");
|
|
2314
|
+
onChange(joined, editedIndex);
|
|
2315
|
+
if (onComplete && joined.length === length && next.every(Boolean)) {
|
|
2316
|
+
onComplete(joined, length - 1);
|
|
2317
|
+
}
|
|
2318
|
+
},
|
|
2319
|
+
[length, onChange, onComplete]
|
|
2320
|
+
);
|
|
2321
|
+
(0, import_react11.useEffect)(() => {
|
|
2322
|
+
if (!autoSelect) return;
|
|
2323
|
+
const id = requestAnimationFrame(() => {
|
|
2324
|
+
var _a2;
|
|
2325
|
+
return (_a2 = refs.current[0]) == null ? void 0 : _a2.focus();
|
|
2326
|
+
});
|
|
2327
|
+
return () => cancelAnimationFrame(id);
|
|
2328
|
+
}, [autoSelect, length]);
|
|
2329
|
+
const charAllowed = (0, import_react11.useCallback)(
|
|
2330
|
+
(ch) => {
|
|
2331
|
+
if (!ch) return true;
|
|
2332
|
+
if (type === "numeric" && /\D/.test(ch)) return false;
|
|
2333
|
+
if (regexCriteria && !regexCriteria.test(ch)) return false;
|
|
2334
|
+
return true;
|
|
2335
|
+
},
|
|
2336
|
+
[regexCriteria, type]
|
|
2337
|
+
);
|
|
2338
|
+
const applyMany = (0, import_react11.useCallback)(
|
|
2339
|
+
(startIdx, raw) => {
|
|
2340
|
+
var _a2;
|
|
2341
|
+
const incoming = (type === "numeric" ? raw.replace(/\D/g, "") : raw).split("");
|
|
2342
|
+
const next = [...cellsRef.current];
|
|
2343
|
+
let i = startIdx;
|
|
2344
|
+
for (const ch of incoming) {
|
|
2345
|
+
if (i >= length) break;
|
|
2346
|
+
if (!charAllowed(ch)) return;
|
|
2347
|
+
next[i] = ch;
|
|
2348
|
+
i += 1;
|
|
2349
|
+
}
|
|
2350
|
+
const focusAt = Math.min(Math.max(i - 1, 0), length - 1);
|
|
2351
|
+
emit(next, focusAt);
|
|
2352
|
+
(_a2 = refs.current[focusAt]) == null ? void 0 : _a2.focus();
|
|
2353
|
+
},
|
|
2354
|
+
[charAllowed, emit, length, type]
|
|
2355
|
+
);
|
|
2356
|
+
const onFieldChange = (idx, e) => {
|
|
2357
|
+
var _a2;
|
|
2358
|
+
const raw = e.target.value;
|
|
2359
|
+
if (raw.length > 1) {
|
|
2360
|
+
applyMany(idx, raw);
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2363
|
+
if (raw && !charAllowed(raw)) return;
|
|
2364
|
+
const next = [...cellsRef.current];
|
|
2365
|
+
next[idx] = raw;
|
|
2366
|
+
emit(next, idx);
|
|
2367
|
+
if (raw && idx < length - 1) {
|
|
2368
|
+
(_a2 = refs.current[idx + 1]) == null ? void 0 : _a2.focus();
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
const onKeyDown = (idx, e) => {
|
|
2372
|
+
var _a2, _b, _c;
|
|
2373
|
+
if (e.key === "Backspace") {
|
|
2374
|
+
const next = [...cellsRef.current];
|
|
2375
|
+
if (next[idx]) {
|
|
2376
|
+
next[idx] = "";
|
|
2377
|
+
emit(next, idx);
|
|
2378
|
+
} else if (idx > 0) {
|
|
2379
|
+
next[idx - 1] = "";
|
|
2380
|
+
emit(next, idx - 1);
|
|
2381
|
+
(_a2 = refs.current[idx - 1]) == null ? void 0 : _a2.focus();
|
|
2382
|
+
}
|
|
2383
|
+
e.preventDefault();
|
|
2384
|
+
} else if (e.key === "ArrowLeft" && idx > 0) {
|
|
2385
|
+
(_b = refs.current[idx - 1]) == null ? void 0 : _b.focus();
|
|
2386
|
+
e.preventDefault();
|
|
2387
|
+
} else if (e.key === "ArrowRight" && idx < length - 1) {
|
|
2388
|
+
(_c = refs.current[idx + 1]) == null ? void 0 : _c.focus();
|
|
2389
|
+
e.preventDefault();
|
|
2390
|
+
}
|
|
2391
|
+
};
|
|
2392
|
+
const onPasteFirst = (e) => {
|
|
2393
|
+
e.preventDefault();
|
|
2394
|
+
const text = e.clipboardData.getData("text").replace(/\s/g, "");
|
|
2395
|
+
if (!text) return;
|
|
2396
|
+
applyMany(0, text);
|
|
2397
|
+
};
|
|
2398
|
+
const inputType = secret ? "password" : type === "numeric" ? "tel" : "text";
|
|
2399
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2400
|
+
import_material17.Box,
|
|
2401
|
+
{
|
|
2402
|
+
sx: [
|
|
2403
|
+
{
|
|
2404
|
+
display: "flex",
|
|
2405
|
+
gap: 1,
|
|
2406
|
+
flexWrap: "nowrap",
|
|
2407
|
+
justifyContent: "center",
|
|
2408
|
+
alignItems: "center"
|
|
2409
|
+
},
|
|
2410
|
+
...sx == null ? [] : Array.isArray(sx) ? sx : [sx]
|
|
2411
|
+
],
|
|
2412
|
+
children: Array.from({ length }).map((_, idx) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2413
|
+
import_material17.TextField,
|
|
2414
|
+
{
|
|
2415
|
+
inputRef: (el) => {
|
|
2416
|
+
refs.current[idx] = el;
|
|
2417
|
+
},
|
|
2418
|
+
value: cells[idx],
|
|
2419
|
+
onChange: (e) => onFieldChange(idx, e),
|
|
2420
|
+
onKeyDown: (e) => onKeyDown(idx, e),
|
|
2421
|
+
onPaste: idx === 0 ? onPasteFirst : void 0,
|
|
2422
|
+
disabled,
|
|
2423
|
+
size: "small",
|
|
2424
|
+
type: inputType,
|
|
2425
|
+
sx: {
|
|
2426
|
+
width: 44,
|
|
2427
|
+
"& .MuiOutlinedInput-input": {
|
|
2428
|
+
textAlign: "center",
|
|
2429
|
+
py: 1,
|
|
2430
|
+
px: 0.5
|
|
2431
|
+
},
|
|
2432
|
+
...inputFocusStyle ? {
|
|
2433
|
+
"& .MuiOutlinedInput-root.Mui-focused fieldset": inputFocusStyle
|
|
2434
|
+
} : {}
|
|
2435
|
+
},
|
|
2436
|
+
inputProps: {
|
|
2437
|
+
maxLength: 1,
|
|
2438
|
+
inputMode: inputMode != null ? inputMode : void 0,
|
|
2439
|
+
"aria-label": `Code character ${idx + 1} of ${length}`,
|
|
2440
|
+
autoComplete: "one-time-code"
|
|
2441
|
+
}
|
|
2442
|
+
},
|
|
2443
|
+
idx
|
|
2444
|
+
))
|
|
2445
|
+
}
|
|
2446
|
+
);
|
|
2447
|
+
};
|
|
2448
|
+
var OTPField_default = OTPField;
|
|
2277
2449
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2278
2450
|
0 && (module.exports = {
|
|
2279
2451
|
CountrySelect,
|
|
@@ -2287,6 +2459,7 @@ var CountrySelect_default = CountrySelect;
|
|
|
2287
2459
|
InputFileUpload,
|
|
2288
2460
|
Loader,
|
|
2289
2461
|
Logo,
|
|
2462
|
+
OTPField,
|
|
2290
2463
|
PhoneNumberField,
|
|
2291
2464
|
Pill,
|
|
2292
2465
|
SearchableSelect,
|