@fanvue/ui 3.5.0 → 3.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.
Files changed (43) hide show
  1. package/dist/charts.d.ts +1 -1
  2. package/dist/cjs/components/Chart/ChartCard.cjs +2 -2
  3. package/dist/cjs/components/Chart/ChartCard.cjs.map +1 -1
  4. package/dist/cjs/components/ChatInput/ChatInput.cjs +128 -92
  5. package/dist/cjs/components/ChatInput/ChatInput.cjs.map +1 -1
  6. package/dist/cjs/components/Chip/Chip.cjs +46 -10
  7. package/dist/cjs/components/Chip/Chip.cjs.map +1 -1
  8. package/dist/cjs/components/Drawer/Drawer.cjs +6 -3
  9. package/dist/cjs/components/Drawer/Drawer.cjs.map +1 -1
  10. package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs +20 -4
  11. package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  12. package/dist/cjs/components/Icons/TickIcon.cjs +3 -1
  13. package/dist/cjs/components/Icons/TickIcon.cjs.map +1 -1
  14. package/dist/cjs/components/Logo/Logo.cjs +107 -33
  15. package/dist/cjs/components/Logo/Logo.cjs.map +1 -1
  16. package/dist/cjs/components/Logo/agenciesIcon.cjs +6 -0
  17. package/dist/cjs/components/Logo/agenciesIcon.cjs.map +1 -0
  18. package/dist/cjs/components/Tabs/TabsList.cjs +20 -12
  19. package/dist/cjs/components/Tabs/TabsList.cjs.map +1 -1
  20. package/dist/cjs/components/Tabs/TabsTrigger.cjs +8 -7
  21. package/dist/cjs/components/Tabs/TabsTrigger.cjs.map +1 -1
  22. package/dist/components/Chart/ChartCard.mjs +2 -2
  23. package/dist/components/Chart/ChartCard.mjs.map +1 -1
  24. package/dist/components/ChatInput/ChatInput.mjs +128 -92
  25. package/dist/components/ChatInput/ChatInput.mjs.map +1 -1
  26. package/dist/components/Chip/Chip.mjs +46 -10
  27. package/dist/components/Chip/Chip.mjs.map +1 -1
  28. package/dist/components/Drawer/Drawer.mjs +6 -3
  29. package/dist/components/Drawer/Drawer.mjs.map +1 -1
  30. package/dist/components/DropdownMenu/DropdownMenu.mjs +20 -4
  31. package/dist/components/DropdownMenu/DropdownMenu.mjs.map +1 -1
  32. package/dist/components/Icons/TickIcon.mjs +3 -1
  33. package/dist/components/Icons/TickIcon.mjs.map +1 -1
  34. package/dist/components/Logo/Logo.mjs +107 -33
  35. package/dist/components/Logo/Logo.mjs.map +1 -1
  36. package/dist/components/Logo/agenciesIcon.mjs +6 -0
  37. package/dist/components/Logo/agenciesIcon.mjs.map +1 -0
  38. package/dist/components/Tabs/TabsList.mjs +20 -12
  39. package/dist/components/Tabs/TabsList.mjs.map +1 -1
  40. package/dist/components/Tabs/TabsTrigger.mjs +8 -7
  41. package/dist/components/Tabs/TabsTrigger.mjs.map +1 -1
  42. package/dist/index.d.ts +61 -6
  43. package/package.json +8 -4
package/dist/charts.d.ts CHANGED
@@ -39,7 +39,7 @@ export declare interface ChartCardProps extends Omit<React_2.HTMLAttributes<HTML
39
39
  tooltipAriaLabel?: string;
40
40
  /** Date range or period label shown below the subtitle. */
41
41
  dateInfo?: React_2.ReactNode;
42
- /** Trend indicator chip config. */
42
+ /** Trend indicator chip config. Only rendered when {@link subtitle} is also provided. */
43
43
  trendChip?: {
44
44
  /** Display label (e.g. "12.5%"). */
45
45
  label: React_2.ReactNode;
@@ -64,13 +64,13 @@ const ChartCard = React__namespace.forwardRef(
64
64
  /* @__PURE__ */ jsxRuntime.jsx(Tooltip.TooltipContent, { children: tooltip })
65
65
  ] }) })
66
66
  ] }),
67
- subtitle && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
67
+ subtitle && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
68
68
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typography-header-heading-sm text-content-primary", children: subtitle }),
69
69
  trendChip && /* @__PURE__ */ jsxRuntime.jsx(
70
70
  "span",
71
71
  {
72
72
  className: cn.cn(
73
- "typography-description-12px-semibold rounded-full px-2 py-0.5",
73
+ "typography-description-12px-semibold whitespace-nowrap rounded-full px-2 py-0.5",
74
74
  TREND_CLASSES[trendChip.trend]
75
75
  ),
76
76
  children: trendChip.label
@@ -1 +1 @@
1
- {"version":3,"file":"ChartCard.cjs","sources":["../../../../src/components/Chart/ChartCard.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Card } from \"../Card/Card\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { InfoCircleIcon } from \"../Icons/InfoCircleIcon\";\nimport { Skeleton } from \"../Skeleton/Skeleton\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../Tooltip/Tooltip\";\n\n/** Props for {@link ChartCard}. */\nexport interface ChartCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Card title text. Pass translated string for i18n. */\n title: React.ReactNode;\n /** Large subtitle value (e.g. formatted price or count). */\n subtitle?: React.ReactNode;\n /** Tooltip text shown next to the title. Pass translated string for i18n. */\n tooltip?: React.ReactNode;\n /** Accessible label for the info tooltip trigger. Override for i18n. @default \"More info\" */\n tooltipAriaLabel?: string;\n /** Date range or period label shown below the subtitle. */\n dateInfo?: React.ReactNode;\n /** Trend indicator chip config. */\n trendChip?: {\n /** Display label (e.g. \"12.5%\"). */\n label: React.ReactNode;\n /** Whether the trend is positive (green) or negative (red). */\n trend: \"positive\" | \"negative\";\n };\n /** Show loading skeleton instead of content. @default false */\n loading?: boolean;\n /** Chart content rendered below the header. */\n children?: React.ReactNode;\n}\n\nconst TREND_CLASSES: Record<\"positive\" | \"negative\", string> = {\n positive: \"bg-success-surface text-success-content\",\n negative: \"bg-error-surface text-error-content\",\n};\n\n/**\n * Wraps any chart with a structured header containing title, subtitle,\n * optional trend chip, date range label, info tooltip, and a loading\n * skeleton state.\n *\n * @example\n * ```tsx\n * <ChartCard\n * title=\"Revenue\"\n * subtitle=\"$4,523\"\n * trendChip={{ label: \"+12.5%\", trend: \"positive\" }}\n * dateInfo=\"Jan 1 – Mar 17\"\n * tooltip=\"Total revenue for the selected period.\"\n * >\n * <MyLineChart />\n * </ChartCard>\n * ```\n */\nexport const ChartCard = React.forwardRef<HTMLDivElement, ChartCardProps>(\n (\n {\n className,\n title,\n subtitle,\n tooltip,\n tooltipAriaLabel = \"More info\",\n dateInfo,\n trendChip,\n loading = false,\n children,\n ...props\n },\n ref,\n ) => {\n return (\n <Card ref={ref} variant=\"outlined\" noPadding className={className} {...props}>\n <div className=\"flex flex-col gap-2 p-4\">\n {loading ? (\n <>\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-4 w-32\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-7 w-44\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-3 w-24\" />\n </>\n ) : (\n <>\n <div className=\"flex items-center gap-1.5\">\n <span className=\"typography-body-small-14px-semibold text-content-primary\">\n {title}\n </span>\n {tooltip && (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={tooltipAriaLabel}\n icon={<InfoCircleIcon className=\"size-4 text-content-tertiary\" />}\n />\n </TooltipTrigger>\n <TooltipContent>{tooltip}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )}\n </div>\n {subtitle && (\n <div className=\"flex items-center gap-2\">\n <span className=\"typography-header-heading-sm text-content-primary\">\n {subtitle}\n </span>\n {trendChip && (\n <span\n className={cn(\n \"typography-description-12px-semibold rounded-full px-2 py-0.5\",\n TREND_CLASSES[trendChip.trend],\n )}\n >\n {trendChip.label}\n </span>\n )}\n </div>\n )}\n {dateInfo && (\n <span className=\"typography-description-12px-regular text-content-tertiary\">\n {dateInfo}\n </span>\n )}\n </>\n )}\n <div className=\"mt-auto\">{children}</div>\n </div>\n </Card>\n );\n },\n);\nChartCard.displayName = \"ChartCard\";\n"],"names":["React","jsx","Card","jsxs","Fragment","Skeleton","TooltipProvider","Tooltip","TooltipTrigger","IconButton","InfoCircleIcon","TooltipContent","cn"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAM,gBAAyD;AAAA,EAC7D,UAAU;AAAA,EACV,UAAU;AACZ;AAoBO,MAAM,YAAYA,iBAAM;AAAA,EAC7B,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,WACEC,2BAAAA,IAACC,KAAAA,MAAA,EAAK,KAAU,SAAQ,YAAW,WAAS,MAAC,WAAuB,GAAG,OACrE,UAAAC,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA,UACCA,2BAAAA,KAAAC,qBAAA,EACE,UAAA;AAAA,QAAAH,+BAACI,SAAAA,YAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,uCACjEA,SAAAA,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,uCACjEA,SAAAA,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,WAAA,CAAW;AAAA,MAAA,EAAA,CACpE,IAEAF,2BAAAA,KAAAC,WAAAA,UAAA,EACE,UAAA;AAAA,QAAAD,2BAAAA,KAAC,OAAA,EAAI,WAAU,6BACb,UAAA;AAAA,UAAAF,2BAAAA,IAAC,QAAA,EAAK,WAAU,4DACb,UAAA,OACH;AAAA,UACC,WACCA,2BAAAA,IAACK,yBAAA,EACC,UAAAH,gCAACI,QAAAA,SAAA,EACC,UAAA;AAAA,YAAAN,2BAAAA,IAACO,QAAAA,gBAAA,EAAe,SAAO,MACrB,UAAAP,2BAAAA;AAAAA,cAACQ,WAAAA;AAAAA,cAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,cAAY;AAAA,gBACZ,MAAMR,2BAAAA,IAACS,eAAAA,gBAAA,EAAe,WAAU,+BAAA,CAA+B;AAAA,cAAA;AAAA,YAAA,GAEnE;AAAA,YACAT,2BAAAA,IAACU,QAAAA,kBAAgB,UAAA,QAAA,CAAQ;AAAA,UAAA,EAAA,CAC3B,EAAA,CACF;AAAA,QAAA,GAEJ;AAAA,QACC,YACCR,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,UAAAF,2BAAAA,IAAC,QAAA,EAAK,WAAU,qDACb,UAAA,UACH;AAAA,UACC,aACCA,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAWW,GAAAA;AAAAA,gBACT;AAAA,gBACA,cAAc,UAAU,KAAK;AAAA,cAAA;AAAA,cAG9B,UAAA,UAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACb,GAEJ;AAAA,QAED,YACCX,2BAAAA,IAAC,QAAA,EAAK,WAAU,6DACb,UAAA,SAAA,CACH;AAAA,MAAA,GAEJ;AAAA,MAEFA,2BAAAA,IAAC,OAAA,EAAI,WAAU,WAAW,SAAA,CAAS;AAAA,IAAA,EAAA,CACrC,EAAA,CACF;AAAA,EAEJ;AACF;AACA,UAAU,cAAc;;"}
1
+ {"version":3,"file":"ChartCard.cjs","sources":["../../../../src/components/Chart/ChartCard.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Card } from \"../Card/Card\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { InfoCircleIcon } from \"../Icons/InfoCircleIcon\";\nimport { Skeleton } from \"../Skeleton/Skeleton\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../Tooltip/Tooltip\";\n\n/** Props for {@link ChartCard}. */\nexport interface ChartCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Card title text. Pass translated string for i18n. */\n title: React.ReactNode;\n /** Large subtitle value (e.g. formatted price or count). */\n subtitle?: React.ReactNode;\n /** Tooltip text shown next to the title. Pass translated string for i18n. */\n tooltip?: React.ReactNode;\n /** Accessible label for the info tooltip trigger. Override for i18n. @default \"More info\" */\n tooltipAriaLabel?: string;\n /** Date range or period label shown below the subtitle. */\n dateInfo?: React.ReactNode;\n /** Trend indicator chip config. Only rendered when {@link subtitle} is also provided. */\n trendChip?: {\n /** Display label (e.g. \"12.5%\"). */\n label: React.ReactNode;\n /** Whether the trend is positive (green) or negative (red). */\n trend: \"positive\" | \"negative\";\n };\n /** Show loading skeleton instead of content. @default false */\n loading?: boolean;\n /** Chart content rendered below the header. */\n children?: React.ReactNode;\n}\n\nconst TREND_CLASSES: Record<\"positive\" | \"negative\", string> = {\n positive: \"bg-success-surface text-success-content\",\n negative: \"bg-error-surface text-error-content\",\n};\n\n/**\n * Wraps any chart with a structured header containing title, subtitle,\n * optional trend chip, date range label, info tooltip, and a loading\n * skeleton state.\n *\n * @example\n * ```tsx\n * <ChartCard\n * title=\"Revenue\"\n * subtitle=\"$4,523\"\n * trendChip={{ label: \"+12.5%\", trend: \"positive\" }}\n * dateInfo=\"Jan 1 – Mar 17\"\n * tooltip=\"Total revenue for the selected period.\"\n * >\n * <MyLineChart />\n * </ChartCard>\n * ```\n */\nexport const ChartCard = React.forwardRef<HTMLDivElement, ChartCardProps>(\n (\n {\n className,\n title,\n subtitle,\n tooltip,\n tooltipAriaLabel = \"More info\",\n dateInfo,\n trendChip,\n loading = false,\n children,\n ...props\n },\n ref,\n ) => {\n return (\n <Card ref={ref} variant=\"outlined\" noPadding className={className} {...props}>\n <div className=\"flex flex-col gap-2 p-4\">\n {loading ? (\n <>\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-4 w-32\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-7 w-44\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-3 w-24\" />\n </>\n ) : (\n <>\n <div className=\"flex items-center gap-1.5\">\n <span className=\"typography-body-small-14px-semibold text-content-primary\">\n {title}\n </span>\n {tooltip && (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={tooltipAriaLabel}\n icon={<InfoCircleIcon className=\"size-4 text-content-tertiary\" />}\n />\n </TooltipTrigger>\n <TooltipContent>{tooltip}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )}\n </div>\n {subtitle && (\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"typography-header-heading-sm text-content-primary\">\n {subtitle}\n </span>\n {trendChip && (\n <span\n className={cn(\n \"typography-description-12px-semibold whitespace-nowrap rounded-full px-2 py-0.5\",\n TREND_CLASSES[trendChip.trend],\n )}\n >\n {trendChip.label}\n </span>\n )}\n </div>\n )}\n {dateInfo && (\n <span className=\"typography-description-12px-regular text-content-tertiary\">\n {dateInfo}\n </span>\n )}\n </>\n )}\n <div className=\"mt-auto\">{children}</div>\n </div>\n </Card>\n );\n },\n);\nChartCard.displayName = \"ChartCard\";\n"],"names":["React","jsx","Card","jsxs","Fragment","Skeleton","TooltipProvider","Tooltip","TooltipTrigger","IconButton","InfoCircleIcon","TooltipContent","cn"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAM,gBAAyD;AAAA,EAC7D,UAAU;AAAA,EACV,UAAU;AACZ;AAoBO,MAAM,YAAYA,iBAAM;AAAA,EAC7B,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,WACEC,2BAAAA,IAACC,KAAAA,MAAA,EAAK,KAAU,SAAQ,YAAW,WAAS,MAAC,WAAuB,GAAG,OACrE,UAAAC,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA,UACCA,2BAAAA,KAAAC,qBAAA,EACE,UAAA;AAAA,QAAAH,+BAACI,SAAAA,YAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,uCACjEA,SAAAA,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,uCACjEA,SAAAA,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,WAAA,CAAW;AAAA,MAAA,EAAA,CACpE,IAEAF,2BAAAA,KAAAC,WAAAA,UAAA,EACE,UAAA;AAAA,QAAAD,2BAAAA,KAAC,OAAA,EAAI,WAAU,6BACb,UAAA;AAAA,UAAAF,2BAAAA,IAAC,QAAA,EAAK,WAAU,4DACb,UAAA,OACH;AAAA,UACC,WACCA,2BAAAA,IAACK,yBAAA,EACC,UAAAH,gCAACI,QAAAA,SAAA,EACC,UAAA;AAAA,YAAAN,2BAAAA,IAACO,QAAAA,gBAAA,EAAe,SAAO,MACrB,UAAAP,2BAAAA;AAAAA,cAACQ,WAAAA;AAAAA,cAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,cAAY;AAAA,gBACZ,MAAMR,2BAAAA,IAACS,eAAAA,gBAAA,EAAe,WAAU,+BAAA,CAA+B;AAAA,cAAA;AAAA,YAAA,GAEnE;AAAA,YACAT,2BAAAA,IAACU,QAAAA,kBAAgB,UAAA,QAAA,CAAQ;AAAA,UAAA,EAAA,CAC3B,EAAA,CACF;AAAA,QAAA,GAEJ;AAAA,QACC,YACCR,2BAAAA,KAAC,OAAA,EAAI,WAAU,qCACb,UAAA;AAAA,UAAAF,2BAAAA,IAAC,QAAA,EAAK,WAAU,qDACb,UAAA,UACH;AAAA,UACC,aACCA,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAWW,GAAAA;AAAAA,gBACT;AAAA,gBACA,cAAc,UAAU,KAAK;AAAA,cAAA;AAAA,cAG9B,UAAA,UAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACb,GAEJ;AAAA,QAED,YACCX,2BAAAA,IAAC,QAAA,EAAK,WAAU,6DACb,UAAA,SAAA,CACH;AAAA,MAAA,GAEJ;AAAA,MAEFA,2BAAAA,IAAC,OAAA,EAAI,WAAU,WAAW,SAAA,CAAS;AAAA,IAAA,EAAA,CACrC,EAAA,CACF;AAAA,EAEJ;AACF;AACA,UAAU,cAAc;;"}
@@ -4,12 +4,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  const jsxRuntime = require("react/jsx-runtime");
5
5
  const React = require("react");
6
6
  const cn = require("../../utils/cn.cjs");
7
+ const Drawer = require("../Drawer/Drawer.cjs");
8
+ const DropdownMenu = require("../DropdownMenu/DropdownMenu.cjs");
7
9
  const IconButton = require("../IconButton/IconButton.cjs");
8
10
  const AddIcon = require("../Icons/AddIcon.cjs");
9
11
  const ArrowUpIcon = require("../Icons/ArrowUpIcon.cjs");
10
- const CheckIcon = require("../Icons/CheckIcon.cjs");
11
12
  const ChevronDownIcon = require("../Icons/ChevronDownIcon.cjs");
12
13
  const CloseIcon = require("../Icons/CloseIcon.cjs");
14
+ const TickIcon = require("../Icons/TickIcon.cjs");
13
15
  function _interopNamespaceDefault(e) {
14
16
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
15
17
  if (e) {
@@ -82,6 +84,8 @@ const ChatInput = React__namespace.forwardRef(
82
84
  submitIcon,
83
85
  toolbarRight,
84
86
  selectOptions,
87
+ selectVariant = "menu",
88
+ selectMenuTitle,
85
89
  selectValue,
86
90
  selectDisabled = false,
87
91
  onSelectChange,
@@ -117,7 +121,7 @@ const ChatInput = React__namespace.forwardRef(
117
121
  }, [minRows, maxRows]);
118
122
  React__namespace.useEffect(() => {
119
123
  adjustHeight();
120
- }, [resolvedValue, adjustHeight]);
124
+ }, [adjustHeight]);
121
125
  const handleChange = (e) => {
122
126
  if (!isControlled) {
123
127
  setInternalValue(e.target.value);
@@ -162,7 +166,9 @@ const ChatInput = React__namespace.forwardRef(
162
166
  value: selectValue,
163
167
  onChange: onSelectChange,
164
168
  disabled: disabled || selectDisabled,
165
- selectedOption
169
+ selectedOption,
170
+ variant: selectVariant,
171
+ menuTitle: selectMenuTitle
166
172
  }
167
173
  ) : null);
168
174
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -217,7 +223,7 @@ const ChatInput = React__namespace.forwardRef(
217
223
  "aria-label": fileButtonAriaLabel,
218
224
  onClick: onFileClick,
219
225
  disabled,
220
- className: "sm:border sm:border-border-primary max-sm:-ml-2"
226
+ className: "max-sm:-ml-2 sm:border sm:border-border-primary"
221
227
  }
222
228
  ) }),
223
229
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
@@ -231,7 +237,7 @@ const ChatInput = React__namespace.forwardRef(
231
237
  "aria-label": submitAriaLabel,
232
238
  onClick: handleSubmit,
233
239
  disabled: !canSubmit,
234
- className: "disabled:bg-surface-secondary disabled:opacity-100 disabled:text-icons-primary"
240
+ className: "disabled:bg-surface-secondary disabled:text-icons-primary disabled:opacity-100"
235
241
  }
236
242
  )
237
243
  ] })
@@ -242,98 +248,128 @@ const ChatInput = React__namespace.forwardRef(
242
248
  }
243
249
  );
244
250
  ChatInput.displayName = "ChatInput";
245
- function InlineSelect({ options, value, onChange, disabled, selectedOption }) {
251
+ const SelectTriggerButton = React__namespace.forwardRef(
252
+ ({ open, selectedOption, fallbackLabel, className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
253
+ "button",
254
+ {
255
+ ref,
256
+ type: "button",
257
+ "aria-label": "Select model",
258
+ className: cn.cn(
259
+ "typography-description-12px-semibold text-content-primary",
260
+ "flex items-center gap-1 rounded-md px-2 py-2",
261
+ "hover:bg-neutral-alphas-50 focus-visible:shadow-focus-ring focus-visible:outline-none",
262
+ "disabled:cursor-not-allowed disabled:opacity-50",
263
+ "motion-safe:transition-colors",
264
+ open && "bg-neutral-alphas-50",
265
+ className
266
+ ),
267
+ ...props,
268
+ children: [
269
+ selectedOption?.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center [&>svg]:size-4", children: selectedOption.icon }),
270
+ selectedOption?.label ?? fallbackLabel ?? "Select",
271
+ /* @__PURE__ */ jsxRuntime.jsx(
272
+ ChevronDownIcon.ChevronDownIcon,
273
+ {
274
+ className: cn.cn("size-4 motion-safe:transition-transform", open && "rotate-180")
275
+ }
276
+ )
277
+ ]
278
+ }
279
+ )
280
+ );
281
+ SelectTriggerButton.displayName = "SelectTriggerButton";
282
+ function SelectedTick() {
283
+ return /* @__PURE__ */ jsxRuntime.jsx(TickIcon.TickIcon, { size: 16, className: "text-success-negative-content", "aria-hidden": "true" });
284
+ }
285
+ function InlineSelect({
286
+ options,
287
+ value,
288
+ onChange,
289
+ disabled,
290
+ selectedOption,
291
+ variant,
292
+ menuTitle
293
+ }) {
246
294
  const [open, setOpen] = React__namespace.useState(false);
247
- const containerRef = React__namespace.useRef(null);
248
- React__namespace.useEffect(() => {
249
- if (!open) return;
250
- const handleClick = (e) => {
251
- if (containerRef.current && !containerRef.current.contains(e.target)) {
252
- setOpen(false);
253
- }
254
- };
255
- document.addEventListener("mousedown", handleClick);
256
- return () => document.removeEventListener("mousedown", handleClick);
257
- }, [open]);
258
- React__namespace.useEffect(() => {
259
- if (!open) return;
260
- const handleKey = (e) => {
261
- if (e.key === "Escape") setOpen(false);
262
- };
263
- document.addEventListener("keydown", handleKey);
264
- return () => document.removeEventListener("keydown", handleKey);
265
- }, [open]);
266
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
267
- /* @__PURE__ */ jsxRuntime.jsxs(
268
- "button",
269
- {
270
- type: "button",
271
- role: "combobox",
272
- "aria-expanded": open,
273
- "aria-haspopup": "listbox",
274
- "aria-label": "Select model",
275
- disabled,
276
- onClick: () => setOpen((prev) => !prev),
277
- className: cn.cn(
278
- "typography-description-12px-semibold text-content-primary",
279
- "flex items-center gap-1 rounded-md px-2 py-2",
280
- "hover:bg-neutral-alphas-50 focus-visible:shadow-focus-ring focus-visible:outline-none",
281
- "disabled:cursor-not-allowed disabled:opacity-50",
282
- "motion-safe:transition-colors",
283
- open && "bg-neutral-alphas-50"
284
- ),
285
- children: [
286
- selectedOption?.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center [&>svg]:size-4", children: selectedOption.icon }),
287
- selectedOption?.label ?? options[0]?.label ?? "Select",
288
- /* @__PURE__ */ jsxRuntime.jsx(
289
- ChevronDownIcon.ChevronDownIcon,
295
+ const fallbackLabel = options[0]?.label;
296
+ const handleOpenChange = (next) => {
297
+ if (disabled && next) return;
298
+ setOpen(next);
299
+ };
300
+ const trigger = /* @__PURE__ */ jsxRuntime.jsx(
301
+ SelectTriggerButton,
302
+ {
303
+ open,
304
+ selectedOption,
305
+ fallbackLabel,
306
+ disabled
307
+ }
308
+ );
309
+ if (variant === "sheet") {
310
+ return /* @__PURE__ */ jsxRuntime.jsxs(Drawer.Drawer, { open, onOpenChange: handleOpenChange, children: [
311
+ /* @__PURE__ */ jsxRuntime.jsx(Drawer.DrawerTrigger, { asChild: true, children: trigger }),
312
+ /* @__PURE__ */ jsxRuntime.jsxs(Drawer.DrawerContent, { position: "bottom", variant: "sheet", children: [
313
+ /* @__PURE__ */ jsxRuntime.jsx(Drawer.DrawerHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(Drawer.DrawerTitle, { className: "typography-header-heading-xs", children: menuTitle ?? "Select an option" }) }),
314
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1 overflow-y-auto px-4 pb-4", role: "listbox", children: options.map((option) => {
315
+ const isSelected = option.value === value;
316
+ return /* @__PURE__ */ jsxRuntime.jsxs(
317
+ "button",
290
318
  {
291
- className: cn.cn("size-4 motion-safe:transition-transform", open && "rotate-180")
292
- }
293
- )
294
- ]
295
- }
296
- ),
297
- open && /* @__PURE__ */ jsxRuntime.jsx(
298
- "div",
299
- {
300
- role: "listbox",
301
- className: cn.cn(
302
- "absolute right-0 bottom-full z-10 mb-1 min-w-[180px]",
303
- "overflow-hidden rounded-xs border border-border-primary bg-surface-primary p-1.5 shadow-lg"
304
- ),
305
- children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsxs(
306
- "div",
307
- {
308
- role: "option",
309
- tabIndex: 0,
310
- "aria-selected": option.value === value,
311
- className: cn.cn(
312
- "typography-body-small-14px-regular flex cursor-pointer items-center gap-2 rounded-xs py-2.5 pr-2 pl-3",
313
- "text-content-primary hover:bg-neutral-alphas-50",
314
- "focus-visible:shadow-focus-ring focus-visible:outline-none"
315
- ),
316
- onClick: () => {
317
- onChange?.(option.value);
318
- setOpen(false);
319
- },
320
- onKeyDown: (e) => {
321
- if (e.key === "Enter" || e.key === " ") {
322
- e.preventDefault();
319
+ type: "button",
320
+ role: "option",
321
+ "aria-selected": isSelected,
322
+ onClick: () => {
323
323
  onChange?.(option.value);
324
324
  setOpen(false);
325
- }
325
+ },
326
+ className: cn.cn(
327
+ "flex w-full items-start gap-2 rounded-sm px-3 py-2 text-left outline-none",
328
+ "focus-visible:shadow-focus-ring",
329
+ isSelected ? "typography-body-default-16px-semibold bg-buttons-primary-default text-content-primary-inverted" : "typography-body-default-16px-regular text-content-primary hover:bg-neutral-alphas-50"
330
+ ),
331
+ children: [
332
+ option.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center pt-1 [&>svg]:size-4", children: option.icon }),
333
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
334
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: option.menuLabel ?? option.label }),
335
+ option.description && /* @__PURE__ */ jsxRuntime.jsx(
336
+ "span",
337
+ {
338
+ className: cn.cn(
339
+ "typography-body-small-14px-regular truncate",
340
+ isSelected ? "text-content-primary-inverted" : "text-content-secondary"
341
+ ),
342
+ children: option.description
343
+ }
344
+ )
345
+ ] }),
346
+ isSelected && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center pt-1", children: /* @__PURE__ */ jsxRuntime.jsx(SelectedTick, {}) })
347
+ ]
326
348
  },
327
- children: [
328
- option.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center [&>svg]:size-4", children: option.icon }),
329
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: option.label }),
330
- option.value === value && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto flex size-4 shrink-0 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(CheckIcon.CheckIcon, { className: "size-4 text-content-primary", "aria-hidden": "true" }) })
331
- ]
332
- },
333
- option.value
334
- ))
335
- }
336
- )
349
+ option.value
350
+ );
351
+ }) })
352
+ ] })
353
+ ] });
354
+ }
355
+ return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu.DropdownMenu, { open, onOpenChange: handleOpenChange, children: [
356
+ /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu.DropdownMenuTrigger, { asChild: true, children: trigger }),
357
+ /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu.DropdownMenuContent, { side: "top", align: "end", className: "min-w-[244px]", children: options.map((option) => {
358
+ const isSelected = option.value === value;
359
+ return /* @__PURE__ */ jsxRuntime.jsx(
360
+ DropdownMenu.DropdownMenuItem,
361
+ {
362
+ size: "40",
363
+ selected: isSelected,
364
+ description: option.description,
365
+ leadingIcon: option.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex size-4 items-center [&>svg]:size-4", children: option.icon }) : void 0,
366
+ trailingIcon: isSelected ? /* @__PURE__ */ jsxRuntime.jsx(SelectedTick, {}) : void 0,
367
+ onSelect: () => onChange?.(option.value),
368
+ children: option.menuLabel ?? option.label
369
+ },
370
+ option.value
371
+ );
372
+ }) })
337
373
  ] });
338
374
  }
339
375
  exports.ChatInput = ChatInput;
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInput.cjs","sources":["../../../../src/components/ChatInput/ChatInput.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { AddIcon } from \"../Icons/AddIcon\";\nimport { ArrowUpIcon } from \"../Icons/ArrowUpIcon\";\nimport { CheckIcon } from \"../Icons/CheckIcon\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** A single image thumbnail in the built-in attachment strip. */\nexport interface ChatInputAttachmentItem {\n /** Stable id passed to {@link ChatInputProps.onAttachmentRemove} and used as React `key`. */\n id: string;\n /** Image URL for the thumbnail. */\n src: string;\n /** Optional value passed to the remove control `aria-label`. */\n ariaLabel?: string;\n}\n\n/** A single option for the inline model/dropdown selector. */\nexport interface ChatInputSelectOption {\n /** Unique value for this option. */\n value: string;\n /** Display label. */\n label: string;\n /** Optional icon rendered to the left of the label. */\n icon?: React.ReactNode;\n}\n\n/**\n * Props for {@link ChatInput}. Standard textarea HTML attributes are forwarded to the inner\n * `<textarea>` except `className` (applied to the outer container), `rows` (use `minRows`), and\n * `onSubmit` (replaced by the chat message submit callback).\n */\nexport interface ChatInputProps\n extends Omit<\n React.TextareaHTMLAttributes<HTMLTextAreaElement>,\n \"className\" | \"rows\" | \"onSubmit\"\n > {\n /** Minimum number of visible rows. @default 1 */\n minRows?: number;\n /** Maximum number of visible rows before scrolling. @default 6 */\n maxRows?: number;\n /** Whether a submission is in progress (disables submit, shows visual feedback). @default false */\n loading?: boolean;\n /**\n * Callback fired when the user submits (clicks the send button or presses Enter without Shift).\n * Receives the current trimmed text value.\n */\n onSubmit?: (value: string) => void;\n /**\n * When `true`, renders an \"attach file\" button in the bottom-left toolbar.\n * @default false\n */\n showFileButton?: boolean;\n /** Callback fired when the attach-file button is clicked. Only relevant when `showFileButton` is `true`. */\n onFileClick?: () => void;\n /** Accessible label for the attach-file button. @default \"Attach file\" */\n fileButtonAriaLabel?: string;\n /** Accessible label for the submit button. @default \"Send message\" */\n submitAriaLabel?: string;\n /** Icon element for the submit button. @default `<ArrowUpIcon />` */\n submitIcon?: React.ReactNode;\n /**\n * Optional content rendered in the bottom-right toolbar, to the left of the submit button.\n * When provided, takes precedence over the built-in `selectOptions` dropdown.\n */\n toolbarRight?: React.ReactNode;\n /**\n * Options for the built-in inline dropdown selector (e.g. model picker).\n * Ignored when `toolbarRight` is provided.\n */\n selectOptions?: ChatInputSelectOption[];\n /** Currently selected value for the built-in dropdown. Should match one of `selectOptions[].value`. */\n selectValue?: string;\n /** When `true`, disables only the built-in dropdown selector. @default false */\n selectDisabled?: boolean;\n /** Callback fired when the user picks a different dropdown option. */\n onSelectChange?: (value: string) => void;\n /**\n * Image attachments shown in the built-in thumbnail strip. Ignored when {@link ChatInputProps.attachmentPreviews}\n * is provided (including `null`).\n */\n attachments?: ChatInputAttachmentItem[];\n /**\n * Called when the user removes a built-in thumbnail. The remove button is disabled when this is\n * omitted or the input is {@link ChatInputProps.disabled}.\n */\n onAttachmentRemove?: (id: string) => void;\n /**\n * Replaces the built-in attachment strip entirely. When set to any value other than `undefined`\n * (including `null` or `[]`), {@link ChatInputProps.attachments} is ignored.\n */\n attachmentPreviews?: React.ReactNode;\n /** Additional className applied to the outermost container. */\n className?: string;\n}\n\nconst LINE_HEIGHT = 18;\nconst TEXTAREA_PY = 12;\n\nfunction calculateHeight(rows: number): number {\n return LINE_HEIGHT * rows + TEXTAREA_PY * 2;\n}\n\ninterface ChatInputDefaultAttachmentThumbnailsProps {\n attachments: ChatInputAttachmentItem[];\n onAttachmentRemove?: (id: string) => void;\n disabled?: boolean;\n}\n\nfunction ChatInputDefaultAttachmentThumbnails({\n attachments,\n onAttachmentRemove,\n disabled = false,\n}: ChatInputDefaultAttachmentThumbnailsProps) {\n return attachments.map((item) => (\n <div\n key={item.id}\n className=\"relative size-16 shrink-0 overflow-hidden rounded-sm border border-neutral-200 bg-background-secondary\"\n >\n <img src={item.src} alt=\"\" className=\"size-full object-cover\" />\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={item.ariaLabel ? `Remove ${item.ariaLabel}` : \"Remove attachment\"}\n icon={<CloseIcon className=\"!size-3\" />}\n disabled={disabled || !onAttachmentRemove}\n onClick={() => onAttachmentRemove?.(item.id)}\n className=\"absolute top-0.5 right-0.5 size-5 bg-neutral-900/40 p-1 text-white hover:bg-neutral-900/55\"\n />\n </div>\n ));\n}\n\n/**\n * A chat-style multi-line input with an integrated toolbar containing an\n * optional file-attach button, optional right-side controls (e.g. a model\n * selector), and a submit button — all inside a single bordered container.\n *\n * Designed to behave like modern AI chat inputs: auto-grows with content,\n * submits on Enter (Shift+Enter for newlines), and keeps controls inline.\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Type a message...\"\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Ask the agent...\"\n * showFileButton\n * onFileClick={() => openFilePicker()}\n * selectOptions={[\n * { value: \"fanvue-ai\", label: \"Fanvue AI\", icon: <AIIcon className=\"size-4\" /> },\n * { value: \"example\", label: \"Example\", icon: <BulbIcon className=\"size-4\" /> },\n * ]}\n * selectValue=\"fanvue-ai\"\n * onSelectChange={(v) => setModel(v)}\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachments={files}\n * onAttachmentRemove={(id) => setFiles((prev) => prev.filter((f) => f.id !== id))}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachmentPreviews={<CustomVideoStrip items={items} />}\n * />\n * ```\n */\nexport const ChatInput = React.forwardRef<HTMLTextAreaElement, ChatInputProps>(\n (\n {\n className,\n minRows = 1,\n maxRows = 6,\n disabled = false,\n loading = false,\n value,\n defaultValue,\n placeholder,\n maxLength,\n \"aria-label\": ariaLabel,\n onChange,\n onKeyDown,\n onSubmit,\n showFileButton = false,\n onFileClick,\n fileButtonAriaLabel = \"Attach file\",\n submitAriaLabel = \"Send message\",\n submitIcon,\n toolbarRight,\n selectOptions,\n selectValue,\n selectDisabled = false,\n onSelectChange,\n attachments,\n onAttachmentRemove,\n attachmentPreviews,\n style,\n ...textareaProps\n },\n ref,\n ) => {\n const internalRef = React.useRef<HTMLTextAreaElement>(null);\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? \"\");\n const resolvedValue = value !== undefined ? value : internalValue;\n const isControlled = value !== undefined;\n\n const mergedRef = React.useCallback(\n (node: HTMLTextAreaElement | null) => {\n (internalRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n }\n },\n [ref],\n );\n\n const adjustHeight = React.useCallback(() => {\n const textarea = internalRef.current;\n if (!textarea) return;\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n textarea.style.height = `${minHeight}px`;\n const desired = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);\n textarea.style.height = `${desired}px`;\n }, [minRows, maxRows]);\n\n React.useEffect(() => {\n adjustHeight();\n }, [resolvedValue, adjustHeight]);\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n if (!isControlled) {\n setInternalValue(e.target.value);\n }\n onChange?.(e);\n };\n\n const canSubmit = !!String(resolvedValue).trim() && !disabled && !loading;\n\n const handleSubmit = () => {\n const text = String(resolvedValue).trim();\n if (!text || !canSubmit) return;\n onSubmit?.(text);\n if (!isControlled) {\n setInternalValue(\"\");\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n onKeyDown?.(e);\n };\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n const useCustomAttachmentPreviews = attachmentPreviews !== undefined;\n const customAttachmentStrip = useCustomAttachmentPreviews ? attachmentPreviews : null;\n const defaultAttachmentStrip =\n !useCustomAttachmentPreviews && !!attachments?.length ? (\n <ChatInputDefaultAttachmentThumbnails\n attachments={attachments ?? []}\n disabled={disabled}\n onAttachmentRemove={onAttachmentRemove}\n />\n ) : null;\n const resolvedAttachmentStrip = customAttachmentStrip ?? defaultAttachmentStrip;\n const hasAttachmentStrip = resolvedAttachmentStrip != null;\n\n const selectedOption =\n selectOptions?.find((o) => o.value === selectValue) ?? selectOptions?.[0];\n const resolvedToolbarRight =\n toolbarRight ??\n (selectOptions && selectOptions.length > 0 ? (\n <InlineSelect\n options={selectOptions}\n value={selectValue}\n onChange={onSelectChange}\n disabled={disabled || selectDisabled}\n selectedOption={selectedOption}\n />\n ) : null);\n\n return (\n <div\n className={cn(\n \"relative flex flex-col gap-6 rounded-lg border border-border-primary bg-surface-primary\",\n \"has-focus-visible:outline-none\",\n \"motion-safe:transition-colors\",\n disabled && \"opacity-50\",\n className,\n )}\n >\n <div className=\"flex flex-col\">\n {hasAttachmentStrip ? (\n <div className=\"flex gap-2 overflow-x-auto px-4 pt-4 pb-2 [scrollbar-width:thin]\">\n {resolvedAttachmentStrip}\n </div>\n ) : null}\n <textarea\n {...textareaProps}\n ref={mergedRef}\n value={isControlled ? value : internalValue}\n placeholder={placeholder}\n maxLength={maxLength}\n disabled={disabled}\n aria-label={ariaLabel ?? placeholder}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n rows={minRows}\n className={cn(\n \"w-full resize-none bg-transparent px-4\",\n hasAttachmentStrip ? \"pt-0\" : \"pt-4\",\n \"typography-body-small-14px-regular text-content-primary\",\n \"placeholder:text-content-tertiary\",\n \"focus:outline-none disabled:cursor-not-allowed\",\n \"overflow-y-auto\",\n )}\n style={{\n minHeight: `${minHeight}px`,\n maxHeight: `${maxHeight}px`,\n ...style,\n }}\n />\n </div>\n\n <div className=\"flex items-center justify-between gap-2 px-4 pb-4\">\n <div className=\"flex items-center gap-1\">\n {showFileButton && (\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<AddIcon />}\n aria-label={fileButtonAriaLabel}\n onClick={onFileClick}\n disabled={disabled}\n className=\"sm:border sm:border-border-primary max-sm:-ml-2\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-1\">\n {resolvedToolbarRight}\n <IconButton\n variant=\"primary\"\n size=\"32\"\n icon={submitIcon ?? <ArrowUpIcon />}\n aria-label={submitAriaLabel}\n onClick={handleSubmit}\n disabled={!canSubmit}\n className=\"disabled:bg-surface-secondary disabled:opacity-100 disabled:text-icons-primary\"\n />\n </div>\n </div>\n </div>\n );\n },\n);\n\nChatInput.displayName = \"ChatInput\";\n\ninterface InlineSelectProps {\n options: ChatInputSelectOption[];\n value?: string;\n onChange?: (value: string) => void;\n disabled?: boolean;\n selectedOption?: ChatInputSelectOption;\n}\n\nfunction InlineSelect({ options, value, onChange, disabled, selectedOption }: InlineSelectProps) {\n const [open, setOpen] = React.useState(false);\n const containerRef = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n const handleClick = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handleClick);\n return () => document.removeEventListener(\"mousedown\", handleClick);\n }, [open]);\n\n React.useEffect(() => {\n if (!open) return;\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"keydown\", handleKey);\n return () => document.removeEventListener(\"keydown\", handleKey);\n }, [open]);\n\n return (\n <div ref={containerRef} className=\"relative\">\n <button\n type=\"button\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-label=\"Select model\"\n disabled={disabled}\n onClick={() => setOpen((prev) => !prev)}\n className={cn(\n \"typography-description-12px-semibold text-content-primary\",\n \"flex items-center gap-1 rounded-md px-2 py-2\",\n \"hover:bg-neutral-alphas-50 focus-visible:shadow-focus-ring focus-visible:outline-none\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"motion-safe:transition-colors\",\n open && \"bg-neutral-alphas-50\",\n )}\n >\n {selectedOption?.icon && (\n <span className=\"flex shrink-0 items-center [&>svg]:size-4\">{selectedOption.icon}</span>\n )}\n {selectedOption?.label ?? options[0]?.label ?? \"Select\"}\n <ChevronDownIcon\n className={cn(\"size-4 motion-safe:transition-transform\", open && \"rotate-180\")}\n />\n </button>\n\n {open && (\n <div\n role=\"listbox\"\n className={cn(\n \"absolute right-0 bottom-full z-10 mb-1 min-w-[180px]\",\n \"overflow-hidden rounded-xs border border-border-primary bg-surface-primary p-1.5 shadow-lg\",\n )}\n >\n {options.map((option) => (\n <div\n key={option.value}\n role=\"option\"\n tabIndex={0}\n aria-selected={option.value === value}\n className={cn(\n \"typography-body-small-14px-regular flex cursor-pointer items-center gap-2 rounded-xs py-2.5 pr-2 pl-3\",\n \"text-content-primary hover:bg-neutral-alphas-50\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n )}\n onClick={() => {\n onChange?.(option.value);\n setOpen(false);\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onChange?.(option.value);\n setOpen(false);\n }\n }}\n >\n {option.icon && (\n <span className=\"flex shrink-0 items-center [&>svg]:size-4\">{option.icon}</span>\n )}\n <span className=\"min-w-0 flex-1 truncate\">{option.label}</span>\n {option.value === value && (\n <span className=\"ml-auto flex size-4 shrink-0 items-center justify-center\">\n <CheckIcon className=\"size-4 text-content-primary\" aria-hidden=\"true\" />\n </span>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":["jsxs","jsx","IconButton","CloseIcon","React","minHeight","maxHeight","cn","AddIcon","ArrowUpIcon","ChevronDownIcon","CheckIcon"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkGA,MAAM,cAAc;AACpB,MAAM,cAAc;AAEpB,SAAS,gBAAgB,MAAsB;AAC7C,SAAO,cAAc,OAAO,cAAc;AAC5C;AAQA,SAAS,qCAAqC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAA8C;AAC5C,SAAO,YAAY,IAAI,CAAC,SACtBA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MAEC,WAAU;AAAA,MAEV,UAAA;AAAA,QAAAC,+BAAC,SAAI,KAAK,KAAK,KAAK,KAAI,IAAG,WAAU,0BAAyB;AAAA,QAC9DA,2BAAAA;AAAAA,UAACC,WAAAA;AAAAA,UAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,cAAY,KAAK,YAAY,UAAU,KAAK,SAAS,KAAK;AAAA,YAC1D,MAAMD,2BAAAA,IAACE,UAAAA,WAAA,EAAU,WAAU,UAAA,CAAU;AAAA,YACrC,UAAU,YAAY,CAAC;AAAA,YACvB,SAAS,MAAM,qBAAqB,KAAK,EAAE;AAAA,YAC3C,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,MACZ;AAAA,IAAA;AAAA,IAZK,KAAK;AAAA,EAAA,CAcb;AACH;AAqDO,MAAM,YAAYC,iBAAM;AAAA,EAC7B,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA,sBAAsB;AAAA,IACtB,kBAAkB;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,cAAcA,iBAAM,OAA4B,IAAI;AAC1D,UAAM,CAAC,eAAe,gBAAgB,IAAIA,iBAAM,SAAS,gBAAgB,EAAE;AAC3E,UAAM,gBAAgB,UAAU,SAAY,QAAQ;AACpD,UAAM,eAAe,UAAU;AAE/B,UAAM,YAAYA,iBAAM;AAAA,MACtB,CAAC,SAAqC;AACnC,oBAAmE,UAAU;AAC9E,YAAI,OAAO,QAAQ,YAAY;AAC7B,cAAI,IAAI;AAAA,QACV,WAAW,KAAK;AACb,cAA2D,UAAU;AAAA,QACxE;AAAA,MACF;AAAA,MACA,CAAC,GAAG;AAAA,IAAA;AAGN,UAAM,eAAeA,iBAAM,YAAY,MAAM;AAC3C,YAAM,WAAW,YAAY;AAC7B,UAAI,CAAC,SAAU;AAEf,YAAMC,aAAY,gBAAgB,OAAO;AACzC,YAAMC,aAAY,gBAAgB,OAAO;AAEzC,eAAS,MAAM,SAAS,GAAGD,UAAS;AACpC,YAAM,UAAU,KAAK,IAAI,KAAK,IAAI,SAAS,cAAcA,UAAS,GAAGC,UAAS;AAC9E,eAAS,MAAM,SAAS,GAAG,OAAO;AAAA,IACpC,GAAG,CAAC,SAAS,OAAO,CAAC;AAErBF,qBAAM,UAAU,MAAM;AACpB,mBAAA;AAAA,IACF,GAAG,CAAC,eAAe,YAAY,CAAC;AAEhC,UAAM,eAAe,CAAC,MAA8C;AAClE,UAAI,CAAC,cAAc;AACjB,yBAAiB,EAAE,OAAO,KAAK;AAAA,MACjC;AACA,iBAAW,CAAC;AAAA,IACd;AAEA,UAAM,YAAY,CAAC,CAAC,OAAO,aAAa,EAAE,KAAA,KAAU,CAAC,YAAY,CAAC;AAElE,UAAM,eAAe,MAAM;AACzB,YAAM,OAAO,OAAO,aAAa,EAAE,KAAA;AACnC,UAAI,CAAC,QAAQ,CAAC,UAAW;AACzB,iBAAW,IAAI;AACf,UAAI,CAAC,cAAc;AACjB,yBAAiB,EAAE;AAAA,MACrB;AAAA,IACF;AAEA,UAAM,gBAAgB,CAAC,MAAgD;AACrE,UAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;AACpC,UAAE,eAAA;AACF,qBAAA;AAAA,MACF;AACA,kBAAY,CAAC;AAAA,IACf;AAEA,UAAM,YAAY,gBAAgB,OAAO;AACzC,UAAM,YAAY,gBAAgB,OAAO;AAEzC,UAAM,8BAA8B,uBAAuB;AAC3D,UAAM,wBAAwB,8BAA8B,qBAAqB;AACjF,UAAM,yBACJ,CAAC,+BAA+B,CAAC,CAAC,aAAa,SAC7CH,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,aAAa,eAAe,CAAA;AAAA,QAC5B;AAAA,QACA;AAAA,MAAA;AAAA,IAAA,IAEA;AACN,UAAM,0BAA0B,yBAAyB;AACzD,UAAM,qBAAqB,2BAA2B;AAEtD,UAAM,iBACJ,eAAe,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW,KAAK,gBAAgB,CAAC;AAC1E,UAAM,uBACJ,iBACC,iBAAiB,cAAc,SAAS,IACvCA,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAS;AAAA,QACT,OAAO;AAAA,QACP,UAAU;AAAA,QACV,UAAU,YAAY;AAAA,QACtB;AAAA,MAAA;AAAA,IAAA,IAEA;AAEN,WACED,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAWO,GAAAA;AAAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAGF,UAAA;AAAA,UAAAP,2BAAAA,KAAC,OAAA,EAAI,WAAU,iBACZ,UAAA;AAAA,YAAA,qBACCC,2BAAAA,IAAC,OAAA,EAAI,WAAU,oEACZ,mCACH,IACE;AAAA,YACJA,2BAAAA;AAAAA,cAAC;AAAA,cAAA;AAAA,gBACE,GAAG;AAAA,gBACJ,KAAK;AAAA,gBACL,OAAO,eAAe,QAAQ;AAAA,gBAC9B;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,cAAY,aAAa;AAAA,gBACzB,UAAU;AAAA,gBACV,WAAW;AAAA,gBACX,MAAM;AAAA,gBACN,WAAWM,GAAAA;AAAAA,kBACT;AAAA,kBACA,qBAAqB,SAAS;AAAA,kBAC9B;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA,gBAAA;AAAA,gBAEF,OAAO;AAAA,kBACL,WAAW,GAAG,SAAS;AAAA,kBACvB,WAAW,GAAG,SAAS;AAAA,kBACvB,GAAG;AAAA,gBAAA;AAAA,cACL;AAAA,YAAA;AAAA,UACF,GACF;AAAA,UAEAP,2BAAAA,KAAC,OAAA,EAAI,WAAU,qDACb,UAAA;AAAA,YAAAC,2BAAAA,IAAC,OAAA,EAAI,WAAU,2BACZ,UAAA,kBACCA,2BAAAA;AAAAA,cAACC,WAAAA;AAAAA,cAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,qCAAOM,QAAAA,SAAA,EAAQ;AAAA,gBACf,cAAY;AAAA,gBACZ,SAAS;AAAA,gBACT;AAAA,gBACA,WAAU;AAAA,cAAA;AAAA,YAAA,GAGhB;AAAA,YAEAR,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,cAAA;AAAA,cACDC,2BAAAA;AAAAA,gBAACC,WAAAA;AAAAA,gBAAA;AAAA,kBACC,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,MAAM,cAAcD,+BAACQ,YAAAA,aAAA,CAAA,CAAY;AAAA,kBACjC,cAAY;AAAA,kBACZ,SAAS;AAAA,kBACT,UAAU,CAAC;AAAA,kBACX,WAAU;AAAA,gBAAA;AAAA,cAAA;AAAA,YACZ,EAAA,CACF;AAAA,UAAA,EAAA,CACF;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACF;AAEA,UAAU,cAAc;AAUxB,SAAS,aAAa,EAAE,SAAS,OAAO,UAAU,UAAU,kBAAqC;AAC/F,QAAM,CAAC,MAAM,OAAO,IAAIL,iBAAM,SAAS,KAAK;AAC5C,QAAM,eAAeA,iBAAM,OAAuB,IAAI;AAEtDA,mBAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,UAAM,cAAc,CAAC,MAAkB;AACrC,UAAI,aAAa,WAAW,CAAC,aAAa,QAAQ,SAAS,EAAE,MAAc,GAAG;AAC5E,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF;AACA,aAAS,iBAAiB,aAAa,WAAW;AAClD,WAAO,MAAM,SAAS,oBAAoB,aAAa,WAAW;AAAA,EACpE,GAAG,CAAC,IAAI,CAAC;AAETA,mBAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,SAAU,SAAQ,KAAK;AAAA,IACvC;AACA,aAAS,iBAAiB,WAAW,SAAS;AAC9C,WAAO,MAAM,SAAS,oBAAoB,WAAW,SAAS;AAAA,EAChE,GAAG,CAAC,IAAI,CAAC;AAET,SACEJ,2BAAAA,KAAC,OAAA,EAAI,KAAK,cAAc,WAAU,YAChC,UAAA;AAAA,IAAAA,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,iBAAe;AAAA,QACf,iBAAc;AAAA,QACd,cAAW;AAAA,QACX;AAAA,QACA,SAAS,MAAM,QAAQ,CAAC,SAAS,CAAC,IAAI;AAAA,QACtC,WAAWO,GAAAA;AAAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,QAAA;AAAA,QAGT,UAAA;AAAA,UAAA,gBAAgB,QACfN,2BAAAA,IAAC,QAAA,EAAK,WAAU,6CAA6C,yBAAe,MAAK;AAAA,UAElF,gBAAgB,SAAS,QAAQ,CAAC,GAAG,SAAS;AAAA,UAC/CA,2BAAAA;AAAAA,YAACS,gBAAAA;AAAAA,YAAA;AAAA,cACC,WAAWH,GAAAA,GAAG,2CAA2C,QAAQ,YAAY;AAAA,YAAA;AAAA,UAAA;AAAA,QAC/E;AAAA,MAAA;AAAA,IAAA;AAAA,IAGD,QACCN,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAWM,GAAAA;AAAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAGD,UAAA,QAAQ,IAAI,CAAC,WACZP,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YAEC,MAAK;AAAA,YACL,UAAU;AAAA,YACV,iBAAe,OAAO,UAAU;AAAA,YAChC,WAAWO,GAAAA;AAAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,YAEF,SAAS,MAAM;AACb,yBAAW,OAAO,KAAK;AACvB,sBAAQ,KAAK;AAAA,YACf;AAAA,YACA,WAAW,CAAC,MAAM;AAChB,kBAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,kBAAE,eAAA;AACF,2BAAW,OAAO,KAAK;AACvB,wBAAQ,KAAK;AAAA,cACf;AAAA,YACF;AAAA,YAEC,UAAA;AAAA,cAAA,OAAO,QACNN,2BAAAA,IAAC,QAAA,EAAK,WAAU,6CAA6C,iBAAO,MAAK;AAAA,cAE3EA,2BAAAA,IAAC,QAAA,EAAK,WAAU,2BAA2B,iBAAO,OAAM;AAAA,cACvD,OAAO,UAAU,SAChBA,2BAAAA,IAAC,QAAA,EAAK,WAAU,4DACd,UAAAA,2BAAAA,IAACU,UAAAA,WAAA,EAAU,WAAU,+BAA8B,eAAY,QAAO,EAAA,CACxE;AAAA,YAAA;AAAA,UAAA;AAAA,UA5BG,OAAO;AAAA,QAAA,CA+Bf;AAAA,MAAA;AAAA,IAAA;AAAA,EACH,GAEJ;AAEJ;;"}
1
+ {"version":3,"file":"ChatInput.cjs","sources":["../../../../src/components/ChatInput/ChatInput.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from \"../Drawer/Drawer\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"../DropdownMenu/DropdownMenu\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { AddIcon } from \"../Icons/AddIcon\";\nimport { ArrowUpIcon } from \"../Icons/ArrowUpIcon\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\nimport { TickIcon } from \"../Icons/TickIcon\";\n\n/** A single image thumbnail in the built-in attachment strip. */\nexport interface ChatInputAttachmentItem {\n /** Stable id passed to {@link ChatInputProps.onAttachmentRemove} and used as React `key`. */\n id: string;\n /** Image URL for the thumbnail. */\n src: string;\n /** Optional value passed to the remove control `aria-label`. */\n ariaLabel?: string;\n}\n\n/** A single option for the inline model/dropdown selector. */\nexport interface ChatInputSelectOption {\n /** Unique value for this option. */\n value: string;\n /** Short label shown on the collapsed trigger button (e.g. \"Sonnet 4.6\"). */\n label: string;\n /**\n * Optional longer title shown on the option's row inside the open menu/sheet\n * (e.g. \"Claude Sonnet 4.6\"). Falls back to {@link label} when omitted.\n */\n menuLabel?: string;\n /** Optional secondary text shown below the label in the dropdown menu. */\n description?: string;\n /** Optional icon rendered to the left of the label. */\n icon?: React.ReactNode;\n}\n\n/**\n * Props for {@link ChatInput}. Standard textarea HTML attributes are forwarded to the inner\n * `<textarea>` except `className` (applied to the outer container), `rows` (use `minRows`), and\n * `onSubmit` (replaced by the chat message submit callback).\n */\nexport interface ChatInputProps\n extends Omit<\n React.TextareaHTMLAttributes<HTMLTextAreaElement>,\n \"className\" | \"rows\" | \"onSubmit\"\n > {\n /** Minimum number of visible rows. @default 1 */\n minRows?: number;\n /** Maximum number of visible rows before scrolling. @default 6 */\n maxRows?: number;\n /** Whether a submission is in progress (disables submit, shows visual feedback). @default false */\n loading?: boolean;\n /**\n * Callback fired when the user submits (clicks the send button or presses Enter without Shift).\n * Receives the current trimmed text value.\n */\n onSubmit?: (value: string) => void;\n /**\n * When `true`, renders an \"attach file\" button in the bottom-left toolbar.\n * @default false\n */\n showFileButton?: boolean;\n /** Callback fired when the attach-file button is clicked. Only relevant when `showFileButton` is `true`. */\n onFileClick?: () => void;\n /** Accessible label for the attach-file button. @default \"Attach file\" */\n fileButtonAriaLabel?: string;\n /** Accessible label for the submit button. @default \"Send message\" */\n submitAriaLabel?: string;\n /** Icon element for the submit button. @default `<ArrowUpIcon />` */\n submitIcon?: React.ReactNode;\n /**\n * Optional content rendered in the bottom-right toolbar, to the left of the submit button.\n * When provided, takes precedence over the built-in `selectOptions` dropdown.\n */\n toolbarRight?: React.ReactNode;\n /**\n * Options for the built-in inline dropdown selector (e.g. model picker).\n * Ignored when `toolbarRight` is provided.\n */\n selectOptions?: ChatInputSelectOption[];\n /**\n * How the built-in selector presents its options:\n * - `\"menu\"` (default) — a dropdown anchored to the trigger, for pointer/desktop.\n * - `\"sheet\"` — a bottom sheet, for mobile/touch viewports.\n *\n * The viewport decision belongs to the consumer (it owns the breakpoint\n * source of truth), so pass e.g. `selectVariant={isDesktop ? \"menu\" : \"sheet\"}`.\n * @default \"menu\"\n */\n selectVariant?: \"menu\" | \"sheet\";\n /**\n * Title shown at the top of the `\"sheet\"` variant of the built-in selector\n * (e.g. \"Switch AI Model\"). @default \"Select an option\"\n */\n selectMenuTitle?: string;\n /** Currently selected value for the built-in dropdown. Should match one of `selectOptions[].value`. */\n selectValue?: string;\n /** When `true`, disables only the built-in dropdown selector. @default false */\n selectDisabled?: boolean;\n /** Callback fired when the user picks a different dropdown option. */\n onSelectChange?: (value: string) => void;\n /**\n * Image attachments shown in the built-in thumbnail strip. Ignored when {@link ChatInputProps.attachmentPreviews}\n * is provided (including `null`).\n */\n attachments?: ChatInputAttachmentItem[];\n /**\n * Called when the user removes a built-in thumbnail. The remove button is disabled when this is\n * omitted or the input is {@link ChatInputProps.disabled}.\n */\n onAttachmentRemove?: (id: string) => void;\n /**\n * Replaces the built-in attachment strip entirely. When set to any value other than `undefined`\n * (including `null` or `[]`), {@link ChatInputProps.attachments} is ignored.\n */\n attachmentPreviews?: React.ReactNode;\n /** Additional className applied to the outermost container. */\n className?: string;\n}\n\nconst LINE_HEIGHT = 18;\nconst TEXTAREA_PY = 12;\n\nfunction calculateHeight(rows: number): number {\n return LINE_HEIGHT * rows + TEXTAREA_PY * 2;\n}\n\ninterface ChatInputDefaultAttachmentThumbnailsProps {\n attachments: ChatInputAttachmentItem[];\n onAttachmentRemove?: (id: string) => void;\n disabled?: boolean;\n}\n\nfunction ChatInputDefaultAttachmentThumbnails({\n attachments,\n onAttachmentRemove,\n disabled = false,\n}: ChatInputDefaultAttachmentThumbnailsProps) {\n return attachments.map((item) => (\n <div\n key={item.id}\n className=\"relative size-16 shrink-0 overflow-hidden rounded-sm border border-neutral-200 bg-background-secondary\"\n >\n <img src={item.src} alt=\"\" className=\"size-full object-cover\" />\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={item.ariaLabel ? `Remove ${item.ariaLabel}` : \"Remove attachment\"}\n icon={<CloseIcon className=\"!size-3\" />}\n disabled={disabled || !onAttachmentRemove}\n onClick={() => onAttachmentRemove?.(item.id)}\n className=\"absolute top-0.5 right-0.5 size-5 bg-neutral-900/40 p-1 text-white hover:bg-neutral-900/55\"\n />\n </div>\n ));\n}\n\n/**\n * A chat-style multi-line input with an integrated toolbar containing an\n * optional file-attach button, optional right-side controls (e.g. a model\n * selector), and a submit button — all inside a single bordered container.\n *\n * Designed to behave like modern AI chat inputs: auto-grows with content,\n * submits on Enter (Shift+Enter for newlines), and keeps controls inline.\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Type a message...\"\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Ask the agent...\"\n * showFileButton\n * onFileClick={() => openFilePicker()}\n * selectOptions={[\n * { value: \"fanvue-ai\", label: \"Fanvue AI\", icon: <AIIcon className=\"size-4\" /> },\n * { value: \"example\", label: \"Example\", icon: <BulbIcon className=\"size-4\" /> },\n * ]}\n * selectValue=\"fanvue-ai\"\n * onSelectChange={(v) => setModel(v)}\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachments={files}\n * onAttachmentRemove={(id) => setFiles((prev) => prev.filter((f) => f.id !== id))}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachmentPreviews={<CustomVideoStrip items={items} />}\n * />\n * ```\n */\nexport const ChatInput = React.forwardRef<HTMLTextAreaElement, ChatInputProps>(\n (\n {\n className,\n minRows = 1,\n maxRows = 6,\n disabled = false,\n loading = false,\n value,\n defaultValue,\n placeholder,\n maxLength,\n \"aria-label\": ariaLabel,\n onChange,\n onKeyDown,\n onSubmit,\n showFileButton = false,\n onFileClick,\n fileButtonAriaLabel = \"Attach file\",\n submitAriaLabel = \"Send message\",\n submitIcon,\n toolbarRight,\n selectOptions,\n selectVariant = \"menu\",\n selectMenuTitle,\n selectValue,\n selectDisabled = false,\n onSelectChange,\n attachments,\n onAttachmentRemove,\n attachmentPreviews,\n style,\n ...textareaProps\n },\n ref,\n ) => {\n const internalRef = React.useRef<HTMLTextAreaElement>(null);\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? \"\");\n const resolvedValue = value !== undefined ? value : internalValue;\n const isControlled = value !== undefined;\n\n const mergedRef = React.useCallback(\n (node: HTMLTextAreaElement | null) => {\n (internalRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n }\n },\n [ref],\n );\n\n const adjustHeight = React.useCallback(() => {\n const textarea = internalRef.current;\n if (!textarea) return;\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n textarea.style.height = `${minHeight}px`;\n const desired = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);\n textarea.style.height = `${desired}px`;\n }, [minRows, maxRows]);\n\n React.useEffect(() => {\n adjustHeight();\n }, [adjustHeight]);\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n if (!isControlled) {\n setInternalValue(e.target.value);\n }\n onChange?.(e);\n };\n\n const canSubmit = !!String(resolvedValue).trim() && !disabled && !loading;\n\n const handleSubmit = () => {\n const text = String(resolvedValue).trim();\n if (!text || !canSubmit) return;\n onSubmit?.(text);\n if (!isControlled) {\n setInternalValue(\"\");\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n onKeyDown?.(e);\n };\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n const useCustomAttachmentPreviews = attachmentPreviews !== undefined;\n const customAttachmentStrip = useCustomAttachmentPreviews ? attachmentPreviews : null;\n const defaultAttachmentStrip =\n !useCustomAttachmentPreviews && !!attachments?.length ? (\n <ChatInputDefaultAttachmentThumbnails\n attachments={attachments ?? []}\n disabled={disabled}\n onAttachmentRemove={onAttachmentRemove}\n />\n ) : null;\n const resolvedAttachmentStrip = customAttachmentStrip ?? defaultAttachmentStrip;\n const hasAttachmentStrip = resolvedAttachmentStrip != null;\n\n const selectedOption =\n selectOptions?.find((o) => o.value === selectValue) ?? selectOptions?.[0];\n const resolvedToolbarRight =\n toolbarRight ??\n (selectOptions && selectOptions.length > 0 ? (\n <InlineSelect\n options={selectOptions}\n value={selectValue}\n onChange={onSelectChange}\n disabled={disabled || selectDisabled}\n selectedOption={selectedOption}\n variant={selectVariant}\n menuTitle={selectMenuTitle}\n />\n ) : null);\n\n return (\n <div\n className={cn(\n \"relative flex flex-col gap-6 rounded-lg border border-border-primary bg-surface-primary\",\n \"has-focus-visible:outline-none\",\n \"motion-safe:transition-colors\",\n disabled && \"opacity-50\",\n className,\n )}\n >\n <div className=\"flex flex-col\">\n {hasAttachmentStrip ? (\n <div className=\"flex gap-2 overflow-x-auto px-4 pt-4 pb-2 [scrollbar-width:thin]\">\n {resolvedAttachmentStrip}\n </div>\n ) : null}\n <textarea\n {...textareaProps}\n ref={mergedRef}\n value={isControlled ? value : internalValue}\n placeholder={placeholder}\n maxLength={maxLength}\n disabled={disabled}\n aria-label={ariaLabel ?? placeholder}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n rows={minRows}\n className={cn(\n \"w-full resize-none bg-transparent px-4\",\n hasAttachmentStrip ? \"pt-0\" : \"pt-4\",\n \"typography-body-small-14px-regular text-content-primary\",\n \"placeholder:text-content-tertiary\",\n \"focus:outline-none disabled:cursor-not-allowed\",\n \"overflow-y-auto\",\n )}\n style={{\n minHeight: `${minHeight}px`,\n maxHeight: `${maxHeight}px`,\n ...style,\n }}\n />\n </div>\n\n <div className=\"flex items-center justify-between gap-2 px-4 pb-4\">\n <div className=\"flex items-center gap-1\">\n {showFileButton && (\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<AddIcon />}\n aria-label={fileButtonAriaLabel}\n onClick={onFileClick}\n disabled={disabled}\n className=\"max-sm:-ml-2 sm:border sm:border-border-primary\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-1\">\n {resolvedToolbarRight}\n <IconButton\n variant=\"primary\"\n size=\"32\"\n icon={submitIcon ?? <ArrowUpIcon />}\n aria-label={submitAriaLabel}\n onClick={handleSubmit}\n disabled={!canSubmit}\n className=\"disabled:bg-surface-secondary disabled:text-icons-primary disabled:opacity-100\"\n />\n </div>\n </div>\n </div>\n );\n },\n);\n\nChatInput.displayName = \"ChatInput\";\n\ninterface InlineSelectProps {\n options: ChatInputSelectOption[];\n value?: string;\n onChange?: (value: string) => void;\n disabled?: boolean;\n selectedOption?: ChatInputSelectOption;\n /** Presentation: anchored dropdown (`\"menu\"`) or bottom sheet (`\"sheet\"`). */\n variant: \"menu\" | \"sheet\";\n /** Title for the bottom-sheet header. */\n menuTitle?: string;\n}\n\ninterface SelectTriggerButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Whether the menu/sheet is open (drives chevron rotation + active background). */\n open: boolean;\n selectedOption?: ChatInputSelectOption;\n /** Label shown when no option is selected. */\n fallbackLabel?: string;\n}\n\n/**\n * The collapsed pill trigger (icon + short label + chevron). Shared by the\n * desktop menu and mobile sheet; spreads Radix-injected trigger props via\n * `asChild`.\n */\nconst SelectTriggerButton = React.forwardRef<HTMLButtonElement, SelectTriggerButtonProps>(\n ({ open, selectedOption, fallbackLabel, className, ...props }, ref) => (\n <button\n ref={ref}\n type=\"button\"\n aria-label=\"Select model\"\n className={cn(\n \"typography-description-12px-semibold text-content-primary\",\n \"flex items-center gap-1 rounded-md px-2 py-2\",\n \"hover:bg-neutral-alphas-50 focus-visible:shadow-focus-ring focus-visible:outline-none\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"motion-safe:transition-colors\",\n open && \"bg-neutral-alphas-50\",\n className,\n )}\n {...props}\n >\n {selectedOption?.icon && (\n <span className=\"flex shrink-0 items-center [&>svg]:size-4\">{selectedOption.icon}</span>\n )}\n {selectedOption?.label ?? fallbackLabel ?? \"Select\"}\n <ChevronDownIcon\n className={cn(\"size-4 motion-safe:transition-transform\", open && \"rotate-180\")}\n />\n </button>\n ),\n);\nSelectTriggerButton.displayName = \"SelectTriggerButton\";\n\n/** The green tick shown on the selected option (matches DropDown V2). */\nfunction SelectedTick() {\n return <TickIcon size={16} className=\"text-success-negative-content\" aria-hidden=\"true\" />;\n}\n\n/**\n * Inline model/option selector for the ChatInput toolbar. Renders the\n * design-system dropdown (DropDown V2) on desktop and a bottom sheet on mobile.\n */\nfunction InlineSelect({\n options,\n value,\n onChange,\n disabled,\n selectedOption,\n variant,\n menuTitle,\n}: InlineSelectProps) {\n const [open, setOpen] = React.useState(false);\n const fallbackLabel = options[0]?.label;\n\n // Never allow the menu/sheet to open while disabled, regardless of how the\n // open request originates (click, keyboard, programmatic).\n const handleOpenChange = (next: boolean) => {\n if (disabled && next) return;\n setOpen(next);\n };\n\n const trigger = (\n <SelectTriggerButton\n open={open}\n selectedOption={selectedOption}\n fallbackLabel={fallbackLabel}\n disabled={disabled}\n />\n );\n\n if (variant === \"sheet\") {\n return (\n <Drawer open={open} onOpenChange={handleOpenChange}>\n <DrawerTrigger asChild>{trigger}</DrawerTrigger>\n <DrawerContent position=\"bottom\" variant=\"sheet\">\n <DrawerHeader>\n <DrawerTitle className=\"typography-header-heading-xs\">\n {menuTitle ?? \"Select an option\"}\n </DrawerTitle>\n </DrawerHeader>\n {/* Mirrors DropdownMenuItem's two-line layout, which can't be reused\n here because it requires a DropdownMenu (Radix Menu) context. */}\n <div className=\"flex flex-col gap-1 overflow-y-auto px-4 pb-4\" role=\"listbox\">\n {options.map((option) => {\n const isSelected = option.value === value;\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"option\"\n aria-selected={isSelected}\n onClick={() => {\n onChange?.(option.value);\n setOpen(false);\n }}\n className={cn(\n \"flex w-full items-start gap-2 rounded-sm px-3 py-2 text-left outline-none\",\n \"focus-visible:shadow-focus-ring\",\n isSelected\n ? \"typography-body-default-16px-semibold bg-buttons-primary-default text-content-primary-inverted\"\n : \"typography-body-default-16px-regular text-content-primary hover:bg-neutral-alphas-50\",\n )}\n >\n {option.icon && (\n <span className=\"flex shrink-0 items-center pt-1 [&>svg]:size-4\">\n {option.icon}\n </span>\n )}\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate\">{option.menuLabel ?? option.label}</span>\n {option.description && (\n <span\n className={cn(\n \"typography-body-small-14px-regular truncate\",\n isSelected ? \"text-content-primary-inverted\" : \"text-content-secondary\",\n )}\n >\n {option.description}\n </span>\n )}\n </span>\n {isSelected && (\n <span className=\"flex shrink-0 items-center pt-1\">\n <SelectedTick />\n </span>\n )}\n </button>\n );\n })}\n </div>\n </DrawerContent>\n </Drawer>\n );\n }\n\n return (\n <DropdownMenu open={open} onOpenChange={handleOpenChange}>\n <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>\n <DropdownMenuContent side=\"top\" align=\"end\" className=\"min-w-[244px]\">\n {options.map((option) => {\n const isSelected = option.value === value;\n return (\n <DropdownMenuItem\n key={option.value}\n size=\"40\"\n selected={isSelected}\n description={option.description}\n leadingIcon={\n option.icon ? (\n <span className=\"flex size-4 items-center [&>svg]:size-4\">{option.icon}</span>\n ) : undefined\n }\n trailingIcon={isSelected ? <SelectedTick /> : undefined}\n onSelect={() => onChange?.(option.value)}\n >\n {option.menuLabel ?? option.label}\n </DropdownMenuItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n"],"names":["jsxs","jsx","IconButton","CloseIcon","React","minHeight","maxHeight","cn","AddIcon","ArrowUpIcon","ChevronDownIcon","TickIcon","Drawer","DrawerTrigger","DrawerContent","DrawerHeader","DrawerTitle","DropdownMenu","DropdownMenuTrigger","DropdownMenuContent","DropdownMenuItem"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+HA,MAAM,cAAc;AACpB,MAAM,cAAc;AAEpB,SAAS,gBAAgB,MAAsB;AAC7C,SAAO,cAAc,OAAO,cAAc;AAC5C;AAQA,SAAS,qCAAqC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAA8C;AAC5C,SAAO,YAAY,IAAI,CAAC,SACtBA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MAEC,WAAU;AAAA,MAEV,UAAA;AAAA,QAAAC,+BAAC,SAAI,KAAK,KAAK,KAAK,KAAI,IAAG,WAAU,0BAAyB;AAAA,QAC9DA,2BAAAA;AAAAA,UAACC,WAAAA;AAAAA,UAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,cAAY,KAAK,YAAY,UAAU,KAAK,SAAS,KAAK;AAAA,YAC1D,MAAMD,2BAAAA,IAACE,UAAAA,WAAA,EAAU,WAAU,UAAA,CAAU;AAAA,YACrC,UAAU,YAAY,CAAC;AAAA,YACvB,SAAS,MAAM,qBAAqB,KAAK,EAAE;AAAA,YAC3C,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,MACZ;AAAA,IAAA;AAAA,IAZK,KAAK;AAAA,EAAA,CAcb;AACH;AAqDO,MAAM,YAAYC,iBAAM;AAAA,EAC7B,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA,sBAAsB;AAAA,IACtB,kBAAkB;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,cAAcA,iBAAM,OAA4B,IAAI;AAC1D,UAAM,CAAC,eAAe,gBAAgB,IAAIA,iBAAM,SAAS,gBAAgB,EAAE;AAC3E,UAAM,gBAAgB,UAAU,SAAY,QAAQ;AACpD,UAAM,eAAe,UAAU;AAE/B,UAAM,YAAYA,iBAAM;AAAA,MACtB,CAAC,SAAqC;AACnC,oBAAmE,UAAU;AAC9E,YAAI,OAAO,QAAQ,YAAY;AAC7B,cAAI,IAAI;AAAA,QACV,WAAW,KAAK;AACb,cAA2D,UAAU;AAAA,QACxE;AAAA,MACF;AAAA,MACA,CAAC,GAAG;AAAA,IAAA;AAGN,UAAM,eAAeA,iBAAM,YAAY,MAAM;AAC3C,YAAM,WAAW,YAAY;AAC7B,UAAI,CAAC,SAAU;AAEf,YAAMC,aAAY,gBAAgB,OAAO;AACzC,YAAMC,aAAY,gBAAgB,OAAO;AAEzC,eAAS,MAAM,SAAS,GAAGD,UAAS;AACpC,YAAM,UAAU,KAAK,IAAI,KAAK,IAAI,SAAS,cAAcA,UAAS,GAAGC,UAAS;AAC9E,eAAS,MAAM,SAAS,GAAG,OAAO;AAAA,IACpC,GAAG,CAAC,SAAS,OAAO,CAAC;AAErBF,qBAAM,UAAU,MAAM;AACpB,mBAAA;AAAA,IACF,GAAG,CAAC,YAAY,CAAC;AAEjB,UAAM,eAAe,CAAC,MAA8C;AAClE,UAAI,CAAC,cAAc;AACjB,yBAAiB,EAAE,OAAO,KAAK;AAAA,MACjC;AACA,iBAAW,CAAC;AAAA,IACd;AAEA,UAAM,YAAY,CAAC,CAAC,OAAO,aAAa,EAAE,KAAA,KAAU,CAAC,YAAY,CAAC;AAElE,UAAM,eAAe,MAAM;AACzB,YAAM,OAAO,OAAO,aAAa,EAAE,KAAA;AACnC,UAAI,CAAC,QAAQ,CAAC,UAAW;AACzB,iBAAW,IAAI;AACf,UAAI,CAAC,cAAc;AACjB,yBAAiB,EAAE;AAAA,MACrB;AAAA,IACF;AAEA,UAAM,gBAAgB,CAAC,MAAgD;AACrE,UAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;AACpC,UAAE,eAAA;AACF,qBAAA;AAAA,MACF;AACA,kBAAY,CAAC;AAAA,IACf;AAEA,UAAM,YAAY,gBAAgB,OAAO;AACzC,UAAM,YAAY,gBAAgB,OAAO;AAEzC,UAAM,8BAA8B,uBAAuB;AAC3D,UAAM,wBAAwB,8BAA8B,qBAAqB;AACjF,UAAM,yBACJ,CAAC,+BAA+B,CAAC,CAAC,aAAa,SAC7CH,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,aAAa,eAAe,CAAA;AAAA,QAC5B;AAAA,QACA;AAAA,MAAA;AAAA,IAAA,IAEA;AACN,UAAM,0BAA0B,yBAAyB;AACzD,UAAM,qBAAqB,2BAA2B;AAEtD,UAAM,iBACJ,eAAe,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW,KAAK,gBAAgB,CAAC;AAC1E,UAAM,uBACJ,iBACC,iBAAiB,cAAc,SAAS,IACvCA,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAS;AAAA,QACT,OAAO;AAAA,QACP,UAAU;AAAA,QACV,UAAU,YAAY;AAAA,QACtB;AAAA,QACA,SAAS;AAAA,QACT,WAAW;AAAA,MAAA;AAAA,IAAA,IAEX;AAEN,WACED,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAWO,GAAAA;AAAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAGF,UAAA;AAAA,UAAAP,2BAAAA,KAAC,OAAA,EAAI,WAAU,iBACZ,UAAA;AAAA,YAAA,qBACCC,2BAAAA,IAAC,OAAA,EAAI,WAAU,oEACZ,mCACH,IACE;AAAA,YACJA,2BAAAA;AAAAA,cAAC;AAAA,cAAA;AAAA,gBACE,GAAG;AAAA,gBACJ,KAAK;AAAA,gBACL,OAAO,eAAe,QAAQ;AAAA,gBAC9B;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,cAAY,aAAa;AAAA,gBACzB,UAAU;AAAA,gBACV,WAAW;AAAA,gBACX,MAAM;AAAA,gBACN,WAAWM,GAAAA;AAAAA,kBACT;AAAA,kBACA,qBAAqB,SAAS;AAAA,kBAC9B;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA,gBAAA;AAAA,gBAEF,OAAO;AAAA,kBACL,WAAW,GAAG,SAAS;AAAA,kBACvB,WAAW,GAAG,SAAS;AAAA,kBACvB,GAAG;AAAA,gBAAA;AAAA,cACL;AAAA,YAAA;AAAA,UACF,GACF;AAAA,UAEAP,2BAAAA,KAAC,OAAA,EAAI,WAAU,qDACb,UAAA;AAAA,YAAAC,2BAAAA,IAAC,OAAA,EAAI,WAAU,2BACZ,UAAA,kBACCA,2BAAAA;AAAAA,cAACC,WAAAA;AAAAA,cAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,qCAAOM,QAAAA,SAAA,EAAQ;AAAA,gBACf,cAAY;AAAA,gBACZ,SAAS;AAAA,gBACT;AAAA,gBACA,WAAU;AAAA,cAAA;AAAA,YAAA,GAGhB;AAAA,YAEAR,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,cAAA;AAAA,cACDC,2BAAAA;AAAAA,gBAACC,WAAAA;AAAAA,gBAAA;AAAA,kBACC,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,MAAM,cAAcD,+BAACQ,YAAAA,aAAA,CAAA,CAAY;AAAA,kBACjC,cAAY;AAAA,kBACZ,SAAS;AAAA,kBACT,UAAU,CAAC;AAAA,kBACX,WAAU;AAAA,gBAAA;AAAA,cAAA;AAAA,YACZ,EAAA,CACF;AAAA,UAAA,EAAA,CACF;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACF;AAEA,UAAU,cAAc;AA2BxB,MAAM,sBAAsBL,iBAAM;AAAA,EAChC,CAAC,EAAE,MAAM,gBAAgB,eAAe,WAAW,GAAG,MAAA,GAAS,QAC7DJ,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,cAAW;AAAA,MACX,WAAWO,GAAAA;AAAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,gBAAgB,QACfN,2BAAAA,IAAC,QAAA,EAAK,WAAU,6CAA6C,yBAAe,MAAK;AAAA,QAElF,gBAAgB,SAAS,iBAAiB;AAAA,QAC3CA,2BAAAA;AAAAA,UAACS,gBAAAA;AAAAA,UAAA;AAAA,YACC,WAAWH,GAAAA,GAAG,2CAA2C,QAAQ,YAAY;AAAA,UAAA;AAAA,QAAA;AAAA,MAC/E;AAAA,IAAA;AAAA,EAAA;AAGN;AACA,oBAAoB,cAAc;AAGlC,SAAS,eAAe;AACtB,wCAAQI,mBAAA,EAAS,MAAM,IAAI,WAAU,iCAAgC,eAAY,QAAO;AAC1F;AAMA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,CAAC,MAAM,OAAO,IAAIP,iBAAM,SAAS,KAAK;AAC5C,QAAM,gBAAgB,QAAQ,CAAC,GAAG;AAIlC,QAAM,mBAAmB,CAAC,SAAkB;AAC1C,QAAI,YAAY,KAAM;AACtB,YAAQ,IAAI;AAAA,EACd;AAEA,QAAM,UACJH,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAAA;AAIJ,MAAI,YAAY,SAAS;AACvB,WACED,2BAAAA,KAACY,OAAAA,QAAA,EAAO,MAAY,cAAc,kBAChC,UAAA;AAAA,MAAAX,2BAAAA,IAACY,OAAAA,eAAA,EAAc,SAAO,MAAE,UAAA,SAAQ;AAAA,MAChCb,2BAAAA,KAACc,OAAAA,eAAA,EAAc,UAAS,UAAS,SAAQ,SACvC,UAAA;AAAA,QAAAb,2BAAAA,IAACc,OAAAA,gBACC,UAAAd,2BAAAA,IAACe,OAAAA,aAAA,EAAY,WAAU,gCACpB,UAAA,aAAa,oBAChB,EAAA,CACF;AAAA,QAGAf,2BAAAA,IAAC,SAAI,WAAU,iDAAgD,MAAK,WACjE,UAAA,QAAQ,IAAI,CAAC,WAAW;AACvB,gBAAM,aAAa,OAAO,UAAU;AACpC,iBACED,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cAEC,MAAK;AAAA,cACL,MAAK;AAAA,cACL,iBAAe;AAAA,cACf,SAAS,MAAM;AACb,2BAAW,OAAO,KAAK;AACvB,wBAAQ,KAAK;AAAA,cACf;AAAA,cACA,WAAWO,GAAAA;AAAAA,gBACT;AAAA,gBACA;AAAA,gBACA,aACI,mGACA;AAAA,cAAA;AAAA,cAGL,UAAA;AAAA,gBAAA,OAAO,QACNN,2BAAAA,IAAC,QAAA,EAAK,WAAU,kDACb,iBAAO,MACV;AAAA,gBAEFD,2BAAAA,KAAC,QAAA,EAAK,WAAU,wCACd,UAAA;AAAA,kBAAAC,+BAAC,UAAK,WAAU,YAAY,UAAA,OAAO,aAAa,OAAO,OAAM;AAAA,kBAC5D,OAAO,eACNA,2BAAAA;AAAAA,oBAAC;AAAA,oBAAA;AAAA,sBACC,WAAWM,GAAAA;AAAAA,wBACT;AAAA,wBACA,aAAa,kCAAkC;AAAA,sBAAA;AAAA,sBAGhD,UAAA,OAAO;AAAA,oBAAA;AAAA,kBAAA;AAAA,gBACV,GAEJ;AAAA,gBACC,cACCN,2BAAAA,IAAC,QAAA,EAAK,WAAU,mCACd,UAAAA,2BAAAA,IAAC,gBAAa,EAAA,CAChB;AAAA,cAAA;AAAA,YAAA;AAAA,YArCG,OAAO;AAAA,UAAA;AAAA,QAyClB,CAAC,EAAA,CACH;AAAA,MAAA,EAAA,CACF;AAAA,IAAA,GACF;AAAA,EAEJ;AAEA,SACED,2BAAAA,KAACiB,aAAAA,cAAA,EAAa,MAAY,cAAc,kBACtC,UAAA;AAAA,IAAAhB,2BAAAA,IAACiB,aAAAA,qBAAA,EAAoB,SAAO,MAAE,UAAA,SAAQ;AAAA,IACtCjB,2BAAAA,IAACkB,aAAAA,qBAAA,EAAoB,MAAK,OAAM,OAAM,OAAM,WAAU,iBACnD,UAAA,QAAQ,IAAI,CAAC,WAAW;AACvB,YAAM,aAAa,OAAO,UAAU;AACpC,aACElB,2BAAAA;AAAAA,QAACmB,aAAAA;AAAAA,QAAA;AAAA,UAEC,MAAK;AAAA,UACL,UAAU;AAAA,UACV,aAAa,OAAO;AAAA,UACpB,aACE,OAAO,OACLnB,2BAAAA,IAAC,UAAK,WAAU,2CAA2C,UAAA,OAAO,KAAA,CAAK,IACrE;AAAA,UAEN,cAAc,aAAaA,2BAAAA,IAAC,cAAA,CAAA,CAAa,IAAK;AAAA,UAC9C,UAAU,MAAM,WAAW,OAAO,KAAK;AAAA,UAEtC,UAAA,OAAO,aAAa,OAAO;AAAA,QAAA;AAAA,QAZvB,OAAO;AAAA,MAAA;AAAA,IAelB,CAAC,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;;"}
@@ -42,32 +42,67 @@ const Chip = React__namespace.forwardRef(
42
42
  const isInteractive = !!onClick && !asChild;
43
43
  const Comp = asChild ? reactSlot.Slot : isInteractive ? "button" : "span";
44
44
  const isDark = variant === "dark";
45
+ const showDottedBorder = !isDark && dotted && !selected && !asChild;
46
+ const dottedBorder = showDottedBorder ? /* @__PURE__ */ jsxRuntime.jsx(
47
+ "svg",
48
+ {
49
+ "aria-hidden": "true",
50
+ className: cn.cn(
51
+ "pointer-events-none absolute inset-0 size-full overflow-visible",
52
+ disabled ? "text-buttons-chip-disabled" : "text-buttons-chip-dotted-default",
53
+ isInteractive && !disabled && "group-hover:text-buttons-chip-dotted-hover-stroke group-active:text-buttons-chip-dotted-hover-stroke"
54
+ ),
55
+ children: /* @__PURE__ */ jsxRuntime.jsx(
56
+ "rect",
57
+ {
58
+ x: "0.5",
59
+ y: "0.5",
60
+ rx: variant === "square" ? 7.5 : size === "40" ? 19.5 : 15.5,
61
+ fill: "none",
62
+ stroke: "currentColor",
63
+ strokeWidth: "1",
64
+ strokeDasharray: variant === "square" && size === "40" ? "8 8" : "6 6",
65
+ style: { width: "calc(100% - 1px)", height: "calc(100% - 1px)" }
66
+ }
67
+ )
68
+ }
69
+ ) : null;
45
70
  return /* @__PURE__ */ jsxRuntime.jsx(
46
71
  Comp,
47
72
  {
48
73
  ref,
49
74
  "data-testid": "chip",
50
75
  className: cn.cn(
51
- "typography-description-12px-semibold relative inline-flex min-w-0 items-center justify-center whitespace-nowrap motion-safe:transition-colors motion-safe:duration-150",
76
+ "relative inline-flex min-w-0 items-center justify-center whitespace-nowrap motion-safe:transition-colors motion-safe:duration-150",
52
77
  // Shape
53
78
  variant === "square" ? "rounded-xs" : "rounded-full",
54
- // Size
55
- size === "32" && "h-8 py-1",
56
- size === "40" && "h-10 py-2.5",
79
+ // Size (32px uses 12px text, 40px uses 14px text per the design spec)
80
+ size === "32" && "typography-description-12px-semibold h-8 py-1",
81
+ size === "40" && "typography-body-small-14px-semibold h-10 py-2.5",
57
82
  // Variant colors
58
83
  isDark && "bg-neutral-alphas-600 text-content-always-white",
59
- !isDark && selected && "bg-brand-primary-muted text-content-primary",
60
- !isDark && !selected && !dotted && "bg-neutral-alphas-50 text-content-primary",
61
- !isDark && !selected && dotted && "border border-buttons-chip-dotted-default border-dashed bg-transparent text-content-primary",
84
+ !isDark && selected && !dotted && "bg-buttons-chip-active text-content-primary-inverted",
85
+ // Active + dotted is a subtle filled state with a solid (non-dashed) border.
86
+ !isDark && selected && dotted && "border border-border-primary border-solid bg-inputs-inputs-primary text-content-primary",
87
+ !isDark && !selected && !dotted && "bg-buttons-chip-default text-content-primary",
88
+ // Dotted (non-selected): the dashed border is drawn via SVG (`dottedBorder`).
89
+ // `group` lets that SVG react to hover/active. `asChild` keeps a CSS dashed
90
+ // border fallback since the SVG is only rendered in the default path.
91
+ !isDark && !selected && dotted && "bg-transparent text-content-primary",
92
+ !isDark && !selected && dotted && !asChild && "group",
93
+ asChild && !isDark && !selected && dotted && (disabled ? "border border-buttons-chip-disabled border-dashed" : "border border-buttons-chip-dotted-default border-dashed"),
62
94
  // Interactive
63
95
  isInteractive && !disabled && "cursor-pointer",
64
- isInteractive && !disabled && !isDark && !selected && !dotted && "hover:bg-brand-primary-muted active:bg-brand-primary-muted",
65
- isInteractive && !disabled && !isDark && !selected && dotted && "hover:border-buttons-chip-dotted-hover-stroke hover:bg-neutral-alphas-50 active:border-buttons-chip-dotted-hover-stroke active:bg-neutral-alphas-50",
96
+ isInteractive && !disabled && !isDark && !selected && !dotted && "hover:bg-buttons-chip-hover",
97
+ isInteractive && !disabled && !isDark && !selected && dotted && "hover:bg-neutral-alphas-50 active:bg-neutral-alphas-50",
66
98
  // Focus
67
99
  "focus-visible:shadow-focus-ring focus-visible:outline-none",
68
100
  // Disabled
69
101
  disabled && isDark && "pointer-events-none opacity-50",
70
- disabled && !isDark && "pointer-events-none text-neutral-alphas-400",
102
+ disabled && !isDark && "pointer-events-none text-content-disabled",
103
+ // Solid (non-dotted) disabled chips get a muted fill; dotted ones stay
104
+ // transparent with their dashed border (drawn via SVG).
105
+ disabled && !isDark && !dotted && "bg-buttons-chip-disabled",
71
106
  className
72
107
  ),
73
108
  ...isInteractive && {
@@ -80,6 +115,7 @@ const Chip = React__namespace.forwardRef(
80
115
  ...selected && { "data-selected": "" },
81
116
  ...props,
82
117
  children: asChild ? /* @__PURE__ */ jsxRuntime.jsx(reactSlot.Slottable, { children }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
118
+ dottedBorder,
83
119
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-0.5 overflow-hidden px-3", children: [
84
120
  leftDot && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-2 shrink-0 rounded-full bg-current", "aria-hidden": "true" }),
85
121
  leftIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center justify-center", "aria-hidden": "true", children: leftIcon }),
@@ -1 +1 @@
1
- {"version":3,"file":"Chip.cjs","sources":["../../../../src/components/Chip/Chip.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Visual variant of the chip. */\nexport type ChipVariant = \"rounded\" | \"square\" | \"dark\";\n/** Height of the chip in pixels. */\nexport type ChipSize = \"32\" | \"40\";\n\nexport interface ChipProps extends React.HTMLAttributes<HTMLElement> {\n /** Visual variant of the chip. @default \"rounded\" */\n variant?: ChipVariant;\n /** Height of the chip in pixels. @default \"32\" */\n size?: ChipSize;\n /** Whether the chip is in a selected (pressed) state. @default false */\n selected?: boolean;\n /** Whether the chip is disabled. @default false */\n disabled?: boolean;\n /** Whether to show a coloured status dot at the leading edge. @default false */\n leftDot?: boolean;\n /**\n * Whether the chip uses a dashed border for add/create affordances.\n * Has no effect when `variant=\"dark\"` or `selected` is `true`.\n * @default false\n */\n dotted?: boolean;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** Notification badge content (e.g. `\"99+\"`). Passed as a string for i18n support. */\n notificationLabel?: string;\n /** Click handler — when provided, the chip renders as a `<button>` for accessibility. */\n onClick?: React.MouseEventHandler<HTMLElement>;\n /** Merge props onto a child element instead of rendering a wrapper. @default false */\n asChild?: boolean;\n}\n\n/**\n * A compact element for filters, tags, or toggleable actions. When an `onClick`\n * handler is provided, the chip renders as an interactive `<button>` with\n * `aria-pressed` support.\n *\n * @example\n * ```tsx\n * <Chip selected onClick={toggle}>Music</Chip>\n * ```\n */\nexport const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(\n (\n {\n className,\n variant = \"rounded\",\n size = \"32\",\n selected = false,\n disabled = false,\n leftDot = false,\n dotted = false,\n leftIcon,\n rightIcon,\n notificationLabel,\n onClick,\n asChild = false,\n children,\n ...props\n },\n ref,\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variant-heavy UI component\n ) => {\n const isInteractive = !!onClick && !asChild;\n const Comp = asChild ? Slot : isInteractive ? \"button\" : \"span\";\n const isDark = variant === \"dark\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"chip\"\n className={cn(\n \"typography-description-12px-semibold relative inline-flex min-w-0 items-center justify-center whitespace-nowrap motion-safe:transition-colors motion-safe:duration-150\",\n // Shape\n variant === \"square\" ? \"rounded-xs\" : \"rounded-full\",\n // Size\n size === \"32\" && \"h-8 py-1\",\n size === \"40\" && \"h-10 py-2.5\",\n // Variant colors\n isDark && \"bg-neutral-alphas-600 text-content-always-white\",\n !isDark && selected && \"bg-brand-primary-muted text-content-primary\",\n !isDark && !selected && !dotted && \"bg-neutral-alphas-50 text-content-primary\",\n !isDark &&\n !selected &&\n dotted &&\n \"border border-buttons-chip-dotted-default border-dashed bg-transparent text-content-primary\",\n // Interactive\n isInteractive && !disabled && \"cursor-pointer\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n !dotted &&\n \"hover:bg-brand-primary-muted active:bg-brand-primary-muted\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n dotted &&\n \"hover:border-buttons-chip-dotted-hover-stroke hover:bg-neutral-alphas-50 active:border-buttons-chip-dotted-hover-stroke active:bg-neutral-alphas-50\",\n // Focus\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled\n disabled && isDark && \"pointer-events-none opacity-50\",\n disabled && !isDark && \"pointer-events-none text-neutral-alphas-400\",\n className,\n )}\n {...(isInteractive && {\n type: \"button\" as const,\n disabled,\n \"aria-pressed\": selected,\n onClick,\n })}\n {...(!isInteractive && disabled && { \"aria-disabled\": true })}\n {...(selected && { \"data-selected\": \"\" })}\n {...props}\n >\n {asChild ? (\n <Slottable>{children}</Slottable>\n ) : (\n <>\n <span className=\"flex min-w-0 items-center gap-0.5 overflow-hidden px-3\">\n {leftDot && (\n <span className=\"size-2 shrink-0 rounded-full bg-current\" aria-hidden=\"true\" />\n )}\n {leftIcon && (\n <span className=\"flex shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <span className=\"min-w-0 truncate\">{children}</span>\n {rightIcon && (\n <span\n className=\"flex size-5 shrink-0 items-center justify-center\"\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n </span>\n {notificationLabel && (\n <span className=\"typography-description-12px-semibold absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-content-primary px-1 text-content-primary-inverted\">\n {notificationLabel}\n </span>\n )}\n </>\n )}\n </Comp>\n );\n },\n);\n\nChip.displayName = \"Chip\";\n"],"names":["React","Slot","jsx","cn","Slottable","jsxs","Fragment"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgDO,MAAM,OAAOA,iBAAM;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,UAAU;AAAA,IACV,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QAEG;AACH,UAAM,gBAAgB,CAAC,CAAC,WAAW,CAAC;AACpC,UAAM,OAAO,UAAUC,UAAAA,OAAO,gBAAgB,WAAW;AACzD,UAAM,SAAS,YAAY;AAE3B,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA,UACT;AAAA;AAAA,UAEA,YAAY,WAAW,eAAe;AAAA;AAAA,UAEtC,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ;AAAA;AAAA,UAEjB,UAAU;AAAA,UACV,CAAC,UAAU,YAAY;AAAA,UACvB,CAAC,UAAU,CAAC,YAAY,CAAC,UAAU;AAAA,UACnC,CAAC,UACC,CAAC,YACD,UACA;AAAA;AAAA,UAEF,iBAAiB,CAAC,YAAY;AAAA,UAC9B,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD,CAAC,UACD;AAAA,UACF,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD,UACA;AAAA;AAAA,UAEF;AAAA;AAAA,UAEA,YAAY,UAAU;AAAA,UACtB,YAAY,CAAC,UAAU;AAAA,UACvB;AAAA,QAAA;AAAA,QAED,GAAI,iBAAiB;AAAA,UACpB,MAAM;AAAA,UACN;AAAA,UACA,gBAAgB;AAAA,UAChB;AAAA,QAAA;AAAA,QAED,GAAI,CAAC,iBAAiB,YAAY,EAAE,iBAAiB,KAAA;AAAA,QACrD,GAAI,YAAY,EAAE,iBAAiB,GAAA;AAAA,QACnC,GAAG;AAAA,QAEH,UAAA,UACCD,2BAAAA,IAACE,qBAAA,EAAW,SAAA,CAAS,IAErBC,2BAAAA,KAAAC,qBAAA,EACE,UAAA;AAAA,UAAAD,2BAAAA,KAAC,QAAA,EAAK,WAAU,0DACb,UAAA;AAAA,YAAA,WACCH,2BAAAA,IAAC,QAAA,EAAK,WAAU,2CAA0C,eAAY,QAAO;AAAA,YAE9E,YACCA,2BAAAA,IAAC,QAAA,EAAK,WAAU,6CAA4C,eAAY,QACrE,UAAA,UACH;AAAA,YAEFA,2BAAAA,IAAC,QAAA,EAAK,WAAU,oBAAoB,SAAA,CAAS;AAAA,YAC5C,aACCA,2BAAAA;AAAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAU;AAAA,gBACV,eAAY;AAAA,gBAEX,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UACH,GAEJ;AAAA,UACC,qBACCA,2BAAAA,IAAC,QAAA,EAAK,WAAU,iLACb,UAAA,kBAAA,CACH;AAAA,QAAA,EAAA,CAEJ;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;;"}
1
+ {"version":3,"file":"Chip.cjs","sources":["../../../../src/components/Chip/Chip.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Visual variant of the chip. */\nexport type ChipVariant = \"rounded\" | \"square\" | \"dark\";\n/** Height of the chip in pixels. */\nexport type ChipSize = \"32\" | \"40\";\n\nexport interface ChipProps extends React.HTMLAttributes<HTMLElement> {\n /** Visual variant of the chip. @default \"rounded\" */\n variant?: ChipVariant;\n /** Height of the chip in pixels. @default \"32\" */\n size?: ChipSize;\n /** Whether the chip is in a selected (pressed) state. @default false */\n selected?: boolean;\n /** Whether the chip is disabled. @default false */\n disabled?: boolean;\n /** Whether to show a coloured status dot at the leading edge. @default false */\n leftDot?: boolean;\n /**\n * Whether the chip uses a dashed border for add/create affordances.\n * When `selected`, it becomes a subtle filled state with a solid border.\n * Has no effect when `variant=\"dark\"`.\n * @default false\n */\n dotted?: boolean;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** Notification badge content (e.g. `\"99+\"`). Passed as a string for i18n support. */\n notificationLabel?: string;\n /** Click handler — when provided, the chip renders as a `<button>` for accessibility. */\n onClick?: React.MouseEventHandler<HTMLElement>;\n /** Merge props onto a child element instead of rendering a wrapper. @default false */\n asChild?: boolean;\n}\n\n/**\n * A compact element for filters, tags, or toggleable actions. When an `onClick`\n * handler is provided, the chip renders as an interactive `<button>` with\n * `aria-pressed` support.\n *\n * @example\n * ```tsx\n * <Chip selected onClick={toggle}>Music</Chip>\n * ```\n */\nexport const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(\n (\n {\n className,\n variant = \"rounded\",\n size = \"32\",\n selected = false,\n disabled = false,\n leftDot = false,\n dotted = false,\n leftIcon,\n rightIcon,\n notificationLabel,\n onClick,\n asChild = false,\n children,\n ...props\n },\n ref,\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variant-heavy UI component\n ) => {\n const isInteractive = !!onClick && !asChild;\n const Comp = asChild ? Slot : isInteractive ? \"button\" : \"span\";\n const isDark = variant === \"dark\";\n\n // The dashed border is drawn as an SVG so the dash length matches the design\n // spec (8/8 for the 40px square, 6/6 otherwise). CSS `border-dashed` only\n // renders browser-default dash lengths, which are too short. The stroke colour\n // is driven by `currentColor`, and `group-hover`/`group-active` react to the\n // chip's interactive states. Rendered only in the default (non-`asChild`) path.\n const showDottedBorder = !isDark && dotted && !selected && !asChild;\n const dottedBorder = showDottedBorder ? (\n <svg\n aria-hidden=\"true\"\n className={cn(\n \"pointer-events-none absolute inset-0 size-full overflow-visible\",\n disabled ? \"text-buttons-chip-disabled\" : \"text-buttons-chip-dotted-default\",\n isInteractive &&\n !disabled &&\n \"group-hover:text-buttons-chip-dotted-hover-stroke group-active:text-buttons-chip-dotted-hover-stroke\",\n )}\n >\n <rect\n x=\"0.5\"\n y=\"0.5\"\n rx={variant === \"square\" ? 7.5 : size === \"40\" ? 19.5 : 15.5}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n strokeDasharray={variant === \"square\" && size === \"40\" ? \"8 8\" : \"6 6\"}\n style={{ width: \"calc(100% - 1px)\", height: \"calc(100% - 1px)\" }}\n />\n </svg>\n ) : null;\n\n return (\n <Comp\n ref={ref}\n data-testid=\"chip\"\n className={cn(\n \"relative inline-flex min-w-0 items-center justify-center whitespace-nowrap motion-safe:transition-colors motion-safe:duration-150\",\n // Shape\n variant === \"square\" ? \"rounded-xs\" : \"rounded-full\",\n // Size (32px uses 12px text, 40px uses 14px text per the design spec)\n size === \"32\" && \"typography-description-12px-semibold h-8 py-1\",\n size === \"40\" && \"typography-body-small-14px-semibold h-10 py-2.5\",\n // Variant colors\n isDark && \"bg-neutral-alphas-600 text-content-always-white\",\n !isDark && selected && !dotted && \"bg-buttons-chip-active text-content-primary-inverted\",\n // Active + dotted is a subtle filled state with a solid (non-dashed) border.\n !isDark &&\n selected &&\n dotted &&\n \"border border-border-primary border-solid bg-inputs-inputs-primary text-content-primary\",\n !isDark && !selected && !dotted && \"bg-buttons-chip-default text-content-primary\",\n // Dotted (non-selected): the dashed border is drawn via SVG (`dottedBorder`).\n // `group` lets that SVG react to hover/active. `asChild` keeps a CSS dashed\n // border fallback since the SVG is only rendered in the default path.\n !isDark && !selected && dotted && \"bg-transparent text-content-primary\",\n !isDark && !selected && dotted && !asChild && \"group\",\n asChild &&\n !isDark &&\n !selected &&\n dotted &&\n (disabled\n ? \"border border-buttons-chip-disabled border-dashed\"\n : \"border border-buttons-chip-dotted-default border-dashed\"),\n // Interactive\n isInteractive && !disabled && \"cursor-pointer\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n !dotted &&\n \"hover:bg-buttons-chip-hover\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n dotted &&\n \"hover:bg-neutral-alphas-50 active:bg-neutral-alphas-50\",\n // Focus\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled\n disabled && isDark && \"pointer-events-none opacity-50\",\n disabled && !isDark && \"pointer-events-none text-content-disabled\",\n // Solid (non-dotted) disabled chips get a muted fill; dotted ones stay\n // transparent with their dashed border (drawn via SVG).\n disabled && !isDark && !dotted && \"bg-buttons-chip-disabled\",\n className,\n )}\n {...(isInteractive && {\n type: \"button\" as const,\n disabled,\n \"aria-pressed\": selected,\n onClick,\n })}\n {...(!isInteractive && disabled && { \"aria-disabled\": true })}\n {...(selected && { \"data-selected\": \"\" })}\n {...props}\n >\n {asChild ? (\n <Slottable>{children}</Slottable>\n ) : (\n <>\n {dottedBorder}\n <span className=\"flex min-w-0 items-center gap-0.5 overflow-hidden px-3\">\n {leftDot && (\n <span className=\"size-2 shrink-0 rounded-full bg-current\" aria-hidden=\"true\" />\n )}\n {leftIcon && (\n <span className=\"flex shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <span className=\"min-w-0 truncate\">{children}</span>\n {rightIcon && (\n <span\n className=\"flex size-5 shrink-0 items-center justify-center\"\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n </span>\n {notificationLabel && (\n <span className=\"typography-description-12px-semibold absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-content-primary px-1 text-content-primary-inverted\">\n {notificationLabel}\n </span>\n )}\n </>\n )}\n </Comp>\n );\n },\n);\n\nChip.displayName = \"Chip\";\n"],"names":["React","Slot","jsx","cn","Slottable","jsxs","Fragment"],"mappings":"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"}