@pos-360/horizon 0.7.0 → 0.8.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.
@@ -1257,6 +1257,7 @@ LargePanel.Stats = LargePanelStats;
1257
1257
  LargePanel.Chart = LargePanelChart;
1258
1258
  LargePanel.Content = LargePanelContent;
1259
1259
  LargePanel.Table = LargePanelTable;
1260
+ var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1260
1261
  var Input = React.forwardRef(
1261
1262
  ({
1262
1263
  label,
@@ -1292,12 +1293,32 @@ var Input = React.forwardRef(
1292
1293
  readOnly = false,
1293
1294
  tabIndex = 0,
1294
1295
  tooltip,
1296
+ passwordRules,
1297
+ onValidationChange,
1295
1298
  ...props
1296
1299
  }, ref) => {
1297
1300
  const [showTooltip, setShowTooltip] = React.useState(false);
1301
+ const [internalError, setInternalError] = React.useState("");
1302
+ const [hasBeenBlurred, setHasBeenBlurred] = React.useState(false);
1303
+ const [showPassword, setShowPassword] = React.useState(false);
1304
+ const inputInternalRef = React.useRef(null);
1305
+ const handleTogglePassword = () => {
1306
+ const input = inputInternalRef.current;
1307
+ const start = input?.selectionStart ?? null;
1308
+ const end = input?.selectionEnd ?? null;
1309
+ setShowPassword((v) => !v);
1310
+ requestAnimationFrame(() => {
1311
+ if (input && start !== null && end !== null) {
1312
+ input.focus();
1313
+ input.setSelectionRange(start, end);
1314
+ }
1315
+ });
1316
+ };
1298
1317
  const isNumberType = type === "number";
1299
1318
  const isSelectType = type === "select";
1300
1319
  const isMobileType = type === "mobile";
1320
+ const isEmailType = type === "email";
1321
+ const isPasswordType = type === "password";
1301
1322
  const [inputValue, setInputValue] = React.useState(
1302
1323
  value !== void 0 ? value.toString() : ""
1303
1324
  );
@@ -1306,6 +1327,28 @@ var Input = React.forwardRef(
1306
1327
  setInputValue(value.toString());
1307
1328
  }
1308
1329
  }, [value]);
1330
+ const displayError = error || internalError;
1331
+ const currentPasswordValue = isPasswordType ? inputValue : "";
1332
+ const passwordRuleResults = passwordRules ? {
1333
+ minLength: passwordRules.minLength !== void 0 ? currentPasswordValue.length >= passwordRules.minLength : null,
1334
+ uppercase: passwordRules.requireUppercase ? /[A-Z]/.test(currentPasswordValue) : null,
1335
+ lowercase: passwordRules.requireLowercase ? /[a-z]/.test(currentPasswordValue) : null,
1336
+ number: passwordRules.requireNumber ? /[0-9]/.test(currentPasswordValue) : null,
1337
+ specialChar: passwordRules.requireSpecialChar ? /[^a-zA-Z0-9]/.test(currentPasswordValue) : null
1338
+ } : null;
1339
+ const isPasswordValid = passwordRuleResults ? Object.values(passwordRuleResults).every((v) => v === null || v === true) : true;
1340
+ React.useEffect(() => {
1341
+ if (isPasswordType && onValidationChange) {
1342
+ onValidationChange(isPasswordValid);
1343
+ }
1344
+ }, [isPasswordValid, isPasswordType, onValidationChange]);
1345
+ const passwordRuleLabels = {
1346
+ minLength: (r) => `At least ${r.minLength} characters`,
1347
+ uppercase: () => "One uppercase letter",
1348
+ lowercase: () => "One lowercase letter",
1349
+ number: () => "One number",
1350
+ specialChar: () => "One special character"
1351
+ };
1309
1352
  const formatNumber = (num) => {
1310
1353
  if (precision > 0) {
1311
1354
  return parseFloat(num.toFixed(precision));
@@ -1321,6 +1364,29 @@ var Input = React.forwardRef(
1321
1364
  if (max !== void 0 && num > max) return false;
1322
1365
  return true;
1323
1366
  };
1367
+ const handleKeyDown = (e) => {
1368
+ if (!isNumberType) return;
1369
+ const allowedKeys = [
1370
+ "Backspace",
1371
+ "Delete",
1372
+ "Tab",
1373
+ "Enter",
1374
+ "ArrowLeft",
1375
+ "ArrowRight",
1376
+ "ArrowUp",
1377
+ "ArrowDown",
1378
+ "Home",
1379
+ "End"
1380
+ ];
1381
+ const isAllowedKey = allowedKeys.includes(e.key);
1382
+ const isDigit = /^[0-9]$/.test(e.key);
1383
+ const isDecimalPoint = e.key === "." && precision > 0 && !inputValue.includes(".");
1384
+ const isNegativeSign = e.key === "-" && allowNegative && inputValue === "";
1385
+ const isCtrlOrMeta = e.ctrlKey || e.metaKey;
1386
+ if (!isAllowedKey && !isDigit && !isDecimalPoint && !isNegativeSign && !isCtrlOrMeta) {
1387
+ e.preventDefault();
1388
+ }
1389
+ };
1324
1390
  const handleInputChange = (e) => {
1325
1391
  if (isMobileType) {
1326
1392
  const digits = (e.target.value || "").replace(/\D/g, "").slice(0, 10);
@@ -1329,7 +1395,15 @@ var Input = React.forwardRef(
1329
1395
  });
1330
1396
  return;
1331
1397
  }
1332
- if (isNumberType && onNumberChange) {
1398
+ if (isPasswordType) {
1399
+ const filtered = e.target.value.replace(/\s/g, "");
1400
+ setInputValue(filtered);
1401
+ if (filtered !== e.target.value && onChange) {
1402
+ onChange({ ...e, target: { ...e.target, value: filtered } });
1403
+ } else {
1404
+ onChange?.(e);
1405
+ }
1406
+ } else if (isNumberType) {
1333
1407
  const newValue = e.target.value;
1334
1408
  if (newValue === "" || allowNegative && newValue === "-") {
1335
1409
  setInputValue(newValue);
@@ -1339,13 +1413,51 @@ var Input = React.forwardRef(
1339
1413
  setInputValue(newValue);
1340
1414
  const numValue = parseFloat(newValue);
1341
1415
  if (!isNaN(numValue)) {
1342
- onNumberChange(formatNumber(numValue));
1416
+ onNumberChange?.(formatNumber(numValue));
1343
1417
  }
1344
1418
  }
1345
1419
  } else {
1346
1420
  onChange?.(e);
1347
1421
  }
1348
1422
  };
1423
+ const handleFocus = (e) => {
1424
+ if (isEmailType || isMobileType) {
1425
+ setInternalError("");
1426
+ }
1427
+ onFocus?.(e);
1428
+ };
1429
+ const handleBlur = (e) => {
1430
+ setHasBeenBlurred(true);
1431
+ if (isEmailType) {
1432
+ const val = (e.target.value || "").trim();
1433
+ if (val && !EMAIL_REGEX.test(val)) {
1434
+ setInternalError("Please enter a valid email address");
1435
+ } else {
1436
+ setInternalError("");
1437
+ }
1438
+ }
1439
+ if (isMobileType) {
1440
+ const digits = (e.target.value || "").replace(/\D/g, "");
1441
+ if (digits && digits.length !== 10) {
1442
+ setInternalError("Please enter a valid 10-digit phone number");
1443
+ } else {
1444
+ setInternalError("");
1445
+ }
1446
+ }
1447
+ if (isNumberType) {
1448
+ if (inputValue !== "" && inputValue !== "-") {
1449
+ const numValue = parseFloat(inputValue);
1450
+ if (!isNaN(numValue)) {
1451
+ const cleanValue = formatNumber(numValue);
1452
+ setInputValue(cleanValue.toString());
1453
+ onNumberChange?.(cleanValue);
1454
+ }
1455
+ } else if (inputValue === "" || inputValue === "-") {
1456
+ setInputValue("");
1457
+ }
1458
+ }
1459
+ onBlur?.(e);
1460
+ };
1349
1461
  const handleSelectChange = (e) => {
1350
1462
  onSelectChange?.(e.target.value);
1351
1463
  };
@@ -1367,22 +1479,7 @@ var Input = React.forwardRef(
1367
1479
  setInputValue(finalValue.toString());
1368
1480
  onNumberChange?.(finalValue);
1369
1481
  };
1370
- const handleBlur = (e) => {
1371
- if (isNumberType && onNumberChange) {
1372
- if (inputValue !== "" && inputValue !== "-") {
1373
- const numValue = parseFloat(inputValue);
1374
- if (!isNaN(numValue)) {
1375
- const cleanValue = formatNumber(numValue);
1376
- setInputValue(cleanValue.toString());
1377
- onNumberChange(cleanValue);
1378
- }
1379
- } else if (inputValue === "" || inputValue === "-") {
1380
- setInputValue("");
1381
- }
1382
- }
1383
- onBlur?.(e);
1384
- };
1385
- const baseInputStyles = "w-full transition-colors focus:outline-none min-h-[52px]";
1482
+ const baseInputStyles = "w-full transition-colors focus:outline-none";
1386
1483
  const sizeStyles = {
1387
1484
  sm: "px-3 py-2 text-sm",
1388
1485
  md: "px-4 py-3 text-base",
@@ -1400,13 +1497,13 @@ var Input = React.forwardRef(
1400
1497
  };
1401
1498
  const variantStyles2 = {
1402
1499
  default: "border bg-white/60 dark:bg-neutral-800/60 backdrop-blur-sm transition-colors duration-hz-slow ease-hz-default rounded-hz-md",
1403
- filled: "border-0 bg-neutral-50/60 dark:bg-neutral-700/60 backdrop-blur-sm transition-colors duration-hz-slow ease-hz-default rounded-hz-md"
1500
+ filled: "border border-transparent bg-neutral-50/60 dark:bg-neutral-700/60 backdrop-blur-sm transition-colors duration-hz-slow ease-hz-default rounded-hz-md"
1404
1501
  };
1405
- const stateStyles = error ? "border-rose-500/50 dark:border-rose-400/50 focus:border-rose-600/70 dark:focus:border-rose-500/70 focus:ring-1 focus:ring-rose-500/50 dark:focus:ring-rose-400/50" : "border-neutral-300/50 dark:border-neutral-600/50 focus:border-blue-500/70 dark:focus:border-blue-400/70 focus:ring-1 focus:ring-blue-500/50 dark:focus:ring-blue-400/50";
1502
+ const stateStyles = displayError ? "border-rose-500/50 dark:border-rose-400/50 focus:border-rose-600/70 dark:focus:border-rose-500/70 focus:ring-1 focus:ring-rose-500/50 dark:focus:ring-rose-400/50" : "border-neutral-300/50 dark:border-neutral-600/50 focus:border-blue-500/70 dark:focus:border-blue-400/70 focus:ring-1 focus:ring-blue-500/50 dark:focus:ring-blue-400/50";
1406
1503
  const disabledStyles = disabled ? "bg-neutral-100/60 dark:bg-neutral-700/60 text-neutral-500 dark:text-neutral-400 cursor-not-allowed" : "text-neutral-900 dark:text-neutral-100";
1407
1504
  const hasControls = isNumberType && showControls;
1408
1505
  const hasLeadingContent = leadingIcon || leadingDecorator;
1409
- const hasTrailingContent = trailingIcon || trailingDecorator || hasControls;
1506
+ const hasTrailingContent = trailingIcon || trailingDecorator || hasControls || isPasswordType;
1410
1507
  const inputClasses = `
1411
1508
  ${baseInputStyles}
1412
1509
  ${sizeStyles[size]}
@@ -1430,6 +1527,7 @@ var Input = React.forwardRef(
1430
1527
  `.trim().replace(/\s+/g, " ");
1431
1528
  const canDecrement = !disabled && (min === void 0 || (parseFloat(inputValue) || 0) > min);
1432
1529
  const canIncrement = !disabled && (max === void 0 || (parseFloat(inputValue) || 0) < max);
1530
+ const showPasswordRules = isPasswordType && passwordRules && hasBeenBlurred;
1433
1531
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full", children: [
1434
1532
  label && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative mb-2", children: [
1435
1533
  /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "text-sm font-medium text-neutral-700 dark:text-neutral-300 transition-colors duration-hz-slow ease-hz-default flex items-center", children: [
@@ -1488,13 +1586,18 @@ var Input = React.forwardRef(
1488
1586
  ) : /* @__PURE__ */ jsxRuntime.jsx(
1489
1587
  "input",
1490
1588
  {
1491
- ref,
1492
- type: isMobileType ? "tel" : isNumberType ? "text" : type,
1589
+ ref: (node) => {
1590
+ inputInternalRef.current = node;
1591
+ if (typeof ref === "function") ref(node);
1592
+ else if (ref) ref.current = node;
1593
+ },
1594
+ type: isMobileType ? "tel" : isNumberType ? "text" : isPasswordType ? showPassword ? "text" : "password" : type,
1493
1595
  inputMode: isMobileType ? "numeric" : isNumberType ? "numeric" : void 0,
1494
- value: isNumberType && onNumberChange ? inputValue : value,
1596
+ value: isNumberType || isPasswordType ? inputValue : value,
1495
1597
  onChange: handleInputChange,
1496
- onFocus,
1497
- onBlur: isNumberType && onNumberChange ? handleBlur : onBlur,
1598
+ onKeyDown: isNumberType ? handleKeyDown : void 0,
1599
+ onFocus: handleFocus,
1600
+ onBlur: handleBlur,
1498
1601
  placeholder,
1499
1602
  disabled,
1500
1603
  required,
@@ -1510,6 +1613,7 @@ var Input = React.forwardRef(
1510
1613
  "button",
1511
1614
  {
1512
1615
  type: "button",
1616
+ onMouseDown: (e) => e.preventDefault(),
1513
1617
  onClick: handleDecrement,
1514
1618
  disabled: !canDecrement,
1515
1619
  className: `
@@ -1527,6 +1631,7 @@ var Input = React.forwardRef(
1527
1631
  "button",
1528
1632
  {
1529
1633
  type: "button",
1634
+ onMouseDown: (e) => e.preventDefault(),
1530
1635
  onClick: handleIncrement,
1531
1636
  disabled: !canIncrement,
1532
1637
  className: `
@@ -1540,6 +1645,17 @@ var Input = React.forwardRef(
1540
1645
  }
1541
1646
  )
1542
1647
  ] }) }),
1648
+ isPasswordType && /* @__PURE__ */ jsxRuntime.jsx(
1649
+ "button",
1650
+ {
1651
+ type: "button",
1652
+ tabIndex: -1,
1653
+ onMouseDown: (e) => e.preventDefault(),
1654
+ onClick: handleTogglePassword,
1655
+ className: "absolute inset-y-0 right-0 pr-3 flex items-center text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-300 transition-colors",
1656
+ children: showPassword ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, { size: iconSizeStyles[size] }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { size: iconSizeStyles[size] })
1657
+ }
1658
+ ),
1543
1659
  isSelectType && !trailingIcon && !trailingDecorator && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx(
1544
1660
  lucideReact.ChevronDown,
1545
1661
  {
@@ -1560,10 +1676,24 @@ var Input = React.forwardRef(
1560
1676
  }
1561
1677
  )
1562
1678
  ] }),
1563
- (error || helperText) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2", children: [
1564
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-rose-600 dark:text-rose-400 transition-colors duration-hz-slow ease-hz-default", children: error }),
1565
- helperText && !error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400 transition-colors duration-hz-slow ease-hz-default", children: helperText })
1566
- ] })
1679
+ (displayError || helperText) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2", children: [
1680
+ displayError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-rose-600 dark:text-rose-400 transition-colors duration-hz-slow ease-hz-default", children: displayError }),
1681
+ helperText && !displayError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400 transition-colors duration-hz-slow ease-hz-default", children: helperText })
1682
+ ] }),
1683
+ showPasswordRules && passwordRuleResults && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-2 space-y-1", children: Object.keys(passwordRuleResults).filter((key) => passwordRuleResults[key] !== null).map((key) => {
1684
+ const passed = passwordRuleResults[key];
1685
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1686
+ "li",
1687
+ {
1688
+ className: `flex items-center gap-1.5 text-xs transition-colors ${passed ? "text-green-600 dark:text-green-400" : "text-rose-500 dark:text-rose-400"}`,
1689
+ children: [
1690
+ passed ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-3 h-3 shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3 shrink-0" }),
1691
+ passwordRules && passwordRuleLabels[key](passwordRules)
1692
+ ]
1693
+ },
1694
+ key
1695
+ );
1696
+ }) })
1567
1697
  ] });
1568
1698
  }
1569
1699
  );
@@ -2597,5 +2727,5 @@ exports.TextButton = TextButton;
2597
2727
  exports.Toast = Toast;
2598
2728
  exports.useDashboardContext = useDashboardContext;
2599
2729
  exports.useSideNavContext = useSideNavContext;
2600
- //# sourceMappingURL=chunk-4GEUF55E.js.map
2601
- //# sourceMappingURL=chunk-4GEUF55E.js.map
2730
+ //# sourceMappingURL=chunk-LUUH457P.js.map
2731
+ //# sourceMappingURL=chunk-LUUH457P.js.map