@geomak/ui 5.7.2 → 5.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.
package/dist/index.cjs CHANGED
@@ -2605,15 +2605,7 @@ function Dropdown({
2605
2605
  hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
2606
2606
  ] });
2607
2607
  }
2608
- var SHIMMER = [
2609
- "relative overflow-hidden rounded-sm bg-surface-raised",
2610
- 'before:absolute before:inset-0 before:content-[""]',
2611
- "before:bg-gradient-to-r before:from-transparent before:via-white/30 before:to-transparent",
2612
- "before:animate-[shimmer_1.6s_linear_infinite]",
2613
- // Respect prefers-reduced-motion — the resting bg-surface-raised is still
2614
- // a perfectly legible placeholder for users who have animations off.
2615
- "motion-reduce:before:hidden"
2616
- ].join(" ");
2608
+ var SHIMMER = "oxy-skeleton rounded-sm bg-surface-raised";
2617
2609
  function SkeletonBox({ width, height = 16, radius, className = "", style }) {
2618
2610
  return /* @__PURE__ */ jsxRuntime.jsx(
2619
2611
  "span",
@@ -6281,7 +6273,7 @@ function useForm(options = {}) {
6281
6273
  isValid: store.isValid,
6282
6274
  getValue: store.getValue,
6283
6275
  getValues: store.getValues,
6284
- setValue: (name, value) => store.setValue(name, value),
6276
+ setValue: (name, value, opts) => store.setValue(name, value, opts),
6285
6277
  setValues: (patch) => store.setValues(patch),
6286
6278
  setError: store.setError,
6287
6279
  validateField: (name) => store.validateField(name),
@@ -6391,6 +6383,208 @@ function useFieldArray(name) {
6391
6383
  };
6392
6384
  }
6393
6385
 
6386
+ // src/components/forms/creditCard.ts
6387
+ var CARD_BRANDS = [
6388
+ { id: "amex", label: "American Express", short: "AMEX", color: "#1F72CD", pattern: /^3[47]/, lengths: [15], cvv: 4, gaps: [4, 10] },
6389
+ { id: "visa", label: "Visa", short: "VISA", color: "#1A1F71", pattern: /^4/, lengths: [16, 18, 19], cvv: 3, gaps: [4, 8, 12, 16] },
6390
+ { id: "mastercard", label: "Mastercard", short: "MC", color: "#EB001B", pattern: /^(5[1-5]|2[2-7])/, lengths: [16], cvv: 3, gaps: [4, 8, 12] },
6391
+ { id: "discover", label: "Discover", short: "DISC", color: "#FF6000", pattern: /^(6011|64[4-9]|65)/, lengths: [16, 19], cvv: 3, gaps: [4, 8, 12] },
6392
+ { id: "diners", label: "Diners Club", short: "DINERS", color: "#0079BE", pattern: /^(36|38|30[0-5])/, lengths: [14, 16, 19], cvv: 3, gaps: [4, 10] },
6393
+ { id: "jcb", label: "JCB", short: "JCB", color: "#0B4EA2", pattern: /^35/, lengths: [16, 17, 18, 19], cvv: 3, gaps: [4, 8, 12] }
6394
+ ];
6395
+ var onlyDigits = (s) => (s || "").replace(/\D/g, "");
6396
+ function detectBrand(value) {
6397
+ const d = onlyDigits(value);
6398
+ if (!d) return null;
6399
+ return CARD_BRANDS.find((b) => b.pattern.test(d)) ?? null;
6400
+ }
6401
+ function maxCardLength(value) {
6402
+ const b = detectBrand(value);
6403
+ return b ? Math.max(...b.lengths) : 19;
6404
+ }
6405
+ function luhnValid(value) {
6406
+ const s = onlyDigits(value);
6407
+ if (s.length < 12) return false;
6408
+ let sum = 0;
6409
+ let double = false;
6410
+ for (let i = s.length - 1; i >= 0; i--) {
6411
+ let n = s.charCodeAt(i) - 48;
6412
+ if (double) {
6413
+ n *= 2;
6414
+ if (n > 9) n -= 9;
6415
+ }
6416
+ sum += n;
6417
+ double = !double;
6418
+ }
6419
+ return sum % 10 === 0;
6420
+ }
6421
+ function formatCardNumber(value) {
6422
+ const brand = detectBrand(value);
6423
+ const digits = onlyDigits(value).slice(0, maxCardLength(value));
6424
+ const gaps = brand?.gaps ?? [4, 8, 12, 16];
6425
+ let out = "";
6426
+ for (let i = 0; i < digits.length; i++) {
6427
+ if (gaps.includes(i)) out += " ";
6428
+ out += digits[i];
6429
+ }
6430
+ return out;
6431
+ }
6432
+ function cardNumberError(value) {
6433
+ const d = onlyDigits(value);
6434
+ if (!d) return "Card number is required";
6435
+ const brand = detectBrand(d);
6436
+ if (!brand) return "Unsupported card type";
6437
+ if (!brand.lengths.includes(d.length)) return "Card number is incomplete";
6438
+ if (!luhnValid(d)) return "Card number looks invalid";
6439
+ return void 0;
6440
+ }
6441
+ function formatExpiry(value) {
6442
+ let d = onlyDigits(value).slice(0, 4);
6443
+ if (d.length === 1 && d > "1") d = "0" + d;
6444
+ if (d.length <= 2) return d;
6445
+ return `${d.slice(0, 2)}/${d.slice(2)}`;
6446
+ }
6447
+ function expiryError(value, now = /* @__PURE__ */ new Date()) {
6448
+ if (!value) return "Expiry is required";
6449
+ const m = value.match(/^(\d{2})\/(\d{2})$/);
6450
+ if (!m) return "Use MM/YY";
6451
+ const mm = Number(m[1]);
6452
+ const yy = Number(m[2]);
6453
+ if (mm < 1 || mm > 12) return "Invalid month";
6454
+ const endOfMonth = new Date(2e3 + yy, mm, 0, 23, 59, 59, 999);
6455
+ if (endOfMonth < now) return "Card has expired";
6456
+ return void 0;
6457
+ }
6458
+ function cvvError(value, cardNumber) {
6459
+ const need = detectBrand(cardNumber)?.cvv ?? 3;
6460
+ const d = onlyDigits(value);
6461
+ if (!d) return "CVV is required";
6462
+ if (d.length !== need) return `CVV must be ${need} digits`;
6463
+ return void 0;
6464
+ }
6465
+ var toCard = (vals) => {
6466
+ const number = String(vals.number ?? "");
6467
+ return {
6468
+ number: onlyDigits(number),
6469
+ name: String(vals.name ?? ""),
6470
+ expiry: String(vals.expiry ?? ""),
6471
+ cvv: onlyDigits(String(vals.cvv ?? "")),
6472
+ brand: detectBrand(number)?.id ?? null
6473
+ };
6474
+ };
6475
+ function BrandMark({ brand }) {
6476
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5", "aria-live": "polite", children: [
6477
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "28", height: "18", viewBox: "0 0 28 18", fill: "none", "aria-hidden": "true", children: [
6478
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "0.5", y: "0.5", width: "27", height: "17", rx: "3", fill: "var(--color-surface-raised)", stroke: "var(--color-border)" }),
6479
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "0.5", y: "3.75", width: "27", height: "3.5", fill: brand ? brand.color : "var(--color-border-strong)" })
6480
+ ] }),
6481
+ brand && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-bold uppercase tracking-wide", style: { color: brand.color }, children: brand.short }),
6482
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: brand ? brand.label : "Unknown card type" })
6483
+ ] });
6484
+ }
6485
+ function CreditCardForm({
6486
+ onSubmit,
6487
+ onChange,
6488
+ defaultValue,
6489
+ size = "md",
6490
+ disabled,
6491
+ requireName = true,
6492
+ hideSubmit = false,
6493
+ submitLabel = "Pay",
6494
+ className = "",
6495
+ style
6496
+ }) {
6497
+ const initial = React8.useRef({
6498
+ number: formatCardNumber(defaultValue?.number ?? ""),
6499
+ name: defaultValue?.name ?? "",
6500
+ expiry: formatExpiry(defaultValue?.expiry ?? ""),
6501
+ cvv: onlyDigits(defaultValue?.cvv ?? "")
6502
+ }).current;
6503
+ const form = useForm({ initialValues: initial });
6504
+ const numberStr = String(form.values.number ?? "");
6505
+ const brand = detectBrand(numberStr);
6506
+ React8.useEffect(() => {
6507
+ onChange?.(toCard(form.values));
6508
+ }, [form.values.number, form.values.name, form.values.expiry, form.values.cvv]);
6509
+ const numberBind = form.fieldNative("number", {
6510
+ required: "Card number is required",
6511
+ validate: (v) => cardNumberError(String(v))
6512
+ });
6513
+ const nameBind = form.fieldNative("name", requireName ? { required: "Cardholder name is required" } : void 0);
6514
+ const expiryBind = form.fieldNative("expiry", {
6515
+ required: "Expiry is required",
6516
+ validate: (v) => expiryError(String(v))
6517
+ });
6518
+ const cvvBind = form.fieldNative("cvv", {
6519
+ required: "CVV is required",
6520
+ validate: (v) => cvvError(String(v), numberStr)
6521
+ });
6522
+ const cvvLen = brand?.cvv ?? 3;
6523
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6524
+ Form,
6525
+ {
6526
+ form,
6527
+ onFinish: (vals) => onSubmit?.(toCard(vals)),
6528
+ className: `flex flex-col gap-4 ${className}`.trim(),
6529
+ style,
6530
+ children: [
6531
+ /* @__PURE__ */ jsxRuntime.jsx(
6532
+ TextInput,
6533
+ {
6534
+ ...numberBind,
6535
+ label: "Card number",
6536
+ placeholder: "1234 5678 9012 3456",
6537
+ size,
6538
+ disabled,
6539
+ value: numberStr,
6540
+ onChange: (e) => form.setValue("number", formatCardNumber(e.target.value), { touch: true }),
6541
+ suffix: /* @__PURE__ */ jsxRuntime.jsx(BrandMark, { brand })
6542
+ }
6543
+ ),
6544
+ /* @__PURE__ */ jsxRuntime.jsx(
6545
+ TextInput,
6546
+ {
6547
+ ...nameBind,
6548
+ label: "Cardholder name",
6549
+ placeholder: "Jane Appleseed",
6550
+ size,
6551
+ disabled,
6552
+ value: String(form.values.name ?? ""),
6553
+ onChange: (e) => form.setValue("name", e.target.value, { touch: true })
6554
+ }
6555
+ ),
6556
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
6557
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
6558
+ TextInput,
6559
+ {
6560
+ ...expiryBind,
6561
+ label: "Expiry",
6562
+ placeholder: "MM/YY",
6563
+ size,
6564
+ disabled,
6565
+ value: String(form.values.expiry ?? ""),
6566
+ onChange: (e) => form.setValue("expiry", formatExpiry(e.target.value), { touch: true })
6567
+ }
6568
+ ) }),
6569
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
6570
+ TextInput,
6571
+ {
6572
+ ...cvvBind,
6573
+ label: "CVV",
6574
+ placeholder: cvvLen === 4 ? "1234" : "123",
6575
+ size,
6576
+ disabled,
6577
+ value: String(form.values.cvv ?? ""),
6578
+ onChange: (e) => form.setValue("cvv", onlyDigits(e.target.value).slice(0, cvvLen), { touch: true })
6579
+ }
6580
+ ) })
6581
+ ] }),
6582
+ !hideSubmit && /* @__PURE__ */ jsxRuntime.jsx(Button, { content: submitLabel, buttonType: "submit", variant: "primary", disabled })
6583
+ ]
6584
+ }
6585
+ );
6586
+ }
6587
+
6394
6588
  Object.defineProperty(exports, "COLORS", {
6395
6589
  enumerable: true,
6396
6590
  get: function () { return chunk255PCZIW_cjs.colors_default; }
@@ -6412,12 +6606,14 @@ exports.AutoComplete = AutoComplete;
6412
6606
  exports.Avatar = Avatar;
6413
6607
  exports.Box = Box;
6414
6608
  exports.Button = Button;
6609
+ exports.CARD_BRANDS = CARD_BRANDS;
6415
6610
  exports.Catalog = Catalog;
6416
6611
  exports.CatalogCarousel = CatalogCarousel;
6417
6612
  exports.CatalogGrid = CatalogGrid;
6418
6613
  exports.Checkbox = Checkbox;
6419
6614
  exports.ColorPicker = ColorPicker;
6420
6615
  exports.ContextMenu = ContextMenu;
6616
+ exports.CreditCardForm = CreditCardForm;
6421
6617
  exports.DateRangePicker = DateRangePicker;
6422
6618
  exports.Drawer = Drawer;
6423
6619
  exports.Dropdown = Dropdown;
@@ -6472,8 +6668,16 @@ exports.Tree = Tree;
6472
6668
  exports.TreeSelect = TreeSelect;
6473
6669
  exports.Typography = Typography;
6474
6670
  exports.Wizard = Wizard;
6671
+ exports.cardNumberError = cardNumberError;
6672
+ exports.cvvError = cvvError;
6673
+ exports.detectBrand = detectBrand;
6674
+ exports.expiryError = expiryError;
6475
6675
  exports.fieldShell = fieldShell;
6676
+ exports.formatCardNumber = formatCardNumber;
6677
+ exports.formatExpiry = formatExpiry;
6476
6678
  exports.isRequired = isRequired;
6679
+ exports.luhnValid = luhnValid;
6680
+ exports.onlyDigits = onlyDigits;
6477
6681
  exports.patterns = patterns;
6478
6682
  exports.runFieldRules = runFieldRules;
6479
6683
  exports.useFieldArray = useFieldArray;