@proef/common-ui 2.3.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/esm/legacy/components/molecules/DataTable/DataTable.js +195 -103
  2. package/dist/esm/legacy/components/molecules/DataTable/DataTable.js.map +1 -1
  3. package/dist/esm/v1/capabilities/overlay/useFloatingPanel.js +5 -2
  4. package/dist/esm/v1/capabilities/overlay/useFloatingPanel.js.map +1 -1
  5. package/dist/esm/v1/components/data/Pagination/Pagination.js +30 -26
  6. package/dist/esm/v1/components/data/Pagination/Pagination.js.map +1 -1
  7. package/dist/esm/v1/components/forms/FormField/FormField.js +3 -2
  8. package/dist/esm/v1/components/forms/FormField/FormField.js.map +1 -1
  9. package/dist/esm/v1/components/layout/AppShell/AppShell.js +8 -3
  10. package/dist/esm/v1/components/layout/AppShell/AppShell.js.map +1 -1
  11. package/dist/esm/v1/components/layout/Page/Page.js +1 -1
  12. package/dist/esm/v1/components/layout/Page/Page.js.map +1 -1
  13. package/dist/esm/v1/components/layout/PageHeader/PageHeader.js +21 -18
  14. package/dist/esm/v1/components/layout/PageHeader/PageHeader.js.map +1 -1
  15. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js +16 -16
  16. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js.map +1 -1
  17. package/dist/esm/v1/components/overlays/Dialog/Dialog.js +2 -2
  18. package/dist/esm/v1/components/overlays/Dialog/Dialog.js.map +1 -1
  19. package/dist/esm/v1/primitives/buttons/SegmentedControl/SegmentedControl.js +3 -3
  20. package/dist/esm/v1/primitives/buttons/SegmentedControl/SegmentedControl.js.map +1 -1
  21. package/dist/esm/v1/primitives/display/Kpi/Kpi.js +6 -3
  22. package/dist/esm/v1/primitives/display/Kpi/Kpi.js.map +1 -1
  23. package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js +28 -8
  24. package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js.map +1 -1
  25. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js +5 -5
  26. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js.map +1 -1
  27. package/dist/esm/v1/primitives/navigation/Tabs/Tabs.js +97 -36
  28. package/dist/esm/v1/primitives/navigation/Tabs/Tabs.js.map +1 -1
  29. package/dist/style.components.css +1 -1
  30. package/dist/style.css +1 -1
  31. package/dist/style.layered.css +1 -1
  32. package/dist/types/legacy/components/molecules/DataTable/DataTable.d.ts +15 -1
  33. package/dist/types/v1/primitives/forms/SelectInput/SelectInput.d.ts +12 -0
  34. package/dist/types/v1/primitives/lists/ListItem/ListItem.d.ts +1 -1
  35. package/package.json +1 -1
@@ -14,11 +14,14 @@ function useFloatingPanel(align = "left") {
14
14
  }
15
15
  const r = triggerRef.current.getBoundingClientRect();
16
16
  const height = panelRef.current?.offsetHeight ?? 240;
17
- const horizontal = align === "right" ? { right: window.innerWidth - r.right } : { left: r.left };
17
+ const vw = window.innerWidth;
18
+ const width = Math.min(Math.max(panelRef.current?.offsetWidth ?? 0, r.width), vw - 16);
19
+ const horizontal = align === "right" ? { right: Math.min(Math.max(8, vw - r.right), vw - 8 - width) } : { left: Math.min(Math.max(8, r.left), vw - 8 - width) };
18
20
  const fitsBelow = r.bottom + 4 + height <= window.innerHeight - 8;
19
21
  setPos({
20
22
  ...horizontal,
21
- minWidth: r.width,
23
+ minWidth: Math.min(r.width, vw - 16),
24
+ maxWidth: vw - 16,
22
25
  ...fitsBelow || r.top < height ? { top: r.bottom + 4 } : { bottom: window.innerHeight - r.top + 4 }
23
26
  });
24
27
  }, [open, align]);
@@ -1 +1 @@
1
- {"version":3,"file":"useFloatingPanel.js","sources":["../../../../../src/v1/capabilities/overlay/useFloatingPanel.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from 'react'\r\n\r\n/**\r\n * Aspeto comum das listas suspensas (SelectInput, MenuAcoes): cartão arredondado com sombra, nos dois temas.\r\n * Um só sítio para ficarem iguais.\r\n */\r\nexport const PANEL_CLASS =\r\n 'fixed z-[60] min-w-[10rem] max-h-72 overflow-auto rounded-xl border border-slate-200 bg-white py-1 text-sm shadow-lg dark:border-slate-700 dark:bg-slate-900'\r\n\r\n/** Item de uma lista suspensa; `active` = o que o teclado tem selecionado. */\r\nexport const itemClass = (active: boolean, danger = false) =>\r\n `w-full flex items-center gap-2.5 px-3 py-2 text-left cursor-pointer select-none ${\r\n danger ? 'text-red-600 dark:text-red-300' : 'text-slate-700 dark:text-slate-200'\r\n } ${active ? 'bg-slate-100 dark:bg-slate-800' : 'bg-transparent hover:bg-slate-50 dark:hover:bg-slate-800'}`\r\n\r\n/**\r\n * Painel flutuante preso a um botão: posição fixa (não é cortado por tabelas ou janelas com scroll), abre para\r\n * cima quando não cabe por baixo, fecha ao clicar fora, ao fazer scroll fora dele ou ao redimensionar.\r\n */\r\nexport function useFloatingPanel(align: 'left' | 'right' = 'left') {\r\n const [open, setOpen] = useState(false)\r\n const triggerRef = useRef<HTMLButtonElement>(null)\r\n const panelRef = useRef<HTMLDivElement>(null)\r\n const [pos, setPos] = useState<CSSProperties | null>(null)\r\n\r\n const close = useCallback(() => setOpen(false), [])\r\n\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) {\r\n setPos(null)\r\n return\r\n }\r\n const r = triggerRef.current.getBoundingClientRect()\r\n const height = panelRef.current?.offsetHeight ?? 240\r\n const horizontal = align === 'right' ? { right: window.innerWidth - r.right } : { left: r.left }\r\n const fitsBelow = r.bottom + 4 + height <= window.innerHeight - 8\r\n setPos({\r\n ...horizontal,\r\n minWidth: r.width,\r\n ...(fitsBelow || r.top < height ? { top: r.bottom + 4 } : { bottom: window.innerHeight - r.top + 4 }),\r\n })\r\n }, [open, align])\r\n\r\n useEffect(() => {\r\n if (!open) return\r\n const click = (e: MouseEvent) => {\r\n const t = e.target as Node\r\n if (!triggerRef.current?.contains(t) && !panelRef.current?.contains(t)) setOpen(false)\r\n }\r\n const scroll = (e: Event) => {\r\n if (!panelRef.current?.contains(e.target as Node)) setOpen(false)\r\n }\r\n const resize = () => setOpen(false)\r\n document.addEventListener('mousedown', click)\r\n window.addEventListener('scroll', scroll, true)\r\n window.addEventListener('resize', resize)\r\n return () => {\r\n document.removeEventListener('mousedown', click)\r\n window.removeEventListener('scroll', scroll, true)\r\n window.removeEventListener('resize', resize)\r\n }\r\n }, [open])\r\n\r\n // Antes de medir, o painel desenha-se invisível (para se saber a altura).\r\n const style: CSSProperties = pos ?? { top: 0, left: 0, visibility: 'hidden' }\r\n return { open, setOpen, close, triggerRef, panelRef, style }\r\n}\r\n"],"names":[],"mappings":";AAMO,MAAM,cACX;AAGK,MAAM,YAAY,CAAC,QAAiB,SAAS,UAClD,mFACE,SAAS,mCAAmC,oCAC9C,IAAI,SAAS,mCAAmC,0DAA0D;AAMrG,SAAS,iBAAiB,QAA0B,QAAQ;AACjE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,aAAa,OAA0B,IAAI;AACjD,QAAM,WAAW,OAAuB,IAAI;AAC5C,QAAM,CAAC,KAAK,MAAM,IAAI,SAA+B,IAAI;AAEzD,QAAM,QAAQ,YAAY,MAAM,QAAQ,KAAK,GAAG,CAAA,CAAE;AAElD,kBAAgB,MAAM;AACpB,QAAI,CAAC,QAAQ,CAAC,WAAW,SAAS;AAChC,aAAO,IAAI;AACX;AAAA,IACF;AACA,UAAM,IAAI,WAAW,QAAQ,sBAAA;AAC7B,UAAM,SAAS,SAAS,SAAS,gBAAgB;AACjD,UAAM,aAAa,UAAU,UAAU,EAAE,OAAO,OAAO,aAAa,EAAE,MAAA,IAAU,EAAE,MAAM,EAAE,KAAA;AAC1F,UAAM,YAAY,EAAE,SAAS,IAAI,UAAU,OAAO,cAAc;AAChE,WAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU,EAAE;AAAA,MACZ,GAAI,aAAa,EAAE,MAAM,SAAS,EAAE,KAAK,EAAE,SAAS,EAAA,IAAM,EAAE,QAAQ,OAAO,cAAc,EAAE,MAAM,EAAA;AAAA,IAAE,CACpG;AAAA,EACH,GAAG,CAAC,MAAM,KAAK,CAAC;AAEhB,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,CAAC,MAAkB;AAC/B,YAAM,IAAI,EAAE;AACZ,UAAI,CAAC,WAAW,SAAS,SAAS,CAAC,KAAK,CAAC,SAAS,SAAS,SAAS,CAAC,WAAW,KAAK;AAAA,IACvF;AACA,UAAM,SAAS,CAAC,MAAa;AAC3B,UAAI,CAAC,SAAS,SAAS,SAAS,EAAE,MAAc,WAAW,KAAK;AAAA,IAClE;AACA,UAAM,SAAS,MAAM,QAAQ,KAAK;AAClC,aAAS,iBAAiB,aAAa,KAAK;AAC5C,WAAO,iBAAiB,UAAU,QAAQ,IAAI;AAC9C,WAAO,iBAAiB,UAAU,MAAM;AACxC,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,KAAK;AAC/C,aAAO,oBAAoB,UAAU,QAAQ,IAAI;AACjD,aAAO,oBAAoB,UAAU,MAAM;AAAA,IAC7C;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAGT,QAAM,QAAuB,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,YAAY,SAAA;AACnE,SAAO,EAAE,MAAM,SAAS,OAAO,YAAY,UAAU,MAAA;AACvD;"}
1
+ {"version":3,"file":"useFloatingPanel.js","sources":["../../../../../src/v1/capabilities/overlay/useFloatingPanel.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from 'react'\r\n\r\n/**\r\n * Aspeto comum das listas suspensas (SelectInput, MenuAcoes): cartão arredondado com sombra, nos dois temas.\r\n * Um só sítio para ficarem iguais.\r\n */\r\nexport const PANEL_CLASS =\r\n 'fixed z-[60] min-w-[10rem] max-h-72 overflow-auto rounded-xl border border-slate-200 bg-white py-1 text-sm shadow-lg dark:border-slate-700 dark:bg-slate-900'\r\n\r\n/** Item de uma lista suspensa; `active` = o que o teclado tem selecionado. */\r\nexport const itemClass = (active: boolean, danger = false) =>\r\n `w-full flex items-center gap-2.5 px-3 py-2 text-left cursor-pointer select-none ${\r\n danger ? 'text-red-600 dark:text-red-300' : 'text-slate-700 dark:text-slate-200'\r\n } ${active ? 'bg-slate-100 dark:bg-slate-800' : 'bg-transparent hover:bg-slate-50 dark:hover:bg-slate-800'}`\r\n\r\n/**\r\n * Painel flutuante preso a um botão: posição fixa (não é cortado por tabelas ou janelas com scroll), abre para\r\n * cima quando não cabe por baixo, fecha ao clicar fora, ao fazer scroll fora dele ou ao redimensionar.\r\n */\r\nexport function useFloatingPanel(align: 'left' | 'right' = 'left') {\r\n const [open, setOpen] = useState(false)\r\n const triggerRef = useRef<HTMLButtonElement>(null)\r\n const panelRef = useRef<HTMLDivElement>(null)\r\n const [pos, setPos] = useState<CSSProperties | null>(null)\r\n\r\n const close = useCallback(() => setOpen(false), [])\r\n\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) {\r\n setPos(null)\r\n return\r\n }\r\n const r = triggerRef.current.getBoundingClientRect()\r\n const height = panelRef.current?.offsetHeight ?? 240\r\n // Nunca sai do ecrã para o lado (ex: um select à direita, no telemóvel, com opções mais largas do que ele):\r\n // encosta-se a 8px da margem e, se for mais largo do que o ecrã, fica com a largura dele.\r\n const vw = window.innerWidth\r\n const width = Math.min(Math.max(panelRef.current?.offsetWidth ?? 0, r.width), vw - 16)\r\n const horizontal =\r\n align === 'right'\r\n ? { right: Math.min(Math.max(8, vw - r.right), vw - 8 - width) }\r\n : { left: Math.min(Math.max(8, r.left), vw - 8 - width) }\r\n const fitsBelow = r.bottom + 4 + height <= window.innerHeight - 8\r\n setPos({\r\n ...horizontal,\r\n minWidth: Math.min(r.width, vw - 16),\r\n maxWidth: vw - 16,\r\n ...(fitsBelow || r.top < height ? { top: r.bottom + 4 } : { bottom: window.innerHeight - r.top + 4 }),\r\n })\r\n }, [open, align])\r\n\r\n useEffect(() => {\r\n if (!open) return\r\n const click = (e: MouseEvent) => {\r\n const t = e.target as Node\r\n if (!triggerRef.current?.contains(t) && !panelRef.current?.contains(t)) setOpen(false)\r\n }\r\n const scroll = (e: Event) => {\r\n if (!panelRef.current?.contains(e.target as Node)) setOpen(false)\r\n }\r\n const resize = () => setOpen(false)\r\n document.addEventListener('mousedown', click)\r\n window.addEventListener('scroll', scroll, true)\r\n window.addEventListener('resize', resize)\r\n return () => {\r\n document.removeEventListener('mousedown', click)\r\n window.removeEventListener('scroll', scroll, true)\r\n window.removeEventListener('resize', resize)\r\n }\r\n }, [open])\r\n\r\n // Antes de medir, o painel desenha-se invisível (para se saber a altura).\r\n const style: CSSProperties = pos ?? { top: 0, left: 0, visibility: 'hidden' }\r\n return { open, setOpen, close, triggerRef, panelRef, style }\r\n}\r\n"],"names":[],"mappings":";AAMO,MAAM,cACX;AAGK,MAAM,YAAY,CAAC,QAAiB,SAAS,UAClD,mFACE,SAAS,mCAAmC,oCAC9C,IAAI,SAAS,mCAAmC,0DAA0D;AAMrG,SAAS,iBAAiB,QAA0B,QAAQ;AACjE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,aAAa,OAA0B,IAAI;AACjD,QAAM,WAAW,OAAuB,IAAI;AAC5C,QAAM,CAAC,KAAK,MAAM,IAAI,SAA+B,IAAI;AAEzD,QAAM,QAAQ,YAAY,MAAM,QAAQ,KAAK,GAAG,CAAA,CAAE;AAElD,kBAAgB,MAAM;AACpB,QAAI,CAAC,QAAQ,CAAC,WAAW,SAAS;AAChC,aAAO,IAAI;AACX;AAAA,IACF;AACA,UAAM,IAAI,WAAW,QAAQ,sBAAA;AAC7B,UAAM,SAAS,SAAS,SAAS,gBAAgB;AAGjD,UAAM,KAAK,OAAO;AAClB,UAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,SAAS,SAAS,eAAe,GAAG,EAAE,KAAK,GAAG,KAAK,EAAE;AACrF,UAAM,aACJ,UAAU,UACN,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,EAAE,KAAK,GAAG,KAAK,IAAI,KAAK,EAAA,IAC3D,EAAE,MAAM,KAAK,IAAI,KAAK,IAAI,GAAG,EAAE,IAAI,GAAG,KAAK,IAAI,KAAK,EAAA;AAC1D,UAAM,YAAY,EAAE,SAAS,IAAI,UAAU,OAAO,cAAc;AAChE,WAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU,KAAK,IAAI,EAAE,OAAO,KAAK,EAAE;AAAA,MACnC,UAAU,KAAK;AAAA,MACf,GAAI,aAAa,EAAE,MAAM,SAAS,EAAE,KAAK,EAAE,SAAS,EAAA,IAAM,EAAE,QAAQ,OAAO,cAAc,EAAE,MAAM,EAAA;AAAA,IAAE,CACpG;AAAA,EACH,GAAG,CAAC,MAAM,KAAK,CAAC;AAEhB,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,CAAC,MAAkB;AAC/B,YAAM,IAAI,EAAE;AACZ,UAAI,CAAC,WAAW,SAAS,SAAS,CAAC,KAAK,CAAC,SAAS,SAAS,SAAS,CAAC,WAAW,KAAK;AAAA,IACvF;AACA,UAAM,SAAS,CAAC,MAAa;AAC3B,UAAI,CAAC,SAAS,SAAS,SAAS,EAAE,MAAc,WAAW,KAAK;AAAA,IAClE;AACA,UAAM,SAAS,MAAM,QAAQ,KAAK;AAClC,aAAS,iBAAiB,aAAa,KAAK;AAC5C,WAAO,iBAAiB,UAAU,QAAQ,IAAI;AAC9C,WAAO,iBAAiB,UAAU,MAAM;AACxC,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,KAAK;AAC/C,aAAO,oBAAoB,UAAU,QAAQ,IAAI;AACjD,aAAO,oBAAoB,UAAU,MAAM;AAAA,IAC7C;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAGT,QAAM,QAAuB,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,YAAY,SAAA;AACnE,SAAO,EAAE,MAAM,SAAS,OAAO,YAAY,UAAU,MAAA;AACvD;"}
@@ -28,35 +28,39 @@ function Pagination({
28
28
  const L = { ...DEFAULT_PAGINATION_LABELS, ...labels };
29
29
  const totalPaginas = Math.max(1, pages);
30
30
  const opcoes = pageSizeOptions && pageSize != null ? [.../* @__PURE__ */ new Set([...pageSizeOptions, pageSize])].sort((a, b) => a - b) : [];
31
- return /* @__PURE__ */ jsxs("nav", { "aria-label": L.page.replace("{page}", String(page)).replace("{pages}", String(totalPaginas)), className: `flex flex-wrap items-center justify-between gap-2 ${className}`, children: [
32
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-black uppercase tracking-widest text-muted-foreground", children: [
33
- total != null && /* @__PURE__ */ jsxs(Fragment, { children: [
34
- L.total.replace("{count}", total.toLocaleString()),
35
- " · "
31
+ return (
32
+ // No telemóvel numa só linha: o texto à esquerda e as setas à direita, só com o ícone (com o texto
33
+ // "Anterior"/"Seguinte" partia-se em duas linhas encostadas à esquerda); sem o seletor por página.
34
+ /* @__PURE__ */ jsxs("nav", { "aria-label": L.page.replace("{page}", String(page)).replace("{pages}", String(totalPaginas)), className: `flex flex-wrap items-center justify-between gap-2 max-sm:flex-nowrap ${className}`, children: [
35
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 text-[10px] font-black uppercase tracking-widest text-muted-foreground max-sm:tracking-wider", children: [
36
+ total != null && /* @__PURE__ */ jsxs(Fragment, { children: [
37
+ L.total.replace("{count}", total.toLocaleString()),
38
+ " · "
39
+ ] }),
40
+ L.page.replace("{page}", String(page)).replace("{pages}", String(totalPaginas))
36
41
  ] }),
37
- L.page.replace("{page}", String(page)).replace("{pages}", String(totalPaginas))
38
- ] }),
39
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
40
- opcoes.length > 0 && onPageSizeChange && /* @__PURE__ */ jsxs("label", { className: "mr-2 flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest text-muted-foreground", children: [
41
- L.perPage,
42
- /* @__PURE__ */ jsx(
43
- SelectInput,
44
- {
45
- "aria-label": L.perPage,
46
- value: String(pageSize),
47
- onChange: (e) => onPageSizeChange(Number(e.target.value)),
48
- className: "w-20 normal-case tracking-normal font-normal",
49
- children: opcoes.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: n }, n))
50
- }
51
- )
52
- ] }),
53
- /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", icon: /* @__PURE__ */ jsx(ChevronLeft, {}), disabled: page <= 1, onClick: () => onPageChange(page - 1), children: L.prev }),
54
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", size: "sm", disabled: page >= totalPaginas, onClick: () => onPageChange(page + 1), children: [
55
- L.next,
56
- /* @__PURE__ */ jsx(ChevronRight, {})
42
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5 max-sm:shrink-0 max-sm:flex-nowrap", children: [
43
+ opcoes.length > 0 && onPageSizeChange && /* @__PURE__ */ jsxs("label", { className: "mr-2 flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest text-muted-foreground max-sm:hidden", children: [
44
+ L.perPage,
45
+ /* @__PURE__ */ jsx(
46
+ SelectInput,
47
+ {
48
+ "aria-label": L.perPage,
49
+ value: String(pageSize),
50
+ onChange: (e) => onPageSizeChange(Number(e.target.value)),
51
+ className: "w-20 normal-case tracking-normal font-normal",
52
+ children: opcoes.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: n }, n))
53
+ }
54
+ )
55
+ ] }),
56
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", icon: /* @__PURE__ */ jsx(ChevronLeft, {}), disabled: page <= 1, onClick: () => onPageChange(page - 1), children: /* @__PURE__ */ jsx("span", { className: "max-sm:sr-only", children: L.prev }) }),
57
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", size: "sm", disabled: page >= totalPaginas, onClick: () => onPageChange(page + 1), children: [
58
+ /* @__PURE__ */ jsx("span", { className: "max-sm:sr-only", children: L.next }),
59
+ /* @__PURE__ */ jsx(ChevronRight, {})
60
+ ] })
57
61
  ] })
58
62
  ] })
59
- ] });
63
+ );
60
64
  }
61
65
  export {
62
66
  DEFAULT_PAGINATION_LABELS,
@@ -1 +1 @@
1
- {"version":3,"file":"Pagination.js","sources":["../../../../../../src/v1/components/data/Pagination/Pagination.tsx"],"sourcesContent":["import React from 'react'\r\nimport Button from '../../../primitives/buttons/Button/Button'\r\nimport SelectInput from '../../../primitives/forms/SelectInput/SelectInput'\r\n\r\nexport interface PaginationLabels {\r\n /** \"{page}\" e \"{pages}\" são substituídos (ex: \"Página {page} de {pages}\"). */\r\n page: string\r\n /** \"{count}\" é substituído pelo total de registos (ex: \"{count} resultados\"). */\r\n total: string\r\n perPage: string\r\n prev: string\r\n next: string\r\n}\r\n\r\nexport const DEFAULT_PAGINATION_LABELS: PaginationLabels = {\r\n page: 'Page {page} of {pages}',\r\n total: '{count} results',\r\n perPage: 'Per page',\r\n prev: 'Previous',\r\n next: 'Next',\r\n}\r\n\r\nexport interface PaginationProps {\r\n /** Página atual, a contar de 1. */\r\n page: number\r\n /** Número de páginas. */\r\n pages: number\r\n onPageChange: (page: number) => void\r\n /** Total de registos (mostrado à esquerda). */\r\n total?: number\r\n /** Registos por página e as opções do seletor (sem opções, não há seletor). */\r\n pageSize?: number\r\n pageSizeOptions?: number[]\r\n onPageSizeChange?: (size: number) => void\r\n labels?: Partial<PaginationLabels>\r\n className?: string\r\n}\r\n\r\nfunction ChevronLeft() {\r\n return (\r\n <svg aria-hidden viewBox=\"0 0 24 24\" width={13} height={13} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <path d=\"m15 18-6-6 6-6\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction ChevronRight() {\r\n return (\r\n <svg aria-hidden viewBox=\"0 0 24 24\" width={12} height={12} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <path d=\"m9 18 6-6-6-6\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Paginação por baixo de uma lista: \"Página 1 de 4\", o total, quantos por página e\r\n * Anterior/Seguinte. É a mesma que a `DataTable` mostra; usar esta quando a app pagina\r\n * no servidor (a tabela recebe só a página atual).\r\n */\r\nexport default function Pagination({\r\n page,\r\n pages,\r\n onPageChange,\r\n total,\r\n pageSize,\r\n pageSizeOptions,\r\n onPageSizeChange,\r\n labels,\r\n className = '',\r\n}: PaginationProps) {\r\n const L = { ...DEFAULT_PAGINATION_LABELS, ...labels }\r\n const totalPaginas = Math.max(1, pages)\r\n const opcoes = pageSizeOptions && pageSize != null ? [...new Set([...pageSizeOptions, pageSize])].sort((a, b) => a - b) : []\r\n return (\r\n <nav aria-label={L.page.replace('{page}', String(page)).replace('{pages}', String(totalPaginas))} className={`flex flex-wrap items-center justify-between gap-2 ${className}`}>\r\n <span className=\"text-[10px] font-black uppercase tracking-widest text-muted-foreground\">\r\n {total != null && <>{L.total.replace('{count}', total.toLocaleString())} · </>}\r\n {L.page.replace('{page}', String(page)).replace('{pages}', String(totalPaginas))}\r\n </span>\r\n <div className=\"flex flex-wrap items-center gap-1.5\">\r\n {opcoes.length > 0 && onPageSizeChange && (\r\n <label className=\"mr-2 flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest text-muted-foreground\">\r\n {L.perPage}\r\n <SelectInput\r\n aria-label={L.perPage}\r\n value={String(pageSize)}\r\n onChange={(e) => onPageSizeChange(Number(e.target.value))}\r\n className=\"w-20 normal-case tracking-normal font-normal\"\r\n >\r\n {opcoes.map((n) => (\r\n <option key={n} value={n}>\r\n {n}\r\n </option>\r\n ))}\r\n </SelectInput>\r\n </label>\r\n )}\r\n <Button variant=\"secondary\" size=\"sm\" icon={<ChevronLeft />} disabled={page <= 1} onClick={() => onPageChange(page - 1)}>\r\n {L.prev}\r\n </Button>\r\n <Button variant=\"secondary\" size=\"sm\" disabled={page >= totalPaginas} onClick={() => onPageChange(page + 1)}>\r\n {L.next}\r\n <ChevronRight />\r\n </Button>\r\n </div>\r\n </nav>\r\n )\r\n}\r\n\r\nexport { Pagination }\r\n"],"names":[],"mappings":";;;AAcO,MAAM,4BAA8C;AAAA,EACzD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,MAAM;AAAA,EACN,MAAM;AACR;AAkBA,SAAS,cAAc;AACrB,SACE,oBAAC,OAAA,EAAI,eAAW,MAAC,SAAQ,aAAY,OAAO,IAAI,QAAQ,IAAI,MAAK,QAAO,QAAO,gBAAe,aAAa,GAAG,eAAc,SAAQ,gBAAe,SACjJ,UAAA,oBAAC,QAAA,EAAK,GAAE,iBAAA,CAAiB,EAAA,CAC3B;AAEJ;AAEA,SAAS,eAAe;AACtB,SACE,oBAAC,OAAA,EAAI,eAAW,MAAC,SAAQ,aAAY,OAAO,IAAI,QAAQ,IAAI,MAAK,QAAO,QAAO,gBAAe,aAAa,GAAG,eAAc,SAAQ,gBAAe,SACjJ,UAAA,oBAAC,QAAA,EAAK,GAAE,gBAAA,CAAgB,EAAA,CAC1B;AAEJ;AAOA,SAAwB,WAAW;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AACd,GAAoB;AAClB,QAAM,IAAI,EAAE,GAAG,2BAA2B,GAAG,OAAA;AAC7C,QAAM,eAAe,KAAK,IAAI,GAAG,KAAK;AACtC,QAAM,SAAS,mBAAmB,YAAY,OAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,iBAAiB,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,IAAI,CAAA;AAC1H,SACE,qBAAC,SAAI,cAAY,EAAE,KAAK,QAAQ,UAAU,OAAO,IAAI,CAAC,EAAE,QAAQ,WAAW,OAAO,YAAY,CAAC,GAAG,WAAW,qDAAqD,SAAS,IACzK,UAAA;AAAA,IAAA,qBAAC,QAAA,EAAK,WAAU,0EACb,UAAA;AAAA,MAAA,SAAS,QAAQ,qBAAA,UAAA,EAAG,UAAA;AAAA,QAAA,EAAE,MAAM,QAAQ,WAAW,MAAM,gBAAgB;AAAA,QAAE;AAAA,MAAA,GAAG;AAAA,MAC1E,EAAE,KAAK,QAAQ,UAAU,OAAO,IAAI,CAAC,EAAE,QAAQ,WAAW,OAAO,YAAY,CAAC;AAAA,IAAA,GACjF;AAAA,IACA,qBAAC,OAAA,EAAI,WAAU,uCACZ,UAAA;AAAA,MAAA,OAAO,SAAS,KAAK,oBACpB,qBAAC,SAAA,EAAM,WAAU,yGACd,UAAA;AAAA,QAAA,EAAE;AAAA,QACH;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,cAAY,EAAE;AAAA,YACd,OAAO,OAAO,QAAQ;AAAA,YACtB,UAAU,CAAC,MAAM,iBAAiB,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,YACxD,WAAU;AAAA,YAET,UAAA,OAAO,IAAI,CAAC,MACX,oBAAC,YAAe,OAAO,GACpB,UAAA,EAAA,GADU,CAEb,CACD;AAAA,UAAA;AAAA,QAAA;AAAA,MACH,GACF;AAAA,MAEF,oBAAC,UAAO,SAAQ,aAAY,MAAK,MAAK,0BAAO,aAAA,EAAY,GAAI,UAAU,QAAQ,GAAG,SAAS,MAAM,aAAa,OAAO,CAAC,GACnH,YAAE,MACL;AAAA,MACA,qBAAC,QAAA,EAAO,SAAQ,aAAY,MAAK,MAAK,UAAU,QAAQ,cAAc,SAAS,MAAM,aAAa,OAAO,CAAC,GACvG,UAAA;AAAA,QAAA,EAAE;AAAA,4BACF,cAAA,CAAA,CAAa;AAAA,MAAA,EAAA,CAChB;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"Pagination.js","sources":["../../../../../../src/v1/components/data/Pagination/Pagination.tsx"],"sourcesContent":["import React from 'react'\r\nimport Button from '../../../primitives/buttons/Button/Button'\r\nimport SelectInput from '../../../primitives/forms/SelectInput/SelectInput'\r\n\r\nexport interface PaginationLabels {\r\n /** \"{page}\" e \"{pages}\" são substituídos (ex: \"Página {page} de {pages}\"). */\r\n page: string\r\n /** \"{count}\" é substituído pelo total de registos (ex: \"{count} resultados\"). */\r\n total: string\r\n perPage: string\r\n prev: string\r\n next: string\r\n}\r\n\r\nexport const DEFAULT_PAGINATION_LABELS: PaginationLabels = {\r\n page: 'Page {page} of {pages}',\r\n total: '{count} results',\r\n perPage: 'Per page',\r\n prev: 'Previous',\r\n next: 'Next',\r\n}\r\n\r\nexport interface PaginationProps {\r\n /** Página atual, a contar de 1. */\r\n page: number\r\n /** Número de páginas. */\r\n pages: number\r\n onPageChange: (page: number) => void\r\n /** Total de registos (mostrado à esquerda). */\r\n total?: number\r\n /** Registos por página e as opções do seletor (sem opções, não há seletor). */\r\n pageSize?: number\r\n pageSizeOptions?: number[]\r\n onPageSizeChange?: (size: number) => void\r\n labels?: Partial<PaginationLabels>\r\n className?: string\r\n}\r\n\r\nfunction ChevronLeft() {\r\n return (\r\n <svg aria-hidden viewBox=\"0 0 24 24\" width={13} height={13} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <path d=\"m15 18-6-6 6-6\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction ChevronRight() {\r\n return (\r\n <svg aria-hidden viewBox=\"0 0 24 24\" width={12} height={12} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <path d=\"m9 18 6-6-6-6\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Paginação por baixo de uma lista: \"Página 1 de 4\", o total, quantos por página e\r\n * Anterior/Seguinte. É a mesma que a `DataTable` mostra; usar esta quando a app pagina\r\n * no servidor (a tabela recebe só a página atual).\r\n */\r\nexport default function Pagination({\r\n page,\r\n pages,\r\n onPageChange,\r\n total,\r\n pageSize,\r\n pageSizeOptions,\r\n onPageSizeChange,\r\n labels,\r\n className = '',\r\n}: PaginationProps) {\r\n const L = { ...DEFAULT_PAGINATION_LABELS, ...labels }\r\n const totalPaginas = Math.max(1, pages)\r\n const opcoes = pageSizeOptions && pageSize != null ? [...new Set([...pageSizeOptions, pageSize])].sort((a, b) => a - b) : []\r\n return (\r\n // No telemóvel numa só linha: o texto à esquerda e as setas à direita, só com o ícone (com o texto\r\n // \"Anterior\"/\"Seguinte\" partia-se em duas linhas encostadas à esquerda); sem o seletor por página.\r\n <nav aria-label={L.page.replace('{page}', String(page)).replace('{pages}', String(totalPaginas))} className={`flex flex-wrap items-center justify-between gap-2 max-sm:flex-nowrap ${className}`}>\r\n <span className=\"min-w-0 text-[10px] font-black uppercase tracking-widest text-muted-foreground max-sm:tracking-wider\">\r\n {total != null && <>{L.total.replace('{count}', total.toLocaleString())} · </>}\r\n {L.page.replace('{page}', String(page)).replace('{pages}', String(totalPaginas))}\r\n </span>\r\n <div className=\"flex flex-wrap items-center gap-1.5 max-sm:shrink-0 max-sm:flex-nowrap\">\r\n {opcoes.length > 0 && onPageSizeChange && (\r\n <label className=\"mr-2 flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest text-muted-foreground max-sm:hidden\">\r\n {L.perPage}\r\n <SelectInput\r\n aria-label={L.perPage}\r\n value={String(pageSize)}\r\n onChange={(e) => onPageSizeChange(Number(e.target.value))}\r\n className=\"w-20 normal-case tracking-normal font-normal\"\r\n >\r\n {opcoes.map((n) => (\r\n <option key={n} value={n}>\r\n {n}\r\n </option>\r\n ))}\r\n </SelectInput>\r\n </label>\r\n )}\r\n <Button variant=\"secondary\" size=\"sm\" icon={<ChevronLeft />} disabled={page <= 1} onClick={() => onPageChange(page - 1)}>\r\n <span className=\"max-sm:sr-only\">{L.prev}</span>\r\n </Button>\r\n <Button variant=\"secondary\" size=\"sm\" disabled={page >= totalPaginas} onClick={() => onPageChange(page + 1)}>\r\n <span className=\"max-sm:sr-only\">{L.next}</span>\r\n <ChevronRight />\r\n </Button>\r\n </div>\r\n </nav>\r\n )\r\n}\r\n\r\nexport { Pagination }\r\n"],"names":[],"mappings":"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"}
@@ -9,11 +9,12 @@ function FormField({ label, required, hint, marks, markSlots, aside, end, wide,
9
9
  const { columns } = useFormGrid();
10
10
  const marksCount = React.Children.count(marks);
11
11
  const slots = Math.max(markSlots ?? 0, marksCount);
12
+ const wideRow = wide && columns === 2;
12
13
  return /* @__PURE__ */ jsxs(FieldContext.Provider, { value: { id, required }, children: [
13
14
  /* @__PURE__ */ jsxs(
14
15
  "div",
15
16
  {
16
- className: `flex flex-col items-end pr-3 text-right @max-form-sm:items-start @max-form-sm:pr-0 @max-form-sm:pt-0 @max-form-sm:text-left ${alignTop ? "self-start pt-[7px]" : ""}`,
17
+ className: `flex flex-col items-end pr-3 text-right @max-form-sm:items-start @max-form-sm:pr-0 @max-form-sm:pt-0 @max-form-sm:text-left ${wideRow ? "col-start-1" : ""} ${alignTop ? "self-start pt-[7px]" : ""}`,
17
18
  children: [
18
19
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end @max-form-sm:justify-start", children: [
19
20
  slots > 0 && /* @__PURE__ */ jsx("span", { className: "mr-[5px] inline-flex flex-none items-center justify-end gap-1", style: { width: slots * MARK + (slots - 1) * MARK_GAP }, children: marks }),
@@ -34,7 +35,7 @@ function FormField({ label, required, hint, marks, markSlots, aside, end, wide,
34
35
  ]
35
36
  }
36
37
  ),
37
- /* @__PURE__ */ jsxs("div", { className: `min-w-0 ${wide && columns === 2 ? "col-span-3 @max-form-sm:col-span-1" : ""} ${end ? "flex items-center gap-1.5" : ""}`, children: [
38
+ /* @__PURE__ */ jsxs("div", { className: `min-w-0 ${wideRow ? "col-span-3 @max-form-sm:col-span-1" : ""} ${end ? "flex items-center gap-1.5" : ""}`, children: [
38
39
  children,
39
40
  end
40
41
  ] })
@@ -1 +1 @@
1
- {"version":3,"file":"FormField.js","sources":["../../../../../../src/v1/components/forms/FormField/FormField.tsx"],"sourcesContent":["import React, { useId } from 'react'\r\nimport { FieldContext } from '../../../primitives/forms/field'\r\nimport { useFormGrid } from '../FormGrid/FormGrid'\r\n\r\nexport interface FormFieldProps {\r\n label: string\r\n /** Asterisco vermelho na etiqueta; o campo lá dentro fica `required`. */\r\n required?: boolean\r\n /** Texto pequeno por baixo da etiqueta. */\r\n hint?: string\r\n /**\r\n * Marcas antes da etiqueta, a dizer de onde veio o valor (ex: lido do QR,\r\n * identificado pela IA). Usar [FieldMark](#fieldmark).\r\n */\r\n marks?: React.ReactNode\r\n /**\r\n * Lugares reservados para marcas, mesmo sem nenhuma. Assim a etiqueta não\r\n * salta quando uma marca aparece depois (ex: ao ler o QR).\r\n */\r\n markSlots?: number\r\n /** Algo junto à etiqueta, depois das marcas (ex: o valor máximo permitido). */\r\n aside?: React.ReactNode\r\n /** Depois do campo, na mesma linha (ex: cadeado, ícone de informação). */\r\n end?: React.ReactNode\r\n /** Ocupa a linha toda (ex: observações, anexo). */\r\n wide?: boolean\r\n /** Etiqueta alinhada ao topo em vez de ao meio (campos altos, como o [TextArea](?path=/docs/v1-primitives-forms-textarea--docs)). */\r\n alignTop?: boolean\r\n /** O campo: [TextInput](?path=/docs/v1-primitives-forms-textinput--docs), [SelectInput](?path=/docs/v1-primitives-forms-selectinput--docs), [TextArea](?path=/docs/v1-primitives-forms-textarea--docs), [LookupInput](?path=/docs/v1-primitives-forms-lookupinput--docs) ou outro. */\r\n children: React.ReactNode\r\n}\r\n\r\nconst MARK = 17\r\nconst MARK_GAP = 4\r\n\r\n/**\r\n * Uma linha do formulário: etiqueta e campo. Vai dentro de um\r\n * [FormGrid](?path=/docs/v1-components-forms-formgrid--docs).\r\n *\r\n * Liga a etiqueta ao campo (clicar na etiqueta foca o campo) e passa-lhe o\r\n * `required`, se o campo for [TextInput](?path=/docs/v1-primitives-forms-textinput--docs), [SelectInput](?path=/docs/v1-primitives-forms-selectinput--docs), [TextArea](?path=/docs/v1-primitives-forms-textarea--docs) ou [LookupInput](?path=/docs/v1-primitives-forms-lookupinput--docs).\r\n */\r\nexport default function FormField({ label, required, hint, marks, markSlots, aside, end, wide, alignTop, children }: FormFieldProps) {\r\n const id = useId()\r\n const { columns } = useFormGrid()\r\n const marksCount = React.Children.count(marks)\r\n const slots = Math.max(markSlots ?? 0, marksCount)\r\n\r\n return (\r\n <FieldContext.Provider value={{ id, required }}>\r\n <div\r\n className={`flex flex-col items-end pr-3 text-right @max-form-sm:items-start @max-form-sm:pr-0 @max-form-sm:pt-0 @max-form-sm:text-left ${alignTop ? 'self-start pt-[7px]' : ''}`}\r\n >\r\n {/* As marcas ficam fora do <label>, para não entrarem no nome do campo lido pelos leitores de ecrã. */}\r\n <div className=\"flex items-center justify-end @max-form-sm:justify-start\">\r\n {slots > 0 && (\r\n <span className=\"mr-[5px] inline-flex flex-none items-center justify-end gap-1\" style={{ width: slots * MARK + (slots - 1) * MARK_GAP }}>\r\n {marks}\r\n </span>\r\n )}\r\n {aside && <span className=\"mr-1.5 flex-none\">{aside}</span>}\r\n <label\r\n htmlFor={id}\r\n className=\"m-0 text-xs leading-[18px] font-medium whitespace-nowrap text-muted-foreground @max-form-md:whitespace-normal\"\r\n >\r\n {label}\r\n {required && (\r\n <span aria-hidden className=\"ml-0.5 text-destructive\">\r\n *\r\n </span>\r\n )}\r\n </label>\r\n </div>\r\n {hint && <span className=\"text-micro text-muted-foreground\">{hint}</span>}\r\n </div>\r\n <div className={`min-w-0 ${wide && columns === 2 ? 'col-span-3 @max-form-sm:col-span-1' : ''} ${end ? 'flex items-center gap-1.5' : ''}`}>\r\n {children}\r\n {end}\r\n </div>\r\n </FieldContext.Provider>\r\n )\r\n}\r\n\r\nexport interface FieldMarkProps {\r\n /** De onde veio o valor (ex: \"Lido do QR Code\"). Mostrado ao passar o rato e lido pelos leitores de ecrã. */\r\n title: string\r\n /** O ícone, a 11 px (ex: `<QrCodeIcon size={11} />`). */\r\n icon: React.ReactNode\r\n}\r\n\r\n/** Marca pequena antes da etiqueta de um FormField, a dizer de onde veio o valor. */\r\nexport function FieldMark({ title, icon }: FieldMarkProps) {\r\n return (\r\n <span\r\n role=\"img\"\r\n aria-label={title}\r\n title={title}\r\n className=\"inline-flex h-[17px] w-[17px] flex-none cursor-help items-center justify-center rounded-[5px] border border-primary/20 bg-primary/5 text-primary\"\r\n >\r\n {icon}\r\n </span>\r\n )\r\n}\r\n"],"names":[],"mappings":";;;;AAgCA,MAAM,OAAO;AACb,MAAM,WAAW;AASjB,SAAwB,UAAU,EAAE,OAAO,UAAU,MAAM,OAAO,WAAW,OAAO,KAAK,MAAM,UAAU,SAAA,GAA4B;AACnI,QAAM,KAAK,MAAA;AACX,QAAM,EAAE,QAAA,IAAY,YAAA;AACpB,QAAM,aAAa,MAAM,SAAS,MAAM,KAAK;AAC7C,QAAM,QAAQ,KAAK,IAAI,aAAa,GAAG,UAAU;AAEjD,SACE,qBAAC,aAAa,UAAb,EAAsB,OAAO,EAAE,IAAI,YAClC,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,+HAA+H,WAAW,wBAAwB,EAAE;AAAA,QAG/K,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,4DACZ,UAAA;AAAA,YAAA,QAAQ,KACP,oBAAC,QAAA,EAAK,WAAU,iEAAgE,OAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ,KAAK,YAC1H,UAAA,OACH;AAAA,YAED,SAAS,oBAAC,QAAA,EAAK,WAAU,oBAAoB,UAAA,OAAM;AAAA,YACpD;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAS;AAAA,gBACT,WAAU;AAAA,gBAET,UAAA;AAAA,kBAAA;AAAA,kBACA,YACC,oBAAC,QAAA,EAAK,eAAW,MAAC,WAAU,2BAA0B,UAAA,IAAA,CAEtD;AAAA,gBAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UAEJ,GACF;AAAA,UACC,QAAQ,oBAAC,QAAA,EAAK,WAAU,oCAAoC,UAAA,KAAA,CAAK;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEpE,qBAAC,OAAA,EAAI,WAAW,WAAW,QAAQ,YAAY,IAAI,uCAAuC,EAAE,IAAI,MAAM,8BAA8B,EAAE,IACnI,UAAA;AAAA,MAAA;AAAA,MACA;AAAA,IAAA,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;AAUO,SAAS,UAAU,EAAE,OAAO,QAAwB;AACzD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ;AAAA,MACA,WAAU;AAAA,MAET,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;"}
1
+ {"version":3,"file":"FormField.js","sources":["../../../../../../src/v1/components/forms/FormField/FormField.tsx"],"sourcesContent":["import React, { useId } from 'react'\r\nimport { FieldContext } from '../../../primitives/forms/field'\r\nimport { useFormGrid } from '../FormGrid/FormGrid'\r\n\r\nexport interface FormFieldProps {\r\n label: string\r\n /** Asterisco vermelho na etiqueta; o campo lá dentro fica `required`. */\r\n required?: boolean\r\n /** Texto pequeno por baixo da etiqueta. */\r\n hint?: string\r\n /**\r\n * Marcas antes da etiqueta, a dizer de onde veio o valor (ex: lido do QR,\r\n * identificado pela IA). Usar [FieldMark](#fieldmark).\r\n */\r\n marks?: React.ReactNode\r\n /**\r\n * Lugares reservados para marcas, mesmo sem nenhuma. Assim a etiqueta não\r\n * salta quando uma marca aparece depois (ex: ao ler o QR).\r\n */\r\n markSlots?: number\r\n /** Algo junto à etiqueta, depois das marcas (ex: o valor máximo permitido). */\r\n aside?: React.ReactNode\r\n /** Depois do campo, na mesma linha (ex: cadeado, ícone de informação). */\r\n end?: React.ReactNode\r\n /** Ocupa a linha toda (ex: observações, anexo). */\r\n wide?: boolean\r\n /** Etiqueta alinhada ao topo em vez de ao meio (campos altos, como o [TextArea](?path=/docs/v1-primitives-forms-textarea--docs)). */\r\n alignTop?: boolean\r\n /** O campo: [TextInput](?path=/docs/v1-primitives-forms-textinput--docs), [SelectInput](?path=/docs/v1-primitives-forms-selectinput--docs), [TextArea](?path=/docs/v1-primitives-forms-textarea--docs), [LookupInput](?path=/docs/v1-primitives-forms-lookupinput--docs) ou outro. */\r\n children: React.ReactNode\r\n}\r\n\r\nconst MARK = 17\r\nconst MARK_GAP = 4\r\n\r\n/**\r\n * Uma linha do formulário: etiqueta e campo. Vai dentro de um\r\n * [FormGrid](?path=/docs/v1-components-forms-formgrid--docs).\r\n *\r\n * Liga a etiqueta ao campo (clicar na etiqueta foca o campo) e passa-lhe o\r\n * `required`, se o campo for [TextInput](?path=/docs/v1-primitives-forms-textinput--docs), [SelectInput](?path=/docs/v1-primitives-forms-selectinput--docs), [TextArea](?path=/docs/v1-primitives-forms-textarea--docs) ou [LookupInput](?path=/docs/v1-primitives-forms-lookupinput--docs).\r\n */\r\nexport default function FormField({ label, required, hint, marks, markSlots, aside, end, wide, alignTop, children }: FormFieldProps) {\r\n const id = useId()\r\n const { columns } = useFormGrid()\r\n const marksCount = React.Children.count(marks)\r\n const slots = Math.max(markSlots ?? 0, marksCount)\r\n // Em 2 colunas, o campo largo começa sempre numa linha nova: depois de um campo\r\n // de meia linha, a etiqueta caía na 3.ª coluna e o campo descia para a linha seguinte.\r\n const wideRow = wide && columns === 2\r\n\r\n return (\r\n <FieldContext.Provider value={{ id, required }}>\r\n <div\r\n className={`flex flex-col items-end pr-3 text-right @max-form-sm:items-start @max-form-sm:pr-0 @max-form-sm:pt-0 @max-form-sm:text-left ${wideRow ? 'col-start-1' : ''} ${alignTop ? 'self-start pt-[7px]' : ''}`}\r\n >\r\n {/* As marcas ficam fora do <label>, para não entrarem no nome do campo lido pelos leitores de ecrã. */}\r\n <div className=\"flex items-center justify-end @max-form-sm:justify-start\">\r\n {slots > 0 && (\r\n <span className=\"mr-[5px] inline-flex flex-none items-center justify-end gap-1\" style={{ width: slots * MARK + (slots - 1) * MARK_GAP }}>\r\n {marks}\r\n </span>\r\n )}\r\n {aside && <span className=\"mr-1.5 flex-none\">{aside}</span>}\r\n <label\r\n htmlFor={id}\r\n className=\"m-0 text-xs leading-[18px] font-medium whitespace-nowrap text-muted-foreground @max-form-md:whitespace-normal\"\r\n >\r\n {label}\r\n {required && (\r\n <span aria-hidden className=\"ml-0.5 text-destructive\">\r\n *\r\n </span>\r\n )}\r\n </label>\r\n </div>\r\n {hint && <span className=\"text-micro text-muted-foreground\">{hint}</span>}\r\n </div>\r\n <div className={`min-w-0 ${wideRow ? 'col-span-3 @max-form-sm:col-span-1' : ''} ${end ? 'flex items-center gap-1.5' : ''}`}>\r\n {children}\r\n {end}\r\n </div>\r\n </FieldContext.Provider>\r\n )\r\n}\r\n\r\nexport interface FieldMarkProps {\r\n /** De onde veio o valor (ex: \"Lido do QR Code\"). Mostrado ao passar o rato e lido pelos leitores de ecrã. */\r\n title: string\r\n /** O ícone, a 11 px (ex: `<QrCodeIcon size={11} />`). */\r\n icon: React.ReactNode\r\n}\r\n\r\n/** Marca pequena antes da etiqueta de um FormField, a dizer de onde veio o valor. */\r\nexport function FieldMark({ title, icon }: FieldMarkProps) {\r\n return (\r\n <span\r\n role=\"img\"\r\n aria-label={title}\r\n title={title}\r\n className=\"inline-flex h-[17px] w-[17px] flex-none cursor-help items-center justify-center rounded-[5px] border border-primary/20 bg-primary/5 text-primary\"\r\n >\r\n {icon}\r\n </span>\r\n )\r\n}\r\n"],"names":[],"mappings":";;;;AAgCA,MAAM,OAAO;AACb,MAAM,WAAW;AASjB,SAAwB,UAAU,EAAE,OAAO,UAAU,MAAM,OAAO,WAAW,OAAO,KAAK,MAAM,UAAU,SAAA,GAA4B;AACnI,QAAM,KAAK,MAAA;AACX,QAAM,EAAE,QAAA,IAAY,YAAA;AACpB,QAAM,aAAa,MAAM,SAAS,MAAM,KAAK;AAC7C,QAAM,QAAQ,KAAK,IAAI,aAAa,GAAG,UAAU;AAGjD,QAAM,UAAU,QAAQ,YAAY;AAEpC,SACE,qBAAC,aAAa,UAAb,EAAsB,OAAO,EAAE,IAAI,YAClC,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,+HAA+H,UAAU,gBAAgB,EAAE,IAAI,WAAW,wBAAwB,EAAE;AAAA,QAG/M,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,4DACZ,UAAA;AAAA,YAAA,QAAQ,KACP,oBAAC,QAAA,EAAK,WAAU,iEAAgE,OAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ,KAAK,YAC1H,UAAA,OACH;AAAA,YAED,SAAS,oBAAC,QAAA,EAAK,WAAU,oBAAoB,UAAA,OAAM;AAAA,YACpD;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAS;AAAA,gBACT,WAAU;AAAA,gBAET,UAAA;AAAA,kBAAA;AAAA,kBACA,YACC,oBAAC,QAAA,EAAK,eAAW,MAAC,WAAU,2BAA0B,UAAA,IAAA,CAEtD;AAAA,gBAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UAEJ,GACF;AAAA,UACC,QAAQ,oBAAC,QAAA,EAAK,WAAU,oCAAoC,UAAA,KAAA,CAAK;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEpE,qBAAC,OAAA,EAAI,WAAW,WAAW,UAAU,uCAAuC,EAAE,IAAI,MAAM,8BAA8B,EAAE,IACrH,UAAA;AAAA,MAAA;AAAA,MACA;AAAA,IAAA,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;AAUO,SAAS,UAAU,EAAE,OAAO,QAAwB;AACzD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ;AAAA,MACA,WAAU;AAAA,MAET,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;"}
@@ -65,8 +65,8 @@ function AppShell({
65
65
  type: "button",
66
66
  onClick: () => setOpen(true),
67
67
  "aria-label": labels?.openMenu ?? "Abrir menu",
68
- style: { top: "calc(var(--app-shell-top) + 1.25rem)" },
69
- className: "fixed left-5 z-40 flex cursor-pointer items-center justify-center rounded-xl border border-border bg-card p-3 text-muted-foreground shadow-page transition-colors hover:text-proef-blue lg:hidden",
68
+ style: { top: "calc(var(--app-shell-top) + 0.75rem)" },
69
+ className: "absolute left-5 z-40 flex cursor-pointer items-center justify-center rounded-xl border border-border bg-card p-3 text-muted-foreground shadow-page transition-colors hover:text-proef-blue lg:hidden",
70
70
  children: /* @__PURE__ */ jsx(MenuIcon, { size: 20 })
71
71
  }
72
72
  ),
@@ -74,7 +74,12 @@ function AppShell({
74
74
  /* @__PURE__ */ jsx(
75
75
  "main",
76
76
  {
77
- className: `min-w-0 px-page-x-narrow transition-[margin] duration-300 sm:px-page-x ${margin} ${topBar ? "pt-6" : "pt-page-top-narrow lg:pt-page-top"} ` + (fullHeight ? "flex min-h-0 flex-1 flex-col overflow-hidden pb-4" : "pb-16"),
77
+ className: (
78
+ // overflow-x-clip: uma peça mais larga do que o ecrã (um botão que não parte, um código comprido) fazia o
79
+ // telemóvel alargar a página toda e cortar tudo à direita. Assim só essa peça fica cortada. Clip, e não
80
+ // hidden, para não criar uma caixa de scroll (o sticky dentro da página continua a funcionar).
81
+ `min-w-0 overflow-x-clip px-page-x-narrow transition-[margin] duration-300 sm:px-page-x ${margin} ${topBar ? "pt-6" : "pt-page-top-narrow lg:pt-page-top max-lg:[--app-shell-title-lift:calc(var(--spacing-page-top-narrow)-19px)] max-lg:[--app-shell-menu-inset:calc(78px-var(--spacing-page-x-narrow))] sm:max-lg:[--app-shell-menu-inset:calc(78px-var(--spacing-page-x))]"} ` + (fullHeight ? "flex min-h-0 flex-1 flex-col overflow-hidden pb-4" : "pb-16")
82
+ ),
78
83
  children: /* @__PURE__ */ jsx(NavTrailContext.Provider, { value: trail, children })
79
84
  }
80
85
  )
@@ -1 +1 @@
1
- {"version":3,"file":"AppShell.js","sources":["../../../../../../src/v1/components/layout/AppShell/AppShell.tsx"],"sourcesContent":["import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'\r\nimport { useLocation } from 'react-router-dom'\r\nimport { MenuIcon } from '../../../foundations/icons/icons'\r\nimport SideNav, { isDrawer, type SideNavLabels, type SideNavProps } from '../SideNav/SideNav'\r\nimport { NavTrailContext, findNavTrail } from '../navTrail'\r\n\r\nexport interface AppShellLabels extends SideNavLabels {\r\n openMenu: string\r\n}\r\n\r\nexport interface AppShellProps extends Omit<SideNavProps, 'open' | 'onOpenChange' | 'footer' | 'labels'> {\r\n children: React.ReactNode\r\n /** Conteúdo extra no rodapé do menu, por cima do tema (ex: um filtro global). */\r\n sidebarFooter?: (expanded: boolean) => React.ReactNode\r\n /** Barras no topo, com a largura toda (ex: \"A ver como…\", \"A sincronizar…\"). Empurram o menu e a página para baixo. */\r\n banners?: React.ReactNode\r\n /** Barra fixa por cima da página, à direita do menu (ex: título e alertas). Traz o seu próprio fundo. */\r\n topBar?: React.ReactNode\r\n /**\r\n * A página ocupa a altura que sobra do ecrã, sem scroll da página (ex:\r\n * quadros e listas que fazem scroll por dentro, com o `Page` em `fill`).\r\n * As margens são as mesmas das outras páginas.\r\n */\r\n fullHeight?: boolean\r\n /** Menu aberto ao entrar (ecrãs grandes). Por omissão sim; fechado fica só com os ícones. */\r\n defaultOpen?: boolean\r\n /** A pessoa abriu ou fechou o menu (para a app guardar a escolha, se quiser). */\r\n onOpenChange?: (open: boolean) => void\r\n labels?: Partial<AppShellLabels>\r\n}\r\n\r\n/**\r\n * A moldura da app: o menu lateral (`SideNav`) à esquerda e a página à\r\n * direita, com barras opcionais no topo.\r\n *\r\n * Em ecrãs pequenos (< 1024px) o menu é uma gaveta, aberta pelo botão no canto,\r\n * que fecha ao mudar de página.\r\n *\r\n * Precisa de estar dentro de um Router do react-router.\r\n */\r\nexport default function AppShell({\r\n children,\r\n sidebarFooter,\r\n banners,\r\n topBar,\r\n fullHeight = false,\r\n defaultOpen = true,\r\n onOpenChange,\r\n labels,\r\n ...sideNav\r\n}: AppShellProps) {\r\n const location = useLocation()\r\n // O lugar da página no menu, para o `Page` mostrar o caminho \"grupo › página\".\r\n const { menu, groupLabels } = sideNav\r\n const trail = useMemo(() => findNavTrail(menu, groupLabels ?? {}, location.pathname), [menu, groupLabels, location.pathname])\r\n const [open, setOpenState] = useState(() => !isDrawer() && defaultOpen)\r\n\r\n // Escolha da pessoa: avisa a app. O ajuste ao redimensionar não é escolha dela.\r\n const setOpen = (value: boolean) => {\r\n setOpenState(value)\r\n onOpenChange?.(value)\r\n }\r\n\r\n useEffect(() => {\r\n const onResize = () => {\r\n if (isDrawer()) setOpenState(false)\r\n }\r\n window.addEventListener('resize', onResize)\r\n return () => window.removeEventListener('resize', onResize)\r\n }, [])\r\n\r\n // A gaveta fecha ao mudar de página (incluindo links fora do menu).\r\n useEffect(() => {\r\n if (isDrawer()) setOpenState(false)\r\n }, [location.pathname])\r\n\r\n // Altura das barras do topo: o menu e a página começam por baixo delas.\r\n const bannersRef = useRef<HTMLDivElement>(null)\r\n const [top, setTop] = useState(0)\r\n useLayoutEffect(() => {\r\n const el = bannersRef.current\r\n if (!el) {\r\n setTop(0)\r\n return\r\n }\r\n const measure = () => setTop(el.offsetHeight)\r\n measure()\r\n if (typeof ResizeObserver === 'undefined') return\r\n const ro = new ResizeObserver(measure)\r\n ro.observe(el)\r\n return () => ro.disconnect()\r\n }, [banners])\r\n\r\n const margin = open ? 'lg:ml-64' : 'lg:ml-20'\r\n\r\n return (\r\n <div\r\n style={{ '--app-shell-top': `${top}px` } as React.CSSProperties}\r\n className={`flex bg-background text-foreground ${fullHeight ? 'h-dvh overflow-hidden' : 'min-h-dvh'}`}\r\n >\r\n {banners && (\r\n <div ref={bannersRef} className=\"fixed inset-x-0 top-0 z-50\">\r\n {banners}\r\n </div>\r\n )}\r\n\r\n <SideNav {...sideNav} footer={sidebarFooter} labels={labels} open={open} onOpenChange={setOpen} />\r\n\r\n {open && <div onClick={() => setOpen(false)} aria-hidden className=\"fixed inset-0 z-30 bg-slate-900/50 backdrop-blur-sm lg:hidden\" />}\r\n\r\n <div className=\"flex min-w-0 flex-1 flex-col\" style={{ paddingTop: 'var(--app-shell-top)' }}>\r\n {!open && (\r\n <button\r\n type=\"button\"\r\n onClick={() => setOpen(true)}\r\n aria-label={labels?.openMenu ?? 'Abrir menu'}\r\n style={{ top: 'calc(var(--app-shell-top) + 1.25rem)' }}\r\n className=\"fixed left-5 z-40 flex cursor-pointer items-center justify-center rounded-xl border border-border bg-card p-3 text-muted-foreground shadow-page transition-colors hover:text-proef-blue lg:hidden\"\r\n >\r\n <MenuIcon size={20} />\r\n </button>\r\n )}\r\n {topBar && (\r\n <div style={{ top: 'var(--app-shell-top)' }} className={`sticky z-30 transition-[margin] duration-300 ${margin}`}>\r\n {topBar}\r\n </div>\r\n )}\r\n {/* As margens da página vêm daqui, iguais em todas as páginas (também nas\r\n de altura total) e com o menu aberto ou fechado: as páginas não põem as\r\n suas. Os valores são tokens (--spacing-page-x, --spacing-page-top). Em\r\n ecrãs pequenos o espaço de cima deixa lugar ao botão do menu. */}\r\n <main\r\n className={\r\n `min-w-0 px-page-x-narrow transition-[margin] duration-300 sm:px-page-x ${margin} ${topBar ? 'pt-6' : 'pt-page-top-narrow lg:pt-page-top'} ` +\r\n (fullHeight ? 'flex min-h-0 flex-1 flex-col overflow-hidden pb-4' : 'pb-16')\r\n }\r\n >\r\n <NavTrailContext.Provider value={trail}>{children}</NavTrailContext.Provider>\r\n </main>\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":";;;;;;AAwCA,SAAwB,SAAS;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,WAAW,YAAA;AAEjB,QAAM,EAAE,MAAM,YAAA,IAAgB;AAC9B,QAAM,QAAQ,QAAQ,MAAM,aAAa,MAAM,eAAe,CAAA,GAAI,SAAS,QAAQ,GAAG,CAAC,MAAM,aAAa,SAAS,QAAQ,CAAC;AAC5H,QAAM,CAAC,MAAM,YAAY,IAAI,SAAS,MAAM,CAAC,SAAA,KAAc,WAAW;AAGtE,QAAM,UAAU,CAAC,UAAmB;AAClC,iBAAa,KAAK;AAClB,mBAAe,KAAK;AAAA,EACtB;AAEA,YAAU,MAAM;AACd,UAAM,WAAW,MAAM;AACrB,UAAI,SAAA,EAAY,cAAa,KAAK;AAAA,IACpC;AACA,WAAO,iBAAiB,UAAU,QAAQ;AAC1C,WAAO,MAAM,OAAO,oBAAoB,UAAU,QAAQ;AAAA,EAC5D,GAAG,CAAA,CAAE;AAGL,YAAU,MAAM;AACd,QAAI,SAAA,EAAY,cAAa,KAAK;AAAA,EACpC,GAAG,CAAC,SAAS,QAAQ,CAAC;AAGtB,QAAM,aAAa,OAAuB,IAAI;AAC9C,QAAM,CAAC,KAAK,MAAM,IAAI,SAAS,CAAC;AAChC,kBAAgB,MAAM;AACpB,UAAM,KAAK,WAAW;AACtB,QAAI,CAAC,IAAI;AACP,aAAO,CAAC;AACR;AAAA,IACF;AACA,UAAM,UAAU,MAAM,OAAO,GAAG,YAAY;AAC5C,YAAA;AACA,QAAI,OAAO,mBAAmB,YAAa;AAC3C,UAAM,KAAK,IAAI,eAAe,OAAO;AACrC,OAAG,QAAQ,EAAE;AACb,WAAO,MAAM,GAAG,WAAA;AAAA,EAClB,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,SAAS,OAAO,aAAa;AAEnC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,OAAO,EAAE,mBAAmB,GAAG,GAAG,KAAA;AAAA,MAClC,WAAW,sCAAsC,aAAa,0BAA0B,WAAW;AAAA,MAElG,UAAA;AAAA,QAAA,+BACE,OAAA,EAAI,KAAK,YAAY,WAAU,8BAC7B,UAAA,SACH;AAAA,QAGF,oBAAC,WAAS,GAAG,SAAS,QAAQ,eAAe,QAAgB,MAAY,cAAc,SAAS;AAAA,QAE/F,QAAQ,oBAAC,OAAA,EAAI,SAAS,MAAM,QAAQ,KAAK,GAAG,eAAW,MAAC,WAAU,gEAAA,CAAgE;AAAA,QAEnI,qBAAC,SAAI,WAAU,gCAA+B,OAAO,EAAE,YAAY,0BAChE,UAAA;AAAA,UAAA,CAAC,QACA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,QAAQ,IAAI;AAAA,cAC3B,cAAY,QAAQ,YAAY;AAAA,cAChC,OAAO,EAAE,KAAK,uCAAA;AAAA,cACd,WAAU;AAAA,cAEV,UAAA,oBAAC,UAAA,EAAS,MAAM,GAAA,CAAI;AAAA,YAAA;AAAA,UAAA;AAAA,UAGvB,UACC,oBAAC,OAAA,EAAI,OAAO,EAAE,KAAK,uBAAA,GAA0B,WAAW,gDAAgD,MAAM,IAC3G,UAAA,OAAA,CACH;AAAA,UAMF;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WACE,0EAA0E,MAAM,IAAI,SAAS,SAAS,mCAAmC,OACxI,aAAa,sDAAsD;AAAA,cAGtE,8BAAC,gBAAgB,UAAhB,EAAyB,OAAO,OAAQ,SAAA,CAAS;AAAA,YAAA;AAAA,UAAA;AAAA,QACpD,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"AppShell.js","sources":["../../../../../../src/v1/components/layout/AppShell/AppShell.tsx"],"sourcesContent":["import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'\r\nimport { useLocation } from 'react-router-dom'\r\nimport { MenuIcon } from '../../../foundations/icons/icons'\r\nimport SideNav, { isDrawer, type SideNavLabels, type SideNavProps } from '../SideNav/SideNav'\r\nimport { NavTrailContext, findNavTrail } from '../navTrail'\r\n\r\nexport interface AppShellLabels extends SideNavLabels {\r\n openMenu: string\r\n}\r\n\r\nexport interface AppShellProps extends Omit<SideNavProps, 'open' | 'onOpenChange' | 'footer' | 'labels'> {\r\n children: React.ReactNode\r\n /** Conteúdo extra no rodapé do menu, por cima do tema (ex: um filtro global). */\r\n sidebarFooter?: (expanded: boolean) => React.ReactNode\r\n /** Barras no topo, com a largura toda (ex: \"A ver como…\", \"A sincronizar…\"). Empurram o menu e a página para baixo. */\r\n banners?: React.ReactNode\r\n /** Barra fixa por cima da página, à direita do menu (ex: título e alertas). Traz o seu próprio fundo. */\r\n topBar?: React.ReactNode\r\n /**\r\n * A página ocupa a altura que sobra do ecrã, sem scroll da página (ex:\r\n * quadros e listas que fazem scroll por dentro, com o `Page` em `fill`).\r\n * As margens são as mesmas das outras páginas.\r\n */\r\n fullHeight?: boolean\r\n /** Menu aberto ao entrar (ecrãs grandes). Por omissão sim; fechado fica só com os ícones. */\r\n defaultOpen?: boolean\r\n /** A pessoa abriu ou fechou o menu (para a app guardar a escolha, se quiser). */\r\n onOpenChange?: (open: boolean) => void\r\n labels?: Partial<AppShellLabels>\r\n}\r\n\r\n/**\r\n * A moldura da app: o menu lateral (`SideNav`) à esquerda e a página à\r\n * direita, com barras opcionais no topo.\r\n *\r\n * Em ecrãs pequenos (< 1024px) o menu é uma gaveta, aberta pelo botão no canto,\r\n * que fecha ao mudar de página.\r\n *\r\n * Precisa de estar dentro de um Router do react-router.\r\n */\r\nexport default function AppShell({\r\n children,\r\n sidebarFooter,\r\n banners,\r\n topBar,\r\n fullHeight = false,\r\n defaultOpen = true,\r\n onOpenChange,\r\n labels,\r\n ...sideNav\r\n}: AppShellProps) {\r\n const location = useLocation()\r\n // O lugar da página no menu, para o `Page` mostrar o caminho \"grupo › página\".\r\n const { menu, groupLabels } = sideNav\r\n const trail = useMemo(() => findNavTrail(menu, groupLabels ?? {}, location.pathname), [menu, groupLabels, location.pathname])\r\n const [open, setOpenState] = useState(() => !isDrawer() && defaultOpen)\r\n\r\n // Escolha da pessoa: avisa a app. O ajuste ao redimensionar não é escolha dela.\r\n const setOpen = (value: boolean) => {\r\n setOpenState(value)\r\n onOpenChange?.(value)\r\n }\r\n\r\n useEffect(() => {\r\n const onResize = () => {\r\n if (isDrawer()) setOpenState(false)\r\n }\r\n window.addEventListener('resize', onResize)\r\n return () => window.removeEventListener('resize', onResize)\r\n }, [])\r\n\r\n // A gaveta fecha ao mudar de página (incluindo links fora do menu).\r\n useEffect(() => {\r\n if (isDrawer()) setOpenState(false)\r\n }, [location.pathname])\r\n\r\n // Altura das barras do topo: o menu e a página começam por baixo delas.\r\n const bannersRef = useRef<HTMLDivElement>(null)\r\n const [top, setTop] = useState(0)\r\n useLayoutEffect(() => {\r\n const el = bannersRef.current\r\n if (!el) {\r\n setTop(0)\r\n return\r\n }\r\n const measure = () => setTop(el.offsetHeight)\r\n measure()\r\n if (typeof ResizeObserver === 'undefined') return\r\n const ro = new ResizeObserver(measure)\r\n ro.observe(el)\r\n return () => ro.disconnect()\r\n }, [banners])\r\n\r\n const margin = open ? 'lg:ml-64' : 'lg:ml-20'\r\n\r\n return (\r\n <div\r\n style={{ '--app-shell-top': `${top}px` } as React.CSSProperties}\r\n className={`flex bg-background text-foreground ${fullHeight ? 'h-dvh overflow-hidden' : 'min-h-dvh'}`}\r\n >\r\n {banners && (\r\n <div ref={bannersRef} className=\"fixed inset-x-0 top-0 z-50\">\r\n {banners}\r\n </div>\r\n )}\r\n\r\n <SideNav {...sideNav} footer={sidebarFooter} labels={labels} open={open} onOpenChange={setOpen} />\r\n\r\n {open && <div onClick={() => setOpen(false)} aria-hidden className=\"fixed inset-0 z-30 bg-slate-900/50 backdrop-blur-sm lg:hidden\" />}\r\n\r\n <div className=\"flex min-w-0 flex-1 flex-col\" style={{ paddingTop: 'var(--app-shell-top)' }}>\r\n {!open && (\r\n <button\r\n type=\"button\"\r\n onClick={() => setOpen(true)}\r\n aria-label={labels?.openMenu ?? 'Abrir menu'}\r\n style={{ top: 'calc(var(--app-shell-top) + 0.75rem)' }}\r\n // absolute (não fixed): fica ao lado do título e sobe com a página; fixo ficava por cima do conteúdo ao deslizar.\r\n className=\"absolute left-5 z-40 flex cursor-pointer items-center justify-center rounded-xl border border-border bg-card p-3 text-muted-foreground shadow-page transition-colors hover:text-proef-blue lg:hidden\"\r\n >\r\n <MenuIcon size={20} />\r\n </button>\r\n )}\r\n {topBar && (\r\n <div style={{ top: 'var(--app-shell-top)' }} className={`sticky z-30 transition-[margin] duration-300 ${margin}`}>\r\n {topBar}\r\n </div>\r\n )}\r\n {/* As margens da página vêm daqui, iguais em todas as páginas (também nas\r\n de altura total) e com o menu aberto ou fechado: as páginas não põem as\r\n suas. Os valores são tokens (--spacing-page-x, --spacing-page-top). Em\r\n ecrãs pequenos o espaço de cima deixa lugar ao botão do menu. */}\r\n {/* Sem topBar, abaixo de 1024px o título do PageHeader sobe para o lado do botão\r\n do menu (as variáveis dizem-lhe quanto subir e quanto se afastar do botão). */}\r\n <main\r\n className={\r\n // overflow-x-clip: uma peça mais larga do que o ecrã (um botão que não parte, um código comprido) fazia o\r\n // telemóvel alargar a página toda e cortar tudo à direita. Assim só essa peça fica cortada. Clip, e não\r\n // hidden, para não criar uma caixa de scroll (o sticky dentro da página continua a funcionar).\r\n `min-w-0 overflow-x-clip px-page-x-narrow transition-[margin] duration-300 sm:px-page-x ${margin} ${topBar ? 'pt-6' : 'pt-page-top-narrow lg:pt-page-top max-lg:[--app-shell-title-lift:calc(var(--spacing-page-top-narrow)-19px)] max-lg:[--app-shell-menu-inset:calc(78px-var(--spacing-page-x-narrow))] sm:max-lg:[--app-shell-menu-inset:calc(78px-var(--spacing-page-x))]'} ` +\r\n (fullHeight ? 'flex min-h-0 flex-1 flex-col overflow-hidden pb-4' : 'pb-16')\r\n }\r\n >\r\n <NavTrailContext.Provider value={trail}>{children}</NavTrailContext.Provider>\r\n </main>\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@ function Page({ tabs, fill = false, children, breadcrumb, ...header }) {
7
7
  return /* @__PURE__ */ jsxs("div", { className: `flex flex-col gap-4 ${fill ? "min-h-0 flex-1" : ""}`, children: [
8
8
  /* @__PURE__ */ jsx(PageHeader, { ...header, breadcrumb: breadcrumb ?? auto }),
9
9
  tabs && /* @__PURE__ */ jsx("div", { className: "flex-none", children: tabs }),
10
- /* @__PURE__ */ jsx("div", { className: `${tabs ? "pt-2" : "pt-4"} ${fill ? "flex min-h-0 flex-1 flex-col overflow-hidden" : ""}`, children })
10
+ /* @__PURE__ */ jsx("div", { className: `${tabs ? "pt-2" : "pt-4 max-lg:pt-0"} ${fill ? "flex min-h-0 flex-1 flex-col overflow-hidden" : ""}`, children })
11
11
  ] });
12
12
  }
13
13
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"Page.js","sources":["../../../../../../src/v1/components/layout/Page/Page.tsx"],"sourcesContent":["import React from 'react'\r\nimport PageHeader, { type PageHeaderProps } from '../PageHeader/PageHeader'\r\nimport { useNavTrail } from '../navTrail'\r\n\r\nexport interface PageProps extends PageHeaderProps {\r\n /** Separadores da página (ex: `<Tabs …/>`), entre o cabeçalho e o conteúdo. */\r\n tabs?: React.ReactNode\r\n /**\r\n * A página ocupa a altura que sobra e o conteúdo faz scroll por dentro (ex:\r\n * quadros e listas compridas). Usar com o `fullHeight` do `AppShell`.\r\n */\r\n fill?: boolean\r\n children: React.ReactNode\r\n}\r\n\r\n/**\r\n * Uma página da app: cabeçalho (`PageHeader`), separadores opcionais e o\r\n * conteúdo, sempre com os mesmos espaços entre eles. As margens à volta vêm do\r\n * `AppShell`; a página não põe as suas.\r\n *\r\n * **Caminho automático**: dentro do `AppShell`, numa página que está num grupo\r\n * do menu, o caminho por cima do título é \"grupo › página\" (ex: \"App & Dev ›\r\n * Sprints\"), com os nomes do menu. Passar `breadcrumb` substitui-o (ex: no\r\n * detalhe de um registo); `breadcrumb={[]}` tira-o.\r\n */\r\nexport default function Page({ tabs, fill = false, children, breadcrumb, ...header }: PageProps) {\r\n const trail = useNavTrail()\r\n const auto = trail?.group ? [{ label: trail.group }, { label: trail.page }] : undefined\r\n\r\n return (\r\n <div className={`flex flex-col gap-4 ${fill ? 'min-h-0 flex-1' : ''}`}>\r\n <PageHeader {...header} breadcrumb={breadcrumb ?? auto} />\r\n {tabs && <div className=\"flex-none\">{tabs}</div>}\r\n <div className={`${tabs ? 'pt-2' : 'pt-4'} ${fill ? 'flex min-h-0 flex-1 flex-col overflow-hidden' : ''}`}>{children}</div>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":";;;AAyBA,SAAwB,KAAK,EAAE,MAAM,OAAO,OAAO,UAAU,YAAY,GAAG,UAAqB;AAC/F,QAAM,QAAQ,YAAA;AACd,QAAM,OAAO,OAAO,QAAQ,CAAC,EAAE,OAAO,MAAM,MAAA,GAAS,EAAE,OAAO,MAAM,KAAA,CAAM,IAAI;AAE9E,8BACG,OAAA,EAAI,WAAW,uBAAuB,OAAO,mBAAmB,EAAE,IACjE,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAY,GAAG,QAAQ,YAAY,cAAc,MAAM;AAAA,IACvD,QAAQ,oBAAC,OAAA,EAAI,WAAU,aAAa,UAAA,MAAK;AAAA,IAC1C,oBAAC,OAAA,EAAI,WAAW,GAAG,OAAO,SAAS,MAAM,IAAI,OAAO,iDAAiD,EAAE,IAAK,SAAA,CAAS;AAAA,EAAA,GACvH;AAEJ;"}
1
+ {"version":3,"file":"Page.js","sources":["../../../../../../src/v1/components/layout/Page/Page.tsx"],"sourcesContent":["import React from 'react'\r\nimport PageHeader, { type PageHeaderProps } from '../PageHeader/PageHeader'\r\nimport { useNavTrail } from '../navTrail'\r\n\r\nexport interface PageProps extends PageHeaderProps {\r\n /** Separadores da página (ex: `<Tabs …/>`), entre o cabeçalho e o conteúdo. */\r\n tabs?: React.ReactNode\r\n /**\r\n * A página ocupa a altura que sobra e o conteúdo faz scroll por dentro (ex:\r\n * quadros e listas compridas). Usar com o `fullHeight` do `AppShell`.\r\n */\r\n fill?: boolean\r\n children: React.ReactNode\r\n}\r\n\r\n/**\r\n * Uma página da app: cabeçalho (`PageHeader`), separadores opcionais e o\r\n * conteúdo, sempre com os mesmos espaços entre eles. As margens à volta vêm do\r\n * `AppShell`; a página não põe as suas.\r\n *\r\n * **Caminho automático**: dentro do `AppShell`, numa página que está num grupo\r\n * do menu, o caminho por cima do título é \"grupo › página\" (ex: \"App & Dev ›\r\n * Sprints\"), com os nomes do menu. Passar `breadcrumb` substitui-o (ex: no\r\n * detalhe de um registo); `breadcrumb={[]}` tira-o.\r\n */\r\nexport default function Page({ tabs, fill = false, children, breadcrumb, ...header }: PageProps) {\r\n const trail = useNavTrail()\r\n const auto = trail?.group ? [{ label: trail.group }, { label: trail.page }] : undefined\r\n\r\n return (\r\n <div className={`flex flex-col gap-4 ${fill ? 'min-h-0 flex-1' : ''}`}>\r\n <PageHeader {...header} breadcrumb={breadcrumb ?? auto} />\r\n {tabs && <div className=\"flex-none\">{tabs}</div>}\r\n {/* Em ecrã estreito, menos espaço entre o cabeçalho e o conteúdo: no telemóvel cada linha conta. */}\r\n <div className={`${tabs ? 'pt-2' : 'pt-4 max-lg:pt-0'} ${fill ? 'flex min-h-0 flex-1 flex-col overflow-hidden' : ''}`}>{children}</div>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":";;;AAyBA,SAAwB,KAAK,EAAE,MAAM,OAAO,OAAO,UAAU,YAAY,GAAG,UAAqB;AAC/F,QAAM,QAAQ,YAAA;AACd,QAAM,OAAO,OAAO,QAAQ,CAAC,EAAE,OAAO,MAAM,MAAA,GAAS,EAAE,OAAO,MAAM,KAAA,CAAM,IAAI;AAE9E,8BACG,OAAA,EAAI,WAAW,uBAAuB,OAAO,mBAAmB,EAAE,IACjE,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAY,GAAG,QAAQ,YAAY,cAAc,MAAM;AAAA,IACvD,QAAQ,oBAAC,OAAA,EAAI,WAAU,aAAa,UAAA,MAAK;AAAA,IAE1C,oBAAC,OAAA,EAAI,WAAW,GAAG,OAAO,SAAS,kBAAkB,IAAI,OAAO,iDAAiD,EAAE,IAAK,SAAA,CAAS;AAAA,EAAA,GACnI;AAEJ;"}
@@ -1,24 +1,27 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  function PageHeader({ title, description, breadcrumb, actions, extra, breadcrumbLabel = "Caminho" }) {
3
- return /* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-3", children: [
4
- breadcrumb && breadcrumb.length > 0 && /* @__PURE__ */ jsx("nav", { "aria-label": breadcrumbLabel, children: /* @__PURE__ */ jsx("ol", { className: "m-0 flex list-none flex-wrap items-center gap-1.5 p-0 text-xs font-semibold text-muted-foreground", children: breadcrumb.map((n, i) => {
5
- const last = i === breadcrumb.length - 1;
6
- const link = "cursor-pointer border-0 bg-transparent p-0 font-semibold text-proef-blue no-underline hover:text-primary hover:underline dark:text-sky-300 dark:hover:text-sky-200";
7
- return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1.5", children: [
8
- last ? /* @__PURE__ */ jsx("span", { "aria-current": "page", className: "text-foreground/70", children: n.label }) : n.href ? /* @__PURE__ */ jsx("a", { href: n.href, onClick: n.onClick, className: link, children: n.label }) : n.onClick ? /* @__PURE__ */ jsx("button", { type: "button", onClick: n.onClick, className: `text-xs ${link}`, children: n.label }) : /* @__PURE__ */ jsx("span", { children: n.label }),
9
- !last && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-muted-foreground/60", children: "›" })
10
- ] }, i);
11
- }) }) }),
12
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between", children: [
13
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
14
- /* @__PURE__ */ jsx("h1", { className: "m-0 text-page-title font-black tracking-tighter text-primary uppercase dark:text-foreground", children: title }),
15
- /* @__PURE__ */ jsx("div", { className: "mt-1 h-1 w-12 bg-proef-turquoise" }),
16
- description && /* @__PURE__ */ jsx("p", { className: "m-0 mt-3 text-sm font-medium text-muted-foreground", children: description }),
17
- extra && /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap items-center gap-2 empty:hidden", children: extra })
18
- ] }),
19
- actions && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 flex-wrap items-center gap-2 empty:hidden", children: actions })
3
+ return (
4
+ // Dentro do AppShell, em ecrã estreito, o caminho e o título ficam ao lado do botão do menu.
5
+ /* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-3 mt-[calc(var(--app-shell-title-lift,0px)*-1)]", children: [
6
+ breadcrumb && breadcrumb.length > 0 && /* @__PURE__ */ jsx("nav", { "aria-label": breadcrumbLabel, className: "pl-[var(--app-shell-menu-inset,0px)]", children: /* @__PURE__ */ jsx("ol", { className: "m-0 flex list-none flex-wrap items-center gap-1.5 p-0 text-xs font-semibold text-muted-foreground", children: breadcrumb.map((n, i) => {
7
+ const last = i === breadcrumb.length - 1;
8
+ const link = "cursor-pointer border-0 bg-transparent p-0 font-semibold text-proef-blue no-underline hover:text-primary hover:underline dark:text-sky-300 dark:hover:text-sky-200";
9
+ return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1.5", children: [
10
+ last ? /* @__PURE__ */ jsx("span", { "aria-current": "page", className: "text-foreground/70", children: n.label }) : n.href ? /* @__PURE__ */ jsx("a", { href: n.href, onClick: n.onClick, className: link, children: n.label }) : n.onClick ? /* @__PURE__ */ jsx("button", { type: "button", onClick: n.onClick, className: `text-xs ${link}`, children: n.label }) : /* @__PURE__ */ jsx("span", { children: n.label }),
11
+ !last && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-muted-foreground/60", children: "›" })
12
+ ] }, i);
13
+ }) }) }),
14
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between", children: [
15
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
16
+ /* @__PURE__ */ jsx("h1", { className: "m-0 pl-[var(--app-shell-menu-inset,0px)] text-page-title font-black tracking-tighter text-primary uppercase dark:text-foreground", children: title }),
17
+ /* @__PURE__ */ jsx("div", { className: "mt-1 ml-[var(--app-shell-menu-inset,0px)] h-1 w-12 bg-proef-turquoise" }),
18
+ description && /* @__PURE__ */ jsx("p", { className: "m-0 mt-3 text-sm font-medium text-muted-foreground", children: description }),
19
+ extra && /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap items-center gap-2 empty:hidden", children: extra })
20
+ ] }),
21
+ actions && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 flex-wrap items-center gap-2 empty:hidden", children: actions })
22
+ ] })
20
23
  ] })
21
- ] });
24
+ );
22
25
  }
23
26
  export {
24
27
  PageHeader as default
@@ -1 +1 @@
1
- {"version":3,"file":"PageHeader.js","sources":["../../../../../../src/v1/components/layout/PageHeader/PageHeader.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n/** Um nível do caminho por cima do título. */\r\nexport interface BreadcrumbItem {\r\n label: React.ReactNode\r\n /** Ligação normal. Para o router da app, usar `onClick` (ex: `() => navigate('/x')`). */\r\n href?: string\r\n onClick?: () => void\r\n}\r\n\r\nexport interface PageHeaderProps {\r\n title: React.ReactNode\r\n description?: React.ReactNode\r\n /**\r\n * Caminho até à página (área › lista › registo). O último nível é a página\r\n * atual e não leva ligação, mesmo que a traga.\r\n */\r\n breadcrumb?: BreadcrumbItem[]\r\n /** Botões da página (ex: \"Novo\", \"Editar\"). Ficam à direita e descem em ecrã estreito. */\r\n actions?: React.ReactNode\r\n /** Linha extra por baixo da descrição (etiquetas de estado, responsável…). */\r\n extra?: React.ReactNode\r\n /** Nome da navegação do caminho, para leitores de ecrã. Por omissão: \"Caminho\". */\r\n breadcrumbLabel?: string\r\n}\r\n\r\n/**\r\n * Cabeçalho comum a todas as páginas: caminho, título com a barra turquesa,\r\n * descrição e ações.\r\n *\r\n * Existe para o título deixar de ser desenhado à mão em cada ecrã — com\r\n * tamanhos, cores e posições diferentes de página para página. O nome no menu,\r\n * o título e o último nível do caminho devem ser o mesmo texto.\r\n *\r\n * Normalmente usa-se através do `Page`, que também arruma os separadores e o\r\n * conteúdo por baixo.\r\n */\r\nexport default function PageHeader({ title, description, breadcrumb, actions, extra, breadcrumbLabel = 'Caminho' }: PageHeaderProps) {\r\n return (\r\n <header className=\"flex flex-col gap-3\">\r\n {breadcrumb && breadcrumb.length > 0 && (\r\n <nav aria-label={breadcrumbLabel}>\r\n <ol className=\"m-0 flex list-none flex-wrap items-center gap-1.5 p-0 text-xs font-semibold text-muted-foreground\">\r\n {breadcrumb.map((n, i) => {\r\n const last = i === breadcrumb.length - 1\r\n const link = 'cursor-pointer border-0 bg-transparent p-0 font-semibold text-proef-blue no-underline hover:text-primary hover:underline dark:text-sky-300 dark:hover:text-sky-200'\r\n return (\r\n <li key={i} className=\"flex items-center gap-1.5\">\r\n {last ? (\r\n <span aria-current=\"page\" className=\"text-foreground/70\">\r\n {n.label}\r\n </span>\r\n ) : n.href ? (\r\n <a href={n.href} onClick={n.onClick} className={link}>\r\n {n.label}\r\n </a>\r\n ) : n.onClick ? (\r\n <button type=\"button\" onClick={n.onClick} className={`text-xs ${link}`}>\r\n {n.label}\r\n </button>\r\n ) : (\r\n <span>{n.label}</span>\r\n )}\r\n {!last && (\r\n <span aria-hidden=\"true\" className=\"text-muted-foreground/60\">\r\n ›\r\n </span>\r\n )}\r\n </li>\r\n )\r\n })}\r\n </ol>\r\n </nav>\r\n )}\r\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between\">\r\n <div className=\"min-w-0\">\r\n {/* Tamanho pelo token --text-page-title (tokens.css): igual em todos os portais e ecrãs. */}\r\n <h1 className=\"m-0 text-page-title font-black tracking-tighter text-primary uppercase dark:text-foreground\">{title}</h1>\r\n <div className=\"mt-1 h-1 w-12 bg-proef-turquoise\" />\r\n {description && <p className=\"m-0 mt-3 text-sm font-medium text-muted-foreground\">{description}</p>}\r\n {/* empty:hidden: um botão condicional que não aparece (ex: `<>{cond && <b/>}</>`)\r\n não deixa espaço vazio por baixo do título. */}\r\n {extra && <div className=\"mt-2 flex flex-wrap items-center gap-2 empty:hidden\">{extra}</div>}\r\n </div>\r\n {actions && <div className=\"flex shrink-0 flex-wrap items-center gap-2 empty:hidden\">{actions}</div>}\r\n </div>\r\n </header>\r\n )\r\n}\r\n"],"names":[],"mappings":";AAqCA,SAAwB,WAAW,EAAE,OAAO,aAAa,YAAY,SAAS,OAAO,kBAAkB,aAA8B;AACnI,SACE,qBAAC,UAAA,EAAO,WAAU,uBACf,UAAA;AAAA,IAAA,cAAc,WAAW,SAAS,KACjC,oBAAC,SAAI,cAAY,iBACf,UAAA,oBAAC,MAAA,EAAG,WAAU,qGACX,UAAA,WAAW,IAAI,CAAC,GAAG,MAAM;AACxB,YAAM,OAAO,MAAM,WAAW,SAAS;AACvC,YAAM,OAAO;AACb,aACE,qBAAC,MAAA,EAAW,WAAU,6BACnB,UAAA;AAAA,QAAA,2BACE,QAAA,EAAK,gBAAa,QAAO,WAAU,sBACjC,YAAE,MAAA,CACL,IACE,EAAE,OACJ,oBAAC,OAAE,MAAM,EAAE,MAAM,SAAS,EAAE,SAAS,WAAW,MAC7C,YAAE,MAAA,CACL,IACE,EAAE,UACJ,oBAAC,YAAO,MAAK,UAAS,SAAS,EAAE,SAAS,WAAW,WAAW,IAAI,IACjE,UAAA,EAAE,MAAA,CACL,IAEA,oBAAC,QAAA,EAAM,YAAE,MAAA,CAAM;AAAA,QAEhB,CAAC,QACA,oBAAC,QAAA,EAAK,eAAY,QAAO,WAAU,4BAA2B,UAAA,IAAA,CAE9D;AAAA,MAAA,EAAA,GAnBK,CAqBT;AAAA,IAEJ,CAAC,GACH,GACF;AAAA,IAEF,qBAAC,OAAA,EAAI,WAAU,mEACb,UAAA;AAAA,MAAA,qBAAC,OAAA,EAAI,WAAU,WAEb,UAAA;AAAA,QAAA,oBAAC,MAAA,EAAG,WAAU,+FAA+F,UAAA,OAAM;AAAA,QACnH,oBAAC,OAAA,EAAI,WAAU,mCAAA,CAAmC;AAAA,QACjD,eAAe,oBAAC,KAAA,EAAE,WAAU,sDAAsD,UAAA,aAAY;AAAA,QAG9F,SAAS,oBAAC,OAAA,EAAI,WAAU,uDAAuD,UAAA,MAAA,CAAM;AAAA,MAAA,GACxF;AAAA,MACC,WAAW,oBAAC,OAAA,EAAI,WAAU,2DAA2D,UAAA,QAAA,CAAQ;AAAA,IAAA,EAAA,CAChG;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"PageHeader.js","sources":["../../../../../../src/v1/components/layout/PageHeader/PageHeader.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n/** Um nível do caminho por cima do título. */\r\nexport interface BreadcrumbItem {\r\n label: React.ReactNode\r\n /** Ligação normal. Para o router da app, usar `onClick` (ex: `() => navigate('/x')`). */\r\n href?: string\r\n onClick?: () => void\r\n}\r\n\r\nexport interface PageHeaderProps {\r\n title: React.ReactNode\r\n description?: React.ReactNode\r\n /**\r\n * Caminho até à página (área › lista › registo). O último nível é a página\r\n * atual e não leva ligação, mesmo que a traga.\r\n */\r\n breadcrumb?: BreadcrumbItem[]\r\n /** Botões da página (ex: \"Novo\", \"Editar\"). Ficam à direita e descem em ecrã estreito. */\r\n actions?: React.ReactNode\r\n /** Linha extra por baixo da descrição (etiquetas de estado, responsável…). */\r\n extra?: React.ReactNode\r\n /** Nome da navegação do caminho, para leitores de ecrã. Por omissão: \"Caminho\". */\r\n breadcrumbLabel?: string\r\n}\r\n\r\n/**\r\n * Cabeçalho comum a todas as páginas: caminho, título com a barra turquesa,\r\n * descrição e ações.\r\n *\r\n * Existe para o título deixar de ser desenhado à mão em cada ecrã — com\r\n * tamanhos, cores e posições diferentes de página para página. O nome no menu,\r\n * o título e o último nível do caminho devem ser o mesmo texto.\r\n *\r\n * Normalmente usa-se através do `Page`, que também arruma os separadores e o\r\n * conteúdo por baixo.\r\n */\r\nexport default function PageHeader({ title, description, breadcrumb, actions, extra, breadcrumbLabel = 'Caminho' }: PageHeaderProps) {\r\n return (\r\n // Dentro do AppShell, em ecrã estreito, o caminho e o título ficam ao lado do botão do menu.\r\n <header className=\"flex flex-col gap-3 mt-[calc(var(--app-shell-title-lift,0px)*-1)]\">\r\n {breadcrumb && breadcrumb.length > 0 && (\r\n <nav aria-label={breadcrumbLabel} className=\"pl-[var(--app-shell-menu-inset,0px)]\">\r\n <ol className=\"m-0 flex list-none flex-wrap items-center gap-1.5 p-0 text-xs font-semibold text-muted-foreground\">\r\n {breadcrumb.map((n, i) => {\r\n const last = i === breadcrumb.length - 1\r\n const link = 'cursor-pointer border-0 bg-transparent p-0 font-semibold text-proef-blue no-underline hover:text-primary hover:underline dark:text-sky-300 dark:hover:text-sky-200'\r\n return (\r\n <li key={i} className=\"flex items-center gap-1.5\">\r\n {last ? (\r\n <span aria-current=\"page\" className=\"text-foreground/70\">\r\n {n.label}\r\n </span>\r\n ) : n.href ? (\r\n <a href={n.href} onClick={n.onClick} className={link}>\r\n {n.label}\r\n </a>\r\n ) : n.onClick ? (\r\n <button type=\"button\" onClick={n.onClick} className={`text-xs ${link}`}>\r\n {n.label}\r\n </button>\r\n ) : (\r\n <span>{n.label}</span>\r\n )}\r\n {!last && (\r\n <span aria-hidden=\"true\" className=\"text-muted-foreground/60\">\r\n ›\r\n </span>\r\n )}\r\n </li>\r\n )\r\n })}\r\n </ol>\r\n </nav>\r\n )}\r\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between\">\r\n <div className=\"min-w-0\">\r\n {/* Tamanho pelo token --text-page-title (tokens.css): igual em todos os portais e ecrãs. */}\r\n <h1 className=\"m-0 pl-[var(--app-shell-menu-inset,0px)] text-page-title font-black tracking-tighter text-primary uppercase dark:text-foreground\">{title}</h1>\r\n <div className=\"mt-1 ml-[var(--app-shell-menu-inset,0px)] h-1 w-12 bg-proef-turquoise\" />\r\n {description && <p className=\"m-0 mt-3 text-sm font-medium text-muted-foreground\">{description}</p>}\r\n {/* empty:hidden: um botão condicional que não aparece (ex: `<>{cond && <b/>}</>`)\r\n não deixa espaço vazio por baixo do título. */}\r\n {extra && <div className=\"mt-2 flex flex-wrap items-center gap-2 empty:hidden\">{extra}</div>}\r\n </div>\r\n {actions && <div className=\"flex shrink-0 flex-wrap items-center gap-2 empty:hidden\">{actions}</div>}\r\n </div>\r\n </header>\r\n )\r\n}\r\n"],"names":[],"mappings":";AAqCA,SAAwB,WAAW,EAAE,OAAO,aAAa,YAAY,SAAS,OAAO,kBAAkB,aAA8B;AACnI;AAAA;AAAA,IAEE,qBAAC,UAAA,EAAO,WAAU,qEACf,UAAA;AAAA,MAAA,cAAc,WAAW,SAAS,KACjC,oBAAC,OAAA,EAAI,cAAY,iBAAiB,WAAU,wCAC1C,UAAA,oBAAC,QAAG,WAAU,qGACX,qBAAW,IAAI,CAAC,GAAG,MAAM;AACxB,cAAM,OAAO,MAAM,WAAW,SAAS;AACvC,cAAM,OAAO;AACb,eACE,qBAAC,MAAA,EAAW,WAAU,6BACnB,UAAA;AAAA,UAAA,2BACE,QAAA,EAAK,gBAAa,QAAO,WAAU,sBACjC,YAAE,MAAA,CACL,IACE,EAAE,OACJ,oBAAC,OAAE,MAAM,EAAE,MAAM,SAAS,EAAE,SAAS,WAAW,MAC7C,YAAE,MAAA,CACL,IACE,EAAE,UACJ,oBAAC,YAAO,MAAK,UAAS,SAAS,EAAE,SAAS,WAAW,WAAW,IAAI,IACjE,UAAA,EAAE,MAAA,CACL,IAEA,oBAAC,QAAA,EAAM,YAAE,MAAA,CAAM;AAAA,UAEhB,CAAC,QACA,oBAAC,QAAA,EAAK,eAAY,QAAO,WAAU,4BAA2B,UAAA,IAAA,CAE9D;AAAA,QAAA,EAAA,GAnBK,CAqBT;AAAA,MAEJ,CAAC,GACH,GACF;AAAA,MAEF,qBAAC,OAAA,EAAI,WAAU,mEACb,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,WAEb,UAAA;AAAA,UAAA,oBAAC,MAAA,EAAG,WAAU,oIAAoI,UAAA,OAAM;AAAA,UACxJ,oBAAC,OAAA,EAAI,WAAU,wEAAA,CAAwE;AAAA,UACtF,eAAe,oBAAC,KAAA,EAAE,WAAU,sDAAsD,UAAA,aAAY;AAAA,UAG9F,SAAS,oBAAC,OAAA,EAAI,WAAU,uDAAuD,UAAA,MAAA,CAAM;AAAA,QAAA,GACxF;AAAA,QACC,WAAW,oBAAC,OAAA,EAAI,WAAU,2DAA2D,UAAA,QAAA,CAAQ;AAAA,MAAA,EAAA,CAChG;AAAA,IAAA,EAAA,CACF;AAAA;AAEJ;"}
@@ -19,11 +19,11 @@ function numerosPagina(atual, total) {
19
19
  }
20
20
  function LinhaEsqueleto({ marcavel, largura }) {
21
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" }) }),
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 dark:border-slate-700 dark:bg-slate-800" }) }),
23
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" })
24
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-20 bg-slate-200 dark:bg-slate-700" }),
25
+ /* @__PURE__ */ jsx("div", { className: `h-2.5 ${largura} bg-slate-200 dark:bg-slate-700` }),
26
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 w-1/3 bg-slate-100 dark:bg-slate-800" })
27
27
  ] })
28
28
  ] });
29
29
  }
@@ -133,9 +133,9 @@ function ListFinder({
133
133
  onSelectionChange?.(n);
134
134
  };
135
135
  const aCarregar = loading && visiveis.length === 0;
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 });
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: [
136
+ const esqueleto = /* @__PURE__ */ jsx("span", { className: "inline-block h-2 w-20 rounded-sm bg-slate-200 dark:bg-slate-700 animate-pulse motion-reduce:animate-none", "aria-hidden": true });
137
+ return /* @__PURE__ */ jsxs("div", { className: `v1-reset flex h-full min-h-0 flex-col bg-white text-control text-slate-800 dark:bg-slate-900 dark:text-slate-100 ${bordered ? "border border-slate-200 dark:border-slate-700" : ""} ${className}`, children: [
138
+ (searchable || onBack || filters) && /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-b border-slate-100 px-3 py-2 dark:border-slate-800", children: [
139
139
  (onBack || filters) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
140
140
  onBack && /* @__PURE__ */ jsx(
141
141
  "button",
@@ -144,7 +144,7 @@ function ListFinder({
144
144
  onClick: onBack,
145
145
  "aria-label": L.back,
146
146
  title: L.back,
147
- 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",
147
+ 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 dark:hover:bg-slate-800 dark:hover:text-slate-200",
148
148
  children: /* @__PURE__ */ jsx(ChevronLeftIcon, { size: 14 })
149
149
  }
150
150
  ),
@@ -161,12 +161,12 @@ function ListFinder({
161
161
  onPaste: colar,
162
162
  placeholder: L.searchPlaceholder,
163
163
  "aria-label": L.search,
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"
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 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-100"
165
165
  }
166
166
  )
167
167
  ] })
168
168
  ] }),
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: [
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 dark:bg-slate-900 dark:text-slate-300 ${LINHA_ESBATIDA}`, children: [
170
170
  /* @__PURE__ */ jsx("span", { className: "flex w-7 shrink-0 justify-center", children: /* @__PURE__ */ jsx(
171
171
  Checkbox,
172
172
  {
@@ -219,19 +219,19 @@ function ListFinder({
219
219
  ] }),
220
220
  /* @__PURE__ */ jsx(OverlayScrollbar, { alvo: caixa, borda: 0 })
221
221
  ] }),
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: [
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 dark:border-slate-800 dark:bg-slate-800/50 ${aRolar ? "h-8 justify-center" : "h-9 justify-between"}`, children: [
223
223
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 whitespace-nowrap", children: [
224
224
  aCarregar ? esqueleto : aRolar ? (
225
225
  // A rolar: quantas já se mostram, de quantas há.
226
226
  /* @__PURE__ */ jsxs(Fragment, { children: [
227
- /* @__PURE__ */ jsx("span", { className: "font-medium text-slate-500 tabular-nums", children: visiveis.length.toLocaleString("pt-PT") }),
227
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-slate-500 tabular-nums dark:text-slate-300", children: visiveis.length.toLocaleString("pt-PT") }),
228
228
  " / ",
229
- /* @__PURE__ */ jsx("span", { className: "font-medium text-slate-500 tabular-nums", children: totalLinhas.toLocaleString("pt-PT") }),
229
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-slate-500 tabular-nums dark:text-slate-300", children: totalLinhas.toLocaleString("pt-PT") }),
230
230
  " ",
231
231
  L.itemsNoun(totalLinhas)
232
232
  ] })
233
233
  ) : /* @__PURE__ */ jsxs(Fragment, { children: [
234
- /* @__PURE__ */ jsx("span", { className: "font-medium text-slate-500 tabular-nums", children: totalLinhas.toLocaleString("pt-PT") }),
234
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-slate-500 tabular-nums dark:text-slate-300", children: totalLinhas.toLocaleString("pt-PT") }),
235
235
  " ",
236
236
  L.itemsNoun(totalLinhas)
237
237
  ] }),
@@ -246,13 +246,13 @@ function ListFinder({
246
246
  " / ",
247
247
  totalPaginas
248
248
  ] }),
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(
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 dark:text-slate-600", children: "…" }, `r${i}`) : /* @__PURE__ */ jsxs(
250
250
  "button",
251
251
  {
252
252
  type: "button",
253
253
  onClick: () => irPara(p),
254
254
  "aria-current": p === pagina ? "page" : void 0,
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"}`,
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 dark:text-slate-100" : "text-slate-400 hover:text-slate-700 dark:hover:text-slate-200"}`,
256
256
  children: [
257
257
  /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "invisible col-start-1 row-start-1 font-semibold", children: p.toLocaleString("pt-PT") }),
258
258
  /* @__PURE__ */ jsx("span", { className: "col-start-1 row-start-1", children: p.toLocaleString("pt-PT") })