@algenium/blocks 1.5.0-rc.1 → 1.5.0-rc.3

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
@@ -16,6 +16,9 @@ var PopoverPrimitive = require('@radix-ui/react-popover');
16
16
  var ScrollAreaPrimitive = require('@radix-ui/react-scroll-area');
17
17
  var dateFns = require('date-fns');
18
18
  var reactDayPicker = require('react-day-picker');
19
+ var valid = require('card-validator');
20
+
21
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
19
22
 
20
23
  function _interopNamespace(e) {
21
24
  if (e && e.__esModule) return e;
@@ -43,6 +46,7 @@ var TogglePrimitive__namespace = /*#__PURE__*/_interopNamespace(TogglePrimitive)
43
46
  var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive);
44
47
  var PopoverPrimitive__namespace = /*#__PURE__*/_interopNamespace(PopoverPrimitive);
45
48
  var ScrollAreaPrimitive__namespace = /*#__PURE__*/_interopNamespace(ScrollAreaPrimitive);
49
+ var valid__default = /*#__PURE__*/_interopDefault(valid);
46
50
 
47
51
  var CalendarContext = React2.createContext(null);
48
52
  function useCalendarContext() {
@@ -3168,7 +3172,7 @@ function DropdownMenuItem({
3168
3172
  "data-inset": inset,
3169
3173
  "data-variant": variant,
3170
3174
  className: cn(
3171
- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
3175
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[highlighted]:[&_svg:not([class*='text-'])]:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:[&_svg:not([class*='text-'])]:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:data-[highlighted]:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 dark:data-[variant=destructive]:data-[highlighted]:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:data-[highlighted]:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
3172
3176
  className
3173
3177
  ),
3174
3178
  ...props
@@ -3186,7 +3190,7 @@ function DropdownMenuCheckboxItem({
3186
3190
  {
3187
3191
  "data-slot": "dropdown-menu-checkbox-item",
3188
3192
  className: cn(
3189
- "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
3193
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[highlighted]:[&_svg:not([class*='text-'])]:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:[&_svg:not([class*='text-'])]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
3190
3194
  className
3191
3195
  ),
3192
3196
  checked,
@@ -3219,7 +3223,7 @@ function DropdownMenuRadioItem({
3219
3223
  {
3220
3224
  "data-slot": "dropdown-menu-radio-item",
3221
3225
  className: cn(
3222
- "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
3226
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[highlighted]:[&_svg:not([class*='text-'])]:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:[&_svg:not([class*='text-'])]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
3223
3227
  className
3224
3228
  ),
3225
3229
  ...props,
@@ -3294,7 +3298,7 @@ function DropdownMenuSubTrigger({
3294
3298
  "data-slot": "dropdown-menu-sub-trigger",
3295
3299
  "data-inset": inset,
3296
3300
  className: cn(
3297
- "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
3301
+ "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden data-[inset]:pl-8 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[highlighted]:[&_svg:not([class*='text-'])]:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:[&_svg:not([class*='text-'])]:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:[&_svg:not([class*='text-'])]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
3298
3302
  className
3299
3303
  ),
3300
3304
  ...props,
@@ -3370,9 +3374,9 @@ var buttonVariants = cva(
3370
3374
  variant: {
3371
3375
  default: "bg-primary text-primary-foreground hover:bg-primary/90",
3372
3376
  destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
3373
- outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
3377
+ outline: "border bg-background text-foreground shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:text-foreground dark:hover:bg-muted dark:hover:text-foreground",
3374
3378
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
3375
- ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
3379
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 dark:hover:text-foreground",
3376
3380
  link: "text-primary underline-offset-4 hover:underline"
3377
3381
  },
3378
3382
  size: {
@@ -3564,7 +3568,7 @@ function ThemeSwitcher({
3564
3568
  onClick: () => handleThemeClick(key),
3565
3569
  className: cn(
3566
3570
  "gap-2 cursor-pointer",
3567
- currentTheme === key && "bg-accent"
3571
+ currentTheme === key && "bg-accent text-accent-foreground [&_svg]:text-accent-foreground"
3568
3572
  ),
3569
3573
  children: [
3570
3574
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4" }),
@@ -3722,7 +3726,7 @@ function LanguageSwitcher({
3722
3726
  onClick: () => onLanguageChange?.(key),
3723
3727
  className: cn(
3724
3728
  "justify-center text-xs font-semibold cursor-pointer px-3",
3725
- currentLanguage === key && "bg-accent"
3729
+ currentLanguage === key && "bg-accent text-accent-foreground"
3726
3730
  ),
3727
3731
  children: nativeName
3728
3732
  },
@@ -3980,7 +3984,7 @@ function EnvironmentSwitcher({
3980
3984
  onClick: () => handleSelect(env),
3981
3985
  className: cn(
3982
3986
  "gap-2 cursor-pointer",
3983
- environment === env && "bg-accent"
3987
+ environment === env && "bg-accent text-accent-foreground [&_svg]:text-accent-foreground"
3984
3988
  ),
3985
3989
  children: [
3986
3990
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -7350,6 +7354,746 @@ function formatRelativeTime(dateStr) {
7350
7354
  if (diffD < 7) return `${diffD}d`;
7351
7355
  return date.toLocaleDateString([], { month: "short", day: "numeric" });
7352
7356
  }
7357
+ function useDebouncedValue(value, delayMs) {
7358
+ const [debounced, setDebounced] = React2.useState(value);
7359
+ React2.useEffect(() => {
7360
+ const id = window.setTimeout(() => setDebounced(value), delayMs);
7361
+ return () => window.clearTimeout(id);
7362
+ }, [value, delayMs]);
7363
+ return debounced;
7364
+ }
7365
+ function onlyDigits(s, max) {
7366
+ return s.replace(/\D/g, "").slice(0, max);
7367
+ }
7368
+ function formatPan(digits) {
7369
+ const cardInfo = valid__default.default.number(digits).card;
7370
+ const gaps = cardInfo?.gaps ?? [4, 8, 12];
7371
+ const parts = [];
7372
+ let idx = 0;
7373
+ for (const g of gaps) {
7374
+ parts.push(digits.slice(idx, g));
7375
+ idx = g;
7376
+ }
7377
+ parts.push(digits.slice(idx));
7378
+ return parts.filter((p) => p.length > 0).join(" ");
7379
+ }
7380
+ function parseExpiry(raw) {
7381
+ const d = onlyDigits(raw, 4);
7382
+ if (d.length < 4) return null;
7383
+ const mm = Number.parseInt(d.slice(0, 2), 10);
7384
+ const yy = Number.parseInt(d.slice(2, 4), 10);
7385
+ if (mm < 1 || mm > 12) return null;
7386
+ const year = yy < 100 ? 2e3 + yy : yy;
7387
+ const exp = valid__default.default.expirationDate({ month: String(mm), year: String(year) });
7388
+ if (!exp.isValid) return null;
7389
+ return { month: mm, year };
7390
+ }
7391
+ function normalizeBrand(detected) {
7392
+ if (!detected) return "";
7393
+ if (detected === "american-express") return "amex";
7394
+ return detected;
7395
+ }
7396
+ function brandAllowed(detected, accepted) {
7397
+ if (!accepted?.length) return true;
7398
+ if (!detected) return true;
7399
+ const n = normalizeBrand(detected);
7400
+ return accepted.map((a) => a.toLowerCase()).includes(n.toLowerCase());
7401
+ }
7402
+ var inputClassName = "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm";
7403
+ function CardInput({
7404
+ tokenize,
7405
+ value,
7406
+ onChange,
7407
+ acceptedBrands,
7408
+ requireHolderName = false,
7409
+ labels,
7410
+ disabled = false,
7411
+ largeText = false,
7412
+ className,
7413
+ onError
7414
+ }) {
7415
+ const baseId = React2.useId();
7416
+ const [panDigits, setPanDigits] = React2.useState("");
7417
+ const [expiryRaw, setExpiryRaw] = React2.useState("");
7418
+ const [cvc, setCvc] = React2.useState("");
7419
+ const [holderName, setHolderName] = React2.useState("");
7420
+ const [panFocused, setPanFocused] = React2.useState(false);
7421
+ const [submitting, setSubmitting] = React2.useState(false);
7422
+ const inputClass = largeText ? "text-base py-3" : "";
7423
+ const labelClass = largeText ? "text-base" : "text-sm";
7424
+ const numberValidation = valid__default.default.number(panDigits);
7425
+ const rawBrand = numberValidation.card?.type;
7426
+ const brand = normalizeBrand(rawBrand) || "unknown";
7427
+ const maxPanLen = numberValidation.card?.lengths?.slice(-1)[0] ?? 19;
7428
+ const minPanLenForType = numberValidation.card?.lengths?.[0];
7429
+ const panPastMinimumLength = minPanLenForType !== void 0 && panDigits.length >= minPanLenForType;
7430
+ const cvcSize = rawBrand === "american-express" || brand === "amex" ? 4 : 3;
7431
+ const expiryParsed = parseExpiry(expiryRaw);
7432
+ const expiryValid = expiryRaw.replace(/\D/g, "").length >= 4 && expiryParsed !== null ? valid__default.default.expirationDate({
7433
+ month: String(expiryParsed.month),
7434
+ year: String(expiryParsed.year)
7435
+ }).isValid : false;
7436
+ const cvvValid = cvc.length >= cvcSize && valid__default.default.cvv(cvc, cvcSize).isValid;
7437
+ const panComplete = numberValidation.isValid && brandAllowed(rawBrand, acceptedBrands);
7438
+ const holderOk = !requireHolderName || holderName.trim().length > 1;
7439
+ const canSubmit = panComplete && expiryValid && cvvValid && holderOk && !disabled && !value;
7440
+ const handlePanChange = (e) => {
7441
+ const d = onlyDigits(e.target.value, maxPanLen);
7442
+ setPanDigits(d);
7443
+ };
7444
+ const handleExpiryChange = (e) => {
7445
+ let d = onlyDigits(e.target.value, 4);
7446
+ if (d.length >= 2) d = `${d.slice(0, 2)}/${d.slice(2)}`;
7447
+ setExpiryRaw(d);
7448
+ };
7449
+ const runTokenize = React2.useCallback(async () => {
7450
+ if (!expiryParsed) return;
7451
+ setSubmitting(true);
7452
+ try {
7453
+ const result = await tokenize({
7454
+ pan: panDigits,
7455
+ expMonth: expiryParsed.month,
7456
+ expYear: expiryParsed.year,
7457
+ cvc,
7458
+ holderName: holderName.trim() || void 0
7459
+ });
7460
+ onChange(result);
7461
+ setPanDigits("");
7462
+ setExpiryRaw("");
7463
+ setCvc("");
7464
+ setHolderName("");
7465
+ } catch (err) {
7466
+ const msg = err instanceof Error ? err.message : labels.tokenizeFailed;
7467
+ onError?.(msg);
7468
+ } finally {
7469
+ setSubmitting(false);
7470
+ }
7471
+ }, [
7472
+ cvc,
7473
+ expiryParsed,
7474
+ holderName,
7475
+ labels.tokenizeFailed,
7476
+ onChange,
7477
+ onError,
7478
+ panDigits,
7479
+ tokenize
7480
+ ]);
7481
+ const displayPan = panFocused || panDigits.length <= 4 ? formatPan(panDigits) : `\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 ${panDigits.slice(-4)}`;
7482
+ if (value?.tokenId) {
7483
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7484
+ "div",
7485
+ {
7486
+ className: cn("space-y-3 rounded-lg border bg-muted/30 p-4", className),
7487
+ children: [
7488
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
7489
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-4 w-4 text-muted-foreground" }),
7490
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: value.masked }),
7491
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground", children: [
7492
+ String(value.expMonth).padStart(2, "0"),
7493
+ "/",
7494
+ String(value.expYear).slice(-2)
7495
+ ] })
7496
+ ] }),
7497
+ value.holderName ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: value.holderName }) : null,
7498
+ /* @__PURE__ */ jsxRuntime.jsx(
7499
+ Button,
7500
+ {
7501
+ type: "button",
7502
+ variant: "outline",
7503
+ size: "sm",
7504
+ disabled,
7505
+ onClick: () => onChange(null),
7506
+ children: labels.replace
7507
+ }
7508
+ )
7509
+ ]
7510
+ }
7511
+ );
7512
+ }
7513
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-4 rounded-lg border bg-card p-4", className), children: [
7514
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7515
+ /* @__PURE__ */ jsxRuntime.jsx(
7516
+ "label",
7517
+ {
7518
+ htmlFor: `${baseId}-pan`,
7519
+ className: cn("font-medium leading-none", labelClass),
7520
+ children: labels.number
7521
+ }
7522
+ ),
7523
+ /* @__PURE__ */ jsxRuntime.jsx(
7524
+ "input",
7525
+ {
7526
+ id: `${baseId}-pan`,
7527
+ inputMode: "numeric",
7528
+ autoComplete: "cc-number",
7529
+ value: displayPan,
7530
+ onChange: handlePanChange,
7531
+ onFocus: () => setPanFocused(true),
7532
+ onBlur: () => setPanFocused(false),
7533
+ placeholder: "4242 4242 4242 4242",
7534
+ disabled,
7535
+ className: cn(inputClassName, inputClass)
7536
+ }
7537
+ ),
7538
+ panDigits.length > 0 && (!numberValidation.isPotentiallyValid || panPastMinimumLength && !numberValidation.isValid) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: labels.invalidNumber }),
7539
+ panDigits.length > 0 && numberValidation.isPotentiallyValid && !brandAllowed(rawBrand, acceptedBrands) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: labels.invalidNumber })
7540
+ ] }),
7541
+ requireHolderName && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7542
+ /* @__PURE__ */ jsxRuntime.jsx(
7543
+ "label",
7544
+ {
7545
+ htmlFor: `${baseId}-holder`,
7546
+ className: cn("font-medium leading-none", labelClass),
7547
+ children: labels.holderName
7548
+ }
7549
+ ),
7550
+ /* @__PURE__ */ jsxRuntime.jsx(
7551
+ "input",
7552
+ {
7553
+ id: `${baseId}-holder`,
7554
+ autoComplete: "cc-name",
7555
+ value: holderName,
7556
+ onChange: (e) => setHolderName(e.target.value),
7557
+ disabled,
7558
+ className: cn(inputClassName, inputClass)
7559
+ }
7560
+ )
7561
+ ] }),
7562
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
7563
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7564
+ /* @__PURE__ */ jsxRuntime.jsx(
7565
+ "label",
7566
+ {
7567
+ htmlFor: `${baseId}-exp`,
7568
+ className: cn("font-medium leading-none", labelClass),
7569
+ children: labels.expiry
7570
+ }
7571
+ ),
7572
+ /* @__PURE__ */ jsxRuntime.jsx(
7573
+ "input",
7574
+ {
7575
+ id: `${baseId}-exp`,
7576
+ inputMode: "numeric",
7577
+ autoComplete: "cc-exp",
7578
+ value: expiryRaw,
7579
+ onChange: handleExpiryChange,
7580
+ placeholder: "MM/YY",
7581
+ disabled,
7582
+ className: cn(inputClassName, inputClass)
7583
+ }
7584
+ ),
7585
+ expiryRaw.replace(/\D/g, "").length >= 4 && !expiryValid && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: labels.expiredCard })
7586
+ ] }),
7587
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7588
+ /* @__PURE__ */ jsxRuntime.jsx(
7589
+ "label",
7590
+ {
7591
+ htmlFor: `${baseId}-cvc`,
7592
+ className: cn("font-medium leading-none", labelClass),
7593
+ children: labels.cvc
7594
+ }
7595
+ ),
7596
+ /* @__PURE__ */ jsxRuntime.jsx(
7597
+ "input",
7598
+ {
7599
+ id: `${baseId}-cvc`,
7600
+ inputMode: "numeric",
7601
+ autoComplete: "cc-csc",
7602
+ value: cvc,
7603
+ onChange: (e) => setCvc(onlyDigits(e.target.value, cvcSize)),
7604
+ placeholder: cvcSize === 4 ? "0000" : "000",
7605
+ disabled,
7606
+ className: cn(inputClassName, inputClass),
7607
+ maxLength: cvcSize
7608
+ }
7609
+ ),
7610
+ cvc.length > 0 && !cvvValid && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: labels.invalidCvc })
7611
+ ] })
7612
+ ] }),
7613
+ /* @__PURE__ */ jsxRuntime.jsxs(
7614
+ Button,
7615
+ {
7616
+ type: "button",
7617
+ disabled: !canSubmit || submitting,
7618
+ onClick: () => void runTokenize(),
7619
+ className: "gap-2",
7620
+ children: [
7621
+ submitting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-4 w-4" }),
7622
+ labels.tokenize
7623
+ ]
7624
+ }
7625
+ )
7626
+ ] });
7627
+ }
7628
+ function parseInitial(value) {
7629
+ if (!value) {
7630
+ return {
7631
+ street: "",
7632
+ street2: "",
7633
+ city: "",
7634
+ state: "",
7635
+ zip: "",
7636
+ country: "US"
7637
+ };
7638
+ }
7639
+ return {
7640
+ street: value.street ?? "",
7641
+ street2: value.street2 ?? "",
7642
+ city: value.city ?? "",
7643
+ state: value.state ?? "",
7644
+ zip: value.zip ?? "",
7645
+ country: "US"
7646
+ };
7647
+ }
7648
+ var inputClassName2 = "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm";
7649
+ function USAddressInput({
7650
+ value,
7651
+ onChange,
7652
+ lookupZip,
7653
+ validateAddress,
7654
+ autocomplete,
7655
+ labels,
7656
+ disabled = false,
7657
+ largeText = false,
7658
+ className,
7659
+ onError,
7660
+ showAutocompleteToggle = true
7661
+ }) {
7662
+ const [autocompleteEnabled, setAutocompleteEnabled] = React2.useState(false);
7663
+ const [searchQuery, setSearchQuery] = React2.useState("");
7664
+ const [showSuggestions, setShowSuggestions] = React2.useState(false);
7665
+ const [suggestions, setSuggestions] = React2.useState([]);
7666
+ const [address, setAddress] = React2.useState(
7667
+ () => parseInitial(value)
7668
+ );
7669
+ const [sessionToken] = React2.useState(
7670
+ () => typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`
7671
+ );
7672
+ const [loadingAutocomplete, setLoadingAutocomplete] = React2.useState(false);
7673
+ const [loadingZip, setLoadingZip] = React2.useState(false);
7674
+ const [loadingValidate, setLoadingValidate] = React2.useState(false);
7675
+ const [uspsSuggestion, setUspsSuggestion] = React2.useState(
7676
+ null
7677
+ );
7678
+ const searchRef = React2.useRef(null);
7679
+ const suggestionCache = React2.useRef(/* @__PURE__ */ new Map());
7680
+ const warnedRef = React2.useRef({
7681
+ autocomplete: false,
7682
+ zip: false,
7683
+ validate: false
7684
+ });
7685
+ const lastZipLookup = React2.useRef("");
7686
+ const debouncedSearch = useDebouncedValue(searchQuery, 300);
7687
+ const zipDigits = address.zip.replace(/\D/g, "").slice(0, 5);
7688
+ const debouncedZip = useDebouncedValue(zipDigits, 400);
7689
+ const inputClass = largeText ? "text-base py-3" : "";
7690
+ const labelClass = largeText ? "text-base" : "text-sm";
7691
+ React2.useEffect(() => {
7692
+ const handleClickOutside = (event) => {
7693
+ if (searchRef.current && !searchRef.current.contains(event.target)) {
7694
+ setShowSuggestions(false);
7695
+ }
7696
+ };
7697
+ document.addEventListener("mousedown", handleClickOutside);
7698
+ return () => document.removeEventListener("mousedown", handleClickOutside);
7699
+ }, []);
7700
+ const valueSyncKey = value == null ? "" : `o:${JSON.stringify(value)}`;
7701
+ React2.useEffect(() => {
7702
+ setAddress(parseInitial(value));
7703
+ }, [valueSyncKey]);
7704
+ const handleFieldChange = (field, fieldValue) => {
7705
+ setAddress((prev) => {
7706
+ const next = field === "country" ? { ...prev, country: "US" } : { ...prev, [field]: fieldValue };
7707
+ onChange(next);
7708
+ return next;
7709
+ });
7710
+ };
7711
+ const allowAutocomplete = Boolean(autocomplete);
7712
+ const allowUspsValidation = Boolean(validateAddress);
7713
+ const allowZipLookup = Boolean(lookupZip);
7714
+ React2.useEffect(() => {
7715
+ if (!allowAutocomplete || !autocompleteEnabled || !autocomplete) return;
7716
+ const q = debouncedSearch.trim();
7717
+ if (q.length < 3) {
7718
+ setSuggestions([]);
7719
+ setShowSuggestions(false);
7720
+ setLoadingAutocomplete(false);
7721
+ return;
7722
+ }
7723
+ const cacheKey = q.toLowerCase();
7724
+ const cached = suggestionCache.current.get(cacheKey);
7725
+ if (cached) {
7726
+ setSuggestions(cached);
7727
+ setShowSuggestions(true);
7728
+ setLoadingAutocomplete(false);
7729
+ return;
7730
+ }
7731
+ const ctrl = new AbortController();
7732
+ setLoadingAutocomplete(true);
7733
+ autocomplete.search(q, sessionToken, ctrl.signal).then((list) => {
7734
+ suggestionCache.current.set(cacheKey, list);
7735
+ setSuggestions(list);
7736
+ setShowSuggestions(list.length > 0);
7737
+ }).catch((e) => {
7738
+ if (e.name === "AbortError") return;
7739
+ if (!warnedRef.current.autocomplete) {
7740
+ onError?.(labels.autocompleteUnavailable);
7741
+ warnedRef.current.autocomplete = true;
7742
+ }
7743
+ setSuggestions([]);
7744
+ setShowSuggestions(false);
7745
+ }).finally(() => setLoadingAutocomplete(false));
7746
+ return () => ctrl.abort();
7747
+ }, [
7748
+ debouncedSearch,
7749
+ allowAutocomplete,
7750
+ autocompleteEnabled,
7751
+ autocomplete,
7752
+ sessionToken,
7753
+ labels.autocompleteUnavailable,
7754
+ onError
7755
+ ]);
7756
+ React2.useEffect(() => {
7757
+ if (!allowZipLookup || !lookupZip) return;
7758
+ if (debouncedZip.length !== 5) return;
7759
+ if (debouncedZip === lastZipLookup.current) return;
7760
+ const ctrl = new AbortController();
7761
+ setLoadingZip(true);
7762
+ lookupZip(debouncedZip, ctrl.signal).then((data) => {
7763
+ lastZipLookup.current = debouncedZip;
7764
+ setAddress((prev) => {
7765
+ const cityEmpty = !prev.city.trim();
7766
+ const stateEmpty = !prev.state.trim();
7767
+ const next = { ...prev };
7768
+ if (cityEmpty && data.city) next.city = data.city;
7769
+ if (stateEmpty && data.state) next.state = data.state;
7770
+ next.country = "US";
7771
+ onChange(next);
7772
+ return next;
7773
+ });
7774
+ }).catch(() => {
7775
+ if (!warnedRef.current.zip) {
7776
+ onError?.(labels.zipLookupFailed);
7777
+ warnedRef.current.zip = true;
7778
+ }
7779
+ }).finally(() => setLoadingZip(false));
7780
+ return () => ctrl.abort();
7781
+ }, [
7782
+ debouncedZip,
7783
+ allowZipLookup,
7784
+ lookupZip,
7785
+ onChange,
7786
+ labels.zipLookupFailed,
7787
+ onError
7788
+ ]);
7789
+ const handleSelectSuggestion = async (row) => {
7790
+ if (!autocomplete) return;
7791
+ try {
7792
+ const next = await autocomplete.details(
7793
+ row.placeId,
7794
+ AbortSignal.timeout(8e3)
7795
+ );
7796
+ const normalized = {
7797
+ ...next,
7798
+ country: "US"
7799
+ };
7800
+ setAddress(normalized);
7801
+ onChange(normalized);
7802
+ setShowSuggestions(false);
7803
+ setSearchQuery("");
7804
+ setUspsSuggestion(null);
7805
+ } catch {
7806
+ onError?.(labels.placeLookupFailed);
7807
+ }
7808
+ };
7809
+ const runUspsValidate = async () => {
7810
+ if (!allowUspsValidation || !validateAddress || disabled) return;
7811
+ setLoadingValidate(true);
7812
+ setUspsSuggestion(null);
7813
+ try {
7814
+ const result = await validateAddress(address, AbortSignal.timeout(8e3));
7815
+ const std = result.standardized;
7816
+ if (result.changed) {
7817
+ setUspsSuggestion(std);
7818
+ }
7819
+ } catch {
7820
+ if (!warnedRef.current.validate) {
7821
+ onError?.(labels.uspsUnavailable);
7822
+ warnedRef.current.validate = true;
7823
+ }
7824
+ } finally {
7825
+ setLoadingValidate(false);
7826
+ }
7827
+ };
7828
+ const applyUspsSuggestion = () => {
7829
+ if (!uspsSuggestion) return;
7830
+ setAddress(uspsSuggestion);
7831
+ onChange(uspsSuggestion);
7832
+ setUspsSuggestion(null);
7833
+ };
7834
+ const showAutocompleteUi = allowAutocomplete && showAutocompleteToggle && autocompleteEnabled;
7835
+ const toggleTitle = labels.autocompleteTitle ?? labels.searchPlaceholder;
7836
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-4", className), children: [
7837
+ allowAutocomplete && showAutocompleteToggle && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between rounded-lg border bg-muted/50 p-3", children: [
7838
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
7839
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MapPin, { className: "h-4 w-4 text-muted-foreground" }),
7840
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
7841
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("font-medium", labelClass), children: toggleTitle }),
7842
+ labels.autocompleteDescription ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: labels.autocompleteDescription }) : null
7843
+ ] })
7844
+ ] }),
7845
+ /* @__PURE__ */ jsxRuntime.jsx(
7846
+ "button",
7847
+ {
7848
+ type: "button",
7849
+ role: "switch",
7850
+ "aria-checked": autocompleteEnabled,
7851
+ disabled,
7852
+ onClick: () => setAutocompleteEnabled((v) => !v),
7853
+ className: cn(
7854
+ "relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors",
7855
+ autocompleteEnabled ? "bg-primary" : "bg-input",
7856
+ disabled && "cursor-not-allowed opacity-50"
7857
+ ),
7858
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7859
+ "span",
7860
+ {
7861
+ className: cn(
7862
+ "pointer-events-none block size-5 translate-x-0 rounded-full bg-background shadow-lg ring-0 transition-transform",
7863
+ autocompleteEnabled && "translate-x-5"
7864
+ )
7865
+ }
7866
+ )
7867
+ }
7868
+ )
7869
+ ] }),
7870
+ showAutocompleteUi && autocomplete && /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: searchRef, className: "relative", children: [
7871
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
7872
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
7873
+ /* @__PURE__ */ jsxRuntime.jsx(
7874
+ "input",
7875
+ {
7876
+ value: searchQuery,
7877
+ onChange: (e) => setSearchQuery(e.target.value),
7878
+ placeholder: labels.searchPlaceholder,
7879
+ disabled,
7880
+ className: cn(inputClassName2, "pl-9 pr-9", inputClass)
7881
+ }
7882
+ ),
7883
+ loadingAutocomplete && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "absolute right-10 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" }),
7884
+ searchQuery ? /* @__PURE__ */ jsxRuntime.jsx(
7885
+ "button",
7886
+ {
7887
+ type: "button",
7888
+ onClick: () => setSearchQuery(""),
7889
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
7890
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
7891
+ }
7892
+ ) : null
7893
+ ] }),
7894
+ showSuggestions && suggestions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute z-10 mt-1 max-h-48 w-full overflow-y-auto rounded-lg border bg-background shadow-lg", children: suggestions.map((suggestion) => /* @__PURE__ */ jsxRuntime.jsxs(
7895
+ "button",
7896
+ {
7897
+ type: "button",
7898
+ onClick: () => void handleSelectSuggestion(suggestion),
7899
+ className: "w-full border-b px-4 py-3 text-left transition-colors last:border-b-0 hover:bg-muted",
7900
+ children: [
7901
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: suggestion.mainText }),
7902
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: suggestion.secondaryText })
7903
+ ]
7904
+ },
7905
+ suggestion.placeId
7906
+ )) })
7907
+ ] }),
7908
+ allowUspsValidation && uspsSuggestion && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950/30", children: [
7909
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-amber-900 dark:text-amber-100", children: labels.uspsSuggested }),
7910
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: [
7911
+ uspsSuggestion.street,
7912
+ uspsSuggestion.street2,
7913
+ uspsSuggestion.city,
7914
+ uspsSuggestion.state,
7915
+ uspsSuggestion.zip
7916
+ ].filter(Boolean).join(", ") }),
7917
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
7918
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", size: "sm", onClick: applyUspsSuggestion, children: labels.uspsUseSuggestion }),
7919
+ /* @__PURE__ */ jsxRuntime.jsx(
7920
+ Button,
7921
+ {
7922
+ type: "button",
7923
+ size: "sm",
7924
+ variant: "outline",
7925
+ onClick: () => setUspsSuggestion(null),
7926
+ children: labels.uspsKeepMine
7927
+ }
7928
+ )
7929
+ ] })
7930
+ ] }),
7931
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4", children: [
7932
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7933
+ /* @__PURE__ */ jsxRuntime.jsx(
7934
+ "label",
7935
+ {
7936
+ htmlFor: "blocks-usaddress-street",
7937
+ className: cn("font-medium leading-none", labelClass),
7938
+ children: labels.street
7939
+ }
7940
+ ),
7941
+ /* @__PURE__ */ jsxRuntime.jsx(
7942
+ "input",
7943
+ {
7944
+ id: "blocks-usaddress-street",
7945
+ value: address.street,
7946
+ onChange: (e) => handleFieldChange("street", e.target.value),
7947
+ placeholder: labels.streetPlaceholder ?? "",
7948
+ disabled,
7949
+ className: cn(inputClassName2, inputClass)
7950
+ }
7951
+ )
7952
+ ] }),
7953
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7954
+ /* @__PURE__ */ jsxRuntime.jsxs(
7955
+ "label",
7956
+ {
7957
+ htmlFor: "blocks-usaddress-street2",
7958
+ className: cn(
7959
+ `${labelClass} text-muted-foreground`,
7960
+ "leading-none"
7961
+ ),
7962
+ children: [
7963
+ labels.street2 ?? "Address line 2",
7964
+ " ",
7965
+ labels.street2OptionalHint ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs", children: [
7966
+ "(",
7967
+ labels.street2OptionalHint,
7968
+ ")"
7969
+ ] }) : null
7970
+ ]
7971
+ }
7972
+ ),
7973
+ /* @__PURE__ */ jsxRuntime.jsx(
7974
+ "input",
7975
+ {
7976
+ id: "blocks-usaddress-street2",
7977
+ value: address.street2 || "",
7978
+ onChange: (e) => handleFieldChange("street2", e.target.value),
7979
+ placeholder: labels.street2Placeholder ?? "",
7980
+ disabled,
7981
+ className: cn(inputClassName2, inputClass)
7982
+ }
7983
+ )
7984
+ ] }),
7985
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
7986
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7987
+ /* @__PURE__ */ jsxRuntime.jsx(
7988
+ "label",
7989
+ {
7990
+ htmlFor: "blocks-usaddress-city",
7991
+ className: cn("font-medium leading-none", labelClass),
7992
+ children: labels.city
7993
+ }
7994
+ ),
7995
+ /* @__PURE__ */ jsxRuntime.jsx(
7996
+ "input",
7997
+ {
7998
+ id: "blocks-usaddress-city",
7999
+ value: address.city,
8000
+ onChange: (e) => handleFieldChange("city", e.target.value),
8001
+ placeholder: labels.cityPlaceholder ?? "",
8002
+ disabled,
8003
+ className: cn(inputClassName2, inputClass)
8004
+ }
8005
+ )
8006
+ ] }),
8007
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
8008
+ /* @__PURE__ */ jsxRuntime.jsx(
8009
+ "label",
8010
+ {
8011
+ htmlFor: "blocks-usaddress-state",
8012
+ className: cn("font-medium leading-none", labelClass),
8013
+ children: labels.state
8014
+ }
8015
+ ),
8016
+ /* @__PURE__ */ jsxRuntime.jsx(
8017
+ "input",
8018
+ {
8019
+ id: "blocks-usaddress-state",
8020
+ value: address.state,
8021
+ onChange: (e) => handleFieldChange("state", e.target.value),
8022
+ placeholder: labels.statePlaceholder ?? "",
8023
+ disabled,
8024
+ className: cn(inputClassName2, inputClass)
8025
+ }
8026
+ )
8027
+ ] })
8028
+ ] }),
8029
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
8030
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
8031
+ /* @__PURE__ */ jsxRuntime.jsx(
8032
+ "label",
8033
+ {
8034
+ htmlFor: "blocks-usaddress-zip",
8035
+ className: cn("font-medium leading-none", labelClass),
8036
+ children: labels.zip
8037
+ }
8038
+ ),
8039
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
8040
+ /* @__PURE__ */ jsxRuntime.jsx(
8041
+ "input",
8042
+ {
8043
+ id: "blocks-usaddress-zip",
8044
+ value: address.zip,
8045
+ onChange: (e) => handleFieldChange(
8046
+ "zip",
8047
+ e.target.value.replace(/\D/g, "").slice(0, 10)
8048
+ ),
8049
+ placeholder: labels.zipPlaceholder ?? "",
8050
+ disabled,
8051
+ className: cn(inputClassName2, inputClass)
8052
+ }
8053
+ ),
8054
+ loadingZip && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" })
8055
+ ] })
8056
+ ] }),
8057
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
8058
+ /* @__PURE__ */ jsxRuntime.jsx(
8059
+ "label",
8060
+ {
8061
+ htmlFor: "blocks-usaddress-country",
8062
+ className: cn("font-medium leading-none", labelClass),
8063
+ children: labels.countryLabel ?? "Country"
8064
+ }
8065
+ ),
8066
+ /* @__PURE__ */ jsxRuntime.jsx(
8067
+ "input",
8068
+ {
8069
+ id: "blocks-usaddress-country",
8070
+ value: "United States",
8071
+ readOnly: true,
8072
+ disabled,
8073
+ className: cn(inputClassName2, inputClass, "bg-muted/50"),
8074
+ "aria-readonly": "true"
8075
+ }
8076
+ )
8077
+ ] })
8078
+ ] }),
8079
+ allowUspsValidation && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(
8080
+ Button,
8081
+ {
8082
+ type: "button",
8083
+ variant: "secondary",
8084
+ size: "sm",
8085
+ disabled: disabled || loadingValidate,
8086
+ onClick: () => void runUspsValidate(),
8087
+ className: "gap-2",
8088
+ children: loadingValidate ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8089
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }),
8090
+ labels.uspsValidating
8091
+ ] }) : labels.uspsValidate
8092
+ }
8093
+ ) })
8094
+ ] })
8095
+ ] });
8096
+ }
7353
8097
 
7354
8098
  exports.AvatarEditor = AvatarEditor;
7355
8099
  exports.AvatarEditorDialog = AvatarEditorDialog;
@@ -7359,6 +8103,7 @@ exports.CalendarContext = CalendarContext;
7359
8103
  exports.CalendarSubscribeButton = CalendarSubscribeButton;
7360
8104
  exports.CalendarView = CalendarView;
7361
8105
  exports.CalendarWidget = CalendarWidget;
8106
+ exports.CardInput = CardInput;
7362
8107
  exports.ChatRoomView = ChatRoomView;
7363
8108
  exports.ChatSidebar = ChatSidebar;
7364
8109
  exports.ChatSidebarContext = ChatSidebarContext;
@@ -7423,6 +8168,7 @@ exports.Tooltip = Tooltip;
7423
8168
  exports.TooltipContent = TooltipContent;
7424
8169
  exports.TooltipProvider = TooltipProvider;
7425
8170
  exports.TooltipTrigger = TooltipTrigger;
8171
+ exports.USAddressInput = USAddressInput;
7426
8172
  exports.UpcomingEvents = UpcomingEvents;
7427
8173
  exports.buttonVariants = buttonVariants;
7428
8174
  exports.cn = cn;
@@ -7434,6 +8180,7 @@ exports.toggleVariants = toggleVariants;
7434
8180
  exports.useCalendarContext = useCalendarContext;
7435
8181
  exports.useChatRoom = useChatRoom;
7436
8182
  exports.useChatSidebar = useChatSidebar;
8183
+ exports.useDebouncedValue = useDebouncedValue;
7437
8184
  exports.useEnvironmentContext = useEnvironmentContext;
7438
8185
  exports.useLanguageContext = useLanguageContext;
7439
8186
  exports.useNotificationsContext = useNotificationsContext;