@proef/common-ui 2.2.0 → 2.2.2

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 (32) hide show
  1. package/dist/esm/index.js +26 -3
  2. package/dist/esm/index.js.map +1 -1
  3. package/dist/esm/legacy/components/molecules/DataTable/DataTable.js +1 -1
  4. package/dist/esm/legacy/components/molecules/DataTable/DataTable.js.map +1 -1
  5. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js +111 -54
  6. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js.map +1 -1
  7. package/dist/esm/v1/components/tables/Table/ColumnFilter.js +14 -14
  8. package/dist/esm/v1/components/tables/Table/ColumnFilter.js.map +1 -1
  9. package/dist/esm/v1/components/tables/Table/Table.js +9 -8
  10. package/dist/esm/v1/components/tables/Table/Table.js.map +1 -1
  11. package/dist/esm/v1/foundations/icons/icons.js +49 -0
  12. package/dist/esm/v1/foundations/icons/icons.js.map +1 -1
  13. package/dist/esm/v1/primitives/display/RichText/RichText.js +12 -0
  14. package/dist/esm/v1/primitives/display/RichText/RichText.js.map +1 -0
  15. package/dist/esm/v1/primitives/display/RichText/utils.js +56 -0
  16. package/dist/esm/v1/primitives/display/RichText/utils.js.map +1 -0
  17. package/dist/esm/v1/primitives/forms/RichTextEditor/RichTextEditor.js +253 -0
  18. package/dist/esm/v1/primitives/forms/RichTextEditor/RichTextEditor.js.map +1 -0
  19. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js +45 -11
  20. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js.map +1 -1
  21. package/dist/style.components.css +1 -1
  22. package/dist/style.css +1 -1
  23. package/dist/style.layered.css +1 -1
  24. package/dist/types/v1/components/lists/ListFinder/ListFinder.d.ts +31 -5
  25. package/dist/types/v1/components/tables/Table/Table.d.ts +9 -1
  26. package/dist/types/v1/foundations/icons/icons.d.ts +20 -0
  27. package/dist/types/v1/index.d.ts +8 -3
  28. package/dist/types/v1/primitives/display/RichText/RichText.d.ts +17 -0
  29. package/dist/types/v1/primitives/display/RichText/utils.d.ts +22 -0
  30. package/dist/types/v1/primitives/forms/RichTextEditor/RichTextEditor.d.ts +64 -0
  31. package/dist/types/v1/primitives/lists/ListItem/ListItem.d.ts +39 -8
  32. package/package.json +6 -1
@@ -1,17 +1,32 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { useState, useMemo, useEffect } from "react";
3
3
  import { ChevronLeftIcon, SearchIcon } from "../../../foundations/icons/icons.js";
4
- import ListItem from "../../../primitives/lists/ListItem/ListItem.js";
4
+ import ListItem, { LINHA_ESBATIDA } from "../../../primitives/lists/ListItem/ListItem.js";
5
5
  import Checkbox from "../../../primitives/forms/Checkbox/Checkbox.js";
6
6
  import { OverlayScrollbar } from "../../tables/Table/OverlayScrollbar.js";
7
7
  import { DEFAULT_LIST_FINDER_LABELS } from "./labels.js";
8
8
  const semAcentos = (s) => s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
9
+ function termosDaPesquisa(texto) {
10
+ const porEspacos = texto.trim().split(/\s+/).filter(Boolean);
11
+ if (porEspacos.length > 1 && porEspacos.every((t) => /^\d{6,}$/.test(t))) return porEspacos;
12
+ return texto.split(/[\n,;\t]+/).map((t) => t.trim()).filter(Boolean);
13
+ }
9
14
  function numerosPagina(atual, total) {
10
15
  if (total <= 5) return Array.from({ length: total }, (_, i) => i + 1);
11
16
  if (atual <= 2) return [1, 2, 3, "…", total];
12
17
  if (atual >= total - 1) return [1, "…", total - 2, total - 1, total];
13
18
  return [1, "…", atual, "…", total];
14
19
  }
20
+ function LinhaEsqueleto({ marcavel, largura }) {
21
+ return /* @__PURE__ */ jsxs("div", { className: `flex items-start gap-1.5 px-3 py-3 animate-pulse motion-reduce:animate-none ${LINHA_ESBATIDA}`, children: [
22
+ marcavel && /* @__PURE__ */ jsx("span", { className: "flex w-7 shrink-0 justify-center", children: /* @__PURE__ */ jsx("span", { className: "mt-0.5 h-4 w-4 border border-slate-200 bg-slate-100" }) }),
23
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [
24
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-20 bg-slate-200" }),
25
+ /* @__PURE__ */ jsx("div", { className: `h-2.5 ${largura} bg-slate-200` }),
26
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 w-1/3 bg-slate-100" })
27
+ ] })
28
+ ] });
29
+ }
15
30
  function ListFinder({
16
31
  items,
17
32
  itemKey,
@@ -22,20 +37,25 @@ function ListFinder({
22
37
  onSelectionChange,
23
38
  filters,
24
39
  onBack,
40
+ searchable = true,
25
41
  search: pesquisaDeFora,
26
42
  onSearchChange,
27
43
  searchText,
44
+ mode = "pages",
45
+ onLoadMore,
28
46
  total,
29
47
  page,
30
48
  onPageChange,
31
49
  pageSize = 50,
32
50
  loading = false,
33
51
  labels,
52
+ footerExtra,
53
+ bordered = true,
34
54
  className = ""
35
55
  }) {
36
56
  const L = { ...DEFAULT_LIST_FINDER_LABELS, ...labels };
37
57
  const [pesquisaPropria, setPesquisaPropria] = useState("");
38
- const pesquisa = pesquisaDeFora ?? pesquisaPropria;
58
+ const pesquisa = searchable ? pesquisaDeFora ?? pesquisaPropria : "";
39
59
  const pesquisar = (s) => {
40
60
  setPesquisaPropria(s);
41
61
  onSearchChange?.(s);
@@ -48,15 +68,16 @@ function ListFinder({
48
68
  const el = e.currentTarget;
49
69
  pesquisar(el.value.slice(0, el.selectionStart ?? el.value.length) + lista + el.value.slice(el.selectionEnd ?? el.value.length));
50
70
  };
71
+ const termos = useMemo(() => termosDaPesquisa(pesquisa).map(semAcentos), [pesquisa]);
51
72
  const filtradas = useMemo(() => {
52
- if (!searchText || !pesquisa.trim()) return items;
53
- const termos = pesquisa.split(/[\n,;\t]+/).map((t) => semAcentos(t.trim())).filter(Boolean);
73
+ if (!searchText || !termos.length) return items;
54
74
  return items.filter((it) => {
55
75
  const texto = semAcentos(searchText(it));
56
76
  return termos.some((t) => texto.includes(t));
57
77
  });
58
- }, [items, pesquisa, searchText]);
78
+ }, [items, termos, searchText]);
59
79
  const peloServidor = total !== void 0;
80
+ const aRolar = mode === "scroll";
60
81
  const [paginaPropria, setPaginaPropria] = useState(1);
61
82
  const totalLinhas = peloServidor ? total : filtradas.length;
62
83
  const totalPaginas = Math.max(1, Math.ceil(totalLinhas / pageSize));
@@ -68,11 +89,31 @@ function ListFinder({
68
89
  if (peloServidor) onPageChange?.(p);
69
90
  else setPaginaPropria(p);
70
91
  };
71
- const visiveis = peloServidor ? filtradas : filtradas.slice((pagina - 1) * pageSize, pagina * pageSize);
92
+ const [lotes, setLotes] = useState(1);
93
+ useEffect(() => {
94
+ setLotes(1);
95
+ }, [pesquisa]);
96
+ const visiveis = peloServidor ? filtradas : aRolar ? filtradas.slice(0, lotes * pageSize) : filtradas.slice((pagina - 1) * pageSize, pagina * pageSize);
97
+ const haMais = aRolar && (peloServidor ? !!onLoadMore && items.length < totalLinhas : visiveis.length < filtradas.length);
72
98
  const [caixa, setCaixa] = useState(null);
73
99
  useEffect(() => {
74
100
  caixa?.scrollTo?.({ top: 0 });
75
101
  }, [pagina, caixa]);
102
+ useEffect(() => {
103
+ if (aRolar) caixa?.scrollTo?.({ top: 0 });
104
+ }, [pesquisa, aRolar, caixa]);
105
+ const [fim, setFim] = useState(null);
106
+ useEffect(() => {
107
+ if (!fim || !caixa || !haMais || typeof IntersectionObserver === "undefined") return;
108
+ const io = new IntersectionObserver(([e]) => {
109
+ if (!e?.isIntersecting) return;
110
+ if (peloServidor) {
111
+ if (!loading) onLoadMore?.();
112
+ } else setLotes((n) => n + 1);
113
+ }, { root: caixa, rootMargin: "300px" });
114
+ io.observe(fim);
115
+ return () => io.disconnect();
116
+ }, [fim, caixa, haMais, peloServidor, loading, onLoadMore, visiveis.length]);
76
117
  const marcadas = selectedKeys ?? /* @__PURE__ */ new Set();
77
118
  const marcaveis = !!onSelectionChange;
78
119
  const nMarcadasVisiveis = visiveis.filter((it) => marcadas.has(itemKey(it))).length;
@@ -93,8 +134,8 @@ function ListFinder({
93
134
  };
94
135
  const aCarregar = loading && visiveis.length === 0;
95
136
  const esqueleto = /* @__PURE__ */ jsx("span", { className: "inline-block h-2 w-20 rounded-sm bg-slate-200 animate-pulse motion-reduce:animate-none", "aria-hidden": true });
96
- return /* @__PURE__ */ jsxs("div", { className: `v1-reset flex h-full min-h-0 flex-col border border-slate-200 bg-white text-control text-slate-800 ${className}`, children: [
97
- /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-b border-slate-100 px-3 py-2.5", children: [
137
+ return /* @__PURE__ */ jsxs("div", { className: `v1-reset flex h-full min-h-0 flex-col bg-white text-control text-slate-800 ${bordered ? "border border-slate-200" : ""} ${className}`, children: [
138
+ (searchable || onBack || filters) && /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-b border-slate-100 px-3 py-2", children: [
98
139
  (onBack || filters) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
99
140
  onBack && /* @__PURE__ */ jsx(
100
141
  "button",
@@ -109,8 +150,8 @@ function ListFinder({
109
150
  ),
110
151
  /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: filters })
111
152
  ] }),
112
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
113
- /* @__PURE__ */ jsx(SearchIcon, { size: 13, className: "pointer-events-none absolute top-1/2 left-2.5 -translate-y-1/2 text-slate-400" }),
153
+ searchable && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
154
+ /* @__PURE__ */ jsx(SearchIcon, { size: 11, className: "pointer-events-none absolute top-1/2 left-2 -translate-y-1/2 text-slate-400" }),
114
155
  /* @__PURE__ */ jsx(
115
156
  "input",
116
157
  {
@@ -120,13 +161,13 @@ function ListFinder({
120
161
  onPaste: colar,
121
162
  placeholder: L.searchPlaceholder,
122
163
  "aria-label": L.search,
123
- className: "block h-control w-full min-w-0 rounded-lg border border-border bg-card pr-2.5 pl-8 text-control text-foreground outline-none transition-[border-color,box-shadow] duration-150 placeholder:text-muted-foreground/70 focus:border-primary/40 focus:ring-3 focus:ring-primary/10"
164
+ className: "block w-full min-w-0 border border-slate-200 bg-white py-1.5 pr-2 pl-6 text-micro text-slate-700 outline-none transition-colors placeholder:text-slate-400 focus:border-proef-blue"
124
165
  }
125
166
  )
126
167
  ] })
127
168
  ] }),
128
- marcaveis && visiveis.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 border-b border-slate-100 bg-slate-50 px-3 py-1.5 text-caption text-slate-500", children: [
129
- /* @__PURE__ */ jsx(
169
+ marcaveis && visiveis.length > 0 && /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-1.5 bg-white px-3 py-2 text-[10px] text-slate-600 ${LINHA_ESBATIDA}`, children: [
170
+ /* @__PURE__ */ jsx("span", { className: "flex w-7 shrink-0 justify-center", children: /* @__PURE__ */ jsx(
130
171
  Checkbox,
131
172
  {
132
173
  checked: todasMarcadas,
@@ -134,58 +175,70 @@ function ListFinder({
134
175
  onChange: marcarVisiveis,
135
176
  "aria-label": L.selectVisible(visiveis.length)
136
177
  }
137
- ),
178
+ ) }),
138
179
  /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: nMarcadasVisiveis > 0 ? L.selectedCount(nMarcadasVisiveis, visiveis.length) : L.selectVisible(visiveis.length) }),
139
180
  marcadas.size > 0 && /* @__PURE__ */ jsx(
140
181
  "button",
141
182
  {
142
183
  type: "button",
143
184
  onClick: () => onSelectionChange?.(/* @__PURE__ */ new Set()),
144
- className: "ml-auto font-medium text-proef-blue hover:underline",
185
+ className: "ml-auto text-proef-blue hover:underline",
145
186
  children: L.clearSelection
146
187
  }
147
188
  )
148
189
  ] }),
149
190
  /* @__PURE__ */ jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
150
- /* @__PURE__ */ jsx("div", { ref: setCaixa, className: "min-h-0 flex-1 overflow-y-auto scrollbar-none", children: aCarregar ? (
151
- // A forma das linhas já diz que vem ali a lista. Mais do que cabem,
152
- // cortadas pela caixa: chega ao fundo em qualquer altura.
153
- /* @__PURE__ */ jsxs("div", { role: "status", "aria-busy": "true", className: "h-full overflow-hidden", children: [
191
+ /* @__PURE__ */ jsxs("div", { ref: setCaixa, className: "relative min-h-0 flex-1 overflow-y-auto scrollbar-none", children: [
192
+ aCarregar ? (
193
+ // A forma das linhas já diz que vem ali a lista. Mais do que cabem,
194
+ // cortadas pela caixa: chega ao fundo em qualquer altura.
195
+ /* @__PURE__ */ jsxs("div", { role: "status", "aria-busy": "true", className: "h-full overflow-hidden", children: [
196
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: L.loading }),
197
+ ["w-11/12", "w-3/4", "w-5/6", "w-2/3", "w-4/5", "w-7/12"].flatMap((l) => [l, l, l, l]).map((largura, i) => /* @__PURE__ */ jsx(LinhaEsqueleto, { marcavel: marcaveis, largura }, i))
198
+ ] })
199
+ ) : visiveis.length === 0 ? /* @__PURE__ */ jsx("p", { className: "m-0 px-3 py-4 text-center text-[10px] text-slate-400", children: L.empty }) : /* @__PURE__ */ jsx("ul", { className: "m-0 list-none p-0", children: visiveis.map((it) => {
200
+ const key = itemKey(it);
201
+ return /* @__PURE__ */ jsx(
202
+ ListItem,
203
+ {
204
+ ...itemContent(it),
205
+ active: activeKey === key,
206
+ onClick: onItemClick ? () => onItemClick(it) : void 0,
207
+ checked: marcadas.has(key),
208
+ onCheckedChange: marcaveis ? (sim) => marcar(key, sim) : void 0,
209
+ checkboxLabel: L.checkItem(key),
210
+ highlight: termos
211
+ },
212
+ key
213
+ );
214
+ }) }),
215
+ !aCarregar && haMais && /* @__PURE__ */ jsxs("div", { ref: setFim, role: "status", "aria-busy": "true", children: [
154
216
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: L.loading }),
155
- ["w-11/12", "w-3/4", "w-5/6", "w-2/3", "w-4/5", "w-7/12"].flatMap((l) => [l, l, l, l]).map((largura, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5 border-b border-slate-100 px-3 py-2.5 animate-pulse motion-reduce:animate-none", children: [
156
- marcaveis && /* @__PURE__ */ jsx("div", { className: "mt-0.5 h-4 w-4 shrink-0 border border-slate-200 bg-slate-100" }),
157
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [
158
- /* @__PURE__ */ jsx("div", { className: "h-3 w-12 bg-slate-200" }),
159
- /* @__PURE__ */ jsx("div", { className: "h-1.5 w-16 bg-slate-100" }),
160
- /* @__PURE__ */ jsx("div", { className: `h-2.5 ${largura} bg-slate-200` }),
161
- /* @__PURE__ */ jsx("div", { className: "h-1.5 w-1/3 bg-slate-100" })
162
- ] })
163
- ] }, i))
217
+ ["w-3/4", "w-5/6", "w-2/3"].map((largura, i) => /* @__PURE__ */ jsx(LinhaEsqueleto, { marcavel: marcaveis, largura }, i))
164
218
  ] })
165
- ) : visiveis.length === 0 ? /* @__PURE__ */ jsx("p", { className: "m-0 px-3 py-6 text-center text-caption text-slate-400", children: L.empty }) : /* @__PURE__ */ jsx("ul", { className: "m-0 list-none p-0", children: visiveis.map((it) => {
166
- const key = itemKey(it);
167
- return /* @__PURE__ */ jsx(
168
- ListItem,
169
- {
170
- ...itemContent(it),
171
- active: activeKey === key,
172
- onClick: onItemClick ? () => onItemClick(it) : void 0,
173
- checked: marcadas.has(key),
174
- onCheckedChange: marcaveis ? (sim) => marcar(key, sim) : void 0,
175
- checkboxLabel: L.checkItem(key)
176
- },
177
- key
178
- );
179
- }) }) }),
219
+ ] }),
180
220
  /* @__PURE__ */ jsx(OverlayScrollbar, { alvo: caixa, borda: 0 })
181
221
  ] }),
182
- /* @__PURE__ */ jsxs("div", { className: "flex h-11 shrink-0 items-center justify-between gap-2 border-t border-slate-100 bg-slate-50 px-3 text-caption text-slate-500", children: [
183
- /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: aCarregar ? esqueleto : /* @__PURE__ */ jsxs(Fragment, { children: [
184
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-800 tabular-nums", children: totalLinhas.toLocaleString("pt-PT") }),
185
- " ",
186
- L.itemsNoun(totalLinhas)
187
- ] }) }),
188
- !aCarregar && totalPaginas > 1 && /* @__PURE__ */ jsxs("nav", { "aria-label": L.pagination, className: "flex items-center gap-0.5", children: [
222
+ /* @__PURE__ */ jsxs("div", { className: `flex shrink-0 items-center gap-2 border-t border-slate-100 bg-slate-50 px-3 text-micro text-slate-400 ${aRolar ? "h-8 justify-center" : "h-9 justify-between"}`, children: [
223
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 whitespace-nowrap", children: [
224
+ aCarregar ? esqueleto : aRolar ? (
225
+ // A rolar: quantas já se mostram, de quantas há.
226
+ /* @__PURE__ */ jsxs(Fragment, { children: [
227
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-slate-500 tabular-nums", children: visiveis.length.toLocaleString("pt-PT") }),
228
+ " / ",
229
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-slate-500 tabular-nums", children: totalLinhas.toLocaleString("pt-PT") }),
230
+ " ",
231
+ L.itemsNoun(totalLinhas)
232
+ ] })
233
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
234
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-slate-500 tabular-nums", children: totalLinhas.toLocaleString("pt-PT") }),
235
+ " ",
236
+ L.itemsNoun(totalLinhas)
237
+ ] }),
238
+ footerExtra,
239
+ loading && !aCarregar && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "animate-pulse motion-reduce:animate-none", children: "…" })
240
+ ] }),
241
+ !aRolar && !aCarregar && totalPaginas > 1 && /* @__PURE__ */ jsxs("nav", { "aria-label": L.pagination, className: "flex items-center gap-0.5", children: [
189
242
  /* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
190
243
  L.page,
191
244
  " ",
@@ -193,14 +246,17 @@ function ListFinder({
193
246
  " / ",
194
247
  totalPaginas
195
248
  ] }),
196
- numerosPagina(pagina, totalPaginas).map((p, i) => p === "…" ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "flex h-7 min-w-4 items-center justify-center text-slate-300", children: "…" }, `r${i}`) : /* @__PURE__ */ jsx(
249
+ numerosPagina(pagina, totalPaginas).map((p, i) => p === "…" ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "flex h-6 min-w-3 items-center justify-center text-slate-300", children: "…" }, `r${i}`) : /* @__PURE__ */ jsxs(
197
250
  "button",
198
251
  {
199
252
  type: "button",
200
253
  onClick: () => irPara(p),
201
254
  "aria-current": p === pagina ? "page" : void 0,
202
- className: `h-7 min-w-7 rounded-md px-1.5 font-semibold tabular-nums transition-colors ${p === pagina ? "cursor-default bg-primary text-white" : "text-slate-500 hover:bg-slate-200/70 hover:text-slate-800"}`,
203
- children: p.toLocaleString("pt-PT")
255
+ className: `grid h-6 min-w-5 place-items-center px-1 tabular-nums transition-colors ${p === pagina ? "cursor-default font-semibold text-slate-800" : "text-slate-400 hover:text-slate-700"}`,
256
+ children: [
257
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "invisible col-start-1 row-start-1 font-semibold", children: p.toLocaleString("pt-PT") }),
258
+ /* @__PURE__ */ jsx("span", { className: "col-start-1 row-start-1", children: p.toLocaleString("pt-PT") })
259
+ ]
204
260
  },
205
261
  p
206
262
  ))
@@ -209,6 +265,7 @@ function ListFinder({
209
265
  ] });
210
266
  }
211
267
  export {
212
- ListFinder as default
268
+ ListFinder as default,
269
+ termosDaPesquisa
213
270
  };
214
271
  //# sourceMappingURL=ListFinder.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ListFinder.js","sources":["../../../../../../src/v1/components/lists/ListFinder/ListFinder.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from 'react'\r\nimport { ChevronLeftIcon, SearchIcon } from '../../../foundations/icons/icons'\r\nimport ListItem, { type ListItemContent } from '../../../primitives/lists/ListItem/ListItem'\r\nimport Checkbox from '../../../primitives/forms/Checkbox/Checkbox'\r\nimport { OverlayScrollbar } from '../../tables/Table/OverlayScrollbar'\r\nimport { DEFAULT_LIST_FINDER_LABELS, type ListFinderLabels } from './labels'\r\n\r\nexport interface ListFinderProps<T> {\r\n /** As linhas: todas, ou só a página atual quando o servidor pagina (com `total`). */\r\n items: T[]\r\n /** Uma chave única por linha (ex: o id). */\r\n itemKey: (item: T) => string\r\n /** O que cada linha mostra: as partes do [ListItem](?path=/docs/v1-primitives-lists-listitem--docs). */\r\n itemContent: (item: T) => ListItemContent\r\n /** A linha aberta (fica realçada). */\r\n activeKey?: string | null\r\n /** Ao clicar numa linha: normalmente, abri-la ao lado. */\r\n onItemClick?: (item: T) => void\r\n /** As marcadas. Só há caixas de marcar com `onSelectionChange`. */\r\n selectedKeys?: ReadonlySet<string>\r\n onSelectionChange?: (keys: Set<string>) => void\r\n /** Filtros ao lado do voltar, por cima da pesquisa (ex: um ou dois [SelectInput](?path=/docs/v1-primitives-forms-selectinput--docs)). */\r\n filters?: React.ReactNode\r\n /** Com isto aparece um ‹ à esquerda, para fechar ou esconder a lista. */\r\n onBack?: () => void\r\n /** O texto da pesquisa. Sem ele, a pesquisa guarda o seu. */\r\n search?: string\r\n onSearchChange?: (search: string) => void\r\n /**\r\n * O texto onde a pesquisa procura, em cada linha. Com isto, a lista filtra-se\r\n * sozinha (sem acentos nem maiúsculas); sem isto, quem filtra é a app (ex: o\r\n * servidor, com `onSearchChange`). Uma lista colada (uma por linha, ou\r\n * separadas por vírgulas) mostra as que tiverem qualquer uma delas.\r\n */\r\n searchText?: (item: T) => string\r\n /** O servidor pagina: quantas linhas há lá ao todo. `items` é só a página atual. */\r\n total?: number\r\n /** A página atual (1, 2, ...), quando o servidor pagina. */\r\n page?: number\r\n onPageChange?: (page: number) => void\r\n /** Quantas linhas por página. Com o servidor a paginar, quantas ele manda. */\r\n pageSize?: number\r\n /** A carregar: sem linhas ainda, mostra o esqueleto delas. */\r\n loading?: boolean\r\n labels?: Partial<ListFinderLabels>\r\n className?: string\r\n}\r\n\r\nconst semAcentos = (s: string) => s.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase()\r\n\r\n/** Os números das páginas numa coluna estreita: no máximo cinco lugares. */\r\nfunction numerosPagina(atual: number, total: number): (number | '…')[] {\r\n if (total <= 5) return Array.from({ length: total }, (_, i) => i + 1)\r\n if (atual <= 2) return [1, 2, 3, '…', total]\r\n if (atual >= total - 1) return [1, '…', total - 2, total - 1, total]\r\n return [1, '…', atual, '…', total]\r\n}\r\n\r\n/**\r\n * Uma lista para encontrar e escolher: filtros e pesquisa em cima, as linhas\r\n * ([ListItem](?path=/docs/v1-primitives-lists-listitem--docs)) a rolar no meio e\r\n * as páginas em baixo. Pensada para a coluna ao lado do que se abre (a lista à\r\n * esquerda, o detalhe da linha escolhida à direita).\r\n *\r\n * Enche a altura do pai. O topo, a barra de \"selecionar todos\" e o rodapé\r\n * ficam sempre no sítio: só as linhas rolam.\r\n *\r\n * - **Pesquisa**: com `searchText` filtra sozinha; sem ele, a app filtra\r\n * (`search`/`onSearchChange`).\r\n * - **Páginas**: pagina sozinha (`pageSize`), ou é o servidor (`total`, `page`,\r\n * `onPageChange`).\r\n * - **Marcar vários**: com `onSelectionChange`. O \"selecionar todos\" só marca\r\n * as linhas à vista, nunca as que não se veem.\r\n */\r\nexport default function ListFinder<T>({\r\n items, itemKey, itemContent, activeKey, onItemClick, selectedKeys, onSelectionChange, filters, onBack,\r\n search: pesquisaDeFora, onSearchChange, searchText, total, page, onPageChange, pageSize = 50, loading = false,\r\n labels, className = '',\r\n}: ListFinderProps<T>) {\r\n const L = { ...DEFAULT_LIST_FINDER_LABELS, ...labels }\r\n const [pesquisaPropria, setPesquisaPropria] = useState('')\r\n const pesquisa = pesquisaDeFora ?? pesquisaPropria\r\n const pesquisar = (s: string) => { setPesquisaPropria(s); onSearchChange?.(s) }\r\n // Uma caixa de uma linha deita fora as quebras de linha: uma coluna colada\r\n // de uma folha de cálculo chegava como um número só, colado. Passam a vírgulas.\r\n const colar = (e: React.ClipboardEvent<HTMLInputElement>) => {\r\n const texto = e.clipboardData.getData('text')\r\n if (!/\\r?\\n/.test(texto.trim())) return\r\n e.preventDefault()\r\n const lista = texto.split(/\\r?\\n/).map(t => t.trim()).filter(Boolean).join(', ')\r\n const el = e.currentTarget\r\n pesquisar(el.value.slice(0, el.selectionStart ?? el.value.length) + lista + el.value.slice(el.selectionEnd ?? el.value.length))\r\n }\r\n\r\n const filtradas = useMemo(() => {\r\n if (!searchText || !pesquisa.trim()) return items\r\n const termos = pesquisa.split(/[\\n,;\\t]+/).map(t => semAcentos(t.trim())).filter(Boolean)\r\n return items.filter(it => {\r\n const texto = semAcentos(searchText(it))\r\n return termos.some(t => texto.includes(t))\r\n })\r\n }, [items, pesquisa, searchText])\r\n\r\n // Paginação: do servidor (com `total`), ou feita aqui.\r\n const peloServidor = total !== undefined\r\n const [paginaPropria, setPaginaPropria] = useState(1)\r\n const totalLinhas = peloServidor ? total : filtradas.length\r\n const totalPaginas = Math.max(1, Math.ceil(totalLinhas / pageSize))\r\n const pagina = Math.min(peloServidor ? (page ?? 1) : paginaPropria, totalPaginas)\r\n // Uma pesquisa nova volta à primeira página: a terceira de antes já não é a mesma lista.\r\n useEffect(() => { if (!peloServidor) setPaginaPropria(1) }, [pesquisa, peloServidor])\r\n const irPara = (p: number) => { if (peloServidor) onPageChange?.(p); else setPaginaPropria(p) }\r\n const visiveis = peloServidor ? filtradas : filtradas.slice((pagina - 1) * pageSize, pagina * pageSize)\r\n\r\n // Ao mudar de página, a lista volta ao topo.\r\n const [caixa, setCaixa] = useState<HTMLDivElement | null>(null)\r\n useEffect(() => { caixa?.scrollTo?.({ top: 0 }) }, [pagina, caixa])\r\n\r\n const marcadas = selectedKeys ?? new Set<string>()\r\n const marcaveis = !!onSelectionChange\r\n const nMarcadasVisiveis = visiveis.filter(it => marcadas.has(itemKey(it))).length\r\n const todasMarcadas = visiveis.length > 0 && nMarcadasVisiveis === visiveis.length\r\n const marcar = (key: string, sim: boolean) => {\r\n const n = new Set(marcadas)\r\n if (sim) n.add(key); else n.delete(key)\r\n onSelectionChange?.(n)\r\n }\r\n const marcarVisiveis = () => {\r\n const n = new Set(marcadas)\r\n for (const it of visiveis) { if (todasMarcadas) n.delete(itemKey(it)); else n.add(itemKey(it)) }\r\n onSelectionChange?.(n)\r\n }\r\n\r\n const aCarregar = loading && visiveis.length === 0\r\n const esqueleto = <span className=\"inline-block h-2 w-20 rounded-sm bg-slate-200 animate-pulse motion-reduce:animate-none\" aria-hidden />\r\n\r\n return (\r\n <div className={`v1-reset flex h-full min-h-0 flex-col border border-slate-200 bg-white text-control text-slate-800 ${className}`}>\r\n <div className=\"space-y-2 border-b border-slate-100 px-3 py-2.5\">\r\n {(onBack || filters) && (\r\n <div className=\"flex items-center gap-1.5\">\r\n {onBack && (\r\n <button type=\"button\" onClick={onBack} aria-label={L.back} title={L.back}\r\n className=\"-ml-1 flex h-7 w-6 shrink-0 items-center justify-center rounded text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700\">\r\n <ChevronLeftIcon size={14} />\r\n </button>\r\n )}\r\n <div className=\"flex min-w-0 flex-1 items-center gap-1.5\">{filters}</div>\r\n </div>\r\n )}\r\n <div className=\"relative\">\r\n <SearchIcon size={13} className=\"pointer-events-none absolute top-1/2 left-2.5 -translate-y-1/2 text-slate-400\" />\r\n <input\r\n type=\"search\"\r\n value={pesquisa}\r\n onChange={e => pesquisar(e.target.value)}\r\n onPaste={colar}\r\n placeholder={L.searchPlaceholder}\r\n aria-label={L.search}\r\n className=\"block h-control w-full min-w-0 rounded-lg border border-border bg-card pr-2.5 pl-8 text-control text-foreground outline-none transition-[border-color,box-shadow] duration-150 placeholder:text-muted-foreground/70 focus:border-primary/40 focus:ring-3 focus:ring-primary/10\"\r\n />\r\n </div>\r\n </div>\r\n\r\n {/* Fora da área que rola: é a cabeça da lista, e é a descer a lista que\r\n se decide marcá-la toda. */}\r\n {marcaveis && visiveis.length > 0 && (\r\n <div className=\"flex items-center gap-2.5 border-b border-slate-100 bg-slate-50 px-3 py-1.5 text-caption text-slate-500\">\r\n <Checkbox\r\n checked={todasMarcadas}\r\n indeterminate={nMarcadasVisiveis > 0 && !todasMarcadas}\r\n onChange={marcarVisiveis}\r\n aria-label={L.selectVisible(visiveis.length)}\r\n />\r\n <span aria-hidden>{nMarcadasVisiveis > 0 ? L.selectedCount(nMarcadasVisiveis, visiveis.length) : L.selectVisible(visiveis.length)}</span>\r\n {marcadas.size > 0 && (\r\n <button type=\"button\" onClick={() => onSelectionChange?.(new Set())}\r\n className=\"ml-auto font-medium text-proef-blue hover:underline\">\r\n {L.clearSelection}\r\n </button>\r\n )}\r\n </div>\r\n )}\r\n\r\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\r\n <div ref={setCaixa} className=\"min-h-0 flex-1 overflow-y-auto scrollbar-none\">\r\n {aCarregar ? (\r\n // A forma das linhas já diz que vem ali a lista. Mais do que cabem,\r\n // cortadas pela caixa: chega ao fundo em qualquer altura.\r\n <div role=\"status\" aria-busy=\"true\" className=\"h-full overflow-hidden\">\r\n <span className=\"sr-only\">{L.loading}</span>\r\n {['w-11/12', 'w-3/4', 'w-5/6', 'w-2/3', 'w-4/5', 'w-7/12'].flatMap(l => [l, l, l, l]).map((largura, i) => (\r\n <div key={i} className=\"flex items-start gap-2.5 border-b border-slate-100 px-3 py-2.5 animate-pulse motion-reduce:animate-none\">\r\n {marcaveis && <div className=\"mt-0.5 h-4 w-4 shrink-0 border border-slate-200 bg-slate-100\" />}\r\n <div className=\"min-w-0 flex-1 space-y-1.5\">\r\n <div className=\"h-3 w-12 bg-slate-200\" />\r\n <div className=\"h-1.5 w-16 bg-slate-100\" />\r\n <div className={`h-2.5 ${largura} bg-slate-200`} />\r\n <div className=\"h-1.5 w-1/3 bg-slate-100\" />\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n ) : visiveis.length === 0 ? (\r\n <p className=\"m-0 px-3 py-6 text-center text-caption text-slate-400\">{L.empty}</p>\r\n ) : (\r\n <ul className=\"m-0 list-none p-0\">\r\n {visiveis.map(it => {\r\n const key = itemKey(it)\r\n return (\r\n <ListItem\r\n key={key}\r\n {...itemContent(it)}\r\n active={activeKey === key}\r\n onClick={onItemClick ? () => onItemClick(it) : undefined}\r\n checked={marcadas.has(key)}\r\n onCheckedChange={marcaveis ? sim => marcar(key, sim) : undefined}\r\n checkboxLabel={L.checkItem(key)}\r\n />\r\n )\r\n })}\r\n </ul>\r\n )}\r\n </div>\r\n <OverlayScrollbar alvo={caixa} borda={0} />\r\n </div>\r\n\r\n {/* Sempre à vista e com a mesma altura, carregada ou não: aparecer do\r\n nada fazia saltar o fundo do painel. */}\r\n <div className=\"flex h-11 shrink-0 items-center justify-between gap-2 border-t border-slate-100 bg-slate-50 px-3 text-caption text-slate-500\">\r\n <span className=\"whitespace-nowrap\">\r\n {aCarregar ? esqueleto : <><span className=\"font-semibold text-slate-800 tabular-nums\">{totalLinhas.toLocaleString('pt-PT')}</span> {L.itemsNoun(totalLinhas)}</>}\r\n </span>\r\n {!aCarregar && totalPaginas > 1 && (\r\n <nav aria-label={L.pagination} className=\"flex items-center gap-0.5\">\r\n <span className=\"sr-only\">{L.page} {pagina} / {totalPaginas}</span>\r\n {numerosPagina(pagina, totalPaginas).map((p, i) => p === '…' ? (\r\n <span key={`r${i}`} aria-hidden className=\"flex h-7 min-w-4 items-center justify-center text-slate-300\">…</span>\r\n ) : (\r\n <button\r\n key={p}\r\n type=\"button\"\r\n onClick={() => irPara(p)}\r\n aria-current={p === pagina ? 'page' : undefined}\r\n className={`h-7 min-w-7 rounded-md px-1.5 font-semibold tabular-nums transition-colors ${\r\n p === pagina ? 'cursor-default bg-primary text-white' : 'text-slate-500 hover:bg-slate-200/70 hover:text-slate-800'}`}\r\n >\r\n {p.toLocaleString('pt-PT')}\r\n </button>\r\n ))}\r\n </nav>\r\n )}\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ListFinder.js","sources":["../../../../../../src/v1/components/lists/ListFinder/ListFinder.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from 'react'\r\nimport { ChevronLeftIcon, SearchIcon } from '../../../foundations/icons/icons'\r\nimport ListItem, { LINHA_ESBATIDA, type ListItemContent } from '../../../primitives/lists/ListItem/ListItem'\r\nimport Checkbox from '../../../primitives/forms/Checkbox/Checkbox'\r\nimport { OverlayScrollbar } from '../../tables/Table/OverlayScrollbar'\r\nimport { DEFAULT_LIST_FINDER_LABELS, type ListFinderLabels } from './labels'\r\n\r\nexport interface ListFinderProps<T> {\r\n /** As linhas: todas, ou só a página atual quando o servidor pagina (com `total`). */\r\n items: T[]\r\n /** Uma chave única por linha (ex: o id). */\r\n itemKey: (item: T) => string\r\n /** O que cada linha mostra: as partes do [ListItem](?path=/docs/v1-primitives-lists-listitem--docs). */\r\n itemContent: (item: T) => ListItemContent\r\n /** A linha aberta (fica realçada). */\r\n activeKey?: string | null\r\n /** Ao clicar numa linha: normalmente, abri-la ao lado. */\r\n onItemClick?: (item: T) => void\r\n /** As marcadas. Só há caixas de marcar com `onSelectionChange`. */\r\n selectedKeys?: ReadonlySet<string>\r\n onSelectionChange?: (keys: Set<string>) => void\r\n /** Filtros ao lado do voltar, por cima da pesquisa (ex: um ou dois selects, no traço fino da pesquisa). */\r\n filters?: React.ReactNode\r\n /** Com isto aparece um ‹ à esquerda, para fechar ou esconder a lista. */\r\n onBack?: () => void\r\n /** Com a caixa de pesquisa (o normal). Sem ela, a lista mostra tudo o que recebe. */\r\n searchable?: boolean\r\n /** O texto da pesquisa. Sem ele, a pesquisa guarda o seu. */\r\n search?: string\r\n onSearchChange?: (search: string) => void\r\n /**\r\n * O texto onde a pesquisa procura, em cada linha. Com isto, a lista filtra-se\r\n * sozinha (sem acentos nem maiúsculas); sem isto, quem filtra é a app (ex: o\r\n * servidor, com `onSearchChange`). Uma lista colada (uma por linha, ou\r\n * separadas por vírgulas) mostra as que tiverem qualquer uma delas.\r\n */\r\n searchText?: (item: T) => string\r\n /**\r\n * Como se chega ao resto da lista:\r\n * - `'pages'` (o normal): páginas numeradas no rodapé;\r\n * - `'scroll'`: uma lista só, que vai mostrando mais `pageSize` linhas ao\r\n * chegar ao fundo. Com o servidor a paginar, `items` são as que já vieram e\r\n * pede-se o lote seguinte com `onLoadMore`.\r\n */\r\n mode?: 'pages' | 'scroll'\r\n /** Com `mode=\"scroll\"` e o servidor a paginar: buscar o lote seguinte (junta-o a `items`). */\r\n onLoadMore?: () => void\r\n /** O servidor pagina: quantas linhas há lá ao todo. `items` é só a página atual (ou, a rolar, as que já vieram). */\r\n total?: number\r\n /** A página atual (1, 2, ...), quando o servidor pagina. */\r\n page?: number\r\n onPageChange?: (page: number) => void\r\n /** Quantas linhas por página. Com o servidor a paginar, quantas ele manda. */\r\n pageSize?: number\r\n /** A carregar: sem linhas ainda, mostra o esqueleto delas. A rolar, com linhas, é o lote seguinte a chegar. */\r\n loading?: boolean\r\n labels?: Partial<ListFinderLabels>\r\n /** No rodapé, a seguir à contagem: algo pequeno da app (ex: um ícone de ajuda com as regras da lista). */\r\n footerExtra?: React.ReactNode\r\n /** Com a moldura à volta (o normal). Sem ela, para ir dentro de um painel que já tem a sua. */\r\n bordered?: boolean\r\n className?: string\r\n}\r\n\r\nconst semAcentos = (s: string) => s.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase()\r\n\r\n/**\r\n * Parte o texto da pesquisa nos termos a procurar: vírgula, ponto e vírgula,\r\n * tab e mudança de linha. O espaço só separa quando tudo o que lá está são\r\n * números de 6 ou mais dígitos (uma coluna colada de uma folha de cálculo);\r\n * fora disso \"vila real\" é uma coisa só, e não \"vila\" ou \"real\".\r\n */\r\nexport function termosDaPesquisa(texto: string): string[] {\r\n const porEspacos = texto.trim().split(/\\s+/).filter(Boolean)\r\n if (porEspacos.length > 1 && porEspacos.every(t => /^\\d{6,}$/.test(t))) return porEspacos\r\n return texto.split(/[\\n,;\\t]+/).map(t => t.trim()).filter(Boolean)\r\n}\r\n\r\n/** Os números das páginas numa coluna estreita: no máximo cinco lugares. */\r\nfunction numerosPagina(atual: number, total: number): (number | '…')[] {\r\n if (total <= 5) return Array.from({ length: total }, (_, i) => i + 1)\r\n if (atual <= 2) return [1, 2, 3, '…', total]\r\n if (atual >= total - 1) return [1, '…', total - 2, total - 1, total]\r\n return [1, '…', atual, '…', total]\r\n}\r\n\r\n/** Uma linha a carregar: a forma das linhas já diz que vem ali a lista. */\r\nfunction LinhaEsqueleto({ marcavel, largura }: { marcavel: boolean; largura: string }) {\r\n return (\r\n <div className={`flex items-start gap-1.5 px-3 py-3 animate-pulse motion-reduce:animate-none ${LINHA_ESBATIDA}`}>\r\n {marcavel && <span className=\"flex w-7 shrink-0 justify-center\"><span className=\"mt-0.5 h-4 w-4 border border-slate-200 bg-slate-100\" /></span>}\r\n <div className=\"min-w-0 flex-1 space-y-1.5\">\r\n <div className=\"h-3 w-20 bg-slate-200\" />\r\n <div className={`h-2.5 ${largura} bg-slate-200`} />\r\n <div className=\"h-1.5 w-1/3 bg-slate-100\" />\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Uma lista para encontrar e escolher: filtros e pesquisa em cima, as linhas\r\n * ([ListItem](?path=/docs/v1-primitives-lists-listitem--docs)) a rolar no meio e\r\n * as páginas (ou a contagem, a rolar) em baixo. Pensada para a coluna ao lado do que se abre (a lista à\r\n * esquerda, o detalhe da linha escolhida à direita).\r\n *\r\n * Enche a altura do pai. O topo, a barra de \"selecionar todos\" e o rodapé\r\n * ficam sempre no sítio: só as linhas rolam.\r\n *\r\n * - **Pesquisa**: com `searchText` filtra sozinha; sem ele, a app filtra\r\n * (`search`/`onSearchChange`).\r\n * - **Páginas**: pagina sozinha (`pageSize`), ou é o servidor (`total`, `page`,\r\n * `onPageChange`).\r\n * - **A rolar** (`mode=\"scroll\"`): sem páginas, vai mostrando mais ao descer;\r\n * com o servidor, `items` são as que já vieram e `onLoadMore` pede mais.\r\n * - **Realce**: o que se procurou realça-se no código e no título das linhas.\r\n * - **Marcar vários**: com `onSelectionChange`. O \"selecionar todos\" só marca\r\n * as linhas à vista, nunca as que não se veem.\r\n */\r\nexport default function ListFinder<T>({\r\n items, itemKey, itemContent, activeKey, onItemClick, selectedKeys, onSelectionChange, filters, onBack,\r\n searchable = true, search: pesquisaDeFora, onSearchChange, searchText, mode = 'pages', onLoadMore, total, page, onPageChange, pageSize = 50, loading = false,\r\n labels, footerExtra, bordered = true, className = '',\r\n}: ListFinderProps<T>) {\r\n const L = { ...DEFAULT_LIST_FINDER_LABELS, ...labels }\r\n const [pesquisaPropria, setPesquisaPropria] = useState('')\r\n const pesquisa = searchable ? (pesquisaDeFora ?? pesquisaPropria) : ''\r\n const pesquisar = (s: string) => { setPesquisaPropria(s); onSearchChange?.(s) }\r\n // Uma caixa de uma linha deita fora as quebras de linha: uma coluna colada\r\n // de uma folha de cálculo chegava como um número só, colado. Passam a vírgulas.\r\n const colar = (e: React.ClipboardEvent<HTMLInputElement>) => {\r\n const texto = e.clipboardData.getData('text')\r\n if (!/\\r?\\n/.test(texto.trim())) return\r\n e.preventDefault()\r\n const lista = texto.split(/\\r?\\n/).map(t => t.trim()).filter(Boolean).join(', ')\r\n const el = e.currentTarget\r\n pesquisar(el.value.slice(0, el.selectionStart ?? el.value.length) + lista + el.value.slice(el.selectionEnd ?? el.value.length))\r\n }\r\n\r\n // Os mesmos termos filtram (com `searchText`) e realçam-se nas linhas.\r\n const termos = useMemo(() => termosDaPesquisa(pesquisa).map(semAcentos), [pesquisa])\r\n const filtradas = useMemo(() => {\r\n if (!searchText || !termos.length) return items\r\n return items.filter(it => {\r\n const texto = semAcentos(searchText(it))\r\n return termos.some(t => texto.includes(t))\r\n })\r\n }, [items, termos, searchText])\r\n\r\n // Paginação: do servidor (com `total`), ou feita aqui.\r\n const peloServidor = total !== undefined\r\n const aRolar = mode === 'scroll'\r\n const [paginaPropria, setPaginaPropria] = useState(1)\r\n const totalLinhas = peloServidor ? total : filtradas.length\r\n const totalPaginas = Math.max(1, Math.ceil(totalLinhas / pageSize))\r\n const pagina = Math.min(peloServidor ? (page ?? 1) : paginaPropria, totalPaginas)\r\n // Uma pesquisa nova volta à primeira página: a terceira de antes já não é a mesma lista.\r\n useEffect(() => { if (!peloServidor) setPaginaPropria(1) }, [pesquisa, peloServidor])\r\n const irPara = (p: number) => { if (peloServidor) onPageChange?.(p); else setPaginaPropria(p) }\r\n // A rolar sem servidor: quantos lotes já se mostram.\r\n const [lotes, setLotes] = useState(1)\r\n useEffect(() => { setLotes(1) }, [pesquisa])\r\n const visiveis = peloServidor ? filtradas\r\n : aRolar ? filtradas.slice(0, lotes * pageSize)\r\n : filtradas.slice((pagina - 1) * pageSize, pagina * pageSize)\r\n const haMais = aRolar && (peloServidor ? !!onLoadMore && items.length < totalLinhas : visiveis.length < filtradas.length)\r\n\r\n // Ao mudar de página, ou de pesquisa a rolar, a lista volta ao topo.\r\n const [caixa, setCaixa] = useState<HTMLDivElement | null>(null)\r\n useEffect(() => { caixa?.scrollTo?.({ top: 0 }) }, [pagina, caixa])\r\n useEffect(() => { if (aRolar) caixa?.scrollTo?.({ top: 0 }) }, [pesquisa, aRolar, caixa])\r\n\r\n // A rolar: a linha-sentinela no fundo, ao chegar a 300px da vista, pede o\r\n // lote seguinte. Volta a observar a cada lote que chega: se continuar à\r\n // vista (uma janela alta), pede logo outro.\r\n const [fim, setFim] = useState<HTMLDivElement | null>(null)\r\n useEffect(() => {\r\n if (!fim || !caixa || !haMais || typeof IntersectionObserver === 'undefined') return\r\n const io = new IntersectionObserver(([e]) => {\r\n if (!e?.isIntersecting) return\r\n if (peloServidor) { if (!loading) onLoadMore?.() } else setLotes(n => n + 1)\r\n }, { root: caixa, rootMargin: '300px' })\r\n io.observe(fim)\r\n return () => io.disconnect()\r\n }, [fim, caixa, haMais, peloServidor, loading, onLoadMore, visiveis.length])\r\n\r\n const marcadas = selectedKeys ?? new Set<string>()\r\n const marcaveis = !!onSelectionChange\r\n const nMarcadasVisiveis = visiveis.filter(it => marcadas.has(itemKey(it))).length\r\n const todasMarcadas = visiveis.length > 0 && nMarcadasVisiveis === visiveis.length\r\n const marcar = (key: string, sim: boolean) => {\r\n const n = new Set(marcadas)\r\n if (sim) n.add(key); else n.delete(key)\r\n onSelectionChange?.(n)\r\n }\r\n const marcarVisiveis = () => {\r\n const n = new Set(marcadas)\r\n for (const it of visiveis) { if (todasMarcadas) n.delete(itemKey(it)); else n.add(itemKey(it)) }\r\n onSelectionChange?.(n)\r\n }\r\n\r\n const aCarregar = loading && visiveis.length === 0\r\n const esqueleto = <span className=\"inline-block h-2 w-20 rounded-sm bg-slate-200 animate-pulse motion-reduce:animate-none\" aria-hidden />\r\n\r\n return (\r\n <div className={`v1-reset flex h-full min-h-0 flex-col bg-white text-control text-slate-800 ${bordered ? 'border border-slate-200' : ''} ${className}`}>\r\n {/* Sem pesquisa, filtros nem voltar, não há cabeça: as linhas começam no topo. */}\r\n {(searchable || onBack || filters) && (\r\n <div className=\"space-y-2 border-b border-slate-100 px-3 py-2\">\r\n {(onBack || filters) && (\r\n <div className=\"flex items-center gap-1.5\">\r\n {onBack && (\r\n <button type=\"button\" onClick={onBack} aria-label={L.back} title={L.back}\r\n className=\"-ml-1 flex h-7 w-6 shrink-0 items-center justify-center rounded text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700\">\r\n <ChevronLeftIcon size={14} />\r\n </button>\r\n )}\r\n <div className=\"flex min-w-0 flex-1 items-center gap-1.5\">{filters}</div>\r\n </div>\r\n )}\r\n {searchable && (\r\n <div className=\"relative\">\r\n {/* Caixa direita e fina, no tamanho do texto das linhas: a lista é\r\n uma coluna estreita, e a pesquisa não deve pesar mais do que ela. */}\r\n <SearchIcon size={11} className=\"pointer-events-none absolute top-1/2 left-2 -translate-y-1/2 text-slate-400\" />\r\n <input\r\n type=\"search\"\r\n value={pesquisa}\r\n onChange={e => pesquisar(e.target.value)}\r\n onPaste={colar}\r\n placeholder={L.searchPlaceholder}\r\n aria-label={L.search}\r\n className=\"block w-full min-w-0 border border-slate-200 bg-white py-1.5 pr-2 pl-6 text-micro text-slate-700 outline-none transition-colors placeholder:text-slate-400 focus:border-proef-blue\"\r\n />\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Fora da área que rola: é a cabeça da lista, e é a descer a lista que\r\n se decide marcá-la toda. Branca, com a linha esbatida das linhas por\r\n baixo: uma faixa cinzenta entre o branco da pesquisa e o das linhas\r\n era um tom quase igual a meio, e lia-se como sujidade. */}\r\n {marcaveis && visiveis.length > 0 && (\r\n <div className={`flex items-center gap-1.5 bg-white px-3 py-2 text-[10px] text-slate-600 ${LINHA_ESBATIDA}`}>\r\n {/* Na coluna de 28px das linhas, para as caixas ficarem umas por baixo das outras. */}\r\n <span className=\"flex w-7 shrink-0 justify-center\">\r\n <Checkbox\r\n checked={todasMarcadas}\r\n indeterminate={nMarcadasVisiveis > 0 && !todasMarcadas}\r\n onChange={marcarVisiveis}\r\n aria-label={L.selectVisible(visiveis.length)}\r\n />\r\n </span>\r\n <span aria-hidden>{nMarcadasVisiveis > 0 ? L.selectedCount(nMarcadasVisiveis, visiveis.length) : L.selectVisible(visiveis.length)}</span>\r\n {marcadas.size > 0 && (\r\n <button type=\"button\" onClick={() => onSelectionChange?.(new Set())}\r\n className=\"ml-auto text-proef-blue hover:underline\">\r\n {L.clearSelection}\r\n </button>\r\n )}\r\n </div>\r\n )}\r\n\r\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\r\n {/* `relative`: o texto só para leitores de ecrã (`sr-only`, posição\r\n absoluta) do fundo da lista tem de ficar preso a esta caixa. Sem isso\r\n ficava no fim das linhas, fora do corte do scroll, e esticava a\r\n página — dava para rolar até um branco sem nada. */}\r\n <div ref={setCaixa} className=\"relative min-h-0 flex-1 overflow-y-auto scrollbar-none\">\r\n {aCarregar ? (\r\n // A forma das linhas já diz que vem ali a lista. Mais do que cabem,\r\n // cortadas pela caixa: chega ao fundo em qualquer altura.\r\n <div role=\"status\" aria-busy=\"true\" className=\"h-full overflow-hidden\">\r\n <span className=\"sr-only\">{L.loading}</span>\r\n {['w-11/12', 'w-3/4', 'w-5/6', 'w-2/3', 'w-4/5', 'w-7/12'].flatMap(l => [l, l, l, l]).map((largura, i) => (\r\n <LinhaEsqueleto key={i} marcavel={marcaveis} largura={largura} />\r\n ))}\r\n </div>\r\n ) : visiveis.length === 0 ? (\r\n <p className=\"m-0 px-3 py-4 text-center text-[10px] text-slate-400\">{L.empty}</p>\r\n ) : (\r\n <ul className=\"m-0 list-none p-0\">\r\n {visiveis.map(it => {\r\n const key = itemKey(it)\r\n return (\r\n <ListItem\r\n key={key}\r\n {...itemContent(it)}\r\n active={activeKey === key}\r\n onClick={onItemClick ? () => onItemClick(it) : undefined}\r\n checked={marcadas.has(key)}\r\n onCheckedChange={marcaveis ? sim => marcar(key, sim) : undefined}\r\n checkboxLabel={L.checkItem(key)}\r\n highlight={termos}\r\n />\r\n )\r\n })}\r\n </ul>\r\n )}\r\n {/* A rolar: o lote seguinte a chegar, e a sentinela que o pede. */}\r\n {!aCarregar && haMais && (\r\n <div ref={setFim} role=\"status\" aria-busy=\"true\">\r\n <span className=\"sr-only\">{L.loading}</span>\r\n {['w-3/4', 'w-5/6', 'w-2/3'].map((largura, i) => <LinhaEsqueleto key={i} marcavel={marcaveis} largura={largura} />)}\r\n </div>\r\n )}\r\n </div>\r\n <OverlayScrollbar alvo={caixa} borda={0} />\r\n </div>\r\n\r\n {/* Sempre à vista e com a mesma altura, carregada ou não: aparecer do\r\n nada fazia saltar o fundo do painel. */}\r\n <div className={`flex shrink-0 items-center gap-2 border-t border-slate-100 bg-slate-50 px-3 text-micro text-slate-400 ${aRolar ? 'h-8 justify-center' : 'h-9 justify-between'}`}>\r\n <span className=\"flex items-center gap-1 whitespace-nowrap\">\r\n {aCarregar ? esqueleto : aRolar ? (\r\n // A rolar: quantas já se mostram, de quantas há.\r\n <><span className=\"font-medium text-slate-500 tabular-nums\">{visiveis.length.toLocaleString('pt-PT')}</span> / <span className=\"font-medium text-slate-500 tabular-nums\">{totalLinhas.toLocaleString('pt-PT')}</span> {L.itemsNoun(totalLinhas)}</>\r\n ) : <><span className=\"font-medium text-slate-500 tabular-nums\">{totalLinhas.toLocaleString('pt-PT')}</span> {L.itemsNoun(totalLinhas)}</>}\r\n {footerExtra}\r\n {/* Com linhas e a pedir outra vez (ex: outra página, ou a atualizar): um \"…\" a pulsar. */}\r\n {loading && !aCarregar && <span aria-hidden className=\"animate-pulse motion-reduce:animate-none\">…</span>}\r\n </span>\r\n {!aRolar && !aCarregar && totalPaginas > 1 && (\r\n <nav aria-label={L.pagination} className=\"flex items-center gap-0.5\">\r\n <span className=\"sr-only\">{L.page} {pagina} / {totalPaginas}</span>\r\n {numerosPagina(pagina, totalPaginas).map((p, i) => p === '…' ? (\r\n <span key={`r${i}`} aria-hidden className=\"flex h-6 min-w-3 items-center justify-center text-slate-300\">…</span>\r\n ) : (\r\n <button\r\n key={p}\r\n type=\"button\"\r\n onClick={() => irPara(p)}\r\n aria-current={p === pagina ? 'page' : undefined}\r\n className={`grid h-6 min-w-5 place-items-center px-1 tabular-nums transition-colors ${\r\n p === pagina ? 'cursor-default font-semibold text-slate-800' : 'text-slate-400 hover:text-slate-700'}`}\r\n >\r\n {/* Sem caixa nem fundo: a página atual é só o número mais forte.\r\n A cópia invisível a negrito guarda a largura, para os números\r\n não se mexerem ao trocar de página. */}\r\n <span aria-hidden className=\"invisible col-start-1 row-start-1 font-semibold\">{p.toLocaleString('pt-PT')}</span>\r\n <span className=\"col-start-1 row-start-1\">{p.toLocaleString('pt-PT')}</span>\r\n </button>\r\n ))}\r\n </nav>\r\n )}\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -111,7 +111,7 @@ function ColumnFilter({
111
111
  width: LARGURA,
112
112
  maxHeight: ALTURA
113
113
  },
114
- className: "v1-reset z-[250] flex flex-col bg-white border border-slate-200 shadow-lg",
114
+ className: "v1-reset z-[250] flex flex-col overflow-hidden rounded-lg bg-white border border-slate-200 shadow-lg",
115
115
  children: [
116
116
  label && /* @__PURE__ */ jsx("div", { className: "px-3 pt-2.5 pb-2 border-b border-slate-100 flex-shrink-0", children: /* @__PURE__ */ jsx("span", { className: "text-[9px] font-black uppercase tracking-widest text-proef-blue", children: label }) }),
117
117
  aCarregar && /* @__PURE__ */ jsxs("div", { className: "px-3 py-1.5 bg-amber-50 border-b border-amber-100 flex items-center gap-1.5 text-micro text-amber-700 flex-shrink-0", children: [
@@ -123,7 +123,7 @@ function ColumnFilter({
123
123
  "button",
124
124
  {
125
125
  onClick: () => onOrdenar("asc"),
126
- className: `w-full flex items-center gap-2 px-2.5 py-1.5 text-caption font-semibold text-left hover:bg-slate-50 transition-colors ${ordenadaPor === "asc" ? "text-proef-blue bg-slate-50" : "text-slate-600"}`,
126
+ className: `w-full flex items-center gap-2 rounded-md px-2.5 py-1.5 text-caption font-semibold text-left hover:bg-slate-50 transition-colors ${ordenadaPor === "asc" ? "text-proef-blue bg-slate-50" : "text-slate-600"}`,
127
127
  children: [
128
128
  /* @__PURE__ */ jsx(ChevronUpIcon, { size: 11 }),
129
129
  " ",
@@ -135,7 +135,7 @@ function ColumnFilter({
135
135
  "button",
136
136
  {
137
137
  onClick: () => onOrdenar("desc"),
138
- className: `w-full flex items-center gap-2 px-2.5 py-1.5 text-caption font-semibold text-left hover:bg-slate-50 transition-colors ${ordenadaPor === "desc" ? "text-proef-blue bg-slate-50" : "text-slate-600"}`,
138
+ className: `w-full flex items-center gap-2 rounded-md px-2.5 py-1.5 text-caption font-semibold text-left hover:bg-slate-50 transition-colors ${ordenadaPor === "desc" ? "text-proef-blue bg-slate-50" : "text-slate-600"}`,
139
139
  children: [
140
140
  /* @__PURE__ */ jsx(ChevronDownIcon, { size: 11 }),
141
141
  " ",
@@ -156,7 +156,7 @@ function ColumnFilter({
156
156
  onKeyDown: (e) => {
157
157
  if (e.key === "Enter") onAplicarDatas?.(de, ate);
158
158
  },
159
- className: "w-full mt-0.5 bg-slate-50 border border-slate-100 px-2.5 py-1.5 text-xs outline-none focus:border-proef-blue"
159
+ className: "w-full mt-0.5 rounded-md bg-slate-50 border border-slate-100 px-2.5 py-1.5 text-xs outline-none focus:border-proef-blue"
160
160
  }
161
161
  )
162
162
  ] }),
@@ -171,7 +171,7 @@ function ColumnFilter({
171
171
  onKeyDown: (e) => {
172
172
  if (e.key === "Enter") onAplicarDatas?.(de, ate);
173
173
  },
174
- className: "w-full mt-0.5 bg-slate-50 border border-slate-100 px-2.5 py-1.5 text-xs outline-none focus:border-proef-blue"
174
+ className: "w-full mt-0.5 rounded-md bg-slate-50 border border-slate-100 px-2.5 py-1.5 text-xs outline-none focus:border-proef-blue"
175
175
  }
176
176
  )
177
177
  ] })
@@ -195,7 +195,7 @@ function ColumnFilter({
195
195
  onKeyDown: (e) => {
196
196
  if (e.key === "Enter") onAplicarNumeros?.(min, max);
197
197
  },
198
- className: "w-full mt-0.5 bg-slate-50 border border-slate-100 px-2.5 py-1.5 text-xs outline-none focus:border-proef-blue"
198
+ className: "w-full mt-0.5 rounded-md bg-slate-50 border border-slate-100 px-2.5 py-1.5 text-xs outline-none focus:border-proef-blue"
199
199
  }
200
200
  )
201
201
  ] }),
@@ -210,7 +210,7 @@ function ColumnFilter({
210
210
  onKeyDown: (e) => {
211
211
  if (e.key === "Enter") onAplicarNumeros?.(min, max);
212
212
  },
213
- className: "w-full mt-0.5 bg-slate-50 border border-slate-100 px-2.5 py-1.5 text-xs outline-none focus:border-proef-blue"
213
+ className: "w-full mt-0.5 rounded-md bg-slate-50 border border-slate-100 px-2.5 py-1.5 text-xs outline-none focus:border-proef-blue"
214
214
  }
215
215
  )
216
216
  ] })
@@ -225,7 +225,7 @@ function ColumnFilter({
225
225
  value: busca,
226
226
  onChange: (e) => setBusca(e.target.value),
227
227
  placeholder: L.search,
228
- className: "flex-1 min-w-0 bg-slate-50 border border-slate-100 px-2.5 py-1.5 text-xs outline-none focus:border-proef-blue"
228
+ className: "flex-1 min-w-0 rounded-md bg-slate-50 border border-slate-100 px-2.5 py-1.5 text-xs outline-none focus:border-proef-blue"
229
229
  }
230
230
  ),
231
231
  botaoModo && /* @__PURE__ */ jsx("span", { className: "pr-1.5", children: botaoModo })
@@ -236,15 +236,15 @@ function ColumnFilter({
236
236
  onClick: alternarTodos,
237
237
  className: "flex items-center gap-2 px-3 py-1.5 border-y border-slate-100 hover:bg-slate-50 text-left flex-shrink-0",
238
238
  children: [
239
- /* @__PURE__ */ jsx("div", { className: `w-3.5 h-3.5 flex items-center justify-center border flex-shrink-0 transition-colors ${todosVisiveis ? "bg-primary border-primary" : algunsVisiveis ? "bg-primary/30 border-primary" : "border-slate-300"}`, children: todosVisiveis && /* @__PURE__ */ jsx(CheckIcon, { size: 9, strokeWidth: 3, className: "text-white" }) }),
239
+ /* @__PURE__ */ jsx("div", { className: `w-3.5 h-3.5 flex items-center justify-center rounded-sm border flex-shrink-0 transition-colors ${todosVisiveis ? "bg-primary border-primary" : algunsVisiveis ? "bg-primary/30 border-primary" : "border-slate-300"}`, children: todosVisiveis && /* @__PURE__ */ jsx(CheckIcon, { size: 9, strokeWidth: 3, className: "text-white" }) }),
240
240
  /* @__PURE__ */ jsx("span", { className: "text-caption font-semibold text-slate-600", children: busca ? L.selectAllFound(visiveis.length) : L.selectAll })
241
241
  ]
242
242
  }
243
243
  ),
244
244
  /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-1.5 space-y-0.5 min-h-0", children: visiveis.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-caption text-slate-400 text-center py-3", children: L.noResults }) : visiveis.map((v) => {
245
245
  const escolhido = rascunho.has(v);
246
- return /* @__PURE__ */ jsxs("button", { onClick: () => alternar(v), className: "w-full flex items-center gap-2 px-2 py-1.5 hover:bg-slate-50 text-left", children: [
247
- /* @__PURE__ */ jsx("div", { className: `w-3.5 h-3.5 flex items-center justify-center border flex-shrink-0 transition-colors ${escolhido ? "bg-primary border-primary" : "border-slate-300"}`, children: escolhido && /* @__PURE__ */ jsx(CheckIcon, { size: 9, strokeWidth: 3, className: "text-white" }) }),
246
+ return /* @__PURE__ */ jsxs("button", { onClick: () => alternar(v), className: "w-full flex items-center gap-2 rounded-md px-2 py-1.5 hover:bg-slate-50 text-left", children: [
247
+ /* @__PURE__ */ jsx("div", { className: `w-3.5 h-3.5 flex items-center justify-center rounded-sm border flex-shrink-0 transition-colors ${escolhido ? "bg-primary border-primary" : "border-slate-300"}`, children: escolhido && /* @__PURE__ */ jsx(CheckIcon, { size: 9, strokeWidth: 3, className: "text-white" }) }),
248
248
  /* @__PURE__ */ jsx("span", { className: "text-xs text-slate-700 whitespace-normal break-words leading-snug", children: v })
249
249
  ] }, v);
250
250
  }) })
@@ -259,7 +259,7 @@ function ColumnFilter({
259
259
  else onAplicar(rascunho);
260
260
  },
261
261
  disabled: !sujo,
262
- className: "flex-1 text-micro font-black uppercase tracking-wider text-white bg-primary hover:bg-primary/90 py-1.5 disabled:opacity-30",
262
+ className: "flex-1 text-micro font-black uppercase tracking-wider text-white bg-primary hover:bg-primary/90 rounded-md py-1.5 disabled:opacity-30",
263
263
  children: L.apply
264
264
  }
265
265
  ),
@@ -267,7 +267,7 @@ function ColumnFilter({
267
267
  "button",
268
268
  {
269
269
  onClick: onFechar,
270
- className: "text-micro font-black uppercase tracking-wider text-slate-500 hover:text-slate-700 px-2 py-1.5",
270
+ className: "text-micro font-black uppercase tracking-wider text-slate-500 hover:text-slate-700 rounded-md px-2 py-1.5",
271
271
  children: L.cancel
272
272
  }
273
273
  ),
@@ -275,7 +275,7 @@ function ColumnFilter({
275
275
  "button",
276
276
  {
277
277
  onClick: onLimpar,
278
- className: "text-micro font-black uppercase tracking-wider text-red-400 hover:text-red-500 px-2 py-1.5",
278
+ className: "text-micro font-black uppercase tracking-wider text-red-400 hover:text-red-500 rounded-md px-2 py-1.5",
279
279
  children: L.clearFilter
280
280
  }
281
281
  )