@fanvue/ui 3.5.0 → 3.6.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.
@@ -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":"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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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"}
@@ -26,6 +26,7 @@ function _interopNamespaceDefault(e) {
26
26
  }
27
27
  const DialogPrimitive__namespace = /* @__PURE__ */ _interopNamespaceDefault(DialogPrimitive);
28
28
  const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
29
+ const SHEET_CLASSES = "rounded-t-xl border border-modal-stroke bg-modal-background shadow-blur-menu backdrop-blur-[4px]";
29
30
  const DrawerContext = React__namespace.createContext({
30
31
  overlay: true
31
32
  });
@@ -65,6 +66,7 @@ const DrawerContent = React__namespace.forwardRef(
65
66
  className,
66
67
  position = "right",
67
68
  size = "sm",
69
+ variant = "panel",
68
70
  overlay: overlayProp,
69
71
  overlayProps,
70
72
  style,
@@ -102,6 +104,7 @@ const DrawerContent = React__namespace.forwardRef(
102
104
  "data-[state=open]:duration-200 data-[state=open]:ease-out",
103
105
  SLIDE_CLASSES[position],
104
106
  sizeClass,
107
+ variant === "sheet" && SHEET_CLASSES,
105
108
  className
106
109
  ),
107
110
  ...props,
@@ -113,15 +116,15 @@ const DrawerContent = React__namespace.forwardRef(
113
116
  );
114
117
  DrawerContent.displayName = "DrawerContent";
115
118
  const DrawerHeader = React__namespace.forwardRef(
116
- ({ className, showClose = true, closeLabel = "Close drawer", children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: cn.cn("flex items-start gap-2 p-4", className), ...props, children: [
119
+ ({ className, showClose = true, closeLabel = "Close drawer", children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: cn.cn("flex items-center gap-2 p-4", className), ...props, children: [
117
120
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children }),
118
121
  showClose && /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
119
122
  IconButton.IconButton,
120
123
  {
121
124
  icon: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon.CloseIcon, {}),
122
125
  "aria-label": closeLabel,
123
- variant: "tertiary",
124
- size: "24",
126
+ variant: "secondary",
127
+ size: "32",
125
128
  className: "shrink-0"
126
129
  }
127
130
  ) })
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.cjs","sources":["../../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useSuppressClickAfterDrag } from \"../../utils/useSuppressClickAfterDrag\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** The side from which the drawer slides in. */\nexport type DrawerPosition = \"left\" | \"right\" | \"top\" | \"bottom\";\n\n/** Size presets for the drawer panel. Maps to max-width (left/right) or max-height (top/bottom). */\nexport type DrawerSize = \"sm\" | \"md\" | \"lg\" | \"full\";\n\n/**\n * Props for the {@link Drawer} root component.\n *\n * Inherits `open`, `onOpenChange`, and `defaultOpen` from Radix Dialog.Root.\n *\n * The Radix `modal` prop (default `true`) can also be passed to create a\n * non-modal drawer — useful for persistent side navigation that does not\n * block interaction with the rest of the page.\n *\n * **`overlay` behaviour:** When `overlay` is `false`, the component\n * automatically sets `modal={false}` on the underlying Radix Dialog.Root.\n * A modal dialog without a visible overlay is confusing because focus is\n * still trapped and scroll is blocked even though the page appears\n * interactive. Passing `modal={true}` explicitly will override this.\n */\nexport interface DrawerProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> {\n /**\n * Whether the default {@link DrawerOverlay} is rendered.\n * When `false`, `modal` is automatically set to `false` as well\n * (unless explicitly overridden) so focus-trap and scroll-lock are disabled.\n * @default true\n */\n overlay?: boolean;\n}\n\nconst DrawerContext = React.createContext<{ overlay: boolean }>({\n overlay: true,\n});\n\n/**\n * Root component that manages open/close state for a drawer.\n * Wraps Radix Dialog.Root.\n *\n * @example\n * ```tsx\n * <Drawer>\n * <DrawerTrigger>Open</DrawerTrigger>\n * <DrawerContent position=\"right\">\n * <DrawerHeader>\n * <DrawerTitle>Settings</DrawerTitle>\n * <DrawerDescription>Adjust your preferences.</DrawerDescription>\n * </DrawerHeader>\n * <p>Content goes here.</p>\n * <DrawerFooter>\n * <DrawerClose>Done</DrawerClose>\n * </DrawerFooter>\n * </DrawerContent>\n * </Drawer>\n * ```\n */\nexport function Drawer({ overlay = true, modal, children, ...props }: DrawerProps) {\n const resolvedModal = modal ?? (overlay ? undefined : false);\n return (\n <DrawerContext.Provider value={{ overlay }}>\n <DialogPrimitive.Root modal={resolvedModal} {...props}>\n {children}\n </DialogPrimitive.Root>\n </DrawerContext.Provider>\n );\n}\nDrawer.displayName = \"Drawer\";\n\n/** Props for the {@link DrawerTrigger} component. */\nexport type DrawerTriggerProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;\n\n/**\n * The element that opens the drawer when clicked.\n *\n * On touch / pen, a press-and-release that crosses a small movement threshold\n * is treated as a drag and the resulting synthetic click is suppressed —\n * defends against Android Chrome opening the drawer on a scroll-drag-end.\n */\nexport const DrawerTrigger = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Trigger>,\n DrawerTriggerProps\n>((props, ref) => <DialogPrimitive.Trigger ref={ref} {...useSuppressClickAfterDrag(props)} />);\nDrawerTrigger.displayName = \"DrawerTrigger\";\n\n/** Props for the {@link DrawerClose} component. */\nexport type DrawerCloseProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>;\n\n/** Closes the drawer when clicked. Can be placed anywhere inside the drawer. */\nexport const DrawerClose = DialogPrimitive.Close;\nDrawerClose.displayName = \"DrawerClose\";\n\n/** Props for the {@link DrawerOverlay} component. */\nexport interface DrawerOverlayProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> {}\n\n/**\n * A translucent backdrop rendered behind the drawer content.\n * Clicking the overlay closes the drawer by default.\n */\nexport const DrawerOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n DrawerOverlayProps\n>(({ className, style, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n className={cn(\n \"fixed inset-0 bg-background-overlay-default\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:fade-in-0 data-[state=open]:duration-200 data-[state=open]:ease-out\",\n className,\n )}\n {...props}\n />\n));\nDrawerOverlay.displayName = \"DrawerOverlay\";\n\n/**\n * Slide-in animation classes keyed by position.\n * Uses Tailwind animate utilities (animate-in / animate-out).\n */\nconst SLIDE_CLASSES: Record<DrawerPosition, string> = {\n right:\n \"inset-y-0 right-0 h-full w-2/3 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right\",\n left: \"inset-y-0 left-0 h-full w-2/3 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left\",\n top: \"inset-x-0 top-0 w-full data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n bottom:\n \"inset-x-0 bottom-0 w-full rounded-t-xs data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n};\n\n/** Props for the {@link DrawerContent} component. */\nexport interface DrawerContentProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /**\n * The edge from which the drawer slides in.\n *\n * Named `position` (rather than `side`) to avoid confusion with the CSS\n * `side` concept used by Radix Popover/Tooltip and to better convey the\n * spatial relationship of the drawer to the viewport.\n *\n * @default \"right\"\n */\n position?: DrawerPosition;\n /**\n * Controls the maximum extent of the drawer panel.\n * For left/right drawers this sets `max-width`; for top/bottom it sets `max-height`.\n * @default \"sm\"\n */\n size?: DrawerSize;\n /**\n * Whether to render the default {@link DrawerOverlay} behind the content.\n * Set to `false` to provide your own overlay or omit it entirely.\n *\n * Prefer setting `overlay` on the {@link Drawer} root instead so that\n * `modal` is also adjusted automatically.\n *\n * @default true\n */\n overlay?: boolean;\n /** Props forwarded to the default {@link DrawerOverlay} when `overlay` is `true`. */\n overlayProps?: DrawerOverlayProps;\n}\n\n/**\n * The panel that slides in from the chosen edge. Renders inside a portal with\n * an overlay backdrop by default.\n *\n * Includes focus-trap, `aria-describedby`, and Escape-to-close from Radix Dialog.\n */\nexport const DrawerContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n DrawerContentProps\n>(\n (\n {\n className,\n position = \"right\",\n size = \"sm\",\n overlay: overlayProp,\n overlayProps,\n style,\n children,\n ...props\n },\n ref,\n ) => {\n const ctx = React.useContext(DrawerContext);\n const overlay = overlayProp ?? ctx.overlay;\n const isHorizontal = position === \"left\" || position === \"right\";\n const sizeClass = isHorizontal\n ? (\n {\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n full: \"max-w-full\",\n } as const\n )[size]\n : (\n {\n sm: \"max-h-[24rem]\",\n md: \"max-h-[28rem]\",\n lg: \"max-h-[32rem]\",\n full: \"max-h-full\",\n } as const\n )[size];\n\n return (\n <DialogPrimitive.Portal>\n {overlay && <DrawerOverlay {...overlayProps} />}\n <DialogPrimitive.Content\n ref={ref}\n style={{\n zIndex: \"calc(var(--fanvue-ui-portal-z-index, 50) + 1)\",\n ...style,\n }}\n className={cn(\n \"fixed flex flex-col bg-surface-secondary shadow-lg outline-none backdrop-blur-lg\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:duration-200 data-[state=open]:ease-out\",\n SLIDE_CLASSES[position],\n sizeClass,\n className,\n )}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n },\n);\nDrawerContent.displayName = \"DrawerContent\";\n\n/** Props for the {@link DrawerHeader} component. */\nexport interface DrawerHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Whether to show a built-in close (X) button. @default true */\n showClose?: boolean;\n /** Accessible label for the close button. @default \"Close drawer\" */\n closeLabel?: string;\n}\n\n/**\n * A semantic header area for the drawer, typically containing a title and description.\n * Renders a built-in close button by default.\n */\nexport const DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, showClose = true, closeLabel = \"Close drawer\", children, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex items-start gap-2 p-4\", className)} {...props}>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1.5\">{children}</div>\n {showClose && (\n <DialogPrimitive.Close asChild>\n <IconButton\n icon={<CloseIcon />}\n aria-label={closeLabel}\n variant=\"tertiary\"\n size=\"24\"\n className=\"shrink-0\"\n />\n </DialogPrimitive.Close>\n )}\n </div>\n ),\n);\nDrawerHeader.displayName = \"DrawerHeader\";\n\n/** Props for the {@link DrawerFooter} component. */\nexport interface DrawerFooterProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/** A semantic footer area for the drawer, typically containing action buttons. */\nexport const DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex flex-col gap-2 p-4\", className)} {...props} />\n ),\n);\nDrawerFooter.displayName = \"DrawerFooter\";\n\n/** Props for the {@link DrawerTitle} component. */\nexport interface DrawerTitleProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> {}\n\n/** An accessible title for the drawer. Required for screen readers. */\nexport const DrawerTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n DrawerTitleProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"typography-body-default-16px-semibold truncate text-content-primary\", className)}\n {...props}\n />\n));\nDrawerTitle.displayName = \"DrawerTitle\";\n\n/** Props for the {@link DrawerDescription} component. */\nexport interface DrawerDescriptionProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> {}\n\n/** An accessible description for the drawer, providing supplementary context. */\nexport const DrawerDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n DrawerDescriptionProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"typography-body-small-14px-regular text-content-secondary\", className)}\n {...props}\n />\n));\nDrawerDescription.displayName = \"DrawerDescription\";\n"],"names":["React","jsx","DialogPrimitive","useSuppressClickAfterDrag","cn","jsxs","IconButton","CloseIcon"],"mappings":"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"}
1
+ {"version":3,"file":"Drawer.cjs","sources":["../../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useSuppressClickAfterDrag } from \"../../utils/useSuppressClickAfterDrag\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** The side from which the drawer slides in. */\nexport type DrawerPosition = \"left\" | \"right\" | \"top\" | \"bottom\";\n\n/** Size presets for the drawer panel. Maps to max-width (left/right) or max-height (top/bottom). */\nexport type DrawerSize = \"sm\" | \"md\" | \"lg\" | \"full\";\n\n/**\n * Visual treatment of the drawer panel.\n *\n * - `\"panel\"` (default) — the standard edge-anchored surface.\n * - `\"sheet\"` — a bottom-sheet treatment matching the modal surface: large\n * top-only radius (32px), modal background/stroke, and menu blur+shadow.\n * Intended for `position=\"bottom\"`.\n */\nexport type DrawerVariant = \"panel\" | \"sheet\";\n\n/**\n * Shared surface classes for the `\"sheet\"` variant. Mirrors the mobile sheet\n * treatment used by {@link Dialog} so bottom sheets are visually consistent.\n */\nconst SHEET_CLASSES =\n \"rounded-t-xl border border-modal-stroke bg-modal-background shadow-blur-menu backdrop-blur-[4px]\";\n\n/**\n * Props for the {@link Drawer} root component.\n *\n * Inherits `open`, `onOpenChange`, and `defaultOpen` from Radix Dialog.Root.\n *\n * The Radix `modal` prop (default `true`) can also be passed to create a\n * non-modal drawer — useful for persistent side navigation that does not\n * block interaction with the rest of the page.\n *\n * **`overlay` behaviour:** When `overlay` is `false`, the component\n * automatically sets `modal={false}` on the underlying Radix Dialog.Root.\n * A modal dialog without a visible overlay is confusing because focus is\n * still trapped and scroll is blocked even though the page appears\n * interactive. Passing `modal={true}` explicitly will override this.\n */\nexport interface DrawerProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> {\n /**\n * Whether the default {@link DrawerOverlay} is rendered.\n * When `false`, `modal` is automatically set to `false` as well\n * (unless explicitly overridden) so focus-trap and scroll-lock are disabled.\n * @default true\n */\n overlay?: boolean;\n}\n\nconst DrawerContext = React.createContext<{ overlay: boolean }>({\n overlay: true,\n});\n\n/**\n * Root component that manages open/close state for a drawer.\n * Wraps Radix Dialog.Root.\n *\n * @example\n * ```tsx\n * <Drawer>\n * <DrawerTrigger>Open</DrawerTrigger>\n * <DrawerContent position=\"right\">\n * <DrawerHeader>\n * <DrawerTitle>Settings</DrawerTitle>\n * <DrawerDescription>Adjust your preferences.</DrawerDescription>\n * </DrawerHeader>\n * <p>Content goes here.</p>\n * <DrawerFooter>\n * <DrawerClose>Done</DrawerClose>\n * </DrawerFooter>\n * </DrawerContent>\n * </Drawer>\n * ```\n */\nexport function Drawer({ overlay = true, modal, children, ...props }: DrawerProps) {\n const resolvedModal = modal ?? (overlay ? undefined : false);\n return (\n <DrawerContext.Provider value={{ overlay }}>\n <DialogPrimitive.Root modal={resolvedModal} {...props}>\n {children}\n </DialogPrimitive.Root>\n </DrawerContext.Provider>\n );\n}\nDrawer.displayName = \"Drawer\";\n\n/** Props for the {@link DrawerTrigger} component. */\nexport type DrawerTriggerProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;\n\n/**\n * The element that opens the drawer when clicked.\n *\n * On touch / pen, a press-and-release that crosses a small movement threshold\n * is treated as a drag and the resulting synthetic click is suppressed —\n * defends against Android Chrome opening the drawer on a scroll-drag-end.\n */\nexport const DrawerTrigger = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Trigger>,\n DrawerTriggerProps\n>((props, ref) => <DialogPrimitive.Trigger ref={ref} {...useSuppressClickAfterDrag(props)} />);\nDrawerTrigger.displayName = \"DrawerTrigger\";\n\n/** Props for the {@link DrawerClose} component. */\nexport type DrawerCloseProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>;\n\n/** Closes the drawer when clicked. Can be placed anywhere inside the drawer. */\nexport const DrawerClose = DialogPrimitive.Close;\nDrawerClose.displayName = \"DrawerClose\";\n\n/** Props for the {@link DrawerOverlay} component. */\nexport interface DrawerOverlayProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> {}\n\n/**\n * A translucent backdrop rendered behind the drawer content.\n * Clicking the overlay closes the drawer by default.\n */\nexport const DrawerOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n DrawerOverlayProps\n>(({ className, style, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n className={cn(\n \"fixed inset-0 bg-background-overlay-default\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:fade-in-0 data-[state=open]:duration-200 data-[state=open]:ease-out\",\n className,\n )}\n {...props}\n />\n));\nDrawerOverlay.displayName = \"DrawerOverlay\";\n\n/**\n * Slide-in animation classes keyed by position.\n * Uses Tailwind animate utilities (animate-in / animate-out).\n */\nconst SLIDE_CLASSES: Record<DrawerPosition, string> = {\n right:\n \"inset-y-0 right-0 h-full w-2/3 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right\",\n left: \"inset-y-0 left-0 h-full w-2/3 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left\",\n top: \"inset-x-0 top-0 w-full data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n bottom:\n \"inset-x-0 bottom-0 w-full rounded-t-xs data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n};\n\n/** Props for the {@link DrawerContent} component. */\nexport interface DrawerContentProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /**\n * The edge from which the drawer slides in.\n *\n * Named `position` (rather than `side`) to avoid confusion with the CSS\n * `side` concept used by Radix Popover/Tooltip and to better convey the\n * spatial relationship of the drawer to the viewport.\n *\n * @default \"right\"\n */\n position?: DrawerPosition;\n /**\n * Controls the maximum extent of the drawer panel.\n * For left/right drawers this sets `max-width`; for top/bottom it sets `max-height`.\n * @default \"sm\"\n */\n size?: DrawerSize;\n /**\n * Visual treatment of the panel. Use `\"sheet\"` (with `position=\"bottom\"`) for\n * a bottom sheet with the modal surface treatment. @default \"panel\"\n */\n variant?: DrawerVariant;\n /**\n * Whether to render the default {@link DrawerOverlay} behind the content.\n * Set to `false` to provide your own overlay or omit it entirely.\n *\n * Prefer setting `overlay` on the {@link Drawer} root instead so that\n * `modal` is also adjusted automatically.\n *\n * @default true\n */\n overlay?: boolean;\n /** Props forwarded to the default {@link DrawerOverlay} when `overlay` is `true`. */\n overlayProps?: DrawerOverlayProps;\n}\n\n/**\n * The panel that slides in from the chosen edge. Renders inside a portal with\n * an overlay backdrop by default.\n *\n * Includes focus-trap, `aria-describedby`, and Escape-to-close from Radix Dialog.\n */\nexport const DrawerContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n DrawerContentProps\n>(\n (\n {\n className,\n position = \"right\",\n size = \"sm\",\n variant = \"panel\",\n overlay: overlayProp,\n overlayProps,\n style,\n children,\n ...props\n },\n ref,\n ) => {\n const ctx = React.useContext(DrawerContext);\n const overlay = overlayProp ?? ctx.overlay;\n const isHorizontal = position === \"left\" || position === \"right\";\n const sizeClass = isHorizontal\n ? (\n {\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n full: \"max-w-full\",\n } as const\n )[size]\n : (\n {\n sm: \"max-h-[24rem]\",\n md: \"max-h-[28rem]\",\n lg: \"max-h-[32rem]\",\n full: \"max-h-full\",\n } as const\n )[size];\n\n return (\n <DialogPrimitive.Portal>\n {overlay && <DrawerOverlay {...overlayProps} />}\n <DialogPrimitive.Content\n ref={ref}\n style={{\n zIndex: \"calc(var(--fanvue-ui-portal-z-index, 50) + 1)\",\n ...style,\n }}\n className={cn(\n \"fixed flex flex-col bg-surface-secondary shadow-lg outline-none backdrop-blur-lg\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:duration-200 data-[state=open]:ease-out\",\n SLIDE_CLASSES[position],\n sizeClass,\n variant === \"sheet\" && SHEET_CLASSES,\n className,\n )}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n },\n);\nDrawerContent.displayName = \"DrawerContent\";\n\n/** Props for the {@link DrawerHeader} component. */\nexport interface DrawerHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Whether to show a built-in close (X) button. @default true */\n showClose?: boolean;\n /** Accessible label for the close button. @default \"Close drawer\" */\n closeLabel?: string;\n}\n\n/**\n * A semantic header area for the drawer, typically containing a title and description.\n * Renders a built-in close button by default.\n */\nexport const DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, showClose = true, closeLabel = \"Close drawer\", children, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex items-center gap-2 p-4\", className)} {...props}>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1.5\">{children}</div>\n {showClose && (\n <DialogPrimitive.Close asChild>\n <IconButton\n icon={<CloseIcon />}\n aria-label={closeLabel}\n variant=\"secondary\"\n size=\"32\"\n className=\"shrink-0\"\n />\n </DialogPrimitive.Close>\n )}\n </div>\n ),\n);\nDrawerHeader.displayName = \"DrawerHeader\";\n\n/** Props for the {@link DrawerFooter} component. */\nexport interface DrawerFooterProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/** A semantic footer area for the drawer, typically containing action buttons. */\nexport const DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex flex-col gap-2 p-4\", className)} {...props} />\n ),\n);\nDrawerFooter.displayName = \"DrawerFooter\";\n\n/** Props for the {@link DrawerTitle} component. */\nexport interface DrawerTitleProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> {}\n\n/** An accessible title for the drawer. Required for screen readers. */\nexport const DrawerTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n DrawerTitleProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"typography-body-default-16px-semibold truncate text-content-primary\", className)}\n {...props}\n />\n));\nDrawerTitle.displayName = \"DrawerTitle\";\n\n/** Props for the {@link DrawerDescription} component. */\nexport interface DrawerDescriptionProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> {}\n\n/** An accessible description for the drawer, providing supplementary context. */\nexport const DrawerDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n DrawerDescriptionProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"typography-body-small-14px-regular text-content-secondary\", className)}\n {...props}\n />\n));\nDrawerDescription.displayName = \"DrawerDescription\";\n"],"names":["React","jsx","DialogPrimitive","useSuppressClickAfterDrag","cn","jsxs","IconButton","CloseIcon"],"mappings":"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"}
@@ -175,6 +175,7 @@ const DropdownMenuItem = React__namespace.forwardRef(
175
175
  destructive,
176
176
  leadingIcon,
177
177
  trailingIcon,
178
+ description,
178
179
  selected,
179
180
  className,
180
181
  children,
@@ -182,8 +183,10 @@ const DropdownMenuItem = React__namespace.forwardRef(
182
183
  ...props
183
184
  }, ref) => {
184
185
  const normalizedSize = SIZE_NORMALIZED[size];
186
+ const hasDescription = description != null;
185
187
  const itemClassName = cn.cn(
186
- "flex w-full cursor-pointer items-center gap-2 rounded-xs px-3 outline-none",
188
+ "flex w-full cursor-pointer gap-2 rounded-xs px-3 outline-none",
189
+ hasDescription ? "items-start" : "items-center",
187
190
  ITEM_SIZE_CLASSES[normalizedSize],
188
191
  "data-[highlighted]:bg-neutral-alphas-50",
189
192
  "data-[disabled]:cursor-not-allowed data-[disabled]:text-content-disabled",
@@ -198,10 +201,23 @@ const DropdownMenuItem = React__namespace.forwardRef(
198
201
  if (asChild) {
199
202
  return /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Item, { ref, asChild: true, className: itemClassName, ...props, children });
200
203
  }
204
+ const iconAlignClassName = hasDescription ? "flex shrink-0 items-center pt-1" : null;
201
205
  return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuPrimitive__namespace.Item, { ref, className: itemClassName, ...props, children: [
202
- leadingIcon,
203
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children }),
204
- trailingIcon
206
+ leadingIcon != null && (hasDescription ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: iconAlignClassName, children: leadingIcon }) : leadingIcon),
207
+ hasDescription ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
208
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children }),
209
+ /* @__PURE__ */ jsxRuntime.jsx(
210
+ "span",
211
+ {
212
+ className: cn.cn(
213
+ "typography-body-small-14px-regular truncate",
214
+ selected ? "text-content-primary-inverted" : "text-content-secondary"
215
+ ),
216
+ children: description
217
+ }
218
+ )
219
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children }),
220
+ trailingIcon != null && (hasDescription ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: iconAlignClassName, children: trailingIcon }) : trailingIcon)
205
221
  ] });
206
222
  }
207
223
  );