@fanvue/ui 1.6.0 → 1.7.1

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;;"}
@@ -35,6 +35,12 @@ const ICON_SIZE_CLASS = {
35
35
  "32": "size-4",
36
36
  "24": "size-3.5"
37
37
  };
38
+ const ICON_WRAPPER_CLASS = {
39
+ "48": "[&>svg]:size-5",
40
+ "40": "[&>svg]:size-5",
41
+ "32": "[&>svg]:size-4",
42
+ "24": "[&>svg]:size-3.5"
43
+ };
38
44
  const VARIANT_CLASSES = {
39
45
  primary: "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",
40
46
  secondary: "border-body-100 border border-1 border-body-100 bg-transparent text-body-100 hover:bg-brand-green-50 active:bg-brand-green-50",
@@ -112,7 +118,7 @@ const Button = React__namespace.forwardRef(
112
118
  ({
113
119
  className,
114
120
  variant = "primary",
115
- size = "48",
121
+ size = "40",
116
122
  leftIcon,
117
123
  rightIcon,
118
124
  loading = false,
@@ -125,8 +131,12 @@ const Button = React__namespace.forwardRef(
125
131
  }, ref) => {
126
132
  const Comp = asChild ? reactSlot.Slot : "button";
127
133
  const isDisabled = disabled || loading;
128
- const iconSizeClass = ICON_SIZE_CLASS[size];
129
- const buttonSpecificProps = !asChild ? { type: "button", "data-testid": "button", disabled: isDisabled } : isDisabled ? { "aria-disabled": true } : {};
134
+ const iconSizeClass = ICON_WRAPPER_CLASS[size];
135
+ const buttonSpecificProps = !asChild ? {
136
+ type: "button",
137
+ "data-testid": "button",
138
+ disabled: isDisabled
139
+ } : isDisabled ? { "aria-disabled": true } : {};
130
140
  const loadingLabelProps = loading && asChild ? { "aria-label": getTextContent(children) } : {};
131
141
  const content = renderContent({
132
142
  loading,
@@ -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 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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"}
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\n/** Targets only direct SVG children so non-icon content (e.g. Pill) can size naturally. */\nconst ICON_WRAPPER_CLASS: Record<ButtonSize, string> = {\n \"48\": \"[&>svg]:size-5\",\n \"40\": \"[&>svg]:size-5\",\n \"32\": \"[&>svg]:size-4\",\n \"24\": \"[&>svg]: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_WRAPPER_CLASS[size];\n\n const buttonSpecificProps = !asChild\n ? {\n type: \"button\" as const,\n \"data-testid\": \"button\",\n disabled: isDisabled,\n }\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;;"}
@@ -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;"}
@@ -16,6 +16,12 @@ const ICON_SIZE_CLASS = {
16
16
  "32": "size-4",
17
17
  "24": "size-3.5"
18
18
  };
19
+ const ICON_WRAPPER_CLASS = {
20
+ "48": "[&>svg]:size-5",
21
+ "40": "[&>svg]:size-5",
22
+ "32": "[&>svg]:size-4",
23
+ "24": "[&>svg]:size-3.5"
24
+ };
19
25
  const VARIANT_CLASSES = {
20
26
  primary: "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",
21
27
  secondary: "border-body-100 border border-1 border-body-100 bg-transparent text-body-100 hover:bg-brand-green-50 active:bg-brand-green-50",
@@ -93,7 +99,7 @@ const Button = React.forwardRef(
93
99
  ({
94
100
  className,
95
101
  variant = "primary",
96
- size = "48",
102
+ size = "40",
97
103
  leftIcon,
98
104
  rightIcon,
99
105
  loading = false,
@@ -106,8 +112,12 @@ const Button = React.forwardRef(
106
112
  }, ref) => {
107
113
  const Comp = asChild ? Slot : "button";
108
114
  const isDisabled = disabled || loading;
109
- const iconSizeClass = ICON_SIZE_CLASS[size];
110
- const buttonSpecificProps = !asChild ? { type: "button", "data-testid": "button", disabled: isDisabled } : isDisabled ? { "aria-disabled": true } : {};
115
+ const iconSizeClass = ICON_WRAPPER_CLASS[size];
116
+ const buttonSpecificProps = !asChild ? {
117
+ type: "button",
118
+ "data-testid": "button",
119
+ disabled: isDisabled
120
+ } : isDisabled ? { "aria-disabled": true } : {};
111
121
  const loadingLabelProps = loading && asChild ? { "aria-label": getTextContent(children) } : {};
112
122
  const content = renderContent({
113
123
  loading,
@@ -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 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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"}
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\n/** Targets only direct SVG children so non-icon content (e.g. Pill) can size naturally. */\nconst ICON_WRAPPER_CLASS: Record<ButtonSize, string> = {\n \"48\": \"[&>svg]:size-5\",\n \"40\": \"[&>svg]:size-5\",\n \"32\": \"[&>svg]:size-4\",\n \"24\": \"[&>svg]: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_WRAPPER_CLASS[size];\n\n const buttonSpecificProps = !asChild\n ? {\n type: \"button\" as const,\n \"data-testid\": \"button\",\n disabled: isDisabled,\n }\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;"}
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanvue/ui",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
4
4
  "description": "React component library built with Tailwind CSS for Fanvue ecosystem",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",