@fanvue/ui 1.5.0 → 1.7.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.
@@ -5,7 +5,11 @@ const jsxRuntime = require("react/jsx-runtime");
5
5
  const React = require("react");
6
6
  const cn = require("../../utils/cn.cjs");
7
7
  const Button = require("../Button/Button.cjs");
8
+ const CheckCircleIcon = require("../Icons/CheckCircleIcon.cjs");
8
9
  const CrossIcon = require("../Icons/CrossIcon.cjs");
10
+ const ErrorCircleIcon = require("../Icons/ErrorCircleIcon.cjs");
11
+ const InfoCircleIcon = require("../Icons/InfoCircleIcon.cjs");
12
+ const WarningTriangleIcon = require("../Icons/WarningTriangleIcon.cjs");
9
13
  function _interopNamespaceDefault(e) {
10
14
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
11
15
  if (e) {
@@ -23,6 +27,12 @@ function _interopNamespaceDefault(e) {
23
27
  return Object.freeze(n);
24
28
  }
25
29
  const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
30
+ const DEFAULT_ICONS = {
31
+ info: /* @__PURE__ */ jsxRuntime.jsx(InfoCircleIcon.InfoCircleIcon, {}),
32
+ success: /* @__PURE__ */ jsxRuntime.jsx(CheckCircleIcon.CheckCircleIcon, {}),
33
+ warning: /* @__PURE__ */ jsxRuntime.jsx(WarningTriangleIcon.WarningTriangleIcon, {}),
34
+ error: /* @__PURE__ */ jsxRuntime.jsx(ErrorCircleIcon.ErrorCircleIcon, {})
35
+ };
26
36
  const CLOSE_BUTTON_CLASSES = {
27
37
  info: "hover:bg-info-500/10 active:bg-info-500/10 text-info-500 motion-safe:transition-colors motion-safe:duration-150",
28
38
  success: "hover:bg-success-500/10 active:bg-success-500/10 text-success-500 motion-safe:transition-colors motion-safe:duration-150",
@@ -41,6 +51,7 @@ const Alert = React__namespace.forwardRef(
41
51
  children,
42
52
  ...props
43
53
  }, ref) => {
54
+ const resolvedIcon = icon === null ? null : icon ?? DEFAULT_ICONS[variant];
44
55
  return /* @__PURE__ */ jsxRuntime.jsxs(
45
56
  "div",
46
57
  {
@@ -49,10 +60,10 @@ const Alert = React__namespace.forwardRef(
49
60
  "data-testid": "alert",
50
61
  className: cn.cn(
51
62
  "grid gap-x-3 rounded-lg p-4 text-sm leading-[18px]",
52
- icon && closable && "grid-cols-[auto_1fr_auto]",
53
- icon && !closable && "grid-cols-[auto_1fr]",
54
- !icon && closable && "grid-cols-[1fr_auto]",
55
- !icon && !closable && "grid-cols-[1fr]",
63
+ resolvedIcon && closable && "grid-cols-[auto_1fr_auto]",
64
+ resolvedIcon && !closable && "grid-cols-[auto_1fr]",
65
+ !resolvedIcon && closable && "grid-cols-[1fr_auto]",
66
+ !resolvedIcon && !closable && "grid-cols-[1fr]",
56
67
  title && children ? "items-start" : "items-center",
57
68
  variant === "info" && "bg-info-50 text-info-500",
58
69
  variant === "success" && "bg-success-50 text-success-500",
@@ -62,7 +73,7 @@ const Alert = React__namespace.forwardRef(
62
73
  ),
63
74
  ...props,
64
75
  children: [
65
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-start", "aria-hidden": "true", children: icon }),
76
+ resolvedIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-start", "aria-hidden": "true", children: resolvedIcon }),
66
77
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [
67
78
  title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "typography-body-2-semibold text-body-100", children: title }),
68
79
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "typography-body-2-regular text-body-200", children })
@@ -1 +1 @@
1
- {"version":3,"file":"Alert.cjs","sources":["../../../../src/components/Alert/Alert.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../Button/Button\";\nimport { CrossIcon } from \"../Icons/CrossIcon\";\n\n/** Visual style variant of the alert. */\nexport type AlertVariant = \"info\" | \"success\" | \"warning\" | \"error\";\n\nexport interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Visual style variant of the alert. @default \"info\" */\n variant?: AlertVariant;\n /** Optional title text displayed in bold above the description. */\n title?: string;\n /** Icon element displayed at the leading edge of the alert. */\n icon?: React.ReactNode;\n /** Whether to show the close button. @default false */\n closable?: boolean;\n /** Callback fired when the close button is clicked. */\n onClose?: () => void;\n /** Accessible label for the close button. @default \"Close alert\" */\n closeLabel?: string;\n}\n\nconst CLOSE_BUTTON_CLASSES: Record<AlertVariant, string> = {\n info: \"hover:bg-info-500/10 active:bg-info-500/10 text-info-500 motion-safe:transition-colors motion-safe:duration-150\",\n success:\n \"hover:bg-success-500/10 active:bg-success-500/10 text-success-500 motion-safe:transition-colors motion-safe:duration-150\",\n warning:\n \"hover:bg-warning-500/10 active:bg-warning-500/10 text-warning-500 motion-safe:transition-colors motion-safe:duration-150\",\n error:\n \"hover:bg-error-500/10 active:bg-error-500/10 text-error-500 motion-safe:transition-colors motion-safe:duration-150\",\n};\n\n/**\n * Displays a contextual feedback message to the user.\n *\n * Supports `info`, `success`, `warning`, and `error` variants with an optional\n * icon, title, description, and dismiss button.\n *\n * @example\n * ```tsx\n * <Alert variant=\"success\" title=\"Saved\" closable onClose={handleClose}>\n * Your changes have been saved.\n * </Alert>\n * ```\n */\nexport const Alert = React.forwardRef<HTMLDivElement, AlertProps>(\n (\n {\n className,\n variant = \"info\",\n title,\n icon,\n closable = false,\n onClose,\n closeLabel = \"Close alert\",\n children,\n ...props\n },\n ref,\n ) => {\n return (\n <div\n ref={ref}\n role=\"alert\"\n data-testid=\"alert\"\n className={cn(\n \"grid gap-x-3 rounded-lg p-4 text-sm leading-[18px]\",\n icon && closable && \"grid-cols-[auto_1fr_auto]\",\n icon && !closable && \"grid-cols-[auto_1fr]\",\n !icon && closable && \"grid-cols-[1fr_auto]\",\n !icon && !closable && \"grid-cols-[1fr]\",\n title && children ? \"items-start\" : \"items-center\",\n variant === \"info\" && \"bg-info-50 text-info-500\",\n variant === \"success\" && \"bg-success-50 text-success-500\",\n variant === \"warning\" && \"bg-warning-50 text-warning-500\",\n variant === \"error\" && \"bg-error-50 text-error-500\",\n className,\n )}\n {...props}\n >\n {icon && (\n <span className=\"flex shrink-0 items-start\" aria-hidden=\"true\">\n {icon}\n </span>\n )}\n\n <div className=\"flex min-w-0 flex-col gap-2\">\n {title && <div className=\"typography-body-2-semibold text-body-100\">{title}</div>}\n <div className=\"typography-body-2-regular text-body-200\">{children}</div>\n </div>\n\n {closable && (\n <Button\n variant=\"tertiary\"\n size=\"24\"\n onClick={onClose}\n className={cn(\"self-start px-0\", CLOSE_BUTTON_CLASSES[variant])}\n aria-label={closeLabel}\n >\n <CrossIcon />\n </Button>\n )}\n </div>\n );\n },\n);\n\nAlert.displayName = \"Alert\";\n"],"names":["React","jsxs","cn","jsx","Button","CrossIcon"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAM,uBAAqD;AAAA,EACzD,MAAM;AAAA,EACN,SACE;AAAA,EACF,SACE;AAAA,EACF,OACE;AACJ;AAeO,MAAM,QAAQA,iBAAM;AAAA,EACzB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA,UACT;AAAA,UACA,QAAQ,YAAY;AAAA,UACpB,QAAQ,CAAC,YAAY;AAAA,UACrB,CAAC,QAAQ,YAAY;AAAA,UACrB,CAAC,QAAQ,CAAC,YAAY;AAAA,UACtB,SAAS,WAAW,gBAAgB;AAAA,UACpC,YAAY,UAAU;AAAA,UACtB,YAAY,aAAa;AAAA,UACzB,YAAY,aAAa;AAAA,UACzB,YAAY,WAAW;AAAA,UACvB;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,uCACE,QAAA,EAAK,WAAU,6BAA4B,eAAY,QACrD,UAAA,MACH;AAAA,UAGFD,2BAAAA,KAAC,OAAA,EAAI,WAAU,+BACZ,UAAA;AAAA,YAAA,SAASE,2BAAAA,IAAC,OAAA,EAAI,WAAU,4CAA4C,UAAA,OAAM;AAAA,YAC3EA,2BAAAA,IAAC,OAAA,EAAI,WAAU,2CAA2C,SAAA,CAAS;AAAA,UAAA,GACrE;AAAA,UAEC,YACCA,2BAAAA;AAAAA,YAACC,OAAAA;AAAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAWF,GAAAA,GAAG,mBAAmB,qBAAqB,OAAO,CAAC;AAAA,cAC9D,cAAY;AAAA,cAEZ,yCAACG,UAAAA,WAAA,CAAA,CAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACb;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,MAAM,cAAc;;"}
1
+ {"version":3,"file":"Alert.cjs","sources":["../../../../src/components/Alert/Alert.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../Button/Button\";\nimport { CheckCircleIcon } from \"../Icons/CheckCircleIcon\";\nimport { CrossIcon } from \"../Icons/CrossIcon\";\nimport { ErrorCircleIcon } from \"../Icons/ErrorCircleIcon\";\nimport { InfoCircleIcon } from \"../Icons/InfoCircleIcon\";\nimport { WarningTriangleIcon } from \"../Icons/WarningTriangleIcon\";\n\n/** Visual style variant of the alert. */\nexport type AlertVariant = \"info\" | \"success\" | \"warning\" | \"error\";\n\nconst DEFAULT_ICONS: Record<AlertVariant, React.ReactNode> = {\n info: <InfoCircleIcon />,\n success: <CheckCircleIcon />,\n warning: <WarningTriangleIcon />,\n error: <ErrorCircleIcon />,\n};\n\nexport interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Visual style variant of the alert. @default \"info\" */\n variant?: AlertVariant;\n /** Optional title text displayed in bold above the description. */\n title?: string;\n /** Custom icon override. Pass `null` to hide the icon entirely. Each variant shows a default icon when left `undefined`. */\n icon?: React.ReactNode | null;\n /** Whether to show the close button. @default false */\n closable?: boolean;\n /** Callback fired when the close button is clicked. */\n onClose?: () => void;\n /** Accessible label for the close button. @default \"Close alert\" */\n closeLabel?: string;\n}\n\nconst CLOSE_BUTTON_CLASSES: Record<AlertVariant, string> = {\n info: \"hover:bg-info-500/10 active:bg-info-500/10 text-info-500 motion-safe:transition-colors motion-safe:duration-150\",\n success:\n \"hover:bg-success-500/10 active:bg-success-500/10 text-success-500 motion-safe:transition-colors motion-safe:duration-150\",\n warning:\n \"hover:bg-warning-500/10 active:bg-warning-500/10 text-warning-500 motion-safe:transition-colors motion-safe:duration-150\",\n error:\n \"hover:bg-error-500/10 active:bg-error-500/10 text-error-500 motion-safe:transition-colors motion-safe:duration-150\",\n};\n\n/**\n * Displays a contextual feedback message to the user.\n *\n * Supports `info`, `success`, `warning`, and `error` variants with a default\n * icon per variant, optional title, description, and dismiss button.\n *\n * Each variant renders a default icon automatically. Pass a custom `icon` to\n * override, or `icon={null}` to hide the icon entirely.\n *\n * @example\n * ```tsx\n * <Alert variant=\"success\" title=\"Saved\" closable onClose={handleClose}>\n * Your changes have been saved.\n * </Alert>\n * ```\n */\nexport const Alert = React.forwardRef<HTMLDivElement, AlertProps>(\n (\n {\n className,\n variant = \"info\",\n title,\n icon,\n closable = false,\n onClose,\n closeLabel = \"Close alert\",\n children,\n ...props\n },\n ref,\n ) => {\n const resolvedIcon = icon === null ? null : (icon ?? DEFAULT_ICONS[variant]);\n\n return (\n <div\n ref={ref}\n role=\"alert\"\n data-testid=\"alert\"\n className={cn(\n \"grid gap-x-3 rounded-lg p-4 text-sm leading-[18px]\",\n resolvedIcon && closable && \"grid-cols-[auto_1fr_auto]\",\n resolvedIcon && !closable && \"grid-cols-[auto_1fr]\",\n !resolvedIcon && closable && \"grid-cols-[1fr_auto]\",\n !resolvedIcon && !closable && \"grid-cols-[1fr]\",\n title && children ? \"items-start\" : \"items-center\",\n variant === \"info\" && \"bg-info-50 text-info-500\",\n variant === \"success\" && \"bg-success-50 text-success-500\",\n variant === \"warning\" && \"bg-warning-50 text-warning-500\",\n variant === \"error\" && \"bg-error-50 text-error-500\",\n className,\n )}\n {...props}\n >\n {resolvedIcon && (\n <span className=\"flex shrink-0 items-start\" aria-hidden=\"true\">\n {resolvedIcon}\n </span>\n )}\n\n <div className=\"flex min-w-0 flex-col gap-2\">\n {title && <div className=\"typography-body-2-semibold text-body-100\">{title}</div>}\n <div className=\"typography-body-2-regular text-body-200\">{children}</div>\n </div>\n\n {closable && (\n <Button\n variant=\"tertiary\"\n size=\"24\"\n onClick={onClose}\n className={cn(\"self-start px-0\", CLOSE_BUTTON_CLASSES[variant])}\n aria-label={closeLabel}\n >\n <CrossIcon />\n </Button>\n )}\n </div>\n );\n },\n);\n\nAlert.displayName = \"Alert\";\n"],"names":["InfoCircleIcon","CheckCircleIcon","WarningTriangleIcon","ErrorCircleIcon","React","jsxs","cn","jsx","Button","CrossIcon"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,MAAM,gBAAuD;AAAA,EAC3D,qCAAOA,eAAAA,gBAAA,EAAe;AAAA,EACtB,wCAAUC,gBAAAA,iBAAA,EAAgB;AAAA,EAC1B,wCAAUC,oBAAAA,qBAAA,EAAoB;AAAA,EAC9B,sCAAQC,gBAAAA,iBAAA,CAAA,CAAgB;AAC1B;AAiBA,MAAM,uBAAqD;AAAA,EACzD,MAAM;AAAA,EACN,SACE;AAAA,EACF,SACE;AAAA,EACF,OACE;AACJ;AAkBO,MAAM,QAAQC,iBAAM;AAAA,EACzB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,eAAe,SAAS,OAAO,OAAQ,QAAQ,cAAc,OAAO;AAE1E,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA,UACT;AAAA,UACA,gBAAgB,YAAY;AAAA,UAC5B,gBAAgB,CAAC,YAAY;AAAA,UAC7B,CAAC,gBAAgB,YAAY;AAAA,UAC7B,CAAC,gBAAgB,CAAC,YAAY;AAAA,UAC9B,SAAS,WAAW,gBAAgB;AAAA,UACpC,YAAY,UAAU;AAAA,UACtB,YAAY,aAAa;AAAA,UACzB,YAAY,aAAa;AAAA,UACzB,YAAY,WAAW;AAAA,UACvB;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,+CACE,QAAA,EAAK,WAAU,6BAA4B,eAAY,QACrD,UAAA,cACH;AAAA,UAGFD,2BAAAA,KAAC,OAAA,EAAI,WAAU,+BACZ,UAAA;AAAA,YAAA,SAASE,2BAAAA,IAAC,OAAA,EAAI,WAAU,4CAA4C,UAAA,OAAM;AAAA,YAC3EA,2BAAAA,IAAC,OAAA,EAAI,WAAU,2CAA2C,SAAA,CAAS;AAAA,UAAA,GACrE;AAAA,UAEC,YACCA,2BAAAA;AAAAA,YAACC,OAAAA;AAAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAWF,GAAAA,GAAG,mBAAmB,qBAAqB,OAAO,CAAC;AAAA,cAC9D,cAAY;AAAA,cAEZ,yCAACG,UAAAA,WAAA,CAAA,CAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACb;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,MAAM,cAAc;;"}
@@ -112,7 +112,7 @@ const Button = React__namespace.forwardRef(
112
112
  ({
113
113
  className,
114
114
  variant = "primary",
115
- size = "48",
115
+ size = "40",
116
116
  leftIcon,
117
117
  rightIcon,
118
118
  loading = false,
@@ -148,7 +148,7 @@ const Button = React__namespace.forwardRef(
148
148
  ...loadingLabelProps,
149
149
  className: cn.cn(
150
150
  // Base styles
151
- "inline-flex cursor-pointer items-center justify-center gap-2 rounded-full transition-colors",
151
+ "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-full transition-colors",
152
152
  // Focus ring
153
153
  "focus-visible:shadow-focus-ring focus-visible:outline-none",
154
154
  // Disabled state
@@ -1 +1 @@
1
- {"version":3,"file":"Button.cjs","sources":["../../../../src/components/Button/Button.tsx"],"sourcesContent":["import { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { SpinnerIcon } from \"../Icons/SpinnerIcon\";\n\n/** Visual style variant of the button. */\nexport type ButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"tertiary\"\n | \"link\"\n | \"brand\"\n | \"destructive\"\n | \"white\"\n | \"tertiaryDestructive\"\n | \"text\";\n\n/** Button height in pixels. */\nexport type ButtonSize = \"48\" | \"40\" | \"32\" | \"24\";\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Visual style variant of the button. @default \"primary\" */\n variant?: ButtonVariant;\n /** Height of the button in pixels. @default \"48\" */\n size?: ButtonSize;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** When `true`, replaces the label with a spinner and disables interaction. @default false */\n loading?: boolean;\n /** Merge props onto a child element instead of rendering a `<button>`. @default false */\n asChild?: boolean;\n /** Old price shown with a strikethrough before the current price. */\n discount?: string;\n /** Current price shown inside the button after the label and icons. */\n price?: string;\n}\n\nconst SIZE_CLASSES: Record<ButtonSize, string> = {\n \"48\": \"h-12 px-4 py-3 typography-button-large\",\n \"40\": \"h-10 px-4 py-2 typography-button-small\",\n \"32\": \"h-8 px-3 py-2 typography-body-2-semibold\",\n \"24\": \"h-6 px-2 py-1 typography-body-2-semibold\",\n};\n\nconst ICON_SIZE_CLASS: Record<ButtonSize, string> = {\n \"48\": \"size-5\",\n \"40\": \"size-5\",\n \"32\": \"size-4\",\n \"24\": \"size-3.5\",\n};\n\nconst VARIANT_CLASSES: Record<ButtonVariant, string> = {\n primary:\n \"bg-neutral-400 text-body-300 hover:bg-brand-green-500 hover:text-body-black-solid-constant active:bg-brand-green-500 active:text-body-black-solid-constant\",\n secondary:\n \"border-body-100 border border-1 border-body-100 bg-transparent text-body-100 hover:bg-brand-green-50 active:bg-brand-green-50\",\n tertiary: \"bg-transparent text-body-100 hover:bg-brand-green-50 active:bg-brand-green-50\",\n link: \"bg-transparent text-body-100 underline decoration-solid hover:bg-brand-green-50 active:bg-brand-green-50\",\n brand:\n \"bg-brand-green-500 text-body-black-solid-constant hover:bg-brand-pink-500 active:bg-brand-pink-500\",\n destructive:\n \"bg-error-500 text-body-white-solid-constant hover:bg-background-solid dark:hover:bg-background-white-solid-constant dark:hover:text-error-500 active:bg-background-solid dark:active:bg-background-white-solid-constant dark:active:text-error-500\",\n white:\n \"bg-background-white-solid-constant text-body-black-solid-constant hover:bg-brand-green-500 active:bg-brand-green-500\",\n tertiaryDestructive: \"bg-transparent text-error-500 hover:bg-error-50 active:bg-error-50\",\n text: \"bg-transparent text-body-100 hover:underline active:underline\",\n};\n\n/** Recursively extract text content from React nodes for accessible labels */\nfunction getTextContent(node: React.ReactNode): string | undefined {\n if (typeof node === \"string\") return node;\n if (typeof node === \"number\") return String(node);\n if (React.isValidElement(node)) {\n return getTextContent((node.props as { children?: React.ReactNode }).children);\n }\n if (Array.isArray(node)) {\n const text = node.map(getTextContent).filter(Boolean).join(\"\");\n return text || undefined;\n }\n return undefined;\n}\n\nconst LoadingSpinner = ({ size }: { size: ButtonSize }) => {\n return (\n <span className=\"animate-spin\" aria-hidden=\"true\">\n <SpinnerIcon className={ICON_SIZE_CLASS[size]}>\n <title>Loading</title>\n </SpinnerIcon>\n </span>\n );\n};\n\nfunction renderContent({\n loading,\n asChild,\n children,\n size,\n leftIcon,\n rightIcon,\n iconSizeClass,\n discount,\n price,\n}: {\n loading: boolean;\n asChild: boolean;\n children: React.ReactNode;\n size: ButtonSize;\n leftIcon: React.ReactNode;\n rightIcon: React.ReactNode;\n iconSizeClass: string;\n discount?: string;\n price?: string;\n}) {\n if (loading) {\n // When asChild, clone the child element with spinner content instead of\n // wrapping in sr-only span (which would nest interactive elements)\n if (asChild && React.isValidElement(children)) {\n return React.cloneElement(\n children as React.ReactElement<{ children?: React.ReactNode }>,\n undefined,\n <LoadingSpinner size={size} />,\n );\n }\n return (\n <>\n <LoadingSpinner size={size} />\n <span className=\"sr-only\">{children}</span>\n </>\n );\n }\n\n if (asChild) return children;\n\n return (\n <>\n {leftIcon && (\n <span\n className={cn(\"flex shrink-0 items-center justify-center\", iconSizeClass)}\n aria-hidden=\"true\"\n >\n {leftIcon}\n </span>\n )}\n {children}\n {rightIcon && (\n <span\n className={cn(\"flex shrink-0 items-center justify-center\", iconSizeClass)}\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n {discount != null && (\n <span className=\"typography-body-1-regular line-through\" aria-hidden=\"true\">\n {discount}\n </span>\n )}\n {price != null && <span aria-hidden=\"true\">{price}</span>}\n </>\n );\n}\n\n/**\n * A versatile button component with multiple visual variants, sizes, icon\n * slots, loading state, and optional pricing display.\n *\n * @example\n * ```tsx\n * <Button variant=\"brand\" size=\"40\" leftIcon={<StarIcon />}>\n * Subscribe\n * </Button>\n * ```\n */\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant = \"primary\",\n size = \"48\",\n leftIcon,\n rightIcon,\n loading = false,\n asChild = false,\n disabled,\n children,\n discount,\n price,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"button\";\n const isDisabled = disabled || loading;\n const iconSizeClass = ICON_SIZE_CLASS[size];\n\n const buttonSpecificProps = !asChild\n ? { type: \"button\" as const, \"data-testid\": \"button\", disabled: isDisabled }\n : isDisabled\n ? { \"aria-disabled\": true }\n : {};\n\n // When asChild + loading, extract text from children for aria-label since we\n // can't wrap element children in an sr-only span (creates invalid nested markup)\n const loadingLabelProps = loading && asChild ? { \"aria-label\": getTextContent(children) } : {};\n\n const content = renderContent({\n loading,\n asChild,\n children,\n size,\n leftIcon,\n rightIcon,\n iconSizeClass,\n discount,\n price,\n });\n\n return (\n <Comp\n ref={ref}\n {...buttonSpecificProps}\n aria-busy={loading}\n {...loadingLabelProps}\n className={cn(\n // Base styles\n \"inline-flex cursor-pointer items-center justify-center gap-2 rounded-full transition-colors\",\n // Focus ring\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled state\n \"disabled:pointer-events-none disabled:opacity-50\",\n \"aria-disabled:pointer-events-none aria-disabled:opacity-50\",\n // Size styles\n SIZE_CLASSES[size],\n // Variant styles\n VARIANT_CLASSES[variant],\n // Manual CSS overrides\n className,\n )}\n {...props}\n >\n {content}\n </Comp>\n );\n },\n);\n\nButton.displayName = \"Button\";\n"],"names":["React","jsx","SpinnerIcon","jsxs","Fragment","cn","Slot"],"mappings":"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"}
1
+ {"version":3,"file":"Button.cjs","sources":["../../../../src/components/Button/Button.tsx"],"sourcesContent":["import { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { SpinnerIcon } from \"../Icons/SpinnerIcon\";\n\n/** Visual style variant of the button. */\nexport type ButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"tertiary\"\n | \"link\"\n | \"brand\"\n | \"destructive\"\n | \"white\"\n | \"tertiaryDestructive\"\n | \"text\";\n\n/** Button height in pixels. */\nexport type ButtonSize = \"48\" | \"40\" | \"32\" | \"24\";\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Visual style variant of the button. @default \"primary\" */\n variant?: ButtonVariant;\n /** Height of the button in pixels. @default \"40\" */\n size?: ButtonSize;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** When `true`, replaces the label with a spinner and disables interaction. @default false */\n loading?: boolean;\n /** Merge props onto a child element instead of rendering a `<button>`. @default false */\n asChild?: boolean;\n /** Old price shown with a strikethrough before the current price. */\n discount?: string;\n /** Current price shown inside the button after the label and icons. */\n price?: string;\n}\n\nconst SIZE_CLASSES: Record<ButtonSize, string> = {\n \"48\": \"h-12 px-4 py-3 typography-button-large\",\n \"40\": \"h-10 px-4 py-2 typography-button-small\",\n \"32\": \"h-8 px-3 py-2 typography-body-2-semibold\",\n \"24\": \"h-6 px-2 py-1 typography-body-2-semibold\",\n};\n\nconst ICON_SIZE_CLASS: Record<ButtonSize, string> = {\n \"48\": \"size-5\",\n \"40\": \"size-5\",\n \"32\": \"size-4\",\n \"24\": \"size-3.5\",\n};\n\nconst VARIANT_CLASSES: Record<ButtonVariant, string> = {\n primary:\n \"bg-neutral-400 text-body-300 hover:bg-brand-green-500 hover:text-body-black-solid-constant active:bg-brand-green-500 active:text-body-black-solid-constant\",\n secondary:\n \"border-body-100 border border-1 border-body-100 bg-transparent text-body-100 hover:bg-brand-green-50 active:bg-brand-green-50\",\n tertiary: \"bg-transparent text-body-100 hover:bg-brand-green-50 active:bg-brand-green-50\",\n link: \"bg-transparent text-body-100 underline decoration-solid hover:bg-brand-green-50 active:bg-brand-green-50\",\n brand:\n \"bg-brand-green-500 text-body-black-solid-constant hover:bg-brand-pink-500 active:bg-brand-pink-500\",\n destructive:\n \"bg-error-500 text-body-white-solid-constant hover:bg-background-solid dark:hover:bg-background-white-solid-constant dark:hover:text-error-500 active:bg-background-solid dark:active:bg-background-white-solid-constant dark:active:text-error-500\",\n white:\n \"bg-background-white-solid-constant text-body-black-solid-constant hover:bg-brand-green-500 active:bg-brand-green-500\",\n tertiaryDestructive: \"bg-transparent text-error-500 hover:bg-error-50 active:bg-error-50\",\n text: \"bg-transparent text-body-100 hover:underline active:underline\",\n};\n\n/** Recursively extract text content from React nodes for accessible labels */\nfunction getTextContent(node: React.ReactNode): string | undefined {\n if (typeof node === \"string\") return node;\n if (typeof node === \"number\") return String(node);\n if (React.isValidElement(node)) {\n return getTextContent((node.props as { children?: React.ReactNode }).children);\n }\n if (Array.isArray(node)) {\n const text = node.map(getTextContent).filter(Boolean).join(\"\");\n return text || undefined;\n }\n return undefined;\n}\n\nconst LoadingSpinner = ({ size }: { size: ButtonSize }) => {\n return (\n <span className=\"animate-spin\" aria-hidden=\"true\">\n <SpinnerIcon className={ICON_SIZE_CLASS[size]}>\n <title>Loading</title>\n </SpinnerIcon>\n </span>\n );\n};\n\nfunction renderContent({\n loading,\n asChild,\n children,\n size,\n leftIcon,\n rightIcon,\n iconSizeClass,\n discount,\n price,\n}: {\n loading: boolean;\n asChild: boolean;\n children: React.ReactNode;\n size: ButtonSize;\n leftIcon: React.ReactNode;\n rightIcon: React.ReactNode;\n iconSizeClass: string;\n discount?: string;\n price?: string;\n}) {\n if (loading) {\n // When asChild, clone the child element with spinner content instead of\n // wrapping in sr-only span (which would nest interactive elements)\n if (asChild && React.isValidElement(children)) {\n return React.cloneElement(\n children as React.ReactElement<{ children?: React.ReactNode }>,\n undefined,\n <LoadingSpinner size={size} />,\n );\n }\n return (\n <>\n <LoadingSpinner size={size} />\n <span className=\"sr-only\">{children}</span>\n </>\n );\n }\n\n if (asChild) return children;\n\n return (\n <>\n {leftIcon && (\n <span\n className={cn(\"flex shrink-0 items-center justify-center\", iconSizeClass)}\n aria-hidden=\"true\"\n >\n {leftIcon}\n </span>\n )}\n {children}\n {rightIcon && (\n <span\n className={cn(\"flex shrink-0 items-center justify-center\", iconSizeClass)}\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n {discount != null && (\n <span className=\"typography-body-1-regular line-through\" aria-hidden=\"true\">\n {discount}\n </span>\n )}\n {price != null && <span aria-hidden=\"true\">{price}</span>}\n </>\n );\n}\n\n/**\n * A versatile button component with multiple visual variants, sizes, icon\n * slots, loading state, and optional pricing display.\n *\n * @example\n * ```tsx\n * <Button variant=\"brand\" size=\"40\" leftIcon={<StarIcon />}>\n * Subscribe\n * </Button>\n * ```\n */\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant = \"primary\",\n size = \"40\",\n leftIcon,\n rightIcon,\n loading = false,\n asChild = false,\n disabled,\n children,\n discount,\n price,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"button\";\n const isDisabled = disabled || loading;\n const iconSizeClass = ICON_SIZE_CLASS[size];\n\n const buttonSpecificProps = !asChild\n ? { type: \"button\" as const, \"data-testid\": \"button\", disabled: isDisabled }\n : isDisabled\n ? { \"aria-disabled\": true }\n : {};\n\n // When asChild + loading, extract text from children for aria-label since we\n // can't wrap element children in an sr-only span (creates invalid nested markup)\n const loadingLabelProps = loading && asChild ? { \"aria-label\": getTextContent(children) } : {};\n\n const content = renderContent({\n loading,\n asChild,\n children,\n size,\n leftIcon,\n rightIcon,\n iconSizeClass,\n discount,\n price,\n });\n\n return (\n <Comp\n ref={ref}\n {...buttonSpecificProps}\n aria-busy={loading}\n {...loadingLabelProps}\n className={cn(\n // Base styles\n \"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-full transition-colors\",\n // Focus ring\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled state\n \"disabled:pointer-events-none disabled:opacity-50\",\n \"aria-disabled:pointer-events-none aria-disabled:opacity-50\",\n // Size styles\n SIZE_CLASSES[size],\n // Variant styles\n VARIANT_CLASSES[variant],\n // Manual CSS overrides\n className,\n )}\n {...props}\n >\n {content}\n </Comp>\n );\n },\n);\n\nButton.displayName = \"Button\";\n"],"names":["React","jsx","SpinnerIcon","jsxs","Fragment","cn","Slot"],"mappings":"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"}
@@ -32,7 +32,7 @@ const TabsTrigger = React__namespace.forwardRef(({ className, ...props }, ref) =
32
32
  "rounded-xs border-transparent",
33
33
  "typography-body-1-semibold cursor-pointer text-body-100",
34
34
  "motion-safe:transition-[color,border-color] motion-safe:duration-150 motion-safe:ease-in-out",
35
- "data-[orientation=horizontal]:border-b-4 data-[orientation=horizontal]:px-3 data-[orientation=horizontal]:pb-4",
35
+ "data-[orientation=horizontal]:border-b-4 data-[orientation=horizontal]:px-4 data-[orientation=horizontal]:py-3",
36
36
  "data-[orientation=vertical]:justify-start data-[orientation=vertical]:border-r-4 data-[orientation=vertical]:px-4 data-[orientation=vertical]:py-3",
37
37
  "data-[state=active]:border-brand-green-500",
38
38
  "data-[state=active]:hover:text-hover-100",
@@ -1 +1 @@
1
- {"version":3,"file":"TabsTrigger.cjs","sources":["../../../../src/components/Tabs/TabsTrigger.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Props for the {@link TabsTrigger} button component. */\nexport type TabsTriggerProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>;\n\n/** An interactive tab button that activates its associated {@link TabsContent} panel when clicked. */\nexport const TabsTrigger = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex items-center justify-center\",\n \"rounded-xs border-transparent\",\n \"typography-body-1-semibold cursor-pointer text-body-100\",\n \"motion-safe:transition-[color,border-color] motion-safe:duration-150 motion-safe:ease-in-out\",\n \"data-[orientation=horizontal]:border-b-4 data-[orientation=horizontal]:px-3 data-[orientation=horizontal]:pb-4\",\n \"data-[orientation=vertical]:justify-start data-[orientation=vertical]:border-r-4 data-[orientation=vertical]:px-4 data-[orientation=vertical]:py-3\",\n \"data-[state=active]:border-brand-green-500\",\n \"data-[state=active]:hover:text-hover-100\",\n \"data-[state=inactive]:hover:text-hover-200\",\n \"data-[state=active]:active:text-hover-100\",\n \"data-[state=inactive]:active:text-hover-200\",\n \"data-disabled:pointer-events-none\",\n \"data-disabled:data-[state=active]:text-disabled-100\",\n \"data-disabled:data-[state=inactive]:text-disabled-400\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-purple-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background-inverse-solid\",\n className,\n )}\n {...props}\n />\n));\n\nTabsTrigger.displayName = \"TabsTrigger\";\n"],"names":["React","jsx","TabsPrimitive","cn"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAQO,MAAM,cAAcA,iBAAM,WAG/B,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1BC,2BAAAA;AAAAA,EAACC,yBAAc;AAAA,EAAd;AAAA,IACC;AAAA,IACA,WAAWC,GAAAA;AAAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAED,GAAG;AAAA,EAAA;AACN,CACD;AAED,YAAY,cAAc;;"}
1
+ {"version":3,"file":"TabsTrigger.cjs","sources":["../../../../src/components/Tabs/TabsTrigger.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Props for the {@link TabsTrigger} button component. */\nexport type TabsTriggerProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>;\n\n/** An interactive tab button that activates its associated {@link TabsContent} panel when clicked. */\nexport const TabsTrigger = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex items-center justify-center\",\n \"rounded-xs border-transparent\",\n \"typography-body-1-semibold cursor-pointer text-body-100\",\n \"motion-safe:transition-[color,border-color] motion-safe:duration-150 motion-safe:ease-in-out\",\n \"data-[orientation=horizontal]:border-b-4 data-[orientation=horizontal]:px-4 data-[orientation=horizontal]:py-3\",\n \"data-[orientation=vertical]:justify-start data-[orientation=vertical]:border-r-4 data-[orientation=vertical]:px-4 data-[orientation=vertical]:py-3\",\n \"data-[state=active]:border-brand-green-500\",\n \"data-[state=active]:hover:text-hover-100\",\n \"data-[state=inactive]:hover:text-hover-200\",\n \"data-[state=active]:active:text-hover-100\",\n \"data-[state=inactive]:active:text-hover-200\",\n \"data-disabled:pointer-events-none\",\n \"data-disabled:data-[state=active]:text-disabled-100\",\n \"data-disabled:data-[state=inactive]:text-disabled-400\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-purple-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background-inverse-solid\",\n className,\n )}\n {...props}\n />\n));\n\nTabsTrigger.displayName = \"TabsTrigger\";\n"],"names":["React","jsx","TabsPrimitive","cn"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAQO,MAAM,cAAcA,iBAAM,WAG/B,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1BC,2BAAAA;AAAAA,EAACC,yBAAc;AAAA,EAAd;AAAA,IACC;AAAA,IACA,WAAWC,GAAAA;AAAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAED,GAAG;AAAA,EAAA;AACN,CACD;AAED,YAAY,cAAc;;"}
@@ -5,6 +5,7 @@ const jsxRuntime = require("react/jsx-runtime");
5
5
  const TooltipPrimitive = require("@radix-ui/react-tooltip");
6
6
  const React = require("react");
7
7
  const cn = require("../../utils/cn.cjs");
8
+ const Button = require("../Button/Button.cjs");
8
9
  function _interopNamespaceDefault(e) {
9
10
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
10
11
  if (e) {
@@ -26,22 +27,64 @@ const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
26
27
  const TooltipProvider = TooltipPrimitive__namespace.Provider;
27
28
  const Tooltip = TooltipPrimitive__namespace.Root;
28
29
  const TooltipTrigger = TooltipPrimitive__namespace.Trigger;
29
- const TooltipContent = React__namespace.forwardRef(({ className, showArrow = true, sideOffset = 8, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(TooltipPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
30
- TooltipPrimitive__namespace.Content,
31
- {
32
- ref,
33
- sideOffset,
34
- className: cn.cn(
35
- "typography-body-2-regular max-w-xs overflow-hidden rounded-3xl bg-background-solid p-4 text-background-inverse-solid shadow-[0_2px_4px_rgba(17,24,39,0.08)]",
36
- className
37
- ),
38
- ...props,
39
- children: [
40
- children,
41
- showArrow && /* @__PURE__ */ jsxRuntime.jsx(TooltipPrimitive__namespace.Arrow, { className: "fill-background-solid", width: 12, height: 6 })
42
- ]
30
+ const TooltipContent = React__namespace.forwardRef(
31
+ ({
32
+ className,
33
+ variant = "tooltip",
34
+ showArrow = true,
35
+ sideOffset = 8,
36
+ heading,
37
+ icon,
38
+ pill,
39
+ primaryAction,
40
+ secondaryAction,
41
+ children,
42
+ side,
43
+ ...props
44
+ }, ref) => {
45
+ const isInfobox = variant === "infobox";
46
+ const hasHeader = isInfobox && (icon !== void 0 || heading !== void 0 || pill !== void 0);
47
+ const hasActions = isInfobox && (primaryAction !== void 0 || secondaryAction !== void 0);
48
+ return /* @__PURE__ */ jsxRuntime.jsx(TooltipPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
49
+ TooltipPrimitive__namespace.Content,
50
+ {
51
+ ref,
52
+ sideOffset,
53
+ className: cn.cn(
54
+ "typography-body-2-regular z-50 max-w-[320px] overflow-hidden rounded-3xl bg-background-solid p-4 text-background-inverse-solid shadow-[0px_2px_4px_0px_rgba(17,24,39,0.08)]",
55
+ isInfobox && "border border-neutral-200",
56
+ className
57
+ ),
58
+ align: "center",
59
+ arrowPadding: 12,
60
+ side,
61
+ ...props,
62
+ children: [
63
+ isInfobox ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
64
+ hasHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
65
+ icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-5 shrink-0", children: icon }),
66
+ heading && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "typography-subtitle min-w-0 flex-1 text-background-inverse-solid", children: heading }),
67
+ pill && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0", children: pill })
68
+ ] }),
69
+ children && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "typography-body-2-regular text-background-inverse-solid", children }),
70
+ hasActions && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
71
+ primaryAction && (primaryAction.element ? primaryAction.element : /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { variant: "brand", size: "32", onClick: primaryAction.onClick, children: primaryAction.label })),
72
+ secondaryAction && (secondaryAction.element ? secondaryAction.element : /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { variant: "tertiary", size: "32", onClick: secondaryAction.onClick, children: secondaryAction.label }))
73
+ ] })
74
+ ] }) : children,
75
+ showArrow && /* @__PURE__ */ jsxRuntime.jsx(
76
+ TooltipPrimitive__namespace.Arrow,
77
+ {
78
+ className: "-translate-y-px! fill-background-solid stroke-2 stroke-background-solid",
79
+ width: 12,
80
+ height: 6
81
+ }
82
+ )
83
+ ]
84
+ }
85
+ ) });
43
86
  }
44
- ) }));
87
+ );
45
88
  TooltipContent.displayName = "TooltipContent";
46
89
  exports.Tooltip = Tooltip;
47
90
  exports.TooltipContent = TooltipContent;
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.cjs","sources":["../../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/**\n * @internal Temporary tooltip implementation — will be rebuilt once design is\n * finalised. See https://linear.app/fanvue/issue/ENG-7226\n */\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Props for the {@link TooltipProvider}. Wraps Radix `Tooltip.Provider`. */\nexport type TooltipProviderProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Provider>;\n\n/** Provides tooltip delay and skip-delay context. Wrap your app or a subtree. */\nexport const TooltipProvider = TooltipPrimitive.Provider;\n\n/** Props for the {@link Tooltip} root component. */\nexport type TooltipProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root>;\n\n/** Root component that manages open/close state for a single tooltip. */\nexport const Tooltip = TooltipPrimitive.Root;\n\n/** Props for the {@link TooltipTrigger} component. */\nexport type TooltipTriggerProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>;\n\n/** The element that triggers the tooltip on hover/focus. */\nexport const TooltipTrigger = TooltipPrimitive.Trigger;\n\nexport interface TooltipContentProps\n extends React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> {\n /** Whether to show the arrow pointer. @default true */\n showArrow?: boolean;\n}\n\n/** The popup content of the tooltip. Renders inside a portal. */\nexport const TooltipContent = React.forwardRef<\n React.ComponentRef<typeof TooltipPrimitive.Content>,\n TooltipContentProps\n>(({ className, showArrow = true, sideOffset = 8, children, ...props }, ref) => (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n \"typography-body-2-regular max-w-xs overflow-hidden rounded-3xl bg-background-solid p-4 text-background-inverse-solid shadow-[0_2px_4px_rgba(17,24,39,0.08)]\",\n className,\n )}\n {...props}\n >\n {children}\n {showArrow && (\n <TooltipPrimitive.Arrow className=\"fill-background-solid\" width={12} height={6} />\n )}\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n));\n\nTooltipContent.displayName = \"TooltipContent\";\n"],"names":["TooltipPrimitive","React","jsx","jsxs","cn"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAYO,MAAM,kBAAkBA,4BAAiB;AAMzC,MAAM,UAAUA,4BAAiB;AAMjC,MAAM,iBAAiBA,4BAAiB;AASxC,MAAM,iBAAiBC,iBAAM,WAGlC,CAAC,EAAE,WAAW,YAAY,MAAM,aAAa,GAAG,UAAU,GAAG,MAAA,GAAS,QACtEC,2BAAAA,IAACF,4BAAiB,QAAjB,EACC,UAAAG,2BAAAA;AAAAA,EAACH,4BAAiB;AAAA,EAAjB;AAAA,IACC;AAAA,IACA;AAAA,IACA,WAAWI,GAAAA;AAAAA,MACT;AAAA,MACA;AAAA,IAAA;AAAA,IAED,GAAG;AAAA,IAEH,UAAA;AAAA,MAAA;AAAA,MACA,aACCF,2BAAAA,IAACF,4BAAiB,OAAjB,EAAuB,WAAU,yBAAwB,OAAO,IAAI,QAAQ,EAAA,CAAG;AAAA,IAAA;AAAA,EAAA;AAEpF,GACF,CACD;AAED,eAAe,cAAc;;;;;"}
1
+ {"version":3,"file":"Tooltip.cjs","sources":["../../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["import * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../Button/Button\";\n\n/** Props for the {@link TooltipProvider}. Wraps Radix `Tooltip.Provider`. */\nexport type TooltipProviderProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Provider>;\n\n/** Provides tooltip delay and skip-delay context. Wrap your app or a subtree. */\nexport const TooltipProvider = TooltipPrimitive.Provider;\n\n/** Props for the {@link Tooltip} root component. */\nexport interface TooltipProps extends React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root> {\n /**\n * Controlled open state. When provided, the component is in controlled mode\n * and you must also supply `onOpenChange` to update the value.\n */\n open?: boolean;\n /** Called when the open state changes. Required when `open` is controlled. */\n onOpenChange?: (open: boolean) => void;\n /** The open state of the tooltip when it is initially rendered (uncontrolled). */\n defaultOpen?: boolean;\n}\n\n/** Root component that manages open/close state for a single tooltip. */\nexport const Tooltip = TooltipPrimitive.Root;\n\n/** Props for the {@link TooltipTrigger} component. */\nexport type TooltipTriggerProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>;\n\n/** The element that triggers the tooltip on hover/focus. */\nexport const TooltipTrigger = TooltipPrimitive.Trigger;\n\n/**\n * Visual style variant of the tooltip content.\n *\n * - `\"tooltip\"` — simple text bubble, no border.\n * - `\"infobox\"` — richer card with a visible border, structured header, body text, and optional actions.\n */\nexport type TooltipContentVariant = \"tooltip\" | \"infobox\";\n\n/** Action button configuration for the infobox variant of {@link TooltipContent}. */\nexport interface TooltipAction {\n /** Button label. */\n label: string;\n /** Click handler. */\n onClick?: () => void;\n /**\n * Optional custom React node to be rendered for the action instead of the default button.\n * Only used in the infobox variant.\n */\n element?: React.ReactNode;\n}\n\nexport interface TooltipContentProps\n extends React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> {\n /**\n * Visual style variant.\n *\n * `\"tooltip\"` is a lightweight text bubble. `\"infobox\"` renders a structured card\n * with optional heading, icon, pill, body text, and action buttons.\n *\n * @default \"tooltip\"\n */\n variant?: TooltipContentVariant;\n /** Whether to show the directional arrow pointer. @default true */\n showArrow?: boolean;\n /**\n * Heading text rendered in subtitle style at the top of the infobox.\n * Infobox variant only.\n */\n heading?: React.ReactNode;\n /**\n * Icon element displayed to the left of the heading.\n * Infobox variant only.\n */\n icon?: React.ReactNode;\n /**\n * Pill or badge element displayed to the right of the heading.\n * Infobox variant only.\n */\n pill?: React.ReactNode;\n /**\n * Primary action button (brand green). Rendered below the body text.\n * Infobox variant only.\n */\n primaryAction?: TooltipAction;\n /**\n * Secondary action button (ghost). Rendered next to the primary action.\n * Infobox variant only.\n */\n secondaryAction?: TooltipAction;\n}\n\n/**\n * The popup content of the tooltip. Renders inside a portal.\n *\n * Arrow direction is controlled via the `side` and `align` props (Radix passthrough).\n *\n * @example\n * ```tsx\n * // Simple tooltip\n * <TooltipContent>Info text</TooltipContent>\n *\n * // Infobox with structured content\n * <TooltipContent\n * variant=\"infobox\"\n * heading=\"Title\"\n * icon={<InfoCircleIcon className=\"size-5\" />}\n * primaryAction={{ label: \"OK\", onClick: () => {} }}\n * secondaryAction={{ label: \"Dismiss\" }}\n * >\n * Info text\n * </TooltipContent>\n * ```\n */\nexport const TooltipContent = React.forwardRef<\n React.ComponentRef<typeof TooltipPrimitive.Content>,\n TooltipContentProps\n>(\n (\n {\n className,\n variant = \"tooltip\",\n showArrow = true,\n sideOffset = 8,\n heading,\n icon,\n pill,\n primaryAction,\n secondaryAction,\n children,\n side,\n ...props\n },\n ref,\n ) => {\n const isInfobox = variant === \"infobox\";\n const hasHeader =\n isInfobox && (icon !== undefined || heading !== undefined || pill !== undefined);\n const hasActions = isInfobox && (primaryAction !== undefined || secondaryAction !== undefined);\n\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n \"typography-body-2-regular z-50 max-w-[320px] overflow-hidden rounded-3xl bg-background-solid p-4 text-background-inverse-solid shadow-[0px_2px_4px_0px_rgba(17,24,39,0.08)]\",\n isInfobox && \"border border-neutral-200\",\n className,\n )}\n align=\"center\"\n arrowPadding={12}\n side={side}\n {...props}\n >\n {isInfobox ? (\n <div className=\"flex flex-col gap-3\">\n {hasHeader && (\n <div className=\"flex items-center gap-3\">\n {icon && <div className=\"size-5 shrink-0\">{icon}</div>}\n {heading && (\n <p className=\"typography-subtitle min-w-0 flex-1 text-background-inverse-solid\">\n {heading}\n </p>\n )}\n {pill && <div className=\"shrink-0\">{pill}</div>}\n </div>\n )}\n {children && (\n <div className=\"typography-body-2-regular text-background-inverse-solid\">\n {children}\n </div>\n )}\n {hasActions && (\n <div className=\"flex items-center gap-1\">\n {primaryAction &&\n (primaryAction.element ? (\n primaryAction.element\n ) : (\n <Button variant=\"brand\" size=\"32\" onClick={primaryAction.onClick}>\n {primaryAction.label}\n </Button>\n ))}\n {secondaryAction &&\n (secondaryAction.element ? (\n secondaryAction.element\n ) : (\n <Button variant=\"tertiary\" size=\"32\" onClick={secondaryAction.onClick}>\n {secondaryAction.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n ) : (\n children\n )}\n {showArrow && (\n <TooltipPrimitive.Arrow\n className={\"-translate-y-px! fill-background-solid stroke-2 stroke-background-solid\"}\n width={12}\n height={6}\n />\n )}\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n );\n },\n);\nTooltipContent.displayName = \"TooltipContent\";\n"],"names":["TooltipPrimitive","React","jsx","jsxs","cn","Button"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AASO,MAAM,kBAAkBA,4BAAiB;AAgBzC,MAAM,UAAUA,4BAAiB;AAMjC,MAAM,iBAAiBA,4BAAiB;AAqFxC,MAAM,iBAAiBC,iBAAM;AAAA,EAIlC,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,YAAY,YAAY;AAC9B,UAAM,YACJ,cAAc,SAAS,UAAa,YAAY,UAAa,SAAS;AACxE,UAAM,aAAa,cAAc,kBAAkB,UAAa,oBAAoB;AAEpF,WACEC,2BAAAA,IAACF,4BAAiB,QAAjB,EACC,UAAAG,2BAAAA;AAAAA,MAACH,4BAAiB;AAAA,MAAjB;AAAA,QACC;AAAA,QACA;AAAA,QACA,WAAWI,GAAAA;AAAAA,UACT;AAAA,UACA,aAAa;AAAA,UACb;AAAA,QAAA;AAAA,QAEF,OAAM;AAAA,QACN,cAAc;AAAA,QACd;AAAA,QACC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,YACCD,2BAAAA,KAAC,OAAA,EAAI,WAAU,uBACZ,UAAA;AAAA,YAAA,aACCA,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,cAAA,QAAQD,2BAAAA,IAAC,OAAA,EAAI,WAAU,mBAAmB,UAAA,MAAK;AAAA,cAC/C,WACCA,2BAAAA,IAAC,KAAA,EAAE,WAAU,oEACV,UAAA,SACH;AAAA,cAED,QAAQA,2BAAAA,IAAC,OAAA,EAAI,WAAU,YAAY,UAAA,KAAA,CAAK;AAAA,YAAA,GAC3C;AAAA,YAED,YACCA,2BAAAA,IAAC,OAAA,EAAI,WAAU,2DACZ,UACH;AAAA,YAED,cACCC,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,cAAA,kBACE,cAAc,UACb,cAAc,yCAEbE,OAAAA,QAAA,EAAO,SAAQ,SAAQ,MAAK,MAAK,SAAS,cAAc,SACtD,wBAAc,MAAA,CACjB;AAAA,cAEH,oBACE,gBAAgB,UACf,gBAAgB,UAEhBH,2BAAAA,IAACG,OAAAA,QAAA,EAAO,SAAQ,YAAW,MAAK,MAAK,SAAS,gBAAgB,SAC3D,0BAAgB,OACnB;AAAA,YAAA,EAAA,CAEN;AAAA,UAAA,EAAA,CAEJ,IAEA;AAAA,UAED,aACCH,2BAAAA;AAAAA,YAACF,4BAAiB;AAAA,YAAjB;AAAA,cACC,WAAW;AAAA,cACX,OAAO;AAAA,cACP,QAAQ;AAAA,YAAA;AAAA,UAAA;AAAA,QACV;AAAA,MAAA;AAAA,IAAA,GAGN;AAAA,EAEJ;AACF;AACA,eAAe,cAAc;;;;;"}
@@ -3,7 +3,17 @@ import { jsxs, jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "../../utils/cn.mjs";
5
5
  import { Button } from "../Button/Button.mjs";
6
+ import { CheckCircleIcon } from "../Icons/CheckCircleIcon.mjs";
6
7
  import { CrossIcon } from "../Icons/CrossIcon.mjs";
8
+ import { ErrorCircleIcon } from "../Icons/ErrorCircleIcon.mjs";
9
+ import { InfoCircleIcon } from "../Icons/InfoCircleIcon.mjs";
10
+ import { WarningTriangleIcon } from "../Icons/WarningTriangleIcon.mjs";
11
+ const DEFAULT_ICONS = {
12
+ info: /* @__PURE__ */ jsx(InfoCircleIcon, {}),
13
+ success: /* @__PURE__ */ jsx(CheckCircleIcon, {}),
14
+ warning: /* @__PURE__ */ jsx(WarningTriangleIcon, {}),
15
+ error: /* @__PURE__ */ jsx(ErrorCircleIcon, {})
16
+ };
7
17
  const CLOSE_BUTTON_CLASSES = {
8
18
  info: "hover:bg-info-500/10 active:bg-info-500/10 text-info-500 motion-safe:transition-colors motion-safe:duration-150",
9
19
  success: "hover:bg-success-500/10 active:bg-success-500/10 text-success-500 motion-safe:transition-colors motion-safe:duration-150",
@@ -22,6 +32,7 @@ const Alert = React.forwardRef(
22
32
  children,
23
33
  ...props
24
34
  }, ref) => {
35
+ const resolvedIcon = icon === null ? null : icon ?? DEFAULT_ICONS[variant];
25
36
  return /* @__PURE__ */ jsxs(
26
37
  "div",
27
38
  {
@@ -30,10 +41,10 @@ const Alert = React.forwardRef(
30
41
  "data-testid": "alert",
31
42
  className: cn(
32
43
  "grid gap-x-3 rounded-lg p-4 text-sm leading-[18px]",
33
- icon && closable && "grid-cols-[auto_1fr_auto]",
34
- icon && !closable && "grid-cols-[auto_1fr]",
35
- !icon && closable && "grid-cols-[1fr_auto]",
36
- !icon && !closable && "grid-cols-[1fr]",
44
+ resolvedIcon && closable && "grid-cols-[auto_1fr_auto]",
45
+ resolvedIcon && !closable && "grid-cols-[auto_1fr]",
46
+ !resolvedIcon && closable && "grid-cols-[1fr_auto]",
47
+ !resolvedIcon && !closable && "grid-cols-[1fr]",
37
48
  title && children ? "items-start" : "items-center",
38
49
  variant === "info" && "bg-info-50 text-info-500",
39
50
  variant === "success" && "bg-success-50 text-success-500",
@@ -43,7 +54,7 @@ const Alert = React.forwardRef(
43
54
  ),
44
55
  ...props,
45
56
  children: [
46
- icon && /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-start", "aria-hidden": "true", children: icon }),
57
+ resolvedIcon && /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-start", "aria-hidden": "true", children: resolvedIcon }),
47
58
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [
48
59
  title && /* @__PURE__ */ jsx("div", { className: "typography-body-2-semibold text-body-100", children: title }),
49
60
  /* @__PURE__ */ jsx("div", { className: "typography-body-2-regular text-body-200", children })
@@ -1 +1 @@
1
- {"version":3,"file":"Alert.mjs","sources":["../../../src/components/Alert/Alert.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../Button/Button\";\nimport { CrossIcon } from \"../Icons/CrossIcon\";\n\n/** Visual style variant of the alert. */\nexport type AlertVariant = \"info\" | \"success\" | \"warning\" | \"error\";\n\nexport interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Visual style variant of the alert. @default \"info\" */\n variant?: AlertVariant;\n /** Optional title text displayed in bold above the description. */\n title?: string;\n /** Icon element displayed at the leading edge of the alert. */\n icon?: React.ReactNode;\n /** Whether to show the close button. @default false */\n closable?: boolean;\n /** Callback fired when the close button is clicked. */\n onClose?: () => void;\n /** Accessible label for the close button. @default \"Close alert\" */\n closeLabel?: string;\n}\n\nconst CLOSE_BUTTON_CLASSES: Record<AlertVariant, string> = {\n info: \"hover:bg-info-500/10 active:bg-info-500/10 text-info-500 motion-safe:transition-colors motion-safe:duration-150\",\n success:\n \"hover:bg-success-500/10 active:bg-success-500/10 text-success-500 motion-safe:transition-colors motion-safe:duration-150\",\n warning:\n \"hover:bg-warning-500/10 active:bg-warning-500/10 text-warning-500 motion-safe:transition-colors motion-safe:duration-150\",\n error:\n \"hover:bg-error-500/10 active:bg-error-500/10 text-error-500 motion-safe:transition-colors motion-safe:duration-150\",\n};\n\n/**\n * Displays a contextual feedback message to the user.\n *\n * Supports `info`, `success`, `warning`, and `error` variants with an optional\n * icon, title, description, and dismiss button.\n *\n * @example\n * ```tsx\n * <Alert variant=\"success\" title=\"Saved\" closable onClose={handleClose}>\n * Your changes have been saved.\n * </Alert>\n * ```\n */\nexport const Alert = React.forwardRef<HTMLDivElement, AlertProps>(\n (\n {\n className,\n variant = \"info\",\n title,\n icon,\n closable = false,\n onClose,\n closeLabel = \"Close alert\",\n children,\n ...props\n },\n ref,\n ) => {\n return (\n <div\n ref={ref}\n role=\"alert\"\n data-testid=\"alert\"\n className={cn(\n \"grid gap-x-3 rounded-lg p-4 text-sm leading-[18px]\",\n icon && closable && \"grid-cols-[auto_1fr_auto]\",\n icon && !closable && \"grid-cols-[auto_1fr]\",\n !icon && closable && \"grid-cols-[1fr_auto]\",\n !icon && !closable && \"grid-cols-[1fr]\",\n title && children ? \"items-start\" : \"items-center\",\n variant === \"info\" && \"bg-info-50 text-info-500\",\n variant === \"success\" && \"bg-success-50 text-success-500\",\n variant === \"warning\" && \"bg-warning-50 text-warning-500\",\n variant === \"error\" && \"bg-error-50 text-error-500\",\n className,\n )}\n {...props}\n >\n {icon && (\n <span className=\"flex shrink-0 items-start\" aria-hidden=\"true\">\n {icon}\n </span>\n )}\n\n <div className=\"flex min-w-0 flex-col gap-2\">\n {title && <div className=\"typography-body-2-semibold text-body-100\">{title}</div>}\n <div className=\"typography-body-2-regular text-body-200\">{children}</div>\n </div>\n\n {closable && (\n <Button\n variant=\"tertiary\"\n size=\"24\"\n onClick={onClose}\n className={cn(\"self-start px-0\", CLOSE_BUTTON_CLASSES[variant])}\n aria-label={closeLabel}\n >\n <CrossIcon />\n </Button>\n )}\n </div>\n );\n },\n);\n\nAlert.displayName = \"Alert\";\n"],"names":[],"mappings":";;;;;;AAuBA,MAAM,uBAAqD;AAAA,EACzD,MAAM;AAAA,EACN,SACE;AAAA,EACF,SACE;AAAA,EACF,OACE;AACJ;AAeO,MAAM,QAAQ,MAAM;AAAA,EACzB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,eAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA,UACA,QAAQ,YAAY;AAAA,UACpB,QAAQ,CAAC,YAAY;AAAA,UACrB,CAAC,QAAQ,YAAY;AAAA,UACrB,CAAC,QAAQ,CAAC,YAAY;AAAA,UACtB,SAAS,WAAW,gBAAgB;AAAA,UACpC,YAAY,UAAU;AAAA,UACtB,YAAY,aAAa;AAAA,UACzB,YAAY,aAAa;AAAA,UACzB,YAAY,WAAW;AAAA,UACvB;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,4BACE,QAAA,EAAK,WAAU,6BAA4B,eAAY,QACrD,UAAA,MACH;AAAA,UAGF,qBAAC,OAAA,EAAI,WAAU,+BACZ,UAAA;AAAA,YAAA,SAAS,oBAAC,OAAA,EAAI,WAAU,4CAA4C,UAAA,OAAM;AAAA,YAC3E,oBAAC,OAAA,EAAI,WAAU,2CAA2C,SAAA,CAAS;AAAA,UAAA,GACrE;AAAA,UAEC,YACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAW,GAAG,mBAAmB,qBAAqB,OAAO,CAAC;AAAA,cAC9D,cAAY;AAAA,cAEZ,8BAAC,WAAA,CAAA,CAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACb;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,MAAM,cAAc;"}
1
+ {"version":3,"file":"Alert.mjs","sources":["../../../src/components/Alert/Alert.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../Button/Button\";\nimport { CheckCircleIcon } from \"../Icons/CheckCircleIcon\";\nimport { CrossIcon } from \"../Icons/CrossIcon\";\nimport { ErrorCircleIcon } from \"../Icons/ErrorCircleIcon\";\nimport { InfoCircleIcon } from \"../Icons/InfoCircleIcon\";\nimport { WarningTriangleIcon } from \"../Icons/WarningTriangleIcon\";\n\n/** Visual style variant of the alert. */\nexport type AlertVariant = \"info\" | \"success\" | \"warning\" | \"error\";\n\nconst DEFAULT_ICONS: Record<AlertVariant, React.ReactNode> = {\n info: <InfoCircleIcon />,\n success: <CheckCircleIcon />,\n warning: <WarningTriangleIcon />,\n error: <ErrorCircleIcon />,\n};\n\nexport interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Visual style variant of the alert. @default \"info\" */\n variant?: AlertVariant;\n /** Optional title text displayed in bold above the description. */\n title?: string;\n /** Custom icon override. Pass `null` to hide the icon entirely. Each variant shows a default icon when left `undefined`. */\n icon?: React.ReactNode | null;\n /** Whether to show the close button. @default false */\n closable?: boolean;\n /** Callback fired when the close button is clicked. */\n onClose?: () => void;\n /** Accessible label for the close button. @default \"Close alert\" */\n closeLabel?: string;\n}\n\nconst CLOSE_BUTTON_CLASSES: Record<AlertVariant, string> = {\n info: \"hover:bg-info-500/10 active:bg-info-500/10 text-info-500 motion-safe:transition-colors motion-safe:duration-150\",\n success:\n \"hover:bg-success-500/10 active:bg-success-500/10 text-success-500 motion-safe:transition-colors motion-safe:duration-150\",\n warning:\n \"hover:bg-warning-500/10 active:bg-warning-500/10 text-warning-500 motion-safe:transition-colors motion-safe:duration-150\",\n error:\n \"hover:bg-error-500/10 active:bg-error-500/10 text-error-500 motion-safe:transition-colors motion-safe:duration-150\",\n};\n\n/**\n * Displays a contextual feedback message to the user.\n *\n * Supports `info`, `success`, `warning`, and `error` variants with a default\n * icon per variant, optional title, description, and dismiss button.\n *\n * Each variant renders a default icon automatically. Pass a custom `icon` to\n * override, or `icon={null}` to hide the icon entirely.\n *\n * @example\n * ```tsx\n * <Alert variant=\"success\" title=\"Saved\" closable onClose={handleClose}>\n * Your changes have been saved.\n * </Alert>\n * ```\n */\nexport const Alert = React.forwardRef<HTMLDivElement, AlertProps>(\n (\n {\n className,\n variant = \"info\",\n title,\n icon,\n closable = false,\n onClose,\n closeLabel = \"Close alert\",\n children,\n ...props\n },\n ref,\n ) => {\n const resolvedIcon = icon === null ? null : (icon ?? DEFAULT_ICONS[variant]);\n\n return (\n <div\n ref={ref}\n role=\"alert\"\n data-testid=\"alert\"\n className={cn(\n \"grid gap-x-3 rounded-lg p-4 text-sm leading-[18px]\",\n resolvedIcon && closable && \"grid-cols-[auto_1fr_auto]\",\n resolvedIcon && !closable && \"grid-cols-[auto_1fr]\",\n !resolvedIcon && closable && \"grid-cols-[1fr_auto]\",\n !resolvedIcon && !closable && \"grid-cols-[1fr]\",\n title && children ? \"items-start\" : \"items-center\",\n variant === \"info\" && \"bg-info-50 text-info-500\",\n variant === \"success\" && \"bg-success-50 text-success-500\",\n variant === \"warning\" && \"bg-warning-50 text-warning-500\",\n variant === \"error\" && \"bg-error-50 text-error-500\",\n className,\n )}\n {...props}\n >\n {resolvedIcon && (\n <span className=\"flex shrink-0 items-start\" aria-hidden=\"true\">\n {resolvedIcon}\n </span>\n )}\n\n <div className=\"flex min-w-0 flex-col gap-2\">\n {title && <div className=\"typography-body-2-semibold text-body-100\">{title}</div>}\n <div className=\"typography-body-2-regular text-body-200\">{children}</div>\n </div>\n\n {closable && (\n <Button\n variant=\"tertiary\"\n size=\"24\"\n onClick={onClose}\n className={cn(\"self-start px-0\", CLOSE_BUTTON_CLASSES[variant])}\n aria-label={closeLabel}\n >\n <CrossIcon />\n </Button>\n )}\n </div>\n );\n },\n);\n\nAlert.displayName = \"Alert\";\n"],"names":[],"mappings":";;;;;;;;;;AAYA,MAAM,gBAAuD;AAAA,EAC3D,0BAAO,gBAAA,EAAe;AAAA,EACtB,6BAAU,iBAAA,EAAgB;AAAA,EAC1B,6BAAU,qBAAA,EAAoB;AAAA,EAC9B,2BAAQ,iBAAA,CAAA,CAAgB;AAC1B;AAiBA,MAAM,uBAAqD;AAAA,EACzD,MAAM;AAAA,EACN,SACE;AAAA,EACF,SACE;AAAA,EACF,OACE;AACJ;AAkBO,MAAM,QAAQ,MAAM;AAAA,EACzB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,eAAe,SAAS,OAAO,OAAQ,QAAQ,cAAc,OAAO;AAE1E,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,eAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA,UACA,gBAAgB,YAAY;AAAA,UAC5B,gBAAgB,CAAC,YAAY;AAAA,UAC7B,CAAC,gBAAgB,YAAY;AAAA,UAC7B,CAAC,gBAAgB,CAAC,YAAY;AAAA,UAC9B,SAAS,WAAW,gBAAgB;AAAA,UACpC,YAAY,UAAU;AAAA,UACtB,YAAY,aAAa;AAAA,UACzB,YAAY,aAAa;AAAA,UACzB,YAAY,WAAW;AAAA,UACvB;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,oCACE,QAAA,EAAK,WAAU,6BAA4B,eAAY,QACrD,UAAA,cACH;AAAA,UAGF,qBAAC,OAAA,EAAI,WAAU,+BACZ,UAAA;AAAA,YAAA,SAAS,oBAAC,OAAA,EAAI,WAAU,4CAA4C,UAAA,OAAM;AAAA,YAC3E,oBAAC,OAAA,EAAI,WAAU,2CAA2C,SAAA,CAAS;AAAA,UAAA,GACrE;AAAA,UAEC,YACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAW,GAAG,mBAAmB,qBAAqB,OAAO,CAAC;AAAA,cAC9D,cAAY;AAAA,cAEZ,8BAAC,WAAA,CAAA,CAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACb;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,MAAM,cAAc;"}
@@ -93,7 +93,7 @@ const Button = React.forwardRef(
93
93
  ({
94
94
  className,
95
95
  variant = "primary",
96
- size = "48",
96
+ size = "40",
97
97
  leftIcon,
98
98
  rightIcon,
99
99
  loading = false,
@@ -129,7 +129,7 @@ const Button = React.forwardRef(
129
129
  ...loadingLabelProps,
130
130
  className: cn(
131
131
  // Base styles
132
- "inline-flex cursor-pointer items-center justify-center gap-2 rounded-full transition-colors",
132
+ "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-full transition-colors",
133
133
  // Focus ring
134
134
  "focus-visible:shadow-focus-ring focus-visible:outline-none",
135
135
  // Disabled state
@@ -1 +1 @@
1
- {"version":3,"file":"Button.mjs","sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { SpinnerIcon } from \"../Icons/SpinnerIcon\";\n\n/** Visual style variant of the button. */\nexport type ButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"tertiary\"\n | \"link\"\n | \"brand\"\n | \"destructive\"\n | \"white\"\n | \"tertiaryDestructive\"\n | \"text\";\n\n/** Button height in pixels. */\nexport type ButtonSize = \"48\" | \"40\" | \"32\" | \"24\";\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Visual style variant of the button. @default \"primary\" */\n variant?: ButtonVariant;\n /** Height of the button in pixels. @default \"48\" */\n size?: ButtonSize;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** When `true`, replaces the label with a spinner and disables interaction. @default false */\n loading?: boolean;\n /** Merge props onto a child element instead of rendering a `<button>`. @default false */\n asChild?: boolean;\n /** Old price shown with a strikethrough before the current price. */\n discount?: string;\n /** Current price shown inside the button after the label and icons. */\n price?: string;\n}\n\nconst SIZE_CLASSES: Record<ButtonSize, string> = {\n \"48\": \"h-12 px-4 py-3 typography-button-large\",\n \"40\": \"h-10 px-4 py-2 typography-button-small\",\n \"32\": \"h-8 px-3 py-2 typography-body-2-semibold\",\n \"24\": \"h-6 px-2 py-1 typography-body-2-semibold\",\n};\n\nconst ICON_SIZE_CLASS: Record<ButtonSize, string> = {\n \"48\": \"size-5\",\n \"40\": \"size-5\",\n \"32\": \"size-4\",\n \"24\": \"size-3.5\",\n};\n\nconst VARIANT_CLASSES: Record<ButtonVariant, string> = {\n primary:\n \"bg-neutral-400 text-body-300 hover:bg-brand-green-500 hover:text-body-black-solid-constant active:bg-brand-green-500 active:text-body-black-solid-constant\",\n secondary:\n \"border-body-100 border border-1 border-body-100 bg-transparent text-body-100 hover:bg-brand-green-50 active:bg-brand-green-50\",\n tertiary: \"bg-transparent text-body-100 hover:bg-brand-green-50 active:bg-brand-green-50\",\n link: \"bg-transparent text-body-100 underline decoration-solid hover:bg-brand-green-50 active:bg-brand-green-50\",\n brand:\n \"bg-brand-green-500 text-body-black-solid-constant hover:bg-brand-pink-500 active:bg-brand-pink-500\",\n destructive:\n \"bg-error-500 text-body-white-solid-constant hover:bg-background-solid dark:hover:bg-background-white-solid-constant dark:hover:text-error-500 active:bg-background-solid dark:active:bg-background-white-solid-constant dark:active:text-error-500\",\n white:\n \"bg-background-white-solid-constant text-body-black-solid-constant hover:bg-brand-green-500 active:bg-brand-green-500\",\n tertiaryDestructive: \"bg-transparent text-error-500 hover:bg-error-50 active:bg-error-50\",\n text: \"bg-transparent text-body-100 hover:underline active:underline\",\n};\n\n/** Recursively extract text content from React nodes for accessible labels */\nfunction getTextContent(node: React.ReactNode): string | undefined {\n if (typeof node === \"string\") return node;\n if (typeof node === \"number\") return String(node);\n if (React.isValidElement(node)) {\n return getTextContent((node.props as { children?: React.ReactNode }).children);\n }\n if (Array.isArray(node)) {\n const text = node.map(getTextContent).filter(Boolean).join(\"\");\n return text || undefined;\n }\n return undefined;\n}\n\nconst LoadingSpinner = ({ size }: { size: ButtonSize }) => {\n return (\n <span className=\"animate-spin\" aria-hidden=\"true\">\n <SpinnerIcon className={ICON_SIZE_CLASS[size]}>\n <title>Loading</title>\n </SpinnerIcon>\n </span>\n );\n};\n\nfunction renderContent({\n loading,\n asChild,\n children,\n size,\n leftIcon,\n rightIcon,\n iconSizeClass,\n discount,\n price,\n}: {\n loading: boolean;\n asChild: boolean;\n children: React.ReactNode;\n size: ButtonSize;\n leftIcon: React.ReactNode;\n rightIcon: React.ReactNode;\n iconSizeClass: string;\n discount?: string;\n price?: string;\n}) {\n if (loading) {\n // When asChild, clone the child element with spinner content instead of\n // wrapping in sr-only span (which would nest interactive elements)\n if (asChild && React.isValidElement(children)) {\n return React.cloneElement(\n children as React.ReactElement<{ children?: React.ReactNode }>,\n undefined,\n <LoadingSpinner size={size} />,\n );\n }\n return (\n <>\n <LoadingSpinner size={size} />\n <span className=\"sr-only\">{children}</span>\n </>\n );\n }\n\n if (asChild) return children;\n\n return (\n <>\n {leftIcon && (\n <span\n className={cn(\"flex shrink-0 items-center justify-center\", iconSizeClass)}\n aria-hidden=\"true\"\n >\n {leftIcon}\n </span>\n )}\n {children}\n {rightIcon && (\n <span\n className={cn(\"flex shrink-0 items-center justify-center\", iconSizeClass)}\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n {discount != null && (\n <span className=\"typography-body-1-regular line-through\" aria-hidden=\"true\">\n {discount}\n </span>\n )}\n {price != null && <span aria-hidden=\"true\">{price}</span>}\n </>\n );\n}\n\n/**\n * A versatile button component with multiple visual variants, sizes, icon\n * slots, loading state, and optional pricing display.\n *\n * @example\n * ```tsx\n * <Button variant=\"brand\" size=\"40\" leftIcon={<StarIcon />}>\n * Subscribe\n * </Button>\n * ```\n */\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant = \"primary\",\n size = \"48\",\n leftIcon,\n rightIcon,\n loading = false,\n asChild = false,\n disabled,\n children,\n discount,\n price,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"button\";\n const isDisabled = disabled || loading;\n const iconSizeClass = ICON_SIZE_CLASS[size];\n\n const buttonSpecificProps = !asChild\n ? { type: \"button\" as const, \"data-testid\": \"button\", disabled: isDisabled }\n : isDisabled\n ? { \"aria-disabled\": true }\n : {};\n\n // When asChild + loading, extract text from children for aria-label since we\n // can't wrap element children in an sr-only span (creates invalid nested markup)\n const loadingLabelProps = loading && asChild ? { \"aria-label\": getTextContent(children) } : {};\n\n const content = renderContent({\n loading,\n asChild,\n children,\n size,\n leftIcon,\n rightIcon,\n iconSizeClass,\n discount,\n price,\n });\n\n return (\n <Comp\n ref={ref}\n {...buttonSpecificProps}\n aria-busy={loading}\n {...loadingLabelProps}\n className={cn(\n // Base styles\n \"inline-flex cursor-pointer items-center justify-center gap-2 rounded-full transition-colors\",\n // Focus ring\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled state\n \"disabled:pointer-events-none disabled:opacity-50\",\n \"aria-disabled:pointer-events-none aria-disabled:opacity-50\",\n // Size styles\n SIZE_CLASSES[size],\n // Variant styles\n VARIANT_CLASSES[variant],\n // Manual CSS overrides\n className,\n )}\n {...props}\n >\n {content}\n </Comp>\n );\n },\n);\n\nButton.displayName = \"Button\";\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Button.mjs","sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { SpinnerIcon } from \"../Icons/SpinnerIcon\";\n\n/** Visual style variant of the button. */\nexport type ButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"tertiary\"\n | \"link\"\n | \"brand\"\n | \"destructive\"\n | \"white\"\n | \"tertiaryDestructive\"\n | \"text\";\n\n/** Button height in pixels. */\nexport type ButtonSize = \"48\" | \"40\" | \"32\" | \"24\";\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Visual style variant of the button. @default \"primary\" */\n variant?: ButtonVariant;\n /** Height of the button in pixels. @default \"40\" */\n size?: ButtonSize;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** When `true`, replaces the label with a spinner and disables interaction. @default false */\n loading?: boolean;\n /** Merge props onto a child element instead of rendering a `<button>`. @default false */\n asChild?: boolean;\n /** Old price shown with a strikethrough before the current price. */\n discount?: string;\n /** Current price shown inside the button after the label and icons. */\n price?: string;\n}\n\nconst SIZE_CLASSES: Record<ButtonSize, string> = {\n \"48\": \"h-12 px-4 py-3 typography-button-large\",\n \"40\": \"h-10 px-4 py-2 typography-button-small\",\n \"32\": \"h-8 px-3 py-2 typography-body-2-semibold\",\n \"24\": \"h-6 px-2 py-1 typography-body-2-semibold\",\n};\n\nconst ICON_SIZE_CLASS: Record<ButtonSize, string> = {\n \"48\": \"size-5\",\n \"40\": \"size-5\",\n \"32\": \"size-4\",\n \"24\": \"size-3.5\",\n};\n\nconst VARIANT_CLASSES: Record<ButtonVariant, string> = {\n primary:\n \"bg-neutral-400 text-body-300 hover:bg-brand-green-500 hover:text-body-black-solid-constant active:bg-brand-green-500 active:text-body-black-solid-constant\",\n secondary:\n \"border-body-100 border border-1 border-body-100 bg-transparent text-body-100 hover:bg-brand-green-50 active:bg-brand-green-50\",\n tertiary: \"bg-transparent text-body-100 hover:bg-brand-green-50 active:bg-brand-green-50\",\n link: \"bg-transparent text-body-100 underline decoration-solid hover:bg-brand-green-50 active:bg-brand-green-50\",\n brand:\n \"bg-brand-green-500 text-body-black-solid-constant hover:bg-brand-pink-500 active:bg-brand-pink-500\",\n destructive:\n \"bg-error-500 text-body-white-solid-constant hover:bg-background-solid dark:hover:bg-background-white-solid-constant dark:hover:text-error-500 active:bg-background-solid dark:active:bg-background-white-solid-constant dark:active:text-error-500\",\n white:\n \"bg-background-white-solid-constant text-body-black-solid-constant hover:bg-brand-green-500 active:bg-brand-green-500\",\n tertiaryDestructive: \"bg-transparent text-error-500 hover:bg-error-50 active:bg-error-50\",\n text: \"bg-transparent text-body-100 hover:underline active:underline\",\n};\n\n/** Recursively extract text content from React nodes for accessible labels */\nfunction getTextContent(node: React.ReactNode): string | undefined {\n if (typeof node === \"string\") return node;\n if (typeof node === \"number\") return String(node);\n if (React.isValidElement(node)) {\n return getTextContent((node.props as { children?: React.ReactNode }).children);\n }\n if (Array.isArray(node)) {\n const text = node.map(getTextContent).filter(Boolean).join(\"\");\n return text || undefined;\n }\n return undefined;\n}\n\nconst LoadingSpinner = ({ size }: { size: ButtonSize }) => {\n return (\n <span className=\"animate-spin\" aria-hidden=\"true\">\n <SpinnerIcon className={ICON_SIZE_CLASS[size]}>\n <title>Loading</title>\n </SpinnerIcon>\n </span>\n );\n};\n\nfunction renderContent({\n loading,\n asChild,\n children,\n size,\n leftIcon,\n rightIcon,\n iconSizeClass,\n discount,\n price,\n}: {\n loading: boolean;\n asChild: boolean;\n children: React.ReactNode;\n size: ButtonSize;\n leftIcon: React.ReactNode;\n rightIcon: React.ReactNode;\n iconSizeClass: string;\n discount?: string;\n price?: string;\n}) {\n if (loading) {\n // When asChild, clone the child element with spinner content instead of\n // wrapping in sr-only span (which would nest interactive elements)\n if (asChild && React.isValidElement(children)) {\n return React.cloneElement(\n children as React.ReactElement<{ children?: React.ReactNode }>,\n undefined,\n <LoadingSpinner size={size} />,\n );\n }\n return (\n <>\n <LoadingSpinner size={size} />\n <span className=\"sr-only\">{children}</span>\n </>\n );\n }\n\n if (asChild) return children;\n\n return (\n <>\n {leftIcon && (\n <span\n className={cn(\"flex shrink-0 items-center justify-center\", iconSizeClass)}\n aria-hidden=\"true\"\n >\n {leftIcon}\n </span>\n )}\n {children}\n {rightIcon && (\n <span\n className={cn(\"flex shrink-0 items-center justify-center\", iconSizeClass)}\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n {discount != null && (\n <span className=\"typography-body-1-regular line-through\" aria-hidden=\"true\">\n {discount}\n </span>\n )}\n {price != null && <span aria-hidden=\"true\">{price}</span>}\n </>\n );\n}\n\n/**\n * A versatile button component with multiple visual variants, sizes, icon\n * slots, loading state, and optional pricing display.\n *\n * @example\n * ```tsx\n * <Button variant=\"brand\" size=\"40\" leftIcon={<StarIcon />}>\n * Subscribe\n * </Button>\n * ```\n */\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant = \"primary\",\n size = \"40\",\n leftIcon,\n rightIcon,\n loading = false,\n asChild = false,\n disabled,\n children,\n discount,\n price,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"button\";\n const isDisabled = disabled || loading;\n const iconSizeClass = ICON_SIZE_CLASS[size];\n\n const buttonSpecificProps = !asChild\n ? { type: \"button\" as const, \"data-testid\": \"button\", disabled: isDisabled }\n : isDisabled\n ? { \"aria-disabled\": true }\n : {};\n\n // When asChild + loading, extract text from children for aria-label since we\n // can't wrap element children in an sr-only span (creates invalid nested markup)\n const loadingLabelProps = loading && asChild ? { \"aria-label\": getTextContent(children) } : {};\n\n const content = renderContent({\n loading,\n asChild,\n children,\n size,\n leftIcon,\n rightIcon,\n iconSizeClass,\n discount,\n price,\n });\n\n return (\n <Comp\n ref={ref}\n {...buttonSpecificProps}\n aria-busy={loading}\n {...loadingLabelProps}\n className={cn(\n // Base styles\n \"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-full transition-colors\",\n // Focus ring\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled state\n \"disabled:pointer-events-none disabled:opacity-50\",\n \"aria-disabled:pointer-events-none aria-disabled:opacity-50\",\n // Size styles\n SIZE_CLASSES[size],\n // Variant styles\n VARIANT_CLASSES[variant],\n // Manual CSS overrides\n className,\n )}\n {...props}\n >\n {content}\n </Comp>\n );\n },\n);\n\nButton.displayName = \"Button\";\n"],"names":[],"mappings":"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"}
@@ -12,7 +12,7 @@ const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
12
12
  "rounded-xs border-transparent",
13
13
  "typography-body-1-semibold cursor-pointer text-body-100",
14
14
  "motion-safe:transition-[color,border-color] motion-safe:duration-150 motion-safe:ease-in-out",
15
- "data-[orientation=horizontal]:border-b-4 data-[orientation=horizontal]:px-3 data-[orientation=horizontal]:pb-4",
15
+ "data-[orientation=horizontal]:border-b-4 data-[orientation=horizontal]:px-4 data-[orientation=horizontal]:py-3",
16
16
  "data-[orientation=vertical]:justify-start data-[orientation=vertical]:border-r-4 data-[orientation=vertical]:px-4 data-[orientation=vertical]:py-3",
17
17
  "data-[state=active]:border-brand-green-500",
18
18
  "data-[state=active]:hover:text-hover-100",
@@ -1 +1 @@
1
- {"version":3,"file":"TabsTrigger.mjs","sources":["../../../src/components/Tabs/TabsTrigger.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Props for the {@link TabsTrigger} button component. */\nexport type TabsTriggerProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>;\n\n/** An interactive tab button that activates its associated {@link TabsContent} panel when clicked. */\nexport const TabsTrigger = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex items-center justify-center\",\n \"rounded-xs border-transparent\",\n \"typography-body-1-semibold cursor-pointer text-body-100\",\n \"motion-safe:transition-[color,border-color] motion-safe:duration-150 motion-safe:ease-in-out\",\n \"data-[orientation=horizontal]:border-b-4 data-[orientation=horizontal]:px-3 data-[orientation=horizontal]:pb-4\",\n \"data-[orientation=vertical]:justify-start data-[orientation=vertical]:border-r-4 data-[orientation=vertical]:px-4 data-[orientation=vertical]:py-3\",\n \"data-[state=active]:border-brand-green-500\",\n \"data-[state=active]:hover:text-hover-100\",\n \"data-[state=inactive]:hover:text-hover-200\",\n \"data-[state=active]:active:text-hover-100\",\n \"data-[state=inactive]:active:text-hover-200\",\n \"data-disabled:pointer-events-none\",\n \"data-disabled:data-[state=active]:text-disabled-100\",\n \"data-disabled:data-[state=inactive]:text-disabled-400\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-purple-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background-inverse-solid\",\n className,\n )}\n {...props}\n />\n));\n\nTabsTrigger.displayName = \"TabsTrigger\";\n"],"names":[],"mappings":";;;;;AAQO,MAAM,cAAc,MAAM,WAG/B,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1B;AAAA,EAAC,cAAc;AAAA,EAAd;AAAA,IACC;AAAA,IACA,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAED,GAAG;AAAA,EAAA;AACN,CACD;AAED,YAAY,cAAc;"}
1
+ {"version":3,"file":"TabsTrigger.mjs","sources":["../../../src/components/Tabs/TabsTrigger.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Props for the {@link TabsTrigger} button component. */\nexport type TabsTriggerProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>;\n\n/** An interactive tab button that activates its associated {@link TabsContent} panel when clicked. */\nexport const TabsTrigger = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex items-center justify-center\",\n \"rounded-xs border-transparent\",\n \"typography-body-1-semibold cursor-pointer text-body-100\",\n \"motion-safe:transition-[color,border-color] motion-safe:duration-150 motion-safe:ease-in-out\",\n \"data-[orientation=horizontal]:border-b-4 data-[orientation=horizontal]:px-4 data-[orientation=horizontal]:py-3\",\n \"data-[orientation=vertical]:justify-start data-[orientation=vertical]:border-r-4 data-[orientation=vertical]:px-4 data-[orientation=vertical]:py-3\",\n \"data-[state=active]:border-brand-green-500\",\n \"data-[state=active]:hover:text-hover-100\",\n \"data-[state=inactive]:hover:text-hover-200\",\n \"data-[state=active]:active:text-hover-100\",\n \"data-[state=inactive]:active:text-hover-200\",\n \"data-disabled:pointer-events-none\",\n \"data-disabled:data-[state=active]:text-disabled-100\",\n \"data-disabled:data-[state=inactive]:text-disabled-400\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-purple-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background-inverse-solid\",\n className,\n )}\n {...props}\n />\n));\n\nTabsTrigger.displayName = \"TabsTrigger\";\n"],"names":[],"mappings":";;;;;AAQO,MAAM,cAAc,MAAM,WAG/B,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1B;AAAA,EAAC,cAAc;AAAA,EAAd;AAAA,IACC;AAAA,IACA,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAED,GAAG;AAAA,EAAA;AACN,CACD;AAED,YAAY,cAAc;"}
@@ -3,25 +3,68 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
4
4
  import * as React from "react";
5
5
  import { cn } from "../../utils/cn.mjs";
6
+ import { Button } from "../Button/Button.mjs";
6
7
  const TooltipProvider = TooltipPrimitive.Provider;
7
8
  const Tooltip = TooltipPrimitive.Root;
8
9
  const TooltipTrigger = TooltipPrimitive.Trigger;
9
- const TooltipContent = React.forwardRef(({ className, showArrow = true, sideOffset = 8, children, ...props }, ref) => /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
10
- TooltipPrimitive.Content,
11
- {
12
- ref,
13
- sideOffset,
14
- className: cn(
15
- "typography-body-2-regular max-w-xs overflow-hidden rounded-3xl bg-background-solid p-4 text-background-inverse-solid shadow-[0_2px_4px_rgba(17,24,39,0.08)]",
16
- className
17
- ),
18
- ...props,
19
- children: [
20
- children,
21
- showArrow && /* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { className: "fill-background-solid", width: 12, height: 6 })
22
- ]
10
+ const TooltipContent = React.forwardRef(
11
+ ({
12
+ className,
13
+ variant = "tooltip",
14
+ showArrow = true,
15
+ sideOffset = 8,
16
+ heading,
17
+ icon,
18
+ pill,
19
+ primaryAction,
20
+ secondaryAction,
21
+ children,
22
+ side,
23
+ ...props
24
+ }, ref) => {
25
+ const isInfobox = variant === "infobox";
26
+ const hasHeader = isInfobox && (icon !== void 0 || heading !== void 0 || pill !== void 0);
27
+ const hasActions = isInfobox && (primaryAction !== void 0 || secondaryAction !== void 0);
28
+ return /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
29
+ TooltipPrimitive.Content,
30
+ {
31
+ ref,
32
+ sideOffset,
33
+ className: cn(
34
+ "typography-body-2-regular z-50 max-w-[320px] overflow-hidden rounded-3xl bg-background-solid p-4 text-background-inverse-solid shadow-[0px_2px_4px_0px_rgba(17,24,39,0.08)]",
35
+ isInfobox && "border border-neutral-200",
36
+ className
37
+ ),
38
+ align: "center",
39
+ arrowPadding: 12,
40
+ side,
41
+ ...props,
42
+ children: [
43
+ isInfobox ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
44
+ hasHeader && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
45
+ icon && /* @__PURE__ */ jsx("div", { className: "size-5 shrink-0", children: icon }),
46
+ heading && /* @__PURE__ */ jsx("p", { className: "typography-subtitle min-w-0 flex-1 text-background-inverse-solid", children: heading }),
47
+ pill && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: pill })
48
+ ] }),
49
+ children && /* @__PURE__ */ jsx("div", { className: "typography-body-2-regular text-background-inverse-solid", children }),
50
+ hasActions && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
51
+ primaryAction && (primaryAction.element ? primaryAction.element : /* @__PURE__ */ jsx(Button, { variant: "brand", size: "32", onClick: primaryAction.onClick, children: primaryAction.label })),
52
+ secondaryAction && (secondaryAction.element ? secondaryAction.element : /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "32", onClick: secondaryAction.onClick, children: secondaryAction.label }))
53
+ ] })
54
+ ] }) : children,
55
+ showArrow && /* @__PURE__ */ jsx(
56
+ TooltipPrimitive.Arrow,
57
+ {
58
+ className: "-translate-y-px! fill-background-solid stroke-2 stroke-background-solid",
59
+ width: 12,
60
+ height: 6
61
+ }
62
+ )
63
+ ]
64
+ }
65
+ ) });
23
66
  }
24
- ) }));
67
+ );
25
68
  TooltipContent.displayName = "TooltipContent";
26
69
  export {
27
70
  Tooltip,
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.mjs","sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/**\n * @internal Temporary tooltip implementation — will be rebuilt once design is\n * finalised. See https://linear.app/fanvue/issue/ENG-7226\n */\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Props for the {@link TooltipProvider}. Wraps Radix `Tooltip.Provider`. */\nexport type TooltipProviderProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Provider>;\n\n/** Provides tooltip delay and skip-delay context. Wrap your app or a subtree. */\nexport const TooltipProvider = TooltipPrimitive.Provider;\n\n/** Props for the {@link Tooltip} root component. */\nexport type TooltipProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root>;\n\n/** Root component that manages open/close state for a single tooltip. */\nexport const Tooltip = TooltipPrimitive.Root;\n\n/** Props for the {@link TooltipTrigger} component. */\nexport type TooltipTriggerProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>;\n\n/** The element that triggers the tooltip on hover/focus. */\nexport const TooltipTrigger = TooltipPrimitive.Trigger;\n\nexport interface TooltipContentProps\n extends React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> {\n /** Whether to show the arrow pointer. @default true */\n showArrow?: boolean;\n}\n\n/** The popup content of the tooltip. Renders inside a portal. */\nexport const TooltipContent = React.forwardRef<\n React.ComponentRef<typeof TooltipPrimitive.Content>,\n TooltipContentProps\n>(({ className, showArrow = true, sideOffset = 8, children, ...props }, ref) => (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n \"typography-body-2-regular max-w-xs overflow-hidden rounded-3xl bg-background-solid p-4 text-background-inverse-solid shadow-[0_2px_4px_rgba(17,24,39,0.08)]\",\n className,\n )}\n {...props}\n >\n {children}\n {showArrow && (\n <TooltipPrimitive.Arrow className=\"fill-background-solid\" width={12} height={6} />\n )}\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n));\n\nTooltipContent.displayName = \"TooltipContent\";\n"],"names":[],"mappings":";;;;;AAYO,MAAM,kBAAkB,iBAAiB;AAMzC,MAAM,UAAU,iBAAiB;AAMjC,MAAM,iBAAiB,iBAAiB;AASxC,MAAM,iBAAiB,MAAM,WAGlC,CAAC,EAAE,WAAW,YAAY,MAAM,aAAa,GAAG,UAAU,GAAG,MAAA,GAAS,QACtE,oBAAC,iBAAiB,QAAjB,EACC,UAAA;AAAA,EAAC,iBAAiB;AAAA,EAAjB;AAAA,IACC;AAAA,IACA;AAAA,IACA,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IAAA;AAAA,IAED,GAAG;AAAA,IAEH,UAAA;AAAA,MAAA;AAAA,MACA,aACC,oBAAC,iBAAiB,OAAjB,EAAuB,WAAU,yBAAwB,OAAO,IAAI,QAAQ,EAAA,CAAG;AAAA,IAAA;AAAA,EAAA;AAEpF,GACF,CACD;AAED,eAAe,cAAc;"}
1
+ {"version":3,"file":"Tooltip.mjs","sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["import * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../Button/Button\";\n\n/** Props for the {@link TooltipProvider}. Wraps Radix `Tooltip.Provider`. */\nexport type TooltipProviderProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Provider>;\n\n/** Provides tooltip delay and skip-delay context. Wrap your app or a subtree. */\nexport const TooltipProvider = TooltipPrimitive.Provider;\n\n/** Props for the {@link Tooltip} root component. */\nexport interface TooltipProps extends React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root> {\n /**\n * Controlled open state. When provided, the component is in controlled mode\n * and you must also supply `onOpenChange` to update the value.\n */\n open?: boolean;\n /** Called when the open state changes. Required when `open` is controlled. */\n onOpenChange?: (open: boolean) => void;\n /** The open state of the tooltip when it is initially rendered (uncontrolled). */\n defaultOpen?: boolean;\n}\n\n/** Root component that manages open/close state for a single tooltip. */\nexport const Tooltip = TooltipPrimitive.Root;\n\n/** Props for the {@link TooltipTrigger} component. */\nexport type TooltipTriggerProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>;\n\n/** The element that triggers the tooltip on hover/focus. */\nexport const TooltipTrigger = TooltipPrimitive.Trigger;\n\n/**\n * Visual style variant of the tooltip content.\n *\n * - `\"tooltip\"` — simple text bubble, no border.\n * - `\"infobox\"` — richer card with a visible border, structured header, body text, and optional actions.\n */\nexport type TooltipContentVariant = \"tooltip\" | \"infobox\";\n\n/** Action button configuration for the infobox variant of {@link TooltipContent}. */\nexport interface TooltipAction {\n /** Button label. */\n label: string;\n /** Click handler. */\n onClick?: () => void;\n /**\n * Optional custom React node to be rendered for the action instead of the default button.\n * Only used in the infobox variant.\n */\n element?: React.ReactNode;\n}\n\nexport interface TooltipContentProps\n extends React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> {\n /**\n * Visual style variant.\n *\n * `\"tooltip\"` is a lightweight text bubble. `\"infobox\"` renders a structured card\n * with optional heading, icon, pill, body text, and action buttons.\n *\n * @default \"tooltip\"\n */\n variant?: TooltipContentVariant;\n /** Whether to show the directional arrow pointer. @default true */\n showArrow?: boolean;\n /**\n * Heading text rendered in subtitle style at the top of the infobox.\n * Infobox variant only.\n */\n heading?: React.ReactNode;\n /**\n * Icon element displayed to the left of the heading.\n * Infobox variant only.\n */\n icon?: React.ReactNode;\n /**\n * Pill or badge element displayed to the right of the heading.\n * Infobox variant only.\n */\n pill?: React.ReactNode;\n /**\n * Primary action button (brand green). Rendered below the body text.\n * Infobox variant only.\n */\n primaryAction?: TooltipAction;\n /**\n * Secondary action button (ghost). Rendered next to the primary action.\n * Infobox variant only.\n */\n secondaryAction?: TooltipAction;\n}\n\n/**\n * The popup content of the tooltip. Renders inside a portal.\n *\n * Arrow direction is controlled via the `side` and `align` props (Radix passthrough).\n *\n * @example\n * ```tsx\n * // Simple tooltip\n * <TooltipContent>Info text</TooltipContent>\n *\n * // Infobox with structured content\n * <TooltipContent\n * variant=\"infobox\"\n * heading=\"Title\"\n * icon={<InfoCircleIcon className=\"size-5\" />}\n * primaryAction={{ label: \"OK\", onClick: () => {} }}\n * secondaryAction={{ label: \"Dismiss\" }}\n * >\n * Info text\n * </TooltipContent>\n * ```\n */\nexport const TooltipContent = React.forwardRef<\n React.ComponentRef<typeof TooltipPrimitive.Content>,\n TooltipContentProps\n>(\n (\n {\n className,\n variant = \"tooltip\",\n showArrow = true,\n sideOffset = 8,\n heading,\n icon,\n pill,\n primaryAction,\n secondaryAction,\n children,\n side,\n ...props\n },\n ref,\n ) => {\n const isInfobox = variant === \"infobox\";\n const hasHeader =\n isInfobox && (icon !== undefined || heading !== undefined || pill !== undefined);\n const hasActions = isInfobox && (primaryAction !== undefined || secondaryAction !== undefined);\n\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n \"typography-body-2-regular z-50 max-w-[320px] overflow-hidden rounded-3xl bg-background-solid p-4 text-background-inverse-solid shadow-[0px_2px_4px_0px_rgba(17,24,39,0.08)]\",\n isInfobox && \"border border-neutral-200\",\n className,\n )}\n align=\"center\"\n arrowPadding={12}\n side={side}\n {...props}\n >\n {isInfobox ? (\n <div className=\"flex flex-col gap-3\">\n {hasHeader && (\n <div className=\"flex items-center gap-3\">\n {icon && <div className=\"size-5 shrink-0\">{icon}</div>}\n {heading && (\n <p className=\"typography-subtitle min-w-0 flex-1 text-background-inverse-solid\">\n {heading}\n </p>\n )}\n {pill && <div className=\"shrink-0\">{pill}</div>}\n </div>\n )}\n {children && (\n <div className=\"typography-body-2-regular text-background-inverse-solid\">\n {children}\n </div>\n )}\n {hasActions && (\n <div className=\"flex items-center gap-1\">\n {primaryAction &&\n (primaryAction.element ? (\n primaryAction.element\n ) : (\n <Button variant=\"brand\" size=\"32\" onClick={primaryAction.onClick}>\n {primaryAction.label}\n </Button>\n ))}\n {secondaryAction &&\n (secondaryAction.element ? (\n secondaryAction.element\n ) : (\n <Button variant=\"tertiary\" size=\"32\" onClick={secondaryAction.onClick}>\n {secondaryAction.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n ) : (\n children\n )}\n {showArrow && (\n <TooltipPrimitive.Arrow\n className={\"-translate-y-px! fill-background-solid stroke-2 stroke-background-solid\"}\n width={12}\n height={6}\n />\n )}\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n );\n },\n);\nTooltipContent.displayName = \"TooltipContent\";\n"],"names":[],"mappings":";;;;;;AASO,MAAM,kBAAkB,iBAAiB;AAgBzC,MAAM,UAAU,iBAAiB;AAMjC,MAAM,iBAAiB,iBAAiB;AAqFxC,MAAM,iBAAiB,MAAM;AAAA,EAIlC,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,YAAY,YAAY;AAC9B,UAAM,YACJ,cAAc,SAAS,UAAa,YAAY,UAAa,SAAS;AACxE,UAAM,aAAa,cAAc,kBAAkB,UAAa,oBAAoB;AAEpF,WACE,oBAAC,iBAAiB,QAAjB,EACC,UAAA;AAAA,MAAC,iBAAiB;AAAA,MAAjB;AAAA,QACC;AAAA,QACA;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA,aAAa;AAAA,UACb;AAAA,QAAA;AAAA,QAEF,OAAM;AAAA,QACN,cAAc;AAAA,QACd;AAAA,QACC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,YACC,qBAAC,OAAA,EAAI,WAAU,uBACZ,UAAA;AAAA,YAAA,aACC,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,cAAA,QAAQ,oBAAC,OAAA,EAAI,WAAU,mBAAmB,UAAA,MAAK;AAAA,cAC/C,WACC,oBAAC,KAAA,EAAE,WAAU,oEACV,UAAA,SACH;AAAA,cAED,QAAQ,oBAAC,OAAA,EAAI,WAAU,YAAY,UAAA,KAAA,CAAK;AAAA,YAAA,GAC3C;AAAA,YAED,YACC,oBAAC,OAAA,EAAI,WAAU,2DACZ,UACH;AAAA,YAED,cACC,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,cAAA,kBACE,cAAc,UACb,cAAc,8BAEb,QAAA,EAAO,SAAQ,SAAQ,MAAK,MAAK,SAAS,cAAc,SACtD,wBAAc,MAAA,CACjB;AAAA,cAEH,oBACE,gBAAgB,UACf,gBAAgB,UAEhB,oBAAC,QAAA,EAAO,SAAQ,YAAW,MAAK,MAAK,SAAS,gBAAgB,SAC3D,0BAAgB,OACnB;AAAA,YAAA,EAAA,CAEN;AAAA,UAAA,EAAA,CAEJ,IAEA;AAAA,UAED,aACC;AAAA,YAAC,iBAAiB;AAAA,YAAjB;AAAA,cACC,WAAW;AAAA,cACX,OAAO;AAAA,cACP,QAAQ;AAAA,YAAA;AAAA,UAAA;AAAA,QACV;AAAA,MAAA;AAAA,IAAA,GAGN;AAAA,EAEJ;AACF;AACA,eAAe,cAAc;"}
package/dist/index.d.ts CHANGED
@@ -15,8 +15,11 @@ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
15
15
  /**
16
16
  * Displays a contextual feedback message to the user.
17
17
  *
18
- * Supports `info`, `success`, `warning`, and `error` variants with an optional
19
- * icon, title, description, and dismiss button.
18
+ * Supports `info`, `success`, `warning`, and `error` variants with a default
19
+ * icon per variant, optional title, description, and dismiss button.
20
+ *
21
+ * Each variant renders a default icon automatically. Pass a custom `icon` to
22
+ * override, or `icon={null}` to hide the icon entirely.
20
23
  *
21
24
  * @example
22
25
  * ```tsx
@@ -32,8 +35,8 @@ export declare interface AlertProps extends React_2.HTMLAttributes<HTMLDivElemen
32
35
  variant?: AlertVariant;
33
36
  /** Optional title text displayed in bold above the description. */
34
37
  title?: string;
35
- /** Icon element displayed at the leading edge of the alert. */
36
- icon?: React_2.ReactNode;
38
+ /** Custom icon override. Pass `null` to hide the icon entirely. Each variant shows a default icon when left `undefined`. */
39
+ icon?: React_2.ReactNode | null;
37
40
  /** Whether to show the close button. @default false */
38
41
  closable?: boolean;
39
42
  /** Callback fired when the close button is clicked. */
@@ -228,7 +231,7 @@ export declare const Button: React_2.ForwardRefExoticComponent<ButtonProps & Rea
228
231
  export declare interface ButtonProps extends React_2.ButtonHTMLAttributes<HTMLButtonElement> {
229
232
  /** Visual style variant of the button. @default "primary" */
230
233
  variant?: ButtonVariant;
231
- /** Height of the button in pixels. @default "48" */
234
+ /** Height of the button in pixels. @default "40" */
232
235
  size?: ButtonSize;
233
236
  /** Icon element displayed before the label. */
234
237
  leftIcon?: React_2.ReactNode;
@@ -1242,16 +1245,102 @@ export declare interface ToastViewportProps extends React_2.ComponentPropsWithou
1242
1245
  /** Root component that manages open/close state for a single tooltip. */
1243
1246
  export declare const Tooltip: React_2.FC<TooltipPrimitive.TooltipProps>;
1244
1247
 
1245
- /** The popup content of the tooltip. Renders inside a portal. */
1248
+ /** Action button configuration for the infobox variant of {@link TooltipContent}. */
1249
+ export declare interface TooltipAction {
1250
+ /** Button label. */
1251
+ label: string;
1252
+ /** Click handler. */
1253
+ onClick?: () => void;
1254
+ /**
1255
+ * Optional custom React node to be rendered for the action instead of the default button.
1256
+ * Only used in the infobox variant.
1257
+ */
1258
+ element?: React_2.ReactNode;
1259
+ }
1260
+
1261
+ /**
1262
+ * The popup content of the tooltip. Renders inside a portal.
1263
+ *
1264
+ * Arrow direction is controlled via the `side` and `align` props (Radix passthrough).
1265
+ *
1266
+ * @example
1267
+ * ```tsx
1268
+ * // Simple tooltip
1269
+ * <TooltipContent>Info text</TooltipContent>
1270
+ *
1271
+ * // Infobox with structured content
1272
+ * <TooltipContent
1273
+ * variant="infobox"
1274
+ * heading="Title"
1275
+ * icon={<InfoCircleIcon className="size-5" />}
1276
+ * primaryAction={{ label: "OK", onClick: () => {} }}
1277
+ * secondaryAction={{ label: "Dismiss" }}
1278
+ * >
1279
+ * Info text
1280
+ * </TooltipContent>
1281
+ * ```
1282
+ */
1246
1283
  export declare const TooltipContent: React_2.ForwardRefExoticComponent<TooltipContentProps & React_2.RefAttributes<HTMLDivElement>>;
1247
1284
 
1248
1285
  export declare interface TooltipContentProps extends React_2.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> {
1249
- /** Whether to show the arrow pointer. @default true */
1286
+ /**
1287
+ * Visual style variant.
1288
+ *
1289
+ * `"tooltip"` is a lightweight text bubble. `"infobox"` renders a structured card
1290
+ * with optional heading, icon, pill, body text, and action buttons.
1291
+ *
1292
+ * @default "tooltip"
1293
+ */
1294
+ variant?: TooltipContentVariant;
1295
+ /** Whether to show the directional arrow pointer. @default true */
1250
1296
  showArrow?: boolean;
1297
+ /**
1298
+ * Heading text rendered in subtitle style at the top of the infobox.
1299
+ * Infobox variant only.
1300
+ */
1301
+ heading?: React_2.ReactNode;
1302
+ /**
1303
+ * Icon element displayed to the left of the heading.
1304
+ * Infobox variant only.
1305
+ */
1306
+ icon?: React_2.ReactNode;
1307
+ /**
1308
+ * Pill or badge element displayed to the right of the heading.
1309
+ * Infobox variant only.
1310
+ */
1311
+ pill?: React_2.ReactNode;
1312
+ /**
1313
+ * Primary action button (brand green). Rendered below the body text.
1314
+ * Infobox variant only.
1315
+ */
1316
+ primaryAction?: TooltipAction;
1317
+ /**
1318
+ * Secondary action button (ghost). Rendered next to the primary action.
1319
+ * Infobox variant only.
1320
+ */
1321
+ secondaryAction?: TooltipAction;
1251
1322
  }
1252
1323
 
1324
+ /**
1325
+ * Visual style variant of the tooltip content.
1326
+ *
1327
+ * - `"tooltip"` — simple text bubble, no border.
1328
+ * - `"infobox"` — richer card with a visible border, structured header, body text, and optional actions.
1329
+ */
1330
+ export declare type TooltipContentVariant = "tooltip" | "infobox";
1331
+
1253
1332
  /** Props for the {@link Tooltip} root component. */
1254
- export declare type TooltipProps = React_2.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root>;
1333
+ export declare interface TooltipProps extends React_2.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root> {
1334
+ /**
1335
+ * Controlled open state. When provided, the component is in controlled mode
1336
+ * and you must also supply `onOpenChange` to update the value.
1337
+ */
1338
+ open?: boolean;
1339
+ /** Called when the open state changes. Required when `open` is controlled. */
1340
+ onOpenChange?: (open: boolean) => void;
1341
+ /** The open state of the tooltip when it is initially rendered (uncontrolled). */
1342
+ defaultOpen?: boolean;
1343
+ }
1255
1344
 
1256
1345
  /** Provides tooltip delay and skip-delay context. Wrap your app or a subtree. */
1257
1346
  export declare const TooltipProvider: React_2.FC<TooltipPrimitive.TooltipProviderProps>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanvue/ui",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "React component library built with Tailwind CSS for Fanvue ecosystem",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",