@pos-360/horizon 0.7.1 → 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.
@@ -2,8 +2,8 @@ import { cn, Text, Badge } from './chunk-E3UN74IA.mjs';
2
2
  import { motion, AnimatePresence } from 'framer-motion';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
  import * as React from 'react';
5
- import { createContext, forwardRef, useId, useMemo, useState, useEffect, useRef, useCallback, useContext } from 'react';
6
- import { ChevronLeft, ChevronRight, Info, ChevronDown, ChevronUp, TrendingUp, TrendingDown, Minus, LayoutGrid, List, Check, ExternalLink, X, Moon, Rocket, Star, Globe, Orbit, Sparkles, AlertTriangle } from 'lucide-react';
5
+ import { createContext, forwardRef, useId, useMemo, useState, useRef, useEffect, useCallback, useContext } from 'react';
6
+ import { ChevronLeft, ChevronRight, Info, ChevronDown, ChevronUp, EyeOff, Eye, Check, X, TrendingUp, TrendingDown, Minus, LayoutGrid, List, ExternalLink, Moon, Rocket, Star, Globe, Orbit, Sparkles, AlertTriangle } from 'lucide-react';
7
7
  export { Globe, Moon, Orbit, Rocket, Sparkles, Star } from 'lucide-react';
8
8
  import { Bar, AreaClosed, LinePath, Pie } from '@visx/shape';
9
9
  import { Group } from '@visx/group';
@@ -1236,6 +1236,7 @@ LargePanel.Stats = LargePanelStats;
1236
1236
  LargePanel.Chart = LargePanelChart;
1237
1237
  LargePanel.Content = LargePanelContent;
1238
1238
  LargePanel.Table = LargePanelTable;
1239
+ var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1239
1240
  var Input = forwardRef(
1240
1241
  ({
1241
1242
  label,
@@ -1271,12 +1272,32 @@ var Input = forwardRef(
1271
1272
  readOnly = false,
1272
1273
  tabIndex = 0,
1273
1274
  tooltip,
1275
+ passwordRules,
1276
+ onValidationChange,
1274
1277
  ...props
1275
1278
  }, ref) => {
1276
1279
  const [showTooltip, setShowTooltip] = useState(false);
1280
+ const [internalError, setInternalError] = useState("");
1281
+ const [hasBeenBlurred, setHasBeenBlurred] = useState(false);
1282
+ const [showPassword, setShowPassword] = useState(false);
1283
+ const inputInternalRef = useRef(null);
1284
+ const handleTogglePassword = () => {
1285
+ const input = inputInternalRef.current;
1286
+ const start = input?.selectionStart ?? null;
1287
+ const end = input?.selectionEnd ?? null;
1288
+ setShowPassword((v) => !v);
1289
+ requestAnimationFrame(() => {
1290
+ if (input && start !== null && end !== null) {
1291
+ input.focus();
1292
+ input.setSelectionRange(start, end);
1293
+ }
1294
+ });
1295
+ };
1277
1296
  const isNumberType = type === "number";
1278
1297
  const isSelectType = type === "select";
1279
1298
  const isMobileType = type === "mobile";
1299
+ const isEmailType = type === "email";
1300
+ const isPasswordType = type === "password";
1280
1301
  const [inputValue, setInputValue] = useState(
1281
1302
  value !== void 0 ? value.toString() : ""
1282
1303
  );
@@ -1285,6 +1306,28 @@ var Input = forwardRef(
1285
1306
  setInputValue(value.toString());
1286
1307
  }
1287
1308
  }, [value]);
1309
+ const displayError = error || internalError;
1310
+ const currentPasswordValue = isPasswordType ? inputValue : "";
1311
+ const passwordRuleResults = passwordRules ? {
1312
+ minLength: passwordRules.minLength !== void 0 ? currentPasswordValue.length >= passwordRules.minLength : null,
1313
+ uppercase: passwordRules.requireUppercase ? /[A-Z]/.test(currentPasswordValue) : null,
1314
+ lowercase: passwordRules.requireLowercase ? /[a-z]/.test(currentPasswordValue) : null,
1315
+ number: passwordRules.requireNumber ? /[0-9]/.test(currentPasswordValue) : null,
1316
+ specialChar: passwordRules.requireSpecialChar ? /[^a-zA-Z0-9]/.test(currentPasswordValue) : null
1317
+ } : null;
1318
+ const isPasswordValid = passwordRuleResults ? Object.values(passwordRuleResults).every((v) => v === null || v === true) : true;
1319
+ useEffect(() => {
1320
+ if (isPasswordType && onValidationChange) {
1321
+ onValidationChange(isPasswordValid);
1322
+ }
1323
+ }, [isPasswordValid, isPasswordType, onValidationChange]);
1324
+ const passwordRuleLabels = {
1325
+ minLength: (r) => `At least ${r.minLength} characters`,
1326
+ uppercase: () => "One uppercase letter",
1327
+ lowercase: () => "One lowercase letter",
1328
+ number: () => "One number",
1329
+ specialChar: () => "One special character"
1330
+ };
1288
1331
  const formatNumber = (num) => {
1289
1332
  if (precision > 0) {
1290
1333
  return parseFloat(num.toFixed(precision));
@@ -1300,6 +1343,29 @@ var Input = forwardRef(
1300
1343
  if (max !== void 0 && num > max) return false;
1301
1344
  return true;
1302
1345
  };
1346
+ const handleKeyDown = (e) => {
1347
+ if (!isNumberType) return;
1348
+ const allowedKeys = [
1349
+ "Backspace",
1350
+ "Delete",
1351
+ "Tab",
1352
+ "Enter",
1353
+ "ArrowLeft",
1354
+ "ArrowRight",
1355
+ "ArrowUp",
1356
+ "ArrowDown",
1357
+ "Home",
1358
+ "End"
1359
+ ];
1360
+ const isAllowedKey = allowedKeys.includes(e.key);
1361
+ const isDigit = /^[0-9]$/.test(e.key);
1362
+ const isDecimalPoint = e.key === "." && precision > 0 && !inputValue.includes(".");
1363
+ const isNegativeSign = e.key === "-" && allowNegative && inputValue === "";
1364
+ const isCtrlOrMeta = e.ctrlKey || e.metaKey;
1365
+ if (!isAllowedKey && !isDigit && !isDecimalPoint && !isNegativeSign && !isCtrlOrMeta) {
1366
+ e.preventDefault();
1367
+ }
1368
+ };
1303
1369
  const handleInputChange = (e) => {
1304
1370
  if (isMobileType) {
1305
1371
  const digits = (e.target.value || "").replace(/\D/g, "").slice(0, 10);
@@ -1308,7 +1374,15 @@ var Input = forwardRef(
1308
1374
  });
1309
1375
  return;
1310
1376
  }
1311
- if (isNumberType && onNumberChange) {
1377
+ if (isPasswordType) {
1378
+ const filtered = e.target.value.replace(/\s/g, "");
1379
+ setInputValue(filtered);
1380
+ if (filtered !== e.target.value && onChange) {
1381
+ onChange({ ...e, target: { ...e.target, value: filtered } });
1382
+ } else {
1383
+ onChange?.(e);
1384
+ }
1385
+ } else if (isNumberType) {
1312
1386
  const newValue = e.target.value;
1313
1387
  if (newValue === "" || allowNegative && newValue === "-") {
1314
1388
  setInputValue(newValue);
@@ -1318,13 +1392,51 @@ var Input = forwardRef(
1318
1392
  setInputValue(newValue);
1319
1393
  const numValue = parseFloat(newValue);
1320
1394
  if (!isNaN(numValue)) {
1321
- onNumberChange(formatNumber(numValue));
1395
+ onNumberChange?.(formatNumber(numValue));
1322
1396
  }
1323
1397
  }
1324
1398
  } else {
1325
1399
  onChange?.(e);
1326
1400
  }
1327
1401
  };
1402
+ const handleFocus = (e) => {
1403
+ if (isEmailType || isMobileType) {
1404
+ setInternalError("");
1405
+ }
1406
+ onFocus?.(e);
1407
+ };
1408
+ const handleBlur = (e) => {
1409
+ setHasBeenBlurred(true);
1410
+ if (isEmailType) {
1411
+ const val = (e.target.value || "").trim();
1412
+ if (val && !EMAIL_REGEX.test(val)) {
1413
+ setInternalError("Please enter a valid email address");
1414
+ } else {
1415
+ setInternalError("");
1416
+ }
1417
+ }
1418
+ if (isMobileType) {
1419
+ const digits = (e.target.value || "").replace(/\D/g, "");
1420
+ if (digits && digits.length !== 10) {
1421
+ setInternalError("Please enter a valid 10-digit phone number");
1422
+ } else {
1423
+ setInternalError("");
1424
+ }
1425
+ }
1426
+ if (isNumberType) {
1427
+ if (inputValue !== "" && inputValue !== "-") {
1428
+ const numValue = parseFloat(inputValue);
1429
+ if (!isNaN(numValue)) {
1430
+ const cleanValue = formatNumber(numValue);
1431
+ setInputValue(cleanValue.toString());
1432
+ onNumberChange?.(cleanValue);
1433
+ }
1434
+ } else if (inputValue === "" || inputValue === "-") {
1435
+ setInputValue("");
1436
+ }
1437
+ }
1438
+ onBlur?.(e);
1439
+ };
1328
1440
  const handleSelectChange = (e) => {
1329
1441
  onSelectChange?.(e.target.value);
1330
1442
  };
@@ -1346,22 +1458,7 @@ var Input = forwardRef(
1346
1458
  setInputValue(finalValue.toString());
1347
1459
  onNumberChange?.(finalValue);
1348
1460
  };
1349
- const handleBlur = (e) => {
1350
- if (isNumberType && onNumberChange) {
1351
- if (inputValue !== "" && inputValue !== "-") {
1352
- const numValue = parseFloat(inputValue);
1353
- if (!isNaN(numValue)) {
1354
- const cleanValue = formatNumber(numValue);
1355
- setInputValue(cleanValue.toString());
1356
- onNumberChange(cleanValue);
1357
- }
1358
- } else if (inputValue === "" || inputValue === "-") {
1359
- setInputValue("");
1360
- }
1361
- }
1362
- onBlur?.(e);
1363
- };
1364
- const baseInputStyles = "w-full transition-colors focus:outline-none min-h-[52px]";
1461
+ const baseInputStyles = "w-full transition-colors focus:outline-none";
1365
1462
  const sizeStyles = {
1366
1463
  sm: "px-3 py-2 text-sm",
1367
1464
  md: "px-4 py-3 text-base",
@@ -1379,13 +1476,13 @@ var Input = forwardRef(
1379
1476
  };
1380
1477
  const variantStyles2 = {
1381
1478
  default: "border bg-white/60 dark:bg-neutral-800/60 backdrop-blur-sm transition-colors duration-hz-slow ease-hz-default rounded-hz-md",
1382
- 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"
1479
+ 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"
1383
1480
  };
1384
- 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";
1481
+ 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";
1385
1482
  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";
1386
1483
  const hasControls = isNumberType && showControls;
1387
1484
  const hasLeadingContent = leadingIcon || leadingDecorator;
1388
- const hasTrailingContent = trailingIcon || trailingDecorator || hasControls;
1485
+ const hasTrailingContent = trailingIcon || trailingDecorator || hasControls || isPasswordType;
1389
1486
  const inputClasses = `
1390
1487
  ${baseInputStyles}
1391
1488
  ${sizeStyles[size]}
@@ -1409,6 +1506,7 @@ var Input = forwardRef(
1409
1506
  `.trim().replace(/\s+/g, " ");
1410
1507
  const canDecrement = !disabled && (min === void 0 || (parseFloat(inputValue) || 0) > min);
1411
1508
  const canIncrement = !disabled && (max === void 0 || (parseFloat(inputValue) || 0) < max);
1509
+ const showPasswordRules = isPasswordType && passwordRules && hasBeenBlurred;
1412
1510
  return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
1413
1511
  label && /* @__PURE__ */ jsxs("div", { className: "relative mb-2", children: [
1414
1512
  /* @__PURE__ */ 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: [
@@ -1467,13 +1565,18 @@ var Input = forwardRef(
1467
1565
  ) : /* @__PURE__ */ jsx(
1468
1566
  "input",
1469
1567
  {
1470
- ref,
1471
- type: isMobileType ? "tel" : isNumberType ? "text" : type,
1568
+ ref: (node) => {
1569
+ inputInternalRef.current = node;
1570
+ if (typeof ref === "function") ref(node);
1571
+ else if (ref) ref.current = node;
1572
+ },
1573
+ type: isMobileType ? "tel" : isNumberType ? "text" : isPasswordType ? showPassword ? "text" : "password" : type,
1472
1574
  inputMode: isMobileType ? "numeric" : isNumberType ? "numeric" : void 0,
1473
- value: isNumberType && onNumberChange ? inputValue : value,
1575
+ value: isNumberType || isPasswordType ? inputValue : value,
1474
1576
  onChange: handleInputChange,
1475
- onFocus,
1476
- onBlur: isNumberType && onNumberChange ? handleBlur : onBlur,
1577
+ onKeyDown: isNumberType ? handleKeyDown : void 0,
1578
+ onFocus: handleFocus,
1579
+ onBlur: handleBlur,
1477
1580
  placeholder,
1478
1581
  disabled,
1479
1582
  required,
@@ -1489,6 +1592,7 @@ var Input = forwardRef(
1489
1592
  "button",
1490
1593
  {
1491
1594
  type: "button",
1595
+ onMouseDown: (e) => e.preventDefault(),
1492
1596
  onClick: handleDecrement,
1493
1597
  disabled: !canDecrement,
1494
1598
  className: `
@@ -1506,6 +1610,7 @@ var Input = forwardRef(
1506
1610
  "button",
1507
1611
  {
1508
1612
  type: "button",
1613
+ onMouseDown: (e) => e.preventDefault(),
1509
1614
  onClick: handleIncrement,
1510
1615
  disabled: !canIncrement,
1511
1616
  className: `
@@ -1519,6 +1624,17 @@ var Input = forwardRef(
1519
1624
  }
1520
1625
  )
1521
1626
  ] }) }),
1627
+ isPasswordType && /* @__PURE__ */ jsx(
1628
+ "button",
1629
+ {
1630
+ type: "button",
1631
+ tabIndex: -1,
1632
+ onMouseDown: (e) => e.preventDefault(),
1633
+ onClick: handleTogglePassword,
1634
+ 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",
1635
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOff, { size: iconSizeStyles[size] }) : /* @__PURE__ */ jsx(Eye, { size: iconSizeStyles[size] })
1636
+ }
1637
+ ),
1522
1638
  isSelectType && !trailingIcon && !trailingDecorator && /* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none", children: /* @__PURE__ */ jsx(
1523
1639
  ChevronDown,
1524
1640
  {
@@ -1539,10 +1655,24 @@ var Input = forwardRef(
1539
1655
  }
1540
1656
  )
1541
1657
  ] }),
1542
- (error || helperText) && /* @__PURE__ */ jsxs("div", { className: "mt-2", children: [
1543
- error && /* @__PURE__ */ jsx("p", { className: "text-sm text-rose-600 dark:text-rose-400 transition-colors duration-hz-slow ease-hz-default", children: error }),
1544
- helperText && !error && /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400 transition-colors duration-hz-slow ease-hz-default", children: helperText })
1545
- ] })
1658
+ (displayError || helperText) && /* @__PURE__ */ jsxs("div", { className: "mt-2", children: [
1659
+ displayError && /* @__PURE__ */ jsx("p", { className: "text-sm text-rose-600 dark:text-rose-400 transition-colors duration-hz-slow ease-hz-default", children: displayError }),
1660
+ helperText && !displayError && /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400 transition-colors duration-hz-slow ease-hz-default", children: helperText })
1661
+ ] }),
1662
+ showPasswordRules && passwordRuleResults && /* @__PURE__ */ jsx("ul", { className: "mt-2 space-y-1", children: Object.keys(passwordRuleResults).filter((key) => passwordRuleResults[key] !== null).map((key) => {
1663
+ const passed = passwordRuleResults[key];
1664
+ return /* @__PURE__ */ jsxs(
1665
+ "li",
1666
+ {
1667
+ 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"}`,
1668
+ children: [
1669
+ passed ? /* @__PURE__ */ jsx(Check, { className: "w-3 h-3 shrink-0" }) : /* @__PURE__ */ jsx(X, { className: "w-3 h-3 shrink-0" }),
1670
+ passwordRules && passwordRuleLabels[key](passwordRules)
1671
+ ]
1672
+ },
1673
+ key
1674
+ );
1675
+ }) })
1546
1676
  ] });
1547
1677
  }
1548
1678
  );
@@ -2533,5 +2663,5 @@ var SideNavFooter = forwardRef(
2533
2663
  SideNavFooter.displayName = "SideNavFooter";
2534
2664
 
2535
2665
  export { AnimatedButton, ChartRenderer, CompactPanel, Dashboard, DashboardPanel, Input, LargePanel, MediumPanel, SideNav, SideNavFooter, SideNavHeader, SideNavItem, SideNavSection, StatDisplay, TableRenderer, TemplateSelector, TextButton, Toast, useDashboardContext, useSideNavContext };
2536
- //# sourceMappingURL=chunk-WLBF2GR6.mjs.map
2537
- //# sourceMappingURL=chunk-WLBF2GR6.mjs.map
2666
+ //# sourceMappingURL=chunk-3CJL52WY.mjs.map
2667
+ //# sourceMappingURL=chunk-3CJL52WY.mjs.map