@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.
- package/dist/charts.d.ts +1 -1
- package/dist/cjs/components/Chart/ChartCard.cjs +2 -2
- package/dist/cjs/components/Chart/ChartCard.cjs.map +1 -1
- package/dist/cjs/components/ChatInput/ChatInput.cjs +128 -92
- package/dist/cjs/components/ChatInput/ChatInput.cjs.map +1 -1
- package/dist/cjs/components/Chip/Chip.cjs +46 -10
- package/dist/cjs/components/Chip/Chip.cjs.map +1 -1
- package/dist/cjs/components/Drawer/Drawer.cjs +6 -3
- package/dist/cjs/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs +20 -4
- package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
- package/dist/cjs/components/Icons/TickIcon.cjs +3 -1
- package/dist/cjs/components/Icons/TickIcon.cjs.map +1 -1
- package/dist/cjs/components/Logo/Logo.cjs +107 -33
- package/dist/cjs/components/Logo/Logo.cjs.map +1 -1
- package/dist/cjs/components/Logo/agenciesIcon.cjs +6 -0
- package/dist/cjs/components/Logo/agenciesIcon.cjs.map +1 -0
- package/dist/cjs/components/Tabs/TabsList.cjs +20 -12
- package/dist/cjs/components/Tabs/TabsList.cjs.map +1 -1
- package/dist/cjs/components/Tabs/TabsTrigger.cjs +8 -7
- package/dist/cjs/components/Tabs/TabsTrigger.cjs.map +1 -1
- package/dist/components/Chart/ChartCard.mjs +2 -2
- package/dist/components/Chart/ChartCard.mjs.map +1 -1
- package/dist/components/ChatInput/ChatInput.mjs +128 -92
- package/dist/components/ChatInput/ChatInput.mjs.map +1 -1
- package/dist/components/Chip/Chip.mjs +46 -10
- package/dist/components/Chip/Chip.mjs.map +1 -1
- package/dist/components/Drawer/Drawer.mjs +6 -3
- package/dist/components/Drawer/Drawer.mjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.mjs +20 -4
- package/dist/components/DropdownMenu/DropdownMenu.mjs.map +1 -1
- package/dist/components/Icons/TickIcon.mjs +3 -1
- package/dist/components/Icons/TickIcon.mjs.map +1 -1
- package/dist/components/Logo/Logo.mjs +107 -33
- package/dist/components/Logo/Logo.mjs.map +1 -1
- package/dist/components/Logo/agenciesIcon.mjs +6 -0
- package/dist/components/Logo/agenciesIcon.mjs.map +1 -0
- package/dist/components/Tabs/TabsList.mjs +20 -12
- package/dist/components/Tabs/TabsList.mjs.map +1 -1
- package/dist/components/Tabs/TabsTrigger.mjs +8 -7
- package/dist/components/Tabs/TabsTrigger.mjs.map +1 -1
- package/dist/index.d.ts +61 -6
- 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,
|
|
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
|
-
}, [
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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":"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"}
|
|
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":"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"}
|
|
@@ -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
|
-
"
|
|
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-
|
|
60
|
-
|
|
61
|
-
!isDark &&
|
|
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-
|
|
65
|
-
isInteractive && !disabled && !isDark && !selected && dotted && "hover:
|
|
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-
|
|
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":";;;;;;;;;;;;;;;;;;;;;;;;AAiDO,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;AAO3B,UAAM,mBAAmB,CAAC,UAAU,UAAU,CAAC,YAAY,CAAC;AAC5D,UAAM,eAAe,mBACnBC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA,UACT;AAAA,UACA,WAAW,+BAA+B;AAAA,UAC1C,iBACE,CAAC,YACD;AAAA,QAAA;AAAA,QAGJ,UAAAD,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YACC,GAAE;AAAA,YACF,GAAE;AAAA,YACF,IAAI,YAAY,WAAW,MAAM,SAAS,OAAO,OAAO;AAAA,YACxD,MAAK;AAAA,YACL,QAAO;AAAA,YACP,aAAY;AAAA,YACZ,iBAAiB,YAAY,YAAY,SAAS,OAAO,QAAQ;AAAA,YACjE,OAAO,EAAE,OAAO,oBAAoB,QAAQ,mBAAA;AAAA,UAAmB;AAAA,QAAA;AAAA,MACjE;AAAA,IAAA,IAEA;AAEJ,WACEA,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,CAAC,UAAU;AAAA;AAAA,UAElC,CAAC,UACC,YACA,UACA;AAAA,UACF,CAAC,UAAU,CAAC,YAAY,CAAC,UAAU;AAAA;AAAA;AAAA;AAAA,UAInC,CAAC,UAAU,CAAC,YAAY,UAAU;AAAA,UAClC,CAAC,UAAU,CAAC,YAAY,UAAU,CAAC,WAAW;AAAA,UAC9C,WACE,CAAC,UACD,CAAC,YACD,WACC,WACG,sDACA;AAAA;AAAA,UAEN,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;AAAA;AAAA,UAGvB,YAAY,CAAC,UAAU,CAAC,UAAU;AAAA,UAClC;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,EACG,UAAA;AAAA,UAAA;AAAA,UACDD,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;;"}
|