@umodeapporg/ui 0.27.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 (153) 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/DataTableBulkEditDialog.js +4 -2
  87. package/dist/components/UI/DataTable/DataTableBulkEditDialog.js.map +1 -1
  88. package/dist/components/UI/DataTable/DataTableBulkEditRowsPicker.js +1 -1
  89. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.d.ts +5 -1
  90. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js +19 -4
  91. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js.map +1 -1
  92. package/dist/components/UI/DataTable/DataTableColumnToggle.js +3 -1
  93. package/dist/components/UI/DataTable/DataTableColumnToggle.js.map +1 -1
  94. package/dist/components/UI/DataTable/DataTableFilters.js +1 -1
  95. package/dist/components/UI/DataTable/DataTablePagination.js +4 -0
  96. package/dist/components/UI/DataTable/DataTablePagination.js.map +1 -1
  97. package/dist/components/UI/DataTable/DataTableStates.js +2 -2
  98. package/dist/components/UI/DataTable/DataTableStates.js.map +1 -1
  99. package/dist/components/UI/DataTable/DataTableToolbar.js +1 -1
  100. package/dist/components/UI/DataTable/applyDataTableFilters.js +1 -1
  101. package/dist/components/UI/DataTable/cells/DataTableEditableCell.js +2 -1
  102. package/dist/components/UI/DataTable/cells/DataTableEditableCell.js.map +1 -1
  103. package/dist/components/UI/DataTable/types.d.ts +4 -0
  104. package/dist/components/UI/DataTable/types.js.map +1 -1
  105. package/dist/components/UI/Flow/FlowBlockNode.js +1 -0
  106. package/dist/components/UI/Flow/FlowBlockNode.js.map +1 -1
  107. package/dist/components/UI/Flow/FlowCanvas.js +2 -2
  108. package/dist/components/UI/Flow/FlowCanvas.js.map +1 -1
  109. package/dist/components/UI/Flow/FlowConnectionEdge.js +1 -0
  110. package/dist/components/UI/Flow/FlowConnectionEdge.js.map +1 -1
  111. package/dist/components/UI/Flow/FlowInspector.js +3 -2
  112. package/dist/components/UI/Flow/FlowInspector.js.map +1 -1
  113. package/dist/components/UI/Flow/FlowIssueList.js +2 -2
  114. package/dist/components/UI/Flow/FlowIssueList.js.map +1 -1
  115. package/dist/components/UI/Flow/FlowLaneMap.js +2 -2
  116. package/dist/components/UI/Flow/FlowLaneMap.js.map +1 -1
  117. package/dist/components/UI/Flow/FlowResizablePanel.js +3 -2
  118. package/dist/components/UI/Flow/FlowResizablePanel.js.map +1 -1
  119. package/dist/components/UI/Flow/FlowZoomControls.js +1 -0
  120. package/dist/components/UI/Flow/FlowZoomControls.js.map +1 -1
  121. package/dist/components/UI/HorizontalScrollArea.d.ts +1 -1
  122. package/dist/components/UI/HorizontalScrollArea.js +1 -0
  123. package/dist/components/UI/HorizontalScrollArea.js.map +1 -1
  124. package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js +1 -0
  125. package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js.map +1 -1
  126. package/dist/components/UI/ImagePlaceholder.js +5 -2
  127. package/dist/components/UI/ImagePlaceholder.js.map +1 -1
  128. package/dist/components/UI/ImageZoomOverlay.js +1 -0
  129. package/dist/components/UI/ImageZoomOverlay.js.map +1 -1
  130. package/dist/components/UI/MaterialSymbol/index.js +5 -0
  131. package/dist/components/UI/MaterialSymbol/index.js.map +1 -1
  132. package/dist/components/UI/NoDataFound/index.js +3 -3
  133. package/dist/components/UI/NoDataFound/index.js.map +1 -1
  134. package/dist/components/UI/RichTextContent.js +3 -3
  135. package/dist/components/UI/RichTextContent.js.map +1 -1
  136. package/dist/components/UI/ThreeDots.js +15 -11
  137. package/dist/components/UI/ThreeDots.js.map +1 -1
  138. package/dist/helpers/mergeClasses.d.ts +7 -3
  139. package/dist/helpers/mergeClasses.js +23 -1
  140. package/dist/helpers/mergeClasses.js.map +1 -1
  141. package/dist/hooks/useVoiceTranscription.js +5 -2
  142. package/dist/hooks/useVoiceTranscription.js.map +1 -1
  143. package/dist/index.d.ts +2 -0
  144. package/dist/index.js +58 -54
  145. package/dist/index.js.map +1 -1
  146. package/dist/locales/en/ui.json.d.ts +8 -3
  147. package/dist/locales/en/ui.json.js +3 -3
  148. package/dist/locales/index.d.ts +10 -0
  149. package/dist/locales/pt/ui.json.d.ts +8 -3
  150. package/dist/locales/pt/ui.json.js +3 -3
  151. package/package.json +1 -1
  152. package/src/assets/styles/base.css +7 -1
  153. package/tailwind-plugins/glass.js +23 -28
@@ -1,6 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { twMerge } from "tailwind-merge";
3
2
  import { MaterialSymbol } from "./MaterialSymbol/index.js";
3
+ import { mergeClasses } from "../../helpers/mergeClasses.js";
4
4
  const VARIANT_ICON = {
5
5
  empty: "image",
6
6
  broken: "broken_image"
@@ -9,7 +9,10 @@ const ImagePlaceholder = ({ variant = "empty", className }) => /* @__PURE__ */ j
9
9
  "div",
10
10
  {
11
11
  "aria-hidden": true,
12
- className: twMerge("flex size-full items-center justify-center overflow-hidden bg-muted @container", className),
12
+ className: mergeClasses(
13
+ "flex size-full items-center justify-center overflow-hidden bg-muted @container",
14
+ className
15
+ ),
13
16
  children: /* @__PURE__ */ jsx(
14
17
  MaterialSymbol,
15
18
  {
@@ -1 +1 @@
1
- {"version":3,"file":"ImagePlaceholder.js","sources":["../../../src/components/UI/ImagePlaceholder.tsx"],"sourcesContent":["import { twMerge } from 'tailwind-merge';\n\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\n\nexport type TImagePlaceholderVariant = 'empty' | 'broken';\n\ninterface IImagePlaceholderProps {\n /**\n * `empty` = there is no image for this record (the normal state).\n * `broken` = there IS a source but it failed to load (a real problem worth distinguishing).\n */\n variant?: TImagePlaceholderVariant;\n className?: string;\n}\n\nconst VARIANT_ICON: Record<TImagePlaceholderVariant, SymbolCodepoints> = {\n empty: 'image',\n broken: 'broken_image',\n};\n\n/**\n * The single placeholder for \"no image here\", replacing the fixed missing-image.png.\n *\n * A raster asset cannot follow the theme: the old PNG was a dark tile, so it read as a\n * hole in light mode and as a slightly-wrong-dark square in dark mode. This draws the\n * same idea from tokens instead, so it is correct in both themes for free.\n *\n * It is used at every size in the app, from a 40px table thumbnail to a full datasheet\n * panel, so the icon is sized in container-query units rather than a fixed rem: it scales\n * with whatever box it is dropped into, with a floor and a ceiling so it never becomes a\n * dot or a poster. `MaterialSymbol` receives no `size` prop on purpose, because that prop\n * writes an inline font-size which would beat this class.\n */\nconst ImagePlaceholder: React.FC<IImagePlaceholderProps> = ({ variant = 'empty', className }) => (\n <div\n aria-hidden\n className={twMerge('flex size-full items-center justify-center overflow-hidden bg-muted @container', className)}\n >\n <MaterialSymbol\n icon={VARIANT_ICON[variant]}\n weight={300}\n /* `!` is required: outlined.css pins `.material-symbols { font-size: 1.5rem }` and,\n being imported last, it wins the cascade over a plain utility. */\n className=\"!text-[clamp(0.875rem,34cqi,2.75rem)] leading-none text-foreground-faint\"\n />\n </div>\n);\n\nexport default ImagePlaceholder;\n"],"names":[],"mappings":";;;AAeA,MAAM,eAAmE;AAAA,EACvE,OAAO;AAAA,EACP,QAAQ;AACV;AAeA,MAAM,mBAAqD,CAAC,EAAE,UAAU,SAAS,gBAC/E;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,eAAW;AAAA,IACX,WAAW,QAAQ,kFAAkF,SAAS;AAAA,IAE9G,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAM,aAAa,OAAO;AAAA,QAC1B,QAAQ;AAAA,QAGR,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EACZ;AACF;"}
1
+ {"version":3,"file":"ImagePlaceholder.js","sources":["../../../src/components/UI/ImagePlaceholder.tsx"],"sourcesContent":["import { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\nexport type TImagePlaceholderVariant = 'empty' | 'broken';\n\ninterface IImagePlaceholderProps {\n /**\n * `empty` = there is no image for this record (the normal state).\n * `broken` = there IS a source but it failed to load (a real problem worth distinguishing).\n */\n variant?: TImagePlaceholderVariant;\n className?: string;\n}\n\nconst VARIANT_ICON: Record<TImagePlaceholderVariant, SymbolCodepoints> = {\n empty: 'image',\n broken: 'broken_image',\n};\n\n/**\n * The single placeholder for \"no image here\", replacing the fixed missing-image.png.\n *\n * A raster asset cannot follow the theme: the old PNG was a dark tile, so it read as a\n * hole in light mode and as a slightly-wrong-dark square in dark mode. This draws the\n * same idea from tokens instead, so it is correct in both themes for free.\n *\n * It is used at every size in the app, from a 40px table thumbnail to a full datasheet\n * panel, so the icon is sized in container-query units rather than a fixed rem: it scales\n * with whatever box it is dropped into, with a floor and a ceiling so it never becomes a\n * dot or a poster. `MaterialSymbol` receives no `size` prop on purpose, because that prop\n * writes an inline font-size which would beat this class.\n */\nconst ImagePlaceholder: React.FC<IImagePlaceholderProps> = ({ variant = 'empty', className }) => (\n <div\n aria-hidden\n className={mergeClasses(\n 'flex size-full items-center justify-center overflow-hidden bg-muted @container',\n className,\n )}\n >\n <MaterialSymbol\n icon={VARIANT_ICON[variant]}\n weight={300}\n /* `!` is required: outlined.css pins `.material-symbols { font-size: 1.5rem }` and,\n being imported last, it wins the cascade over a plain utility. */\n className=\"!text-[clamp(0.875rem,34cqi,2.75rem)] leading-none text-foreground-faint\"\n />\n </div>\n);\n\nexport default ImagePlaceholder;\n"],"names":[],"mappings":";;;AAcA,MAAM,eAAmE;AAAA,EACvE,OAAO;AAAA,EACP,QAAQ;AACV;AAeA,MAAM,mBAAqD,CAAC,EAAE,UAAU,SAAS,gBAC/E;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,eAAW;AAAA,IACX,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IAAA;AAAA,IAGF,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAM,aAAa,OAAO;AAAA,QAC1B,QAAQ;AAAA,QAGR,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EACZ;AACF;"}
@@ -33,6 +33,7 @@ const ImageZoomOverlay = ({ src, onClose }) => {
33
33
  /* @__PURE__ */ jsx(ControlledTooltip, { content: t("imageZoom.close"), className: "z-[9999]", children: /* @__PURE__ */ jsx(
34
34
  ControlledButton,
35
35
  {
36
+ "aria-label": t("actions.close"),
36
37
  defaultHtmlButton: true,
37
38
  onClick: onClose,
38
39
  className: "absolute right-4 top-4 flex items-center justify-center rounded-full bg-glass/10 p-2 text-white transition-interactive duration-base ease-enter hover:bg-glass/20 active:scale-90",
@@ -1 +1 @@
1
- {"version":3,"file":"ImageZoomOverlay.js","sources":["../../../src/components/UI/ImageZoomOverlay.tsx"],"sourcesContent":["import React, { useEffect } from 'react';\nimport { createPortal } from 'react-dom';\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 { getPortalHost } from '@/helpers/portalHost';\n\ninterface IImageZoomOverlayProps {\n src: string;\n onClose: () => void;\n}\n\n// Visualização ampliada em tela cheia (portal). Fecha com Esc, clique fora ou\n// no botão de fechar.\nconst ImageZoomOverlay: React.FC<IImageZoomOverlayProps> = ({ src, onClose }) => {\n const { t } = useTranslation('ui');\n\n useEffect(() => {\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key !== 'Escape') return;\n // Na captura e barrando a propagação: o drawer que abriu esta imagem também escuta Esc no\n // document, e sem isso uma tecla fecharia a imagem E o drawer atrás dela.\n event.stopPropagation();\n onClose();\n };\n document.addEventListener('keydown', onKeyDown, true);\n return () => document.removeEventListener('keydown', onKeyDown, true);\n }, [onClose]);\n\n const portalHost = getPortalHost();\n if (portalHost === null) return null;\n\n return createPortal(\n <div\n onClick={onClose}\n // `role=\"dialog\"` não é enfeite: é como `isPressOnLayeredSurface` reconhece esta camada. Sem\n // ele, o drawer por baixo lê o clique no X como \"clicou fora de mim\" e se fecha junto.\n role=\"dialog\"\n aria-modal={true}\n aria-label={t('imageZoom.title')}\n // Same layer as ControlledDialog: mounted after the dialog that opened it, DOM order puts\n // the zoom on top. Any lower value hides it behind the dialog's scrim.\n className=\"fixed inset-0 z-[9999] flex items-center justify-center p-6 glass-scrim\"\n >\n <ControlledTooltip content={t('imageZoom.close')} className=\"z-[9999]\">\n <ControlledButton\n defaultHtmlButton\n onClick={onClose}\n className=\"absolute right-4 top-4 flex items-center justify-center rounded-full bg-glass/10 p-2 text-white transition-interactive duration-base ease-enter hover:bg-glass/20 active:scale-90\"\n >\n <MaterialSymbol icon=\"close\" size={24} />\n </ControlledButton>\n </ControlledTooltip>\n <div onClick={(event) => event.stopPropagation()} className=\"flex max-h-full max-w-full\">\n <ControlledImage src={src} className=\"max-h-[90vh] max-w-[90vw] object-contain\" />\n </div>\n </div>,\n portalHost,\n );\n};\n\nexport default ImageZoomOverlay;\n"],"names":[],"mappings":";;;;;;;;;AAiBA,MAAM,mBAAqD,CAAC,EAAE,KAAK,cAAc;AAC/E,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,YAAU,MAAM;AACd,UAAM,YAAY,CAAC,UAA+B;AAChD,UAAI,MAAM,QAAQ,SAAU;AAG5B,YAAM,gBAAA;AACN,cAAA;AAAA,IACF;AACA,aAAS,iBAAiB,WAAW,WAAW,IAAI;AACpD,WAAO,MAAM,SAAS,oBAAoB,WAAW,WAAW,IAAI;AAAA,EACtE,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,aAAa,cAAA;AACnB,MAAI,eAAe,KAAM,QAAO;AAEhC,SAAO;AAAA,IACL;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAS;AAAA,QAGT,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,cAAY,EAAE,iBAAiB;AAAA,QAG/B,WAAU;AAAA,QAEV,UAAA;AAAA,UAAA,oBAAC,qBAAkB,SAAS,EAAE,iBAAiB,GAAG,WAAU,YAC1D,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,mBAAiB;AAAA,cACjB,SAAS;AAAA,cACT,WAAU;AAAA,cAEV,UAAA,oBAAC,gBAAA,EAAe,MAAK,SAAQ,MAAM,GAAA,CAAI;AAAA,YAAA;AAAA,UAAA,GAE3C;AAAA,UACA,oBAAC,OAAA,EAAI,SAAS,CAAC,UAAU,MAAM,mBAAmB,WAAU,8BAC1D,UAAA,oBAAC,iBAAA,EAAgB,KAAU,WAAU,4CAA2C,EAAA,CAClF;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"ImageZoomOverlay.js","sources":["../../../src/components/UI/ImageZoomOverlay.tsx"],"sourcesContent":["import React, { useEffect } from 'react';\nimport { createPortal } from 'react-dom';\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 { getPortalHost } from '@/helpers/portalHost';\n\ninterface IImageZoomOverlayProps {\n src: string;\n onClose: () => void;\n}\n\n// Visualização ampliada em tela cheia (portal). Fecha com Esc, clique fora ou\n// no botão de fechar.\nconst ImageZoomOverlay: React.FC<IImageZoomOverlayProps> = ({ src, onClose }) => {\n const { t } = useTranslation('ui');\n\n useEffect(() => {\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key !== 'Escape') return;\n // Na captura e barrando a propagação: o drawer que abriu esta imagem também escuta Esc no\n // document, e sem isso uma tecla fecharia a imagem E o drawer atrás dela.\n event.stopPropagation();\n onClose();\n };\n document.addEventListener('keydown', onKeyDown, true);\n return () => document.removeEventListener('keydown', onKeyDown, true);\n }, [onClose]);\n\n const portalHost = getPortalHost();\n if (portalHost === null) return null;\n\n return createPortal(\n <div\n onClick={onClose}\n // `role=\"dialog\"` não é enfeite: é como `isPressOnLayeredSurface` reconhece esta camada. Sem\n // ele, o drawer por baixo lê o clique no X como \"clicou fora de mim\" e se fecha junto.\n role=\"dialog\"\n aria-modal={true}\n aria-label={t('imageZoom.title')}\n // Same layer as ControlledDialog: mounted after the dialog that opened it, DOM order puts\n // the zoom on top. Any lower value hides it behind the dialog's scrim.\n className=\"fixed inset-0 z-[9999] flex items-center justify-center p-6 glass-scrim\"\n >\n <ControlledTooltip content={t('imageZoom.close')} className=\"z-[9999]\">\n <ControlledButton\n aria-label={t('actions.close')}\n defaultHtmlButton\n onClick={onClose}\n className=\"absolute right-4 top-4 flex items-center justify-center rounded-full bg-glass/10 p-2 text-white transition-interactive duration-base ease-enter hover:bg-glass/20 active:scale-90\"\n >\n <MaterialSymbol icon=\"close\" size={24} />\n </ControlledButton>\n </ControlledTooltip>\n <div onClick={(event) => event.stopPropagation()} className=\"flex max-h-full max-w-full\">\n <ControlledImage src={src} className=\"max-h-[90vh] max-w-[90vw] object-contain\" />\n </div>\n </div>,\n portalHost,\n );\n};\n\nexport default ImageZoomOverlay;\n"],"names":[],"mappings":";;;;;;;;;AAiBA,MAAM,mBAAqD,CAAC,EAAE,KAAK,cAAc;AAC/E,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,YAAU,MAAM;AACd,UAAM,YAAY,CAAC,UAA+B;AAChD,UAAI,MAAM,QAAQ,SAAU;AAG5B,YAAM,gBAAA;AACN,cAAA;AAAA,IACF;AACA,aAAS,iBAAiB,WAAW,WAAW,IAAI;AACpD,WAAO,MAAM,SAAS,oBAAoB,WAAW,WAAW,IAAI;AAAA,EACtE,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,aAAa,cAAA;AACnB,MAAI,eAAe,KAAM,QAAO;AAEhC,SAAO;AAAA,IACL;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAS;AAAA,QAGT,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,cAAY,EAAE,iBAAiB;AAAA,QAG/B,WAAU;AAAA,QAEV,UAAA;AAAA,UAAA,oBAAC,qBAAkB,SAAS,EAAE,iBAAiB,GAAG,WAAU,YAC1D,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,cAAY,EAAE,eAAe;AAAA,cAC7B,mBAAiB;AAAA,cACjB,SAAS;AAAA,cACT,WAAU;AAAA,cAEV,UAAA,oBAAC,gBAAA,EAAe,MAAK,SAAQ,MAAM,GAAA,CAAI;AAAA,YAAA;AAAA,UAAA,GAE3C;AAAA,UACA,oBAAC,OAAA,EAAI,SAAS,CAAC,UAAU,MAAM,mBAAmB,WAAU,8BAC1D,UAAA,oBAAC,iBAAA,EAAgB,KAAU,WAAU,4CAA2C,EAAA,CAClF;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF;AAAA,EAAA;AAEJ;"}
@@ -1,5 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
+ const MEANINGFUL_PROPS = ["aria-label", "aria-labelledby", "role", "href", "tabIndex"];
3
4
  function combineClasses(...classes) {
4
5
  return classes.filter(Boolean).map((c) => c.trim()).join(" ");
5
6
  }
@@ -18,6 +19,9 @@ const MaterialSymbolBase = ({
18
19
  }, ref) => {
19
20
  const Component = onClick !== void 0 ? "button" : as || "span";
20
21
  const style = { color, ...propStyle };
22
+ const passed = props;
23
+ const decorative = onClick === void 0 && !MEANINGFUL_PROPS.some((key) => passed[key] !== void 0);
24
+ const ariaHidden = passed["aria-hidden"] ?? (decorative ? true : void 0);
21
25
  if (fill) style.fontVariationSettings = [style.fontVariationSettings, '"FILL" 1'].filter(Boolean).join(", ");
22
26
  if (weight)
23
27
  style.fontVariationSettings = [style.fontVariationSettings, `"wght" ${weight}`].filter(Boolean).join(", ");
@@ -30,6 +34,7 @@ const MaterialSymbolBase = ({
30
34
  Component,
31
35
  {
32
36
  ...props,
37
+ "aria-hidden": ariaHidden,
33
38
  ref,
34
39
  style,
35
40
  onClick,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/components/UI/MaterialSymbol/index.tsx"],"sourcesContent":["import { forwardRef } from 'react';\nimport type { CSSProperties, ElementType, ReactElement, Ref } from 'react';\n\nimport type { Classes, MaterialSymbolWeight, PolymorphicComponentProps, SymbolCodepoints } from './types';\nexport type { MaterialSymbolWeight, SymbolCodepoints } from './types';\n\nexport type MaterialSymbolProps = {\n icon: SymbolCodepoints;\n fill?: boolean;\n weight?: MaterialSymbolWeight;\n grade?: number;\n size?: number;\n color?: CSSProperties['color'] | string;\n className?: string;\n style?: CSSProperties;\n};\n\nexport type PolymorphicMaterialSymbolProps<C extends ElementType> = PolymorphicComponentProps<C, MaterialSymbolProps>;\n\nexport function combineClasses(...classes: Classes): string {\n return (classes.filter(Boolean) as Array<string>).map((c) => c.trim()).join(' ');\n}\n\nconst MaterialSymbolBase = <C extends ElementType>(\n {\n icon,\n onClick,\n as,\n weight,\n fill = false,\n grade,\n size,\n style: propStyle,\n color,\n className,\n ...props\n }: PolymorphicMaterialSymbolProps<C>,\n ref: Ref<Element>,\n): ReactElement => {\n const Component = onClick !== undefined ? 'button' : ((as || 'span') as ElementType);\n const style = { color, ...propStyle };\n\n if (fill) style.fontVariationSettings = [style.fontVariationSettings, '\"FILL\" 1'].filter(Boolean).join(', ');\n if (weight)\n style.fontVariationSettings = [style.fontVariationSettings, `\"wght\" ${weight}`].filter(Boolean).join(', ');\n if (grade) style.fontVariationSettings = [style.fontVariationSettings, `\"GRAD\" ${grade}`].filter(Boolean).join(', ');\n if (size) {\n style.fontVariationSettings = [style.fontVariationSettings, `\"opsz\" ${size}`].filter(Boolean).join(', ');\n // rem (not px) so icons follow the fluid root density scale (assets/styles/index.css).\n style.fontSize = `${size / 16}rem`;\n }\n\n return (\n <Component\n {...props}\n ref={ref}\n style={style}\n onClick={onClick}\n className={combineClasses('material-symbols', className)}\n >\n {icon}\n </Component>\n );\n};\n\n// @ts-ignore\nexport const MaterialSymbol = forwardRef(MaterialSymbolBase) as <C extends ElementType>(\n props: PolymorphicMaterialSymbolProps<C> & { ref?: Ref<Element> },\n) => ReactElement;\n"],"names":[],"mappings":";;AAmBO,SAAS,kBAAkB,SAA0B;AAC1D,SAAQ,QAAQ,OAAO,OAAO,EAAoB,IAAI,CAAC,MAAM,EAAE,KAAA,CAAM,EAAE,KAAK,GAAG;AACjF;AAEA,MAAM,qBAAqB,CACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,QACiB;AACjB,QAAM,YAAY,YAAY,SAAY,WAAa,MAAM;AAC7D,QAAM,QAAQ,EAAE,OAAO,GAAG,UAAA;AAE1B,MAAI,KAAM,OAAM,wBAAwB,CAAC,MAAM,uBAAuB,UAAU,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAC3G,MAAI;AACF,UAAM,wBAAwB,CAAC,MAAM,uBAAuB,UAAU,MAAM,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAC3G,MAAI,MAAO,OAAM,wBAAwB,CAAC,MAAM,uBAAuB,UAAU,KAAK,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AACnH,MAAI,MAAM;AACR,UAAM,wBAAwB,CAAC,MAAM,uBAAuB,UAAU,IAAI,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAEvG,UAAM,WAAW,GAAG,OAAO,EAAE;AAAA,EAC/B;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,eAAe,oBAAoB,SAAS;AAAA,MAEtD,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;AAGO,MAAM,iBAAiB,WAAW,kBAAkB;"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/UI/MaterialSymbol/index.tsx"],"sourcesContent":["import { forwardRef } from 'react';\nimport type { CSSProperties, ElementType, ReactElement, Ref } from 'react';\n\nimport type { Classes, MaterialSymbolWeight, PolymorphicComponentProps, SymbolCodepoints } from './types';\nexport type { MaterialSymbolWeight, SymbolCodepoints } from './types';\n\nexport type MaterialSymbolProps = {\n icon: SymbolCodepoints;\n fill?: boolean;\n weight?: MaterialSymbolWeight;\n grade?: number;\n size?: number;\n color?: CSSProperties['color'] | string;\n className?: string;\n style?: CSSProperties;\n};\n\nexport type PolymorphicMaterialSymbolProps<C extends ElementType> = PolymorphicComponentProps<C, MaterialSymbolProps>;\n\n/** Props that give a glyph a meaning of its own, so it stays in the accessibility tree. */\nconst MEANINGFUL_PROPS: ReadonlyArray<string> = ['aria-label', 'aria-labelledby', 'role', 'href', 'tabIndex'];\n\nexport function combineClasses(...classes: Classes): string {\n return (classes.filter(Boolean) as Array<string>).map((c) => c.trim()).join(' ');\n}\n\nconst MaterialSymbolBase = <C extends ElementType>(\n {\n icon,\n onClick,\n as,\n weight,\n fill = false,\n grade,\n size,\n style: propStyle,\n color,\n className,\n ...props\n }: PolymorphicMaterialSymbolProps<C>,\n ref: Ref<Element>,\n): ReactElement => {\n const Component = onClick !== undefined ? 'button' : ((as || 'span') as ElementType);\n const style = { color, ...propStyle };\n // The ligature name (\"close\", \"arrow_back\") is not a label: read aloud, it lands in the name of\n // the control or link around the glyph. Decorative unless it is the control itself or the host\n // gave it a meaning; an explicit `aria-hidden` always wins.\n const passed = props as Record<string, unknown>;\n const decorative = onClick === undefined && !MEANINGFUL_PROPS.some((key) => passed[key] !== undefined);\n const ariaHidden = passed['aria-hidden'] ?? (decorative ? true : undefined);\n\n if (fill) style.fontVariationSettings = [style.fontVariationSettings, '\"FILL\" 1'].filter(Boolean).join(', ');\n if (weight)\n style.fontVariationSettings = [style.fontVariationSettings, `\"wght\" ${weight}`].filter(Boolean).join(', ');\n if (grade) style.fontVariationSettings = [style.fontVariationSettings, `\"GRAD\" ${grade}`].filter(Boolean).join(', ');\n if (size) {\n style.fontVariationSettings = [style.fontVariationSettings, `\"opsz\" ${size}`].filter(Boolean).join(', ');\n // rem (not px) so icons follow the fluid root density scale (assets/styles/index.css).\n style.fontSize = `${size / 16}rem`;\n }\n\n return (\n <Component\n {...props}\n aria-hidden={ariaHidden}\n ref={ref}\n style={style}\n onClick={onClick}\n className={combineClasses('material-symbols', className)}\n >\n {icon}\n </Component>\n );\n};\n\n// @ts-ignore\nexport const MaterialSymbol = forwardRef(MaterialSymbolBase) as <C extends ElementType>(\n props: PolymorphicMaterialSymbolProps<C> & { ref?: Ref<Element> },\n) => ReactElement;\n"],"names":[],"mappings":";;AAoBA,MAAM,mBAA0C,CAAC,cAAc,mBAAmB,QAAQ,QAAQ,UAAU;AAErG,SAAS,kBAAkB,SAA0B;AAC1D,SAAQ,QAAQ,OAAO,OAAO,EAAoB,IAAI,CAAC,MAAM,EAAE,KAAA,CAAM,EAAE,KAAK,GAAG;AACjF;AAEA,MAAM,qBAAqB,CACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,QACiB;AACjB,QAAM,YAAY,YAAY,SAAY,WAAa,MAAM;AAC7D,QAAM,QAAQ,EAAE,OAAO,GAAG,UAAA;AAI1B,QAAM,SAAS;AACf,QAAM,aAAa,YAAY,UAAa,CAAC,iBAAiB,KAAK,CAAC,QAAQ,OAAO,GAAG,MAAM,MAAS;AACrG,QAAM,aAAa,OAAO,aAAa,MAAM,aAAa,OAAO;AAEjE,MAAI,KAAM,OAAM,wBAAwB,CAAC,MAAM,uBAAuB,UAAU,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAC3G,MAAI;AACF,UAAM,wBAAwB,CAAC,MAAM,uBAAuB,UAAU,MAAM,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAC3G,MAAI,MAAO,OAAM,wBAAwB,CAAC,MAAM,uBAAuB,UAAU,KAAK,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AACnH,MAAI,MAAM;AACR,UAAM,wBAAwB,CAAC,MAAM,uBAAuB,UAAU,IAAI,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAEvG,UAAM,WAAW,GAAG,OAAO,EAAE;AAAA,EAC/B;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ,eAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,eAAe,oBAAoB,SAAS;AAAA,MAEtD,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;AAGO,MAAM,iBAAiB,WAAW,kBAAkB;"}
@@ -2,7 +2,7 @@ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { memo, isValidElement } from "react";
3
3
  import classNames from "classnames";
4
4
  import { useTranslation } from "react-i18next";
5
- import { twMerge } from "tailwind-merge";
5
+ import { mergeClasses } from "../../../helpers/mergeClasses.js";
6
6
  import ControlledButton from "../../Form/ControlledButton.js";
7
7
  import { MaterialSymbol } from "../MaterialSymbol/index.js";
8
8
  import { NO_DATA_FOUND_TONES, NO_DATA_FOUND_VARIANTS, NO_DATA_FOUND_SIZES } from "./noDataFoundPresets.js";
@@ -138,7 +138,7 @@ const NoDataFound = ({
138
138
  const primaryAction = renderAction(action, true);
139
139
  const supportAction = renderAction(secondaryAction, false);
140
140
  if (compact || size === "inline") {
141
- return /* @__PURE__ */ jsxs("div", { ...liveRegionProps, className: twMerge("flex w-full items-center gap-x-1.5 text-left", className), children: [
141
+ return /* @__PURE__ */ jsxs("div", { ...liveRegionProps, className: mergeClasses("flex w-full items-center gap-x-1.5 text-left", className), children: [
142
142
  /* @__PURE__ */ jsx(MaterialSymbol, { icon: resolvedIcon, size: 16, className: classNames("shrink-0", toneClasses.icon) }),
143
143
  /* @__PURE__ */ jsx("span", { className: "min-w-0 text-xs text-foreground-subtle", children: resolvedTitle }),
144
144
  description ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -153,7 +153,7 @@ const NoDataFound = ({
153
153
  "div",
154
154
  {
155
155
  ...liveRegionProps,
156
- className: twMerge(
156
+ className: mergeClasses(
157
157
  classNames(
158
158
  "relative flex w-full flex-col items-center justify-center overflow-hidden text-center @container",
159
159
  sizeClasses.root
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/components/UI/NoDataFound/index.tsx"],"sourcesContent":["import { isValidElement, memo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '../../Form/ControlledButton';\nimport { MaterialSymbol, SymbolCodepoints } from '../MaterialSymbol';\n\nimport {\n NO_DATA_FOUND_SIZES,\n NO_DATA_FOUND_TONES,\n NO_DATA_FOUND_VARIANTS,\n TNoDataFoundSize,\n TNoDataFoundTone,\n TNoDataFoundVariant,\n} from './noDataFoundPresets';\n\nexport type { TNoDataFoundSize, TNoDataFoundTone, TNoDataFoundVariant } from './noDataFoundPresets';\n\nexport interface INoDataFoundAction {\n label: string;\n onClick: () => void;\n icon?: SymbolCodepoints;\n disabled?: boolean;\n}\n\ntype TNoDataFoundActionProp = INoDataFoundAction | React.ReactNode;\n\nexport interface INoDataFoundProps {\n variant?: TNoDataFoundVariant;\n size?: TNoDataFoundSize;\n tone?: TNoDataFoundTone;\n title?: string;\n /** Alias of `title`, kept because most call sites already speak this name. */\n message?: string;\n description?: string;\n icon?: SymbolCodepoints;\n illustration?: React.ReactNode;\n action?: TNoDataFoundActionProp;\n secondaryAction?: TNoDataFoundActionProp;\n /** Sugar for `size=\"inline\"`: a single row for dropdowns, list slots and dense panels. */\n compact?: boolean;\n children?: React.ReactNode;\n className?: string;\n /** Turn off the live region when the host already announces the state itself. */\n announce?: boolean;\n}\n\ntype TToneClasses = (typeof NO_DATA_FOUND_TONES)[TNoDataFoundTone];\ntype TSizeClasses = (typeof NO_DATA_FOUND_SIZES)[Exclude<TNoDataFoundSize, 'inline'>];\n\nconst isActionConfig = (action: TNoDataFoundActionProp): action is INoDataFoundAction =>\n typeof action === 'object' && action !== null && !isValidElement(action) && 'label' in action;\n\ninterface INoDataFoundVisualProps {\n icon: SymbolCodepoints;\n toneClasses: TToneClasses;\n sizeClasses: TSizeClasses;\n}\n\nconst NoDataFoundVisual: React.FC<INoDataFoundVisualProps> = ({ icon, toneClasses, sizeClasses }) => (\n <span\n aria-hidden\n className={classNames(\n 'relative flex shrink-0 animate-state-reveal items-center justify-center motion-reduce:animate-state-fade',\n sizeClasses.visual,\n )}\n >\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-breathe rounded-full blur-2xl motion-reduce:animate-none motion-reduce:opacity-60',\n toneClasses.aura,\n sizeClasses.aura,\n )}\n />\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-halo rounded-full border motion-reduce:hidden',\n toneClasses.halo,\n sizeClasses.halo,\n )}\n />\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-halo rounded-full border [animation-delay:1.5s] motion-reduce:hidden',\n toneClasses.halo,\n sizeClasses.halo,\n )}\n />\n\n <span\n className={classNames(\n 'relative flex animate-state-float items-center justify-center border bg-gradient-to-br shadow-elevation3 glass-tint motion-reduce:animate-none',\n toneClasses.tile,\n sizeClasses.tile,\n )}\n >\n <MaterialSymbol icon={icon} weight={300} className={toneClasses.icon} />\n </span>\n\n <span\n className={classNames(\n 'absolute right-[6%] top-[18%] size-1.5 animate-state-drift rounded-full motion-reduce:animate-none',\n toneClasses.accent,\n )}\n />\n <span\n className={classNames(\n 'absolute bottom-[16%] left-[9%] size-1 animate-state-drift rounded-full [animation-delay:2.5s] motion-reduce:animate-none',\n toneClasses.accent,\n )}\n />\n <span\n className={classNames(\n 'absolute left-[24%] top-[4%] size-1 animate-state-drift rounded-full [animation-delay:5s] motion-reduce:animate-none',\n toneClasses.accent,\n )}\n />\n </span>\n);\n\nconst NoDataFoundActionButton: React.FC<{ action: INoDataFoundAction; isPrimary: boolean }> = ({\n action,\n isPrimary,\n}) => (\n <ControlledButton\n size=\"sm\"\n variant={isPrimary ? 'filled' : 'text'}\n tone={isPrimary ? 'brand' : 'neutral'}\n disabled={action.disabled}\n onClick={action.onClick}\n className={classNames(\n 'flex items-center gap-x-1.5 px-3.5 text-xs transition-interactive duration-base',\n isPrimary\n ? 'shadow-md shadow-brand/20 hover:-translate-y-px hover:shadow-lg hover:shadow-brand/25'\n : 'text-foreground-muted hover:bg-muted-hover',\n )}\n >\n {action.icon ? <MaterialSymbol icon={action.icon} size={16} aria-hidden /> : null}\n <span>{action.label}</span>\n </ControlledButton>\n);\n\nconst renderAction = (action: TNoDataFoundActionProp, isPrimary: boolean): React.ReactNode => {\n if (!action) return null;\n if (isActionConfig(action)) return <NoDataFoundActionButton action={action} isPrimary={isPrimary} />;\n\n return action;\n};\n\nconst NoDataFound: React.FC<INoDataFoundProps> = ({\n variant = 'empty',\n size = 'md',\n tone,\n title,\n message,\n description,\n icon,\n illustration,\n action,\n secondaryAction,\n compact = false,\n children,\n className,\n announce = true,\n}) => {\n const { t } = useTranslation('ui');\n\n const preset = NO_DATA_FOUND_VARIANTS[variant] ?? NO_DATA_FOUND_VARIANTS.empty;\n const toneClasses = NO_DATA_FOUND_TONES[tone ?? preset.tone] ?? NO_DATA_FOUND_TONES.neutral;\n const resolvedIcon = icon ?? preset.icon;\n const resolvedTitle = title ?? message ?? t(preset.titleKey);\n const liveRegionProps = announce ? { role: 'status', 'aria-live': 'polite' as const } : {};\n\n const primaryAction = renderAction(action, true);\n const supportAction = renderAction(secondaryAction, false);\n\n if (compact || size === 'inline') {\n return (\n <div {...liveRegionProps} className={twMerge('flex w-full items-center gap-x-1.5 text-left', className)}>\n <MaterialSymbol icon={resolvedIcon} size={16} className={classNames('shrink-0', toneClasses.icon)} />\n <span className=\"min-w-0 text-xs text-foreground-subtle\">{resolvedTitle}</span>\n\n {description ? (\n <>\n <span aria-hidden className=\"size-1 shrink-0 rounded-full bg-border-emphasis\" />\n <span className=\"min-w-0 truncate text-xs text-foreground-faint\">{description}</span>\n </>\n ) : null}\n\n {primaryAction ? <span className=\"ml-auto shrink-0\">{primaryAction}</span> : null}\n </div>\n );\n }\n\n const sizeClasses = NO_DATA_FOUND_SIZES[size] ?? NO_DATA_FOUND_SIZES.md;\n\n return (\n <div\n {...liveRegionProps}\n className={twMerge(\n classNames(\n 'relative flex w-full flex-col items-center justify-center overflow-hidden text-center @container',\n sizeClasses.root,\n ),\n className,\n )}\n >\n {sizeClasses.hasTexture ? (\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-0 [mask-image:radial-gradient(60%_55%_at_50%_35%,black,transparent)]\"\n >\n <span className=\"absolute inset-0 bg-[radial-gradient(circle,rgba(148,163,184,0.3)_1px,transparent_1px)] [background-size:1.25rem_1.25rem]\" />\n </span>\n ) : null}\n\n {illustration ?? <NoDataFoundVisual icon={resolvedIcon} toneClasses={toneClasses} sizeClasses={sizeClasses} />}\n\n <p\n className={classNames(\n 'relative animate-state-rise font-semibold tracking-[-0.01em] text-foreground [animation-delay:80ms] motion-reduce:animate-state-fade',\n sizeClasses.titleClass,\n )}\n >\n {resolvedTitle}\n </p>\n\n {description ? (\n <p\n className={classNames(\n 'relative animate-state-rise text-balance leading-relaxed text-foreground-subtle [animation-delay:140ms] motion-reduce:animate-state-fade',\n sizeClasses.descriptionClass,\n )}\n >\n {description}\n </p>\n ) : null}\n\n {children ? (\n <div className=\"relative w-full animate-state-rise [animation-delay:180ms] motion-reduce:animate-state-fade\">\n {children}\n </div>\n ) : null}\n\n {primaryAction || supportAction ? (\n <div\n className={classNames(\n 'relative flex animate-state-rise flex-wrap items-center justify-center gap-2 [animation-delay:220ms] motion-reduce:animate-state-fade',\n sizeClasses.actions,\n )}\n >\n {primaryAction}\n {supportAction}\n </div>\n ) : null}\n </div>\n );\n};\n\nexport default memo(NoDataFound);\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/UI/NoDataFound/index.tsx"],"sourcesContent":["import { isValidElement, memo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\nimport ControlledButton from '../../Form/ControlledButton';\nimport { MaterialSymbol, SymbolCodepoints } from '../MaterialSymbol';\n\nimport {\n NO_DATA_FOUND_SIZES,\n NO_DATA_FOUND_TONES,\n NO_DATA_FOUND_VARIANTS,\n TNoDataFoundSize,\n TNoDataFoundTone,\n TNoDataFoundVariant,\n} from './noDataFoundPresets';\n\nexport type { TNoDataFoundSize, TNoDataFoundTone, TNoDataFoundVariant } from './noDataFoundPresets';\n\nexport interface INoDataFoundAction {\n label: string;\n onClick: () => void;\n icon?: SymbolCodepoints;\n disabled?: boolean;\n}\n\ntype TNoDataFoundActionProp = INoDataFoundAction | React.ReactNode;\n\nexport interface INoDataFoundProps {\n variant?: TNoDataFoundVariant;\n size?: TNoDataFoundSize;\n tone?: TNoDataFoundTone;\n title?: string;\n /** Alias of `title`, kept because most call sites already speak this name. */\n message?: string;\n description?: string;\n icon?: SymbolCodepoints;\n illustration?: React.ReactNode;\n action?: TNoDataFoundActionProp;\n secondaryAction?: TNoDataFoundActionProp;\n /** Sugar for `size=\"inline\"`: a single row for dropdowns, list slots and dense panels. */\n compact?: boolean;\n children?: React.ReactNode;\n className?: string;\n /** Turn off the live region when the host already announces the state itself. */\n announce?: boolean;\n}\n\ntype TToneClasses = (typeof NO_DATA_FOUND_TONES)[TNoDataFoundTone];\ntype TSizeClasses = (typeof NO_DATA_FOUND_SIZES)[Exclude<TNoDataFoundSize, 'inline'>];\n\nconst isActionConfig = (action: TNoDataFoundActionProp): action is INoDataFoundAction =>\n typeof action === 'object' && action !== null && !isValidElement(action) && 'label' in action;\n\ninterface INoDataFoundVisualProps {\n icon: SymbolCodepoints;\n toneClasses: TToneClasses;\n sizeClasses: TSizeClasses;\n}\n\nconst NoDataFoundVisual: React.FC<INoDataFoundVisualProps> = ({ icon, toneClasses, sizeClasses }) => (\n <span\n aria-hidden\n className={classNames(\n 'relative flex shrink-0 animate-state-reveal items-center justify-center motion-reduce:animate-state-fade',\n sizeClasses.visual,\n )}\n >\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-breathe rounded-full blur-2xl motion-reduce:animate-none motion-reduce:opacity-60',\n toneClasses.aura,\n sizeClasses.aura,\n )}\n />\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-halo rounded-full border motion-reduce:hidden',\n toneClasses.halo,\n sizeClasses.halo,\n )}\n />\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-halo rounded-full border [animation-delay:1.5s] motion-reduce:hidden',\n toneClasses.halo,\n sizeClasses.halo,\n )}\n />\n\n <span\n className={classNames(\n 'relative flex animate-state-float items-center justify-center border bg-gradient-to-br shadow-elevation3 glass-tint motion-reduce:animate-none',\n toneClasses.tile,\n sizeClasses.tile,\n )}\n >\n <MaterialSymbol icon={icon} weight={300} className={toneClasses.icon} />\n </span>\n\n <span\n className={classNames(\n 'absolute right-[6%] top-[18%] size-1.5 animate-state-drift rounded-full motion-reduce:animate-none',\n toneClasses.accent,\n )}\n />\n <span\n className={classNames(\n 'absolute bottom-[16%] left-[9%] size-1 animate-state-drift rounded-full [animation-delay:2.5s] motion-reduce:animate-none',\n toneClasses.accent,\n )}\n />\n <span\n className={classNames(\n 'absolute left-[24%] top-[4%] size-1 animate-state-drift rounded-full [animation-delay:5s] motion-reduce:animate-none',\n toneClasses.accent,\n )}\n />\n </span>\n);\n\nconst NoDataFoundActionButton: React.FC<{ action: INoDataFoundAction; isPrimary: boolean }> = ({\n action,\n isPrimary,\n}) => (\n <ControlledButton\n size=\"sm\"\n variant={isPrimary ? 'filled' : 'text'}\n tone={isPrimary ? 'brand' : 'neutral'}\n disabled={action.disabled}\n onClick={action.onClick}\n className={classNames(\n 'flex items-center gap-x-1.5 px-3.5 text-xs transition-interactive duration-base',\n isPrimary\n ? 'shadow-md shadow-brand/20 hover:-translate-y-px hover:shadow-lg hover:shadow-brand/25'\n : 'text-foreground-muted hover:bg-muted-hover',\n )}\n >\n {action.icon ? <MaterialSymbol icon={action.icon} size={16} aria-hidden /> : null}\n <span>{action.label}</span>\n </ControlledButton>\n);\n\nconst renderAction = (action: TNoDataFoundActionProp, isPrimary: boolean): React.ReactNode => {\n if (!action) return null;\n if (isActionConfig(action)) return <NoDataFoundActionButton action={action} isPrimary={isPrimary} />;\n\n return action;\n};\n\nconst NoDataFound: React.FC<INoDataFoundProps> = ({\n variant = 'empty',\n size = 'md',\n tone,\n title,\n message,\n description,\n icon,\n illustration,\n action,\n secondaryAction,\n compact = false,\n children,\n className,\n announce = true,\n}) => {\n const { t } = useTranslation('ui');\n\n const preset = NO_DATA_FOUND_VARIANTS[variant] ?? NO_DATA_FOUND_VARIANTS.empty;\n const toneClasses = NO_DATA_FOUND_TONES[tone ?? preset.tone] ?? NO_DATA_FOUND_TONES.neutral;\n const resolvedIcon = icon ?? preset.icon;\n const resolvedTitle = title ?? message ?? t(preset.titleKey);\n const liveRegionProps = announce ? { role: 'status', 'aria-live': 'polite' as const } : {};\n\n const primaryAction = renderAction(action, true);\n const supportAction = renderAction(secondaryAction, false);\n\n if (compact || size === 'inline') {\n return (\n <div {...liveRegionProps} className={mergeClasses('flex w-full items-center gap-x-1.5 text-left', className)}>\n <MaterialSymbol icon={resolvedIcon} size={16} className={classNames('shrink-0', toneClasses.icon)} />\n <span className=\"min-w-0 text-xs text-foreground-subtle\">{resolvedTitle}</span>\n\n {description ? (\n <>\n <span aria-hidden className=\"size-1 shrink-0 rounded-full bg-border-emphasis\" />\n <span className=\"min-w-0 truncate text-xs text-foreground-faint\">{description}</span>\n </>\n ) : null}\n\n {primaryAction ? <span className=\"ml-auto shrink-0\">{primaryAction}</span> : null}\n </div>\n );\n }\n\n const sizeClasses = NO_DATA_FOUND_SIZES[size] ?? NO_DATA_FOUND_SIZES.md;\n\n return (\n <div\n {...liveRegionProps}\n className={mergeClasses(\n classNames(\n 'relative flex w-full flex-col items-center justify-center overflow-hidden text-center @container',\n sizeClasses.root,\n ),\n className,\n )}\n >\n {sizeClasses.hasTexture ? (\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-0 [mask-image:radial-gradient(60%_55%_at_50%_35%,black,transparent)]\"\n >\n <span className=\"absolute inset-0 bg-[radial-gradient(circle,rgba(148,163,184,0.3)_1px,transparent_1px)] [background-size:1.25rem_1.25rem]\" />\n </span>\n ) : null}\n\n {illustration ?? <NoDataFoundVisual icon={resolvedIcon} toneClasses={toneClasses} sizeClasses={sizeClasses} />}\n\n <p\n className={classNames(\n 'relative animate-state-rise font-semibold tracking-[-0.01em] text-foreground [animation-delay:80ms] motion-reduce:animate-state-fade',\n sizeClasses.titleClass,\n )}\n >\n {resolvedTitle}\n </p>\n\n {description ? (\n <p\n className={classNames(\n 'relative animate-state-rise text-balance leading-relaxed text-foreground-subtle [animation-delay:140ms] motion-reduce:animate-state-fade',\n sizeClasses.descriptionClass,\n )}\n >\n {description}\n </p>\n ) : null}\n\n {children ? (\n <div className=\"relative w-full animate-state-rise [animation-delay:180ms] motion-reduce:animate-state-fade\">\n {children}\n </div>\n ) : null}\n\n {primaryAction || supportAction ? (\n <div\n className={classNames(\n 'relative flex animate-state-rise flex-wrap items-center justify-center gap-2 [animation-delay:220ms] motion-reduce:animate-state-fade',\n sizeClasses.actions,\n )}\n >\n {primaryAction}\n {supportAction}\n </div>\n ) : null}\n </div>\n );\n};\n\nexport default memo(NoDataFound);\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import classNames from "classnames";
3
- import { twMerge } from "tailwind-merge";
3
+ import { mergeClasses } from "../../helpers/mergeClasses.js";
4
4
  import { sanitizeRichText } from "../../helpers/richText.js";
5
5
  const RICH_TEXT_CONTENT_CLASS = classNames(
6
6
  "break-words text-sm leading-relaxed",
@@ -13,12 +13,12 @@ const RICH_TEXT_CONTENT_CLASS = classNames(
13
13
  );
14
14
  const RichTextContent = ({ html, className, plainText = false }) => {
15
15
  if (plainText) {
16
- return /* @__PURE__ */ jsx("div", { className: twMerge(RICH_TEXT_CONTENT_CLASS, "whitespace-pre-line", className), children: html });
16
+ return /* @__PURE__ */ jsx("div", { className: mergeClasses(RICH_TEXT_CONTENT_CLASS, "whitespace-pre-line", className), children: html });
17
17
  }
18
18
  return /* @__PURE__ */ jsx(
19
19
  "div",
20
20
  {
21
- className: twMerge(RICH_TEXT_CONTENT_CLASS, className),
21
+ className: mergeClasses(RICH_TEXT_CONTENT_CLASS, className),
22
22
  dangerouslySetInnerHTML: { __html: sanitizeRichText(html) }
23
23
  }
24
24
  );
@@ -1 +1 @@
1
- {"version":3,"file":"RichTextContent.js","sources":["../../../src/components/UI/RichTextContent.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { twMerge } from 'tailwind-merge';\n\nimport { sanitizeRichText } from '@/helpers/richText';\n\ninterface IRichTextContentProps {\n html?: string;\n className?: string;\n /**\n * Reads the value as legacy plain text: real line breaks, no markup. Opt in per surface with\n * `isPlainTextBody`, never by default: a body whose only markup is a `<user>` mention would\n * otherwise render its tags as visible text.\n */\n plainText?: boolean;\n}\n\n/** The typography of a rich text body, shared by every surface that renders one. */\nexport const RICH_TEXT_CONTENT_CLASS = classNames(\n 'break-words text-sm leading-relaxed',\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 '[&_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 '[&_user]:whitespace-nowrap [&_user]:rounded [&_user]:bg-brand-soft [&_user]:px-1 [&_user]:font-medium [&_user]:text-brand-emphasis',\n);\n\nconst RichTextContent: React.FC<IRichTextContentProps> = ({ html, className, plainText = false }) => {\n if (plainText) {\n return <div className={twMerge(RICH_TEXT_CONTENT_CLASS, 'whitespace-pre-line', className)}>{html}</div>;\n }\n\n return (\n <div\n className={twMerge(RICH_TEXT_CONTENT_CLASS, className)}\n dangerouslySetInnerHTML={{ __html: sanitizeRichText(html) }}\n />\n );\n};\n\nexport default RichTextContent;\n"],"names":[],"mappings":";;;;AAiBO,MAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,kBAAmD,CAAC,EAAE,MAAM,WAAW,YAAY,YAAY;AACnG,MAAI,WAAW;AACb,WAAO,oBAAC,SAAI,WAAW,QAAQ,yBAAyB,uBAAuB,SAAS,GAAI,UAAA,KAAA,CAAK;AAAA,EACnG;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,QAAQ,yBAAyB,SAAS;AAAA,MACrD,yBAAyB,EAAE,QAAQ,iBAAiB,IAAI,EAAA;AAAA,IAAE;AAAA,EAAA;AAGhE;"}
1
+ {"version":3,"file":"RichTextContent.js","sources":["../../../src/components/UI/RichTextContent.tsx"],"sourcesContent":["import classNames from 'classnames';\n\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport { sanitizeRichText } from '@/helpers/richText';\n\ninterface IRichTextContentProps {\n html?: string;\n className?: string;\n /**\n * Reads the value as legacy plain text: real line breaks, no markup. Opt in per surface with\n * `isPlainTextBody`, never by default: a body whose only markup is a `<user>` mention would\n * otherwise render its tags as visible text.\n */\n plainText?: boolean;\n}\n\n/** The typography of a rich text body, shared by every surface that renders one. */\nexport const RICH_TEXT_CONTENT_CLASS = classNames(\n 'break-words text-sm leading-relaxed',\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 '[&_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 '[&_user]:whitespace-nowrap [&_user]:rounded [&_user]:bg-brand-soft [&_user]:px-1 [&_user]:font-medium [&_user]:text-brand-emphasis',\n);\n\nconst RichTextContent: React.FC<IRichTextContentProps> = ({ html, className, plainText = false }) => {\n if (plainText) {\n return <div className={mergeClasses(RICH_TEXT_CONTENT_CLASS, 'whitespace-pre-line', className)}>{html}</div>;\n }\n\n return (\n <div\n className={mergeClasses(RICH_TEXT_CONTENT_CLASS, className)}\n dangerouslySetInnerHTML={{ __html: sanitizeRichText(html) }}\n />\n );\n};\n\nexport default RichTextContent;\n"],"names":[],"mappings":";;;;AAiBO,MAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,kBAAmD,CAAC,EAAE,MAAM,WAAW,YAAY,YAAY;AACnG,MAAI,WAAW;AACb,WAAO,oBAAC,SAAI,WAAW,aAAa,yBAAyB,uBAAuB,SAAS,GAAI,UAAA,KAAA,CAAK;AAAA,EACxG;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,aAAa,yBAAyB,SAAS;AAAA,MAC1D,yBAAyB,EAAE,QAAQ,iBAAiB,IAAI,EAAA;AAAA,IAAE;AAAA,EAAA;AAGhE;"}
@@ -1,17 +1,21 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
+ import { useTranslation } from "react-i18next";
2
3
  import ControlledMenu from "./ControlledMenu.js";
3
4
  import { MaterialSymbol } from "./MaterialSymbol/index.js";
4
- const ThreeDots = ({ children, items, tooltip, placement, className }) => /* @__PURE__ */ jsx(
5
- ControlledMenu,
6
- {
7
- trigger: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "more_vert", size: 16 }),
8
- items,
9
- triggerTooltip: tooltip,
10
- placement,
11
- listClassName: className,
12
- children
13
- }
14
- );
5
+ const ThreeDots = ({ children, items, tooltip, placement, className }) => {
6
+ const { t } = useTranslation("ui");
7
+ return /* @__PURE__ */ jsx(
8
+ ControlledMenu,
9
+ {
10
+ trigger: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "more_vert", size: 16, "aria-label": tooltip ?? t("actions.moreOptions") }),
11
+ items,
12
+ triggerTooltip: tooltip,
13
+ placement,
14
+ listClassName: className,
15
+ children
16
+ }
17
+ );
18
+ };
15
19
  export {
16
20
  ThreeDots as default
17
21
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ThreeDots.js","sources":["../../../src/components/UI/ThreeDots.tsx"],"sourcesContent":["import React from 'react';\n\nimport ControlledMenu, { IControlledMenuItem, TMenuPlacement } from '@/components/UI/ControlledMenu';\n\nimport { MaterialSymbol } from './MaterialSymbol';\n\ninterface IThreeDotsProps {\n children?: React.ReactNode;\n items?: Array<IControlledMenuItem>;\n tooltip?: string;\n placement?: TMenuPlacement;\n className?: string;\n}\n\nconst ThreeDots: React.FC<IThreeDotsProps> = ({ children, items, tooltip, placement, className }) => (\n <ControlledMenu\n trigger={<MaterialSymbol icon=\"more_vert\" size={16} />}\n items={items}\n triggerTooltip={tooltip}\n placement={placement}\n listClassName={className}\n >\n {children}\n </ControlledMenu>\n);\n\nexport default ThreeDots;\n"],"names":[],"mappings":";;;AAcA,MAAM,YAAuC,CAAC,EAAE,UAAU,OAAO,SAAS,WAAW,gBACnF;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,SAAS,oBAAC,gBAAA,EAAe,MAAK,aAAY,MAAM,IAAI;AAAA,IACpD;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA,eAAe;AAAA,IAEd;AAAA,EAAA;AACH;"}
1
+ {"version":3,"file":"ThreeDots.js","sources":["../../../src/components/UI/ThreeDots.tsx"],"sourcesContent":["import React from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledMenu, { IControlledMenuItem, TMenuPlacement } from '@/components/UI/ControlledMenu';\n\nimport { MaterialSymbol } from './MaterialSymbol';\n\ninterface IThreeDotsProps {\n children?: React.ReactNode;\n items?: Array<IControlledMenuItem>;\n tooltip?: string;\n placement?: TMenuPlacement;\n className?: string;\n}\n\nconst ThreeDots: React.FC<IThreeDotsProps> = ({ children, items, tooltip, placement, className }) => {\n const { t } = useTranslation('ui');\n\n return (\n <ControlledMenu\n trigger={<MaterialSymbol icon=\"more_vert\" size={16} aria-label={tooltip ?? t('actions.moreOptions')} />}\n items={items}\n triggerTooltip={tooltip}\n placement={placement}\n listClassName={className}\n >\n {children}\n </ControlledMenu>\n );\n};\n\nexport default ThreeDots;\n"],"names":[],"mappings":";;;;AAeA,MAAM,YAAuC,CAAC,EAAE,UAAU,OAAO,SAAS,WAAW,gBAAgB;AACnG,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,SAAS,oBAAC,gBAAA,EAAe,MAAK,aAAY,MAAM,IAAI,cAAY,WAAW,EAAE,qBAAqB,EAAA,CAAG;AAAA,MACrG;AAAA,MACA,gBAAgB;AAAA,MAChB;AAAA,MACA,eAAe;AAAA,MAEd;AAAA,IAAA;AAAA,EAAA;AAGP;"}
@@ -1,6 +1,10 @@
1
1
  /**
2
- * tailwind-merge only knows the stock scale, so `text-xxs` (our extra step) is read as a text
3
- * COLOR and is silently dropped whenever a tone class follows it in the same merge. Registering
4
- * the step keeps size and colour in different groups.
2
+ * tailwind-merge only knows Tailwind's stock scales, and what it does not know it misfiles:
3
+ * `text-xxs` reads as a text COLOR and is dropped as soon as a tone follows it, `shadow-elevation1`
4
+ * reads as a shadow colour and survives the `shadow-none` meant to replace it, `duration-fast`
5
+ * and `animate-state-rise` read as foreign classes and never conflict with anything.
6
+ *
7
+ * Every class string in this library merges through here (ESLint enforces it), and the values
8
+ * below are the ones `tailwind.preset.js` adds; test/mergeClasses.test.ts fails when they drift.
5
9
  */
6
10
  export declare const mergeClasses: (...classLists: import('tailwind-merge').ClassNameValue[]) => string;
@@ -1,8 +1,30 @@
1
1
  import { extendTailwindMerge } from "tailwind-merge";
2
2
  const mergeClasses = extendTailwindMerge({
3
3
  extend: {
4
+ theme: {
5
+ text: ["xxs"],
6
+ shadow: ["elevation1", "elevation2", "elevation3", "elevation4", "focus"],
7
+ ease: ["enter", "exit", "standard"],
8
+ animate: [
9
+ "fade-in-up",
10
+ "fade-in",
11
+ "ai-orbit",
12
+ "ai-bob",
13
+ "ai-sweep",
14
+ "ai-step",
15
+ "pop-in",
16
+ "state-fade",
17
+ "state-rise",
18
+ "state-reveal",
19
+ "state-float",
20
+ "state-breathe",
21
+ "state-halo",
22
+ "state-drift"
23
+ ]
24
+ },
4
25
  classGroups: {
5
- "font-size": [{ text: ["xxs"] }]
26
+ duration: [{ duration: ["instant", "fast", "base", "slow", "highlight"] }],
27
+ transition: [{ transition: ["interactive"] }]
6
28
  }
7
29
  }
8
30
  });
@@ -1 +1 @@
1
- {"version":3,"file":"mergeClasses.js","sources":["../../src/helpers/mergeClasses.ts"],"sourcesContent":["import { extendTailwindMerge } from 'tailwind-merge';\n\n/**\n * tailwind-merge only knows the stock scale, so `text-xxs` (our extra step) is read as a text\n * COLOR and is silently dropped whenever a tone class follows it in the same merge. Registering\n * the step keeps size and colour in different groups.\n */\nexport const mergeClasses = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [{ text: ['xxs'] }],\n },\n },\n});\n"],"names":[],"mappings":";AAOO,MAAM,eAAe,oBAAoB;AAAA,EAC9C,QAAQ;AAAA,IACN,aAAa;AAAA,MACX,aAAa,CAAC,EAAE,MAAM,CAAC,KAAK,GAAG;AAAA,IAAA;AAAA,EACjC;AAEJ,CAAC;"}
1
+ {"version":3,"file":"mergeClasses.js","sources":["../../src/helpers/mergeClasses.ts"],"sourcesContent":["import { extendTailwindMerge } from 'tailwind-merge';\n\n/**\n * tailwind-merge only knows Tailwind's stock scales, and what it does not know it misfiles:\n * `text-xxs` reads as a text COLOR and is dropped as soon as a tone follows it, `shadow-elevation1`\n * reads as a shadow colour and survives the `shadow-none` meant to replace it, `duration-fast`\n * and `animate-state-rise` read as foreign classes and never conflict with anything.\n *\n * Every class string in this library merges through here (ESLint enforces it), and the values\n * below are the ones `tailwind.preset.js` adds; test/mergeClasses.test.ts fails when they drift.\n */\nexport const mergeClasses = extendTailwindMerge({\n extend: {\n theme: {\n text: ['xxs'],\n shadow: ['elevation1', 'elevation2', 'elevation3', 'elevation4', 'focus'],\n ease: ['enter', 'exit', 'standard'],\n animate: [\n 'fade-in-up',\n 'fade-in',\n 'ai-orbit',\n 'ai-bob',\n 'ai-sweep',\n 'ai-step',\n 'pop-in',\n 'state-fade',\n 'state-rise',\n 'state-reveal',\n 'state-float',\n 'state-breathe',\n 'state-halo',\n 'state-drift',\n ],\n },\n classGroups: {\n duration: [{ duration: ['instant', 'fast', 'base', 'slow', 'highlight'] }],\n transition: [{ transition: ['interactive'] }],\n },\n },\n});\n"],"names":[],"mappings":";AAWO,MAAM,eAAe,oBAAoB;AAAA,EAC9C,QAAQ;AAAA,IACN,OAAO;AAAA,MACL,MAAM,CAAC,KAAK;AAAA,MACZ,QAAQ,CAAC,cAAc,cAAc,cAAc,cAAc,OAAO;AAAA,MACxE,MAAM,CAAC,SAAS,QAAQ,UAAU;AAAA,MAClC,SAAS;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IACF;AAAA,IAEF,aAAa;AAAA,MACX,UAAU,CAAC,EAAE,UAAU,CAAC,WAAW,QAAQ,QAAQ,QAAQ,WAAW,GAAG;AAAA,MACzE,YAAY,CAAC,EAAE,YAAY,CAAC,aAAa,GAAG;AAAA,IAAA;AAAA,EAC9C;AAEJ,CAAC;"}
@@ -1,9 +1,12 @@
1
- import { useState, useRef, useCallback, useEffect } from "react";
1
+ import { useState, useRef, useSyncExternalStore, useCallback, useEffect } from "react";
2
2
  import { uiLogger } from "../helpers/uiLogger.js";
3
3
  const getRecognitionCtor = () => {
4
4
  const w = window;
5
5
  return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
6
6
  };
7
+ const subscribeToNothing = () => () => void 0;
8
+ const isRecognitionAvailable = () => getRecognitionCtor() !== null;
9
+ const isUnavailableOnServer = () => false;
7
10
  const VOICE_COMMANDS = [
8
11
  { key: "comma", phrases: ["vírgula", "virgula", "comma"], insert: ", " },
9
12
  { key: "period", phrases: ["ponto", "ponto final", "period"], insert: ". " },
@@ -86,7 +89,7 @@ const useVoiceTranscription = ({
86
89
  const onInterimRef = useRef(onInterim);
87
90
  onCommitRef.current = onCommit;
88
91
  onInterimRef.current = onInterim;
89
- const isSupported = getRecognitionCtor() !== null;
92
+ const isSupported = useSyncExternalStore(subscribeToNothing, isRecognitionAvailable, isUnavailableOnServer);
90
93
  const stop = useCallback(() => {
91
94
  wantsListeningRef.current = false;
92
95
  recognitionRef.current?.stop();
@@ -1 +1 @@
1
- {"version":3,"file":"useVoiceTranscription.js","sources":["../../src/hooks/useVoiceTranscription.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { uiLogger } from '@/helpers/uiLogger';\n\ninterface ISpeechRecognitionAlternative {\n transcript: string;\n}\n\ninterface ISpeechRecognitionResult {\n 0: ISpeechRecognitionAlternative;\n isFinal: boolean;\n}\n\ninterface ISpeechRecognitionEvent {\n resultIndex: number;\n results: { length: number; [index: number]: ISpeechRecognitionResult };\n}\n\ninterface ISpeechRecognition {\n lang: string;\n continuous: boolean;\n interimResults: boolean;\n start: () => void;\n stop: () => void;\n abort: () => void;\n onresult: ((event: ISpeechRecognitionEvent) => void) | null;\n onerror: ((event: { error: string }) => void) | null;\n onend: (() => void) | null;\n}\n\ntype TSpeechRecognitionCtor = new () => ISpeechRecognition;\n\nconst getRecognitionCtor = (): TSpeechRecognitionCtor | null => {\n const w = window as unknown as {\n SpeechRecognition?: TSpeechRecognitionCtor;\n webkitSpeechRecognition?: TSpeechRecognitionCtor;\n };\n return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;\n};\n\nexport type TVoiceCommandKey = 'comma' | 'period' | 'newline' | 'space' | 'question' | 'exclamation' | 'deleteWord';\n\ninterface IVoiceCommand {\n key: TVoiceCommandKey;\n phrases: Array<string>;\n insert?: string;\n action?: 'deleteWord';\n}\n\n// Spoken keywords matched against the user's speech. They must stay as literal\n// recognition tokens (not UI strings), so the i18n check is disabled here.\n/* eslint-disable i18n-check/no-portuguese-strings */\nexport const VOICE_COMMANDS: Array<IVoiceCommand> = [\n { key: 'comma', phrases: ['vírgula', 'virgula', 'comma'], insert: ', ' },\n { key: 'period', phrases: ['ponto', 'ponto final', 'period'], insert: '. ' },\n { key: 'newline', phrases: ['nova linha', 'quebra de linha', 'parágrafo', 'paragrafo', 'new line'], insert: '\\n' },\n { key: 'space', phrases: ['espaço', 'espaco', 'space'], insert: ' ' },\n {\n key: 'question',\n phrases: ['interrogação', 'interrogacao', 'ponto de interrogação', 'question mark'],\n insert: '? ',\n },\n {\n key: 'exclamation',\n phrases: ['exclamação', 'exclamacao', 'ponto de exclamação', 'exclamation mark'],\n insert: '! ',\n },\n { key: 'deleteWord', phrases: ['apagar', 'apaga', 'apagar palavra', 'delete'], action: 'deleteWord' },\n];\n/* eslint-enable i18n-check/no-portuguese-strings */\n\nconst PHRASE_TO_COMMAND = new Map<string, IVoiceCommand>();\nfor (const command of VOICE_COMMANDS) {\n for (const phrase of command.phrases) PHRASE_TO_COMMAND.set(phrase, command);\n}\n\nconst normalize = (value: string): string =>\n value\n .trim()\n .toLowerCase()\n .replace(/[.,!?;:]+$/, '');\n\nexport interface IVoiceTranscriptionUpdate {\n insert: string;\n deleteWords: number;\n}\n\nconst buildUpdateFromTranscript = (transcript: string): IVoiceTranscriptionUpdate => {\n const words = transcript.trim().split(/\\s+/).filter(Boolean);\n let insert = '';\n let deleteWords = 0;\n\n for (const word of words) {\n const command = PHRASE_TO_COMMAND.get(normalize(word));\n if (command?.action === 'deleteWord') {\n deleteWords += 1;\n insert = '';\n continue;\n }\n if (command?.insert !== undefined) {\n insert = insert.replace(/\\s+$/, '') + command.insert;\n continue;\n }\n insert += (insert && !insert.endsWith(' ') && !insert.endsWith('\\n') ? ' ' : '') + word;\n }\n\n return { insert, deleteWords };\n};\n\nconst appendTranscriptText = (current: string, addition: string): string => {\n if (!addition) return current;\n const needsSpace = current.length > 0 && !/\\s$/.test(current) && !/^[\\s.,!?]/.test(addition);\n return current + (needsSpace ? ' ' : '') + addition;\n};\n\nexport const applyVoiceUpdate = (current: string, update: IVoiceTranscriptionUpdate): string => {\n let next = current;\n for (let wordIndex = 0; wordIndex < update.deleteWords; wordIndex += 1) {\n next = next.replace(/\\s*\\S+\\s*$/, '');\n }\n return appendTranscriptText(next, update.insert);\n};\n\nexport const appendInterimTranscript = (committed: string, interim: string): string =>\n appendTranscriptText(committed, interim);\n\nexport type TVoiceTranscriptionError = 'permission' | 'microphone' | 'network' | 'unavailable';\n\nconst RECOGNITION_ERROR_TO_VOICE_ERROR: Record<string, TVoiceTranscriptionError> = {\n 'not-allowed': 'permission',\n 'service-not-allowed': 'permission',\n 'audio-capture': 'microphone',\n network: 'network',\n 'language-not-supported': 'unavailable',\n 'bad-grammar': 'unavailable',\n};\n\nconst MAX_SILENT_RESTARTS = 2;\n\ninterface IUseVoiceTranscriptionParams {\n lang?: string;\n onCommit: (update: IVoiceTranscriptionUpdate) => void;\n onInterim?: (text: string) => void;\n}\n\ninterface IUseVoiceTranscriptionResult {\n isSupported: boolean;\n isListening: boolean;\n error: TVoiceTranscriptionError | null;\n toggle: () => void;\n stop: () => void;\n}\n\nconst detachRecognition = (recognition: ISpeechRecognition) => {\n recognition.onresult = null;\n recognition.onerror = null;\n recognition.onend = null;\n};\n\nexport const useVoiceTranscription = ({\n lang = 'pt-BR',\n onCommit,\n onInterim,\n}: IUseVoiceTranscriptionParams): IUseVoiceTranscriptionResult => {\n const [isListening, setIsListening] = useState(false);\n const [error, setError] = useState<TVoiceTranscriptionError | null>(null);\n const recognitionRef = useRef<ISpeechRecognition | null>(null);\n const wantsListeningRef = useRef(false);\n const silentRestartCountRef = useRef(0);\n const onCommitRef = useRef(onCommit);\n const onInterimRef = useRef(onInterim);\n\n onCommitRef.current = onCommit;\n onInterimRef.current = onInterim;\n\n const isSupported = getRecognitionCtor() !== null;\n\n const stop = useCallback(() => {\n wantsListeningRef.current = false;\n recognitionRef.current?.stop();\n }, []);\n\n const startSession = useCallback((): boolean => {\n const Ctor = getRecognitionCtor();\n if (!Ctor) return false;\n\n const recognition = new Ctor();\n recognition.lang = lang;\n recognition.continuous = true;\n recognition.interimResults = true;\n\n let sessionHeardSpeech = false;\n let sessionEndedByError = false;\n\n recognition.onresult = (event) => {\n if (recognitionRef.current !== recognition) return;\n sessionHeardSpeech = true;\n let interim = '';\n for (let resultIndex = event.resultIndex; resultIndex < event.results.length; resultIndex += 1) {\n const result = event.results[resultIndex];\n const transcript = result?.[0]?.transcript ?? '';\n if (result?.isFinal) {\n onCommitRef.current(buildUpdateFromTranscript(transcript));\n } else {\n interim += transcript;\n }\n }\n onInterimRef.current?.(interim.trim());\n };\n\n recognition.onerror = (event) => {\n if (recognitionRef.current !== recognition) return;\n if (event.error === 'no-speech') return;\n sessionEndedByError = true;\n if (event.error === 'aborted') return;\n uiLogger.warn('[useVoiceTranscription] error', event.error);\n setError(RECOGNITION_ERROR_TO_VOICE_ERROR[event.error] ?? 'unavailable');\n };\n\n recognition.onend = () => {\n if (recognitionRef.current !== recognition) return;\n onInterimRef.current?.('');\n silentRestartCountRef.current = sessionHeardSpeech ? 0 : silentRestartCountRef.current + 1;\n const shouldResume =\n wantsListeningRef.current && !sessionEndedByError && silentRestartCountRef.current <= MAX_SILENT_RESTARTS;\n if (shouldResume && startSession()) return;\n wantsListeningRef.current = false;\n recognitionRef.current = null;\n setIsListening(false);\n };\n\n recognitionRef.current = recognition;\n try {\n recognition.start();\n return true;\n } catch (startError) {\n uiLogger.warn('[useVoiceTranscription] start failed', startError);\n detachRecognition(recognition);\n recognitionRef.current = null;\n setError('unavailable');\n return false;\n }\n }, [lang]);\n\n const start = useCallback(() => {\n setError(null);\n silentRestartCountRef.current = 0;\n wantsListeningRef.current = true;\n if (startSession()) {\n setIsListening(true);\n return;\n }\n wantsListeningRef.current = false;\n }, [startSession]);\n\n const toggle = useCallback(() => {\n if (isListening) stop();\n else start();\n }, [isListening, start, stop]);\n\n useEffect(\n () => () => {\n wantsListeningRef.current = false;\n const recognition = recognitionRef.current;\n if (!recognition) return;\n detachRecognition(recognition);\n recognitionRef.current = null;\n recognition.abort();\n },\n [],\n );\n\n return { isSupported, isListening, error, toggle, stop };\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useVoiceTranscription.js","sources":["../../src/hooks/useVoiceTranscription.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';\n\nimport { uiLogger } from '@/helpers/uiLogger';\n\ninterface ISpeechRecognitionAlternative {\n transcript: string;\n}\n\ninterface ISpeechRecognitionResult {\n 0: ISpeechRecognitionAlternative;\n isFinal: boolean;\n}\n\ninterface ISpeechRecognitionEvent {\n resultIndex: number;\n results: { length: number; [index: number]: ISpeechRecognitionResult };\n}\n\ninterface ISpeechRecognition {\n lang: string;\n continuous: boolean;\n interimResults: boolean;\n start: () => void;\n stop: () => void;\n abort: () => void;\n onresult: ((event: ISpeechRecognitionEvent) => void) | null;\n onerror: ((event: { error: string }) => void) | null;\n onend: (() => void) | null;\n}\n\ntype TSpeechRecognitionCtor = new () => ISpeechRecognition;\n\nconst getRecognitionCtor = (): TSpeechRecognitionCtor | null => {\n const w = window as unknown as {\n SpeechRecognition?: TSpeechRecognitionCtor;\n webkitSpeechRecognition?: TSpeechRecognitionCtor;\n };\n return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;\n};\n\n/* The API never appears or goes away while a page is open, so there is nothing to subscribe to.\n The store exists for the server answer: `window` is not there, and the first client render has\n to agree with it for hydration to hold. */\nconst subscribeToNothing = () => () => undefined;\nconst isRecognitionAvailable = () => getRecognitionCtor() !== null;\nconst isUnavailableOnServer = () => false;\n\nexport type TVoiceCommandKey = 'comma' | 'period' | 'newline' | 'space' | 'question' | 'exclamation' | 'deleteWord';\n\ninterface IVoiceCommand {\n key: TVoiceCommandKey;\n phrases: Array<string>;\n insert?: string;\n action?: 'deleteWord';\n}\n\n// Spoken keywords matched against the user's speech. They must stay as literal\n// recognition tokens (not UI strings), so the i18n check is disabled here.\n/* eslint-disable i18n-check/no-portuguese-strings */\nexport const VOICE_COMMANDS: Array<IVoiceCommand> = [\n { key: 'comma', phrases: ['vírgula', 'virgula', 'comma'], insert: ', ' },\n { key: 'period', phrases: ['ponto', 'ponto final', 'period'], insert: '. ' },\n { key: 'newline', phrases: ['nova linha', 'quebra de linha', 'parágrafo', 'paragrafo', 'new line'], insert: '\\n' },\n { key: 'space', phrases: ['espaço', 'espaco', 'space'], insert: ' ' },\n {\n key: 'question',\n phrases: ['interrogação', 'interrogacao', 'ponto de interrogação', 'question mark'],\n insert: '? ',\n },\n {\n key: 'exclamation',\n phrases: ['exclamação', 'exclamacao', 'ponto de exclamação', 'exclamation mark'],\n insert: '! ',\n },\n { key: 'deleteWord', phrases: ['apagar', 'apaga', 'apagar palavra', 'delete'], action: 'deleteWord' },\n];\n/* eslint-enable i18n-check/no-portuguese-strings */\n\nconst PHRASE_TO_COMMAND = new Map<string, IVoiceCommand>();\nfor (const command of VOICE_COMMANDS) {\n for (const phrase of command.phrases) PHRASE_TO_COMMAND.set(phrase, command);\n}\n\nconst normalize = (value: string): string =>\n value\n .trim()\n .toLowerCase()\n .replace(/[.,!?;:]+$/, '');\n\nexport interface IVoiceTranscriptionUpdate {\n insert: string;\n deleteWords: number;\n}\n\nconst buildUpdateFromTranscript = (transcript: string): IVoiceTranscriptionUpdate => {\n const words = transcript.trim().split(/\\s+/).filter(Boolean);\n let insert = '';\n let deleteWords = 0;\n\n for (const word of words) {\n const command = PHRASE_TO_COMMAND.get(normalize(word));\n if (command?.action === 'deleteWord') {\n deleteWords += 1;\n insert = '';\n continue;\n }\n if (command?.insert !== undefined) {\n insert = insert.replace(/\\s+$/, '') + command.insert;\n continue;\n }\n insert += (insert && !insert.endsWith(' ') && !insert.endsWith('\\n') ? ' ' : '') + word;\n }\n\n return { insert, deleteWords };\n};\n\nconst appendTranscriptText = (current: string, addition: string): string => {\n if (!addition) return current;\n const needsSpace = current.length > 0 && !/\\s$/.test(current) && !/^[\\s.,!?]/.test(addition);\n return current + (needsSpace ? ' ' : '') + addition;\n};\n\nexport const applyVoiceUpdate = (current: string, update: IVoiceTranscriptionUpdate): string => {\n let next = current;\n for (let wordIndex = 0; wordIndex < update.deleteWords; wordIndex += 1) {\n next = next.replace(/\\s*\\S+\\s*$/, '');\n }\n return appendTranscriptText(next, update.insert);\n};\n\nexport const appendInterimTranscript = (committed: string, interim: string): string =>\n appendTranscriptText(committed, interim);\n\nexport type TVoiceTranscriptionError = 'permission' | 'microphone' | 'network' | 'unavailable';\n\nconst RECOGNITION_ERROR_TO_VOICE_ERROR: Record<string, TVoiceTranscriptionError> = {\n 'not-allowed': 'permission',\n 'service-not-allowed': 'permission',\n 'audio-capture': 'microphone',\n network: 'network',\n 'language-not-supported': 'unavailable',\n 'bad-grammar': 'unavailable',\n};\n\nconst MAX_SILENT_RESTARTS = 2;\n\ninterface IUseVoiceTranscriptionParams {\n lang?: string;\n onCommit: (update: IVoiceTranscriptionUpdate) => void;\n onInterim?: (text: string) => void;\n}\n\ninterface IUseVoiceTranscriptionResult {\n isSupported: boolean;\n isListening: boolean;\n error: TVoiceTranscriptionError | null;\n toggle: () => void;\n stop: () => void;\n}\n\nconst detachRecognition = (recognition: ISpeechRecognition) => {\n recognition.onresult = null;\n recognition.onerror = null;\n recognition.onend = null;\n};\n\nexport const useVoiceTranscription = ({\n lang = 'pt-BR',\n onCommit,\n onInterim,\n}: IUseVoiceTranscriptionParams): IUseVoiceTranscriptionResult => {\n const [isListening, setIsListening] = useState(false);\n const [error, setError] = useState<TVoiceTranscriptionError | null>(null);\n const recognitionRef = useRef<ISpeechRecognition | null>(null);\n const wantsListeningRef = useRef(false);\n const silentRestartCountRef = useRef(0);\n const onCommitRef = useRef(onCommit);\n const onInterimRef = useRef(onInterim);\n\n onCommitRef.current = onCommit;\n onInterimRef.current = onInterim;\n\n const isSupported = useSyncExternalStore(subscribeToNothing, isRecognitionAvailable, isUnavailableOnServer);\n\n const stop = useCallback(() => {\n wantsListeningRef.current = false;\n recognitionRef.current?.stop();\n }, []);\n\n const startSession = useCallback((): boolean => {\n const Ctor = getRecognitionCtor();\n if (!Ctor) return false;\n\n const recognition = new Ctor();\n recognition.lang = lang;\n recognition.continuous = true;\n recognition.interimResults = true;\n\n let sessionHeardSpeech = false;\n let sessionEndedByError = false;\n\n recognition.onresult = (event) => {\n if (recognitionRef.current !== recognition) return;\n sessionHeardSpeech = true;\n let interim = '';\n for (let resultIndex = event.resultIndex; resultIndex < event.results.length; resultIndex += 1) {\n const result = event.results[resultIndex];\n const transcript = result?.[0]?.transcript ?? '';\n if (result?.isFinal) {\n onCommitRef.current(buildUpdateFromTranscript(transcript));\n } else {\n interim += transcript;\n }\n }\n onInterimRef.current?.(interim.trim());\n };\n\n recognition.onerror = (event) => {\n if (recognitionRef.current !== recognition) return;\n if (event.error === 'no-speech') return;\n sessionEndedByError = true;\n if (event.error === 'aborted') return;\n uiLogger.warn('[useVoiceTranscription] error', event.error);\n setError(RECOGNITION_ERROR_TO_VOICE_ERROR[event.error] ?? 'unavailable');\n };\n\n recognition.onend = () => {\n if (recognitionRef.current !== recognition) return;\n onInterimRef.current?.('');\n silentRestartCountRef.current = sessionHeardSpeech ? 0 : silentRestartCountRef.current + 1;\n const shouldResume =\n wantsListeningRef.current && !sessionEndedByError && silentRestartCountRef.current <= MAX_SILENT_RESTARTS;\n if (shouldResume && startSession()) return;\n wantsListeningRef.current = false;\n recognitionRef.current = null;\n setIsListening(false);\n };\n\n recognitionRef.current = recognition;\n try {\n recognition.start();\n return true;\n } catch (startError) {\n uiLogger.warn('[useVoiceTranscription] start failed', startError);\n detachRecognition(recognition);\n recognitionRef.current = null;\n setError('unavailable');\n return false;\n }\n }, [lang]);\n\n const start = useCallback(() => {\n setError(null);\n silentRestartCountRef.current = 0;\n wantsListeningRef.current = true;\n if (startSession()) {\n setIsListening(true);\n return;\n }\n wantsListeningRef.current = false;\n }, [startSession]);\n\n const toggle = useCallback(() => {\n if (isListening) stop();\n else start();\n }, [isListening, start, stop]);\n\n useEffect(\n () => () => {\n wantsListeningRef.current = false;\n const recognition = recognitionRef.current;\n if (!recognition) return;\n detachRecognition(recognition);\n recognitionRef.current = null;\n recognition.abort();\n },\n [],\n );\n\n return { isSupported, isListening, error, toggle, stop };\n};\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export { default as ControlledSwitch } from './components/Form/ControlledSwitch'
10
10
  export { default as ControlledTabs } from './components/Form/ControlledTabs';
11
11
  export { default as ControlledTextarea } from './components/Form/ControlledTextarea';
12
12
  export { default as ControlledViewToggle } from './components/Form/ControlledViewToggle';
13
+ export { default as FieldLabel } from './components/Form/FieldLabel';
13
14
  export { default as ControlledBreadcrumb } from './components/Navigation/ControlledBreadcrumb';
14
15
  export { default as ErrorPage } from './components/Navigation/ErrorPage';
15
16
  export { default as LinkButton } from './components/Navigation/LinkButton';
@@ -45,6 +46,7 @@ export { default as SkeletonLoading } from './components/UI/SkeletonLoading';
45
46
  export { default as UserMenu } from './components/UI/UserMenu';
46
47
  export * from './helpers/composeRefs';
47
48
  export * from './helpers/layeredSurfaces';
49
+ export * from './helpers/mergeClasses';
48
50
  export * from './helpers/themeSkins';
49
51
  export * from './helpers/themeTokens';
50
52
  export * from './helpers/uiLogger';