@umodeapporg/ui 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/dist/assets/umode-ui.css +0 -5
  2. package/dist/components/DateTime/DatePicker.d.ts +2 -0
  3. package/dist/components/DateTime/DatePicker.js +5 -2
  4. package/dist/components/DateTime/DatePicker.js.map +1 -1
  5. package/dist/components/DateTime/DateRangePicker.d.ts +2 -0
  6. package/dist/components/DateTime/DateRangePicker.js +3 -0
  7. package/dist/components/DateTime/DateRangePicker.js.map +1 -1
  8. package/dist/components/Form/ControlledButton.d.ts +1 -1
  9. package/dist/components/Form/ControlledButton.js +2 -2
  10. package/dist/components/Form/ControlledButton.js.map +1 -1
  11. package/dist/components/Form/ControlledCheckbox.d.ts +5 -1
  12. package/dist/components/Form/ControlledCheckbox.js +53 -39
  13. package/dist/components/Form/ControlledCheckbox.js.map +1 -1
  14. package/dist/components/Form/ControlledDialogPrimitives.js +17 -5
  15. package/dist/components/Form/ControlledDialogPrimitives.js.map +1 -1
  16. package/dist/components/Form/ControlledDrawer.js +1 -0
  17. package/dist/components/Form/ControlledDrawer.js.map +1 -1
  18. package/dist/components/Form/ControlledImage/index.js +2 -0
  19. package/dist/components/Form/ControlledImage/index.js.map +1 -1
  20. package/dist/components/Form/ControlledInput.d.ts +1 -1
  21. package/dist/components/Form/ControlledInput.js +4 -4
  22. package/dist/components/Form/ControlledInput.js.map +1 -1
  23. package/dist/components/Form/ControlledSelect.js +1 -1
  24. package/dist/components/Form/ControlledSelect.js.map +1 -1
  25. package/dist/components/Form/ControlledTabs.js +17 -3
  26. package/dist/components/Form/ControlledTabs.js.map +1 -1
  27. package/dist/components/Form/ControlledTabsPrimitives.js +6 -6
  28. package/dist/components/Form/ControlledTabsPrimitives.js.map +1 -1
  29. package/dist/components/Form/ControlledTextarea.js +2 -2
  30. package/dist/components/Form/ControlledTextarea.js.map +1 -1
  31. package/dist/components/Form/ControlledTimeline.js +11 -8
  32. package/dist/components/Form/ControlledTimeline.js.map +1 -1
  33. package/dist/components/Form/ControlledVoiceTextarea.d.ts +2 -0
  34. package/dist/components/Form/ControlledVoiceTextarea.js +3 -0
  35. package/dist/components/Form/ControlledVoiceTextarea.js.map +1 -1
  36. package/dist/components/Form/FieldLabel.d.ts +16 -0
  37. package/dist/components/Form/FieldLabel.js +7 -0
  38. package/dist/components/Form/FieldLabel.js.map +1 -0
  39. package/dist/components/Form/RichTextEditor.js +15 -1
  40. package/dist/components/Form/RichTextEditor.js.map +1 -1
  41. package/dist/components/Form/attachments/AttachmentPreviewList.js +3 -0
  42. package/dist/components/Form/attachments/AttachmentPreviewList.js.map +1 -1
  43. package/dist/components/Form/attachments/FileDropzone.js +3 -3
  44. package/dist/components/Form/attachments/FileDropzone.js.map +1 -1
  45. package/dist/components/Navigation/PageTitle.d.ts +6 -0
  46. package/dist/components/Navigation/PageTitle.js +6 -4
  47. package/dist/components/Navigation/PageTitle.js.map +1 -1
  48. package/dist/components/Search/filters/FiltersPanel.js +1 -0
  49. package/dist/components/Search/filters/FiltersPanel.js.map +1 -1
  50. package/dist/components/Search/filters/FiltersSummaryTooltip.js +2 -2
  51. package/dist/components/Search/filters/FiltersSummaryTooltip.js.map +1 -1
  52. package/dist/components/Shell/LanguageToggle.js +1 -1
  53. package/dist/components/Shell/LanguageToggle.js.map +1 -1
  54. package/dist/components/Shell/SidebarMenu.js +9 -1
  55. package/dist/components/Shell/SidebarMenu.js.map +1 -1
  56. package/dist/components/Shell/ThemeToggle.js +2 -1
  57. package/dist/components/Shell/ThemeToggle.js.map +1 -1
  58. package/dist/components/UI/BaseDataCard.d.ts +2 -0
  59. package/dist/components/UI/BaseDataCard.js +6 -0
  60. package/dist/components/UI/BaseDataCard.js.map +1 -1
  61. package/dist/components/UI/BulkActionBar.js +2 -0
  62. package/dist/components/UI/BulkActionBar.js.map +1 -1
  63. package/dist/components/UI/Chips/OutlinedChip.js +13 -6
  64. package/dist/components/UI/Chips/OutlinedChip.js.map +1 -1
  65. package/dist/components/UI/ColorSwatch.js +2 -2
  66. package/dist/components/UI/ColorSwatch.js.map +1 -1
  67. package/dist/components/UI/ControlledAccordion.js +10 -4
  68. package/dist/components/UI/ControlledAccordion.js.map +1 -1
  69. package/dist/components/UI/ControlledCollapse.js +2 -2
  70. package/dist/components/UI/ControlledCollapse.js.map +1 -1
  71. package/dist/components/UI/ControlledMenu.js +3 -3
  72. package/dist/components/UI/ControlledMenu.js.map +1 -1
  73. package/dist/components/UI/ControlledPopover.d.ts +12 -0
  74. package/dist/components/UI/ControlledPopover.js +35 -5
  75. package/dist/components/UI/ControlledPopover.js.map +1 -1
  76. package/dist/components/UI/ControlledProgress.js +3 -3
  77. package/dist/components/UI/ControlledProgress.js.map +1 -1
  78. package/dist/components/UI/ControlledSlider.d.ts +2 -0
  79. package/dist/components/UI/ControlledSlider.js +8 -3
  80. package/dist/components/UI/ControlledSlider.js.map +1 -1
  81. package/dist/components/UI/ControlledTooltip.js +7 -3
  82. package/dist/components/UI/ControlledTooltip.js.map +1 -1
  83. package/dist/components/UI/DataTable/DataTable.js +1 -1
  84. package/dist/components/UI/DataTable/DataTableBody.js +4 -0
  85. package/dist/components/UI/DataTable/DataTableBody.js.map +1 -1
  86. package/dist/components/UI/DataTable/DataTableBulkEditRowsPicker.js +1 -1
  87. package/dist/components/UI/DataTable/DataTableColumnToggle.js +3 -1
  88. package/dist/components/UI/DataTable/DataTableColumnToggle.js.map +1 -1
  89. package/dist/components/UI/DataTable/DataTableFilters.js +1 -1
  90. package/dist/components/UI/DataTable/DataTablePagination.js +4 -0
  91. package/dist/components/UI/DataTable/DataTablePagination.js.map +1 -1
  92. package/dist/components/UI/DataTable/DataTableStates.js +2 -2
  93. package/dist/components/UI/DataTable/DataTableStates.js.map +1 -1
  94. package/dist/components/UI/DataTable/DataTableToolbar.js +1 -1
  95. package/dist/components/UI/DataTable/applyDataTableFilters.js +1 -1
  96. package/dist/components/UI/Flow/FlowBlockNode.js +1 -0
  97. package/dist/components/UI/Flow/FlowBlockNode.js.map +1 -1
  98. package/dist/components/UI/Flow/FlowCanvas.js +2 -2
  99. package/dist/components/UI/Flow/FlowCanvas.js.map +1 -1
  100. package/dist/components/UI/Flow/FlowConnectionEdge.js +1 -0
  101. package/dist/components/UI/Flow/FlowConnectionEdge.js.map +1 -1
  102. package/dist/components/UI/Flow/FlowInspector.js +3 -2
  103. package/dist/components/UI/Flow/FlowInspector.js.map +1 -1
  104. package/dist/components/UI/Flow/FlowIssueList.js +2 -2
  105. package/dist/components/UI/Flow/FlowIssueList.js.map +1 -1
  106. package/dist/components/UI/Flow/FlowLaneMap.js +2 -2
  107. package/dist/components/UI/Flow/FlowLaneMap.js.map +1 -1
  108. package/dist/components/UI/Flow/FlowResizablePanel.js +3 -2
  109. package/dist/components/UI/Flow/FlowResizablePanel.js.map +1 -1
  110. package/dist/components/UI/Flow/FlowZoomControls.js +1 -0
  111. package/dist/components/UI/Flow/FlowZoomControls.js.map +1 -1
  112. package/dist/components/UI/HorizontalScrollArea.d.ts +1 -1
  113. package/dist/components/UI/HorizontalScrollArea.js +1 -0
  114. package/dist/components/UI/HorizontalScrollArea.js.map +1 -1
  115. package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js +1 -0
  116. package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js.map +1 -1
  117. package/dist/components/UI/ImagePlaceholder.js +5 -2
  118. package/dist/components/UI/ImagePlaceholder.js.map +1 -1
  119. package/dist/components/UI/ImageZoomOverlay.js +1 -0
  120. package/dist/components/UI/ImageZoomOverlay.js.map +1 -1
  121. package/dist/components/UI/MaterialSymbol/index.js +5 -0
  122. package/dist/components/UI/MaterialSymbol/index.js.map +1 -1
  123. package/dist/components/UI/NoDataFound/index.js +3 -3
  124. package/dist/components/UI/NoDataFound/index.js.map +1 -1
  125. package/dist/components/UI/RichTextContent.js +3 -3
  126. package/dist/components/UI/RichTextContent.js.map +1 -1
  127. package/dist/components/UI/ThreeDots.js +15 -11
  128. package/dist/components/UI/ThreeDots.js.map +1 -1
  129. package/dist/helpers/mergeClasses.d.ts +7 -3
  130. package/dist/helpers/mergeClasses.js +23 -1
  131. package/dist/helpers/mergeClasses.js.map +1 -1
  132. package/dist/hooks/useVoiceTranscription.js +5 -2
  133. package/dist/hooks/useVoiceTranscription.js.map +1 -1
  134. package/dist/index.d.ts +2 -0
  135. package/dist/index.js +58 -54
  136. package/dist/index.js.map +1 -1
  137. package/dist/locales/en/ui.json.d.ts +8 -3
  138. package/dist/locales/en/ui.json.js +3 -3
  139. package/dist/locales/index.d.ts +10 -0
  140. package/dist/locales/pt/ui.json.d.ts +8 -3
  141. package/dist/locales/pt/ui.json.js +3 -3
  142. package/package.json +1 -1
  143. package/src/assets/styles/base.css +7 -1
  144. package/tailwind-plugins/glass.js +23 -28
@@ -339,6 +339,7 @@ const RichTextEditor = forwardRef(function RichTextEditor2({
339
339
  return /* @__PURE__ */ jsx(ControlledTooltip, { content: t(tooltipKey), children: /* @__PURE__ */ jsx(
340
340
  ControlledButton,
341
341
  {
342
+ "aria-label": t(tooltipKey),
342
343
  defaultHtmlButton: true,
343
344
  disabled,
344
345
  onMouseDown: (event) => {
@@ -362,6 +363,7 @@ const RichTextEditor = forwardRef(function RichTextEditor2({
362
363
  /* @__PURE__ */ jsx(ControlledTooltip, { content: t("createTask.rich.textColor"), children: /* @__PURE__ */ jsx(ControlledPopoverHandler, { children: /* @__PURE__ */ jsx(
363
364
  ControlledButton,
364
365
  {
366
+ "aria-label": t("createTask.rich.textColor"),
365
367
  defaultHtmlButton: true,
366
368
  disabled,
367
369
  onMouseDown: (event) => {
@@ -379,6 +381,7 @@ const RichTextEditor = forwardRef(function RichTextEditor2({
379
381
  /* @__PURE__ */ jsx(ControlledPopoverContent, { className: "zIndexMax w-44 p-2", children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: RICH_TEXT_COLORS.map((color) => /* @__PURE__ */ jsx(ControlledTooltip, { content: t(`createTask.rich.colors.${color}`), children: /* @__PURE__ */ jsx(
380
382
  ControlledButton,
381
383
  {
384
+ "aria-label": t(`createTask.rich.colors.${color}`),
382
385
  defaultHtmlButton: true,
383
386
  "aria-pressed": activeColor === color,
384
387
  onMouseDown: (event) => {
@@ -405,6 +408,7 @@ const RichTextEditor = forwardRef(function RichTextEditor2({
405
408
  /* @__PURE__ */ jsx(ControlledTooltip, { content: t("createTask.rich.link"), children: /* @__PURE__ */ jsx(
406
409
  ControlledButton,
407
410
  {
411
+ "aria-label": t("createTask.rich.link"),
408
412
  defaultHtmlButton: true,
409
413
  disabled,
410
414
  onMouseDown: (event) => {
@@ -495,6 +499,7 @@ const RichTextEditor = forwardRef(function RichTextEditor2({
495
499
  formattingToggle && /* @__PURE__ */ jsx(ControlledTooltip, { content: t("followUp.chat.formatting"), className: "z-[9999]", children: /* @__PURE__ */ jsx(
496
500
  ControlledButton,
497
501
  {
502
+ "aria-label": t("followUp.chat.formatting"),
498
503
  defaultHtmlButton: true,
499
504
  disabled,
500
505
  onClick: () => setFormattingOpen((previous) => !previous),
@@ -504,7 +509,16 @@ const RichTextEditor = forwardRef(function RichTextEditor2({
504
509
  }
505
510
  ) }),
506
511
  attachmentsController && /* @__PURE__ */ jsxs(ControlledMenuRoot, { placement: "top-start", children: [
507
- /* @__PURE__ */ jsx(ControlledTooltip, { content: t("followUp.attachments.attach"), className: "z-[9999]", children: /* @__PURE__ */ jsx(ControlledMenuHandler, { children: /* @__PURE__ */ jsx(ControlledButton, { defaultHtmlButton: true, disabled, className: ACTION_BUTTON_CLASS, children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "attach_file", size: 18 }) }) }) }),
512
+ /* @__PURE__ */ jsx(ControlledTooltip, { content: t("followUp.attachments.attach"), className: "z-[9999]", children: /* @__PURE__ */ jsx(ControlledMenuHandler, { children: /* @__PURE__ */ jsx(
513
+ ControlledButton,
514
+ {
515
+ defaultHtmlButton: true,
516
+ "aria-label": t("followUp.attachments.attach"),
517
+ disabled,
518
+ className: ACTION_BUTTON_CLASS,
519
+ children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "attach_file", size: 18 })
520
+ }
521
+ ) }) }),
508
522
  /* @__PURE__ */ jsx(ControlledMenuList, { className: "zIndexMax p-1", children: attachMenuActions.map((action) => /* @__PURE__ */ jsxs(ControlledMenuItem, { className: "flex items-center gap-x-2", onClick: action.onClick, children: [
509
523
  /* @__PURE__ */ jsx(MaterialSymbol, { icon: action.icon, size: 18 }),
510
524
  action.label
@@ -1 +1 @@
1
- {"version":3,"file":"RichTextEditor.js","sources":["../../../src/components/Form/RichTextEditor.tsx"],"sourcesContent":["import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport {\n ControlledMenuHandler,\n ControlledMenuItem,\n ControlledMenuList,\n ControlledMenuRoot,\n} from '@/components/UI/ControlledMenu';\nimport {\n ControlledPopoverContent,\n ControlledPopoverHandler,\n ControlledPopoverRoot,\n} from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport {\n activeRichTextColor,\n applyRichTextColor,\n buildFileChipMarkup,\n normalizeRichTextColorNodes,\n RICH_TEXT_COLOR_SWATCH_CLASS,\n RICH_TEXT_COLORS,\n TRichTextColor,\n} from '@/helpers/richText';\nimport { IUseFileAttachmentsResult, TFileUploader, useFileAttachments } from '@/hooks/useFileAttachments';\n\nimport { MaterialSymbol, SymbolCodepoints } from '../UI/MaterialSymbol';\n\nimport AttachmentPreviewList, { IStagedImageAction } from './attachments/AttachmentPreviewList';\nimport FileDropzone from './attachments/FileDropzone';\nimport ControlledButton from './ControlledButton';\nimport ControlledInput from './ControlledInput';\n\n/** An extra entry in the attach menu, for a source only the host knows about (a camera dialog). */\nexport interface IRichTextAttachAction {\n key: string;\n icon: SymbolCodepoints;\n label: string;\n onClick: () => void;\n}\n\n/**\n * Files travel BESIDE the text: the host owns a list of attachments of its own (a chat message\n * has one) and reads `controller.readyAttachments` when it saves.\n */\nexport interface IRichTextAttachmentsConfig {\n controller: IUseFileAttachmentsResult;\n extraActions?: Array<IRichTextAttachAction>;\n /**\n * Actions offered on a staged IMAGE before it is sent, so a picture is corrected on its way out\n * instead of being sent and then corrected as a second file.\n */\n imageActions?: Array<IStagedImageAction>;\n /** The host already renders the staged previews somewhere else. */\n hidePreviews?: boolean;\n /** The host already wraps a wider drop target around this editor. */\n hideDropzone?: boolean;\n}\n\n/**\n * Files travel INSIDE the text: the upload result is written into the body as a chip, so the\n * reference persists anywhere the HTML itself is stored and no host needs a new field.\n */\nexport interface IRichTextInlineUploadConfig {\n uploadFile?: TFileUploader;\n maxFiles?: number;\n maxFileSizeBytes?: number;\n}\n\ninterface IRichTextEditorProps {\n value?: string;\n onChange?: (html: string) => void;\n placeholder?: string;\n disabled?: boolean;\n error?: boolean;\n className?: string;\n minHeight?: number;\n /** Rendered inside the toolbar row, right-aligned: host actions that belong to the field. */\n toolbarSlot?: React.ReactNode;\n /** Rendered in the editor's own bottom bar, after the controls the editor owns. */\n actionsSlot?: React.ReactNode;\n onKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;\n /** Fires when the editable area loses focus, for hosts that persist on blur. */\n onBlur?: () => void;\n /**\n * Fires with the full HTML when the editor changes the content on its own, which today means\n * an inline upload landing after the field already lost focus. A host that persists on blur\n * would otherwise never hear about the file it just attached.\n */\n onCommit?: (html: string) => void;\n /** 'bare' drops the border and focus ring: the host owns the chrome around the field. */\n variant?: 'default' | 'bare';\n /** Hosts with their own action bar (the chat composer) toggle the formatting row on demand. */\n showToolbar?: boolean;\n /** Renders the formatting toggle in the bottom bar and lets the editor own the row's state. */\n formattingToggle?: boolean;\n attachments?: IRichTextAttachmentsConfig;\n inlineUpload?: IRichTextInlineUploadConfig;\n}\n\nexport interface IRichTextEditorHandle {\n focus: () => void;\n /** Viewport rect of the caret, for anchoring a popover to what is being typed. */\n getCaretRect: () => DOMRect | null;\n /**\n * Text typed after the trigger and before the caret, or null when the caret is not inside a\n * trigger word. Drives both \"is the mention menu relevant\" and \"what is being searched\".\n * Spaces are allowed inside the query (people have compound names); the host decides when a\n * spaced query stopped matching anyone and closes the menu.\n */\n getMentionQuery: (trigger: string) => string | null;\n /**\n * Deletes the trigger AND the query the user typed after it, then writes the markup in their\n * place, so picking someone from a mention menu replaces everything typed since the `@`\n * instead of stacking the mention after it.\n */\n replaceTriggerWith: (trigger: string, query: string, html: string) => void;\n}\n\ninterface IToolbarGroup {\n buttons: Array<IToolbarButton>;\n}\n\ninterface IToolbarButton {\n command: string;\n arg?: string;\n icon: SymbolCodepoints;\n tooltipKey: string;\n isBlock?: boolean;\n}\n\nconst TOOLBAR_GROUPS: Array<IToolbarGroup> = [\n {\n buttons: [\n { command: 'bold', icon: 'format_bold', tooltipKey: 'createTask.rich.bold' },\n { command: 'italic', icon: 'format_italic', tooltipKey: 'createTask.rich.italic' },\n { command: 'strikeThrough', icon: 'strikethrough_s', tooltipKey: 'createTask.rich.strikethrough' },\n ],\n },\n {\n buttons: [\n {\n command: 'insertUnorderedList',\n icon: 'format_list_bulleted',\n tooltipKey: 'createTask.rich.bulletList',\n isBlock: true,\n },\n {\n command: 'insertOrderedList',\n icon: 'format_list_numbered',\n tooltipKey: 'createTask.rich.numberedList',\n isBlock: true,\n },\n {\n command: 'formatBlock',\n arg: 'BLOCKQUOTE',\n icon: 'format_quote',\n tooltipKey: 'createTask.rich.quote',\n isBlock: true,\n },\n ],\n },\n {\n buttons: [\n { command: 'indent', icon: 'format_indent_increase', tooltipKey: 'createTask.rich.indent' },\n { command: 'outdent', icon: 'format_indent_decrease', tooltipKey: 'createTask.rich.outdent' },\n ],\n },\n];\n\nconst MENTION_QUERY_MAX_LENGTH = 40;\nconst IMAGE_INPUT_ACCEPT = 'image/*';\nconst DROPZONE_OVERLAY_CLASS = 'rounded-xl';\n\nconst TOOLBAR_BUTTON_CLASS = 'flex size-7 items-center justify-center rounded-lg transition-colors';\nconst TOOLBAR_BUTTON_IDLE_CLASS = 'text-foreground-subtle hover:bg-muted-hover hover:text-foreground-muted';\nconst TOOLBAR_BUTTON_ACTIVE_CLASS = 'bg-brand-soft text-brand-emphasis';\nconst ACTION_BUTTON_CLASS =\n 'flex size-8 items-center justify-center rounded-full text-foreground-faint transition-colors hover:bg-muted-hover hover:text-foreground-muted disabled:text-foreground-faint';\nconst ACTION_BUTTON_ACTIVE_CLASS =\n 'flex size-8 items-center justify-center rounded-full bg-brand-soft text-brand-emphasis transition-colors hover:bg-brand-soft';\n\nconst isCommandActive = (command: string, arg?: string): boolean => {\n try {\n if (arg) return document.queryCommandValue(command) === arg.toLowerCase();\n return document.queryCommandState(command);\n } catch {\n return false;\n }\n};\n\nconst RichTextEditor = forwardRef<IRichTextEditorHandle, IRichTextEditorProps>(function RichTextEditor(\n {\n value = '',\n onChange,\n placeholder,\n disabled = false,\n error = false,\n className,\n minHeight = 120,\n toolbarSlot,\n actionsSlot,\n onKeyDown,\n onBlur,\n onCommit,\n variant = 'default',\n showToolbar = true,\n formattingToggle = false,\n attachments,\n inlineUpload,\n },\n ref,\n) {\n const { t } = useTranslation('ui');\n const editorRef = useRef<HTMLDivElement>(null);\n const [activeCommands, setActiveCommands] = useState<Set<string>>(new Set());\n const [activeColor, setActiveColor] = useState<TRichTextColor>('default');\n const [colorOpen, setColorOpen] = useState(false);\n const [showLinkInput, setShowLinkInput] = useState(false);\n const [linkUrl, setLinkUrl] = useState('');\n const [isFocused, setIsFocused] = useState(false);\n const [formattingOpen, setFormattingOpen] = useState(false);\n const savedRangeRef = useRef<Range | null>(null);\n const isInternalUpdateRef = useRef(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const imageInputRef = useRef<HTMLInputElement>(null);\n const insertedStagingIdsRef = useRef<Set<string>>(new Set());\n const pendingColorRef = useRef<TRichTextColor>('default');\n\n const inlineController = useFileAttachments({\n uploadFile: inlineUpload?.uploadFile,\n maxFiles: inlineUpload?.maxFiles,\n maxFileSizeBytes: inlineUpload?.maxFileSizeBytes,\n });\n\n const attachmentsController = attachments?.controller ?? (inlineUpload ? inlineController : null);\n const inlineStagedAttachments = inlineController.stagedAttachments;\n const toolbarVisible = formattingToggle ? formattingOpen : showToolbar;\n\n useEffect(() => {\n const editor = editorRef.current;\n if (!editor) return;\n if (isInternalUpdateRef.current) return;\n if (editor.innerHTML !== value) {\n editor.innerHTML = value;\n }\n }, [value]);\n\n const emitChange = useCallback(() => {\n const editor = editorRef.current;\n if (!editor) return;\n isInternalUpdateRef.current = true;\n onChange?.(editor.innerHTML);\n isInternalUpdateRef.current = false;\n }, [onChange]);\n\n const syncActiveCommands = useCallback(() => {\n const active = new Set<string>();\n TOOLBAR_GROUPS.flatMap((group) => group.buttons).forEach(({ command, arg }) => {\n if (isCommandActive(command, arg)) active.add(arg ?? command);\n });\n setActiveCommands(active);\n\n const editor = editorRef.current;\n if (editor) setActiveColor(activeRichTextColor(editor));\n }, []);\n\n const execFormat = useCallback(\n (command: string, arg?: string) => {\n const editor = editorRef.current;\n if (!editor || disabled) return;\n editor.focus();\n document.execCommand(command, false, arg);\n emitChange();\n syncActiveCommands();\n },\n [disabled, emitChange, syncActiveCommands],\n );\n\n const saveRange = useCallback(() => {\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n savedRangeRef.current = selection.getRangeAt(0).cloneRange();\n }\n }, []);\n\n const restoreRange = useCallback(() => {\n const range = savedRangeRef.current;\n if (!range) return;\n const selection = window.getSelection();\n if (!selection) return;\n selection.removeAllRanges();\n selection.addRange(range);\n }, []);\n\n const applyLink = useCallback(() => {\n const url = linkUrl.trim();\n restoreRange();\n if (url) execFormat('createLink', url.startsWith('http') ? url : `https://${url}`);\n setShowLinkInput(false);\n setLinkUrl('');\n }, [linkUrl, restoreRange, execFormat]);\n\n const handleApplyColor = useCallback(\n (color: TRichTextColor) => {\n const editor = editorRef.current;\n if (!editor || disabled) return;\n\n editor.focus();\n restoreRange();\n pendingColorRef.current = color;\n applyRichTextColor(editor, color);\n emitChange();\n setActiveColor(color);\n setColorOpen(false);\n },\n [disabled, emitChange, restoreRange],\n );\n\n const insertHtmlAtCaret = useCallback(\n (html: string, commit = false) => {\n const editor = editorRef.current;\n if (!editor || disabled) return;\n\n const previouslyFocused = document.activeElement as HTMLElement | null;\n const editorHadFocus = editor.contains(previouslyFocused);\n\n editor.focus();\n restoreRange();\n\n const selection = window.getSelection();\n const caretIsInside = selection?.anchorNode ? editor.contains(selection.anchorNode) : false;\n\n if (!caretIsInside) {\n const range = document.createRange();\n range.selectNodeContents(editor);\n range.collapse(false);\n selection?.removeAllRanges();\n selection?.addRange(range);\n }\n\n document.execCommand('insertHTML', false, html);\n saveRange();\n emitChange();\n\n if (!editorHadFocus) previouslyFocused?.focus();\n if (commit) onCommit?.(editor.innerHTML);\n },\n [disabled, emitChange, onCommit, restoreRange, saveRange],\n );\n\n const inlineControllerRef = useRef(inlineController);\n inlineControllerRef.current = inlineController;\n\n // An inline upload lands in the body the moment it finishes, and leaves the staging list so\n // the progress card disappears with it.\n useEffect(() => {\n if (!inlineUpload) return;\n\n inlineStagedAttachments.forEach((staged) => {\n const uploaded = staged.uploaded;\n if (staged.status !== 'done' || !uploaded) return;\n\n if (!insertedStagingIdsRef.current.has(staged.stagingId)) {\n insertedStagingIdsRef.current.add(staged.stagingId);\n insertHtmlAtCaret(`${buildFileChipMarkup(uploaded)}&nbsp;`, true);\n }\n inlineControllerRef.current.removeAttachment(staged.stagingId);\n });\n }, [inlineUpload, inlineStagedAttachments, insertHtmlAtCaret]);\n\n const handleInput = useCallback(() => {\n const editor = editorRef.current;\n if (editor) normalizeRichTextColorNodes(editor, pendingColorRef.current);\n emitChange();\n syncActiveCommands();\n }, [emitChange, syncActiveCommands]);\n\n const handleFilesPicked = (event: React.ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(event.target.files ?? []);\n if (files.length) attachmentsController?.addFiles(files);\n event.target.value = '';\n };\n\n const handlePaste = (event: React.ClipboardEvent) => {\n if (!attachmentsController) return;\n\n const files = Array.from(event.clipboardData?.files ?? []);\n if (!files.length) return;\n\n event.preventDefault();\n event.stopPropagation();\n attachmentsController.addFiles(files);\n };\n\n useImperativeHandle(ref, () => ({\n focus: () => editorRef.current?.focus(),\n\n getCaretRect: () => {\n const selection = window.getSelection();\n if (!selection?.rangeCount) return null;\n\n const range = selection.getRangeAt(0).cloneRange();\n range.collapse(false);\n\n const [rect] = Array.from(range.getClientRects());\n if (rect) return rect;\n\n // A collapsed range inside an empty node measures nothing, so a throwaway marker gives it\n // something to measure. Removed in the same tick, before anything can observe it.\n const marker = document.createElement('span');\n marker.textContent = '\\u200b';\n range.insertNode(marker);\n const markerRect = marker.getBoundingClientRect();\n marker.remove();\n\n return markerRect;\n },\n\n getMentionQuery: (trigger) => {\n const selection = window.getSelection();\n if (!selection?.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n if (!range.collapsed || range.startContainer.nodeType !== Node.TEXT_NODE) return null;\n\n const textBeforeCaret = range.startContainer.textContent?.slice(0, range.startOffset) ?? '';\n const escapedTrigger = trigger.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = new RegExp(`(?:^|\\\\s)${escapedTrigger}([^${escapedTrigger}]*)$`).exec(textBeforeCaret);\n if (!match) return null;\n\n const query = match[1];\n if (query.startsWith(' ') || query.length > MENTION_QUERY_MAX_LENGTH) return null;\n\n return query;\n },\n\n replaceTriggerWith: (trigger, query, html) => {\n const editor = editorRef.current;\n if (!editor || disabled) return;\n\n editor.focus();\n restoreRange();\n\n const selection = window.getSelection();\n const range = selection?.rangeCount ? selection.getRangeAt(0) : null;\n\n if (range?.collapsed && range.startContainer.nodeType === Node.TEXT_NODE) {\n const textBeforeCaret = range.startContainer.textContent?.slice(0, range.startOffset) ?? '';\n const typed = `${trigger}${query}`;\n if (textBeforeCaret.endsWith(typed)) {\n range.setStart(range.startContainer, range.startOffset - typed.length);\n range.deleteContents();\n } else if (textBeforeCaret.endsWith(trigger)) {\n range.setStart(range.startContainer, range.startOffset - trigger.length);\n range.deleteContents();\n }\n }\n\n document.execCommand('insertHTML', false, `${html}&nbsp;`);\n emitChange();\n },\n }));\n\n const isEmpty = !editorRef.current?.textContent?.trim() && !editorRef.current?.querySelector('img, li, blockquote');\n\n const attachMenuActions = useMemo<Array<IRichTextAttachAction>>(\n () => [\n {\n key: 'file',\n icon: 'description',\n label: t('followUp.attachments.pickFile'),\n onClick: () => fileInputRef.current?.click(),\n },\n {\n key: 'image',\n icon: 'image',\n label: t('followUp.attachments.pickImage'),\n onClick: () => imageInputRef.current?.click(),\n },\n ...(attachments?.extraActions ?? []),\n ],\n [attachments?.extraActions, t],\n );\n\n const showPreviews = Boolean(attachmentsController) && !attachments?.hidePreviews;\n const showActionsBar = Boolean(attachmentsController) || Boolean(actionsSlot) || formattingToggle;\n\n const editorShell = (\n <div\n className={classNames(\n 'flex w-full flex-col transition-colors',\n variant === 'default' && 'rounded-xl border bg-surface',\n variant === 'default' &&\n (error\n ? 'border-danger-border'\n : isFocused\n ? 'border-brand-border ring-2 ring-brand-border'\n : 'border-border'),\n className,\n )}\n >\n {toolbarVisible && (\n <div\n className={classNames(\n 'flex flex-wrap items-center gap-px border-b px-2 py-1.5',\n error ? 'border-danger-border' : 'border-border-subtle',\n )}\n >\n {TOOLBAR_GROUPS.map((group, groupIndex) => (\n <div\n key={groupIndex}\n className={classNames(\n 'flex items-center gap-px',\n groupIndex < TOOLBAR_GROUPS.length - 1 && 'mr-1 border-r border-border pr-1',\n )}\n >\n {group.buttons.map(({ command, arg, icon, tooltipKey }) => {\n const key = arg ?? command;\n const isActive = activeCommands.has(key);\n\n return (\n <ControlledTooltip key={key} content={t(tooltipKey)}>\n <ControlledButton\n defaultHtmlButton\n disabled={disabled}\n onMouseDown={(event: React.MouseEvent) => {\n event.preventDefault();\n execFormat(command, arg);\n }}\n className={classNames(\n TOOLBAR_BUTTON_CLASS,\n isActive ? TOOLBAR_BUTTON_ACTIVE_CLASS : TOOLBAR_BUTTON_IDLE_CLASS,\n disabled && 'cursor-not-allowed opacity-40',\n )}\n >\n <MaterialSymbol icon={icon} size={18} />\n </ControlledButton>\n </ControlledTooltip>\n );\n })}\n </div>\n ))}\n\n <div className=\"flex items-center gap-px border-l border-border pl-1\">\n <ControlledPopoverRoot open={colorOpen} handler={setColorOpen} placement=\"bottom-start\">\n <ControlledTooltip content={t('createTask.rich.textColor')}>\n <ControlledPopoverHandler>\n <ControlledButton\n defaultHtmlButton\n disabled={disabled}\n onMouseDown={(event: React.MouseEvent) => {\n event.preventDefault();\n saveRange();\n }}\n className={classNames(\n TOOLBAR_BUTTON_CLASS,\n colorOpen ? TOOLBAR_BUTTON_ACTIVE_CLASS : TOOLBAR_BUTTON_IDLE_CLASS,\n disabled && 'cursor-not-allowed opacity-40',\n )}\n >\n <MaterialSymbol icon=\"format_color_text\" size={18} />\n </ControlledButton>\n </ControlledPopoverHandler>\n </ControlledTooltip>\n\n <ControlledPopoverContent className=\"zIndexMax w-44 p-2\">\n <div className=\"flex flex-wrap gap-1.5\">\n {RICH_TEXT_COLORS.map((color) => (\n <ControlledTooltip key={color} content={t(`createTask.rich.colors.${color}`)}>\n <ControlledButton\n defaultHtmlButton\n aria-pressed={activeColor === color}\n onMouseDown={(event: React.MouseEvent) => {\n event.preventDefault();\n handleApplyColor(color);\n }}\n className={classNames(\n 'flex size-7 items-center justify-center rounded-lg border transition-colors',\n activeColor === color ? 'border-brand-border ring-2 ring-brand-border' : 'border-border',\n )}\n >\n <span\n className={classNames(\n 'size-4 rounded-full',\n RICH_TEXT_COLOR_SWATCH_CLASS[color],\n color === 'default' && 'ring-1 ring-inset ring-border-strong',\n )}\n />\n </ControlledButton>\n </ControlledTooltip>\n ))}\n </div>\n </ControlledPopoverContent>\n </ControlledPopoverRoot>\n\n <ControlledTooltip content={t('createTask.rich.link')}>\n <ControlledButton\n defaultHtmlButton\n disabled={disabled}\n onMouseDown={(event: React.MouseEvent) => {\n event.preventDefault();\n saveRange();\n setShowLinkInput((previous) => !previous);\n }}\n className={classNames(\n TOOLBAR_BUTTON_CLASS,\n showLinkInput ? TOOLBAR_BUTTON_ACTIVE_CLASS : TOOLBAR_BUTTON_IDLE_CLASS,\n disabled && 'cursor-not-allowed opacity-40',\n )}\n >\n <MaterialSymbol icon=\"link\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n\n {toolbarSlot && <div className=\"ml-auto flex items-center gap-x-1\">{toolbarSlot}</div>}\n </div>\n )}\n\n {showLinkInput && (\n <div className=\"flex items-center gap-2 border-b border-border-subtle bg-muted px-3 py-2\">\n <ControlledInput\n autoFocus\n value={linkUrl}\n onChange={(event) => setLinkUrl(event.target.value)}\n placeholder=\"https://\"\n aria-label={t('createTask.rich.link')}\n onKeyDown={(event: React.KeyboardEvent) => {\n if (event.key === 'Enter') applyLink();\n if (event.key === 'Escape') setShowLinkInput(false);\n }}\n />\n <ControlledButton size=\"sm\" onClick={applyLink}>\n {t('actions.apply')}\n </ControlledButton>\n <ControlledButton size=\"sm\" variant=\"outlined\" tone=\"neutral\" onClick={() => setShowLinkInput(false)}>\n {t('actions.cancel')}\n </ControlledButton>\n </div>\n )}\n\n <div className=\"relative\">\n <div\n ref={editorRef}\n contentEditable={!disabled}\n suppressContentEditableWarning\n onInput={handleInput}\n onKeyDown={onKeyDown}\n onPaste={handlePaste}\n onKeyUp={() => {\n saveRange();\n syncActiveCommands();\n }}\n onMouseUp={() => {\n saveRange();\n syncActiveCommands();\n }}\n onFocus={() => setIsFocused(true)}\n onBlur={() => {\n saveRange();\n setIsFocused(false);\n onBlur?.();\n }}\n className={classNames(\n 'w-full px-3 py-2.5 text-sm text-foreground outline-none',\n '[&_ol]:ml-4 [&_ol]:list-decimal [&_ul]:ml-4 [&_ul]:list-disc',\n '[&_blockquote]:border-l-2 [&_blockquote]:border-brand-border [&_blockquote]:pl-3 [&_blockquote]:italic [&_blockquote]:text-foreground-subtle',\n '[&_a]:text-brand-emphasis [&_a]:underline',\n '[&_a.rt-file]:text-foreground-muted [&_a.rt-file]:no-underline',\n '[&_a.rt-image-link]:no-underline',\n '[&_user]:whitespace-nowrap [&_user]:rounded [&_user]:bg-brand-soft [&_user]:px-1 [&_user]:font-medium [&_user]:text-brand-emphasis',\n disabled && 'cursor-not-allowed opacity-60',\n )}\n style={{ minHeight }}\n />\n {isEmpty && !isFocused && placeholder && (\n <span className=\"pointer-events-none absolute left-3 top-2.5 text-sm text-foreground-faint\">\n {placeholder}\n </span>\n )}\n </div>\n\n {showPreviews && attachmentsController && attachmentsController.stagedAttachments.length > 0 && (\n <div className=\"px-3 pb-2\">\n <AttachmentPreviewList\n stagedAttachments={attachmentsController.stagedAttachments}\n onRemove={attachmentsController.removeAttachment}\n onRetry={attachmentsController.retryAttachment}\n imageActions={attachments?.imageActions}\n />\n </div>\n )}\n\n {showActionsBar && (\n <div className=\"flex items-center gap-x-0.5 px-2 pb-2 pt-0.5\">\n {formattingToggle && (\n <ControlledTooltip content={t('followUp.chat.formatting')} className=\"z-[9999]\">\n <ControlledButton\n defaultHtmlButton\n disabled={disabled}\n onClick={() => setFormattingOpen((previous) => !previous)}\n aria-pressed={formattingOpen}\n className={formattingOpen ? ACTION_BUTTON_ACTIVE_CLASS : ACTION_BUTTON_CLASS}\n >\n <MaterialSymbol icon=\"format_size\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n {attachmentsController && (\n <ControlledMenuRoot placement=\"top-start\">\n <ControlledTooltip content={t('followUp.attachments.attach')} className=\"z-[9999]\">\n <ControlledMenuHandler>\n <ControlledButton defaultHtmlButton disabled={disabled} className={ACTION_BUTTON_CLASS}>\n <MaterialSymbol icon=\"attach_file\" size={18} />\n </ControlledButton>\n </ControlledMenuHandler>\n </ControlledTooltip>\n <ControlledMenuList className=\"zIndexMax p-1\">\n {attachMenuActions.map((action) => (\n <ControlledMenuItem key={action.key} className=\"flex items-center gap-x-2\" onClick={action.onClick}>\n <MaterialSymbol icon={action.icon} size={18} />\n {action.label}\n </ControlledMenuItem>\n ))}\n </ControlledMenuList>\n </ControlledMenuRoot>\n )}\n\n {actionsSlot}\n </div>\n )}\n\n {attachmentsController && (\n <>\n <input ref={fileInputRef} type=\"file\" multiple hidden onChange={handleFilesPicked} />\n <input\n ref={imageInputRef}\n type=\"file\"\n multiple\n hidden\n accept={IMAGE_INPUT_ACCEPT}\n onChange={handleFilesPicked}\n />\n </>\n )}\n </div>\n );\n\n if (!attachmentsController || attachments?.hideDropzone) return editorShell;\n\n return (\n <FileDropzone\n stopPropagation\n disabled={disabled}\n className=\"flex-none\"\n overlayClassName={DROPZONE_OVERLAY_CLASS}\n onFilesDropped={(files) => attachmentsController.addFiles(files)}\n >\n {editorShell}\n </FileDropzone>\n );\n});\n\nexport default RichTextEditor;\n"],"names":["RichTextEditor"],"mappings":"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"}
1
+ {"version":3,"file":"RichTextEditor.js","sources":["../../../src/components/Form/RichTextEditor.tsx"],"sourcesContent":["import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport {\n ControlledMenuHandler,\n ControlledMenuItem,\n ControlledMenuList,\n ControlledMenuRoot,\n} from '@/components/UI/ControlledMenu';\nimport {\n ControlledPopoverContent,\n ControlledPopoverHandler,\n ControlledPopoverRoot,\n} from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport {\n activeRichTextColor,\n applyRichTextColor,\n buildFileChipMarkup,\n normalizeRichTextColorNodes,\n RICH_TEXT_COLOR_SWATCH_CLASS,\n RICH_TEXT_COLORS,\n TRichTextColor,\n} from '@/helpers/richText';\nimport { IUseFileAttachmentsResult, TFileUploader, useFileAttachments } from '@/hooks/useFileAttachments';\n\nimport { MaterialSymbol, SymbolCodepoints } from '../UI/MaterialSymbol';\n\nimport AttachmentPreviewList, { IStagedImageAction } from './attachments/AttachmentPreviewList';\nimport FileDropzone from './attachments/FileDropzone';\nimport ControlledButton from './ControlledButton';\nimport ControlledInput from './ControlledInput';\n\n/** An extra entry in the attach menu, for a source only the host knows about (a camera dialog). */\nexport interface IRichTextAttachAction {\n key: string;\n icon: SymbolCodepoints;\n label: string;\n onClick: () => void;\n}\n\n/**\n * Files travel BESIDE the text: the host owns a list of attachments of its own (a chat message\n * has one) and reads `controller.readyAttachments` when it saves.\n */\nexport interface IRichTextAttachmentsConfig {\n controller: IUseFileAttachmentsResult;\n extraActions?: Array<IRichTextAttachAction>;\n /**\n * Actions offered on a staged IMAGE before it is sent, so a picture is corrected on its way out\n * instead of being sent and then corrected as a second file.\n */\n imageActions?: Array<IStagedImageAction>;\n /** The host already renders the staged previews somewhere else. */\n hidePreviews?: boolean;\n /** The host already wraps a wider drop target around this editor. */\n hideDropzone?: boolean;\n}\n\n/**\n * Files travel INSIDE the text: the upload result is written into the body as a chip, so the\n * reference persists anywhere the HTML itself is stored and no host needs a new field.\n */\nexport interface IRichTextInlineUploadConfig {\n uploadFile?: TFileUploader;\n maxFiles?: number;\n maxFileSizeBytes?: number;\n}\n\ninterface IRichTextEditorProps {\n value?: string;\n onChange?: (html: string) => void;\n placeholder?: string;\n disabled?: boolean;\n error?: boolean;\n className?: string;\n minHeight?: number;\n /** Rendered inside the toolbar row, right-aligned: host actions that belong to the field. */\n toolbarSlot?: React.ReactNode;\n /** Rendered in the editor's own bottom bar, after the controls the editor owns. */\n actionsSlot?: React.ReactNode;\n onKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;\n /** Fires when the editable area loses focus, for hosts that persist on blur. */\n onBlur?: () => void;\n /**\n * Fires with the full HTML when the editor changes the content on its own, which today means\n * an inline upload landing after the field already lost focus. A host that persists on blur\n * would otherwise never hear about the file it just attached.\n */\n onCommit?: (html: string) => void;\n /** 'bare' drops the border and focus ring: the host owns the chrome around the field. */\n variant?: 'default' | 'bare';\n /** Hosts with their own action bar (the chat composer) toggle the formatting row on demand. */\n showToolbar?: boolean;\n /** Renders the formatting toggle in the bottom bar and lets the editor own the row's state. */\n formattingToggle?: boolean;\n attachments?: IRichTextAttachmentsConfig;\n inlineUpload?: IRichTextInlineUploadConfig;\n}\n\nexport interface IRichTextEditorHandle {\n focus: () => void;\n /** Viewport rect of the caret, for anchoring a popover to what is being typed. */\n getCaretRect: () => DOMRect | null;\n /**\n * Text typed after the trigger and before the caret, or null when the caret is not inside a\n * trigger word. Drives both \"is the mention menu relevant\" and \"what is being searched\".\n * Spaces are allowed inside the query (people have compound names); the host decides when a\n * spaced query stopped matching anyone and closes the menu.\n */\n getMentionQuery: (trigger: string) => string | null;\n /**\n * Deletes the trigger AND the query the user typed after it, then writes the markup in their\n * place, so picking someone from a mention menu replaces everything typed since the `@`\n * instead of stacking the mention after it.\n */\n replaceTriggerWith: (trigger: string, query: string, html: string) => void;\n}\n\ninterface IToolbarGroup {\n buttons: Array<IToolbarButton>;\n}\n\ninterface IToolbarButton {\n command: string;\n arg?: string;\n icon: SymbolCodepoints;\n tooltipKey: string;\n isBlock?: boolean;\n}\n\nconst TOOLBAR_GROUPS: Array<IToolbarGroup> = [\n {\n buttons: [\n { command: 'bold', icon: 'format_bold', tooltipKey: 'createTask.rich.bold' },\n { command: 'italic', icon: 'format_italic', tooltipKey: 'createTask.rich.italic' },\n { command: 'strikeThrough', icon: 'strikethrough_s', tooltipKey: 'createTask.rich.strikethrough' },\n ],\n },\n {\n buttons: [\n {\n command: 'insertUnorderedList',\n icon: 'format_list_bulleted',\n tooltipKey: 'createTask.rich.bulletList',\n isBlock: true,\n },\n {\n command: 'insertOrderedList',\n icon: 'format_list_numbered',\n tooltipKey: 'createTask.rich.numberedList',\n isBlock: true,\n },\n {\n command: 'formatBlock',\n arg: 'BLOCKQUOTE',\n icon: 'format_quote',\n tooltipKey: 'createTask.rich.quote',\n isBlock: true,\n },\n ],\n },\n {\n buttons: [\n { command: 'indent', icon: 'format_indent_increase', tooltipKey: 'createTask.rich.indent' },\n { command: 'outdent', icon: 'format_indent_decrease', tooltipKey: 'createTask.rich.outdent' },\n ],\n },\n];\n\nconst MENTION_QUERY_MAX_LENGTH = 40;\nconst IMAGE_INPUT_ACCEPT = 'image/*';\nconst DROPZONE_OVERLAY_CLASS = 'rounded-xl';\n\nconst TOOLBAR_BUTTON_CLASS = 'flex size-7 items-center justify-center rounded-lg transition-colors';\nconst TOOLBAR_BUTTON_IDLE_CLASS = 'text-foreground-subtle hover:bg-muted-hover hover:text-foreground-muted';\nconst TOOLBAR_BUTTON_ACTIVE_CLASS = 'bg-brand-soft text-brand-emphasis';\nconst ACTION_BUTTON_CLASS =\n 'flex size-8 items-center justify-center rounded-full text-foreground-faint transition-colors hover:bg-muted-hover hover:text-foreground-muted disabled:text-foreground-faint';\nconst ACTION_BUTTON_ACTIVE_CLASS =\n 'flex size-8 items-center justify-center rounded-full bg-brand-soft text-brand-emphasis transition-colors hover:bg-brand-soft';\n\nconst isCommandActive = (command: string, arg?: string): boolean => {\n try {\n if (arg) return document.queryCommandValue(command) === arg.toLowerCase();\n return document.queryCommandState(command);\n } catch {\n return false;\n }\n};\n\nconst RichTextEditor = forwardRef<IRichTextEditorHandle, IRichTextEditorProps>(function RichTextEditor(\n {\n value = '',\n onChange,\n placeholder,\n disabled = false,\n error = false,\n className,\n minHeight = 120,\n toolbarSlot,\n actionsSlot,\n onKeyDown,\n onBlur,\n onCommit,\n variant = 'default',\n showToolbar = true,\n formattingToggle = false,\n attachments,\n inlineUpload,\n },\n ref,\n) {\n const { t } = useTranslation('ui');\n const editorRef = useRef<HTMLDivElement>(null);\n const [activeCommands, setActiveCommands] = useState<Set<string>>(new Set());\n const [activeColor, setActiveColor] = useState<TRichTextColor>('default');\n const [colorOpen, setColorOpen] = useState(false);\n const [showLinkInput, setShowLinkInput] = useState(false);\n const [linkUrl, setLinkUrl] = useState('');\n const [isFocused, setIsFocused] = useState(false);\n const [formattingOpen, setFormattingOpen] = useState(false);\n const savedRangeRef = useRef<Range | null>(null);\n const isInternalUpdateRef = useRef(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const imageInputRef = useRef<HTMLInputElement>(null);\n const insertedStagingIdsRef = useRef<Set<string>>(new Set());\n const pendingColorRef = useRef<TRichTextColor>('default');\n\n const inlineController = useFileAttachments({\n uploadFile: inlineUpload?.uploadFile,\n maxFiles: inlineUpload?.maxFiles,\n maxFileSizeBytes: inlineUpload?.maxFileSizeBytes,\n });\n\n const attachmentsController = attachments?.controller ?? (inlineUpload ? inlineController : null);\n const inlineStagedAttachments = inlineController.stagedAttachments;\n const toolbarVisible = formattingToggle ? formattingOpen : showToolbar;\n\n useEffect(() => {\n const editor = editorRef.current;\n if (!editor) return;\n if (isInternalUpdateRef.current) return;\n if (editor.innerHTML !== value) {\n editor.innerHTML = value;\n }\n }, [value]);\n\n const emitChange = useCallback(() => {\n const editor = editorRef.current;\n if (!editor) return;\n isInternalUpdateRef.current = true;\n onChange?.(editor.innerHTML);\n isInternalUpdateRef.current = false;\n }, [onChange]);\n\n const syncActiveCommands = useCallback(() => {\n const active = new Set<string>();\n TOOLBAR_GROUPS.flatMap((group) => group.buttons).forEach(({ command, arg }) => {\n if (isCommandActive(command, arg)) active.add(arg ?? command);\n });\n setActiveCommands(active);\n\n const editor = editorRef.current;\n if (editor) setActiveColor(activeRichTextColor(editor));\n }, []);\n\n const execFormat = useCallback(\n (command: string, arg?: string) => {\n const editor = editorRef.current;\n if (!editor || disabled) return;\n editor.focus();\n document.execCommand(command, false, arg);\n emitChange();\n syncActiveCommands();\n },\n [disabled, emitChange, syncActiveCommands],\n );\n\n const saveRange = useCallback(() => {\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n savedRangeRef.current = selection.getRangeAt(0).cloneRange();\n }\n }, []);\n\n const restoreRange = useCallback(() => {\n const range = savedRangeRef.current;\n if (!range) return;\n const selection = window.getSelection();\n if (!selection) return;\n selection.removeAllRanges();\n selection.addRange(range);\n }, []);\n\n const applyLink = useCallback(() => {\n const url = linkUrl.trim();\n restoreRange();\n if (url) execFormat('createLink', url.startsWith('http') ? url : `https://${url}`);\n setShowLinkInput(false);\n setLinkUrl('');\n }, [linkUrl, restoreRange, execFormat]);\n\n const handleApplyColor = useCallback(\n (color: TRichTextColor) => {\n const editor = editorRef.current;\n if (!editor || disabled) return;\n\n editor.focus();\n restoreRange();\n pendingColorRef.current = color;\n applyRichTextColor(editor, color);\n emitChange();\n setActiveColor(color);\n setColorOpen(false);\n },\n [disabled, emitChange, restoreRange],\n );\n\n const insertHtmlAtCaret = useCallback(\n (html: string, commit = false) => {\n const editor = editorRef.current;\n if (!editor || disabled) return;\n\n const previouslyFocused = document.activeElement as HTMLElement | null;\n const editorHadFocus = editor.contains(previouslyFocused);\n\n editor.focus();\n restoreRange();\n\n const selection = window.getSelection();\n const caretIsInside = selection?.anchorNode ? editor.contains(selection.anchorNode) : false;\n\n if (!caretIsInside) {\n const range = document.createRange();\n range.selectNodeContents(editor);\n range.collapse(false);\n selection?.removeAllRanges();\n selection?.addRange(range);\n }\n\n document.execCommand('insertHTML', false, html);\n saveRange();\n emitChange();\n\n if (!editorHadFocus) previouslyFocused?.focus();\n if (commit) onCommit?.(editor.innerHTML);\n },\n [disabled, emitChange, onCommit, restoreRange, saveRange],\n );\n\n const inlineControllerRef = useRef(inlineController);\n inlineControllerRef.current = inlineController;\n\n // An inline upload lands in the body the moment it finishes, and leaves the staging list so\n // the progress card disappears with it.\n useEffect(() => {\n if (!inlineUpload) return;\n\n inlineStagedAttachments.forEach((staged) => {\n const uploaded = staged.uploaded;\n if (staged.status !== 'done' || !uploaded) return;\n\n if (!insertedStagingIdsRef.current.has(staged.stagingId)) {\n insertedStagingIdsRef.current.add(staged.stagingId);\n insertHtmlAtCaret(`${buildFileChipMarkup(uploaded)}&nbsp;`, true);\n }\n inlineControllerRef.current.removeAttachment(staged.stagingId);\n });\n }, [inlineUpload, inlineStagedAttachments, insertHtmlAtCaret]);\n\n const handleInput = useCallback(() => {\n const editor = editorRef.current;\n if (editor) normalizeRichTextColorNodes(editor, pendingColorRef.current);\n emitChange();\n syncActiveCommands();\n }, [emitChange, syncActiveCommands]);\n\n const handleFilesPicked = (event: React.ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(event.target.files ?? []);\n if (files.length) attachmentsController?.addFiles(files);\n event.target.value = '';\n };\n\n const handlePaste = (event: React.ClipboardEvent) => {\n if (!attachmentsController) return;\n\n const files = Array.from(event.clipboardData?.files ?? []);\n if (!files.length) return;\n\n event.preventDefault();\n event.stopPropagation();\n attachmentsController.addFiles(files);\n };\n\n useImperativeHandle(ref, () => ({\n focus: () => editorRef.current?.focus(),\n\n getCaretRect: () => {\n const selection = window.getSelection();\n if (!selection?.rangeCount) return null;\n\n const range = selection.getRangeAt(0).cloneRange();\n range.collapse(false);\n\n const [rect] = Array.from(range.getClientRects());\n if (rect) return rect;\n\n // A collapsed range inside an empty node measures nothing, so a throwaway marker gives it\n // something to measure. Removed in the same tick, before anything can observe it.\n const marker = document.createElement('span');\n marker.textContent = '\\u200b';\n range.insertNode(marker);\n const markerRect = marker.getBoundingClientRect();\n marker.remove();\n\n return markerRect;\n },\n\n getMentionQuery: (trigger) => {\n const selection = window.getSelection();\n if (!selection?.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n if (!range.collapsed || range.startContainer.nodeType !== Node.TEXT_NODE) return null;\n\n const textBeforeCaret = range.startContainer.textContent?.slice(0, range.startOffset) ?? '';\n const escapedTrigger = trigger.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = new RegExp(`(?:^|\\\\s)${escapedTrigger}([^${escapedTrigger}]*)$`).exec(textBeforeCaret);\n if (!match) return null;\n\n const query = match[1];\n if (query.startsWith(' ') || query.length > MENTION_QUERY_MAX_LENGTH) return null;\n\n return query;\n },\n\n replaceTriggerWith: (trigger, query, html) => {\n const editor = editorRef.current;\n if (!editor || disabled) return;\n\n editor.focus();\n restoreRange();\n\n const selection = window.getSelection();\n const range = selection?.rangeCount ? selection.getRangeAt(0) : null;\n\n if (range?.collapsed && range.startContainer.nodeType === Node.TEXT_NODE) {\n const textBeforeCaret = range.startContainer.textContent?.slice(0, range.startOffset) ?? '';\n const typed = `${trigger}${query}`;\n if (textBeforeCaret.endsWith(typed)) {\n range.setStart(range.startContainer, range.startOffset - typed.length);\n range.deleteContents();\n } else if (textBeforeCaret.endsWith(trigger)) {\n range.setStart(range.startContainer, range.startOffset - trigger.length);\n range.deleteContents();\n }\n }\n\n document.execCommand('insertHTML', false, `${html}&nbsp;`);\n emitChange();\n },\n }));\n\n const isEmpty = !editorRef.current?.textContent?.trim() && !editorRef.current?.querySelector('img, li, blockquote');\n\n const attachMenuActions = useMemo<Array<IRichTextAttachAction>>(\n () => [\n {\n key: 'file',\n icon: 'description',\n label: t('followUp.attachments.pickFile'),\n onClick: () => fileInputRef.current?.click(),\n },\n {\n key: 'image',\n icon: 'image',\n label: t('followUp.attachments.pickImage'),\n onClick: () => imageInputRef.current?.click(),\n },\n ...(attachments?.extraActions ?? []),\n ],\n [attachments?.extraActions, t],\n );\n\n const showPreviews = Boolean(attachmentsController) && !attachments?.hidePreviews;\n const showActionsBar = Boolean(attachmentsController) || Boolean(actionsSlot) || formattingToggle;\n\n const editorShell = (\n <div\n className={classNames(\n 'flex w-full flex-col transition-colors',\n variant === 'default' && 'rounded-xl border bg-surface',\n variant === 'default' &&\n (error\n ? 'border-danger-border'\n : isFocused\n ? 'border-brand-border ring-2 ring-brand-border'\n : 'border-border'),\n className,\n )}\n >\n {toolbarVisible && (\n <div\n className={classNames(\n 'flex flex-wrap items-center gap-px border-b px-2 py-1.5',\n error ? 'border-danger-border' : 'border-border-subtle',\n )}\n >\n {TOOLBAR_GROUPS.map((group, groupIndex) => (\n <div\n key={groupIndex}\n className={classNames(\n 'flex items-center gap-px',\n groupIndex < TOOLBAR_GROUPS.length - 1 && 'mr-1 border-r border-border pr-1',\n )}\n >\n {group.buttons.map(({ command, arg, icon, tooltipKey }) => {\n const key = arg ?? command;\n const isActive = activeCommands.has(key);\n\n return (\n <ControlledTooltip key={key} content={t(tooltipKey)}>\n <ControlledButton\n aria-label={t(tooltipKey)}\n defaultHtmlButton\n disabled={disabled}\n onMouseDown={(event: React.MouseEvent) => {\n event.preventDefault();\n execFormat(command, arg);\n }}\n className={classNames(\n TOOLBAR_BUTTON_CLASS,\n isActive ? TOOLBAR_BUTTON_ACTIVE_CLASS : TOOLBAR_BUTTON_IDLE_CLASS,\n disabled && 'cursor-not-allowed opacity-40',\n )}\n >\n <MaterialSymbol icon={icon} size={18} />\n </ControlledButton>\n </ControlledTooltip>\n );\n })}\n </div>\n ))}\n\n <div className=\"flex items-center gap-px border-l border-border pl-1\">\n <ControlledPopoverRoot open={colorOpen} handler={setColorOpen} placement=\"bottom-start\">\n <ControlledTooltip content={t('createTask.rich.textColor')}>\n <ControlledPopoverHandler>\n <ControlledButton\n aria-label={t('createTask.rich.textColor')}\n defaultHtmlButton\n disabled={disabled}\n onMouseDown={(event: React.MouseEvent) => {\n event.preventDefault();\n saveRange();\n }}\n className={classNames(\n TOOLBAR_BUTTON_CLASS,\n colorOpen ? TOOLBAR_BUTTON_ACTIVE_CLASS : TOOLBAR_BUTTON_IDLE_CLASS,\n disabled && 'cursor-not-allowed opacity-40',\n )}\n >\n <MaterialSymbol icon=\"format_color_text\" size={18} />\n </ControlledButton>\n </ControlledPopoverHandler>\n </ControlledTooltip>\n\n <ControlledPopoverContent className=\"zIndexMax w-44 p-2\">\n <div className=\"flex flex-wrap gap-1.5\">\n {RICH_TEXT_COLORS.map((color) => (\n <ControlledTooltip key={color} content={t(`createTask.rich.colors.${color}`)}>\n <ControlledButton\n aria-label={t(`createTask.rich.colors.${color}`)}\n defaultHtmlButton\n aria-pressed={activeColor === color}\n onMouseDown={(event: React.MouseEvent) => {\n event.preventDefault();\n handleApplyColor(color);\n }}\n className={classNames(\n 'flex size-7 items-center justify-center rounded-lg border transition-colors',\n activeColor === color ? 'border-brand-border ring-2 ring-brand-border' : 'border-border',\n )}\n >\n <span\n className={classNames(\n 'size-4 rounded-full',\n RICH_TEXT_COLOR_SWATCH_CLASS[color],\n color === 'default' && 'ring-1 ring-inset ring-border-strong',\n )}\n />\n </ControlledButton>\n </ControlledTooltip>\n ))}\n </div>\n </ControlledPopoverContent>\n </ControlledPopoverRoot>\n\n <ControlledTooltip content={t('createTask.rich.link')}>\n <ControlledButton\n aria-label={t('createTask.rich.link')}\n defaultHtmlButton\n disabled={disabled}\n onMouseDown={(event: React.MouseEvent) => {\n event.preventDefault();\n saveRange();\n setShowLinkInput((previous) => !previous);\n }}\n className={classNames(\n TOOLBAR_BUTTON_CLASS,\n showLinkInput ? TOOLBAR_BUTTON_ACTIVE_CLASS : TOOLBAR_BUTTON_IDLE_CLASS,\n disabled && 'cursor-not-allowed opacity-40',\n )}\n >\n <MaterialSymbol icon=\"link\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n\n {toolbarSlot && <div className=\"ml-auto flex items-center gap-x-1\">{toolbarSlot}</div>}\n </div>\n )}\n\n {showLinkInput && (\n <div className=\"flex items-center gap-2 border-b border-border-subtle bg-muted px-3 py-2\">\n <ControlledInput\n autoFocus\n value={linkUrl}\n onChange={(event) => setLinkUrl(event.target.value)}\n placeholder=\"https://\"\n aria-label={t('createTask.rich.link')}\n onKeyDown={(event: React.KeyboardEvent) => {\n if (event.key === 'Enter') applyLink();\n if (event.key === 'Escape') setShowLinkInput(false);\n }}\n />\n <ControlledButton size=\"sm\" onClick={applyLink}>\n {t('actions.apply')}\n </ControlledButton>\n <ControlledButton size=\"sm\" variant=\"outlined\" tone=\"neutral\" onClick={() => setShowLinkInput(false)}>\n {t('actions.cancel')}\n </ControlledButton>\n </div>\n )}\n\n <div className=\"relative\">\n <div\n ref={editorRef}\n contentEditable={!disabled}\n suppressContentEditableWarning\n onInput={handleInput}\n onKeyDown={onKeyDown}\n onPaste={handlePaste}\n onKeyUp={() => {\n saveRange();\n syncActiveCommands();\n }}\n onMouseUp={() => {\n saveRange();\n syncActiveCommands();\n }}\n onFocus={() => setIsFocused(true)}\n onBlur={() => {\n saveRange();\n setIsFocused(false);\n onBlur?.();\n }}\n className={classNames(\n 'w-full px-3 py-2.5 text-sm text-foreground outline-none',\n '[&_ol]:ml-4 [&_ol]:list-decimal [&_ul]:ml-4 [&_ul]:list-disc',\n '[&_blockquote]:border-l-2 [&_blockquote]:border-brand-border [&_blockquote]:pl-3 [&_blockquote]:italic [&_blockquote]:text-foreground-subtle',\n '[&_a]:text-brand-emphasis [&_a]:underline',\n '[&_a.rt-file]:text-foreground-muted [&_a.rt-file]:no-underline',\n '[&_a.rt-image-link]:no-underline',\n '[&_user]:whitespace-nowrap [&_user]:rounded [&_user]:bg-brand-soft [&_user]:px-1 [&_user]:font-medium [&_user]:text-brand-emphasis',\n disabled && 'cursor-not-allowed opacity-60',\n )}\n style={{ minHeight }}\n />\n {isEmpty && !isFocused && placeholder && (\n <span className=\"pointer-events-none absolute left-3 top-2.5 text-sm text-foreground-faint\">\n {placeholder}\n </span>\n )}\n </div>\n\n {showPreviews && attachmentsController && attachmentsController.stagedAttachments.length > 0 && (\n <div className=\"px-3 pb-2\">\n <AttachmentPreviewList\n stagedAttachments={attachmentsController.stagedAttachments}\n onRemove={attachmentsController.removeAttachment}\n onRetry={attachmentsController.retryAttachment}\n imageActions={attachments?.imageActions}\n />\n </div>\n )}\n\n {showActionsBar && (\n <div className=\"flex items-center gap-x-0.5 px-2 pb-2 pt-0.5\">\n {formattingToggle && (\n <ControlledTooltip content={t('followUp.chat.formatting')} className=\"z-[9999]\">\n <ControlledButton\n aria-label={t('followUp.chat.formatting')}\n defaultHtmlButton\n disabled={disabled}\n onClick={() => setFormattingOpen((previous) => !previous)}\n aria-pressed={formattingOpen}\n className={formattingOpen ? ACTION_BUTTON_ACTIVE_CLASS : ACTION_BUTTON_CLASS}\n >\n <MaterialSymbol icon=\"format_size\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n {attachmentsController && (\n <ControlledMenuRoot placement=\"top-start\">\n <ControlledTooltip content={t('followUp.attachments.attach')} className=\"z-[9999]\">\n <ControlledMenuHandler>\n <ControlledButton\n defaultHtmlButton\n aria-label={t('followUp.attachments.attach')}\n disabled={disabled}\n className={ACTION_BUTTON_CLASS}\n >\n <MaterialSymbol icon=\"attach_file\" size={18} />\n </ControlledButton>\n </ControlledMenuHandler>\n </ControlledTooltip>\n <ControlledMenuList className=\"zIndexMax p-1\">\n {attachMenuActions.map((action) => (\n <ControlledMenuItem key={action.key} className=\"flex items-center gap-x-2\" onClick={action.onClick}>\n <MaterialSymbol icon={action.icon} size={18} />\n {action.label}\n </ControlledMenuItem>\n ))}\n </ControlledMenuList>\n </ControlledMenuRoot>\n )}\n\n {actionsSlot}\n </div>\n )}\n\n {attachmentsController && (\n <>\n <input ref={fileInputRef} type=\"file\" multiple hidden onChange={handleFilesPicked} />\n <input\n ref={imageInputRef}\n type=\"file\"\n multiple\n hidden\n accept={IMAGE_INPUT_ACCEPT}\n onChange={handleFilesPicked}\n />\n </>\n )}\n </div>\n );\n\n if (!attachmentsController || attachments?.hideDropzone) return editorShell;\n\n return (\n <FileDropzone\n stopPropagation\n disabled={disabled}\n className=\"flex-none\"\n overlayClassName={DROPZONE_OVERLAY_CLASS}\n onFilesDropped={(files) => attachmentsController.addFiles(files)}\n >\n {editorShell}\n </FileDropzone>\n );\n});\n\nexport default RichTextEditor;\n"],"names":["RichTextEditor"],"mappings":"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"}
@@ -37,6 +37,7 @@ const AttachmentPreviewList = ({
37
37
  actions.map((action) => /* @__PURE__ */ jsx(ControlledTooltip, { content: action.label, className: "z-[9999]", children: /* @__PURE__ */ jsx(
38
38
  ControlledButton,
39
39
  {
40
+ "aria-label": action.label,
40
41
  defaultHtmlButton: true,
41
42
  onClick: () => action.onClick(attachment),
42
43
  className: "flex size-6 shrink-0 items-center justify-center rounded-full text-foreground-faint transition-colors hover:bg-muted-hover hover:text-brand-emphasis",
@@ -46,6 +47,7 @@ const AttachmentPreviewList = ({
46
47
  attachment.status === "error" && /* @__PURE__ */ jsx(ControlledTooltip, { content: t("actions.tryAgain"), className: "z-[9999]", children: /* @__PURE__ */ jsx(
47
48
  ControlledButton,
48
49
  {
50
+ "aria-label": t("actions.tryAgain"),
49
51
  defaultHtmlButton: true,
50
52
  onClick: () => onRetry(attachment.stagingId),
51
53
  className: "flex size-6 shrink-0 items-center justify-center rounded-full text-danger-emphasis hover:bg-danger-soft",
@@ -55,6 +57,7 @@ const AttachmentPreviewList = ({
55
57
  /* @__PURE__ */ jsx(ControlledTooltip, { content: t("actions.remove"), className: "z-[9999]", children: /* @__PURE__ */ jsx(
56
58
  ControlledButton,
57
59
  {
60
+ "aria-label": t("actions.remove"),
58
61
  defaultHtmlButton: true,
59
62
  onClick: () => onRemove(attachment.stagingId),
60
63
  className: "flex size-6 shrink-0 items-center justify-center rounded-full text-foreground-faint hover:bg-muted-hover hover:text-foreground-muted",
@@ -1 +1 @@
1
- {"version":3,"file":"AttachmentPreviewList.js","sources":["../../../../src/components/Form/attachments/AttachmentPreviewList.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledImage from '@/components/Form/ControlledImage';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { formatFileSize, iconForMimeType } from '@/helpers/fileAttachments';\nimport { IStagedAttachment } from '@/hooks/useFileAttachments';\n\nimport { SymbolCodepoints } from '../../UI/MaterialSymbol';\n\n/**\n * An action offered on a staged IMAGE before it is sent (annotate it, run an AI adjustment). The\n * library does not know what any of them do: it draws the button and hands the staged file back.\n */\nexport interface IStagedImageAction {\n key: string;\n icon: SymbolCodepoints;\n label: string;\n onClick: (attachment: IStagedAttachment) => void;\n hidden?: (attachment: IStagedAttachment) => boolean;\n}\n\ninterface IAttachmentPreviewListProps {\n stagedAttachments: Array<IStagedAttachment>;\n onRemove: (stagingId: string) => void;\n onRetry: (stagingId: string) => void;\n /** Offered only on an image whose upload finished: editing one mid-flight would race the upload. */\n imageActions?: Array<IStagedImageAction>;\n}\n\nconst isEditableImage = (attachment: IStagedAttachment): boolean =>\n attachment.status === 'done' && Boolean(attachment.previewUrl) && attachment.file.type.startsWith('image/');\n\nconst AttachmentPreviewList: React.FC<IAttachmentPreviewListProps> = ({\n stagedAttachments,\n onRemove,\n onRetry,\n imageActions,\n}) => {\n const { t } = useTranslation('ui');\n\n if (!stagedAttachments.length) return null;\n\n return (\n <div className=\"flex flex-wrap gap-2\">\n {stagedAttachments.map((attachment) => {\n const actions = isEditableImage(attachment)\n ? (imageActions ?? []).filter((action) => !action.hidden?.(attachment))\n : [];\n\n return (\n <div\n key={attachment.stagingId}\n className={classNames(\n 'relative flex animate-pop-in flex-col overflow-hidden rounded-xl border bg-surface',\n // The buttons sit on the same row as the remove one, so the chip grows to keep the\n // file name readable instead of squeezing it to a couple of characters.\n actions.length > 0 ? 'w-56' : 'w-44',\n attachment.status === 'error' ? 'border-danger-border' : 'border-border',\n )}\n >\n <div className=\"flex items-center gap-x-2 p-2\">\n {attachment.previewUrl && attachment.file.type.startsWith('image/') ? (\n <ControlledImage src={attachment.previewUrl} className=\"size-10 shrink-0 rounded-lg object-cover\" />\n ) : (\n <span className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-brand-soft text-brand-emphasis\">\n <MaterialSymbol icon={iconForMimeType(attachment.file.type)} size={20} />\n </span>\n )}\n\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"truncate text-xs font-medium text-foreground\">{attachment.file.name}</span>\n <span className=\"text-xxs text-foreground-subtle\">\n {attachment.status === 'error'\n ? t('followUp.attachments.uploadFailed')\n : formatFileSize(attachment.file.size)}\n </span>\n </span>\n\n {actions.map((action) => (\n <ControlledTooltip key={action.key} content={action.label} className=\"z-[9999]\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => action.onClick(attachment)}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-full text-foreground-faint transition-colors hover:bg-muted-hover hover:text-brand-emphasis\"\n >\n <MaterialSymbol icon={action.icon} size={14} />\n </ControlledButton>\n </ControlledTooltip>\n ))}\n\n {attachment.status === 'error' && (\n <ControlledTooltip content={t('actions.tryAgain')} className=\"z-[9999]\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => onRetry(attachment.stagingId)}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-full text-danger-emphasis hover:bg-danger-soft\"\n >\n <MaterialSymbol icon=\"refresh\" size={14} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n <ControlledTooltip content={t('actions.remove')} className=\"z-[9999]\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => onRemove(attachment.stagingId)}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-full text-foreground-faint hover:bg-muted-hover hover:text-foreground-muted\"\n >\n <MaterialSymbol icon=\"close\" size={14} />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n\n {attachment.status === 'uploading' && (\n <div\n className=\"h-1 w-full bg-muted-hover\"\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={attachment.progress}\n aria-label={t('followUp.attachments.uploading')}\n >\n <div\n className=\"h-1 rounded-r-full bg-brand transition-interactive duration-base\"\n style={{ width: `${attachment.progress}%` }}\n />\n </div>\n )}\n </div>\n );\n })}\n </div>\n );\n};\n\nexport default AttachmentPreviewList;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AttachmentPreviewList.js","sources":["../../../../src/components/Form/attachments/AttachmentPreviewList.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledImage from '@/components/Form/ControlledImage';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { formatFileSize, iconForMimeType } from '@/helpers/fileAttachments';\nimport { IStagedAttachment } from '@/hooks/useFileAttachments';\n\nimport { SymbolCodepoints } from '../../UI/MaterialSymbol';\n\n/**\n * An action offered on a staged IMAGE before it is sent (annotate it, run an AI adjustment). The\n * library does not know what any of them do: it draws the button and hands the staged file back.\n */\nexport interface IStagedImageAction {\n key: string;\n icon: SymbolCodepoints;\n label: string;\n onClick: (attachment: IStagedAttachment) => void;\n hidden?: (attachment: IStagedAttachment) => boolean;\n}\n\ninterface IAttachmentPreviewListProps {\n stagedAttachments: Array<IStagedAttachment>;\n onRemove: (stagingId: string) => void;\n onRetry: (stagingId: string) => void;\n /** Offered only on an image whose upload finished: editing one mid-flight would race the upload. */\n imageActions?: Array<IStagedImageAction>;\n}\n\nconst isEditableImage = (attachment: IStagedAttachment): boolean =>\n attachment.status === 'done' && Boolean(attachment.previewUrl) && attachment.file.type.startsWith('image/');\n\nconst AttachmentPreviewList: React.FC<IAttachmentPreviewListProps> = ({\n stagedAttachments,\n onRemove,\n onRetry,\n imageActions,\n}) => {\n const { t } = useTranslation('ui');\n\n if (!stagedAttachments.length) return null;\n\n return (\n <div className=\"flex flex-wrap gap-2\">\n {stagedAttachments.map((attachment) => {\n const actions = isEditableImage(attachment)\n ? (imageActions ?? []).filter((action) => !action.hidden?.(attachment))\n : [];\n\n return (\n <div\n key={attachment.stagingId}\n className={classNames(\n 'relative flex animate-pop-in flex-col overflow-hidden rounded-xl border bg-surface',\n // The buttons sit on the same row as the remove one, so the chip grows to keep the\n // file name readable instead of squeezing it to a couple of characters.\n actions.length > 0 ? 'w-56' : 'w-44',\n attachment.status === 'error' ? 'border-danger-border' : 'border-border',\n )}\n >\n <div className=\"flex items-center gap-x-2 p-2\">\n {attachment.previewUrl && attachment.file.type.startsWith('image/') ? (\n <ControlledImage src={attachment.previewUrl} className=\"size-10 shrink-0 rounded-lg object-cover\" />\n ) : (\n <span className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-brand-soft text-brand-emphasis\">\n <MaterialSymbol icon={iconForMimeType(attachment.file.type)} size={20} />\n </span>\n )}\n\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"truncate text-xs font-medium text-foreground\">{attachment.file.name}</span>\n <span className=\"text-xxs text-foreground-subtle\">\n {attachment.status === 'error'\n ? t('followUp.attachments.uploadFailed')\n : formatFileSize(attachment.file.size)}\n </span>\n </span>\n\n {actions.map((action) => (\n <ControlledTooltip key={action.key} content={action.label} className=\"z-[9999]\">\n <ControlledButton\n aria-label={action.label}\n defaultHtmlButton\n onClick={() => action.onClick(attachment)}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-full text-foreground-faint transition-colors hover:bg-muted-hover hover:text-brand-emphasis\"\n >\n <MaterialSymbol icon={action.icon} size={14} />\n </ControlledButton>\n </ControlledTooltip>\n ))}\n\n {attachment.status === 'error' && (\n <ControlledTooltip content={t('actions.tryAgain')} className=\"z-[9999]\">\n <ControlledButton\n aria-label={t('actions.tryAgain')}\n defaultHtmlButton\n onClick={() => onRetry(attachment.stagingId)}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-full text-danger-emphasis hover:bg-danger-soft\"\n >\n <MaterialSymbol icon=\"refresh\" size={14} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n <ControlledTooltip content={t('actions.remove')} className=\"z-[9999]\">\n <ControlledButton\n aria-label={t('actions.remove')}\n defaultHtmlButton\n onClick={() => onRemove(attachment.stagingId)}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-full text-foreground-faint hover:bg-muted-hover hover:text-foreground-muted\"\n >\n <MaterialSymbol icon=\"close\" size={14} />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n\n {attachment.status === 'uploading' && (\n <div\n className=\"h-1 w-full bg-muted-hover\"\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={attachment.progress}\n aria-label={t('followUp.attachments.uploading')}\n >\n <div\n className=\"h-1 rounded-r-full bg-brand transition-interactive duration-base\"\n style={{ width: `${attachment.progress}%` }}\n />\n </div>\n )}\n </div>\n );\n })}\n </div>\n );\n};\n\nexport default AttachmentPreviewList;\n"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useState, useRef, useCallback } from "react";
3
3
  import { useTranslation } from "react-i18next";
4
- import { twMerge } from "tailwind-merge";
5
4
  import { MaterialSymbol } from "../../UI/MaterialSymbol/index.js";
5
+ import { mergeClasses } from "../../../helpers/mergeClasses.js";
6
6
  const FileDropzone = ({
7
7
  disabled = false,
8
8
  onFilesDropped,
@@ -54,7 +54,7 @@ const FileDropzone = ({
54
54
  return /* @__PURE__ */ jsxs(
55
55
  "div",
56
56
  {
57
- className: twMerge("relative flex min-h-0 flex-1 flex-col", className),
57
+ className: mergeClasses("relative flex min-h-0 flex-1 flex-col", className),
58
58
  onDragEnter: handleDragEnter,
59
59
  onDragOver: handleDragOver,
60
60
  onDragLeave: handleDragLeave,
@@ -64,7 +64,7 @@ const FileDropzone = ({
64
64
  isDraggingOver && /* @__PURE__ */ jsx(
65
65
  "div",
66
66
  {
67
- className: twMerge(
67
+ className: mergeClasses(
68
68
  "pointer-events-none absolute inset-0 z-20 flex items-center justify-center rounded-2xl border-2 border-dashed border-brand-border bg-brand-soft/90",
69
69
  overlayClassName
70
70
  ),
@@ -1 +1 @@
1
- {"version":3,"file":"FileDropzone.js","sources":["../../../../src/components/Form/attachments/FileDropzone.tsx"],"sourcesContent":["import { useCallback, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\ninterface IFileDropzoneProps {\n disabled?: boolean;\n onFilesDropped: (files: Array<File>) => void;\n className?: string;\n overlayClassName?: string;\n /** Set by a zone nested inside another one, so a drop is staged once and not twice. */\n stopPropagation?: boolean;\n children: React.ReactNode;\n}\n\n/** Wraps a composing surface so a file dragged anywhere over it becomes an attachment. */\nconst FileDropzone: React.FC<IFileDropzoneProps> = ({\n disabled = false,\n onFilesDropped,\n className,\n overlayClassName,\n stopPropagation = false,\n children,\n}) => {\n const { t } = useTranslation('ui');\n const [isDraggingOver, setIsDraggingOver] = useState(false);\n const dragDepthRef = useRef(0);\n\n const hasFiles = (event: React.DragEvent): boolean => Array.from(event.dataTransfer?.types ?? []).includes('Files');\n\n const handleDragEnter = useCallback(\n (event: React.DragEvent) => {\n if (disabled || !hasFiles(event)) return;\n event.preventDefault();\n dragDepthRef.current += 1;\n setIsDraggingOver(true);\n },\n [disabled],\n );\n\n const handleDragOver = useCallback(\n (event: React.DragEvent) => {\n if (disabled || !hasFiles(event)) return;\n event.preventDefault();\n },\n [disabled],\n );\n\n const handleDragLeave = useCallback(\n (event: React.DragEvent) => {\n if (disabled || !hasFiles(event)) return;\n dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);\n if (dragDepthRef.current === 0) setIsDraggingOver(false);\n },\n [disabled],\n );\n\n const handleDrop = useCallback(\n (event: React.DragEvent) => {\n if (disabled) return;\n event.preventDefault();\n if (stopPropagation) event.stopPropagation();\n dragDepthRef.current = 0;\n setIsDraggingOver(false);\n\n const files = Array.from(event.dataTransfer?.files ?? []);\n if (files.length) onFilesDropped(files);\n },\n [disabled, onFilesDropped, stopPropagation],\n );\n\n return (\n <div\n className={twMerge('relative flex min-h-0 flex-1 flex-col', className)}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n {children}\n\n {isDraggingOver && (\n <div\n className={twMerge(\n 'pointer-events-none absolute inset-0 z-20 flex items-center justify-center rounded-2xl border-2 border-dashed border-brand-border bg-brand-soft/90',\n overlayClassName,\n )}\n >\n <div className=\"flex animate-pop-in flex-col items-center gap-y-2 text-brand-emphasis\">\n <MaterialSymbol icon=\"upload_file\" size={40} />\n <span className=\"text-sm font-medium\">{t('followUp.attachments.dropHere')}</span>\n </div>\n </div>\n )}\n </div>\n );\n};\n\nexport default FileDropzone;\n"],"names":[],"mappings":";;;;;AAiBA,MAAM,eAA6C,CAAC;AAAA,EAClD,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,KAAK;AAC1D,QAAM,eAAe,OAAO,CAAC;AAE7B,QAAM,WAAW,CAAC,UAAoC,MAAM,KAAK,MAAM,cAAc,SAAS,CAAA,CAAE,EAAE,SAAS,OAAO;AAElH,QAAM,kBAAkB;AAAA,IACtB,CAAC,UAA2B;AAC1B,UAAI,YAAY,CAAC,SAAS,KAAK,EAAG;AAClC,YAAM,eAAA;AACN,mBAAa,WAAW;AACxB,wBAAkB,IAAI;AAAA,IACxB;AAAA,IACA,CAAC,QAAQ;AAAA,EAAA;AAGX,QAAM,iBAAiB;AAAA,IACrB,CAAC,UAA2B;AAC1B,UAAI,YAAY,CAAC,SAAS,KAAK,EAAG;AAClC,YAAM,eAAA;AAAA,IACR;AAAA,IACA,CAAC,QAAQ;AAAA,EAAA;AAGX,QAAM,kBAAkB;AAAA,IACtB,CAAC,UAA2B;AAC1B,UAAI,YAAY,CAAC,SAAS,KAAK,EAAG;AAClC,mBAAa,UAAU,KAAK,IAAI,GAAG,aAAa,UAAU,CAAC;AAC3D,UAAI,aAAa,YAAY,EAAG,mBAAkB,KAAK;AAAA,IACzD;AAAA,IACA,CAAC,QAAQ;AAAA,EAAA;AAGX,QAAM,aAAa;AAAA,IACjB,CAAC,UAA2B;AAC1B,UAAI,SAAU;AACd,YAAM,eAAA;AACN,UAAI,uBAAuB,gBAAA;AAC3B,mBAAa,UAAU;AACvB,wBAAkB,KAAK;AAEvB,YAAM,QAAQ,MAAM,KAAK,MAAM,cAAc,SAAS,EAAE;AACxD,UAAI,MAAM,OAAQ,gBAAe,KAAK;AAAA,IACxC;AAAA,IACA,CAAC,UAAU,gBAAgB,eAAe;AAAA,EAAA;AAG5C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,QAAQ,yCAAyC,SAAS;AAAA,MACrE,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,QAAQ;AAAA,MAEP,UAAA;AAAA,QAAA;AAAA,QAEA,kBACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA;AAAA,YAAA;AAAA,YAGF,UAAA,qBAAC,OAAA,EAAI,WAAU,yEACb,UAAA;AAAA,cAAA,oBAAC,gBAAA,EAAe,MAAK,eAAc,MAAM,IAAI;AAAA,kCAC5C,QAAA,EAAK,WAAU,uBAAuB,UAAA,EAAE,+BAA+B,EAAA,CAAE;AAAA,YAAA,EAAA,CAC5E;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA;AAAA,EAAA;AAIR;"}
1
+ {"version":3,"file":"FileDropzone.js","sources":["../../../../src/components/Form/attachments/FileDropzone.tsx"],"sourcesContent":["import { useCallback, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\ninterface IFileDropzoneProps {\n disabled?: boolean;\n onFilesDropped: (files: Array<File>) => void;\n className?: string;\n overlayClassName?: string;\n /** Set by a zone nested inside another one, so a drop is staged once and not twice. */\n stopPropagation?: boolean;\n children: React.ReactNode;\n}\n\n/** Wraps a composing surface so a file dragged anywhere over it becomes an attachment. */\nconst FileDropzone: React.FC<IFileDropzoneProps> = ({\n disabled = false,\n onFilesDropped,\n className,\n overlayClassName,\n stopPropagation = false,\n children,\n}) => {\n const { t } = useTranslation('ui');\n const [isDraggingOver, setIsDraggingOver] = useState(false);\n const dragDepthRef = useRef(0);\n\n const hasFiles = (event: React.DragEvent): boolean => Array.from(event.dataTransfer?.types ?? []).includes('Files');\n\n const handleDragEnter = useCallback(\n (event: React.DragEvent) => {\n if (disabled || !hasFiles(event)) return;\n event.preventDefault();\n dragDepthRef.current += 1;\n setIsDraggingOver(true);\n },\n [disabled],\n );\n\n const handleDragOver = useCallback(\n (event: React.DragEvent) => {\n if (disabled || !hasFiles(event)) return;\n event.preventDefault();\n },\n [disabled],\n );\n\n const handleDragLeave = useCallback(\n (event: React.DragEvent) => {\n if (disabled || !hasFiles(event)) return;\n dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);\n if (dragDepthRef.current === 0) setIsDraggingOver(false);\n },\n [disabled],\n );\n\n const handleDrop = useCallback(\n (event: React.DragEvent) => {\n if (disabled) return;\n event.preventDefault();\n if (stopPropagation) event.stopPropagation();\n dragDepthRef.current = 0;\n setIsDraggingOver(false);\n\n const files = Array.from(event.dataTransfer?.files ?? []);\n if (files.length) onFilesDropped(files);\n },\n [disabled, onFilesDropped, stopPropagation],\n );\n\n return (\n <div\n className={mergeClasses('relative flex min-h-0 flex-1 flex-col', className)}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n {children}\n\n {isDraggingOver && (\n <div\n className={mergeClasses(\n 'pointer-events-none absolute inset-0 z-20 flex items-center justify-center rounded-2xl border-2 border-dashed border-brand-border bg-brand-soft/90',\n overlayClassName,\n )}\n >\n <div className=\"flex animate-pop-in flex-col items-center gap-y-2 text-brand-emphasis\">\n <MaterialSymbol icon=\"upload_file\" size={40} />\n <span className=\"text-sm font-medium\">{t('followUp.attachments.dropHere')}</span>\n </div>\n </div>\n )}\n </div>\n );\n};\n\nexport default FileDropzone;\n"],"names":[],"mappings":";;;;;AAiBA,MAAM,eAA6C,CAAC;AAAA,EAClD,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,KAAK;AAC1D,QAAM,eAAe,OAAO,CAAC;AAE7B,QAAM,WAAW,CAAC,UAAoC,MAAM,KAAK,MAAM,cAAc,SAAS,CAAA,CAAE,EAAE,SAAS,OAAO;AAElH,QAAM,kBAAkB;AAAA,IACtB,CAAC,UAA2B;AAC1B,UAAI,YAAY,CAAC,SAAS,KAAK,EAAG;AAClC,YAAM,eAAA;AACN,mBAAa,WAAW;AACxB,wBAAkB,IAAI;AAAA,IACxB;AAAA,IACA,CAAC,QAAQ;AAAA,EAAA;AAGX,QAAM,iBAAiB;AAAA,IACrB,CAAC,UAA2B;AAC1B,UAAI,YAAY,CAAC,SAAS,KAAK,EAAG;AAClC,YAAM,eAAA;AAAA,IACR;AAAA,IACA,CAAC,QAAQ;AAAA,EAAA;AAGX,QAAM,kBAAkB;AAAA,IACtB,CAAC,UAA2B;AAC1B,UAAI,YAAY,CAAC,SAAS,KAAK,EAAG;AAClC,mBAAa,UAAU,KAAK,IAAI,GAAG,aAAa,UAAU,CAAC;AAC3D,UAAI,aAAa,YAAY,EAAG,mBAAkB,KAAK;AAAA,IACzD;AAAA,IACA,CAAC,QAAQ;AAAA,EAAA;AAGX,QAAM,aAAa;AAAA,IACjB,CAAC,UAA2B;AAC1B,UAAI,SAAU;AACd,YAAM,eAAA;AACN,UAAI,uBAAuB,gBAAA;AAC3B,mBAAa,UAAU;AACvB,wBAAkB,KAAK;AAEvB,YAAM,QAAQ,MAAM,KAAK,MAAM,cAAc,SAAS,EAAE;AACxD,UAAI,MAAM,OAAQ,gBAAe,KAAK;AAAA,IACxC;AAAA,IACA,CAAC,UAAU,gBAAgB,eAAe;AAAA,EAAA;AAG5C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,aAAa,yCAAyC,SAAS;AAAA,MAC1E,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,QAAQ;AAAA,MAEP,UAAA;AAAA,QAAA;AAAA,QAEA,kBACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA;AAAA,YAAA;AAAA,YAGF,UAAA,qBAAC,OAAA,EAAI,WAAU,yEACb,UAAA;AAAA,cAAA,oBAAC,gBAAA,EAAe,MAAK,eAAc,MAAM,IAAI;AAAA,kCAC5C,QAAA,EAAK,WAAU,uBAAuB,UAAA,EAAE,+BAA+B,EAAA,CAAE;AAAA,YAAA,EAAA,CAC5E;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA;AAAA,EAAA;AAIR;"}
@@ -11,6 +11,12 @@ interface IPageTitleProps {
11
11
  title?: string;
12
12
  /** Substitui o span do título por conteúdo rico (ex.: nome editável inline da ficha técnica). */
13
13
  titleContent?: ReactNode;
14
+ /**
15
+ * Element of the default title. The page's main title passes `h1`, so a page has its heading
16
+ * without rebuilding these classes in `titleContent`; `span` stays the default because a compact
17
+ * or embedded header is not one, and neither are the several a screen can hold.
18
+ */
19
+ titleAs?: 'h1' | 'h2' | 'h3' | 'span';
14
20
  /** Rótulo pequeno em caixa alta acima do título (ex.: "FICHA TÉCNICA"). */
15
21
  eyebrow?: string;
16
22
  /** Conteúdo abaixo da linha do título, dentro da coluna de texto (tags, steppers). */
@@ -2,9 +2,9 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useState, useRef, useEffect } from "react";
3
3
  import classNames from "classnames";
4
4
  import { useTranslation } from "react-i18next";
5
- import { twMerge } from "tailwind-merge";
6
5
  import ControlledButton from "../Form/ControlledButton.js";
7
6
  import ControlledTooltip from "../UI/ControlledTooltip.js";
7
+ import { mergeClasses } from "../../helpers/mergeClasses.js";
8
8
  import { capitalizeAllLetters } from "../../helpers/stringHelpers.js";
9
9
  import { MaterialSymbol } from "../UI/MaterialSymbol/index.js";
10
10
  import ControlledBreadcrumb from "./ControlledBreadcrumb.js";
@@ -35,6 +35,7 @@ const PageTitle = ({
35
35
  canBackTo,
36
36
  title = "",
37
37
  titleContent,
38
+ titleAs: TitleTag = "span",
38
39
  eyebrow,
39
40
  subtitleSlot,
40
41
  headline,
@@ -160,6 +161,7 @@ const PageTitle = ({
160
161
  canBack && /* @__PURE__ */ jsx(ControlledTooltip, { content: t("actions.back"), children: /* @__PURE__ */ jsx(
161
162
  ControlledButton,
162
163
  {
164
+ "aria-label": t("actions.back"),
163
165
  defaultHtmlButton: true,
164
166
  onClick: handleBack,
165
167
  disabled: disableBack,
@@ -176,7 +178,7 @@ const PageTitle = ({
176
178
  src: titleImage.src,
177
179
  alt: titleImage.alt,
178
180
  onError: () => setFailedTitleImageSrc(titleImage.src),
179
- className: twMerge(
181
+ className: mergeClasses(
180
182
  "shrink-0 border border-border object-cover",
181
183
  mediaTransitionClassName,
182
184
  mediaBoxClassName,
@@ -186,7 +188,7 @@ const PageTitle = ({
186
188
  ) : resolvedTitleIcon && /* @__PURE__ */ jsx(
187
189
  "span",
188
190
  {
189
- className: twMerge(
191
+ className: mergeClasses(
190
192
  "flex shrink-0 items-center justify-center",
191
193
  mediaTransitionClassName,
192
194
  mediaBoxClassName,
@@ -199,7 +201,7 @@ const PageTitle = ({
199
201
  eyebrow && !isCollapsed && /* @__PURE__ */ jsx("span", { className: "mb-0.5 text-xs font-semibold uppercase tracking-widest text-foreground-faint", children: eyebrow }),
200
202
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
201
203
  titleContent ?? /* @__PURE__ */ jsx(
202
- "span",
204
+ TitleTag,
203
205
  {
204
206
  className: classNames(
205
207
  "min-w-0 truncate",
@@ -1 +1 @@
1
- {"version":3,"file":"PageTitle.js","sources":["../../../src/components/Navigation/PageTitle.tsx"],"sourcesContent":["import { ReactNode, RefObject, useEffect, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { capitalizeAllLetters } from '@/helpers/stringHelpers';\n\nimport { MaterialSymbol, SymbolCodepoints } from '../UI/MaterialSymbol';\n\nimport ControlledBreadcrumb from './ControlledBreadcrumb';\nimport { PageTitleCollapseProvider } from './PageTitleCollapseContext';\nimport { usePageTitleNavigation } from './PageTitleNavigationContext';\nimport { useUiNavigation } from './UiNavigationContext';\n\nexport interface IPageTitleImage {\n src: string;\n /** The image REPLACES the icon chip, so it carries the accessible name of what it depicts. */\n alt: string;\n /** Escape hatch for shape and size, e.g. `rounded-full` for an avatar. Merged last, so it wins. */\n className?: string;\n}\n\nconst MEDIA_BOX_CLASSNAME: Record<'sm' | 'md' | 'lg', string> = {\n sm: 'size-8 rounded-lg',\n md: 'size-10 rounded-xl',\n lg: 'size-20 rounded-xl',\n};\n\nconst MEDIA_ICON_SIZE: Record<'sm' | 'md' | 'lg', number> = { sm: 18, md: 22, lg: 34 };\n\n/** One step down, so a record cover stays recognisable in the collapsed header instead of vanishing. */\nconst COLLAPSED_MEDIA_SIZE: Record<'sm' | 'md' | 'lg', 'sm' | 'md' | 'lg'> = { sm: 'sm', md: 'sm', lg: 'md' };\n\n/**\n * Scroll offsets, in px, at which the header shrinks and grows back. They differ on purpose:\n * collapsing and expanding at the same position makes the header flicker for a whole gesture when a\n * scroll lands on it.\n */\nconst COLLAPSE_AT_SCROLL = 48;\nconst EXPAND_AT_SCROLL = 16;\n\nconst findScrollParent = (node: HTMLElement | null): HTMLElement | null => {\n let current = node?.parentElement ?? null;\n while (current) {\n const { overflowY } = getComputedStyle(current);\n if (overflowY === 'auto' || overflowY === 'scroll') return current;\n current = current.parentElement;\n }\n return null;\n};\n\ninterface IPageTitleProps {\n title?: string;\n /** Substitui o span do título por conteúdo rico (ex.: nome editável inline da ficha técnica). */\n titleContent?: ReactNode;\n /** Rótulo pequeno em caixa alta acima do título (ex.: \"FICHA TÉCNICA\"). */\n eyebrow?: string;\n /** Conteúdo abaixo da linha do título, dentro da coluna de texto (tags, steppers). */\n subtitleSlot?: ReactNode;\n slot?: ReactNode;\n /**\n * Where the `slot` sits against the text column. `center` (default) is right for a one-line title with\n * a couple of buttons; `start` pins it to the TOP of the header, which is what a tall identification\n * block needs so its actions line up with the eyebrow instead of floating at the middle.\n */\n slotAlign?: 'center' | 'start';\n /** Conteúdo renderizado à ESQUERDA, imediatamente ao lado do título (o `slot` fica à direita). */\n titleSlot?: ReactNode;\n /** Conteúdo renderizado ANTES do ícone/título (ex.: botão voltar customizado, alvo de drop). */\n leadingSlot?: ReactNode;\n /** Trilha custom acima do título (use `ControlledBreadcrumb`). Sem ela, a trilha é derivada do menu. */\n breadcrumb?: ReactNode;\n /** Deriva a trilha automaticamente da árvore de navegação quando `breadcrumb` não é passado. */\n autoBreadcrumb?: boolean;\n /** Ícone em chip à esquerda do título. Sem ele, herda o ícone do item de menu da rota atual. */\n titleIcon?: SymbolCodepoints;\n titleIconClassName?: string;\n /**\n * An image in the icon chip's place, at the same size and radius (e.g. the product cover on a\n * datasheet header). A src that fails to load falls back to the icon instead of leaving a broken\n * image in the header.\n */\n titleImage?: IPageTitleImage;\n /**\n * Size of the leading media (icon chip or `titleImage`). `md` is the 2.5rem chip every page uses;\n * `lg` is the 5rem cover of a RECORD header, where the image identifies the record itself (a product\n * datasheet). `compact` already implies `sm`, so pass this only to go bigger.\n */\n titleMediaSize?: 'sm' | 'md' | 'lg';\n /**\n * The header shrinks once the content beside it has been scrolled: the breadcrumb, the eyebrow, the\n * headline and the `subtitleSlot` go, the media drops one size (the record's picture stays, at the\n * icon chip's size) and the title takes its compact type. The back button and the `slot` never move.\n *\n * It only applies where the header STAYS ON SCREEN while something else scrolls, which is a page\n * whose content lives in a container beside the header (`collapseScrollRef`). When the page itself\n * scrolls, the header goes with it and there is nothing to shrink: pinning it there would mean\n * painting a background under it, and a header that grows a surface halfway through a scroll reads\n * as a panel appearing out of nowhere.\n *\n * EXPANDED, IT RENDERS EXACTLY WHAT IT RENDERED BEFORE THIS EXISTED: no wrapper node, no extra\n * element, no background, no positioning. That is deliberate. The first version added a sentinel\n * node as a sibling of the header, and since almost every page wraps it in a `flex flex-col\n * gap-y-*`, that node became a flex item and every page grew a stray gap.\n */\n collapseOnScroll?: boolean;\n /**\n * The element whose scrolling drives the collapse. Only needed when the page's content scrolls in a\n * container that is NOT an ancestor of the header, which is what a split layout does (a fixed header\n * over an independently scrolling body). Left out, the header finds its own scrollable ancestor.\n */\n collapseScrollRef?: RefObject<HTMLElement | null>;\n /** Deriva o ícone automaticamente do menu quando `titleIcon` não é passado. */\n autoIcon?: boolean;\n /** Variante compacta: título menor, para headers de navegação interna (ex.: pastas). */\n compact?: boolean;\n /** Desligue para títulos digitados pelo usuário (nomes de pastas etc.), que não podem ter a caixa alterada. */\n capitalizeTitle?: boolean;\n /** Desligue quando o header é seguido imediatamente por tabs ou outra divisão própria. */\n showDivider?: boolean;\n canBack?: boolean;\n canBackTo?: string;\n canFloat?: boolean;\n headline?: string;\n className?: string;\n titleClassName?: string;\n onBackClick?: () => void;\n disableBack?: boolean;\n}\n\nconst PageTitle: React.FC<IPageTitleProps> = ({\n canBack = false,\n canFloat = false,\n canBackTo,\n title = '',\n titleContent,\n eyebrow,\n subtitleSlot,\n headline,\n slot,\n slotAlign = 'center',\n titleSlot,\n leadingSlot,\n breadcrumb,\n autoBreadcrumb = true,\n titleIcon,\n titleIconClassName,\n titleImage,\n titleMediaSize,\n collapseOnScroll = true,\n collapseScrollRef,\n autoIcon = true,\n compact = false,\n capitalizeTitle = true,\n showDivider = true,\n className,\n titleClassName,\n onBackClick,\n disableBack = false,\n}) => {\n const { t } = useTranslation('ui');\n const { navigate, goBack, location } = useUiNavigation();\n\n const isFirefox = () => typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().includes('firefox');\n\n const handleBack = () => {\n if (disableBack) return;\n\n if (onBackClick) {\n onBackClick();\n } else if (canBackTo) {\n navigate(canBackTo);\n } else {\n goBack();\n }\n };\n\n const { resolveTrail } = usePageTitleNavigation();\n\n const menuTrail = useMemo(() => {\n const needsTrail = (autoBreadcrumb && !breadcrumb && !canFloat) || (autoIcon && !titleIcon && !titleImage);\n return needsTrail ? resolveTrail(location.pathname) : [];\n }, [autoBreadcrumb, breadcrumb, canFloat, autoIcon, titleIcon, titleImage, location.pathname, resolveTrail]);\n\n const resolvedBreadcrumb =\n breadcrumb ??\n (autoBreadcrumb && !canFloat && menuTrail.length >= 2 ? (\n <ControlledBreadcrumb\n ariaLabel={t('pageTitle.breadcrumbLabel')}\n items={menuTrail.map((item, index) => ({\n key: item.key,\n label: item.label,\n to: index < menuTrail.length - 1 ? item.to : undefined,\n }))}\n />\n ) : undefined);\n\n const resolvedTitleIcon = titleIcon ?? (autoIcon ? menuTrail[menuTrail.length - 1]?.icon : undefined);\n\n // Holds the src that failed rather than a boolean, so pointing at another image retries on its own.\n const [failedTitleImageSrc, setFailedTitleImageSrc] = useState<string | null>(null);\n const rendersTitleImage = Boolean(titleImage?.src) && titleImage?.src !== failedTitleImageSrc;\n\n const rootRef = useRef<HTMLDivElement>(null);\n const [isCollapsed, setIsCollapsed] = useState(false);\n // A floating header is already pinned by its own mechanism, and collapsing it would fight it.\n const collapses = collapseOnScroll && !canFloat;\n\n useEffect(() => {\n if (!collapses) {\n setIsCollapsed(false);\n return;\n }\n\n const root = rootRef.current;\n if (!root) return;\n\n const container = collapseScrollRef?.current ?? findScrollParent(root);\n // A container that CONTAINS the header is the page scrolling, header included: it leaves with\n // the content, so there is nothing to shrink. Shrinking only means something where the header\n // stays put while the content moves, which is a container beside it, never around it.\n if (!container || container.contains(root)) return;\n\n let frame = 0;\n\n const evaluate = () => {\n frame = 0;\n const offset = container.scrollTop;\n setIsCollapsed((previous) => (previous ? offset > EXPAND_AT_SCROLL : offset >= COLLAPSE_AT_SCROLL));\n };\n\n const handleScroll = () => {\n if (!frame) frame = requestAnimationFrame(evaluate);\n };\n\n evaluate();\n container.addEventListener('scroll', handleScroll, { passive: true });\n return () => {\n container.removeEventListener('scroll', handleScroll);\n if (frame) cancelAnimationFrame(frame);\n };\n }, [collapses, collapseScrollRef]);\n\n const isCompactLayout = compact || isCollapsed;\n const resolvedMediaSize = titleMediaSize ?? (compact ? 'sm' : 'md');\n const mediaSize = isCollapsed ? COLLAPSED_MEDIA_SIZE[resolvedMediaSize] : resolvedMediaSize;\n const mediaBoxClassName = MEDIA_BOX_CLASSNAME[mediaSize];\n const mediaIconSize = MEDIA_ICON_SIZE[mediaSize];\n const mediaTransitionClassName = 'transition-[width,height] duration-base ease-enter';\n const collapseValue = useMemo(() => ({ isCollapsed }), [isCollapsed]);\n\n return (\n <PageTitleCollapseProvider value={collapseValue}>\n <div\n ref={rootRef}\n className={classNames(\n 'flex flex-col',\n {\n 'zIndexMax fixed top-16 mr-7': canFloat,\n // Collapsed it keeps NO background: the header never has content passing behind it, so\n // painting one would only add a panel nobody asked for. The padding is what keeps the\n // shrunken row off the content below instead.\n 'py-2': isCollapsed,\n },\n className,\n )}\n style={{\n width: canFloat ? (isFirefox() ? '-moz-available' : '-webkit-fill-available') : '100%',\n }}\n >\n {resolvedBreadcrumb && !isCollapsed && <div className=\"mb-2 shrink-0\">{resolvedBreadcrumb}</div>}\n\n <div\n className={classNames('flex w-full flex-wrap justify-between gap-x-4 gap-y-2', {\n 'items-start': slotAlign === 'start',\n 'items-center': slotAlign !== 'start',\n 'bg-surface py-5': canFloat,\n })}\n >\n <div className={classNames('flex min-w-0 flex-1 basis-60 items-center', isCompactLayout ? 'gap-2' : 'gap-3')}>\n {leadingSlot}\n\n {canBack && (\n <ControlledTooltip content={t('actions.back')}>\n <ControlledButton\n defaultHtmlButton\n onClick={handleBack}\n disabled={disableBack}\n className={classNames(\n 'flex shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-interactive duration-base ease-enter hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border active:scale-90 disabled:pointer-events-none disabled:text-foreground-faint',\n isCompactLayout ? 'size-8' : 'size-9',\n )}\n >\n <MaterialSymbol icon=\"arrow_back\" size={isCompactLayout ? 18 : 20} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n {rendersTitleImage && titleImage ? (\n <img\n src={titleImage.src}\n alt={titleImage.alt}\n onError={() => setFailedTitleImageSrc(titleImage.src)}\n className={twMerge(\n 'shrink-0 border border-border object-cover',\n mediaTransitionClassName,\n mediaBoxClassName,\n titleImage.className,\n )}\n />\n ) : (\n resolvedTitleIcon && (\n <span\n className={twMerge(\n 'flex shrink-0 items-center justify-center',\n mediaTransitionClassName,\n mediaBoxClassName,\n titleIconClassName ?? 'bg-brand-soft text-brand-emphasis',\n )}\n >\n <MaterialSymbol icon={resolvedTitleIcon} size={mediaIconSize} fill className=\"text-inherit\" />\n </span>\n )\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {eyebrow && !isCollapsed && (\n <span className=\"mb-0.5 text-xs font-semibold uppercase tracking-widest text-foreground-faint\">\n {eyebrow}\n </span>\n )}\n\n <div className=\"flex min-w-0 items-center gap-2\">\n {titleContent ?? (\n <span\n className={classNames(\n 'min-w-0 truncate',\n isCompactLayout\n ? 'text-sm font-semibold text-foreground'\n : 'text-lg font-bold tracking-tight text-foreground laptop:text-xl',\n titleClassName,\n )}\n >\n {capitalizeTitle ? capitalizeAllLetters(title) : title}\n </span>\n )}\n\n {titleSlot}\n </div>\n\n {headline && !isCollapsed && <span className=\"mt-0.5 text-xs text-foreground-subtle\">{headline}</span>}\n\n {subtitleSlot && !isCollapsed && <div className=\"mt-1 flex min-w-0 flex-col gap-1\">{subtitleSlot}</div>}\n </div>\n </div>\n\n {slot && (\n <div\n className={classNames(\n 'ml-auto flex shrink-0 flex-wrap justify-end gap-2',\n slotAlign === 'start' ? 'items-start' : 'items-center',\n )}\n >\n {slot}\n </div>\n )}\n </div>\n {showDivider && !canFloat && (title.length > 0 || Boolean(titleContent)) && (\n <hr className={classNames('border-border-subtle', isCompactLayout ? 'mb-2 mt-2' : 'mb-2 mt-3')} />\n )}\n </div>\n </PageTitleCollapseProvider>\n );\n};\n\nexport default PageTitle;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PageTitle.js","sources":["../../../src/components/Navigation/PageTitle.tsx"],"sourcesContent":["import { ReactNode, RefObject, useEffect, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport { capitalizeAllLetters } from '@/helpers/stringHelpers';\n\nimport { MaterialSymbol, SymbolCodepoints } from '../UI/MaterialSymbol';\n\nimport ControlledBreadcrumb from './ControlledBreadcrumb';\nimport { PageTitleCollapseProvider } from './PageTitleCollapseContext';\nimport { usePageTitleNavigation } from './PageTitleNavigationContext';\nimport { useUiNavigation } from './UiNavigationContext';\n\nexport interface IPageTitleImage {\n src: string;\n /** The image REPLACES the icon chip, so it carries the accessible name of what it depicts. */\n alt: string;\n /** Escape hatch for shape and size, e.g. `rounded-full` for an avatar. Merged last, so it wins. */\n className?: string;\n}\n\nconst MEDIA_BOX_CLASSNAME: Record<'sm' | 'md' | 'lg', string> = {\n sm: 'size-8 rounded-lg',\n md: 'size-10 rounded-xl',\n lg: 'size-20 rounded-xl',\n};\n\nconst MEDIA_ICON_SIZE: Record<'sm' | 'md' | 'lg', number> = { sm: 18, md: 22, lg: 34 };\n\n/** One step down, so a record cover stays recognisable in the collapsed header instead of vanishing. */\nconst COLLAPSED_MEDIA_SIZE: Record<'sm' | 'md' | 'lg', 'sm' | 'md' | 'lg'> = { sm: 'sm', md: 'sm', lg: 'md' };\n\n/**\n * Scroll offsets, in px, at which the header shrinks and grows back. They differ on purpose:\n * collapsing and expanding at the same position makes the header flicker for a whole gesture when a\n * scroll lands on it.\n */\nconst COLLAPSE_AT_SCROLL = 48;\nconst EXPAND_AT_SCROLL = 16;\n\nconst findScrollParent = (node: HTMLElement | null): HTMLElement | null => {\n let current = node?.parentElement ?? null;\n while (current) {\n const { overflowY } = getComputedStyle(current);\n if (overflowY === 'auto' || overflowY === 'scroll') return current;\n current = current.parentElement;\n }\n return null;\n};\n\ninterface IPageTitleProps {\n title?: string;\n /** Substitui o span do título por conteúdo rico (ex.: nome editável inline da ficha técnica). */\n titleContent?: ReactNode;\n /**\n * Element of the default title. The page's main title passes `h1`, so a page has its heading\n * without rebuilding these classes in `titleContent`; `span` stays the default because a compact\n * or embedded header is not one, and neither are the several a screen can hold.\n */\n titleAs?: 'h1' | 'h2' | 'h3' | 'span';\n /** Rótulo pequeno em caixa alta acima do título (ex.: \"FICHA TÉCNICA\"). */\n eyebrow?: string;\n /** Conteúdo abaixo da linha do título, dentro da coluna de texto (tags, steppers). */\n subtitleSlot?: ReactNode;\n slot?: ReactNode;\n /**\n * Where the `slot` sits against the text column. `center` (default) is right for a one-line title with\n * a couple of buttons; `start` pins it to the TOP of the header, which is what a tall identification\n * block needs so its actions line up with the eyebrow instead of floating at the middle.\n */\n slotAlign?: 'center' | 'start';\n /** Conteúdo renderizado à ESQUERDA, imediatamente ao lado do título (o `slot` fica à direita). */\n titleSlot?: ReactNode;\n /** Conteúdo renderizado ANTES do ícone/título (ex.: botão voltar customizado, alvo de drop). */\n leadingSlot?: ReactNode;\n /** Trilha custom acima do título (use `ControlledBreadcrumb`). Sem ela, a trilha é derivada do menu. */\n breadcrumb?: ReactNode;\n /** Deriva a trilha automaticamente da árvore de navegação quando `breadcrumb` não é passado. */\n autoBreadcrumb?: boolean;\n /** Ícone em chip à esquerda do título. Sem ele, herda o ícone do item de menu da rota atual. */\n titleIcon?: SymbolCodepoints;\n titleIconClassName?: string;\n /**\n * An image in the icon chip's place, at the same size and radius (e.g. the product cover on a\n * datasheet header). A src that fails to load falls back to the icon instead of leaving a broken\n * image in the header.\n */\n titleImage?: IPageTitleImage;\n /**\n * Size of the leading media (icon chip or `titleImage`). `md` is the 2.5rem chip every page uses;\n * `lg` is the 5rem cover of a RECORD header, where the image identifies the record itself (a product\n * datasheet). `compact` already implies `sm`, so pass this only to go bigger.\n */\n titleMediaSize?: 'sm' | 'md' | 'lg';\n /**\n * The header shrinks once the content beside it has been scrolled: the breadcrumb, the eyebrow, the\n * headline and the `subtitleSlot` go, the media drops one size (the record's picture stays, at the\n * icon chip's size) and the title takes its compact type. The back button and the `slot` never move.\n *\n * It only applies where the header STAYS ON SCREEN while something else scrolls, which is a page\n * whose content lives in a container beside the header (`collapseScrollRef`). When the page itself\n * scrolls, the header goes with it and there is nothing to shrink: pinning it there would mean\n * painting a background under it, and a header that grows a surface halfway through a scroll reads\n * as a panel appearing out of nowhere.\n *\n * EXPANDED, IT RENDERS EXACTLY WHAT IT RENDERED BEFORE THIS EXISTED: no wrapper node, no extra\n * element, no background, no positioning. That is deliberate. The first version added a sentinel\n * node as a sibling of the header, and since almost every page wraps it in a `flex flex-col\n * gap-y-*`, that node became a flex item and every page grew a stray gap.\n */\n collapseOnScroll?: boolean;\n /**\n * The element whose scrolling drives the collapse. Only needed when the page's content scrolls in a\n * container that is NOT an ancestor of the header, which is what a split layout does (a fixed header\n * over an independently scrolling body). Left out, the header finds its own scrollable ancestor.\n */\n collapseScrollRef?: RefObject<HTMLElement | null>;\n /** Deriva o ícone automaticamente do menu quando `titleIcon` não é passado. */\n autoIcon?: boolean;\n /** Variante compacta: título menor, para headers de navegação interna (ex.: pastas). */\n compact?: boolean;\n /** Desligue para títulos digitados pelo usuário (nomes de pastas etc.), que não podem ter a caixa alterada. */\n capitalizeTitle?: boolean;\n /** Desligue quando o header é seguido imediatamente por tabs ou outra divisão própria. */\n showDivider?: boolean;\n canBack?: boolean;\n canBackTo?: string;\n canFloat?: boolean;\n headline?: string;\n className?: string;\n titleClassName?: string;\n onBackClick?: () => void;\n disableBack?: boolean;\n}\n\nconst PageTitle: React.FC<IPageTitleProps> = ({\n canBack = false,\n canFloat = false,\n canBackTo,\n title = '',\n titleContent,\n titleAs: TitleTag = 'span',\n eyebrow,\n subtitleSlot,\n headline,\n slot,\n slotAlign = 'center',\n titleSlot,\n leadingSlot,\n breadcrumb,\n autoBreadcrumb = true,\n titleIcon,\n titleIconClassName,\n titleImage,\n titleMediaSize,\n collapseOnScroll = true,\n collapseScrollRef,\n autoIcon = true,\n compact = false,\n capitalizeTitle = true,\n showDivider = true,\n className,\n titleClassName,\n onBackClick,\n disableBack = false,\n}) => {\n const { t } = useTranslation('ui');\n const { navigate, goBack, location } = useUiNavigation();\n\n const isFirefox = () => typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().includes('firefox');\n\n const handleBack = () => {\n if (disableBack) return;\n\n if (onBackClick) {\n onBackClick();\n } else if (canBackTo) {\n navigate(canBackTo);\n } else {\n goBack();\n }\n };\n\n const { resolveTrail } = usePageTitleNavigation();\n\n const menuTrail = useMemo(() => {\n const needsTrail = (autoBreadcrumb && !breadcrumb && !canFloat) || (autoIcon && !titleIcon && !titleImage);\n return needsTrail ? resolveTrail(location.pathname) : [];\n }, [autoBreadcrumb, breadcrumb, canFloat, autoIcon, titleIcon, titleImage, location.pathname, resolveTrail]);\n\n const resolvedBreadcrumb =\n breadcrumb ??\n (autoBreadcrumb && !canFloat && menuTrail.length >= 2 ? (\n <ControlledBreadcrumb\n ariaLabel={t('pageTitle.breadcrumbLabel')}\n items={menuTrail.map((item, index) => ({\n key: item.key,\n label: item.label,\n to: index < menuTrail.length - 1 ? item.to : undefined,\n }))}\n />\n ) : undefined);\n\n const resolvedTitleIcon = titleIcon ?? (autoIcon ? menuTrail[menuTrail.length - 1]?.icon : undefined);\n\n // Holds the src that failed rather than a boolean, so pointing at another image retries on its own.\n const [failedTitleImageSrc, setFailedTitleImageSrc] = useState<string | null>(null);\n const rendersTitleImage = Boolean(titleImage?.src) && titleImage?.src !== failedTitleImageSrc;\n\n const rootRef = useRef<HTMLDivElement>(null);\n const [isCollapsed, setIsCollapsed] = useState(false);\n // A floating header is already pinned by its own mechanism, and collapsing it would fight it.\n const collapses = collapseOnScroll && !canFloat;\n\n useEffect(() => {\n if (!collapses) {\n setIsCollapsed(false);\n return;\n }\n\n const root = rootRef.current;\n if (!root) return;\n\n const container = collapseScrollRef?.current ?? findScrollParent(root);\n // A container that CONTAINS the header is the page scrolling, header included: it leaves with\n // the content, so there is nothing to shrink. Shrinking only means something where the header\n // stays put while the content moves, which is a container beside it, never around it.\n if (!container || container.contains(root)) return;\n\n let frame = 0;\n\n const evaluate = () => {\n frame = 0;\n const offset = container.scrollTop;\n setIsCollapsed((previous) => (previous ? offset > EXPAND_AT_SCROLL : offset >= COLLAPSE_AT_SCROLL));\n };\n\n const handleScroll = () => {\n if (!frame) frame = requestAnimationFrame(evaluate);\n };\n\n evaluate();\n container.addEventListener('scroll', handleScroll, { passive: true });\n return () => {\n container.removeEventListener('scroll', handleScroll);\n if (frame) cancelAnimationFrame(frame);\n };\n }, [collapses, collapseScrollRef]);\n\n const isCompactLayout = compact || isCollapsed;\n const resolvedMediaSize = titleMediaSize ?? (compact ? 'sm' : 'md');\n const mediaSize = isCollapsed ? COLLAPSED_MEDIA_SIZE[resolvedMediaSize] : resolvedMediaSize;\n const mediaBoxClassName = MEDIA_BOX_CLASSNAME[mediaSize];\n const mediaIconSize = MEDIA_ICON_SIZE[mediaSize];\n const mediaTransitionClassName = 'transition-[width,height] duration-base ease-enter';\n const collapseValue = useMemo(() => ({ isCollapsed }), [isCollapsed]);\n\n return (\n <PageTitleCollapseProvider value={collapseValue}>\n <div\n ref={rootRef}\n className={classNames(\n 'flex flex-col',\n {\n 'zIndexMax fixed top-16 mr-7': canFloat,\n // Collapsed it keeps NO background: the header never has content passing behind it, so\n // painting one would only add a panel nobody asked for. The padding is what keeps the\n // shrunken row off the content below instead.\n 'py-2': isCollapsed,\n },\n className,\n )}\n style={{\n width: canFloat ? (isFirefox() ? '-moz-available' : '-webkit-fill-available') : '100%',\n }}\n >\n {resolvedBreadcrumb && !isCollapsed && <div className=\"mb-2 shrink-0\">{resolvedBreadcrumb}</div>}\n\n <div\n className={classNames('flex w-full flex-wrap justify-between gap-x-4 gap-y-2', {\n 'items-start': slotAlign === 'start',\n 'items-center': slotAlign !== 'start',\n 'bg-surface py-5': canFloat,\n })}\n >\n <div className={classNames('flex min-w-0 flex-1 basis-60 items-center', isCompactLayout ? 'gap-2' : 'gap-3')}>\n {leadingSlot}\n\n {canBack && (\n <ControlledTooltip content={t('actions.back')}>\n <ControlledButton\n aria-label={t('actions.back')}\n defaultHtmlButton\n onClick={handleBack}\n disabled={disableBack}\n className={classNames(\n 'flex shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-interactive duration-base ease-enter hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border active:scale-90 disabled:pointer-events-none disabled:text-foreground-faint',\n isCompactLayout ? 'size-8' : 'size-9',\n )}\n >\n <MaterialSymbol icon=\"arrow_back\" size={isCompactLayout ? 18 : 20} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n {rendersTitleImage && titleImage ? (\n <img\n src={titleImage.src}\n alt={titleImage.alt}\n onError={() => setFailedTitleImageSrc(titleImage.src)}\n className={mergeClasses(\n 'shrink-0 border border-border object-cover',\n mediaTransitionClassName,\n mediaBoxClassName,\n titleImage.className,\n )}\n />\n ) : (\n resolvedTitleIcon && (\n <span\n className={mergeClasses(\n 'flex shrink-0 items-center justify-center',\n mediaTransitionClassName,\n mediaBoxClassName,\n titleIconClassName ?? 'bg-brand-soft text-brand-emphasis',\n )}\n >\n <MaterialSymbol icon={resolvedTitleIcon} size={mediaIconSize} fill className=\"text-inherit\" />\n </span>\n )\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {eyebrow && !isCollapsed && (\n <span className=\"mb-0.5 text-xs font-semibold uppercase tracking-widest text-foreground-faint\">\n {eyebrow}\n </span>\n )}\n\n <div className=\"flex min-w-0 items-center gap-2\">\n {titleContent ?? (\n <TitleTag\n className={classNames(\n 'min-w-0 truncate',\n isCompactLayout\n ? 'text-sm font-semibold text-foreground'\n : 'text-lg font-bold tracking-tight text-foreground laptop:text-xl',\n titleClassName,\n )}\n >\n {capitalizeTitle ? capitalizeAllLetters(title) : title}\n </TitleTag>\n )}\n\n {titleSlot}\n </div>\n\n {headline && !isCollapsed && <span className=\"mt-0.5 text-xs text-foreground-subtle\">{headline}</span>}\n\n {subtitleSlot && !isCollapsed && <div className=\"mt-1 flex min-w-0 flex-col gap-1\">{subtitleSlot}</div>}\n </div>\n </div>\n\n {slot && (\n <div\n className={classNames(\n 'ml-auto flex shrink-0 flex-wrap justify-end gap-2',\n slotAlign === 'start' ? 'items-start' : 'items-center',\n )}\n >\n {slot}\n </div>\n )}\n </div>\n {showDivider && !canFloat && (title.length > 0 || Boolean(titleContent)) && (\n <hr className={classNames('border-border-subtle', isCompactLayout ? 'mb-2 mt-2' : 'mb-2 mt-3')} />\n )}\n </div>\n </PageTitleCollapseProvider>\n );\n};\n\nexport default PageTitle;\n"],"names":[],"mappings":"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"}
@@ -167,6 +167,7 @@ const FiltersPanel = ({
167
167
  /* @__PURE__ */ jsx(ControlledTooltip, { className: "z-[9999]", content: t("actions.close"), children: /* @__PURE__ */ jsx(
168
168
  ControlledButton,
169
169
  {
170
+ "aria-label": t("actions.close"),
170
171
  defaultHtmlButton: true,
171
172
  onClick: closeRequest,
172
173
  className: "rounded-full p-1 text-foreground-subtle transition-colors hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border",