acai-ui-library 0.1.13 → 0.1.15

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.
Files changed (71) hide show
  1. package/dist/components/alert.cjs +3 -2
  2. package/dist/components/alert.cjs.map +1 -1
  3. package/dist/components/alert.js +3 -2
  4. package/dist/components/alert.js.map +1 -1
  5. package/dist/components/card.cjs +1 -1
  6. package/dist/components/card.cjs.map +1 -1
  7. package/dist/components/card.js +1 -1
  8. package/dist/components/card.js.map +1 -1
  9. package/dist/components/checkbox.cjs +2 -1
  10. package/dist/components/checkbox.cjs.map +1 -1
  11. package/dist/components/checkbox.js +2 -1
  12. package/dist/components/checkbox.js.map +1 -1
  13. package/dist/components/data-table.cjs +2 -1
  14. package/dist/components/data-table.cjs.map +1 -1
  15. package/dist/components/data-table.js +2 -1
  16. package/dist/components/data-table.js.map +1 -1
  17. package/dist/components/date-picker.cjs.map +1 -1
  18. package/dist/components/date-picker.js.map +1 -1
  19. package/dist/components/dropdown-menu.cjs +2 -2
  20. package/dist/components/dropdown-menu.cjs.map +1 -1
  21. package/dist/components/dropdown-menu.js +2 -2
  22. package/dist/components/dropdown-menu.js.map +1 -1
  23. package/dist/components/impersonation-banner.cjs +137 -0
  24. package/dist/components/impersonation-banner.cjs.map +1 -0
  25. package/dist/components/impersonation-banner.d.cts +36 -0
  26. package/dist/components/impersonation-banner.d.ts +36 -0
  27. package/dist/components/impersonation-banner.js +101 -0
  28. package/dist/components/impersonation-banner.js.map +1 -0
  29. package/dist/components/input.cjs +2 -1
  30. package/dist/components/input.cjs.map +1 -1
  31. package/dist/components/input.js +2 -1
  32. package/dist/components/input.js.map +1 -1
  33. package/dist/components/item.cjs +1 -2
  34. package/dist/components/item.cjs.map +1 -1
  35. package/dist/components/item.js +1 -2
  36. package/dist/components/item.js.map +1 -1
  37. package/dist/components/pagination.cjs +3 -1
  38. package/dist/components/pagination.cjs.map +1 -1
  39. package/dist/components/pagination.d.cts +1 -1
  40. package/dist/components/pagination.d.ts +1 -1
  41. package/dist/components/pagination.js +3 -1
  42. package/dist/components/pagination.js.map +1 -1
  43. package/dist/components/read-only-guard.cjs +103 -0
  44. package/dist/components/read-only-guard.cjs.map +1 -0
  45. package/dist/components/read-only-guard.d.cts +40 -0
  46. package/dist/components/read-only-guard.d.ts +40 -0
  47. package/dist/components/read-only-guard.js +64 -0
  48. package/dist/components/read-only-guard.js.map +1 -0
  49. package/dist/components/stepper.cjs.map +1 -1
  50. package/dist/components/stepper.js.map +1 -1
  51. package/dist/components/switch.cjs +2 -2
  52. package/dist/components/switch.cjs.map +1 -1
  53. package/dist/components/switch.js +2 -2
  54. package/dist/components/switch.js.map +1 -1
  55. package/dist/components/typography.cjs +4 -4
  56. package/dist/components/typography.cjs.map +1 -1
  57. package/dist/components/typography.js +4 -4
  58. package/dist/components/typography.js.map +1 -1
  59. package/dist/index.cjs +240 -16
  60. package/dist/index.cjs.map +1 -1
  61. package/dist/index.d.cts +3 -0
  62. package/dist/index.d.ts +3 -0
  63. package/dist/index.js +228 -16
  64. package/dist/index.js.map +1 -1
  65. package/dist/lib/impersonation.cjs +127 -0
  66. package/dist/lib/impersonation.cjs.map +1 -0
  67. package/dist/lib/impersonation.d.cts +45 -0
  68. package/dist/lib/impersonation.d.ts +45 -0
  69. package/dist/lib/impersonation.js +86 -0
  70. package/dist/lib/impersonation.js.map +1 -0
  71. package/package.json +1 -1
package/dist/index.d.cts CHANGED
@@ -62,6 +62,9 @@ export { Toaster } from './components/sonner.cjs';
62
62
  export { Body, Caption, Code, H1, H2, H3, H4, Lead, Muted, Small, TypographyColor, TypographyWeight } from './components/typography.cjs';
63
63
  export { ThemeProvider } from './components/theme-provider.cjs';
64
64
  export { Banner, BannerProps } from './components/banner.cjs';
65
+ export { ImpersonationBanner, ImpersonationBannerProps, formatRemaining } from './components/impersonation-banner.cjs';
66
+ export { READ_ONLY_REASON, ReadOnlyGuard, ReadOnlyGuardProps, isGuarded } from './components/read-only-guard.cjs';
67
+ export { INACTIVE_IMPERSONATION, ImpersonationMode, ImpersonationState, OPERATION_CLASSES, OperationClass, approvedOpsOf, decodeJwtPayload, modeOfOps, readImpersonation, useImpersonation } from './lib/impersonation.cjs';
65
68
  export { ThemeProviderProps, useTheme } from 'next-themes';
66
69
  export { toast } from 'sonner';
67
70
  import 'clsx';
package/dist/index.d.ts CHANGED
@@ -62,6 +62,9 @@ export { Toaster } from './components/sonner.js';
62
62
  export { Body, Caption, Code, H1, H2, H3, H4, Lead, Muted, Small, TypographyColor, TypographyWeight } from './components/typography.js';
63
63
  export { ThemeProvider } from './components/theme-provider.js';
64
64
  export { Banner, BannerProps } from './components/banner.js';
65
+ export { ImpersonationBanner, ImpersonationBannerProps, formatRemaining } from './components/impersonation-banner.js';
66
+ export { READ_ONLY_REASON, ReadOnlyGuard, ReadOnlyGuardProps, isGuarded } from './components/read-only-guard.js';
67
+ export { INACTIVE_IMPERSONATION, ImpersonationMode, ImpersonationState, OPERATION_CLASSES, OperationClass, approvedOpsOf, decodeJwtPayload, modeOfOps, readImpersonation, useImpersonation } from './lib/impersonation.js';
65
68
  export { ThemeProviderProps, useTheme } from 'next-themes';
66
69
  export { toast } from 'sonner';
67
70
  import 'clsx';
package/dist/index.js CHANGED
@@ -126,7 +126,8 @@ var Input = React2.forwardRef(
126
126
  onChange,
127
127
  ...props
128
128
  }, ref) => {
129
- const inputId = id || React2.useId();
129
+ const generatedId = React2.useId();
130
+ const inputId = id || generatedId;
130
131
  const isControlled = value !== void 0;
131
132
  const [internalValue, setInternalValue] = React2.useState(defaultValue);
132
133
  const innerRef = React2.useRef(null);
@@ -499,7 +500,8 @@ var checkboxIconSize = {
499
500
  lg: "size-4"
500
501
  };
501
502
  var Checkbox = React7.forwardRef(({ className, label, id, variant, size = "default", ...props }, ref) => {
502
- const checkboxId = id || React7.useId();
503
+ const generatedId = React7.useId();
504
+ const checkboxId = id || generatedId;
503
505
  return /* @__PURE__ */ jsxs6("div", { className: "inline-flex items-center gap-2", children: [
504
506
  /* @__PURE__ */ jsx7(
505
507
  CheckboxPrimitive.Root,
@@ -561,7 +563,7 @@ import * as SwitchPrimitives from "@radix-ui/react-switch";
561
563
  import { cva as cva5 } from "class-variance-authority";
562
564
  import { jsx as jsx9 } from "react/jsx-runtime";
563
565
  var switchVariants = cva5(
564
- "peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=unchecked]:bg-[var(--hc-grey-200)] aria-[invalid=true]:ring-2 aria-[invalid=true]:ring-destructive aria-[invalid=true]:ring-offset-2",
566
+ "peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=unchecked]:bg-[var(--hc-grey-400,#7C7C83)] aria-[invalid=true]:ring-2 aria-[invalid=true]:ring-destructive aria-[invalid=true]:ring-offset-2",
565
567
  {
566
568
  variants: {
567
569
  variant: {
@@ -596,7 +598,7 @@ var Switch = React9.forwardRef(({ className, variant, size = "default", ...props
596
598
  SwitchPrimitives.Thumb,
597
599
  {
598
600
  className: cn(
599
- "pointer-events-none block rounded-full bg-white shadow-lg ring-0 transition-transform duration-200 [[disabled]_&]:bg-[var(--hc-grey-300)]",
601
+ "pointer-events-none block rounded-full bg-white shadow-lg ring-0 transition-transform duration-200 [[disabled]_&]:bg-[var(--hc-grey-300,#A2A2A6)]",
600
602
  thumbStyles[size ?? "default"]
601
603
  )
602
604
  }
@@ -1249,12 +1251,13 @@ var Alert = React17.forwardRef(({ className, variant, ...props }, ref) => /* @__
1249
1251
  }
1250
1252
  ));
1251
1253
  Alert.displayName = "Alert";
1252
- var AlertTitle = React17.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19(
1254
+ var AlertTitle = React17.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsx19(
1253
1255
  "h5",
1254
1256
  {
1255
1257
  ref,
1256
1258
  className: cn("mb-1 font-medium leading-none tracking-tight", className),
1257
- ...props
1259
+ ...props,
1260
+ children
1258
1261
  }
1259
1262
  ));
1260
1263
  AlertTitle.displayName = "AlertTitle";
@@ -1626,7 +1629,7 @@ var Item4 = React24.forwardRef(
1626
1629
  ({ className, clickable, onClick, children, ...props }, ref) => {
1627
1630
  const baseClasses = "flex items-center gap-3 px-4 py-3 rounded-md";
1628
1631
  const interactiveClasses = "cursor-pointer transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--hc-primary-500)] w-full text-start";
1629
- if (clickable) {
1632
+ if (clickable || onClick) {
1630
1633
  return /* @__PURE__ */ jsx27(
1631
1634
  "button",
1632
1635
  {
@@ -1643,7 +1646,6 @@ var Item4 = React24.forwardRef(
1643
1646
  "div",
1644
1647
  {
1645
1648
  ref,
1646
- onClick,
1647
1649
  className: cn(baseClasses, className),
1648
1650
  ...props,
1649
1651
  children
@@ -2364,7 +2366,7 @@ var DropdownMenuSubContent = React32.forwardRef(({ className, ...props }, ref) =
2364
2366
  {
2365
2367
  ref,
2366
2368
  className: cn(
2367
- "z-50 min-w-[8rem] overflow-hidden rounded-[6px] border border-[var(--hc-color-border-subtle)] bg-popover p-1 text-popover-foreground shadow-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
2369
+ "z-50 min-w-[8rem] overflow-hidden rounded-[6px] border border-[var(--hc-color-border-subtle)] bg-popover p-1 text-popover-foreground shadow-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:pointer-events-none data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
2368
2370
  className
2369
2371
  ),
2370
2372
  ...props
@@ -2377,7 +2379,7 @@ var DropdownMenuContent = React32.forwardRef(({ className, sideOffset = 4, ...pr
2377
2379
  ref,
2378
2380
  sideOffset,
2379
2381
  className: cn(
2380
- "z-50 min-w-[8rem] overflow-hidden rounded-[6px] border border-[var(--hc-color-border-subtle)] bg-popover p-1 text-popover-foreground shadow-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
2382
+ "z-50 min-w-[8rem] overflow-hidden rounded-[6px] border border-[var(--hc-color-border-subtle)] bg-popover p-1 text-popover-foreground shadow-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:pointer-events-none data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
2381
2383
  className
2382
2384
  ),
2383
2385
  ...props
@@ -3209,7 +3211,7 @@ var CardHeader = React39.forwardRef(
3209
3211
  );
3210
3212
  CardHeader.displayName = "CardHeader";
3211
3213
  var CardTitle = React39.forwardRef(
3212
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx42("h3", { ref, className: cn("text-2xl font-semibold leading-none tracking-tight", className), ...props })
3214
+ ({ className, children, ...props }, ref) => /* @__PURE__ */ jsx42("h3", { ref, className: cn("text-2xl font-semibold leading-none tracking-tight", className), ...props, children })
3213
3215
  );
3214
3216
  CardTitle.displayName = "CardTitle";
3215
3217
  var CardDescription = React39.forwardRef(
@@ -3584,6 +3586,7 @@ var PaginationLink = ({
3584
3586
  className,
3585
3587
  isActive,
3586
3588
  size = "icon",
3589
+ children,
3587
3590
  ...props
3588
3591
  }) => /* @__PURE__ */ jsx47(
3589
3592
  "a",
@@ -3596,7 +3599,8 @@ var PaginationLink = ({
3596
3599
  }),
3597
3600
  className
3598
3601
  ),
3599
- ...props
3602
+ ...props,
3603
+ children
3600
3604
  }
3601
3605
  );
3602
3606
  PaginationLink.displayName = "PaginationLink";
@@ -4354,19 +4358,19 @@ function resolveColor(color, defaultColorClass) {
4354
4358
  return colorMap[color];
4355
4359
  }
4356
4360
  var H1 = React50.forwardRef(
4357
- ({ className, weight = "semibold", color, ...props }, ref) => /* @__PURE__ */ jsx58("h1", { ref, className: cn("scroll-m-20 text-3xl tracking-tight", resolveColor(color, "text-foreground"), weightMap[weight], className), ...props })
4361
+ ({ className, weight = "semibold", color, children, ...props }, ref) => /* @__PURE__ */ jsx58("h1", { ref, className: cn("scroll-m-20 text-3xl tracking-tight", resolveColor(color, "text-foreground"), weightMap[weight], className), ...props, children })
4358
4362
  );
4359
4363
  H1.displayName = "H1";
4360
4364
  var H2 = React50.forwardRef(
4361
- ({ className, weight = "semibold", color, ...props }, ref) => /* @__PURE__ */ jsx58("h2", { ref, className: cn("scroll-m-20 text-2xl tracking-tight", resolveColor(color, "text-foreground"), weightMap[weight], className), ...props })
4365
+ ({ className, weight = "semibold", color, children, ...props }, ref) => /* @__PURE__ */ jsx58("h2", { ref, className: cn("scroll-m-20 text-2xl tracking-tight", resolveColor(color, "text-foreground"), weightMap[weight], className), ...props, children })
4362
4366
  );
4363
4367
  H2.displayName = "H2";
4364
4368
  var H3 = React50.forwardRef(
4365
- ({ className, weight = "semibold", color, ...props }, ref) => /* @__PURE__ */ jsx58("h3", { ref, className: cn("scroll-m-20 text-xl tracking-tight", resolveColor(color, "text-foreground"), weightMap[weight], className), ...props })
4369
+ ({ className, weight = "semibold", color, children, ...props }, ref) => /* @__PURE__ */ jsx58("h3", { ref, className: cn("scroll-m-20 text-xl tracking-tight", resolveColor(color, "text-foreground"), weightMap[weight], className), ...props, children })
4366
4370
  );
4367
4371
  H3.displayName = "H3";
4368
4372
  var H4 = React50.forwardRef(
4369
- ({ className, weight = "semibold", color, ...props }, ref) => /* @__PURE__ */ jsx58("h4", { ref, className: cn("scroll-m-20 text-lg tracking-tight", resolveColor(color, "text-foreground"), weightMap[weight], className), ...props })
4373
+ ({ className, weight = "semibold", color, children, ...props }, ref) => /* @__PURE__ */ jsx58("h4", { ref, className: cn("scroll-m-20 text-lg tracking-tight", resolveColor(color, "text-foreground"), weightMap[weight], className), ...props, children })
4370
4374
  );
4371
4375
  H4.displayName = "H4";
4372
4376
  var Body = React50.forwardRef(
@@ -4451,6 +4455,202 @@ var Banner = React51.forwardRef(
4451
4455
  )
4452
4456
  );
4453
4457
  Banner.displayName = "Banner";
4458
+
4459
+ // src/components/impersonation-banner.tsx
4460
+ import * as React52 from "react";
4461
+ import { Fragment as Fragment3, jsx as jsx61, jsxs as jsxs41 } from "react/jsx-runtime";
4462
+ function toMs(value) {
4463
+ if (value instanceof Date) return value.getTime();
4464
+ if (typeof value === "number") return value;
4465
+ const parsed = Date.parse(value);
4466
+ return Number.isNaN(parsed) ? 0 : parsed;
4467
+ }
4468
+ function formatRemaining(ms) {
4469
+ const total = Math.max(0, Math.floor(ms / 1e3));
4470
+ const h = Math.floor(total / 3600);
4471
+ const m = Math.floor(total % 3600 / 60);
4472
+ const s = total % 60;
4473
+ const pad = (n) => String(n).padStart(2, "0");
4474
+ return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
4475
+ }
4476
+ var ImpersonationBanner = React52.forwardRef(
4477
+ ({ actorLabel, mode, expiresAt, onExit, targetLabel, ops, inline = false, onExpire, className }, ref) => {
4478
+ const endMs = toMs(expiresAt);
4479
+ const [now, setNow] = React52.useState(() => Date.now());
4480
+ const remaining = endMs - now;
4481
+ const expired = remaining <= 0;
4482
+ const onExpireRef = React52.useRef(onExpire);
4483
+ onExpireRef.current = onExpire;
4484
+ React52.useEffect(() => {
4485
+ if (expired) return void 0;
4486
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
4487
+ return () => clearInterval(timer);
4488
+ }, [expired]);
4489
+ const firedFor = React52.useRef(null);
4490
+ React52.useEffect(() => {
4491
+ if (expired && firedFor.current !== endMs) {
4492
+ firedFor.current = endMs;
4493
+ onExpireRef.current?.();
4494
+ }
4495
+ }, [expired, endMs]);
4496
+ const isRead = mode === "READ";
4497
+ const modeText = isRead ? "Read-only" : "Changes allowed";
4498
+ const opsText = ops && ops.length > 0 ? ops.join(", ") : null;
4499
+ return /* @__PURE__ */ jsxs41(Fragment3, { children: [
4500
+ inline ? null : /* @__PURE__ */ jsx61("div", { "aria-hidden": "true", className: "h-12", "data-impersonation-spacer": "" }),
4501
+ /* @__PURE__ */ jsxs41(
4502
+ "div",
4503
+ {
4504
+ ref,
4505
+ role: "alert",
4506
+ "data-impersonation-banner": "",
4507
+ "data-mode": mode,
4508
+ className: cn(
4509
+ inline ? "relative" : "fixed inset-x-0 top-0 z-[2147483000]",
4510
+ "flex h-12 items-center gap-3 border-b-2 px-4 text-sm font-medium shadow-md",
4511
+ isRead ? "border-black bg-[#FDE047] text-black" : "border-white bg-[#991B1B] text-white",
4512
+ className
4513
+ ),
4514
+ children: [
4515
+ /* @__PURE__ */ jsx61("span", { className: "shrink-0 rounded px-2 py-0.5 text-xs font-bold uppercase tracking-wide ring-1 ring-current", children: "Support session" }),
4516
+ /* @__PURE__ */ jsxs41("p", { className: "min-w-0 flex-1 truncate", children: [
4517
+ /* @__PURE__ */ jsx61("span", { className: "font-semibold", children: actorLabel }),
4518
+ targetLabel ? /* @__PURE__ */ jsxs41(Fragment3, { children: [
4519
+ " is acting as ",
4520
+ /* @__PURE__ */ jsx61("span", { className: "font-semibold", children: targetLabel })
4521
+ ] }) : " is acting as this user",
4522
+ " \xB7 ",
4523
+ /* @__PURE__ */ jsx61("span", { "data-testid": "impersonation-mode", children: modeText }),
4524
+ opsText ? /* @__PURE__ */ jsx61("span", { className: "hidden sm:inline", children: ` (${opsText})` }) : null
4525
+ ] }),
4526
+ /* @__PURE__ */ jsx61("span", { className: "shrink-0 tabular-nums", "aria-live": "off", title: "Time left in this support session", children: /* @__PURE__ */ jsx61("time", { "data-testid": "impersonation-countdown", dateTime: new Date(endMs).toISOString(), children: expired ? "Expired" : formatRemaining(remaining) }) }),
4527
+ /* @__PURE__ */ jsx61(
4528
+ "button",
4529
+ {
4530
+ type: "button",
4531
+ onClick: onExit,
4532
+ className: cn(
4533
+ "shrink-0 rounded-md px-3 py-1 text-sm font-semibold focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
4534
+ isRead ? "bg-black text-[#FDE047] hover:bg-black/85 focus-visible:ring-black focus-visible:ring-offset-[#FDE047]" : "bg-white text-[#991B1B] hover:bg-white/90 focus-visible:ring-white focus-visible:ring-offset-[#991B1B]"
4535
+ ),
4536
+ children: "Exit"
4537
+ }
4538
+ )
4539
+ ]
4540
+ }
4541
+ )
4542
+ ] });
4543
+ }
4544
+ );
4545
+ ImpersonationBanner.displayName = "ImpersonationBanner";
4546
+
4547
+ // src/components/read-only-guard.tsx
4548
+ import { Fragment as Fragment4, jsx as jsx62, jsxs as jsxs42 } from "react/jsx-runtime";
4549
+ var READ_ONLY_REASON = "Read-only support session: this action is turned off.";
4550
+ function isGuarded({ mode, ops, op = "WRITE" }) {
4551
+ if (ops) return !ops.includes(op);
4552
+ return mode === "READ";
4553
+ }
4554
+ function ReadOnlyGuard({ mode, ops, op = "WRITE", reason = READ_ONLY_REASON, className, children }) {
4555
+ if (!isGuarded({ mode, ops, op })) return /* @__PURE__ */ jsx62(Fragment4, { children });
4556
+ const swallow = (e) => {
4557
+ e.preventDefault();
4558
+ e.stopPropagation();
4559
+ };
4560
+ return /* @__PURE__ */ jsx62(TooltipProvider, { delayDuration: 150, children: /* @__PURE__ */ jsxs42(Tooltip, { children: [
4561
+ /* @__PURE__ */ jsx62(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx62(
4562
+ "span",
4563
+ {
4564
+ tabIndex: 0,
4565
+ "data-read-only": "",
4566
+ "aria-disabled": "true",
4567
+ className: cn("inline-flex cursor-not-allowed", className),
4568
+ onClickCapture: swallow,
4569
+ onSubmitCapture: swallow,
4570
+ children: /* @__PURE__ */ jsx62("fieldset", { disabled: true, className: "contents", children })
4571
+ }
4572
+ ) }),
4573
+ /* @__PURE__ */ jsx62(TooltipContent, { children: reason })
4574
+ ] }) });
4575
+ }
4576
+ ReadOnlyGuard.displayName = "ReadOnlyGuard";
4577
+
4578
+ // src/lib/impersonation.ts
4579
+ import * as React53 from "react";
4580
+ var OPERATION_CLASSES = ["READ", "WRITE", "DESTRUCTIVE", "FINANCIAL", "SECURITY"];
4581
+ var PLATFORM_BLOCKED = /* @__PURE__ */ new Set(["FINANCIAL", "SECURITY"]);
4582
+ var INACTIVE_IMPERSONATION = Object.freeze({
4583
+ active: false,
4584
+ mode: null,
4585
+ ops: [],
4586
+ actorSub: null,
4587
+ actorUserType: null,
4588
+ sessionId: null,
4589
+ expiresAt: null
4590
+ });
4591
+ function base64UrlDecode(segment) {
4592
+ const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
4593
+ const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
4594
+ const binary = atob(padded);
4595
+ const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
4596
+ return new TextDecoder().decode(bytes);
4597
+ }
4598
+ function decodeJwtPayload(token) {
4599
+ if (typeof token !== "string") return null;
4600
+ const parts = token.split(".");
4601
+ if (parts.length !== 3 || !parts[1]) return null;
4602
+ try {
4603
+ const payload = JSON.parse(base64UrlDecode(parts[1]));
4604
+ return payload !== null && typeof payload === "object" && !Array.isArray(payload) ? payload : null;
4605
+ } catch {
4606
+ return null;
4607
+ }
4608
+ }
4609
+ var nonEmpty = (v) => typeof v === "string" && v.trim() !== "" ? v : null;
4610
+ function approvedOpsOf(claims) {
4611
+ let ops;
4612
+ if (Array.isArray(claims.imp_ops)) ops = claims.imp_ops.filter((v) => typeof v === "string");
4613
+ else if (claims.imp_mode === "WRITE") ops = ["READ", "WRITE"];
4614
+ else ops = ["READ"];
4615
+ const set = new Set(ops.filter((op) => !PLATFORM_BLOCKED.has(op)));
4616
+ return OPERATION_CLASSES.filter((c) => set.has(c));
4617
+ }
4618
+ function modeOfOps(ops) {
4619
+ return ops.every((op) => op === "READ") ? "READ" : "WRITE";
4620
+ }
4621
+ function readImpersonation(token, now = Date.now()) {
4622
+ const claims = decodeJwtPayload(token);
4623
+ if (!claims) return INACTIVE_IMPERSONATION;
4624
+ const amr = Array.isArray(claims.amr) ? claims.amr : [];
4625
+ if (!amr.includes("impersonation")) return INACTIVE_IMPERSONATION;
4626
+ const act = claims.act !== null && typeof claims.act === "object" ? claims.act : {};
4627
+ const actorSub = nonEmpty(act.sub);
4628
+ const sessionId = nonEmpty(claims.imp_sid);
4629
+ const expiresAt = typeof claims.exp === "number" ? new Date(claims.exp * 1e3) : null;
4630
+ if (!actorSub || !sessionId || !expiresAt || expiresAt.getTime() <= now) return INACTIVE_IMPERSONATION;
4631
+ const ops = approvedOpsOf(claims);
4632
+ return {
4633
+ active: true,
4634
+ mode: modeOfOps(ops),
4635
+ ops,
4636
+ actorSub,
4637
+ actorUserType: nonEmpty(act.userType),
4638
+ sessionId,
4639
+ expiresAt
4640
+ };
4641
+ }
4642
+ function useImpersonation(token) {
4643
+ const [tick, setTick] = React53.useState(0);
4644
+ const state = React53.useMemo(() => readImpersonation(token), [token, tick]);
4645
+ const expiresAtMs = state.expiresAt?.getTime() ?? null;
4646
+ React53.useEffect(() => {
4647
+ if (expiresAtMs === null) return void 0;
4648
+ const delay = Math.max(0, expiresAtMs - Date.now());
4649
+ const timer = setTimeout(() => setTick((t) => t + 1), Math.min(delay + 50, 2147e6));
4650
+ return () => clearTimeout(timer);
4651
+ }, [expiresAtMs]);
4652
+ return state;
4653
+ }
4454
4654
  export {
4455
4655
  Accordion,
4456
4656
  AccordionContent,
@@ -4595,7 +4795,9 @@ export {
4595
4795
  HoverCard,
4596
4796
  HoverCardContent,
4597
4797
  HoverCardTrigger,
4798
+ INACTIVE_IMPERSONATION,
4598
4799
  IconInfoLineIcon,
4800
+ ImpersonationBanner,
4599
4801
  Input,
4600
4802
  InputAddon,
4601
4803
  InputGroup,
@@ -4637,6 +4839,7 @@ export {
4637
4839
  NavigationMenuList,
4638
4840
  NavigationMenuTrigger,
4639
4841
  NavigationMenuViewport,
4842
+ OPERATION_CLASSES,
4640
4843
  Pagination,
4641
4844
  PaginationContent,
4642
4845
  PaginationEllipsis,
@@ -4649,8 +4852,10 @@ export {
4649
4852
  PopoverContent,
4650
4853
  PopoverTrigger,
4651
4854
  Progress,
4855
+ READ_ONLY_REASON,
4652
4856
  RadioGroup,
4653
4857
  RadioGroupItem,
4858
+ ReadOnlyGuard,
4654
4859
  ResizableHandle,
4655
4860
  ResizablePanel,
4656
4861
  ResizablePanelGroup,
@@ -4721,17 +4926,24 @@ export {
4721
4926
  TooltipProvider,
4722
4927
  TooltipTrigger,
4723
4928
  alertVariants,
4929
+ approvedOpsOf,
4724
4930
  buttonVariants,
4725
4931
  checkboxVariants,
4726
4932
  cn,
4933
+ decodeJwtPayload,
4727
4934
  emptyStateVariants,
4935
+ formatRemaining,
4728
4936
  inputVariants,
4937
+ isGuarded,
4938
+ modeOfOps,
4729
4939
  navigationMenuTriggerStyle,
4940
+ readImpersonation,
4730
4941
  spinnerVariants,
4731
4942
  switchVariants,
4732
4943
  toast,
4733
4944
  toggleVariants,
4734
4945
  useFormField,
4946
+ useImpersonation,
4735
4947
  useSidebar,
4736
4948
  useTheme2 as useTheme
4737
4949
  };