@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.js CHANGED
@@ -2570,15 +2570,7 @@ function Dropdown({
2570
2570
  hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
2571
2571
  ] });
2572
2572
  }
2573
- var SHIMMER = [
2574
- "relative overflow-hidden rounded-sm bg-surface-raised",
2575
- 'before:absolute before:inset-0 before:content-[""]',
2576
- "before:bg-gradient-to-r before:from-transparent before:via-white/30 before:to-transparent",
2577
- "before:animate-[shimmer_1.6s_linear_infinite]",
2578
- // Respect prefers-reduced-motion — the resting bg-surface-raised is still
2579
- // a perfectly legible placeholder for users who have animations off.
2580
- "motion-reduce:before:hidden"
2581
- ].join(" ");
2573
+ var SHIMMER = "oxy-skeleton rounded-sm bg-surface-raised";
2582
2574
  function SkeletonBox({ width, height = 16, radius, className = "", style }) {
2583
2575
  return /* @__PURE__ */ jsx(
2584
2576
  "span",
@@ -6246,7 +6238,7 @@ function useForm(options = {}) {
6246
6238
  isValid: store.isValid,
6247
6239
  getValue: store.getValue,
6248
6240
  getValues: store.getValues,
6249
- setValue: (name, value) => store.setValue(name, value),
6241
+ setValue: (name, value, opts) => store.setValue(name, value, opts),
6250
6242
  setValues: (patch) => store.setValues(patch),
6251
6243
  setError: store.setError,
6252
6244
  validateField: (name) => store.validateField(name),
@@ -6356,6 +6348,208 @@ function useFieldArray(name) {
6356
6348
  };
6357
6349
  }
6358
6350
 
6359
- export { AppShell, AutoComplete, Avatar, Box, Button, Catalog, CatalogCarousel, CatalogGrid, Checkbox, ColorPicker, ContextMenu, DateRangePicker, Drawer, Dropdown, FadingBase, Field, FieldHelpIcon, FieldLabel, FileInput, Flex, Form, FormContext, FormField, FormStore, Grid2 as Grid, GridCard, icons_default as Icon, IconButton, List2 as List, LoadingSpinner, Modal, NotificationProvider, NumberInput, OpaqueGridCard, OtpInput, Password, Portal, RadioGroup, Rating, ScalableContainer, SearchInput_default as SearchInput, SegmentedControl, Sidebar, SkeletonBox, SkeletonCard, SkeletonCircle, SkeletonText, Slider, Switch, Table, Tabs, TagsInput, DatePicker as Temporal, TextArea, TextInput, ThemeProvider, ThemeSwitch, TimePicker, Tooltip, TooltipProvider, TopBar, Tree, TreeSelect, Typography, Wizard, fieldShell, isRequired, patterns, runFieldRules, useFieldArray, useForm, useFormField, useFormStore, useNotification };
6351
+ // src/components/forms/creditCard.ts
6352
+ var CARD_BRANDS = [
6353
+ { id: "amex", label: "American Express", short: "AMEX", color: "#1F72CD", pattern: /^3[47]/, lengths: [15], cvv: 4, gaps: [4, 10] },
6354
+ { id: "visa", label: "Visa", short: "VISA", color: "#1A1F71", pattern: /^4/, lengths: [16, 18, 19], cvv: 3, gaps: [4, 8, 12, 16] },
6355
+ { id: "mastercard", label: "Mastercard", short: "MC", color: "#EB001B", pattern: /^(5[1-5]|2[2-7])/, lengths: [16], cvv: 3, gaps: [4, 8, 12] },
6356
+ { id: "discover", label: "Discover", short: "DISC", color: "#FF6000", pattern: /^(6011|64[4-9]|65)/, lengths: [16, 19], cvv: 3, gaps: [4, 8, 12] },
6357
+ { id: "diners", label: "Diners Club", short: "DINERS", color: "#0079BE", pattern: /^(36|38|30[0-5])/, lengths: [14, 16, 19], cvv: 3, gaps: [4, 10] },
6358
+ { id: "jcb", label: "JCB", short: "JCB", color: "#0B4EA2", pattern: /^35/, lengths: [16, 17, 18, 19], cvv: 3, gaps: [4, 8, 12] }
6359
+ ];
6360
+ var onlyDigits = (s) => (s || "").replace(/\D/g, "");
6361
+ function detectBrand(value) {
6362
+ const d = onlyDigits(value);
6363
+ if (!d) return null;
6364
+ return CARD_BRANDS.find((b) => b.pattern.test(d)) ?? null;
6365
+ }
6366
+ function maxCardLength(value) {
6367
+ const b = detectBrand(value);
6368
+ return b ? Math.max(...b.lengths) : 19;
6369
+ }
6370
+ function luhnValid(value) {
6371
+ const s = onlyDigits(value);
6372
+ if (s.length < 12) return false;
6373
+ let sum = 0;
6374
+ let double = false;
6375
+ for (let i = s.length - 1; i >= 0; i--) {
6376
+ let n = s.charCodeAt(i) - 48;
6377
+ if (double) {
6378
+ n *= 2;
6379
+ if (n > 9) n -= 9;
6380
+ }
6381
+ sum += n;
6382
+ double = !double;
6383
+ }
6384
+ return sum % 10 === 0;
6385
+ }
6386
+ function formatCardNumber(value) {
6387
+ const brand = detectBrand(value);
6388
+ const digits = onlyDigits(value).slice(0, maxCardLength(value));
6389
+ const gaps = brand?.gaps ?? [4, 8, 12, 16];
6390
+ let out = "";
6391
+ for (let i = 0; i < digits.length; i++) {
6392
+ if (gaps.includes(i)) out += " ";
6393
+ out += digits[i];
6394
+ }
6395
+ return out;
6396
+ }
6397
+ function cardNumberError(value) {
6398
+ const d = onlyDigits(value);
6399
+ if (!d) return "Card number is required";
6400
+ const brand = detectBrand(d);
6401
+ if (!brand) return "Unsupported card type";
6402
+ if (!brand.lengths.includes(d.length)) return "Card number is incomplete";
6403
+ if (!luhnValid(d)) return "Card number looks invalid";
6404
+ return void 0;
6405
+ }
6406
+ function formatExpiry(value) {
6407
+ let d = onlyDigits(value).slice(0, 4);
6408
+ if (d.length === 1 && d > "1") d = "0" + d;
6409
+ if (d.length <= 2) return d;
6410
+ return `${d.slice(0, 2)}/${d.slice(2)}`;
6411
+ }
6412
+ function expiryError(value, now = /* @__PURE__ */ new Date()) {
6413
+ if (!value) return "Expiry is required";
6414
+ const m = value.match(/^(\d{2})\/(\d{2})$/);
6415
+ if (!m) return "Use MM/YY";
6416
+ const mm = Number(m[1]);
6417
+ const yy = Number(m[2]);
6418
+ if (mm < 1 || mm > 12) return "Invalid month";
6419
+ const endOfMonth = new Date(2e3 + yy, mm, 0, 23, 59, 59, 999);
6420
+ if (endOfMonth < now) return "Card has expired";
6421
+ return void 0;
6422
+ }
6423
+ function cvvError(value, cardNumber) {
6424
+ const need = detectBrand(cardNumber)?.cvv ?? 3;
6425
+ const d = onlyDigits(value);
6426
+ if (!d) return "CVV is required";
6427
+ if (d.length !== need) return `CVV must be ${need} digits`;
6428
+ return void 0;
6429
+ }
6430
+ var toCard = (vals) => {
6431
+ const number = String(vals.number ?? "");
6432
+ return {
6433
+ number: onlyDigits(number),
6434
+ name: String(vals.name ?? ""),
6435
+ expiry: String(vals.expiry ?? ""),
6436
+ cvv: onlyDigits(String(vals.cvv ?? "")),
6437
+ brand: detectBrand(number)?.id ?? null
6438
+ };
6439
+ };
6440
+ function BrandMark({ brand }) {
6441
+ return /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", "aria-live": "polite", children: [
6442
+ /* @__PURE__ */ jsxs("svg", { width: "28", height: "18", viewBox: "0 0 28 18", fill: "none", "aria-hidden": "true", children: [
6443
+ /* @__PURE__ */ jsx("rect", { x: "0.5", y: "0.5", width: "27", height: "17", rx: "3", fill: "var(--color-surface-raised)", stroke: "var(--color-border)" }),
6444
+ /* @__PURE__ */ jsx("rect", { x: "0.5", y: "3.75", width: "27", height: "3.5", fill: brand ? brand.color : "var(--color-border-strong)" })
6445
+ ] }),
6446
+ brand && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold uppercase tracking-wide", style: { color: brand.color }, children: brand.short }),
6447
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: brand ? brand.label : "Unknown card type" })
6448
+ ] });
6449
+ }
6450
+ function CreditCardForm({
6451
+ onSubmit,
6452
+ onChange,
6453
+ defaultValue,
6454
+ size = "md",
6455
+ disabled,
6456
+ requireName = true,
6457
+ hideSubmit = false,
6458
+ submitLabel = "Pay",
6459
+ className = "",
6460
+ style
6461
+ }) {
6462
+ const initial = useRef({
6463
+ number: formatCardNumber(defaultValue?.number ?? ""),
6464
+ name: defaultValue?.name ?? "",
6465
+ expiry: formatExpiry(defaultValue?.expiry ?? ""),
6466
+ cvv: onlyDigits(defaultValue?.cvv ?? "")
6467
+ }).current;
6468
+ const form = useForm({ initialValues: initial });
6469
+ const numberStr = String(form.values.number ?? "");
6470
+ const brand = detectBrand(numberStr);
6471
+ useEffect(() => {
6472
+ onChange?.(toCard(form.values));
6473
+ }, [form.values.number, form.values.name, form.values.expiry, form.values.cvv]);
6474
+ const numberBind = form.fieldNative("number", {
6475
+ required: "Card number is required",
6476
+ validate: (v) => cardNumberError(String(v))
6477
+ });
6478
+ const nameBind = form.fieldNative("name", requireName ? { required: "Cardholder name is required" } : void 0);
6479
+ const expiryBind = form.fieldNative("expiry", {
6480
+ required: "Expiry is required",
6481
+ validate: (v) => expiryError(String(v))
6482
+ });
6483
+ const cvvBind = form.fieldNative("cvv", {
6484
+ required: "CVV is required",
6485
+ validate: (v) => cvvError(String(v), numberStr)
6486
+ });
6487
+ const cvvLen = brand?.cvv ?? 3;
6488
+ return /* @__PURE__ */ jsxs(
6489
+ Form,
6490
+ {
6491
+ form,
6492
+ onFinish: (vals) => onSubmit?.(toCard(vals)),
6493
+ className: `flex flex-col gap-4 ${className}`.trim(),
6494
+ style,
6495
+ children: [
6496
+ /* @__PURE__ */ jsx(
6497
+ TextInput,
6498
+ {
6499
+ ...numberBind,
6500
+ label: "Card number",
6501
+ placeholder: "1234 5678 9012 3456",
6502
+ size,
6503
+ disabled,
6504
+ value: numberStr,
6505
+ onChange: (e) => form.setValue("number", formatCardNumber(e.target.value), { touch: true }),
6506
+ suffix: /* @__PURE__ */ jsx(BrandMark, { brand })
6507
+ }
6508
+ ),
6509
+ /* @__PURE__ */ jsx(
6510
+ TextInput,
6511
+ {
6512
+ ...nameBind,
6513
+ label: "Cardholder name",
6514
+ placeholder: "Jane Appleseed",
6515
+ size,
6516
+ disabled,
6517
+ value: String(form.values.name ?? ""),
6518
+ onChange: (e) => form.setValue("name", e.target.value, { touch: true })
6519
+ }
6520
+ ),
6521
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
6522
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
6523
+ TextInput,
6524
+ {
6525
+ ...expiryBind,
6526
+ label: "Expiry",
6527
+ placeholder: "MM/YY",
6528
+ size,
6529
+ disabled,
6530
+ value: String(form.values.expiry ?? ""),
6531
+ onChange: (e) => form.setValue("expiry", formatExpiry(e.target.value), { touch: true })
6532
+ }
6533
+ ) }),
6534
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
6535
+ TextInput,
6536
+ {
6537
+ ...cvvBind,
6538
+ label: "CVV",
6539
+ placeholder: cvvLen === 4 ? "1234" : "123",
6540
+ size,
6541
+ disabled,
6542
+ value: String(form.values.cvv ?? ""),
6543
+ onChange: (e) => form.setValue("cvv", onlyDigits(e.target.value).slice(0, cvvLen), { touch: true })
6544
+ }
6545
+ ) })
6546
+ ] }),
6547
+ !hideSubmit && /* @__PURE__ */ jsx(Button, { content: submitLabel, buttonType: "submit", variant: "primary", disabled })
6548
+ ]
6549
+ }
6550
+ );
6551
+ }
6552
+
6553
+ export { AppShell, AutoComplete, Avatar, Box, Button, CARD_BRANDS, Catalog, CatalogCarousel, CatalogGrid, Checkbox, ColorPicker, ContextMenu, CreditCardForm, DateRangePicker, Drawer, Dropdown, FadingBase, Field, FieldHelpIcon, FieldLabel, FileInput, Flex, Form, FormContext, FormField, FormStore, Grid2 as Grid, GridCard, icons_default as Icon, IconButton, List2 as List, LoadingSpinner, Modal, NotificationProvider, NumberInput, OpaqueGridCard, OtpInput, Password, Portal, RadioGroup, Rating, ScalableContainer, SearchInput_default as SearchInput, SegmentedControl, Sidebar, SkeletonBox, SkeletonCard, SkeletonCircle, SkeletonText, Slider, Switch, Table, Tabs, TagsInput, DatePicker as Temporal, TextArea, TextInput, ThemeProvider, ThemeSwitch, TimePicker, Tooltip, TooltipProvider, TopBar, Tree, TreeSelect, Typography, Wizard, cardNumberError, cvvError, detectBrand, expiryError, fieldShell, formatCardNumber, formatExpiry, isRequired, luhnValid, onlyDigits, patterns, runFieldRules, useFieldArray, useForm, useFormField, useFormStore, useNotification };
6360
6554
  //# sourceMappingURL=index.js.map
6361
6555
  //# sourceMappingURL=index.js.map