@nextelco/common-ui 2.1.105 → 2.1.107

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 (199) hide show
  1. package/dist/esm/index.js +201 -145
  2. package/dist/esm/index.js.map +1 -1
  3. package/dist/esm/legacy/components/atoms/InputData/InputData.js +2 -13
  4. package/dist/esm/legacy/components/atoms/InputData/InputData.js.map +1 -1
  5. package/dist/esm/legacy/components/charts/Charts.js +147 -57
  6. package/dist/esm/legacy/components/charts/Charts.js.map +1 -1
  7. package/dist/esm/legacy/components/layout/Layout/Layout.js +21 -82
  8. package/dist/esm/legacy/components/layout/Layout/Layout.js.map +1 -1
  9. package/dist/esm/legacy/components/layout/Sidebar/Sidebar.js +29 -218
  10. package/dist/esm/legacy/components/layout/Sidebar/Sidebar.js.map +1 -1
  11. package/dist/esm/legacy/components/molecules/ColumnPicker/ColumnPicker.js +77 -10
  12. package/dist/esm/legacy/components/molecules/ColumnPicker/ColumnPicker.js.map +1 -1
  13. package/dist/esm/legacy/components/molecules/DataTable/DataTable.js +445 -337
  14. package/dist/esm/legacy/components/molecules/DataTable/DataTable.js.map +1 -1
  15. package/dist/esm/legacy/components/molecules/UserAvatar/UserAvatar.js +15 -97
  16. package/dist/esm/legacy/components/molecules/UserAvatar/UserAvatar.js.map +1 -1
  17. package/dist/esm/legacy/components/organisms/MapaPontos/MapaPontos.js +3 -3
  18. package/dist/esm/legacy/components/organisms/MapaPontos/MapaPontos.js.map +1 -1
  19. package/dist/esm/legacy/components/organisms/PainelFiltros/PainelFiltros.js +15 -3
  20. package/dist/esm/legacy/components/organisms/PainelFiltros/PainelFiltros.js.map +1 -1
  21. package/dist/esm/legacy/components/organisms/Selector/Selector.js +4 -4
  22. package/dist/esm/legacy/components/organisms/Selector/Selector.js.map +1 -1
  23. package/dist/esm/legacy/components/ui/Avisos.js +23 -76
  24. package/dist/esm/legacy/components/ui/Avisos.js.map +1 -1
  25. package/dist/esm/legacy/components/ui/CabecalhoPagina.js +14 -27
  26. package/dist/esm/legacy/components/ui/CabecalhoPagina.js.map +1 -1
  27. package/dist/esm/legacy/components/ui/CaixaPesquisa.js +3 -51
  28. package/dist/esm/legacy/components/ui/CaixaPesquisa.js.map +1 -1
  29. package/dist/esm/legacy/components/ui/Confirmacao.js +13 -31
  30. package/dist/esm/legacy/components/ui/Confirmacao.js.map +1 -1
  31. package/dist/esm/legacy/components/ui/Escolha.js +21 -137
  32. package/dist/esm/legacy/components/ui/Escolha.js.map +1 -1
  33. package/dist/esm/legacy/components/ui/FaixaAviso.js +13 -11
  34. package/dist/esm/legacy/components/ui/FaixaAviso.js.map +1 -1
  35. package/dist/esm/legacy/components/ui/Separadores.js +11 -46
  36. package/dist/esm/legacy/components/ui/Separadores.js.map +1 -1
  37. package/dist/esm/legacy/components/ui/Ui.js +18 -115
  38. package/dist/esm/legacy/components/ui/Ui.js.map +1 -1
  39. package/dist/esm/legacy/components/ui/painelFlutuante.js +5 -44
  40. package/dist/esm/legacy/components/ui/painelFlutuante.js.map +1 -1
  41. package/dist/esm/v1/capabilities/overlay/useFloatingPanel.js +52 -0
  42. package/dist/esm/v1/capabilities/overlay/useFloatingPanel.js.map +1 -0
  43. package/dist/esm/v1/capabilities/theme/useIsDark.js +15 -0
  44. package/dist/esm/v1/capabilities/theme/useIsDark.js.map +1 -0
  45. package/dist/esm/v1/capabilities/theme/useTheme.js +66 -0
  46. package/dist/esm/v1/capabilities/theme/useTheme.js.map +1 -0
  47. package/dist/esm/v1/components/data/Pagination/Pagination.js +66 -0
  48. package/dist/esm/v1/components/data/Pagination/Pagination.js.map +1 -0
  49. package/dist/esm/v1/components/feedback/Toast/Toast.js +105 -0
  50. package/dist/esm/v1/components/feedback/Toast/Toast.js.map +1 -0
  51. package/dist/esm/v1/components/forms/FormField/FormField.js.map +1 -1
  52. package/dist/esm/v1/components/forms/FormGrid/FormGrid.js.map +1 -1
  53. package/dist/esm/v1/components/layout/AppShell/AppShell.js +89 -0
  54. package/dist/esm/v1/components/layout/AppShell/AppShell.js.map +1 -0
  55. package/dist/esm/v1/components/layout/Page/Page.js +16 -0
  56. package/dist/esm/v1/components/layout/Page/Page.js.map +1 -0
  57. package/dist/esm/v1/components/layout/PageHeader/PageHeader.js +26 -0
  58. package/dist/esm/v1/components/layout/PageHeader/PageHeader.js.map +1 -0
  59. package/dist/esm/v1/components/layout/SideNav/SideNav.js +277 -0
  60. package/dist/esm/v1/components/layout/SideNav/SideNav.js.map +1 -0
  61. package/dist/esm/v1/components/layout/navTrail.js +19 -0
  62. package/dist/esm/v1/components/layout/navTrail.js.map +1 -0
  63. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js +4 -4
  64. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js.map +1 -1
  65. package/dist/esm/v1/components/overlays/ConfirmDialog/ConfirmDialog.js +95 -0
  66. package/dist/esm/v1/components/overlays/ConfirmDialog/ConfirmDialog.js.map +1 -0
  67. package/dist/esm/v1/components/overlays/Dialog/Dialog.js.map +1 -1
  68. package/dist/esm/v1/components/overlays/Tooltip/Tooltip.js +98 -0
  69. package/dist/esm/v1/components/overlays/Tooltip/Tooltip.js.map +1 -0
  70. package/dist/esm/v1/components/overlays/Tooltip/TooltipLayer.js +84 -0
  71. package/dist/esm/v1/components/overlays/Tooltip/TooltipLayer.js.map +1 -0
  72. package/dist/esm/v1/components/overlays/Tooltip/TooltipPanel.js +33 -0
  73. package/dist/esm/v1/components/overlays/Tooltip/TooltipPanel.js.map +1 -0
  74. package/dist/esm/v1/components/overlays/topLayer.js +28 -0
  75. package/dist/esm/v1/components/overlays/topLayer.js.map +1 -0
  76. package/dist/esm/v1/components/tables/PaginatedTable/PaginatedTable.js.map +1 -1
  77. package/dist/esm/v1/components/tables/Table/Table.js.map +1 -1
  78. package/dist/esm/v1/foundations/icons/icons.js +61 -2
  79. package/dist/esm/v1/foundations/icons/icons.js.map +1 -1
  80. package/dist/esm/v1/primitives/buttons/Button/Button.js +18 -4
  81. package/dist/esm/v1/primitives/buttons/Button/Button.js.map +1 -1
  82. package/dist/esm/v1/primitives/buttons/SegmentedControl/SegmentedControl.js +65 -0
  83. package/dist/esm/v1/primitives/buttons/SegmentedControl/SegmentedControl.js.map +1 -0
  84. package/dist/esm/v1/primitives/buttons/SegmentedControl/segmentedStyles.js +10 -0
  85. package/dist/esm/v1/primitives/buttons/SegmentedControl/segmentedStyles.js.map +1 -0
  86. package/dist/esm/v1/primitives/display/Avatar/Avatar.js +120 -0
  87. package/dist/esm/v1/primitives/display/Avatar/Avatar.js.map +1 -0
  88. package/dist/esm/v1/primitives/display/Badge/Badge.js +23 -0
  89. package/dist/esm/v1/primitives/display/Badge/Badge.js.map +1 -0
  90. package/dist/esm/v1/primitives/display/Card/Card.js +22 -0
  91. package/dist/esm/v1/primitives/display/Card/Card.js.map +1 -0
  92. package/dist/esm/v1/primitives/display/FilterChips/FilterChips.js +45 -0
  93. package/dist/esm/v1/primitives/display/FilterChips/FilterChips.js.map +1 -0
  94. package/dist/esm/v1/primitives/display/Kpi/Kpi.js +103 -0
  95. package/dist/esm/v1/primitives/display/Kpi/Kpi.js.map +1 -0
  96. package/dist/esm/v1/primitives/display/Notice/Notice.js +71 -0
  97. package/dist/esm/v1/primitives/display/Notice/Notice.js.map +1 -0
  98. package/dist/esm/v1/primitives/display/Person/Person.js +14 -0
  99. package/dist/esm/v1/primitives/display/Person/Person.js.map +1 -0
  100. package/dist/esm/v1/primitives/forms/Checkbox/Checkbox.js +10 -5
  101. package/dist/esm/v1/primitives/forms/Checkbox/Checkbox.js.map +1 -1
  102. package/dist/esm/v1/primitives/forms/DateInput/DateInput.js +150 -0
  103. package/dist/esm/v1/primitives/forms/DateInput/DateInput.js.map +1 -0
  104. package/dist/esm/v1/primitives/forms/LookupInput/LookupInput.js.map +1 -1
  105. package/dist/esm/v1/primitives/forms/SearchInput/SearchInput.js +43 -0
  106. package/dist/esm/v1/primitives/forms/SearchInput/SearchInput.js.map +1 -0
  107. package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js +190 -12
  108. package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js.map +1 -1
  109. package/dist/esm/v1/primitives/forms/TextArea/TextArea.js.map +1 -1
  110. package/dist/esm/v1/primitives/forms/field.js +4 -2
  111. package/dist/esm/v1/primitives/forms/field.js.map +1 -1
  112. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js +2 -2
  113. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js.map +1 -1
  114. package/dist/esm/v1/primitives/navigation/Tabs/Tabs.js +96 -0
  115. package/dist/esm/v1/primitives/navigation/Tabs/Tabs.js.map +1 -0
  116. package/dist/style.components.css +1 -1
  117. package/dist/style.css +1 -1
  118. package/dist/style.layered.css +1 -1
  119. package/dist/tailwind.css +24 -0
  120. package/dist/theme-boot.js +1 -0
  121. package/dist/theme.css +10 -3
  122. package/dist/tokens.css +119 -0
  123. package/dist/types/legacy/components/atoms/InputData/InputData.d.ts +4 -1
  124. package/dist/types/legacy/components/charts/Charts.d.ts +58 -5
  125. package/dist/types/legacy/components/layout/Layout/Layout.d.ts +7 -20
  126. package/dist/types/legacy/components/layout/Sidebar/Sidebar.d.ts +13 -26
  127. package/dist/types/legacy/components/molecules/ColumnPicker/ColumnPicker.d.ts +56 -3
  128. package/dist/types/legacy/components/molecules/DataTable/DataTable.d.ts +72 -7
  129. package/dist/types/legacy/components/molecules/UserAvatar/UserAvatar.d.ts +4 -5
  130. package/dist/types/legacy/components/ui/Avisos.d.ts +6 -16
  131. package/dist/types/legacy/components/ui/CabecalhoPagina.d.ts +6 -20
  132. package/dist/types/legacy/components/ui/CaixaPesquisa.d.ts +2 -4
  133. package/dist/types/legacy/components/ui/Confirmacao.d.ts +3 -10
  134. package/dist/types/legacy/components/ui/Escolha.d.ts +5 -15
  135. package/dist/types/legacy/components/ui/FaixaAviso.d.ts +2 -6
  136. package/dist/types/legacy/components/ui/Separadores.d.ts +3 -10
  137. package/dist/types/legacy/components/ui/Ui.d.ts +12 -54
  138. package/dist/types/legacy/components/ui/painelFlutuante.d.ts +6 -10
  139. package/dist/types/legacy/exports/atoms.d.ts +1 -1
  140. package/dist/types/legacy/exports/charts.d.ts +2 -2
  141. package/dist/types/legacy/exports/hooks.d.ts +2 -2
  142. package/dist/types/legacy/exports/molecules.d.ts +2 -2
  143. package/dist/types/v1/capabilities/overlay/useFloatingPanel.d.ts +20 -0
  144. package/dist/types/v1/capabilities/theme/useIsDark.d.ts +10 -0
  145. package/dist/types/v1/capabilities/theme/useTheme.d.ts +29 -0
  146. package/dist/types/v1/components/data/Pagination/Pagination.d.ts +33 -0
  147. package/dist/types/v1/components/feedback/Toast/Toast.d.ts +51 -0
  148. package/dist/types/v1/components/forms/FormField/FormField.d.ts +3 -3
  149. package/dist/types/v1/components/forms/FormGrid/FormGrid.d.ts +2 -0
  150. package/dist/types/v1/components/layout/AppShell/AppShell.d.ts +35 -0
  151. package/dist/types/v1/components/layout/Page/Page.d.ts +23 -0
  152. package/dist/types/v1/components/layout/PageHeader/PageHeader.d.ts +35 -0
  153. package/dist/types/v1/components/layout/SideNav/SideNav.d.ts +92 -0
  154. package/dist/types/v1/components/layout/navTrail.d.ts +15 -0
  155. package/dist/types/v1/components/lists/ListFinder/ListFinder.d.ts +1 -1
  156. package/dist/types/v1/components/overlays/ConfirmDialog/ConfirmDialog.d.ts +59 -0
  157. package/dist/types/v1/components/overlays/Dialog/Dialog.d.ts +6 -3
  158. package/dist/types/v1/components/overlays/Tooltip/Tooltip.d.ts +34 -0
  159. package/dist/types/v1/components/overlays/Tooltip/TooltipLayer.d.ts +18 -0
  160. package/dist/types/v1/components/overlays/Tooltip/TooltipPanel.d.ts +23 -0
  161. package/dist/types/v1/components/overlays/topLayer.d.ts +23 -0
  162. package/dist/types/v1/components/tables/PaginatedTable/PaginatedTable.d.ts +1 -1
  163. package/dist/types/v1/components/tables/Table/Table.d.ts +1 -1
  164. package/dist/types/v1/foundations/icons/icons.d.ts +20 -0
  165. package/dist/types/v1/index.d.ts +43 -1
  166. package/dist/types/v1/primitives/buttons/Button/Button.d.ts +22 -5
  167. package/dist/types/v1/primitives/buttons/SegmentedControl/SegmentedControl.d.ts +28 -0
  168. package/dist/types/v1/primitives/buttons/SegmentedControl/segmentedStyles.d.ts +16 -0
  169. package/dist/types/v1/primitives/display/Avatar/Avatar.d.ts +30 -0
  170. package/dist/types/v1/primitives/display/Badge/Badge.d.ts +25 -0
  171. package/dist/types/v1/primitives/display/Card/Card.d.ts +22 -0
  172. package/dist/types/v1/primitives/display/FilterChips/FilterChips.d.ts +33 -0
  173. package/dist/types/v1/primitives/display/Kpi/Kpi.d.ts +68 -0
  174. package/dist/types/v1/primitives/display/Notice/Notice.d.ts +29 -0
  175. package/dist/types/v1/primitives/display/Person/Person.d.ts +15 -0
  176. package/dist/types/v1/primitives/forms/Checkbox/Checkbox.d.ts +7 -0
  177. package/dist/types/v1/primitives/forms/DateInput/DateInput.d.ts +43 -0
  178. package/dist/types/v1/primitives/forms/LookupInput/LookupInput.d.ts +3 -0
  179. package/dist/types/v1/primitives/forms/SearchInput/SearchInput.d.ts +23 -0
  180. package/dist/types/v1/primitives/forms/SelectInput/SelectInput.d.ts +61 -10
  181. package/dist/types/v1/primitives/forms/TextArea/TextArea.d.ts +3 -0
  182. package/dist/types/v1/primitives/forms/field.d.ts +2 -0
  183. package/dist/types/v1/primitives/lists/ListItem/ListItem.d.ts +2 -1
  184. package/dist/types/v1/primitives/navigation/Tabs/Tabs.d.ts +58 -0
  185. package/package.json +3 -3
  186. package/dist/esm/legacy/components/atoms/Tooltip/Tooltip.css +0 -17
  187. package/dist/esm/legacy/components/atoms/Tooltip/Tooltip.js +0 -41
  188. package/dist/esm/legacy/components/atoms/Tooltip/Tooltip.js.map +0 -1
  189. package/dist/esm/legacy/components/molecules/Search/Search.css +0 -18
  190. package/dist/esm/legacy/components/molecules/Search/Search.js +0 -30
  191. package/dist/esm/legacy/components/molecules/Search/Search.js.map +0 -1
  192. package/dist/esm/legacy/hooks/useTheme.js +0 -39
  193. package/dist/esm/legacy/hooks/useTheme.js.map +0 -1
  194. package/dist/esm/v1/primitives/lists/ListItem/ListCheckbox.js +0 -28
  195. package/dist/esm/v1/primitives/lists/ListItem/ListCheckbox.js.map +0 -1
  196. package/dist/types/legacy/components/atoms/Tooltip/Tooltip.d.ts +0 -13
  197. package/dist/types/legacy/components/molecules/Search/Search.d.ts +0 -7
  198. package/dist/types/legacy/hooks/useTheme.d.ts +0 -17
  199. package/dist/types/v1/primitives/lists/ListItem/ListCheckbox.d.ts +0 -14
@@ -1 +1 @@
1
- {"version":3,"file":"Ui.js","sources":["../../../../../src/legacy/components/ui/Ui.tsx"],"sourcesContent":["import React, { type ReactNode } from 'react'\r\nimport Escolha from './Escolha'\r\n\r\n/** Visual UI primitives (proef design system). Prefer these over repeating classes. */\r\n\r\n/**\r\n * SLA status accepted by BadgeSla.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport type StatusSla = 'ok' | 'risco' | 'incumprido'\r\n\r\nfunction IconX({ className = '' }: { className?: string }) {\r\n return (\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth={2}\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n className={className}\r\n aria-hidden=\"true\"\r\n >\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Card({ title, extra, children, className = '' }: { title?: ReactNode; extra?: ReactNode; children: ReactNode; className?: string }) {\r\n return (\r\n <div className={`bg-white border border-slate-200 dark:bg-slate-900 dark:border-slate-700 rounded-xl p-4 sm:p-5 ${className}`}>\r\n {(title || extra) && (\r\n <div className=\"flex items-start justify-between gap-3 mb-3.5\">\r\n {title && <div className=\"text-sm font-semibold dark:text-slate-100\">{title}</div>}\r\n {extra}\r\n </div>\r\n )}\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Colour-bordered group — the overview sections in the prototype.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function SectionGroup({\r\n title,\r\n tone = 'accent',\r\n children,\r\n}: {\r\n title: string\r\n tone?: 'accent' | 'ok' | 'blue' | 'red' | 'purple' | 'yellow'\r\n children: ReactNode\r\n}) {\r\n const tones: Record<string, [string, string]> = {\r\n accent: ['border-slate-200 dark:border-slate-700', 'text-proef-navy dark:text-slate-100'],\r\n ok: ['border-ok', 'text-ok'],\r\n blue: ['border-[#4FA8FF]', 'text-[#2E8CE0]'],\r\n red: ['border-incumprido', 'text-incumprido'],\r\n purple: ['border-[#8B5CF6]', 'text-[#7C3AED]'],\r\n yellow: ['border-[#D9A400]', 'text-[#B78400]'],\r\n }\r\n const [borda, texto] = tones[tone]!\r\n return (\r\n <div className={`border-[1.5px] ${borda} rounded-2xl p-4 pb-1 mb-5 bg-[rgba(24,34,56,0.015)] dark:bg-slate-900/40`}>\r\n <div className={`text-[15px] font-bold text-center tracking-wide mb-3.5 ${texto}`}>{title}</div>\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\nexport type KpiTone = 'default' | 'brand' | 'ok' | 'risco' | 'incumprido'\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Kpi({\r\n label,\r\n value,\r\n tone = 'default',\r\n delta,\r\n onClick,\r\n bgTint = false,\r\n active = false,\r\n hint,\r\n dot,\r\n barra,\r\n rodape,\r\n denso = false,\r\n}: {\r\n label: string\r\n value: ReactNode\r\n tone?: KpiTone\r\n delta?: ReactNode\r\n onClick?: () => void\r\n /** Ponto colorido antes do rótulo — liga o indicador à cor que ele tem no mapa ou na legenda. */\r\n dot?: string\r\n /**\r\n * Barra por baixo do valor. Um número é uma percentagem; uma lista é uma\r\n * barra repartida, com cada troço proporcional ao seu peso.\r\n *\r\n * Serve para o indicador dizer a PROPORÇÃO e não só o valor: \"253 concluídas\"\r\n * não diz nada sem saber de quantas.\r\n */\r\n barra?: number | { peso: number; cor: string }[]\r\n /** Linha pequena no fim, para um dado que acompanha mas não é o indicador. */\r\n rodape?: ReactNode\r\n /**\r\n * Faixa fina em vez de cartão.\r\n *\r\n * Oito indicadores em cartões de 88px empurram o conteúdo para fora do ecrã,\r\n * e o conteúdo é que é o trabalho. Denso alinha à esquerda e corta a altura\r\n * para metade — os números continuam a ler-se, e o mapa fica acima da dobra.\r\n */\r\n denso?: boolean\r\n /** Fundo colorido (tom claro) em vez de só o número — opt-in, sem quebrar o uso existente. */\r\n bgTint?: boolean\r\n /** Filtro ativo (KPI clicável selecionado): contorno azul. */\r\n active?: boolean\r\n /** Tooltip do KPI clicável (ex: \"Filtrar a tabela\"). */\r\n hint?: string\r\n}) {\r\n const cores: Record<KpiTone, string> = {\r\n default: 'text-slate-800 dark:text-slate-100',\r\n brand: 'text-proef-blue dark:text-sky-300',\r\n ok: 'text-ok',\r\n risco: 'text-risco',\r\n incumprido: 'text-incumprido',\r\n }\r\n const fundos: Record<KpiTone, string> = {\r\n default: 'bg-white border-slate-200 dark:bg-slate-900 dark:border-slate-700',\r\n brand: 'bg-proef-blue/10 border-transparent dark:bg-sky-500/15',\r\n ok: 'bg-ok-bg border-transparent',\r\n risco: 'bg-risco-bg border-transparent',\r\n incumprido: 'bg-incumprido-bg border-transparent',\r\n }\r\n const base = denso\r\n ? `relative border rounded-lg px-2.5 py-1.5 flex flex-col justify-center text-left gap-0.5 ${\r\n bgTint ? fundos[tone] : 'bg-white border-slate-200 dark:bg-slate-900 dark:border-slate-700'\r\n }`\r\n : `relative border rounded-xl px-4 py-3 min-h-[88px] flex flex-col items-center justify-center text-center gap-1.5 ${\r\n bgTint ? fundos[tone] : 'bg-white border-slate-200 dark:bg-slate-900 dark:border-slate-700'\r\n }`\r\n const totalBarra = Array.isArray(barra) ? barra.reduce((s, x) => s + x.peso, 0) : 0\r\n const conteudo = (\r\n <>\r\n <div className={`${denso ? 'text-[10px] uppercase tracking-wide' : 'text-xs'} text-slate-500 dark:text-slate-400 flex items-center gap-1.5`}>\r\n {dot && <i className=\"w-2 h-2 rounded-full inline-block shrink-0\" style={{ background: dot }} />}\r\n <span className=\"truncate\">{label}</span>\r\n </div>\r\n {/* Em denso o valor e o secundário partilham a linha: é o que poupa\r\n altura sem tirar informação. */}\r\n <div className={denso ? 'flex items-baseline gap-1.5' : ''}>\r\n <span className={`${denso ? 'text-xl' : 'text-2xl'} font-semibold ${cores[tone]}`}>{value}</span>\r\n {delta && (\r\n <span className={`${denso ? 'text-[11px]' : 'hidden'} text-slate-400 truncate`}>{delta}</span>\r\n )}\r\n </div>\r\n {delta && !denso && <div className=\"text-[11.5px] text-slate-400\">{delta}</div>}\r\n {barra != null && (\r\n <div className=\"w-full h-1 bg-slate-100 dark:bg-slate-800 rounded overflow-hidden flex\">\r\n {Array.isArray(barra) ? (\r\n barra.map((x, i) => (\r\n <i\r\n key={i}\r\n style={{ width: totalBarra ? `${(x.peso / totalBarra) * 100}%` : 0, background: x.cor }}\r\n />\r\n ))\r\n ) : (\r\n <i style={{ width: `${Math.min(100, Math.max(0, barra))}%`, background: 'currentColor' }} className={cores[tone]} />\r\n )}\r\n </div>\r\n )}\r\n {rodape && <div className=\"text-[11px] text-slate-400\">{rodape}</div>}\r\n </>\r\n )\r\n if (!onClick) return <div className={base}>{conteudo}</div>\r\n return (\r\n <button\r\n type=\"button\"\r\n onClick={onClick}\r\n title={hint}\r\n aria-pressed={active}\r\n className={`${base} w-full cursor-pointer shadow-sm transition hover:-translate-y-0.5 hover:border-proef-blue dark:hover:border-sky-400 hover:shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-proef-blue ${\r\n active ? 'ring-2 ring-proef-blue border-proef-blue' : ''\r\n }`}\r\n >\r\n <svg\r\n aria-hidden\r\n viewBox=\"0 0 24 24\"\r\n className={`absolute right-2.5 top-2.5 h-3.5 w-3.5 ${active ? 'text-proef-blue dark:text-sky-300' : 'text-slate-300 dark:text-slate-600'}`}\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth={2}\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <path d=\"M3 4h18l-7 8.5V19l-4 2v-8.5L3 4z\" />\r\n </svg>\r\n {conteudo}\r\n </button>\r\n )\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport type BadgeTone = 'ok' | 'risco' | 'incumprido' | 'neutral' | 'accent'\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Badge({ tone = 'neutral', children }: { tone?: BadgeTone; children: ReactNode }) {\r\n const cores: Record<BadgeTone, string> = {\r\n ok: 'bg-ok-bg text-ok',\r\n risco: 'bg-risco-bg text-risco',\r\n incumprido: 'bg-incumprido-bg text-incumprido',\r\n neutral: 'bg-neutral-bg text-neutral',\r\n accent: 'bg-proef-blue/10 text-proef-navy dark:bg-sky-500/15 dark:text-sky-300',\r\n }\r\n return (\r\n <span className={`inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium whitespace-nowrap ${cores[tone]}`}>\r\n <span className=\"w-1.5 h-1.5 rounded-full bg-current\" />\r\n {children}\r\n </span>\r\n )\r\n}\r\n\r\n/**\r\n * SLA badge — picks the tone and label from `status`. Labels overridable via `labels`.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function BadgeSla({ status, labels }: { status: StatusSla; labels?: { ok?: string; risco?: string; incumprido?: string } }) {\r\n const tone: BadgeTone = status === 'ok' ? 'ok' : status === 'risco' ? 'risco' : 'incumprido'\r\n const defaults: Record<StatusSla, string> = { ok: 'On time', risco: 'At risk', incumprido: 'Breached' }\r\n const label = labels?.[status] ?? defaults[status]\r\n return <Badge tone={tone}>{label}</Badge>\r\n}\r\n\r\n/** Internal-state chip — prototype colours (ESTADO_INTERNO_COLORS). */\r\nconst CORES_ESTADO_INTERNO: Record<string, [string, string]> = {\r\n 'Concluído': ['#1F8A55', '#E5F6EC'],\r\n 'Aferição': ['#2E6DA4', '#E5F0FA'],\r\n 'Para Tratamento': ['#1868B0', '#E5F0FA'],\r\n 'Aguarda Validação LS': ['#2E6DA4', '#E5F0FA'],\r\n 'Aguarda Produção LS': ['#6E8CAD', '#EDF2F7'],\r\n 'Incompleto/Devolvido': ['#C2255C', '#FBE5EF'],\r\n 'Falta de Documentação': ['#B96E00', '#FCF0DC'],\r\n Ignorar: ['#767C88', '#EEF0F2'],\r\n Cancelado: ['#767C88', '#EEF0F2'],\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function ChipEstadoInterno({ estado }: { estado: string | null }) {\r\n if (!estado) return <span className=\"text-slate-400\">—</span>\r\n const cor = CORES_ESTADO_INTERNO[estado]\r\n if (!cor) return <span>{estado}</span>\r\n return (\r\n <span className=\"px-2 py-0.5 rounded text-[11px] font-medium inline-block\" style={{ color: cor[0], background: cor[1] }}>\r\n {estado}\r\n </span>\r\n )\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Btn({\r\n children,\r\n onClick,\r\n variant = 'default',\r\n active = false,\r\n disabled = false,\r\n title,\r\n type = 'button',\r\n}: {\r\n children: ReactNode\r\n onClick?: () => void\r\n variant?: 'default' | 'accent' | 'danger' | 'ghost'\r\n active?: boolean\r\n disabled?: boolean\r\n title?: string\r\n type?: 'button' | 'submit'\r\n}) {\r\n // Estilo do design system proef (Tickets): uppercase, font-black, tracking-widest; activo em navy.\r\n // Mesma altura (h-8) e cantos (rounded-lg) de todos os controlos da biblioteca: Exportar, Colunas, selects, campos.\r\n const base = 'inline-flex items-center justify-center gap-1.5 h-8 px-3.5 rounded-lg text-[11px] font-black uppercase tracking-widest transition-colors disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap'\r\n const variantes: Record<string, string> = {\r\n default: active\r\n ? 'bg-proef-navy text-white border border-proef-navy dark:bg-proef-blue dark:border-proef-blue'\r\n : 'bg-white text-slate-500 border border-slate-200 hover:border-proef-navy hover:text-proef-navy dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700 dark:hover:border-slate-400 dark:hover:text-white',\r\n accent: 'bg-proef-navy text-white border border-proef-navy hover:bg-proef-blue dark:bg-proef-blue dark:border-proef-blue dark:hover:bg-sky-500',\r\n danger: 'bg-incumprido text-white border border-incumprido hover:opacity-90 dark:bg-red-600 dark:border-red-600',\r\n ghost: 'text-slate-500 hover:text-proef-navy dark:text-slate-400 dark:hover:text-white',\r\n }\r\n return (\r\n <button type={type} onClick={onClick} disabled={disabled} title={title} className={`${base} ${variantes[variant]}`}>\r\n {children}\r\n </button>\r\n )\r\n}\r\n\r\n/**\r\n * Mutually exclusive button group (granularity, dimension…).\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function BtnGroup<T extends string>({\r\n value,\r\n options,\r\n onChange,\r\n label,\r\n}: {\r\n value: T\r\n options: { value: T; label: string }[]\r\n onChange: (v: T) => void\r\n /** Rótulo opcional à esquerda (ex: \"Situação\"), com espaçamento próprio. */\r\n label?: string\r\n}) {\r\n return (\r\n <div className=\"flex items-center gap-3 flex-wrap\">\r\n {label && (\r\n <span className=\"text-[10px] font-black uppercase tracking-wider text-slate-400 shrink-0 whitespace-nowrap leading-normal\">{label}</span>\r\n )}\r\n <div className=\"flex gap-1.5 flex-wrap\">\r\n {options.map((o) => (\r\n <Btn key={o.value} active={value === o.value} onClick={() => onChange(o.value)}>\r\n {o.label}\r\n </Btn>\r\n ))}\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Estado({\r\n carregando,\r\n erro,\r\n vazio,\r\n labels,\r\n}: {\r\n carregando?: boolean\r\n erro?: boolean\r\n vazio?: boolean\r\n labels?: { loading?: string; error?: string; empty?: string }\r\n}) {\r\n if (carregando) return <div className=\"py-10 text-center text-sm text-slate-400\">{labels?.loading ?? 'Loading…'}</div>\r\n if (erro) return <div className=\"py-10 text-center text-sm text-incumprido\">{labels?.error ?? 'Failed to load'}</div>\r\n if (vazio) return <div className=\"py-10 text-center text-sm text-slate-400\">{labels?.empty ?? 'No data'}</div>\r\n return null\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Modal({ open, onClose, title, children, wide }: { open: boolean; onClose: () => void; title: ReactNode; children: ReactNode; wide?: boolean }) {\r\n if (!open) return null\r\n return (\r\n <div\r\n onClick={(e) => {\r\n if (e.target === e.currentTarget) onClose()\r\n }}\r\n className=\"fixed inset-0 bg-[rgba(20,30,50,0.45)] z-50 flex items-center justify-center p-4\"\r\n >\r\n {/*\r\n `wide` acompanha o ecrã em vez de parar num número fixo.\r\n\r\n 1200px era estreito num ecrã grande: um modal com mapa e tabela lado a\r\n lado ficava com a tabela cortada e o mapa pequeno, enquanto meio ecrã\r\n ficava vazio. O tecto de 1600px existe para o texto não ficar a\r\n atravessar o monitor todo, e os 95vw para o modal nunca encostar às\r\n margens.\r\n */}\r\n <div className={`bg-white dark:bg-slate-900 rounded-xl w-full ${wide ? 'max-w-[min(1600px,95vw)]' : 'max-w-[720px]'} max-h-[85vh] flex flex-col shadow-2xl`}>\r\n <div className=\"flex items-center justify-between gap-4 px-5 py-4 border-b border-slate-200 dark:border-slate-700\">\r\n <div className=\"text-[15px] font-bold text-proef-navy dark:text-slate-100\">{title}</div>\r\n <button onClick={onClose} aria-label=\"Close\" className=\"text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-100\">\r\n <IconX className=\"w-5 h-5\" />\r\n </button>\r\n </div>\r\n <div className=\"overflow-auto thin-scroll px-5 py-4\">{children}</div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\n// ─── Filtros ────────────────────────────────────────────────────────────────\r\n// O kit não tinha campos de formulário, e cada ecrã acabava a repetir o mesmo\r\n// <label> com <select> e as mesmas classes. Estes três resolvem a barra de\r\n// filtros que praticamente todos os ecrãs de listagem têm.\r\n\r\n/**\r\n * Rótulo pequeno em maiúsculas por cima do campo.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function Campo({ label, children, className = '' }: { label: string; children: ReactNode; className?: string }) {\r\n return (\r\n <label className={`flex flex-col gap-1 ${className}`}>\r\n <span className=\"text-[10px] font-black uppercase tracking-wider text-slate-400 leading-normal\">{label}</span>\r\n {children}\r\n </label>\r\n )\r\n}\r\n\r\n/**\r\n * Select com opção vazia à cabeça.\r\n *\r\n * A opção vazia é obrigatória e não opcional: sem ela não há como desfazer uma\r\n * escolha, e o utilizador fica preso ao primeiro valor que carregou.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function CampoSelect({\r\n label,\r\n value,\r\n options,\r\n onChange,\r\n placeholderVazio,\r\n disabled = false,\r\n className = '',\r\n}: {\r\n label: string\r\n value: string\r\n options: readonly string[] | readonly { value: string; label: string }[]\r\n onChange: (v: string) => void\r\n placeholderVazio: string\r\n disabled?: boolean\r\n className?: string\r\n}) {\r\n const normalizadas = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o))\r\n return (\r\n <Campo label={label} className={className}>\r\n {/* Mesma lista suspensa em todos os portais (Escolha), em vez do <select> nativo. */}\r\n <Escolha\r\n valor={value}\r\n opcoes={normalizadas.map((o) => ({ valor: o.value, rotulo: o.label }))}\r\n onMudar={onChange}\r\n rotulo={label}\r\n opcaoVazia={placeholderVazio}\r\n placeholder={placeholderVazio}\r\n disabled={disabled}\r\n className=\"min-w-[10rem] max-w-56\"\r\n />\r\n </Campo>\r\n )\r\n}\r\n\r\n/**\r\n * Campo de pesquisa que só dispara ao confirmar.\r\n *\r\n * Deliberadamente NÃO pesquisa a cada tecla: sobre dezenas de milhares de\r\n * registos isso seria um pedido por letra. Confirma-se com Enter ou ao sair do\r\n * campo.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function CampoPesquisa({\r\n label,\r\n valorInicial = '',\r\n onConfirmar,\r\n placeholder,\r\n className = '',\r\n}: {\r\n label: string\r\n valorInicial?: string\r\n onConfirmar: (v: string | undefined) => void\r\n placeholder?: string\r\n className?: string\r\n}) {\r\n const confirmar = (v: string): void => onConfirmar(v.trim() || undefined)\r\n return (\r\n <Campo label={label} className={className}>\r\n <input\r\n type=\"search\"\r\n defaultValue={valorInicial}\r\n placeholder={placeholder}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter') confirmar((e.target as HTMLInputElement).value)\r\n }}\r\n onBlur={(e) => confirmar(e.target.value)}\r\n className=\"h-8 border border-slate-300 rounded-lg px-2.5 text-sm w-64 bg-white dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100 dark:placeholder:text-slate-500\"\r\n />\r\n </Campo>\r\n )\r\n}\r\n\r\n/**\r\n * Caixa de interruptor com rótulo à direita, alinhada com os campos ao lado.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function CampoInterruptor({\r\n label,\r\n checked,\r\n onChange,\r\n}: {\r\n label: string\r\n checked: boolean\r\n onChange: (v: boolean) => void\r\n}) {\r\n return (\r\n <label className=\"flex items-center gap-2 text-sm pb-1.5 cursor-pointer select-none\">\r\n <input type=\"checkbox\" checked={checked} onChange={(e) => onChange(e.target.checked)} />\r\n {label}\r\n </label>\r\n )\r\n}\r\n\r\n/**\r\n * Barra onde os campos de filtro assentam.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function BarraFiltros({ children }: { children: ReactNode }) {\r\n return (\r\n <div className=\"bg-white border border-slate-200 dark:bg-slate-900 dark:border-slate-700 rounded-xl px-4 py-3 flex flex-wrap items-end gap-3\">\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// ─── Listas ─────────────────────────────────────────────────────────────────\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport type SeveridadeAlerta = 'critico' | 'grave' | 'atencao' | 'info'\r\n\r\n/**\r\n * Linha de uma lista de alertas.\r\n *\r\n * A severidade está na barra lateral colorida e não no fundo: uma lista de\r\n * cinquenta linhas com fundos saturados é ilegível, e o que se quer é poder\r\n * varrer a coluna da esquerda e saber onde parar.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function LinhaAlerta({\r\n severidade,\r\n icone,\r\n titulo,\r\n detalhe,\r\n referencia,\r\n onReferenciaClick,\r\n quando,\r\n}: {\r\n severidade: SeveridadeAlerta\r\n icone?: ReactNode\r\n titulo: ReactNode\r\n detalhe?: ReactNode\r\n /** Identificador clicável à direita (ex: número da ordem). */\r\n referencia?: string\r\n onReferenciaClick?: () => void\r\n quando?: ReactNode\r\n}) {\r\n const barras: Record<SeveridadeAlerta, string> = {\r\n critico: 'border-l-incumprido',\r\n grave: 'border-l-orange-500',\r\n atencao: 'border-l-risco',\r\n info: 'border-l-sky-400',\r\n }\r\n const textos: Record<SeveridadeAlerta, string> = {\r\n critico: 'text-incumprido',\r\n grave: 'text-orange-600 dark:text-orange-400',\r\n atencao: 'text-risco',\r\n info: 'text-sky-600 dark:text-sky-400',\r\n }\r\n\r\n return (\r\n <div className={`bg-white border border-slate-200 dark:bg-slate-900 dark:border-y-slate-700 dark:border-r-slate-700 border-l-4 ${barras[severidade]} rounded-lg px-4 py-2.5 flex items-start gap-3`}>\r\n {icone && <span className={`${textos[severidade]} shrink-0 mt-0.5`}>{icone}</span>}\r\n <div className=\"min-w-0 flex-1\">\r\n <div className=\"text-sm font-semibold text-slate-800 dark:text-slate-100\">{titulo}</div>\r\n {detalhe && <div className=\"text-xs text-slate-500 dark:text-slate-400\">{detalhe}</div>}\r\n </div>\r\n {referencia && (\r\n <button\r\n type=\"button\"\r\n onClick={onReferenciaClick}\r\n className=\"text-xs font-mono text-proef-navy dark:text-sky-300 hover:underline shrink-0\"\r\n >\r\n {referencia}\r\n </button>\r\n )}\r\n {quando && <span className=\"text-xs text-slate-400 shrink-0 tabular-nums\">{quando}</span>}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Grelha de pares rótulo/valor, para painéis de detalhe.\r\n *\r\n * Os campos vazios são omitidos por omissão: num registo vindo de várias\r\n * origens, metade dos campos costuma não existir, e uma coluna de travessões\r\n * não informa ninguém. `mostrarVazios` repõe-os quando a ausência é o que\r\n * interessa ver.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function ListaCampos({\r\n campos,\r\n colunas = 2,\r\n mostrarVazios = false,\r\n}: {\r\n campos: { label: string; valor: ReactNode }[]\r\n colunas?: 1 | 2 | 3\r\n mostrarVazios?: boolean\r\n}) {\r\n const vazio = (v: ReactNode): boolean => v == null || v === '' || v === '—'\r\n const visiveis = mostrarVazios ? campos : campos.filter((c) => !vazio(c.valor))\r\n if (!visiveis.length) return null\r\n\r\n const grelha = { 1: '', 2: 'md:grid-cols-2', 3: 'md:grid-cols-3' }[colunas]\r\n\r\n return (\r\n <dl className={`grid ${grelha} gap-x-6 gap-y-1`}>\r\n {visiveis.map((c) => (\r\n <div key={c.label} className=\"flex justify-between gap-3 border-b border-slate-100 dark:border-slate-800 py-1\">\r\n <dt className=\"text-xs text-slate-500 dark:text-slate-400\">{c.label}</dt>\r\n <dd className=\"text-xs font-medium text-slate-800 dark:text-slate-100 text-right\">{c.valor}</dd>\r\n </div>\r\n ))}\r\n </dl>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Ui.js","sources":["../../../../../src/legacy/components/ui/Ui.tsx"],"sourcesContent":["import React, { type ReactNode } from 'react'\r\nimport Escolha from './Escolha'\r\nimport SegmentedControl from '../../../v1/primitives/buttons/SegmentedControl/SegmentedControl'\r\nimport Button from '../../../v1/primitives/buttons/Button/Button'\r\n\r\n/** Visual UI primitives (proef design system). Prefer these over repeating classes. */\r\n\r\n/**\r\n * SLA status accepted by BadgeSla.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport type StatusSla = 'ok' | 'risco' | 'incumprido'\r\n\r\nfunction IconX({ className = '' }: { className?: string }) {\r\n return (\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth={2}\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n className={className}\r\n aria-hidden=\"true\"\r\n >\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\r\n </svg>\r\n )\r\n}\r\n\r\n// Migrado para o V1: o Card é o do V1 (aceita o nome antigo `extra`). Reexportado daqui para o código legacy.\r\nimport { Card } from '../../../v1/primitives/display/Card/Card'\r\nexport { Card }\r\n\r\n/**\r\n * Colour-bordered group — the overview sections in the prototype.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function SectionGroup({\r\n title,\r\n tone = 'accent',\r\n children,\r\n}: {\r\n title: string\r\n tone?: 'accent' | 'ok' | 'blue' | 'red' | 'purple' | 'yellow'\r\n children: ReactNode\r\n}) {\r\n const tones: Record<string, [string, string]> = {\r\n accent: ['border-slate-200 dark:border-slate-700', 'text-proef-navy dark:text-slate-100'],\r\n ok: ['border-ok', 'text-ok'],\r\n blue: ['border-[#4FA8FF]', 'text-[#2E8CE0]'],\r\n red: ['border-incumprido', 'text-incumprido'],\r\n purple: ['border-[#8B5CF6]', 'text-[#7C3AED]'],\r\n yellow: ['border-[#D9A400]', 'text-[#B78400]'],\r\n }\r\n const [borda, texto] = tones[tone]!\r\n return (\r\n <div className={`border-[1.5px] ${borda} rounded-2xl p-4 pb-1 mb-5 bg-[rgba(24,34,56,0.015)] dark:bg-slate-900/40`}>\r\n <div className={`text-[15px] font-bold text-center tracking-wide mb-3.5 ${texto}`}>{title}</div>\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// Migrado para o V1: o Kpi é o do V1 (aceita os nomes antigos `delta`, `bgTint`, `barra`, `rodape`, `denso`;\r\n// `hint` passou de `title` para a camada `data-tip`). Reexportado daqui para o código legacy que o importa.\r\nimport { Kpi, type KpiTone } from '../../../v1/primitives/display/Kpi/Kpi'\r\nexport { Kpi, type KpiTone }\r\n\r\n// Migrado para o V1: o Badge é o do V1 (mesmo nome e API; ganhou `dot` e `icon`). Reexportado daqui para\r\n// o código legacy que o importa deste ficheiro.\r\nimport { Badge, type BadgeTone } from '../../../v1/primitives/display/Badge/Badge'\r\nexport { Badge, type BadgeTone }\r\n\r\n/**\r\n * SLA badge — picks the tone and label from `status`. Labels overridable via `labels`.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function BadgeSla({ status, labels }: { status: StatusSla; labels?: { ok?: string; risco?: string; incumprido?: string } }) {\r\n const tone: BadgeTone = status === 'ok' ? 'ok' : status === 'risco' ? 'risco' : 'incumprido'\r\n const defaults: Record<StatusSla, string> = { ok: 'On time', risco: 'At risk', incumprido: 'Breached' }\r\n const label = labels?.[status] ?? defaults[status]\r\n return <Badge tone={tone}>{label}</Badge>\r\n}\r\n\r\n/** Internal-state chip — prototype colours (ESTADO_INTERNO_COLORS). */\r\nconst CORES_ESTADO_INTERNO: Record<string, [string, string]> = {\r\n 'Concluído': ['#1F8A55', '#E5F6EC'],\r\n 'Aferição': ['#2E6DA4', '#E5F0FA'],\r\n 'Para Tratamento': ['#1868B0', '#E5F0FA'],\r\n 'Aguarda Validação LS': ['#2E6DA4', '#E5F0FA'],\r\n 'Aguarda Produção LS': ['#6E8CAD', '#EDF2F7'],\r\n 'Incompleto/Devolvido': ['#C2255C', '#FBE5EF'],\r\n 'Falta de Documentação': ['#B96E00', '#FCF0DC'],\r\n Ignorar: ['#767C88', '#EEF0F2'],\r\n Cancelado: ['#767C88', '#EEF0F2'],\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function ChipEstadoInterno({ estado }: { estado: string | null }) {\r\n if (!estado) return <span className=\"text-slate-400\">—</span>\r\n const cor = CORES_ESTADO_INTERNO[estado]\r\n if (!cor) return <span>{estado}</span>\r\n return (\r\n <span className=\"px-2 py-0.5 rounded text-[11px] font-medium inline-block\" style={{ color: cor[0], background: cor[1] }}>\r\n {estado}\r\n </span>\r\n )\r\n}\r\n\r\nconst VARIANTE_V1 = { default: 'secondary', accent: 'primary', danger: 'danger', ghost: 'ghost' } as const\r\n\r\n/**\r\n * Migrado para o V1: é o `Button` do V1 (mesmo aspeto); isto só mantém a API antiga.\r\n * @deprecated Legacy — usar `Button` do V1.\r\n */\r\nexport function Btn({\r\n children,\r\n onClick,\r\n variant = 'default',\r\n active = false,\r\n disabled = false,\r\n title,\r\n type = 'button',\r\n}: {\r\n children: ReactNode\r\n onClick?: () => void\r\n variant?: 'default' | 'accent' | 'danger' | 'ghost'\r\n active?: boolean\r\n disabled?: boolean\r\n title?: string\r\n type?: 'button' | 'submit'\r\n}) {\r\n return (\r\n <Button\r\n type={type}\r\n onClick={onClick}\r\n disabled={disabled}\r\n title={title}\r\n variant={VARIANTE_V1[variant]}\r\n // Só o `default` tinha estado ligado; nos outros o `active` não fazia nada.\r\n active={variant === 'default' && active ? true : undefined}\r\n >\r\n {children}\r\n </Button>\r\n )\r\n}\r\n\r\n/**\r\n * Mutually exclusive button group (granularity, dimension…).\r\n * Migrado para o V1: é o `SegmentedControl` do V1; isto só mantém a API antiga.\r\n * @deprecated Legacy — usar `SegmentedControl` do V1.\r\n */\r\nexport function BtnGroup<T extends string>({\r\n value,\r\n options,\r\n onChange,\r\n label,\r\n}: {\r\n value: T\r\n options: { value: T; label: string }[]\r\n onChange: (v: T) => void\r\n /** Rótulo opcional à esquerda (ex: \"Situação\"), com espaçamento próprio. */\r\n label?: string\r\n}) {\r\n return <SegmentedControl<T> value={value} options={options} onChange={onChange} label={label} />\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Estado({\r\n carregando,\r\n erro,\r\n vazio,\r\n labels,\r\n}: {\r\n carregando?: boolean\r\n erro?: boolean\r\n vazio?: boolean\r\n labels?: { loading?: string; error?: string; empty?: string }\r\n}) {\r\n if (carregando) return <div className=\"py-10 text-center text-sm text-slate-400\">{labels?.loading ?? 'Loading…'}</div>\r\n if (erro) return <div className=\"py-10 text-center text-sm text-incumprido\">{labels?.error ?? 'Failed to load'}</div>\r\n if (vazio) return <div className=\"py-10 text-center text-sm text-slate-400\">{labels?.empty ?? 'No data'}</div>\r\n return null\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Modal({ open, onClose, title, children, wide }: { open: boolean; onClose: () => void; title: ReactNode; children: ReactNode; wide?: boolean }) {\r\n if (!open) return null\r\n return (\r\n <div\r\n onClick={(e) => {\r\n if (e.target === e.currentTarget) onClose()\r\n }}\r\n className=\"fixed inset-0 bg-[rgba(20,30,50,0.45)] z-50 flex items-center justify-center p-4\"\r\n >\r\n {/*\r\n `wide` acompanha o ecrã em vez de parar num número fixo.\r\n\r\n 1200px era estreito num ecrã grande: um modal com mapa e tabela lado a\r\n lado ficava com a tabela cortada e o mapa pequeno, enquanto meio ecrã\r\n ficava vazio. O tecto de 1600px existe para o texto não ficar a\r\n atravessar o monitor todo, e os 95vw para o modal nunca encostar às\r\n margens.\r\n */}\r\n <div className={`bg-white dark:bg-slate-900 rounded-xl w-full ${wide ? 'max-w-[min(1600px,95vw)]' : 'max-w-[720px]'} max-h-[85vh] flex flex-col shadow-2xl`}>\r\n <div className=\"flex items-center justify-between gap-4 px-5 py-4 border-b border-slate-200 dark:border-slate-700\">\r\n <div className=\"text-[15px] font-bold text-proef-navy dark:text-slate-100\">{title}</div>\r\n <button onClick={onClose} aria-label=\"Close\" className=\"text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-100\">\r\n <IconX className=\"w-5 h-5\" />\r\n </button>\r\n </div>\r\n <div className=\"overflow-auto thin-scroll px-5 py-4\">{children}</div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\n// ─── Filtros ────────────────────────────────────────────────────────────────\r\n// O kit não tinha campos de formulário, e cada ecrã acabava a repetir o mesmo\r\n// <label> com <select> e as mesmas classes. Estes três resolvem a barra de\r\n// filtros que praticamente todos os ecrãs de listagem têm.\r\n\r\n/**\r\n * Rótulo pequeno em maiúsculas por cima do campo.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function Campo({ label, children, className = '' }: { label: string; children: ReactNode; className?: string }) {\r\n return (\r\n <label className={`flex flex-col gap-1 ${className}`}>\r\n <span className=\"text-[10px] font-black uppercase tracking-wider text-slate-400 leading-normal\">{label}</span>\r\n {children}\r\n </label>\r\n )\r\n}\r\n\r\n/**\r\n * Select com opção vazia à cabeça.\r\n *\r\n * A opção vazia é obrigatória e não opcional: sem ela não há como desfazer uma\r\n * escolha, e o utilizador fica preso ao primeiro valor que carregou.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function CampoSelect({\r\n label,\r\n value,\r\n options,\r\n onChange,\r\n placeholderVazio,\r\n disabled = false,\r\n className = '',\r\n}: {\r\n label: string\r\n value: string\r\n options: readonly string[] | readonly { value: string; label: string }[]\r\n onChange: (v: string) => void\r\n placeholderVazio: string\r\n disabled?: boolean\r\n className?: string\r\n}) {\r\n const normalizadas = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o))\r\n return (\r\n <Campo label={label} className={className}>\r\n {/* Mesma lista suspensa em todos os portais (Escolha), em vez do <select> nativo. */}\r\n <Escolha\r\n valor={value}\r\n opcoes={normalizadas.map((o) => ({ valor: o.value, rotulo: o.label }))}\r\n onMudar={onChange}\r\n rotulo={label}\r\n opcaoVazia={placeholderVazio}\r\n placeholder={placeholderVazio}\r\n disabled={disabled}\r\n className=\"min-w-[10rem] max-w-56\"\r\n />\r\n </Campo>\r\n )\r\n}\r\n\r\n/**\r\n * Campo de pesquisa que só dispara ao confirmar.\r\n *\r\n * Deliberadamente NÃO pesquisa a cada tecla: sobre dezenas de milhares de\r\n * registos isso seria um pedido por letra. Confirma-se com Enter ou ao sair do\r\n * campo.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function CampoPesquisa({\r\n label,\r\n valorInicial = '',\r\n onConfirmar,\r\n placeholder,\r\n className = '',\r\n}: {\r\n label: string\r\n valorInicial?: string\r\n onConfirmar: (v: string | undefined) => void\r\n placeholder?: string\r\n className?: string\r\n}) {\r\n const confirmar = (v: string): void => onConfirmar(v.trim() || undefined)\r\n return (\r\n <Campo label={label} className={className}>\r\n <input\r\n type=\"search\"\r\n defaultValue={valorInicial}\r\n placeholder={placeholder}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter') confirmar((e.target as HTMLInputElement).value)\r\n }}\r\n onBlur={(e) => confirmar(e.target.value)}\r\n className=\"h-8 border border-slate-300 rounded-lg px-2.5 text-sm w-64 bg-white dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100 dark:placeholder:text-slate-500\"\r\n />\r\n </Campo>\r\n )\r\n}\r\n\r\n/**\r\n * Caixa de interruptor com rótulo à direita, alinhada com os campos ao lado.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function CampoInterruptor({\r\n label,\r\n checked,\r\n onChange,\r\n}: {\r\n label: string\r\n checked: boolean\r\n onChange: (v: boolean) => void\r\n}) {\r\n return (\r\n <label className=\"flex items-center gap-2 text-sm pb-1.5 cursor-pointer select-none\">\r\n <input type=\"checkbox\" checked={checked} onChange={(e) => onChange(e.target.checked)} />\r\n {label}\r\n </label>\r\n )\r\n}\r\n\r\n/**\r\n * Barra onde os campos de filtro assentam.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function BarraFiltros({ children }: { children: ReactNode }) {\r\n return (\r\n <div className=\"bg-white border border-slate-200 dark:bg-slate-900 dark:border-slate-700 rounded-xl px-4 py-3 flex flex-wrap items-end gap-3\">\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// ─── Listas ─────────────────────────────────────────────────────────────────\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport type SeveridadeAlerta = 'critico' | 'grave' | 'atencao' | 'info'\r\n\r\n/**\r\n * Linha de uma lista de alertas.\r\n *\r\n * A severidade está na barra lateral colorida e não no fundo: uma lista de\r\n * cinquenta linhas com fundos saturados é ilegível, e o que se quer é poder\r\n * varrer a coluna da esquerda e saber onde parar.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function LinhaAlerta({\r\n severidade,\r\n icone,\r\n titulo,\r\n detalhe,\r\n referencia,\r\n onReferenciaClick,\r\n quando,\r\n}: {\r\n severidade: SeveridadeAlerta\r\n icone?: ReactNode\r\n titulo: ReactNode\r\n detalhe?: ReactNode\r\n /** Identificador clicável à direita (ex: número da ordem). */\r\n referencia?: string\r\n onReferenciaClick?: () => void\r\n quando?: ReactNode\r\n}) {\r\n const barras: Record<SeveridadeAlerta, string> = {\r\n critico: 'border-l-incumprido',\r\n grave: 'border-l-orange-500',\r\n atencao: 'border-l-risco',\r\n info: 'border-l-sky-400',\r\n }\r\n const textos: Record<SeveridadeAlerta, string> = {\r\n critico: 'text-incumprido',\r\n grave: 'text-orange-600 dark:text-orange-400',\r\n atencao: 'text-risco',\r\n info: 'text-sky-600 dark:text-sky-400',\r\n }\r\n\r\n return (\r\n <div className={`bg-white border border-slate-200 dark:bg-slate-900 dark:border-y-slate-700 dark:border-r-slate-700 border-l-4 ${barras[severidade]} rounded-lg px-4 py-2.5 flex items-start gap-3`}>\r\n {icone && <span className={`${textos[severidade]} shrink-0 mt-0.5`}>{icone}</span>}\r\n <div className=\"min-w-0 flex-1\">\r\n <div className=\"text-sm font-semibold text-slate-800 dark:text-slate-100\">{titulo}</div>\r\n {detalhe && <div className=\"text-xs text-slate-500 dark:text-slate-400\">{detalhe}</div>}\r\n </div>\r\n {referencia && (\r\n <button\r\n type=\"button\"\r\n onClick={onReferenciaClick}\r\n className=\"text-xs font-mono text-proef-navy dark:text-sky-300 hover:underline shrink-0\"\r\n >\r\n {referencia}\r\n </button>\r\n )}\r\n {quando && <span className=\"text-xs text-slate-400 shrink-0 tabular-nums\">{quando}</span>}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Grelha de pares rótulo/valor, para painéis de detalhe.\r\n *\r\n * Os campos vazios são omitidos por omissão: num registo vindo de várias\r\n * origens, metade dos campos costuma não existir, e uma coluna de travessões\r\n * não informa ninguém. `mostrarVazios` repõe-os quando a ausência é o que\r\n * interessa ver.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function ListaCampos({\r\n campos,\r\n colunas = 2,\r\n mostrarVazios = false,\r\n}: {\r\n campos: { label: string; valor: ReactNode }[]\r\n colunas?: 1 | 2 | 3\r\n mostrarVazios?: boolean\r\n}) {\r\n const vazio = (v: ReactNode): boolean => v == null || v === '' || v === '—'\r\n const visiveis = mostrarVazios ? campos : campos.filter((c) => !vazio(c.valor))\r\n if (!visiveis.length) return null\r\n\r\n const grelha = { 1: '', 2: 'md:grid-cols-2', 3: 'md:grid-cols-3' }[colunas]\r\n\r\n return (\r\n <dl className={`grid ${grelha} gap-x-6 gap-y-1`}>\r\n {visiveis.map((c) => (\r\n <div key={c.label} className=\"flex justify-between gap-3 border-b border-slate-100 dark:border-slate-800 py-1\">\r\n <dt className=\"text-xs text-slate-500 dark:text-slate-400\">{c.label}</dt>\r\n <dd className=\"text-xs font-medium text-slate-800 dark:text-slate-100 text-right\">{c.valor}</dd>\r\n </div>\r\n ))}\r\n </dl>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -1,48 +1,9 @@
1
- import { useState, useRef, useCallback, useLayoutEffect, useEffect } from "react";
2
- const CLS_PAINEL = "fixed z-[60] min-w-[10rem] max-h-72 overflow-auto rounded-xl border border-slate-200 bg-white py-1 text-sm shadow-lg dark:border-slate-700 dark:bg-slate-900";
3
- const clsItem = (ativo, perigo = false) => `w-full flex items-center gap-2.5 px-3 py-2 text-left cursor-pointer select-none ${perigo ? "text-red-600 dark:text-red-300" : "text-slate-700 dark:text-slate-200"} ${ativo ? "bg-slate-100 dark:bg-slate-800" : "bg-transparent hover:bg-slate-50 dark:hover:bg-slate-800"}`;
1
+ import { PANEL_CLASS, useFloatingPanel, itemClass } from "../../../v1/capabilities/overlay/useFloatingPanel.js";
2
+ const CLS_PAINEL = PANEL_CLASS;
3
+ const clsItem = itemClass;
4
4
  function usePainelFlutuante(alinhar = "esquerda") {
5
- const [aberto, setAberto] = useState(false);
6
- const refBotao = useRef(null);
7
- const refPainel = useRef(null);
8
- const [pos, setPos] = useState(null);
9
- const fechar = useCallback(() => setAberto(false), []);
10
- useLayoutEffect(() => {
11
- if (!aberto || !refBotao.current) {
12
- setPos(null);
13
- return;
14
- }
15
- const r = refBotao.current.getBoundingClientRect();
16
- const altura = refPainel.current?.offsetHeight ?? 240;
17
- const horizontal = alinhar === "direita" ? { right: window.innerWidth - r.right } : { left: r.left };
18
- const cabeEmBaixo = r.bottom + 4 + altura <= window.innerHeight - 8;
19
- setPos({
20
- ...horizontal,
21
- minWidth: r.width,
22
- ...cabeEmBaixo || r.top < altura ? { top: r.bottom + 4 } : { bottom: window.innerHeight - r.top + 4 }
23
- });
24
- }, [aberto, alinhar]);
25
- useEffect(() => {
26
- if (!aberto) return;
27
- const clique = (e) => {
28
- const t = e.target;
29
- if (!refBotao.current?.contains(t) && !refPainel.current?.contains(t)) setAberto(false);
30
- };
31
- const scroll = (e) => {
32
- if (!refPainel.current?.contains(e.target)) setAberto(false);
33
- };
34
- const redimensionar = () => setAberto(false);
35
- document.addEventListener("mousedown", clique);
36
- window.addEventListener("scroll", scroll, true);
37
- window.addEventListener("resize", redimensionar);
38
- return () => {
39
- document.removeEventListener("mousedown", clique);
40
- window.removeEventListener("scroll", scroll, true);
41
- window.removeEventListener("resize", redimensionar);
42
- };
43
- }, [aberto]);
44
- const estilo = pos ?? { top: 0, left: 0, visibility: "hidden" };
45
- return { aberto, setAberto, fechar, refBotao, refPainel, estilo };
5
+ const p = useFloatingPanel(alinhar === "direita" ? "right" : "left");
6
+ return { aberto: p.open, setAberto: p.setOpen, fechar: p.close, refBotao: p.triggerRef, refPainel: p.panelRef, estilo: p.style };
46
7
  }
47
8
  export {
48
9
  CLS_PAINEL,
@@ -1 +1 @@
1
- {"version":3,"file":"painelFlutuante.js","sources":["../../../../../src/legacy/components/ui/painelFlutuante.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from 'react'\r\n\r\n/**\r\n * Aspeto comum das listas suspensas (Escolha e MenuAcoes): cartão arredondado com sombra, nos dois temas.\r\n * Um só sítio para as duas ficarem iguais.\r\n */\r\nexport const CLS_PAINEL =\r\n 'fixed z-[60] min-w-[10rem] max-h-72 overflow-auto rounded-xl border border-slate-200 bg-white py-1 text-sm shadow-lg dark:border-slate-700 dark:bg-slate-900'\r\n/** Item de uma lista suspensa; `ativo` = o que o teclado tem selecionado. */\r\nexport const clsItem = (ativo: boolean, perigo = false) =>\r\n `w-full flex items-center gap-2.5 px-3 py-2 text-left cursor-pointer select-none ${\r\n perigo ? 'text-red-600 dark:text-red-300' : 'text-slate-700 dark:text-slate-200'\r\n } ${ativo ? 'bg-slate-100 dark:bg-slate-800' : 'bg-transparent hover:bg-slate-50 dark:hover:bg-slate-800'}`\r\n\r\n/**\r\n * Painel flutuante preso a um botão: posição fixa (não é cortado por tabelas ou modais com scroll), abre para\r\n * cima quando não cabe por baixo, fecha ao clicar fora, com Esc, ao fazer scroll fora dele ou ao redimensionar.\r\n */\r\nexport function usePainelFlutuante(alinhar: 'esquerda' | 'direita' = 'esquerda') {\r\n const [aberto, setAberto] = useState(false)\r\n const refBotao = useRef<HTMLButtonElement>(null)\r\n const refPainel = useRef<HTMLDivElement>(null)\r\n const [pos, setPos] = useState<CSSProperties | null>(null)\r\n\r\n const fechar = useCallback(() => setAberto(false), [])\r\n\r\n useLayoutEffect(() => {\r\n if (!aberto || !refBotao.current) {\r\n setPos(null)\r\n return\r\n }\r\n const r = refBotao.current.getBoundingClientRect()\r\n const altura = refPainel.current?.offsetHeight ?? 240\r\n const horizontal = alinhar === 'direita' ? { right: window.innerWidth - r.right } : { left: r.left }\r\n const cabeEmBaixo = r.bottom + 4 + altura <= window.innerHeight - 8\r\n setPos({\r\n ...horizontal,\r\n minWidth: r.width,\r\n ...(cabeEmBaixo || r.top < altura ? { top: r.bottom + 4 } : { bottom: window.innerHeight - r.top + 4 }),\r\n })\r\n }, [aberto, alinhar])\r\n\r\n useEffect(() => {\r\n if (!aberto) return\r\n const clique = (e: MouseEvent) => {\r\n const t = e.target as Node\r\n if (!refBotao.current?.contains(t) && !refPainel.current?.contains(t)) setAberto(false)\r\n }\r\n const scroll = (e: Event) => {\r\n if (!refPainel.current?.contains(e.target as Node)) setAberto(false)\r\n }\r\n const redimensionar = () => setAberto(false)\r\n document.addEventListener('mousedown', clique)\r\n window.addEventListener('scroll', scroll, true)\r\n window.addEventListener('resize', redimensionar)\r\n return () => {\r\n document.removeEventListener('mousedown', clique)\r\n window.removeEventListener('scroll', scroll, true)\r\n window.removeEventListener('resize', redimensionar)\r\n }\r\n }, [aberto])\r\n\r\n // Antes de medir, o painel desenha-se invisível (para se saber a altura).\r\n const estilo: CSSProperties = pos ?? { top: 0, left: 0, visibility: 'hidden' }\r\n return { aberto, setAberto, fechar, refBotao, refPainel, estilo }\r\n}\r\n"],"names":[],"mappings":";AAMO,MAAM,aACX;AAEK,MAAM,UAAU,CAAC,OAAgB,SAAS,UAC/C,mFACE,SAAS,mCAAmC,oCAC9C,IAAI,QAAQ,mCAAmC,0DAA0D;AAMpG,SAAS,mBAAmB,UAAkC,YAAY;AAC/E,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,WAAW,OAA0B,IAAI;AAC/C,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,CAAC,KAAK,MAAM,IAAI,SAA+B,IAAI;AAEzD,QAAM,SAAS,YAAY,MAAM,UAAU,KAAK,GAAG,CAAA,CAAE;AAErD,kBAAgB,MAAM;AACpB,QAAI,CAAC,UAAU,CAAC,SAAS,SAAS;AAChC,aAAO,IAAI;AACX;AAAA,IACF;AACA,UAAM,IAAI,SAAS,QAAQ,sBAAA;AAC3B,UAAM,SAAS,UAAU,SAAS,gBAAgB;AAClD,UAAM,aAAa,YAAY,YAAY,EAAE,OAAO,OAAO,aAAa,EAAE,MAAA,IAAU,EAAE,MAAM,EAAE,KAAA;AAC9F,UAAM,cAAc,EAAE,SAAS,IAAI,UAAU,OAAO,cAAc;AAClE,WAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU,EAAE;AAAA,MACZ,GAAI,eAAe,EAAE,MAAM,SAAS,EAAE,KAAK,EAAE,SAAS,EAAA,IAAM,EAAE,QAAQ,OAAO,cAAc,EAAE,MAAM,EAAA;AAAA,IAAE,CACtG;AAAA,EACH,GAAG,CAAC,QAAQ,OAAO,CAAC;AAEpB,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,SAAS,CAAC,MAAkB;AAChC,YAAM,IAAI,EAAE;AACZ,UAAI,CAAC,SAAS,SAAS,SAAS,CAAC,KAAK,CAAC,UAAU,SAAS,SAAS,CAAC,aAAa,KAAK;AAAA,IACxF;AACA,UAAM,SAAS,CAAC,MAAa;AAC3B,UAAI,CAAC,UAAU,SAAS,SAAS,EAAE,MAAc,aAAa,KAAK;AAAA,IACrE;AACA,UAAM,gBAAgB,MAAM,UAAU,KAAK;AAC3C,aAAS,iBAAiB,aAAa,MAAM;AAC7C,WAAO,iBAAiB,UAAU,QAAQ,IAAI;AAC9C,WAAO,iBAAiB,UAAU,aAAa;AAC/C,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,MAAM;AAChD,aAAO,oBAAoB,UAAU,QAAQ,IAAI;AACjD,aAAO,oBAAoB,UAAU,aAAa;AAAA,IACpD;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAGX,QAAM,SAAwB,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,YAAY,SAAA;AACpE,SAAO,EAAE,QAAQ,WAAW,QAAQ,UAAU,WAAW,OAAA;AAC3D;"}
1
+ {"version":3,"file":"painelFlutuante.js","sources":["../../../../../src/legacy/components/ui/painelFlutuante.ts"],"sourcesContent":["import { useFloatingPanel, PANEL_CLASS, itemClass } from '../../../v1/capabilities/overlay/useFloatingPanel'\r\n\r\n/**\r\n * Migrado para o V1 (`useFloatingPanel`): estes nomes ficam para o MenuAcoes e o código legacy.\r\n * @deprecated Legacy — usar o `useFloatingPanel` do V1.\r\n */\r\nexport const CLS_PAINEL = PANEL_CLASS\r\n/** @deprecated Legacy — usar o `itemClass` do V1. */\r\nexport const clsItem = itemClass\r\n\r\n/** @deprecated Legacy — usar o `useFloatingPanel` do V1. */\r\nexport function usePainelFlutuante(alinhar: 'esquerda' | 'direita' = 'esquerda') {\r\n const p = useFloatingPanel(alinhar === 'direita' ? 'right' : 'left')\r\n return { aberto: p.open, setAberto: p.setOpen, fechar: p.close, refBotao: p.triggerRef, refPainel: p.panelRef, estilo: p.style }\r\n}\r\n"],"names":[],"mappings":";AAMO,MAAM,aAAa;AAEnB,MAAM,UAAU;AAGhB,SAAS,mBAAmB,UAAkC,YAAY;AAC/E,QAAM,IAAI,iBAAiB,YAAY,YAAY,UAAU,MAAM;AACnE,SAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,EAAE,SAAS,QAAQ,EAAE,OAAO,UAAU,EAAE,YAAY,WAAW,EAAE,UAAU,QAAQ,EAAE,MAAA;AAC3H;"}
@@ -0,0 +1,52 @@
1
+ import { useState, useRef, useCallback, useLayoutEffect, useEffect } from "react";
2
+ const PANEL_CLASS = "fixed z-[60] min-w-[10rem] max-h-72 overflow-auto rounded-xl border border-slate-200 bg-white py-1 text-sm shadow-lg dark:border-slate-700 dark:bg-slate-900";
3
+ const itemClass = (active, danger = false) => `w-full flex items-center gap-2.5 px-3 py-2 text-left cursor-pointer select-none ${danger ? "text-red-600 dark:text-red-300" : "text-slate-700 dark:text-slate-200"} ${active ? "bg-slate-100 dark:bg-slate-800" : "bg-transparent hover:bg-slate-50 dark:hover:bg-slate-800"}`;
4
+ function useFloatingPanel(align = "left") {
5
+ const [open, setOpen] = useState(false);
6
+ const triggerRef = useRef(null);
7
+ const panelRef = useRef(null);
8
+ const [pos, setPos] = useState(null);
9
+ const close = useCallback(() => setOpen(false), []);
10
+ useLayoutEffect(() => {
11
+ if (!open || !triggerRef.current) {
12
+ setPos(null);
13
+ return;
14
+ }
15
+ const r = triggerRef.current.getBoundingClientRect();
16
+ const height = panelRef.current?.offsetHeight ?? 240;
17
+ const horizontal = align === "right" ? { right: window.innerWidth - r.right } : { left: r.left };
18
+ const fitsBelow = r.bottom + 4 + height <= window.innerHeight - 8;
19
+ setPos({
20
+ ...horizontal,
21
+ minWidth: r.width,
22
+ ...fitsBelow || r.top < height ? { top: r.bottom + 4 } : { bottom: window.innerHeight - r.top + 4 }
23
+ });
24
+ }, [open, align]);
25
+ useEffect(() => {
26
+ if (!open) return;
27
+ const click = (e) => {
28
+ const t = e.target;
29
+ if (!triggerRef.current?.contains(t) && !panelRef.current?.contains(t)) setOpen(false);
30
+ };
31
+ const scroll = (e) => {
32
+ if (!panelRef.current?.contains(e.target)) setOpen(false);
33
+ };
34
+ const resize = () => setOpen(false);
35
+ document.addEventListener("mousedown", click);
36
+ window.addEventListener("scroll", scroll, true);
37
+ window.addEventListener("resize", resize);
38
+ return () => {
39
+ document.removeEventListener("mousedown", click);
40
+ window.removeEventListener("scroll", scroll, true);
41
+ window.removeEventListener("resize", resize);
42
+ };
43
+ }, [open]);
44
+ const style = pos ?? { top: 0, left: 0, visibility: "hidden" };
45
+ return { open, setOpen, close, triggerRef, panelRef, style };
46
+ }
47
+ export {
48
+ PANEL_CLASS,
49
+ itemClass,
50
+ useFloatingPanel
51
+ };
52
+ //# sourceMappingURL=useFloatingPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFloatingPanel.js","sources":["../../../../../src/v1/capabilities/overlay/useFloatingPanel.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from 'react'\r\n\r\n/**\r\n * Aspeto comum das listas suspensas (SelectInput, MenuAcoes): cartão arredondado com sombra, nos dois temas.\r\n * Um só sítio para ficarem iguais.\r\n */\r\nexport const PANEL_CLASS =\r\n 'fixed z-[60] min-w-[10rem] max-h-72 overflow-auto rounded-xl border border-slate-200 bg-white py-1 text-sm shadow-lg dark:border-slate-700 dark:bg-slate-900'\r\n\r\n/** Item de uma lista suspensa; `active` = o que o teclado tem selecionado. */\r\nexport const itemClass = (active: boolean, danger = false) =>\r\n `w-full flex items-center gap-2.5 px-3 py-2 text-left cursor-pointer select-none ${\r\n danger ? 'text-red-600 dark:text-red-300' : 'text-slate-700 dark:text-slate-200'\r\n } ${active ? 'bg-slate-100 dark:bg-slate-800' : 'bg-transparent hover:bg-slate-50 dark:hover:bg-slate-800'}`\r\n\r\n/**\r\n * Painel flutuante preso a um botão: posição fixa (não é cortado por tabelas ou janelas com scroll), abre para\r\n * cima quando não cabe por baixo, fecha ao clicar fora, ao fazer scroll fora dele ou ao redimensionar.\r\n */\r\nexport function useFloatingPanel(align: 'left' | 'right' = 'left') {\r\n const [open, setOpen] = useState(false)\r\n const triggerRef = useRef<HTMLButtonElement>(null)\r\n const panelRef = useRef<HTMLDivElement>(null)\r\n const [pos, setPos] = useState<CSSProperties | null>(null)\r\n\r\n const close = useCallback(() => setOpen(false), [])\r\n\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) {\r\n setPos(null)\r\n return\r\n }\r\n const r = triggerRef.current.getBoundingClientRect()\r\n const height = panelRef.current?.offsetHeight ?? 240\r\n const horizontal = align === 'right' ? { right: window.innerWidth - r.right } : { left: r.left }\r\n const fitsBelow = r.bottom + 4 + height <= window.innerHeight - 8\r\n setPos({\r\n ...horizontal,\r\n minWidth: r.width,\r\n ...(fitsBelow || r.top < height ? { top: r.bottom + 4 } : { bottom: window.innerHeight - r.top + 4 }),\r\n })\r\n }, [open, align])\r\n\r\n useEffect(() => {\r\n if (!open) return\r\n const click = (e: MouseEvent) => {\r\n const t = e.target as Node\r\n if (!triggerRef.current?.contains(t) && !panelRef.current?.contains(t)) setOpen(false)\r\n }\r\n const scroll = (e: Event) => {\r\n if (!panelRef.current?.contains(e.target as Node)) setOpen(false)\r\n }\r\n const resize = () => setOpen(false)\r\n document.addEventListener('mousedown', click)\r\n window.addEventListener('scroll', scroll, true)\r\n window.addEventListener('resize', resize)\r\n return () => {\r\n document.removeEventListener('mousedown', click)\r\n window.removeEventListener('scroll', scroll, true)\r\n window.removeEventListener('resize', resize)\r\n }\r\n }, [open])\r\n\r\n // Antes de medir, o painel desenha-se invisível (para se saber a altura).\r\n const style: CSSProperties = pos ?? { top: 0, left: 0, visibility: 'hidden' }\r\n return { open, setOpen, close, triggerRef, panelRef, style }\r\n}\r\n"],"names":[],"mappings":";AAMO,MAAM,cACX;AAGK,MAAM,YAAY,CAAC,QAAiB,SAAS,UAClD,mFACE,SAAS,mCAAmC,oCAC9C,IAAI,SAAS,mCAAmC,0DAA0D;AAMrG,SAAS,iBAAiB,QAA0B,QAAQ;AACjE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,aAAa,OAA0B,IAAI;AACjD,QAAM,WAAW,OAAuB,IAAI;AAC5C,QAAM,CAAC,KAAK,MAAM,IAAI,SAA+B,IAAI;AAEzD,QAAM,QAAQ,YAAY,MAAM,QAAQ,KAAK,GAAG,CAAA,CAAE;AAElD,kBAAgB,MAAM;AACpB,QAAI,CAAC,QAAQ,CAAC,WAAW,SAAS;AAChC,aAAO,IAAI;AACX;AAAA,IACF;AACA,UAAM,IAAI,WAAW,QAAQ,sBAAA;AAC7B,UAAM,SAAS,SAAS,SAAS,gBAAgB;AACjD,UAAM,aAAa,UAAU,UAAU,EAAE,OAAO,OAAO,aAAa,EAAE,MAAA,IAAU,EAAE,MAAM,EAAE,KAAA;AAC1F,UAAM,YAAY,EAAE,SAAS,IAAI,UAAU,OAAO,cAAc;AAChE,WAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU,EAAE;AAAA,MACZ,GAAI,aAAa,EAAE,MAAM,SAAS,EAAE,KAAK,EAAE,SAAS,EAAA,IAAM,EAAE,QAAQ,OAAO,cAAc,EAAE,MAAM,EAAA;AAAA,IAAE,CACpG;AAAA,EACH,GAAG,CAAC,MAAM,KAAK,CAAC;AAEhB,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,CAAC,MAAkB;AAC/B,YAAM,IAAI,EAAE;AACZ,UAAI,CAAC,WAAW,SAAS,SAAS,CAAC,KAAK,CAAC,SAAS,SAAS,SAAS,CAAC,WAAW,KAAK;AAAA,IACvF;AACA,UAAM,SAAS,CAAC,MAAa;AAC3B,UAAI,CAAC,SAAS,SAAS,SAAS,EAAE,MAAc,WAAW,KAAK;AAAA,IAClE;AACA,UAAM,SAAS,MAAM,QAAQ,KAAK;AAClC,aAAS,iBAAiB,aAAa,KAAK;AAC5C,WAAO,iBAAiB,UAAU,QAAQ,IAAI;AAC9C,WAAO,iBAAiB,UAAU,MAAM;AACxC,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,KAAK;AAC/C,aAAO,oBAAoB,UAAU,QAAQ,IAAI;AACjD,aAAO,oBAAoB,UAAU,MAAM;AAAA,IAC7C;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAGT,QAAM,QAAuB,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,YAAY,SAAA;AACnE,SAAO,EAAE,MAAM,SAAS,OAAO,YAAY,UAAU,MAAA;AACvD;"}
@@ -0,0 +1,15 @@
1
+ import { useState, useEffect } from "react";
2
+ function useIsDark() {
3
+ const [isDark, setIsDark] = useState(() => typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
4
+ useEffect(() => {
5
+ const el = document.documentElement;
6
+ const obs = new MutationObserver(() => setIsDark(el.classList.contains("dark")));
7
+ obs.observe(el, { attributes: true, attributeFilter: ["class"] });
8
+ return () => obs.disconnect();
9
+ }, []);
10
+ return isDark;
11
+ }
12
+ export {
13
+ useIsDark
14
+ };
15
+ //# sourceMappingURL=useIsDark.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useIsDark.js","sources":["../../../../../src/v1/capabilities/theme/useIsDark.ts"],"sourcesContent":["import { useEffect, useState } from 'react'\r\n\r\n/**\r\n * Se a app está em escuro agora (a classe `.dark` no `<html>`), e muda quando o\r\n * tema muda.\r\n *\r\n * Para o que não pode usar o `dark:` do Tailwind e precisa das cores em\r\n * JavaScript: gráficos, `<iframe>`, SVG com cores por props. O componente volta\r\n * a desenhar quando o tema muda.\r\n */\r\nexport function useIsDark(): boolean {\r\n const [isDark, setIsDark] = useState(() => typeof document !== 'undefined' && document.documentElement.classList.contains('dark'))\r\n useEffect(() => {\r\n const el = document.documentElement\r\n const obs = new MutationObserver(() => setIsDark(el.classList.contains('dark')))\r\n obs.observe(el, { attributes: true, attributeFilter: ['class'] })\r\n return () => obs.disconnect()\r\n }, [])\r\n return isDark\r\n}\r\n\r\nexport default useIsDark\r\n"],"names":[],"mappings":";AAUO,SAAS,YAAqB;AACnC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,MAAM,OAAO,aAAa,eAAe,SAAS,gBAAgB,UAAU,SAAS,MAAM,CAAC;AACjI,YAAU,MAAM;AACd,UAAM,KAAK,SAAS;AACpB,UAAM,MAAM,IAAI,iBAAiB,MAAM,UAAU,GAAG,UAAU,SAAS,MAAM,CAAC,CAAC;AAC/E,QAAI,QAAQ,IAAI,EAAE,YAAY,MAAM,iBAAiB,CAAC,OAAO,GAAG;AAChE,WAAO,MAAM,IAAI,WAAA;AAAA,EACnB,GAAG,CAAA,CAAE;AACL,SAAO;AACT;"}
@@ -0,0 +1,66 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ const THEME_STORAGE_KEY = "theme";
3
+ const LEGACY_KEY = "proef-theme";
4
+ const isTheme = (v) => v === "light" || v === "dark" || v === "system";
5
+ function readStored() {
6
+ try {
7
+ const v = localStorage.getItem(THEME_STORAGE_KEY);
8
+ if (isTheme(v)) return v;
9
+ const old = localStorage.getItem(LEGACY_KEY);
10
+ return isTheme(old) ? old : null;
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function persist(theme) {
16
+ try {
17
+ localStorage.setItem(THEME_STORAGE_KEY, theme);
18
+ } catch {
19
+ }
20
+ }
21
+ const systemPrefersDark = () => typeof window !== "undefined" && window.matchMedia?.("(prefers-color-scheme: dark)").matches;
22
+ function applyTheme(theme) {
23
+ if (typeof document === "undefined") return;
24
+ document.documentElement.classList.toggle("dark", theme === "dark" || theme === "system" && systemPrefersDark());
25
+ }
26
+ const themeBootScript = `(function(){try{var k='${THEME_STORAGE_KEY}',o='${LEGACY_KEY}',v=localStorage.getItem(k)||localStorage.getItem(o)||'system';if(v==='dark'||(v==='system'&&window.matchMedia('(prefers-color-scheme: dark)').matches))document.documentElement.classList.add('dark')}catch(e){}})();`;
27
+ const ORDER = ["system", "light", "dark"];
28
+ function useTheme() {
29
+ const [theme, setThemeState] = useState(() => readStored() ?? "system");
30
+ useEffect(() => {
31
+ applyTheme(theme);
32
+ }, [theme]);
33
+ useEffect(() => {
34
+ if (theme !== "system" || typeof window === "undefined" || !window.matchMedia) return;
35
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
36
+ const onChange = () => applyTheme("system");
37
+ mq.addEventListener("change", onChange);
38
+ return () => mq.removeEventListener("change", onChange);
39
+ }, [theme]);
40
+ useEffect(() => {
41
+ const onStorage = (e) => {
42
+ if (e.key === THEME_STORAGE_KEY) setThemeState(readStored() ?? "system");
43
+ };
44
+ window.addEventListener("storage", onStorage);
45
+ return () => window.removeEventListener("storage", onStorage);
46
+ }, []);
47
+ const setTheme = useCallback((t) => {
48
+ persist(t);
49
+ setThemeState(t);
50
+ }, []);
51
+ const cycle = useCallback(() => {
52
+ setThemeState((prev) => {
53
+ const next = ORDER[(ORDER.indexOf(prev) + 1) % ORDER.length];
54
+ persist(next);
55
+ return next;
56
+ });
57
+ }, []);
58
+ return { theme, setTheme, cycle };
59
+ }
60
+ export {
61
+ THEME_STORAGE_KEY,
62
+ applyTheme,
63
+ themeBootScript,
64
+ useTheme
65
+ };
66
+ //# sourceMappingURL=useTheme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTheme.js","sources":["../../../../../src/v1/capabilities/theme/useTheme.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react'\r\n\r\nexport type Theme = 'light' | 'dark' | 'system'\r\n\r\n/** Chave da escolha no localStorage. A mesma nos portais (Tickets, AuditoriasIT). */\r\nexport const THEME_STORAGE_KEY = 'theme'\r\n/** Chave antiga do `useTheme` legacy: lida só quando a nova ainda não existe. */\r\nconst LEGACY_KEY = 'proef-theme'\r\n\r\nconst isTheme = (v: string | null): v is Theme => v === 'light' || v === 'dark' || v === 'system'\r\n\r\n/** A escolha guardada, ou `null` se não houver (ou o storage estiver bloqueado). */\r\nfunction readStored(): Theme | null {\r\n try {\r\n const v = localStorage.getItem(THEME_STORAGE_KEY)\r\n if (isTheme(v)) return v\r\n const old = localStorage.getItem(LEGACY_KEY)\r\n return isTheme(old) ? old : null\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\nfunction persist(theme: Theme) {\r\n try {\r\n localStorage.setItem(THEME_STORAGE_KEY, theme)\r\n } catch {\r\n // Sem storage (ex: janela privada): o tema aplica-se só nesta sessão.\r\n }\r\n}\r\n\r\nconst systemPrefersDark = () => typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches\r\n\r\n/** Põe ou tira a classe `.dark` no `<html>`: é a ela que os componentes e os tokens reagem. */\r\nexport function applyTheme(theme: Theme) {\r\n if (typeof document === 'undefined') return\r\n document.documentElement.classList.toggle('dark', theme === 'dark' || (theme === 'system' && systemPrefersDark()))\r\n}\r\n\r\n/**\r\n * Script para pôr no `<head>` do `index.html`, antes do CSS: aplica o tema\r\n * guardado antes da primeira pintura (sem o clarão branco ao abrir em escuro).\r\n *\r\n * O build também o põe em `dist/theme-boot.js`, para a app o injetar no\r\n * `index.html` sem o copiar (ex: num plugin `transformIndexHtml` do Vite).\r\n */\r\nexport const themeBootScript = `(function(){try{var k='${THEME_STORAGE_KEY}',o='${LEGACY_KEY}',v=localStorage.getItem(k)||localStorage.getItem(o)||'system';if(v==='dark'||(v==='system'&&window.matchMedia('(prefers-color-scheme: dark)').matches))document.documentElement.classList.add('dark')}catch(e){}})();`\r\n\r\nconst ORDER: Theme[] = ['system', 'light', 'dark']\r\n\r\n/**\r\n * Tema da app: claro, escuro ou o do sistema. Guarda a escolha (sozinho, a cada\r\n * mudança), aplica a classe `.dark` no `<html>`, segue o sistema em `system` e\r\n * acompanha a escolha feita noutro separador.\r\n *\r\n * const { theme, setTheme } = useTheme()\r\n * <AppShell theme={theme} onThemeChange={setTheme} …/>\r\n *\r\n * Para não haver clarão ao abrir, pôr o `themeBootScript` no `index.html`.\r\n */\r\nexport function useTheme(): { theme: Theme; setTheme: (t: Theme) => void; cycle: () => void } {\r\n const [theme, setThemeState] = useState<Theme>(() => readStored() ?? 'system')\r\n\r\n useEffect(() => {\r\n applyTheme(theme)\r\n }, [theme])\r\n\r\n // Em `system`, segue as mudanças do sistema ao vivo.\r\n useEffect(() => {\r\n if (theme !== 'system' || typeof window === 'undefined' || !window.matchMedia) return\r\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\r\n const onChange = () => applyTheme('system')\r\n mq.addEventListener('change', onChange)\r\n return () => mq.removeEventListener('change', onChange)\r\n }, [theme])\r\n\r\n // A escolha feita noutro separador ou janela.\r\n useEffect(() => {\r\n const onStorage = (e: StorageEvent) => {\r\n if (e.key === THEME_STORAGE_KEY) setThemeState(readStored() ?? 'system')\r\n }\r\n window.addEventListener('storage', onStorage)\r\n return () => window.removeEventListener('storage', onStorage)\r\n }, [])\r\n\r\n const setTheme = useCallback((t: Theme) => {\r\n persist(t)\r\n setThemeState(t)\r\n }, [])\r\n\r\n const cycle = useCallback(() => {\r\n setThemeState((prev) => {\r\n const next = ORDER[(ORDER.indexOf(prev) + 1) % ORDER.length]\r\n persist(next)\r\n return next\r\n })\r\n }, [])\r\n\r\n return { theme, setTheme, cycle }\r\n}\r\n\r\nexport default useTheme\r\n"],"names":[],"mappings":";AAKO,MAAM,oBAAoB;AAEjC,MAAM,aAAa;AAEnB,MAAM,UAAU,CAAC,MAAiC,MAAM,WAAW,MAAM,UAAU,MAAM;AAGzF,SAAS,aAA2B;AAClC,MAAI;AACF,UAAM,IAAI,aAAa,QAAQ,iBAAiB;AAChD,QAAI,QAAQ,CAAC,EAAG,QAAO;AACvB,UAAM,MAAM,aAAa,QAAQ,UAAU;AAC3C,WAAO,QAAQ,GAAG,IAAI,MAAM;AAAA,EAC9B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,QAAQ,OAAc;AAC7B,MAAI;AACF,iBAAa,QAAQ,mBAAmB,KAAK;AAAA,EAC/C,QAAQ;AAAA,EAER;AACF;AAEA,MAAM,oBAAoB,MAAM,OAAO,WAAW,eAAe,OAAO,aAAa,8BAA8B,EAAE;AAG9G,SAAS,WAAW,OAAc;AACvC,MAAI,OAAO,aAAa,YAAa;AACrC,WAAS,gBAAgB,UAAU,OAAO,QAAQ,UAAU,UAAW,UAAU,YAAY,mBAAoB;AACnH;AASO,MAAM,kBAAkB,0BAA0B,iBAAiB,QAAQ,UAAU;AAE5F,MAAM,QAAiB,CAAC,UAAU,SAAS,MAAM;AAY1C,SAAS,WAA8E;AAC5F,QAAM,CAAC,OAAO,aAAa,IAAI,SAAgB,MAAM,WAAA,KAAgB,QAAQ;AAE7E,YAAU,MAAM;AACd,eAAW,KAAK;AAAA,EAClB,GAAG,CAAC,KAAK,CAAC;AAGV,YAAU,MAAM;AACd,QAAI,UAAU,YAAY,OAAO,WAAW,eAAe,CAAC,OAAO,WAAY;AAC/E,UAAM,KAAK,OAAO,WAAW,8BAA8B;AAC3D,UAAM,WAAW,MAAM,WAAW,QAAQ;AAC1C,OAAG,iBAAiB,UAAU,QAAQ;AACtC,WAAO,MAAM,GAAG,oBAAoB,UAAU,QAAQ;AAAA,EACxD,GAAG,CAAC,KAAK,CAAC;AAGV,YAAU,MAAM;AACd,UAAM,YAAY,CAAC,MAAoB;AACrC,UAAI,EAAE,QAAQ,kBAAmB,eAAc,WAAA,KAAgB,QAAQ;AAAA,IACzE;AACA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,MAAM,OAAO,oBAAoB,WAAW,SAAS;AAAA,EAC9D,GAAG,CAAA,CAAE;AAEL,QAAM,WAAW,YAAY,CAAC,MAAa;AACzC,YAAQ,CAAC;AACT,kBAAc,CAAC;AAAA,EACjB,GAAG,CAAA,CAAE;AAEL,QAAM,QAAQ,YAAY,MAAM;AAC9B,kBAAc,CAAC,SAAS;AACtB,YAAM,OAAO,OAAO,MAAM,QAAQ,IAAI,IAAI,KAAK,MAAM,MAAM;AAC3D,cAAQ,IAAI;AACZ,aAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAA,CAAE;AAEL,SAAO,EAAE,OAAO,UAAU,MAAA;AAC5B;"}
@@ -0,0 +1,66 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import Button from "../../../primitives/buttons/Button/Button.js";
3
+ import { SelectInput } from "../../../primitives/forms/SelectInput/SelectInput.js";
4
+ const DEFAULT_PAGINATION_LABELS = {
5
+ page: "Page {page} of {pages}",
6
+ total: "{count} results",
7
+ perPage: "Per page",
8
+ prev: "Previous",
9
+ next: "Next"
10
+ };
11
+ function ChevronLeft() {
12
+ return /* @__PURE__ */ jsx("svg", { "aria-hidden": true, viewBox: "0 0 24 24", width: 13, height: 13, fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" }) });
13
+ }
14
+ function ChevronRight() {
15
+ return /* @__PURE__ */ jsx("svg", { "aria-hidden": true, viewBox: "0 0 24 24", width: 12, height: 12, fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) });
16
+ }
17
+ function Pagination({
18
+ page,
19
+ pages,
20
+ onPageChange,
21
+ total,
22
+ pageSize,
23
+ pageSizeOptions,
24
+ onPageSizeChange,
25
+ labels,
26
+ className = ""
27
+ }) {
28
+ const L = { ...DEFAULT_PAGINATION_LABELS, ...labels };
29
+ const totalPaginas = Math.max(1, pages);
30
+ const opcoes = pageSizeOptions && pageSize != null ? [.../* @__PURE__ */ new Set([...pageSizeOptions, pageSize])].sort((a, b) => a - b) : [];
31
+ return /* @__PURE__ */ jsxs("nav", { "aria-label": L.page.replace("{page}", String(page)).replace("{pages}", String(totalPaginas)), className: `flex flex-wrap items-center justify-between gap-2 ${className}`, children: [
32
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-black uppercase tracking-widest text-muted-foreground", children: [
33
+ total != null && /* @__PURE__ */ jsxs(Fragment, { children: [
34
+ L.total.replace("{count}", total.toLocaleString()),
35
+ " · "
36
+ ] }),
37
+ L.page.replace("{page}", String(page)).replace("{pages}", String(totalPaginas))
38
+ ] }),
39
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
40
+ opcoes.length > 0 && onPageSizeChange && /* @__PURE__ */ jsxs("label", { className: "mr-2 flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest text-muted-foreground", children: [
41
+ L.perPage,
42
+ /* @__PURE__ */ jsx(
43
+ SelectInput,
44
+ {
45
+ "aria-label": L.perPage,
46
+ value: String(pageSize),
47
+ onChange: (e) => onPageSizeChange(Number(e.target.value)),
48
+ className: "w-20 normal-case tracking-normal font-normal",
49
+ children: opcoes.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: n }, n))
50
+ }
51
+ )
52
+ ] }),
53
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", icon: /* @__PURE__ */ jsx(ChevronLeft, {}), disabled: page <= 1, onClick: () => onPageChange(page - 1), children: L.prev }),
54
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", size: "sm", disabled: page >= totalPaginas, onClick: () => onPageChange(page + 1), children: [
55
+ L.next,
56
+ /* @__PURE__ */ jsx(ChevronRight, {})
57
+ ] })
58
+ ] })
59
+ ] });
60
+ }
61
+ export {
62
+ DEFAULT_PAGINATION_LABELS,
63
+ Pagination,
64
+ Pagination as default
65
+ };
66
+ //# sourceMappingURL=Pagination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pagination.js","sources":["../../../../../../src/v1/components/data/Pagination/Pagination.tsx"],"sourcesContent":["import React from 'react'\r\nimport Button from '../../../primitives/buttons/Button/Button'\r\nimport SelectInput from '../../../primitives/forms/SelectInput/SelectInput'\r\n\r\nexport interface PaginationLabels {\r\n /** \"{page}\" e \"{pages}\" são substituídos (ex: \"Página {page} de {pages}\"). */\r\n page: string\r\n /** \"{count}\" é substituído pelo total de registos (ex: \"{count} resultados\"). */\r\n total: string\r\n perPage: string\r\n prev: string\r\n next: string\r\n}\r\n\r\nexport const DEFAULT_PAGINATION_LABELS: PaginationLabels = {\r\n page: 'Page {page} of {pages}',\r\n total: '{count} results',\r\n perPage: 'Per page',\r\n prev: 'Previous',\r\n next: 'Next',\r\n}\r\n\r\nexport interface PaginationProps {\r\n /** Página atual, a contar de 1. */\r\n page: number\r\n /** Número de páginas. */\r\n pages: number\r\n onPageChange: (page: number) => void\r\n /** Total de registos (mostrado à esquerda). */\r\n total?: number\r\n /** Registos por página e as opções do seletor (sem opções, não há seletor). */\r\n pageSize?: number\r\n pageSizeOptions?: number[]\r\n onPageSizeChange?: (size: number) => void\r\n labels?: Partial<PaginationLabels>\r\n className?: string\r\n}\r\n\r\nfunction ChevronLeft() {\r\n return (\r\n <svg aria-hidden viewBox=\"0 0 24 24\" width={13} height={13} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <path d=\"m15 18-6-6 6-6\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction ChevronRight() {\r\n return (\r\n <svg aria-hidden viewBox=\"0 0 24 24\" width={12} height={12} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <path d=\"m9 18 6-6-6-6\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Paginação por baixo de uma lista: \"Página 1 de 4\", o total, quantos por página e\r\n * Anterior/Seguinte. É a mesma que a `DataTable` mostra; usar esta quando a app pagina\r\n * no servidor (a tabela recebe só a página atual).\r\n */\r\nexport default function Pagination({\r\n page,\r\n pages,\r\n onPageChange,\r\n total,\r\n pageSize,\r\n pageSizeOptions,\r\n onPageSizeChange,\r\n labels,\r\n className = '',\r\n}: PaginationProps) {\r\n const L = { ...DEFAULT_PAGINATION_LABELS, ...labels }\r\n const totalPaginas = Math.max(1, pages)\r\n const opcoes = pageSizeOptions && pageSize != null ? [...new Set([...pageSizeOptions, pageSize])].sort((a, b) => a - b) : []\r\n return (\r\n <nav aria-label={L.page.replace('{page}', String(page)).replace('{pages}', String(totalPaginas))} className={`flex flex-wrap items-center justify-between gap-2 ${className}`}>\r\n <span className=\"text-[10px] font-black uppercase tracking-widest text-muted-foreground\">\r\n {total != null && <>{L.total.replace('{count}', total.toLocaleString())} · </>}\r\n {L.page.replace('{page}', String(page)).replace('{pages}', String(totalPaginas))}\r\n </span>\r\n <div className=\"flex flex-wrap items-center gap-1.5\">\r\n {opcoes.length > 0 && onPageSizeChange && (\r\n <label className=\"mr-2 flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest text-muted-foreground\">\r\n {L.perPage}\r\n <SelectInput\r\n aria-label={L.perPage}\r\n value={String(pageSize)}\r\n onChange={(e) => onPageSizeChange(Number(e.target.value))}\r\n className=\"w-20 normal-case tracking-normal font-normal\"\r\n >\r\n {opcoes.map((n) => (\r\n <option key={n} value={n}>\r\n {n}\r\n </option>\r\n ))}\r\n </SelectInput>\r\n </label>\r\n )}\r\n <Button variant=\"secondary\" size=\"sm\" icon={<ChevronLeft />} disabled={page <= 1} onClick={() => onPageChange(page - 1)}>\r\n {L.prev}\r\n </Button>\r\n <Button variant=\"secondary\" size=\"sm\" disabled={page >= totalPaginas} onClick={() => onPageChange(page + 1)}>\r\n {L.next}\r\n <ChevronRight />\r\n </Button>\r\n </div>\r\n </nav>\r\n )\r\n}\r\n\r\nexport { Pagination }\r\n"],"names":[],"mappings":";;;AAcO,MAAM,4BAA8C;AAAA,EACzD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,MAAM;AAAA,EACN,MAAM;AACR;AAkBA,SAAS,cAAc;AACrB,SACE,oBAAC,OAAA,EAAI,eAAW,MAAC,SAAQ,aAAY,OAAO,IAAI,QAAQ,IAAI,MAAK,QAAO,QAAO,gBAAe,aAAa,GAAG,eAAc,SAAQ,gBAAe,SACjJ,UAAA,oBAAC,QAAA,EAAK,GAAE,iBAAA,CAAiB,EAAA,CAC3B;AAEJ;AAEA,SAAS,eAAe;AACtB,SACE,oBAAC,OAAA,EAAI,eAAW,MAAC,SAAQ,aAAY,OAAO,IAAI,QAAQ,IAAI,MAAK,QAAO,QAAO,gBAAe,aAAa,GAAG,eAAc,SAAQ,gBAAe,SACjJ,UAAA,oBAAC,QAAA,EAAK,GAAE,gBAAA,CAAgB,EAAA,CAC1B;AAEJ;AAOA,SAAwB,WAAW;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AACd,GAAoB;AAClB,QAAM,IAAI,EAAE,GAAG,2BAA2B,GAAG,OAAA;AAC7C,QAAM,eAAe,KAAK,IAAI,GAAG,KAAK;AACtC,QAAM,SAAS,mBAAmB,YAAY,OAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,iBAAiB,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,IAAI,CAAA;AAC1H,SACE,qBAAC,SAAI,cAAY,EAAE,KAAK,QAAQ,UAAU,OAAO,IAAI,CAAC,EAAE,QAAQ,WAAW,OAAO,YAAY,CAAC,GAAG,WAAW,qDAAqD,SAAS,IACzK,UAAA;AAAA,IAAA,qBAAC,QAAA,EAAK,WAAU,0EACb,UAAA;AAAA,MAAA,SAAS,QAAQ,qBAAA,UAAA,EAAG,UAAA;AAAA,QAAA,EAAE,MAAM,QAAQ,WAAW,MAAM,gBAAgB;AAAA,QAAE;AAAA,MAAA,GAAG;AAAA,MAC1E,EAAE,KAAK,QAAQ,UAAU,OAAO,IAAI,CAAC,EAAE,QAAQ,WAAW,OAAO,YAAY,CAAC;AAAA,IAAA,GACjF;AAAA,IACA,qBAAC,OAAA,EAAI,WAAU,uCACZ,UAAA;AAAA,MAAA,OAAO,SAAS,KAAK,oBACpB,qBAAC,SAAA,EAAM,WAAU,yGACd,UAAA;AAAA,QAAA,EAAE;AAAA,QACH;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,cAAY,EAAE;AAAA,YACd,OAAO,OAAO,QAAQ;AAAA,YACtB,UAAU,CAAC,MAAM,iBAAiB,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,YACxD,WAAU;AAAA,YAET,UAAA,OAAO,IAAI,CAAC,MACX,oBAAC,YAAe,OAAO,GACpB,UAAA,EAAA,GADU,CAEb,CACD;AAAA,UAAA;AAAA,QAAA;AAAA,MACH,GACF;AAAA,MAEF,oBAAC,UAAO,SAAQ,aAAY,MAAK,MAAK,0BAAO,aAAA,EAAY,GAAI,UAAU,QAAQ,GAAG,SAAS,MAAM,aAAa,OAAO,CAAC,GACnH,YAAE,MACL;AAAA,MACA,qBAAC,QAAA,EAAO,SAAQ,aAAY,MAAK,MAAK,UAAU,QAAQ,cAAc,SAAS,MAAM,aAAa,OAAO,CAAC,GACvG,UAAA;AAAA,QAAA,EAAE;AAAA,4BACF,cAAA,CAAA,CAAa;AAAA,MAAA,EAAA,CAChB;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
@@ -0,0 +1,105 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useRef, useCallback, useMemo, useEffect, createContext, useContext } from "react";
3
+ import { CloseIcon, ErrorIcon, WarningIcon, InfoIcon, CheckIcon } from "../../../foundations/icons/icons.js";
4
+ import { useTopLayer, popoverReset } from "../../overlays/topLayer.js";
5
+ const ToastContext = createContext(null);
6
+ function useToast() {
7
+ const api = useContext(ToastContext);
8
+ if (!api) throw new Error("useToast: falta o <ToastProvider> à volta da app");
9
+ return api;
10
+ }
11
+ const TONES = {
12
+ success: { icon: /* @__PURE__ */ jsx(CheckIcon, { size: 16 }), className: "text-ok", duration: 4e3 },
13
+ info: { icon: /* @__PURE__ */ jsx(InfoIcon, { size: 16 }), className: "text-proef-blue", duration: 5e3 },
14
+ warning: { icon: /* @__PURE__ */ jsx(WarningIcon, { size: 16 }), className: "text-risco", duration: 7e3 },
15
+ error: { icon: /* @__PURE__ */ jsx(ErrorIcon, { size: 16 }), className: "text-incumprido", duration: 9e3 }
16
+ };
17
+ function ToastProvider({ children, closeLabel = "Fechar aviso", regionLabel = "Avisos", max = 4 }) {
18
+ const [toasts, setToasts] = useState([]);
19
+ const nextId = useRef(1);
20
+ const dismiss = useCallback((id) => setToasts((list) => list.filter((t) => t.id !== id)), []);
21
+ const api = useMemo(() => {
22
+ const push = (tone) => (message, options = {}) => {
23
+ const id = nextId.current++;
24
+ setToasts((list) => [...list, { id, tone, message, ...options }].slice(-max));
25
+ return id;
26
+ };
27
+ return { success: push("success"), error: push("error"), warning: push("warning"), info: push("info"), dismiss };
28
+ }, [dismiss, max]);
29
+ const regionRef = useRef(null);
30
+ const raise = useTopLayer(regionRef, toasts.length > 0);
31
+ const lastId = toasts[toasts.length - 1]?.id;
32
+ useEffect(() => {
33
+ if (lastId !== void 0) raise();
34
+ }, [lastId, raise]);
35
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: api, children: [
36
+ children,
37
+ /* @__PURE__ */ jsx(
38
+ "div",
39
+ {
40
+ ref: regionRef,
41
+ popover: "manual",
42
+ role: "region",
43
+ "aria-label": regionLabel,
44
+ className: `${popoverReset} pointer-events-none fixed inset-auto right-4 bottom-4 bg-transparent p-0 z-[300] flex w-[min(400px,calc(100vw-2rem))] flex-col gap-2`,
45
+ children: toasts.map((t) => /* @__PURE__ */ jsx(ToastItem, { toast: t, dismiss, closeLabel }, t.id))
46
+ }
47
+ )
48
+ ] });
49
+ }
50
+ function ToastItem({ toast, dismiss, closeLabel }) {
51
+ const tone = TONES[toast.tone];
52
+ const duration = toast.duration ?? tone.duration;
53
+ const [paused, setPaused] = useState(false);
54
+ const { id } = toast;
55
+ const onDismiss = useCallback(() => dismiss(id), [dismiss, id]);
56
+ useEffect(() => {
57
+ if (paused || duration <= 0) return;
58
+ const timer = setTimeout(onDismiss, duration);
59
+ return () => clearTimeout(timer);
60
+ }, [paused, duration, onDismiss]);
61
+ return /* @__PURE__ */ jsxs(
62
+ "div",
63
+ {
64
+ role: toast.tone === "error" ? "alert" : "status",
65
+ onMouseEnter: () => setPaused(true),
66
+ onMouseLeave: () => setPaused(false),
67
+ className: "pointer-events-auto flex items-start gap-3 rounded-xl border border-border bg-card px-4 py-3 text-control text-foreground shadow-popover motion-safe:animate-dialog-in",
68
+ children: [
69
+ /* @__PURE__ */ jsx("span", { className: `mt-px flex-none ${tone.className}`, children: tone.icon }),
70
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
71
+ toast.title && /* @__PURE__ */ jsx("p", { className: "m-0 font-semibold", children: toast.title }),
72
+ /* @__PURE__ */ jsx("div", { className: `m-0 break-words whitespace-pre-line ${toast.title ? "text-muted-foreground" : "font-semibold"}`, children: toast.message }),
73
+ toast.action && /* @__PURE__ */ jsx(
74
+ "button",
75
+ {
76
+ type: "button",
77
+ onClick: () => {
78
+ toast.action.onClick();
79
+ onDismiss();
80
+ },
81
+ className: "mt-1.5 cursor-pointer border-0 bg-transparent p-0 text-control font-semibold text-proef-blue hover:underline focus-visible:underline focus-visible:outline-none",
82
+ children: toast.action.label
83
+ }
84
+ )
85
+ ] }),
86
+ /* @__PURE__ */ jsx(
87
+ "button",
88
+ {
89
+ type: "button",
90
+ "aria-label": closeLabel,
91
+ title: closeLabel,
92
+ onClick: onDismiss,
93
+ className: "flex h-6 w-6 flex-none cursor-pointer items-center justify-center rounded-md border-0 bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:outline-none",
94
+ children: /* @__PURE__ */ jsx(CloseIcon, { size: 14 })
95
+ }
96
+ )
97
+ ]
98
+ }
99
+ );
100
+ }
101
+ export {
102
+ ToastProvider as default,
103
+ useToast
104
+ };
105
+ //# sourceMappingURL=Toast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Toast.js","sources":["../../../../../../src/v1/components/feedback/Toast/Toast.tsx"],"sourcesContent":["import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'\r\nimport { CheckIcon, CloseIcon, ErrorIcon, InfoIcon, WarningIcon } from '../../../foundations/icons/icons'\r\nimport { popoverReset, useTopLayer } from '../../overlays/topLayer'\r\n\r\nexport type ToastTone = 'success' | 'error' | 'warning' | 'info'\r\n\r\nexport interface ToastOptions {\r\n /** Linha em negrito por cima da mensagem. */\r\n title?: string\r\n /** Botão no aviso (ex: Desfazer, Tentar de novo). Fecha o aviso depois de correr. */\r\n action?: { label: string; onClick: () => void }\r\n /**\r\n * Quanto tempo fica visível, em ms. `0`: fica até ser fechado. Por omissão\r\n * depende do tipo: ler um erro demora mais do que um \"Guardado\".\r\n */\r\n duration?: number\r\n}\r\n\r\nexport interface ToastApi {\r\n success: (message: React.ReactNode, options?: ToastOptions) => number\r\n error: (message: React.ReactNode, options?: ToastOptions) => number\r\n warning: (message: React.ReactNode, options?: ToastOptions) => number\r\n info: (message: React.ReactNode, options?: ToastOptions) => number\r\n /** Fecha um aviso pelo número que as funções acima devolvem. */\r\n dismiss: (id: number) => void\r\n}\r\n\r\nexport interface ToastProviderProps {\r\n children: React.ReactNode\r\n /** Nome do botão × de cada aviso, para leitores de ecrã. Por omissão: \"Fechar aviso\". */\r\n closeLabel?: string\r\n /** Nome da zona dos avisos, para leitores de ecrã. Por omissão: \"Avisos\". */\r\n regionLabel?: string\r\n /** Máximo de avisos visíveis ao mesmo tempo; os mais antigos saem. Por omissão 4. */\r\n max?: number\r\n}\r\n\r\ninterface Toast extends ToastOptions {\r\n id: number\r\n tone: ToastTone\r\n message: React.ReactNode\r\n}\r\n\r\nconst ToastContext = createContext<ToastApi | null>(null)\r\n\r\n/**\r\n * Avisos da app. Fora de um ToastProvider dá erro, para não se perderem\r\n * avisos sem ninguém reparar.\r\n */\r\nexport function useToast(): ToastApi {\r\n const api = useContext(ToastContext)\r\n if (!api) throw new Error('useToast: falta o <ToastProvider> à volta da app')\r\n return api\r\n}\r\n\r\nconst TONES: Record<ToastTone, { icon: React.ReactNode; className: string; duration: number }> = {\r\n success: { icon: <CheckIcon size={16} />, className: 'text-ok', duration: 4000 },\r\n info: { icon: <InfoIcon size={16} />, className: 'text-proef-blue', duration: 5000 },\r\n warning: { icon: <WarningIcon size={16} />, className: 'text-risco', duration: 7000 },\r\n error: { icon: <ErrorIcon size={16} />, className: 'text-incumprido', duration: 9000 },\r\n}\r\n\r\n/**\r\n * Avisos do resultado de uma ação, no canto inferior direito (ex: \"Ticket\r\n * guardado\", \"Não foi possível apagar\").\r\n *\r\n * Monta-se uma vez à volta da app; em qualquer ecrã, `useToast()` dá\r\n * `success`, `error`, `warning` e `info`. Desaparecem sozinhos (os erros\r\n * ficam mais tempo); com o rato por cima ficam até ele sair. Aparecem por cima\r\n * de uma janela (Dialog) aberta.\r\n *\r\n * Substituem o `alert()` do browser. Erros de validação de um formulário não\r\n * vão aqui: ficam junto aos campos.\r\n */\r\nexport default function ToastProvider({ children, closeLabel = 'Fechar aviso', regionLabel = 'Avisos', max = 4 }: ToastProviderProps) {\r\n const [toasts, setToasts] = useState<Toast[]>([])\r\n const nextId = useRef(1)\r\n\r\n const dismiss = useCallback((id: number) => setToasts((list) => list.filter((t) => t.id !== id)), [])\r\n\r\n const api = useMemo<ToastApi>(() => {\r\n const push = (tone: ToastTone) => (message: React.ReactNode, options: ToastOptions = {}) => {\r\n const id = nextId.current++\r\n setToasts((list) => [...list, { id, tone, message, ...options }].slice(-max))\r\n return id\r\n }\r\n return { success: push('success'), error: push('error'), warning: push('warning'), info: push('info'), dismiss }\r\n }, [dismiss, max])\r\n\r\n const regionRef = useRef<HTMLDivElement>(null)\r\n const raise = useTopLayer(regionRef, toasts.length > 0)\r\n // Um aviso novo vem para a frente de uma janela que tenha aberto depois do anterior.\r\n const lastId = toasts[toasts.length - 1]?.id\r\n useEffect(() => {\r\n if (lastId !== undefined) raise()\r\n }, [lastId, raise])\r\n\r\n return (\r\n <ToastContext.Provider value={api}>\r\n {children}\r\n <div\r\n ref={regionRef}\r\n popover=\"manual\"\r\n role=\"region\"\r\n aria-label={regionLabel}\r\n className={`${popoverReset} pointer-events-none fixed inset-auto right-4 bottom-4 bg-transparent p-0 z-[300] flex w-[min(400px,calc(100vw-2rem))] flex-col gap-2`}\r\n >\r\n {toasts.map((t) => (\r\n <ToastItem key={t.id} toast={t} dismiss={dismiss} closeLabel={closeLabel} />\r\n ))}\r\n </div>\r\n </ToastContext.Provider>\r\n )\r\n}\r\n\r\nfunction ToastItem({ toast, dismiss, closeLabel }: { toast: Toast; dismiss: (id: number) => void; closeLabel: string }) {\r\n const tone = TONES[toast.tone]\r\n const duration = toast.duration ?? tone.duration\r\n const [paused, setPaused] = useState(false)\r\n const { id } = toast\r\n const onDismiss = useCallback(() => dismiss(id), [dismiss, id])\r\n\r\n // Estável entre renders: um aviso novo não reinicia o tempo dos outros.\r\n useEffect(() => {\r\n if (paused || duration <= 0) return\r\n const timer = setTimeout(onDismiss, duration)\r\n return () => clearTimeout(timer)\r\n }, [paused, duration, onDismiss])\r\n\r\n return (\r\n <div\r\n role={toast.tone === 'error' ? 'alert' : 'status'}\r\n onMouseEnter={() => setPaused(true)}\r\n onMouseLeave={() => setPaused(false)}\r\n className=\"pointer-events-auto flex items-start gap-3 rounded-xl border border-border bg-card px-4 py-3 text-control text-foreground shadow-popover motion-safe:animate-dialog-in\"\r\n >\r\n <span className={`mt-px flex-none ${tone.className}`}>{tone.icon}</span>\r\n <div className=\"min-w-0 flex-1\">\r\n {toast.title && <p className=\"m-0 font-semibold\">{toast.title}</p>}\r\n <div className={`m-0 break-words whitespace-pre-line ${toast.title ? 'text-muted-foreground' : 'font-semibold'}`}>{toast.message}</div>\r\n {toast.action && (\r\n <button\r\n type=\"button\"\r\n onClick={() => {\r\n toast.action!.onClick()\r\n onDismiss()\r\n }}\r\n className=\"mt-1.5 cursor-pointer border-0 bg-transparent p-0 text-control font-semibold text-proef-blue hover:underline focus-visible:underline focus-visible:outline-none\"\r\n >\r\n {toast.action.label}\r\n </button>\r\n )}\r\n </div>\r\n <button\r\n type=\"button\"\r\n aria-label={closeLabel}\r\n title={closeLabel}\r\n onClick={onDismiss}\r\n className=\"flex h-6 w-6 flex-none cursor-pointer items-center justify-center rounded-md border-0 bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:outline-none\"\r\n >\r\n <CloseIcon size={14} />\r\n </button>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}