@nextelco/common-ui 2.1.106 → 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 (177) 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/layout/AppShell/AppShell.js +89 -0
  52. package/dist/esm/v1/components/layout/AppShell/AppShell.js.map +1 -0
  53. package/dist/esm/v1/components/layout/Page/Page.js +16 -0
  54. package/dist/esm/v1/components/layout/Page/Page.js.map +1 -0
  55. package/dist/esm/v1/components/layout/PageHeader/PageHeader.js +26 -0
  56. package/dist/esm/v1/components/layout/PageHeader/PageHeader.js.map +1 -0
  57. package/dist/esm/v1/components/layout/SideNav/SideNav.js +277 -0
  58. package/dist/esm/v1/components/layout/SideNav/SideNav.js.map +1 -0
  59. package/dist/esm/v1/components/layout/navTrail.js +19 -0
  60. package/dist/esm/v1/components/layout/navTrail.js.map +1 -0
  61. package/dist/esm/v1/components/overlays/ConfirmDialog/ConfirmDialog.js +95 -0
  62. package/dist/esm/v1/components/overlays/ConfirmDialog/ConfirmDialog.js.map +1 -0
  63. package/dist/esm/v1/components/overlays/Dialog/Dialog.js.map +1 -1
  64. package/dist/esm/v1/components/overlays/Tooltip/Tooltip.js +98 -0
  65. package/dist/esm/v1/components/overlays/Tooltip/Tooltip.js.map +1 -0
  66. package/dist/esm/v1/components/overlays/Tooltip/TooltipLayer.js +84 -0
  67. package/dist/esm/v1/components/overlays/Tooltip/TooltipLayer.js.map +1 -0
  68. package/dist/esm/v1/components/overlays/Tooltip/TooltipPanel.js +33 -0
  69. package/dist/esm/v1/components/overlays/Tooltip/TooltipPanel.js.map +1 -0
  70. package/dist/esm/v1/components/overlays/topLayer.js +28 -0
  71. package/dist/esm/v1/components/overlays/topLayer.js.map +1 -0
  72. package/dist/esm/v1/foundations/icons/icons.js +61 -2
  73. package/dist/esm/v1/foundations/icons/icons.js.map +1 -1
  74. package/dist/esm/v1/primitives/buttons/Button/Button.js +18 -4
  75. package/dist/esm/v1/primitives/buttons/Button/Button.js.map +1 -1
  76. package/dist/esm/v1/primitives/buttons/SegmentedControl/SegmentedControl.js +65 -0
  77. package/dist/esm/v1/primitives/buttons/SegmentedControl/SegmentedControl.js.map +1 -0
  78. package/dist/esm/v1/primitives/buttons/SegmentedControl/segmentedStyles.js +10 -0
  79. package/dist/esm/v1/primitives/buttons/SegmentedControl/segmentedStyles.js.map +1 -0
  80. package/dist/esm/v1/primitives/display/Avatar/Avatar.js +120 -0
  81. package/dist/esm/v1/primitives/display/Avatar/Avatar.js.map +1 -0
  82. package/dist/esm/v1/primitives/display/Badge/Badge.js +23 -0
  83. package/dist/esm/v1/primitives/display/Badge/Badge.js.map +1 -0
  84. package/dist/esm/v1/primitives/display/Card/Card.js +22 -0
  85. package/dist/esm/v1/primitives/display/Card/Card.js.map +1 -0
  86. package/dist/esm/v1/primitives/display/FilterChips/FilterChips.js +45 -0
  87. package/dist/esm/v1/primitives/display/FilterChips/FilterChips.js.map +1 -0
  88. package/dist/esm/v1/primitives/display/Kpi/Kpi.js +103 -0
  89. package/dist/esm/v1/primitives/display/Kpi/Kpi.js.map +1 -0
  90. package/dist/esm/v1/primitives/display/Notice/Notice.js +71 -0
  91. package/dist/esm/v1/primitives/display/Notice/Notice.js.map +1 -0
  92. package/dist/esm/v1/primitives/display/Person/Person.js +14 -0
  93. package/dist/esm/v1/primitives/display/Person/Person.js.map +1 -0
  94. package/dist/esm/v1/primitives/forms/Checkbox/Checkbox.js +1 -1
  95. package/dist/esm/v1/primitives/forms/Checkbox/Checkbox.js.map +1 -1
  96. package/dist/esm/v1/primitives/forms/DateInput/DateInput.js +150 -0
  97. package/dist/esm/v1/primitives/forms/DateInput/DateInput.js.map +1 -0
  98. package/dist/esm/v1/primitives/forms/SearchInput/SearchInput.js +43 -0
  99. package/dist/esm/v1/primitives/forms/SearchInput/SearchInput.js.map +1 -0
  100. package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js +190 -12
  101. package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js.map +1 -1
  102. package/dist/esm/v1/primitives/forms/field.js +4 -2
  103. package/dist/esm/v1/primitives/forms/field.js.map +1 -1
  104. package/dist/esm/v1/primitives/navigation/Tabs/Tabs.js +96 -0
  105. package/dist/esm/v1/primitives/navigation/Tabs/Tabs.js.map +1 -0
  106. package/dist/style.components.css +1 -1
  107. package/dist/style.css +1 -1
  108. package/dist/style.layered.css +1 -1
  109. package/dist/tailwind.css +24 -0
  110. package/dist/theme-boot.js +1 -0
  111. package/dist/theme.css +10 -3
  112. package/dist/tokens.css +119 -0
  113. package/dist/types/legacy/components/atoms/InputData/InputData.d.ts +4 -1
  114. package/dist/types/legacy/components/charts/Charts.d.ts +58 -5
  115. package/dist/types/legacy/components/layout/Layout/Layout.d.ts +7 -20
  116. package/dist/types/legacy/components/layout/Sidebar/Sidebar.d.ts +13 -26
  117. package/dist/types/legacy/components/molecules/ColumnPicker/ColumnPicker.d.ts +56 -3
  118. package/dist/types/legacy/components/molecules/DataTable/DataTable.d.ts +72 -7
  119. package/dist/types/legacy/components/molecules/UserAvatar/UserAvatar.d.ts +4 -5
  120. package/dist/types/legacy/components/ui/Avisos.d.ts +6 -16
  121. package/dist/types/legacy/components/ui/CabecalhoPagina.d.ts +6 -20
  122. package/dist/types/legacy/components/ui/CaixaPesquisa.d.ts +2 -4
  123. package/dist/types/legacy/components/ui/Confirmacao.d.ts +3 -10
  124. package/dist/types/legacy/components/ui/Escolha.d.ts +5 -15
  125. package/dist/types/legacy/components/ui/FaixaAviso.d.ts +2 -6
  126. package/dist/types/legacy/components/ui/Separadores.d.ts +3 -10
  127. package/dist/types/legacy/components/ui/Ui.d.ts +12 -54
  128. package/dist/types/legacy/components/ui/painelFlutuante.d.ts +6 -10
  129. package/dist/types/legacy/exports/atoms.d.ts +1 -1
  130. package/dist/types/legacy/exports/charts.d.ts +2 -2
  131. package/dist/types/legacy/exports/hooks.d.ts +2 -2
  132. package/dist/types/legacy/exports/molecules.d.ts +2 -2
  133. package/dist/types/v1/capabilities/overlay/useFloatingPanel.d.ts +20 -0
  134. package/dist/types/v1/capabilities/theme/useIsDark.d.ts +10 -0
  135. package/dist/types/v1/capabilities/theme/useTheme.d.ts +29 -0
  136. package/dist/types/v1/components/data/Pagination/Pagination.d.ts +33 -0
  137. package/dist/types/v1/components/feedback/Toast/Toast.d.ts +51 -0
  138. package/dist/types/v1/components/layout/AppShell/AppShell.d.ts +35 -0
  139. package/dist/types/v1/components/layout/Page/Page.d.ts +23 -0
  140. package/dist/types/v1/components/layout/PageHeader/PageHeader.d.ts +35 -0
  141. package/dist/types/v1/components/layout/SideNav/SideNav.d.ts +92 -0
  142. package/dist/types/v1/components/layout/navTrail.d.ts +15 -0
  143. package/dist/types/v1/components/overlays/ConfirmDialog/ConfirmDialog.d.ts +59 -0
  144. package/dist/types/v1/components/overlays/Dialog/Dialog.d.ts +3 -2
  145. package/dist/types/v1/components/overlays/Tooltip/Tooltip.d.ts +34 -0
  146. package/dist/types/v1/components/overlays/Tooltip/TooltipLayer.d.ts +18 -0
  147. package/dist/types/v1/components/overlays/Tooltip/TooltipPanel.d.ts +23 -0
  148. package/dist/types/v1/components/overlays/topLayer.d.ts +23 -0
  149. package/dist/types/v1/foundations/icons/icons.d.ts +20 -0
  150. package/dist/types/v1/index.d.ts +43 -1
  151. package/dist/types/v1/primitives/buttons/Button/Button.d.ts +20 -5
  152. package/dist/types/v1/primitives/buttons/SegmentedControl/SegmentedControl.d.ts +28 -0
  153. package/dist/types/v1/primitives/buttons/SegmentedControl/segmentedStyles.d.ts +16 -0
  154. package/dist/types/v1/primitives/display/Avatar/Avatar.d.ts +30 -0
  155. package/dist/types/v1/primitives/display/Badge/Badge.d.ts +25 -0
  156. package/dist/types/v1/primitives/display/Card/Card.d.ts +22 -0
  157. package/dist/types/v1/primitives/display/FilterChips/FilterChips.d.ts +33 -0
  158. package/dist/types/v1/primitives/display/Kpi/Kpi.d.ts +68 -0
  159. package/dist/types/v1/primitives/display/Notice/Notice.d.ts +29 -0
  160. package/dist/types/v1/primitives/display/Person/Person.d.ts +15 -0
  161. package/dist/types/v1/primitives/forms/DateInput/DateInput.d.ts +43 -0
  162. package/dist/types/v1/primitives/forms/SearchInput/SearchInput.d.ts +23 -0
  163. package/dist/types/v1/primitives/forms/SelectInput/SelectInput.d.ts +60 -12
  164. package/dist/types/v1/primitives/forms/field.d.ts +2 -0
  165. package/dist/types/v1/primitives/navigation/Tabs/Tabs.d.ts +58 -0
  166. package/package.json +3 -3
  167. package/dist/esm/legacy/components/atoms/Tooltip/Tooltip.css +0 -17
  168. package/dist/esm/legacy/components/atoms/Tooltip/Tooltip.js +0 -41
  169. package/dist/esm/legacy/components/atoms/Tooltip/Tooltip.js.map +0 -1
  170. package/dist/esm/legacy/components/molecules/Search/Search.css +0 -18
  171. package/dist/esm/legacy/components/molecules/Search/Search.js +0 -30
  172. package/dist/esm/legacy/components/molecules/Search/Search.js.map +0 -1
  173. package/dist/esm/legacy/hooks/useTheme.js +0 -39
  174. package/dist/esm/legacy/hooks/useTheme.js.map +0 -1
  175. package/dist/types/legacy/components/atoms/Tooltip/Tooltip.d.ts +0 -13
  176. package/dist/types/legacy/components/molecules/Search/Search.d.ts +0 -7
  177. package/dist/types/legacy/hooks/useTheme.d.ts +0 -17
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Kpi.js","sources":["../../../../../../src/v1/primitives/display/Kpi/Kpi.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n/**\r\n * Estados com significado: `ok` (verde), `risco` (âmbar), `incumprido` (vermelho).\r\n * Sem estado: `default` (texto normal) e `brand` (azul da marca).\r\n * A cor é a do número — não usar cores só para enfeitar: um indicador sem\r\n * significado bom/mau fica em `default`.\r\n */\r\nexport type KpiTone = 'default' | 'brand' | 'ok' | 'risco' | 'incumprido'\r\n\r\nexport interface KpiProps {\r\n label: React.ReactNode\r\n value: React.ReactNode\r\n tone?: KpiTone\r\n /** Linha por baixo do valor (ex: \"de 120\", \"+12% do compromisso\"). */\r\n detail?: React.ReactNode\r\n /** Ícone antes do rótulo (ex: `<Inbox size={14} />`). */\r\n icon?: React.ReactNode\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 bar?: number | { weight: number; color: string }[]\r\n /** Linha pequena no fim, para um dado que acompanha mas não é o indicador. */\r\n footer?: React.ReactNode\r\n /**\r\n * Faixa fina em vez de cartão: alinha à esquerda e corta a altura para metade.\r\n * Para muitos indicadores que empurrariam o conteúdo para fora do ecrã.\r\n */\r\n dense?: boolean\r\n /** Fundo no tom claro da cor, em vez de só o número colorido. */\r\n tinted?: boolean\r\n /** Clicável (ex: filtra a tabela). Mostra o funil no canto (ou a seta, com `clickAction=\"open\"`). */\r\n onClick?: () => void\r\n /** O que o clique faz: `filter` (funil, por omissão) ou `open` (seta: abre uma lista ou um detalhe, não filtra). */\r\n clickAction?: 'filter' | 'open'\r\n /** Filtro ativo (Kpi clicável selecionado): contorno azul. */\r\n active?: boolean\r\n /** Explicação no tooltip (camada `data-tip`), ex: \"Filtrar a tabela\" ou como o valor é calculado. */\r\n hint?: string\r\n className?: string\r\n /** @deprecated Nome antigo (Legacy) de `detail`. */\r\n delta?: React.ReactNode\r\n /** @deprecated Nome antigo (Legacy) de `tinted`. */\r\n bgTint?: boolean\r\n /** @deprecated Nome antigo (Legacy) de `bar`. */\r\n barra?: number | { peso: number; cor: string }[]\r\n /** @deprecated Nome antigo (Legacy) de `footer`. */\r\n rodape?: React.ReactNode\r\n /** @deprecated Nome antigo (Legacy) de `dense`. */\r\n denso?: boolean\r\n}\r\n\r\nconst VALUE_TONES: 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\r\nconst TINTS: 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\r\nfunction OpenIcon({ className }: { className: string }) {\r\n return (\r\n <svg aria-hidden viewBox=\"0 0 24 24\" className={className} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <path d=\"M7 17 17 7M7 7h10v10\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction FunnelIcon({ className }: { className: string }) {\r\n return (\r\n <svg\r\n aria-hidden\r\n viewBox=\"0 0 24 24\"\r\n className={className}\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 )\r\n}\r\n\r\n/**\r\n * Indicador: um número com o seu rótulo (ex: \"Em progresso 13\"). Quando é\r\n * clicável filtra a vista e mostra-o com o funil; `active` marca o filtro\r\n * aplicado. Dispor vários numa grelha (`grid gap-3`).\r\n */\r\nexport default function Kpi({\r\n label,\r\n value,\r\n tone = 'default',\r\n detail: detailProp,\r\n icon,\r\n dot,\r\n bar: barProp,\r\n footer: footerProp,\r\n dense: denseProp,\r\n tinted: tintedProp,\r\n onClick,\r\n clickAction = 'filter',\r\n active = false,\r\n hint,\r\n className = '',\r\n delta,\r\n bgTint,\r\n barra,\r\n rodape,\r\n denso,\r\n}: KpiProps) {\r\n // A Legacy usa este mesmo Kpi com os nomes antigos.\r\n const detail = detailProp ?? delta\r\n const footer = footerProp ?? rodape\r\n const dense = denseProp ?? denso ?? false\r\n const tinted = tintedProp ?? bgTint ?? false\r\n const bar = barProp ?? (Array.isArray(barra) ? barra.map((x) => ({ weight: x.peso, color: x.cor })) : barra)\r\n const surface = tinted ? TINTS[tone] : TINTS.default\r\n const layout = dense\r\n ? 'rounded-lg px-2.5 py-1.5 flex flex-col justify-center text-left gap-0.5'\r\n : 'rounded-xl px-4 py-3 min-h-[88px] flex flex-col items-center justify-center text-center gap-1.5'\r\n const base = `relative border ${surface} ${layout} ${className}`\r\n const totalBar = Array.isArray(bar) ? bar.reduce((s, x) => s + x.weight, 0) : 0\r\n\r\n const content = (\r\n <>\r\n <div className={`${dense ? 'text-[10px] uppercase tracking-wide' : 'text-xs'} text-slate-500 dark:text-slate-400 flex items-center gap-1.5 max-w-full`}>\r\n {icon ? (\r\n <span aria-hidden className=\"shrink-0 flex\">{icon}</span>\r\n ) : (\r\n dot && <i aria-hidden className=\"w-2 h-2 rounded-full inline-block shrink-0\" style={{ background: dot }} />\r\n )}\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 altura sem tirar informação. */}\r\n <div className={dense ? 'flex items-baseline gap-1.5' : ''}>\r\n <span className={`${dense ? 'text-xl' : 'text-2xl'} font-semibold ${VALUE_TONES[tone]}`}>{value}</span>\r\n {detail && dense && <span className=\"text-[11px] text-slate-400 truncate\">{detail}</span>}\r\n </div>\r\n {detail && !dense && <div className=\"text-[11.5px] text-slate-400\">{detail}</div>}\r\n {bar != null && (\r\n <div className=\"w-full h-1 bg-slate-100 dark:bg-slate-800 rounded overflow-hidden flex\">\r\n {Array.isArray(bar) ? (\r\n bar.map((x, i) => (\r\n <i key={i} style={{ width: totalBar ? `${(x.weight / totalBar) * 100}%` : 0, background: x.color }} />\r\n ))\r\n ) : (\r\n <i style={{ width: `${Math.min(100, Math.max(0, bar))}%`, background: 'currentColor' }} className={VALUE_TONES[tone]} />\r\n )}\r\n </div>\r\n )}\r\n {footer && <div className=\"text-[11px] text-slate-400\">{footer}</div>}\r\n </>\r\n )\r\n\r\n if (!onClick) {\r\n return (\r\n <div data-tip={hint} className={`${base} ${hint ? 'cursor-help' : ''}`}>\r\n {content}\r\n </div>\r\n )\r\n }\r\n return (\r\n <button\r\n type=\"button\"\r\n onClick={onClick}\r\n data-tip={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 dark:border-sky-400' : ''\r\n }`}\r\n >\r\n {(() => {\r\n const cls = `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 return clickAction === 'open' ? <OpenIcon className={cls} /> : <FunnelIcon className={cls} />\r\n })()}\r\n {content}\r\n </button>\r\n )\r\n}\r\n\r\nexport { Kpi }\r\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ const TONES = {
3
+ info: {
4
+ box: "border-sky-200 bg-sky-50 text-sky-950 dark:border-sky-500/30 dark:bg-sky-500/10 dark:text-sky-100",
5
+ banner: "border-sky-200 bg-sky-50 text-sky-900 dark:border-sky-500/30 dark:bg-sky-500/10 dark:text-sky-100",
6
+ icon: "text-sky-600 dark:text-sky-300"
7
+ },
8
+ warning: {
9
+ box: "border-amber-200 bg-amber-50 text-amber-950 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100",
10
+ banner: "border-amber-300 bg-amber-100 text-amber-950 dark:border-amber-500/30 dark:bg-amber-500/15 dark:text-amber-100",
11
+ icon: "text-amber-600 dark:text-amber-300"
12
+ },
13
+ error: {
14
+ box: "border-incumprido/30 bg-incumprido-bg text-incumprido",
15
+ banner: "border-incumprido/30 bg-incumprido-bg text-incumprido",
16
+ icon: "text-incumprido"
17
+ },
18
+ success: {
19
+ box: "border-ok/30 bg-ok-bg text-ok",
20
+ banner: "border-ok/30 bg-ok-bg text-ok",
21
+ icon: "text-ok"
22
+ }
23
+ };
24
+ const ICON_PATHS = {
25
+ info: /* @__PURE__ */ jsxs(Fragment, { children: [
26
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
27
+ /* @__PURE__ */ jsx("path", { d: "M12 16v-4M12 8h.01" })
28
+ ] }),
29
+ warning: /* @__PURE__ */ jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3ZM12 9v4M12 17h.01" }),
30
+ error: /* @__PURE__ */ jsxs(Fragment, { children: [
31
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
32
+ /* @__PURE__ */ jsx("path", { d: "m15 9-6 6M9 9l6 6" })
33
+ ] }),
34
+ success: /* @__PURE__ */ jsxs(Fragment, { children: [
35
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
36
+ /* @__PURE__ */ jsx("path", { d: "m9 12 2 2 4-4" })
37
+ ] })
38
+ };
39
+ function Notice({ tone = "info", title, children, icon, action, variant = "box", className = "" }) {
40
+ const c = TONES[tone];
41
+ const shownIcon = icon === null ? null : icon ?? /* @__PURE__ */ jsx("svg", { "aria-hidden": true, viewBox: "0 0 24 24", width: 16, height: 16, fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: ICON_PATHS[tone] });
42
+ const banner = variant === "banner";
43
+ return /* @__PURE__ */ jsxs(
44
+ "div",
45
+ {
46
+ role: tone === "error" ? "alert" : "status",
47
+ className: banner ? `flex flex-wrap items-center gap-x-3 gap-y-1 border-b px-4 py-2 text-xs font-bold ${c.banner} ${className}` : `flex items-start gap-3 rounded-xl border px-4 py-3 text-sm ${c.box} ${className}`,
48
+ children: [
49
+ shownIcon && /* @__PURE__ */ jsx("span", { className: `shrink-0 ${banner ? "" : `mt-0.5 ${c.icon}`}`, children: shownIcon }),
50
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
51
+ title && /* @__PURE__ */ jsx("div", { className: banner ? "" : "mb-0.5 font-semibold", children: title }),
52
+ children && /* @__PURE__ */ jsx("div", { className: banner ? "" : "leading-relaxed opacity-90", children })
53
+ ] }),
54
+ action && /* @__PURE__ */ jsx(
55
+ "button",
56
+ {
57
+ type: "button",
58
+ onClick: action.onClick,
59
+ className: "shrink-0 cursor-pointer rounded-md border-0 bg-transparent p-0 text-[11px] font-black tracking-widest text-current uppercase underline underline-offset-2 hover:no-underline",
60
+ children: action.label
61
+ }
62
+ )
63
+ ]
64
+ }
65
+ );
66
+ }
67
+ export {
68
+ Notice,
69
+ Notice as default
70
+ };
71
+ //# sourceMappingURL=Notice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Notice.js","sources":["../../../../../../src/v1/primitives/display/Notice/Notice.tsx"],"sourcesContent":["import React from 'react'\r\n\r\nexport type NoticeTone = 'info' | 'warning' | 'error' | 'success'\r\n\r\nexport interface NoticeProps {\r\n /** `info` (azul), `warning` (âmbar), `error` (vermelho), `success` (verde). */\r\n tone?: NoticeTone\r\n /** Título curto em cima (ex: \"Automação — saída de colaborador\"). */\r\n title?: React.ReactNode\r\n children?: React.ReactNode\r\n /** Ícone à esquerda; por omissão, o do tom. `null` sem ícone. */\r\n icon?: React.ReactNode | null\r\n /** Ação à direita (ex: \"Voltar ao meu perfil\"). */\r\n action?: { label: React.ReactNode; onClick: () => void }\r\n /**\r\n * `box` (por omissão): caixa no meio do conteúdo, para explicar uma regra ou avisar de algo nesse sítio.\r\n * `banner`: faixa a toda a largura no topo, para um estado que afeta a página inteira (ex: \"Ver como\").\r\n */\r\n variant?: 'box' | 'banner'\r\n className?: string\r\n}\r\n\r\nconst TONES: Record<NoticeTone, { box: string; banner: string; icon: string }> = {\r\n info: {\r\n box: 'border-sky-200 bg-sky-50 text-sky-950 dark:border-sky-500/30 dark:bg-sky-500/10 dark:text-sky-100',\r\n banner: 'border-sky-200 bg-sky-50 text-sky-900 dark:border-sky-500/30 dark:bg-sky-500/10 dark:text-sky-100',\r\n icon: 'text-sky-600 dark:text-sky-300',\r\n },\r\n warning: {\r\n box: 'border-amber-200 bg-amber-50 text-amber-950 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100',\r\n banner: 'border-amber-300 bg-amber-100 text-amber-950 dark:border-amber-500/30 dark:bg-amber-500/15 dark:text-amber-100',\r\n icon: 'text-amber-600 dark:text-amber-300',\r\n },\r\n error: {\r\n box: 'border-incumprido/30 bg-incumprido-bg text-incumprido',\r\n banner: 'border-incumprido/30 bg-incumprido-bg text-incumprido',\r\n icon: 'text-incumprido',\r\n },\r\n success: {\r\n box: 'border-ok/30 bg-ok-bg text-ok',\r\n banner: 'border-ok/30 bg-ok-bg text-ok',\r\n icon: 'text-ok',\r\n },\r\n}\r\n\r\nconst ICON_PATHS: Record<NoticeTone, React.ReactNode> = {\r\n info: (\r\n <>\r\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\r\n <path d=\"M12 16v-4M12 8h.01\" />\r\n </>\r\n ),\r\n warning: <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3ZM12 9v4M12 17h.01\" />,\r\n error: (\r\n <>\r\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\r\n <path d=\"m15 9-6 6M9 9l6 6\" />\r\n </>\r\n ),\r\n success: (\r\n <>\r\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\r\n <path d=\"m9 12 2 2 4-4\" />\r\n </>\r\n ),\r\n}\r\n\r\n/**\r\n * Aviso ou explicação: uma caixa com ícone, título e texto (ex: a regra de uma\r\n * automação, dados possivelmente incompletos), ou uma faixa no topo da página.\r\n * Não é para o resultado de uma ação — esse é um toast (`useToast`).\r\n */\r\nexport default function Notice({ tone = 'info', title, children, icon, action, variant = 'box', className = '' }: NoticeProps) {\r\n const c = TONES[tone]\r\n const shownIcon =\r\n icon === null ? null : (icon ?? (\r\n <svg aria-hidden viewBox=\"0 0 24 24\" width={16} height={16} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n {ICON_PATHS[tone]}\r\n </svg>\r\n ))\r\n const banner = variant === 'banner'\r\n return (\r\n <div\r\n role={tone === 'error' ? 'alert' : 'status'}\r\n className={\r\n banner\r\n ? `flex flex-wrap items-center gap-x-3 gap-y-1 border-b px-4 py-2 text-xs font-bold ${c.banner} ${className}`\r\n : `flex items-start gap-3 rounded-xl border px-4 py-3 text-sm ${c.box} ${className}`\r\n }\r\n >\r\n {shownIcon && <span className={`shrink-0 ${banner ? '' : `mt-0.5 ${c.icon}`}`}>{shownIcon}</span>}\r\n <div className=\"min-w-0 flex-1\">\r\n {title && <div className={banner ? '' : 'mb-0.5 font-semibold'}>{title}</div>}\r\n {children && <div className={banner ? '' : 'leading-relaxed opacity-90'}>{children}</div>}\r\n </div>\r\n {action && (\r\n <button\r\n type=\"button\"\r\n onClick={action.onClick}\r\n className=\"shrink-0 cursor-pointer rounded-md border-0 bg-transparent p-0 text-[11px] font-black tracking-widest text-current uppercase underline underline-offset-2 hover:no-underline\"\r\n >\r\n {action.label}\r\n </button>\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\nexport { Notice }\r\n"],"names":[],"mappings":";AAsBA,MAAM,QAA2E;AAAA,EAC/E,MAAM;AAAA,IACJ,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA;AAAA,EAER,SAAS;AAAA,IACP,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA;AAAA,EAER,OAAO;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA;AAAA,EAER,SAAS;AAAA,IACP,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA;AAEV;AAEA,MAAM,aAAkD;AAAA,EACtD,MACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA,oBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,MAAK;AAAA,IAC/B,oBAAC,QAAA,EAAK,GAAE,qBAAA,CAAqB;AAAA,EAAA,GAC/B;AAAA,EAEF,SAAS,oBAAC,QAAA,EAAK,GAAE,6FAAA,CAA6F;AAAA,EAC9G,OACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA,oBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,MAAK;AAAA,IAC/B,oBAAC,QAAA,EAAK,GAAE,oBAAA,CAAoB;AAAA,EAAA,GAC9B;AAAA,EAEF,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA,oBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,MAAK;AAAA,IAC/B,oBAAC,QAAA,EAAK,GAAE,gBAAA,CAAgB;AAAA,EAAA,EAAA,CAC1B;AAEJ;AAOA,SAAwB,OAAO,EAAE,OAAO,QAAQ,OAAO,UAAU,MAAM,QAAQ,UAAU,OAAO,YAAY,GAAA,GAAmB;AAC7H,QAAM,IAAI,MAAM,IAAI;AACpB,QAAM,YACJ,SAAS,OAAO,OAAQ,QACtB,oBAAC,OAAA,EAAI,eAAW,MAAC,SAAQ,aAAY,OAAO,IAAI,QAAQ,IAAI,MAAK,QAAO,QAAO,gBAAe,aAAa,GAAG,eAAc,SAAQ,gBAAe,SAChJ,UAAA,WAAW,IAAI,EAAA,CAClB;AAEJ,QAAM,SAAS,YAAY;AAC3B,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,SAAS,UAAU,UAAU;AAAA,MACnC,WACE,SACI,oFAAoF,EAAE,MAAM,IAAI,SAAS,KACzG,8DAA8D,EAAE,GAAG,IAAI,SAAS;AAAA,MAGrF,UAAA;AAAA,QAAA,aAAa,oBAAC,QAAA,EAAK,WAAW,YAAY,SAAS,KAAK,UAAU,EAAE,IAAI,EAAE,IAAK,UAAA,WAAU;AAAA,QAC1F,qBAAC,OAAA,EAAI,WAAU,kBACZ,UAAA;AAAA,UAAA,6BAAU,OAAA,EAAI,WAAW,SAAS,KAAK,wBAAyB,UAAA,OAAM;AAAA,UACtE,YAAY,oBAAC,OAAA,EAAI,WAAW,SAAS,KAAK,8BAA+B,SAAA,CAAS;AAAA,QAAA,GACrF;AAAA,QACC,UACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,OAAO;AAAA,YAChB,WAAU;AAAA,YAET,UAAA,OAAO;AAAA,UAAA;AAAA,QAAA;AAAA,MACV;AAAA,IAAA;AAAA,EAAA;AAIR;"}
@@ -0,0 +1,14 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Avatar } from "../Avatar/Avatar.js";
3
+ function Person({ hideName = false, empty = "—", className = "", ...avatar }) {
4
+ const label = (avatar.name ?? "").trim() || avatar.email || "";
5
+ if (!label) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: empty });
6
+ return /* @__PURE__ */ jsxs("span", { className: `inline-flex min-w-0 items-center gap-2 ${className}`, "data-tip": hideName ? label : void 0, children: [
7
+ /* @__PURE__ */ jsx(Avatar, { ...avatar, name: label }),
8
+ !hideName && /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
9
+ ] });
10
+ }
11
+ export {
12
+ Person
13
+ };
14
+ //# sourceMappingURL=Person.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Person.js","sources":["../../../../../../src/v1/primitives/display/Person/Person.tsx"],"sourcesContent":["import React from 'react'\r\nimport Avatar, { type AvatarProps } from '../Avatar/Avatar'\r\n\r\nexport interface PersonProps extends AvatarProps {\r\n /** Só o avatar (o nome fica no cartão e no tooltip), ex: em colunas estreitas. */\r\n hideName?: boolean\r\n /** O que mostrar sem nome nem email. */\r\n empty?: React.ReactNode\r\n}\r\n\r\n/**\r\n * Uma pessoa, igual em todo o lado: o avatar (fotografia ou iniciais, com o cartão\r\n * de detalhes ao passar o rato) e o nome ao lado, numa linha. Usar sempre que se\r\n * mostra o nome de alguém (ex: solicitante, responsável, quem aprovou).\r\n */\r\nexport default function Person({ hideName = false, empty = '—', className = '', ...avatar }: PersonProps) {\r\n const label = (avatar.name ?? '').trim() || avatar.email || ''\r\n if (!label) return <span className=\"text-muted-foreground\">{empty}</span>\r\n return (\r\n <span className={`inline-flex min-w-0 items-center gap-2 ${className}`} data-tip={hideName ? label : undefined}>\r\n <Avatar {...avatar} name={label} />\r\n {!hideName && <span className=\"truncate\">{label}</span>}\r\n </span>\r\n )\r\n}\r\n\r\nexport { Person }\r\n"],"names":[],"mappings":";;AAeA,SAAwB,OAAO,EAAE,WAAW,OAAO,QAAQ,KAAK,YAAY,IAAI,GAAG,UAAuB;AACxG,QAAM,SAAS,OAAO,QAAQ,IAAI,KAAA,KAAU,OAAO,SAAS;AAC5D,MAAI,CAAC,MAAO,4BAAQ,QAAA,EAAK,WAAU,yBAAyB,UAAA,OAAM;AAClE,SACE,qBAAC,UAAK,WAAW,0CAA0C,SAAS,IAAI,YAAU,WAAW,QAAQ,QACnG,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAQ,GAAG,QAAQ,MAAM,MAAA,CAAO;AAAA,IAChC,CAAC,YAAY,oBAAC,QAAA,EAAK,WAAU,YAAY,UAAA,MAAA,CAAM;AAAA,EAAA,GAClD;AAEJ;"}
@@ -18,7 +18,7 @@ const Checkbox = forwardRef(function Checkbox2({ invalid, indeterminate = false,
18
18
  id: id ?? field.id,
19
19
  required: required ?? field.required,
20
20
  "aria-invalid": invalid || void 0,
21
- className: "peer absolute inset-0 m-0 size-full cursor-pointer appearance-none rounded-sm border border-border bg-card outline-none transition-[background-color,border-color,box-shadow] duration-150 checked:border-primary checked:bg-primary indeterminate:border-primary indeterminate:bg-primary focus-visible:ring-3 focus-visible:ring-primary/20 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/15 disabled:cursor-not-allowed disabled:bg-muted disabled:checked:border-border disabled:checked:bg-muted disabled:indeterminate:border-border disabled:indeterminate:bg-muted",
21
+ className: "peer absolute inset-0 m-0 size-full cursor-pointer appearance-none rounded-sm border border-muted-foreground/40 bg-card outline-none transition-[background-color,border-color,box-shadow] duration-150 checked:border-primary checked:bg-primary indeterminate:border-primary indeterminate:bg-primary focus-visible:ring-3 focus-visible:ring-primary/20 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/15 disabled:cursor-not-allowed disabled:bg-muted disabled:checked:border-border disabled:checked:bg-muted disabled:indeterminate:border-border disabled:indeterminate:bg-muted",
22
22
  ...props
23
23
  }
24
24
  ),
@@ -1 +1 @@
1
- {"version":3,"file":"Checkbox.js","sources":["../../../../../../src/v1/primitives/forms/Checkbox/Checkbox.tsx"],"sourcesContent":["import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'\r\nimport { CheckIcon } from '../../../foundations/icons/icons'\r\nimport { useField } from '../field'\r\n\r\nexport interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {\r\n /** Marca a caixa a vermelho (ex: aceitar os termos, por marcar ao submeter). */\r\n invalid?: boolean\r\n /**\r\n * Meio marcada: um traço em vez do visto (ex: \"selecionar todos\" com só\r\n * algumas marcadas). Só o aspeto: ao clicar, o browser tira-o e a caixa passa\r\n * a marcada ou desmarcada pelo `onChange`, como sempre. Para leitores de ecrã é\r\n * \"parcialmente marcada\".\r\n */\r\n indeterminate?: boolean\r\n}\r\n\r\n/**\r\n * Caixa de marcar.\r\n *\r\n * Dentro de um [FormField](?path=/docs/v1-components-forms-formfield--docs) fica\r\n * ligada à etiqueta automaticamente. Fora dele, dar-lhe um `aria-label` ou\r\n * envolvê-la num `<label>`: sozinha não tem nome para os leitores de ecrã.\r\n *\r\n * **Aceita os atributos HTML do [&lt;input type=\"checkbox\"&gt;](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/checkbox)**, com o mesmo comportamento: `checked`/`defaultChecked`, `onChange`, `required`, `disabled`, `name`, `value`, `ref`, ...\r\n * Só os próprios do V1 aparecem na tabela abaixo.\r\n * Diferenças: o `className` aplica-se à caixa à volta; o `type` é sempre `checkbox`.\r\n */\r\nconst Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox({ invalid, indeterminate = false, className = '', id, required, ...props }, ref) {\r\n const field = useField()\r\n // O meio-marcado não é atributo HTML, só propriedade do elemento: vai pela ref.\r\n const caixa = useRef<HTMLInputElement>(null)\r\n useImperativeHandle(ref, () => caixa.current!)\r\n // A cada render, e não só quando a prop muda: um clique tira-o no browser, e\r\n // se a app o mantiver, tem de voltar.\r\n useEffect(() => { if (caixa.current) caixa.current.indeterminate = indeterminate })\r\n return (\r\n <span className={`relative inline-flex size-4 shrink-0 ${className}`}>\r\n <input\r\n ref={caixa}\r\n type=\"checkbox\"\r\n id={id ?? field.id}\r\n required={required ?? field.required}\r\n aria-invalid={invalid || undefined}\r\n className={\r\n 'peer absolute inset-0 m-0 size-full cursor-pointer appearance-none rounded-sm border border-border bg-card outline-none ' +\r\n 'transition-[background-color,border-color,box-shadow] duration-150 ' +\r\n 'checked:border-primary checked:bg-primary indeterminate:border-primary indeterminate:bg-primary ' +\r\n 'focus-visible:ring-3 focus-visible:ring-primary/20 ' +\r\n 'aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/15 ' +\r\n 'disabled:cursor-not-allowed disabled:bg-muted disabled:checked:border-border disabled:checked:bg-muted disabled:indeterminate:border-border disabled:indeterminate:bg-muted'\r\n }\r\n {...props}\r\n />\r\n {indeterminate ? (\r\n <span aria-hidden className=\"pointer-events-none absolute inset-0 m-auto h-0.5 w-2 rounded-full bg-primary-foreground peer-disabled:bg-muted-foreground\" />\r\n ) : (\r\n <CheckIcon\r\n size={12}\r\n className=\"pointer-events-none absolute inset-0 m-auto hidden text-primary-foreground peer-checked:block peer-disabled:text-muted-foreground\"\r\n />\r\n )}\r\n </span>\r\n )\r\n})\r\n\r\nexport default Checkbox\r\n"],"names":["Checkbox"],"mappings":";;;;AA2BA,MAAM,WAAW,WAA4C,SAASA,UAAS,EAAE,SAAS,gBAAgB,OAAO,YAAY,IAAI,IAAI,UAAU,GAAG,MAAA,GAAS,KAAK;AAC9J,QAAM,QAAQ,SAAA;AAEd,QAAM,QAAQ,OAAyB,IAAI;AAC3C,sBAAoB,KAAK,MAAM,MAAM,OAAQ;AAG7C,YAAU,MAAM;AAAE,QAAI,MAAM,QAAS,OAAM,QAAQ,gBAAgB;AAAA,EAAc,CAAC;AAClF,SACE,qBAAC,QAAA,EAAK,WAAW,wCAAwC,SAAS,IAChE,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAK;AAAA,QACL,IAAI,MAAM,MAAM;AAAA,QAChB,UAAU,YAAY,MAAM;AAAA,QAC5B,gBAAc,WAAW;AAAA,QACzB,WACE;AAAA,QAOD,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,IAEL,gBACC,oBAAC,QAAA,EAAK,eAAW,MAAC,WAAU,8HAA6H,IAEzJ;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAM;AAAA,QACN,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EACZ,GAEJ;AAEJ,CAAC;"}
1
+ {"version":3,"file":"Checkbox.js","sources":["../../../../../../src/v1/primitives/forms/Checkbox/Checkbox.tsx"],"sourcesContent":["import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'\r\nimport { CheckIcon } from '../../../foundations/icons/icons'\r\nimport { useField } from '../field'\r\n\r\nexport interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {\r\n /** Marca a caixa a vermelho (ex: aceitar os termos, por marcar ao submeter). */\r\n invalid?: boolean\r\n /**\r\n * Meio marcada: um traço em vez do visto (ex: \"selecionar todos\" com só\r\n * algumas marcadas). Só o aspeto: ao clicar, o browser tira-o e a caixa passa\r\n * a marcada ou desmarcada pelo `onChange`, como sempre. Para leitores de ecrã é\r\n * \"parcialmente marcada\".\r\n */\r\n indeterminate?: boolean\r\n}\r\n\r\n/**\r\n * Caixa de marcar.\r\n *\r\n * Dentro de um [FormField](?path=/docs/v1-components-forms-formfield--docs) fica\r\n * ligada à etiqueta automaticamente. Fora dele, dar-lhe um `aria-label` ou\r\n * envolvê-la num `<label>`: sozinha não tem nome para os leitores de ecrã.\r\n *\r\n * **Aceita os atributos HTML do [&lt;input type=\"checkbox\"&gt;](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/checkbox)**, com o mesmo comportamento: `checked`/`defaultChecked`, `onChange`, `required`, `disabled`, `name`, `value`, `ref`, ...\r\n * Só os próprios do V1 aparecem na tabela abaixo.\r\n * Diferenças: o `className` aplica-se à caixa à volta; o `type` é sempre `checkbox`.\r\n */\r\nconst Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox({ invalid, indeterminate = false, className = '', id, required, ...props }, ref) {\r\n const field = useField()\r\n // O meio-marcado não é atributo HTML, só propriedade do elemento: vai pela ref.\r\n const caixa = useRef<HTMLInputElement>(null)\r\n useImperativeHandle(ref, () => caixa.current!)\r\n // A cada render, e não só quando a prop muda: um clique tira-o no browser, e\r\n // se a app o mantiver, tem de voltar.\r\n useEffect(() => { if (caixa.current) caixa.current.indeterminate = indeterminate })\r\n return (\r\n <span className={`relative inline-flex size-4 shrink-0 ${className}`}>\r\n <input\r\n ref={caixa}\r\n type=\"checkbox\"\r\n id={id ?? field.id}\r\n required={required ?? field.required}\r\n aria-invalid={invalid || undefined}\r\n className={\r\n 'peer absolute inset-0 m-0 size-full cursor-pointer appearance-none rounded-sm border border-muted-foreground/40 bg-card outline-none ' +\r\n 'transition-[background-color,border-color,box-shadow] duration-150 ' +\r\n 'checked:border-primary checked:bg-primary indeterminate:border-primary indeterminate:bg-primary ' +\r\n 'focus-visible:ring-3 focus-visible:ring-primary/20 ' +\r\n 'aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/15 ' +\r\n 'disabled:cursor-not-allowed disabled:bg-muted disabled:checked:border-border disabled:checked:bg-muted disabled:indeterminate:border-border disabled:indeterminate:bg-muted'\r\n }\r\n {...props}\r\n />\r\n {indeterminate ? (\r\n <span aria-hidden className=\"pointer-events-none absolute inset-0 m-auto h-0.5 w-2 rounded-full bg-primary-foreground peer-disabled:bg-muted-foreground\" />\r\n ) : (\r\n <CheckIcon\r\n size={12}\r\n className=\"pointer-events-none absolute inset-0 m-auto hidden text-primary-foreground peer-checked:block peer-disabled:text-muted-foreground\"\r\n />\r\n )}\r\n </span>\r\n )\r\n})\r\n\r\nexport default Checkbox\r\n"],"names":["Checkbox"],"mappings":";;;;AA2BA,MAAM,WAAW,WAA4C,SAASA,UAAS,EAAE,SAAS,gBAAgB,OAAO,YAAY,IAAI,IAAI,UAAU,GAAG,MAAA,GAAS,KAAK;AAC9J,QAAM,QAAQ,SAAA;AAEd,QAAM,QAAQ,OAAyB,IAAI;AAC3C,sBAAoB,KAAK,MAAM,MAAM,OAAQ;AAG7C,YAAU,MAAM;AAAE,QAAI,MAAM,QAAS,OAAM,QAAQ,gBAAgB;AAAA,EAAc,CAAC;AAClF,SACE,qBAAC,QAAA,EAAK,WAAW,wCAAwC,SAAS,IAChE,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAK;AAAA,QACL,IAAI,MAAM,MAAM;AAAA,QAChB,UAAU,YAAY,MAAM;AAAA,QAC5B,gBAAc,WAAW;AAAA,QACzB,WACE;AAAA,QAOD,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,IAEL,gBACC,oBAAC,QAAA,EAAK,eAAW,MAAC,WAAU,8HAA6H,IAEzJ;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAM;AAAA,QACN,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EACZ,GAEJ;AAEJ,CAAC;"}
@@ -0,0 +1,150 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useRef, useEffect } from "react";
3
+ import { useField, fieldClasses, readOnlyClasses } from "../field.js";
4
+ const isoToBr = (iso) => {
5
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso ?? "");
6
+ return m ? `${m[3]}/${m[2]}/${m[1]}` : "";
7
+ };
8
+ const brToIso = (br) => {
9
+ const m = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(br.trim());
10
+ if (!m) return "";
11
+ const [, dd, mm, yyyy] = m;
12
+ const d = /* @__PURE__ */ new Date(`${yyyy}-${mm}-${dd}T00:00:00`);
13
+ if (Number.isNaN(d.getTime()) || d.getDate() !== Number(dd) || d.getMonth() + 1 !== Number(mm)) return "";
14
+ return `${yyyy}-${mm}-${dd}`;
15
+ };
16
+ const mask = (raw) => {
17
+ const d = raw.replace(/\D/g, "").slice(0, 8);
18
+ if (d.length <= 2) return d;
19
+ if (d.length <= 4) return `${d.slice(0, 2)}/${d.slice(2)}`;
20
+ return `${d.slice(0, 2)}/${d.slice(2, 4)}/${d.slice(4)}`;
21
+ };
22
+ function CalendarIcon() {
23
+ return /* @__PURE__ */ jsxs("svg", { "aria-hidden": true, viewBox: "0 0 24 24", width: 13, height: 13, fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
24
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
25
+ /* @__PURE__ */ jsx("path", { d: "M16 2v4M8 2v4M3 10h18" })
26
+ ] });
27
+ }
28
+ function DateInput({
29
+ value,
30
+ defaultValue,
31
+ onChange,
32
+ onBlur,
33
+ onKeyDown,
34
+ min,
35
+ max,
36
+ disabled,
37
+ readOnly,
38
+ required,
39
+ autoFocus,
40
+ id,
41
+ invalid,
42
+ placeholder = "dd/mm/aaaa",
43
+ "aria-label": ariaLabel,
44
+ calendarLabel = "Abrir calendário",
45
+ className = ""
46
+ }) {
47
+ const field = useField();
48
+ const controlled = value !== void 0;
49
+ const [iso, setIso] = useState(value ?? defaultValue ?? "");
50
+ const [text, setText] = useState(isoToBr(value ?? defaultValue ?? ""));
51
+ const nativeRef = useRef(null);
52
+ useEffect(() => {
53
+ if (!controlled) return;
54
+ const outside = value ?? "";
55
+ if (outside === brToIso(text)) return;
56
+ setIso(outside);
57
+ setText(isoToBr(outside));
58
+ }, [value, controlled]);
59
+ const onBlurRef = useRef(onBlur);
60
+ useEffect(() => {
61
+ onBlurRef.current = onBlur;
62
+ });
63
+ const pendingCommit = useRef(false);
64
+ useEffect(() => {
65
+ if (!pendingCommit.current) return;
66
+ pendingCommit.current = false;
67
+ onBlurRef.current?.();
68
+ }, [value]);
69
+ const emit = (next) => {
70
+ setIso(next);
71
+ onChange?.(next);
72
+ };
73
+ const type = (raw) => {
74
+ const masked = mask(raw);
75
+ setText(masked);
76
+ if (masked === "") return emit("");
77
+ const next = brToIso(masked);
78
+ if (next) emit(next);
79
+ };
80
+ const leave = () => {
81
+ if (text !== "" && !brToIso(text)) setText(isoToBr(iso));
82
+ onBlur?.();
83
+ };
84
+ const openCalendar = () => {
85
+ const el = nativeRef.current;
86
+ if (!el || disabled || readOnly) return;
87
+ if (typeof el.showPicker === "function") el.showPicker();
88
+ else el.click();
89
+ };
90
+ return /* @__PURE__ */ jsxs("span", { className: `relative inline-flex w-full items-center ${className}`, children: [
91
+ /* @__PURE__ */ jsx(
92
+ "input",
93
+ {
94
+ id: id ?? field.id,
95
+ type: "text",
96
+ inputMode: "numeric",
97
+ autoComplete: "off",
98
+ "aria-label": ariaLabel,
99
+ "aria-invalid": invalid || void 0,
100
+ placeholder,
101
+ value: text,
102
+ onChange: (e) => type(e.target.value),
103
+ onBlur: leave,
104
+ onKeyDown,
105
+ autoFocus,
106
+ disabled,
107
+ readOnly,
108
+ required: required ?? field.required,
109
+ className: `h-control ${fieldClasses} ${readOnlyClasses} pr-8 tabular-nums`
110
+ }
111
+ ),
112
+ !disabled && !readOnly && /* @__PURE__ */ jsx(
113
+ "button",
114
+ {
115
+ type: "button",
116
+ tabIndex: -1,
117
+ onClick: openCalendar,
118
+ "aria-label": calendarLabel,
119
+ "data-tip": calendarLabel,
120
+ className: "absolute right-1.5 flex h-6 w-6 cursor-pointer items-center justify-center rounded border-0 bg-transparent p-0 text-muted-foreground hover:text-foreground",
121
+ children: /* @__PURE__ */ jsx(CalendarIcon, {})
122
+ }
123
+ ),
124
+ /* @__PURE__ */ jsx(
125
+ "input",
126
+ {
127
+ ref: nativeRef,
128
+ type: "date",
129
+ tabIndex: -1,
130
+ "aria-hidden": true,
131
+ value: iso,
132
+ min,
133
+ max,
134
+ onChange: (e) => {
135
+ const v = e.target.value;
136
+ setText(isoToBr(v));
137
+ emit(v);
138
+ if (controlled) pendingCommit.current = true;
139
+ else onBlurRef.current?.();
140
+ },
141
+ className: "sr-only absolute right-1.5 bottom-0 h-0 w-0 border-0 p-0"
142
+ }
143
+ )
144
+ ] });
145
+ }
146
+ export {
147
+ DateInput,
148
+ DateInput as default
149
+ };
150
+ //# sourceMappingURL=DateInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DateInput.js","sources":["../../../../../../src/v1/primitives/forms/DateInput/DateInput.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react'\r\nimport { fieldClasses, readOnlyClasses, useField } from '../field'\r\n\r\nexport interface DateInputProps {\r\n /** Data em ISO (`aaaa-mm-dd`) ou `''` (vazio). Controlado. */\r\n value?: string\r\n /** Valor inicial, sem controlar (ISO). */\r\n defaultValue?: string\r\n /** Recebe só datas completas e válidas, em ISO; `''` quando se apaga. Enquanto se escreve, não é chamado. */\r\n onChange?: (value: string) => void\r\n /** Ao terminar a edição: sair do campo ou escolher no calendário. */\r\n onBlur?: () => void\r\n onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void\r\n /** Limites, em ISO (só no calendário). */\r\n min?: string\r\n max?: string\r\n disabled?: boolean\r\n readOnly?: boolean\r\n required?: boolean\r\n autoFocus?: boolean\r\n id?: string\r\n /** Marca o campo a vermelho (ex: obrigatório por preencher ao submeter). */\r\n invalid?: boolean\r\n placeholder?: string\r\n /** Nome do campo para leitores de ecrã, quando não há etiqueta. */\r\n 'aria-label'?: string\r\n /** Texto do botão do calendário. */\r\n calendarLabel?: string\r\n /** Classes da caixa à volta (ex: largura). */\r\n className?: string\r\n}\r\n\r\nconst isoToBr = (iso: string): string => {\r\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(iso ?? '')\r\n return m ? `${m[3]}/${m[2]}/${m[1]}` : ''\r\n}\r\n\r\nconst brToIso = (br: string): string => {\r\n const m = /^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/.exec(br.trim())\r\n if (!m) return ''\r\n const [, dd, mm, yyyy] = m\r\n const d = new Date(`${yyyy}-${mm}-${dd}T00:00:00`)\r\n // Rejeita 31/02 e afins: o Date normaliza para 03/03 em vez de falhar.\r\n if (Number.isNaN(d.getTime()) || d.getDate() !== Number(dd) || d.getMonth() + 1 !== Number(mm)) return ''\r\n return `${yyyy}-${mm}-${dd}`\r\n}\r\n\r\n/** Mascara o que se escreve, deixando só dígitos e pondo as barras no sítio. */\r\nconst mask = (raw: string): string => {\r\n const d = raw.replace(/\\D/g, '').slice(0, 8)\r\n if (d.length <= 2) return d\r\n if (d.length <= 4) return `${d.slice(0, 2)}/${d.slice(2)}`\r\n return `${d.slice(0, 2)}/${d.slice(2, 4)}/${d.slice(4)}`\r\n}\r\n\r\nfunction CalendarIcon() {\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 <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" />\r\n <path d=\"M16 2v4M8 2v4M3 10h18\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Campo de data, sempre em **dd/mm/aaaa**.\r\n *\r\n * O `<input type=\"date\">` do browser mostra a data no idioma do BROWSER, não no\r\n * da página (num Chrome em inglês aparece mm/dd/aaaa), e não há atributo que o\r\n * force. Aqui o texto é escrito pela biblioteca, com máscara; o calendário\r\n * continua a ser o nativo (acessível e traduzido), aberto pelo ícone.\r\n *\r\n * O valor entra e sai em ISO (`aaaa-mm-dd`). Dentro de um\r\n * [FormField](?path=/docs/v1-components-forms-formfield--docs) fica ligado à\r\n * etiqueta automaticamente.\r\n */\r\nexport default function DateInput({\r\n value,\r\n defaultValue,\r\n onChange,\r\n onBlur,\r\n onKeyDown,\r\n min,\r\n max,\r\n disabled,\r\n readOnly,\r\n required,\r\n autoFocus,\r\n id,\r\n invalid,\r\n placeholder = 'dd/mm/aaaa',\r\n 'aria-label': ariaLabel,\r\n calendarLabel = 'Abrir calendário',\r\n className = '',\r\n}: DateInputProps) {\r\n const field = useField()\r\n const controlled = value !== undefined\r\n const [iso, setIso] = useState(value ?? defaultValue ?? '')\r\n const [text, setText] = useState(isoToBr(value ?? defaultValue ?? ''))\r\n const nativeRef = useRef<HTMLInputElement>(null)\r\n\r\n // Segue o valor de fora, exceto enquanto o campo tem uma data a meio: senão\r\n // um re-render durante a escrita apagava os dígitos já introduzidos.\r\n useEffect(() => {\r\n if (!controlled) return\r\n const outside = value ?? ''\r\n if (outside === brToIso(text)) return\r\n setIso(outside)\r\n setText(isoToBr(outside))\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [value, controlled])\r\n\r\n // Escolher no calendário conta como terminar a edição (quem grava está quase\r\n // sempre no `onBlur`). O aviso espera que o valor novo volte em `value`:\r\n // chamado logo, o `onBlur` de fora ainda lia o valor anterior.\r\n const onBlurRef = useRef(onBlur)\r\n useEffect(() => {\r\n onBlurRef.current = onBlur\r\n })\r\n const pendingCommit = useRef(false)\r\n useEffect(() => {\r\n if (!pendingCommit.current) return\r\n pendingCommit.current = false\r\n onBlurRef.current?.()\r\n }, [value])\r\n\r\n const emit = (next: string) => {\r\n setIso(next)\r\n onChange?.(next)\r\n }\r\n\r\n const type = (raw: string) => {\r\n const masked = mask(raw)\r\n setText(masked)\r\n if (masked === '') return emit('')\r\n const next = brToIso(masked)\r\n if (next) emit(next)\r\n }\r\n\r\n // Ao sair, uma data incompleta ou impossível volta ao último valor bom.\r\n const leave = () => {\r\n if (text !== '' && !brToIso(text)) setText(isoToBr(iso))\r\n onBlur?.()\r\n }\r\n\r\n const openCalendar = () => {\r\n const el = nativeRef.current\r\n if (!el || disabled || readOnly) return\r\n // showPicker() é o caminho normal; sem ele (browsers antigos) resta o clique.\r\n if (typeof (el as HTMLInputElement & { showPicker?: () => void }).showPicker === 'function') el.showPicker()\r\n else el.click()\r\n }\r\n\r\n return (\r\n <span className={`relative inline-flex w-full items-center ${className}`}>\r\n <input\r\n id={id ?? field.id}\r\n type=\"text\"\r\n inputMode=\"numeric\"\r\n autoComplete=\"off\"\r\n aria-label={ariaLabel}\r\n aria-invalid={invalid || undefined}\r\n placeholder={placeholder}\r\n value={text}\r\n onChange={(e) => type(e.target.value)}\r\n onBlur={leave}\r\n onKeyDown={onKeyDown}\r\n autoFocus={autoFocus}\r\n disabled={disabled}\r\n readOnly={readOnly}\r\n required={required ?? field.required}\r\n className={`h-control ${fieldClasses} ${readOnlyClasses} pr-8 tabular-nums`}\r\n />\r\n {!disabled && !readOnly && (\r\n <button\r\n type=\"button\"\r\n tabIndex={-1}\r\n onClick={openCalendar}\r\n aria-label={calendarLabel}\r\n data-tip={calendarLabel}\r\n className=\"absolute right-1.5 flex h-6 w-6 cursor-pointer items-center justify-center rounded border-0 bg-transparent p-0 text-muted-foreground hover:text-foreground\"\r\n >\r\n <CalendarIcon />\r\n </button>\r\n )}\r\n {/* O nativo só serve de calendário: nunca se vê, por isso o idioma dele não\r\n chega ao ecrã. `sr-only` em vez de display:none — o showPicker() recusa\r\n abrir num campo escondido. */}\r\n <input\r\n ref={nativeRef}\r\n type=\"date\"\r\n tabIndex={-1}\r\n aria-hidden\r\n value={iso}\r\n min={min}\r\n max={max}\r\n onChange={(e) => {\r\n const v = e.target.value\r\n setText(isoToBr(v))\r\n emit(v)\r\n // Sem `value` de fora não há nada por que esperar.\r\n if (controlled) pendingCommit.current = true\r\n else onBlurRef.current?.()\r\n }}\r\n className=\"sr-only absolute right-1.5 bottom-0 h-0 w-0 border-0 p-0\"\r\n />\r\n </span>\r\n )\r\n}\r\n\r\nexport { DateInput }\r\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { fieldClasses } from "../field.js";
4
+ import { SearchIcon, CloseIcon } from "../../../foundations/icons/icons.js";
5
+ const SearchInput = forwardRef(function SearchInput2({ value, onChange, placeholder = "Pesquisar…", label, clearLabel = "Limpar pesquisa", className = "w-56", icon, onKeyDown, ...props }, ref) {
6
+ return /* @__PURE__ */ jsxs("div", { className: `relative ${className}`, children: [
7
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "pointer-events-none absolute top-1/2 left-2.5 flex -translate-y-1/2 text-muted-foreground", children: icon ?? /* @__PURE__ */ jsx(SearchIcon, { size: 14 }) }),
8
+ /* @__PURE__ */ jsx(
9
+ "input",
10
+ {
11
+ ref,
12
+ type: "search",
13
+ value,
14
+ onChange: (e) => onChange(e.target.value),
15
+ onKeyDown: (e) => {
16
+ if (e.key === "Escape" && value) {
17
+ e.stopPropagation();
18
+ onChange("");
19
+ }
20
+ onKeyDown?.(e);
21
+ },
22
+ placeholder,
23
+ "aria-label": label ?? placeholder,
24
+ className: `h-control ${fieldClasses} pr-7 pl-8 [&::-webkit-search-cancel-button]:hidden`,
25
+ ...props
26
+ }
27
+ ),
28
+ value && /* @__PURE__ */ jsx(
29
+ "button",
30
+ {
31
+ type: "button",
32
+ onClick: () => onChange(""),
33
+ "aria-label": clearLabel,
34
+ className: "absolute top-1/2 right-1.5 flex h-5 w-5 -translate-y-1/2 cursor-pointer items-center justify-center rounded border-0 bg-transparent p-0 text-muted-foreground hover:text-foreground focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:outline-none",
35
+ children: /* @__PURE__ */ jsx(CloseIcon, { size: 12 })
36
+ }
37
+ )
38
+ ] });
39
+ });
40
+ export {
41
+ SearchInput as default
42
+ };
43
+ //# sourceMappingURL=SearchInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchInput.js","sources":["../../../../../../src/v1/primitives/forms/SearchInput/SearchInput.tsx"],"sourcesContent":["import React, { forwardRef } from 'react'\r\nimport { fieldClasses } from '../field'\r\nimport { CloseIcon, SearchIcon } from '../../../foundations/icons/icons'\r\n\r\nexport interface SearchInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type' | 'className'> {\r\n value: string\r\n /** O texto novo, a cada tecla (e `''` ao limpar). */\r\n onChange: (value: string) => void\r\n /** Nome do campo para leitores de ecrã. Por omissão, o placeholder. */\r\n label?: string\r\n /** Nome do botão ×, para leitores de ecrã. Por omissão: \"Limpar pesquisa\". */\r\n clearLabel?: string\r\n /** Largura e posição (classes do invólucro). Por omissão `w-56`. */\r\n className?: string\r\n /** Ícone à esquerda, no lugar da lupa, quando o filtro é de algo concreto (ex: empresa, colaborador). Tamanho 14. */\r\n icon?: React.ReactNode\r\n}\r\n\r\n/**\r\n * Caixa de pesquisa das barras de páginas, tabelas e listas: a mesma altura\r\n * dos botões ao lado. Lupa à esquerda; com texto, um × para limpar (Esc também\r\n * limpa).\r\n *\r\n * Aceita os outros atributos do `<input>` (`autoFocus`, `name`, `onKeyDown`, …).\r\n */\r\nconst SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(function SearchInput(\r\n { value, onChange, placeholder = 'Pesquisar…', label, clearLabel = 'Limpar pesquisa', className = 'w-56', icon, onKeyDown, ...props },\r\n ref,\r\n) {\r\n return (\r\n <div className={`relative ${className}`}>\r\n <span aria-hidden className=\"pointer-events-none absolute top-1/2 left-2.5 flex -translate-y-1/2 text-muted-foreground\">\r\n {icon ?? <SearchIcon size={14} />}\r\n </span>\r\n <input\r\n ref={ref}\r\n type=\"search\"\r\n value={value}\r\n onChange={(e) => onChange(e.target.value)}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape' && value) {\r\n e.stopPropagation()\r\n onChange('')\r\n }\r\n onKeyDown?.(e)\r\n }}\r\n placeholder={placeholder}\r\n aria-label={label ?? placeholder}\r\n className={`h-control ${fieldClasses} pr-7 pl-8 [&::-webkit-search-cancel-button]:hidden`}\r\n {...props}\r\n />\r\n {value && (\r\n <button\r\n type=\"button\"\r\n onClick={() => onChange('')}\r\n aria-label={clearLabel}\r\n className=\"absolute top-1/2 right-1.5 flex h-5 w-5 -translate-y-1/2 cursor-pointer items-center justify-center rounded border-0 bg-transparent p-0 text-muted-foreground hover:text-foreground focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:outline-none\"\r\n >\r\n <CloseIcon size={12} />\r\n </button>\r\n )}\r\n </div>\r\n )\r\n})\r\n\r\nexport default SearchInput\r\n"],"names":["SearchInput"],"mappings":";;;;AAyBA,MAAM,cAAc,WAA+C,SAASA,aAC1E,EAAE,OAAO,UAAU,cAAc,cAAc,OAAO,aAAa,mBAAmB,YAAY,QAAQ,MAAM,WAAW,GAAG,MAAA,GAC9H,KACA;AACA,SACE,qBAAC,OAAA,EAAI,WAAW,YAAY,SAAS,IACnC,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAK,eAAW,MAAC,WAAU,6FACzB,kBAAQ,oBAAC,YAAA,EAAW,MAAM,GAAA,CAAI,EAAA,CACjC;AAAA,IACA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL;AAAA,QACA,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,QACxC,WAAW,CAAC,MAAM;AAChB,cAAI,EAAE,QAAQ,YAAY,OAAO;AAC/B,cAAE,gBAAA;AACF,qBAAS,EAAE;AAAA,UACb;AACA,sBAAY,CAAC;AAAA,QACf;AAAA,QACA;AAAA,QACA,cAAY,SAAS;AAAA,QACrB,WAAW,aAAa,YAAY;AAAA,QACnC,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,IAEL,SACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM,SAAS,EAAE;AAAA,QAC1B,cAAY;AAAA,QACZ,WAAU;AAAA,QAEV,UAAA,oBAAC,WAAA,EAAU,MAAM,GAAA,CAAI;AAAA,MAAA;AAAA,IAAA;AAAA,EACvB,GAEJ;AAEJ,CAAC;"}
@@ -1,29 +1,207 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { forwardRef } from "react";
2
+ import React, { forwardRef, useImperativeHandle, useState, useMemo, useRef, useEffect } from "react";
3
+ import { createPortal } from "react-dom";
3
4
  import { ChevronDownIcon } from "../../../foundations/icons/icons.js";
4
- import { useField, fieldClasses } from "../field.js";
5
- const SelectInput = forwardRef(function SelectInput2({ invalid, placeholder, className = "", id, required, children, ...props }, ref) {
5
+ import { useField, fieldBoxClasses } from "../field.js";
6
+ import { useFloatingPanel, PANEL_CLASS, itemClass } from "../../../capabilities/overlay/useFloatingPanel.js";
7
+ const DEFAULT_SELECT_LABELS = {
8
+ search: "Search…",
9
+ searchIn: "Search in {label}",
10
+ noResults: "No results"
11
+ };
12
+ const Check = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4 shrink-0 text-proef-blue dark:text-sky-300", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) });
13
+ function optionsFromChildren(children) {
14
+ const out = [];
15
+ React.Children.forEach(children, (child) => {
16
+ if (!React.isValidElement(child)) return;
17
+ const props = child.props;
18
+ if (child.type === "optgroup") return out.push(...optionsFromChildren(props.children));
19
+ if (child.type !== "option") return;
20
+ const text = React.Children.toArray(props.children).join("");
21
+ out.push({ value: props.value !== void 0 ? String(props.value) : text, label: text, disabled: props.disabled });
22
+ });
23
+ return out;
24
+ }
25
+ const SelectInput = forwardRef(function SelectInput2({
26
+ value: controlled,
27
+ defaultValue = "",
28
+ options,
29
+ children,
30
+ onValueChange,
31
+ onChange,
32
+ placeholder,
33
+ placeholderOption = true,
34
+ emptyOption,
35
+ searchable,
36
+ disabled = false,
37
+ required,
38
+ invalid,
39
+ id,
40
+ name,
41
+ "aria-label": ariaLabel,
42
+ "aria-labelledby": ariaLabelledBy,
43
+ variant = "field",
44
+ pillClassName = "bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-200",
45
+ tall = false,
46
+ labels,
47
+ className = ""
48
+ }, ref) {
49
+ const L = { ...DEFAULT_SELECT_LABELS, ...labels };
6
50
  const field = useField();
7
- return /* @__PURE__ */ jsxs("div", { className: `relative min-w-0 ${className}`, children: [
51
+ const p = useFloatingPanel("left");
52
+ useImperativeHandle(ref, () => p.triggerRef.current);
53
+ const [own, setOwn] = useState(defaultValue);
54
+ const value = controlled ?? own;
55
+ const list = useMemo(() => {
56
+ const base = options ? options.map((o) => typeof o === "string" ? { value: o, label: o } : o) : optionsFromChildren(children);
57
+ const empty = emptyOption ?? (placeholder !== void 0 && placeholderOption ? placeholder : void 0);
58
+ return empty !== void 0 && !base.some((o) => o.value === "") ? [{ value: "", label: empty }, ...base] : base;
59
+ }, [options, children, emptyOption, placeholder, placeholderOption]);
60
+ const [query, setQuery] = useState("");
61
+ const withSearch = searchable ?? list.length > 10;
62
+ const visible = useMemo(() => {
63
+ const q = query.trim().toLowerCase();
64
+ return q ? list.filter((o) => o.label.toLowerCase().includes(q) || o.description?.toLowerCase().includes(q)) : list;
65
+ }, [list, query]);
66
+ const [active, setActive] = useState(0);
67
+ const searchRef = useRef(null);
68
+ const chosen = list.find((o) => o.value === value);
69
+ useEffect(() => {
70
+ if (!p.open) return;
71
+ setQuery("");
72
+ setActive(Math.max(0, list.findIndex((o) => o.value === value)));
73
+ if (withSearch) setTimeout(() => searchRef.current?.focus(), 0);
74
+ }, [p.open]);
75
+ useEffect(() => {
76
+ if (p.open) p.panelRef.current?.querySelector(`[data-index="${active}"]`)?.scrollIntoView?.({ block: "nearest" });
77
+ }, [active, p.open, p.panelRef]);
78
+ const choose = (o) => {
79
+ if (o.disabled) return;
80
+ p.close();
81
+ p.triggerRef.current?.focus();
82
+ if (o.value === value) return;
83
+ if (controlled === void 0) setOwn(o.value);
84
+ onValueChange?.(o.value);
85
+ if (onChange) {
86
+ const target = { value: o.value, name };
87
+ onChange({ target, currentTarget: target });
88
+ }
89
+ };
90
+ const keys = (e) => {
91
+ if (!p.open) {
92
+ if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
93
+ e.preventDefault();
94
+ p.setOpen(true);
95
+ }
96
+ return;
97
+ }
98
+ if (e.key === "Escape") {
99
+ e.preventDefault();
100
+ e.stopPropagation();
101
+ p.close();
102
+ p.triggerRef.current?.focus();
103
+ } else if (e.key === "ArrowDown") {
104
+ e.preventDefault();
105
+ setActive((i) => Math.min(visible.length - 1, i + 1));
106
+ } else if (e.key === "ArrowUp") {
107
+ e.preventDefault();
108
+ setActive((i) => Math.max(0, i - 1));
109
+ } else if (e.key === "Home") {
110
+ e.preventDefault();
111
+ setActive(0);
112
+ } else if (e.key === "End") {
113
+ e.preventDefault();
114
+ setActive(visible.length - 1);
115
+ } else if (e.key === "Enter") {
116
+ e.preventDefault();
117
+ const o = visible[active];
118
+ if (o) choose(o);
119
+ } else if (e.key === "Tab") {
120
+ p.close();
121
+ } else if (!withSearch && e.key.length === 1) {
122
+ const i = visible.findIndex((o) => o.label.toLowerCase().startsWith(e.key.toLowerCase()));
123
+ if (i >= 0) setActive(i);
124
+ }
125
+ };
126
+ const pill = variant === "pill";
127
+ const showsPlaceholder = !chosen || chosen.value === "" && placeholder !== void 0 && emptyOption === void 0;
128
+ return /* @__PURE__ */ jsxs("div", { className: `relative min-w-0 ${className}`, onKeyDown: keys, children: [
8
129
  /* @__PURE__ */ jsxs(
9
- "select",
130
+ "button",
10
131
  {
11
- ref,
132
+ ref: p.triggerRef,
12
133
  id: id ?? field.id,
13
- required: required ?? field.required,
134
+ type: "button",
135
+ disabled,
136
+ "aria-haspopup": "listbox",
137
+ "aria-expanded": p.open,
138
+ "aria-label": ariaLabel,
139
+ "aria-labelledby": ariaLabelledBy,
14
140
  "aria-invalid": invalid || void 0,
15
- className: `h-control cursor-pointer appearance-none pr-7 ${fieldClasses}`,
16
- ...props,
141
+ "aria-required": (required ?? field.required) || void 0,
142
+ onClick: (e) => {
143
+ e.stopPropagation();
144
+ p.setOpen((o) => !o);
145
+ },
146
+ className: pill ? `inline-flex cursor-pointer appearance-none items-center gap-1 rounded-full px-2.5 py-1 text-xs font-bold whitespace-nowrap transition hover:brightness-95 focus:outline-none focus-visible:ring-2 focus-visible:ring-proef-blue disabled:cursor-not-allowed disabled:opacity-50 ${pillClassName}` : `flex w-full min-w-0 cursor-pointer appearance-none items-center justify-between gap-2 text-left ${tall ? "h-[38px]" : "h-control"} ${fieldBoxClasses}`,
17
147
  children: [
18
- placeholder !== void 0 && /* @__PURE__ */ jsx("option", { value: "", children: placeholder }),
19
- children
148
+ /* @__PURE__ */ jsx("span", { className: `truncate ${showsPlaceholder && !pill ? "text-muted-foreground/70" : ""}`, children: showsPlaceholder ? placeholder ?? chosen?.label ?? "" : chosen?.label }),
149
+ /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14, className: `shrink-0 ${pill ? "opacity-60" : "text-muted-foreground"}` })
20
150
  ]
21
151
  }
22
152
  ),
23
- /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14, className: "pointer-events-none absolute top-1/2 right-2 -translate-y-1/2 text-muted-foreground" })
153
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value }),
154
+ p.open && createPortal(
155
+ /* @__PURE__ */ jsxs("div", { ref: p.panelRef, style: p.style, className: PANEL_CLASS, onClick: (e) => e.stopPropagation(), onKeyDown: keys, children: [
156
+ withSearch && /* @__PURE__ */ jsx("div", { className: "px-2 pt-0.5 pb-1", children: /* @__PURE__ */ jsx(
157
+ "input",
158
+ {
159
+ ref: searchRef,
160
+ type: "search",
161
+ value: query,
162
+ onChange: (e) => {
163
+ setQuery(e.target.value);
164
+ setActive(0);
165
+ },
166
+ placeholder: L.search,
167
+ "aria-label": L.searchIn.replace("{label}", ariaLabel ?? ""),
168
+ className: "h-8 w-full rounded-lg border border-slate-300 bg-white px-2.5 text-sm outline-none focus:border-proef-blue dark:border-slate-600 dark:bg-slate-800 dark:text-slate-100"
169
+ }
170
+ ) }),
171
+ /* @__PURE__ */ jsxs("ul", { role: "listbox", "aria-label": ariaLabel, children: [
172
+ visible.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-slate-400", children: L.noResults }),
173
+ visible.map((o, i) => {
174
+ const selected = o.value === value;
175
+ return /* @__PURE__ */ jsxs(
176
+ "li",
177
+ {
178
+ role: "option",
179
+ "aria-selected": selected,
180
+ "aria-disabled": o.disabled || void 0,
181
+ "data-index": i,
182
+ onMouseEnter: () => setActive(i),
183
+ onClick: () => choose(o),
184
+ className: `${itemClass(i === active)} ${o.disabled ? "cursor-not-allowed opacity-50" : ""}`,
185
+ children: [
186
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
187
+ /* @__PURE__ */ jsx("span", { className: `block truncate ${selected ? "font-semibold" : ""} ${o.value === "" ? "text-slate-500 dark:text-slate-400" : ""}`, children: o.label }),
188
+ o.description && /* @__PURE__ */ jsx("span", { className: "block truncate text-[11px] text-slate-500 dark:text-slate-400", children: o.description })
189
+ ] }),
190
+ /* @__PURE__ */ jsx("span", { className: "w-4 shrink-0", children: selected && /* @__PURE__ */ jsx(Check, {}) })
191
+ ]
192
+ },
193
+ o.value || "__empty"
194
+ );
195
+ })
196
+ ] })
197
+ ] }),
198
+ document.body
199
+ )
24
200
  ] });
25
201
  });
26
202
  export {
203
+ DEFAULT_SELECT_LABELS,
204
+ SelectInput,
27
205
  SelectInput as default
28
206
  };
29
207
  //# sourceMappingURL=SelectInput.js.map