@proef/common-ui 2.6.6 → 2.6.7

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 (29) hide show
  1. package/dist/esm/index.js +34 -32
  2. package/dist/esm/index.js.map +1 -1
  3. package/dist/esm/legacy/components/layout/Spinner/Spinner.js +1 -15
  4. package/dist/esm/legacy/components/layout/Spinner/Spinner.js.map +1 -1
  5. package/dist/esm/legacy/components/molecules/ConfirmModal/ConfirmModal.js +1 -1
  6. package/dist/esm/legacy/components/molecules/MultiTagPicker/MultiTagPicker.js +1 -1
  7. package/dist/esm/legacy/components/organisms/LazyLoader/LazyLoader.js +1 -1
  8. package/dist/esm/legacy/components/organisms/Selector/Selector.js +1 -1
  9. package/dist/esm/v1/components/data/MonthCalendar/MonthCalendar.js +83 -0
  10. package/dist/esm/v1/components/data/MonthCalendar/MonthCalendar.js.map +1 -0
  11. package/dist/esm/v1/primitives/display/Badge/Badge.js +3 -2
  12. package/dist/esm/v1/primitives/display/Badge/Badge.js.map +1 -1
  13. package/dist/esm/v1/primitives/display/DescriptionList/DescriptionList.js +31 -0
  14. package/dist/esm/v1/primitives/display/DescriptionList/DescriptionList.js.map +1 -0
  15. package/dist/esm/v1/primitives/feedback/Spinner/Spinner.js +24 -0
  16. package/dist/esm/v1/primitives/feedback/Spinner/Spinner.js.map +1 -0
  17. package/dist/esm/v1/primitives/forms/DateInput/DateInput.js +3 -1
  18. package/dist/esm/v1/primitives/forms/DateInput/DateInput.js.map +1 -1
  19. package/dist/style.components.css +1 -1
  20. package/dist/style.css +1 -1
  21. package/dist/style.layered.css +1 -1
  22. package/dist/types/legacy/components/layout/Spinner/Spinner.d.ts +1 -4
  23. package/dist/types/v1/components/data/MonthCalendar/MonthCalendar.d.ts +37 -0
  24. package/dist/types/v1/index.d.ts +6 -0
  25. package/dist/types/v1/primitives/display/Badge/Badge.d.ts +2 -0
  26. package/dist/types/v1/primitives/display/DescriptionList/DescriptionList.d.ts +29 -0
  27. package/dist/types/v1/primitives/feedback/Spinner/Spinner.d.ts +20 -0
  28. package/package.json +1 -1
  29. package/dist/esm/legacy/components/layout/Spinner/Spinner.css +0 -10
package/dist/esm/index.js CHANGED
@@ -71,23 +71,22 @@ import { default as default66 } from "./legacy/components/molecules/SeletorPesqu
71
71
  import { default as default67 } from "./legacy/components/ui/Separadores.js";
72
72
  import { default as default68 } from "./v1/components/layout/SideNav/SideNav.js";
73
73
  import { default as default69 } from "./legacy/components/molecules/Sidebar/Sidebar.js";
74
- import { default as default70 } from "./legacy/components/layout/Spinner/Spinner.js";
75
- import { default as default71 } from "./legacy/components/atoms/Stack/Stack.js";
76
- import { default as default72 } from "./legacy/components/organisms/StatusMatrix/StatusMatrix.js";
74
+ import { default as default70 } from "./legacy/components/atoms/Stack/Stack.js";
75
+ import { default as default71 } from "./legacy/components/organisms/StatusMatrix/StatusMatrix.js";
77
76
  import { DEFAULT_STEPS_LABELS, Steps } from "./v1/primitives/navigation/Steps/Steps.js";
78
- import { default as default73 } from "./v1/primitives/forms/Switch/Switch.js";
79
- import { default as default74 } from "./legacy/components/atoms/Button/variants/TabButton.js";
80
- import { default as default75 } from "./v1/components/tables/Table/Table.js";
81
- import { default as default76 } from "./v1/primitives/navigation/Tabs/Tabs.js";
82
- import { default as default77 } from "./v1/primitives/forms/TextArea/TextArea.js";
83
- import { default as default78 } from "./v1/primitives/forms/TextInput/TextInput.js";
84
- import { default as default79 } from "./legacy/components/ui/TextoAuto.js";
85
- import { default as default80, useToast } from "./v1/components/feedback/Toast/Toast.js";
86
- import { default as default81 } from "./v1/components/overlays/Tooltip/Tooltip.js";
87
- import { default as default82 } from "./v1/components/overlays/Tooltip/TooltipLayer.js";
88
- import { default as default83 } from "./legacy/components/layout/TopBar/TopBar.js";
89
- import { default as default84 } from "./legacy/components/layout/TopNavLayout/TopNavLayout.js";
90
- import { default as default85 } from "./legacy/components/molecules/UserAvatar/UserAvatar.js";
77
+ import { default as default72 } from "./v1/primitives/forms/Switch/Switch.js";
78
+ import { default as default73 } from "./legacy/components/atoms/Button/variants/TabButton.js";
79
+ import { default as default74 } from "./v1/components/tables/Table/Table.js";
80
+ import { default as default75 } from "./v1/primitives/navigation/Tabs/Tabs.js";
81
+ import { default as default76 } from "./v1/primitives/forms/TextArea/TextArea.js";
82
+ import { default as default77 } from "./v1/primitives/forms/TextInput/TextInput.js";
83
+ import { default as default78 } from "./legacy/components/ui/TextoAuto.js";
84
+ import { default as default79, useToast } from "./v1/components/feedback/Toast/Toast.js";
85
+ import { default as default80 } from "./v1/components/overlays/Tooltip/Tooltip.js";
86
+ import { default as default81 } from "./v1/components/overlays/Tooltip/TooltipLayer.js";
87
+ import { default as default82 } from "./legacy/components/layout/TopBar/TopBar.js";
88
+ import { default as default83 } from "./legacy/components/layout/TopNavLayout/TopNavLayout.js";
89
+ import { default as default84 } from "./legacy/components/molecules/UserAvatar/UserAvatar.js";
91
90
  import { Anel, Barras, BarrasEmpilhadas, BarrasHorizontais, Bolhas, Linha, Linhas, PALETA, cor } from "./legacy/components/charts/Charts.js";
92
91
  import { ApiProvider } from "./legacy/contexts/apiContext.js";
93
92
  import { AuthProvider, useAuth } from "./legacy/contexts/authContext.js";
@@ -101,11 +100,13 @@ import { BoldIcon, CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon
101
100
  import { Card } from "./v1/primitives/display/Card/Card.js";
102
101
  import { DEFAULT_LIST_FINDER_LABELS } from "./v1/components/lists/ListFinder/labels.js";
103
102
  import { DEFAULT_TABLE_LABELS } from "./v1/components/tables/Table/labels.js";
103
+ import { DescriptionList } from "./v1/primitives/display/DescriptionList/DescriptionList.js";
104
104
  import { FilterChips } from "./v1/primitives/display/FilterChips/FilterChips.js";
105
105
  import { FontWeight } from "./legacy/constants/FontWeight.js";
106
106
  import { InputData, InputHora } from "./legacy/components/atoms/InputData/InputData.js";
107
107
  import { Kpi } from "./v1/primitives/display/Kpi/Kpi.js";
108
108
  import { LazyLoader } from "./legacy/components/organisms/LazyLoader/LazyLoader.js";
109
+ import { MonthCalendar } from "./v1/components/data/MonthCalendar/MonthCalendar.js";
109
110
  import { Notice } from "./v1/primitives/display/Notice/Notice.js";
110
111
  import { Pagination } from "./v1/components/data/Pagination/Pagination.js";
111
112
  import { PeopleFilter } from "./v1/primitives/display/PeopleFilter/PeopleFilter.js";
@@ -174,6 +175,7 @@ export {
174
175
  DEFAULT_TABLE_LABELS,
175
176
  default19 as DataTable,
176
177
  DateInput,
178
+ DescriptionList,
177
179
  default20 as Dialog,
178
180
  default21 as DocumentDropzone,
179
181
  DonutChart,
@@ -239,6 +241,7 @@ export {
239
241
  MinimizeIcon,
240
242
  default50 as Modal,
241
243
  MonitorIcon,
244
+ MonthCalendar,
242
245
  MoonIcon,
243
246
  MoreIcon,
244
247
  default51 as MultiTagPicker,
@@ -280,27 +283,26 @@ export {
280
283
  SlidersIcon,
281
284
  SocketProvider,
282
285
  SparklesIcon,
283
- default70 as Spinner,
284
- default71 as Stack,
285
- default72 as StatusMatrix,
286
+ default70 as Stack,
287
+ default71 as StatusMatrix,
286
288
  Steps,
287
289
  StrikethroughIcon,
288
290
  SunIcon,
289
- default73 as Switch,
291
+ default72 as Switch,
290
292
  THEME_STORAGE_KEY,
291
- default74 as TabButton,
292
- default75 as Table,
293
- default76 as Tabs,
294
- default77 as TextArea,
295
- default78 as TextInput,
296
- default79 as TextoAuto,
297
- default80 as ToastProvider,
298
- default81 as Tooltip,
299
- default82 as TooltipLayer,
300
- default83 as TopBar,
301
- default84 as TopNavLayout,
293
+ default73 as TabButton,
294
+ default74 as Table,
295
+ default75 as Tabs,
296
+ default76 as TextArea,
297
+ default77 as TextInput,
298
+ default78 as TextoAuto,
299
+ default79 as ToastProvider,
300
+ default80 as Tooltip,
301
+ default81 as TooltipLayer,
302
+ default82 as TopBar,
303
+ default83 as TopNavLayout,
302
304
  UnderlineIcon,
303
- default85 as UserAvatar,
305
+ default84 as UserAvatar,
304
306
  WarningIcon,
305
307
  applyColumnLayout,
306
308
  applyTheme,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,18 +1,4 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
- import { faSpinner } from "@fortawesome/free-solid-svg-icons";
4
- import './Spinner.css';/* empty css */
5
- const Spinner = () => {
6
- return /* @__PURE__ */ jsx("div", { className: "cui-spinner", children: /* @__PURE__ */ jsx(
7
- FontAwesomeIcon,
8
- {
9
- icon: faSpinner,
10
- className: "cui-icon",
11
- spin: true,
12
- "aria-hidden": "true"
13
- }
14
- ) });
15
- };
1
+ import { Spinner } from "../../../../v1/primitives/feedback/Spinner/Spinner.js";
16
2
  export {
17
3
  Spinner as default
18
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Spinner.js","sources":["../../../../../../src/legacy/components/layout/Spinner/Spinner.tsx"],"sourcesContent":["import React from 'react'\r\n\r\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\r\nimport { faSpinner } from '@fortawesome/free-solid-svg-icons'\r\nimport './Spinner.scss'\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nconst Spinner: React.FC = () => {\r\n return (\r\n <div className=\"cui-spinner\">\r\n <FontAwesomeIcon\r\n icon={faSpinner}\r\n className={'cui-icon'}\r\n spin\r\n aria-hidden=\"true\"\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default Spinner\r\n"],"names":[],"mappings":";;;;AAOA,MAAM,UAAoB,MAAM;AAC9B,SACE,oBAAC,OAAA,EAAI,WAAU,eACb,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM;AAAA,MACN,WAAW;AAAA,MACX,MAAI;AAAA,MACJ,eAAY;AAAA,IAAA;AAAA,EAAA,GAEhB;AAEJ;"}
1
+ {"version":3,"file":"Spinner.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -2,7 +2,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { faCheck } from "@fortawesome/free-solid-svg-icons";
3
3
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4
4
  import Modal from "../Modal/Modal.js";
5
- import Spinner from "../../layout/Spinner/Spinner.js";
5
+ import { Spinner } from "../../../../v1/primitives/feedback/Spinner/Spinner.js";
6
6
  import { ActionButtonThemeStyled } from "../../../styles/themes/ButtonThemes.js";
7
7
  import './ConfirmModal.css';/* empty css */
8
8
  import ActionButton from "../../atoms/Button/variants/ActionButton.js";
@@ -5,7 +5,7 @@ import { faCircleMinus, faMagnifyingGlass } from "@fortawesome/free-solid-svg-ic
5
5
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6
6
  import { Color } from "../../../constants/Colors.js";
7
7
  import Select from "../../atoms/MultiTagPickerTag/MultiTagPickerTag.js";
8
- import Spinner from "../../layout/Spinner/Spinner.js";
8
+ import { Spinner } from "../../../../v1/primitives/feedback/Spinner/Spinner.js";
9
9
  import './MultiTagPicker.css';/* empty css */
10
10
  const MultiTagPicker = (props) => {
11
11
  const {
@@ -2,7 +2,7 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { useState, useRef, useEffect } from "react";
3
3
  import InfiniteScroll from "react-infinite-scroll-component";
4
4
  import { useApi } from "../../../hooks/useApi.js";
5
- import Spinner from "../../layout/Spinner/Spinner.js";
5
+ import { Spinner } from "../../../../v1/primitives/feedback/Spinner/Spinner.js";
6
6
  const LazyLoader = ({
7
7
  endpoint,
8
8
  params = {},
@@ -6,7 +6,7 @@ import Modal from "../../molecules/Modal/Modal.js";
6
6
  import SearchInput from "../../../../v1/primitives/forms/SearchInput/SearchInput.js";
7
7
  import { LazyLoader } from "../LazyLoader/LazyLoader.js";
8
8
  import './Selector.css';/* empty css */
9
- import Spinner from "../../layout/Spinner/Spinner.js";
9
+ import { Spinner } from "../../../../v1/primitives/feedback/Spinner/Spinner.js";
10
10
  import Tooltip from "../../../../v1/components/overlays/Tooltip/Tooltip.js";
11
11
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
12
12
  import ActionButton from "../../atoms/Button/variants/ActionButton.js";
@@ -0,0 +1,83 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { BADGE_TONE_CLASSES } from "../../../primitives/display/Badge/Badge.js";
4
+ const pad = (n) => String(n).padStart(2, "0");
5
+ const hoje = () => {
6
+ const d = /* @__PURE__ */ new Date();
7
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
8
+ };
9
+ function MonthCalendar({
10
+ year,
11
+ month,
12
+ days = [],
13
+ title,
14
+ locale = "pt-PT",
15
+ weekStartsOn = 1,
16
+ today,
17
+ className = "",
18
+ ...props
19
+ }) {
20
+ const prefixo = `${year}-${pad(month)}-`;
21
+ const marcados = React.useMemo(() => {
22
+ const m = /* @__PURE__ */ new Map();
23
+ for (const d of days) {
24
+ if (!d.date.startsWith(prefixo)) continue;
25
+ const atual = m.get(d.date);
26
+ if (atual) {
27
+ if (d.label) atual.labels.push(d.label);
28
+ } else {
29
+ m.set(d.date, { tone: d.tone, labels: d.label ? [d.label] : [] });
30
+ }
31
+ }
32
+ return m;
33
+ }, [days, prefixo]);
34
+ const nomes = React.useMemo(() => {
35
+ const utc = { timeZone: "UTC" };
36
+ const primeiro = weekStartsOn === 1 ? 5 : 4;
37
+ const semana = new Intl.DateTimeFormat(locale, { ...utc, weekday: "narrow" });
38
+ return {
39
+ mes: new Intl.DateTimeFormat(locale, { ...utc, month: "long" }).format(new Date(Date.UTC(year, month - 1, 1))),
40
+ semana: Array.from({ length: 7 }, (_, i) => semana.format(new Date(Date.UTC(2026, 0, primeiro + i)))),
41
+ longo: new Intl.DateTimeFormat(locale, { ...utc, day: "numeric", month: "long" })
42
+ };
43
+ }, [locale, weekStartsOn, year, month]);
44
+ const total = new Date(Date.UTC(year, month, 0)).getUTCDate();
45
+ const inicio = (new Date(Date.UTC(year, month - 1, 1)).getUTCDay() - weekStartsOn + 7) % 7;
46
+ const diaHoje = today === void 0 ? hoje() : today;
47
+ const cabecalho = title === void 0 ? nomes.mes : title;
48
+ return /* @__PURE__ */ jsxs("section", { ...props, "aria-label": `${nomes.mes} ${year}`, className: `min-w-[16rem] rounded-lg border border-border bg-card p-3 ${className}`, children: [
49
+ cabecalho !== null && /* @__PURE__ */ jsx("h3", { className: "mb-2 text-center text-sm font-semibold capitalize text-foreground", children: cabecalho }),
50
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[repeat(7,minmax(2rem,1fr))] gap-0.5 text-center text-xs tabular-nums", children: [
51
+ nomes.semana.map((s, i) => /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "pb-1 font-medium uppercase text-muted-foreground", children: s }, i)),
52
+ Array.from({ length: inicio }, (_, i) => /* @__PURE__ */ jsx("span", { "aria-hidden": true }, `v${i}`)),
53
+ Array.from({ length: total }, (_, i) => {
54
+ const dia = i + 1;
55
+ const data = `${prefixo}${pad(dia)}`;
56
+ const coluna = (inicio + i) % 7;
57
+ const fimDeSemana = weekStartsOn === 1 ? coluna >= 5 : coluna === 0 || coluna === 6;
58
+ const marca = marcados.get(data);
59
+ const texto = marca?.labels.join(" · ");
60
+ return /* @__PURE__ */ jsx(
61
+ "span",
62
+ {
63
+ "data-date": data,
64
+ "data-tip": texto || void 0,
65
+ "aria-label": texto ? `${nomes.longo.format(new Date(Date.UTC(year, month - 1, dia)))}: ${texto}` : void 0,
66
+ className: [
67
+ "flex h-7 items-center justify-center rounded-md",
68
+ marca ? `${BADGE_TONE_CLASSES[marca.tone]} font-semibold` : fimDeSemana ? "text-muted-foreground" : "text-foreground",
69
+ data === diaHoje ? "ring-1 ring-primary dark:ring-sky-300" : "",
70
+ texto ? "cursor-help" : ""
71
+ ].join(" "),
72
+ children: dia
73
+ },
74
+ data
75
+ );
76
+ })
77
+ ] })
78
+ ] });
79
+ }
80
+ export {
81
+ MonthCalendar
82
+ };
83
+ //# sourceMappingURL=MonthCalendar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MonthCalendar.js","sources":["../../../../../../src/v1/components/data/MonthCalendar/MonthCalendar.tsx"],"sourcesContent":["import React from 'react'\r\nimport { BADGE_TONE_CLASSES, type BadgeTone } from '../../../primitives/display/Badge/Badge'\r\n\r\n/** Os mesmos tons do `Badge`: estados (`ok`, `risco`, `incumprido`, `neutral`, `accent`) e classificações. */\r\nexport type MonthCalendarTone = BadgeTone\r\n\r\nexport interface MonthCalendarDay {\r\n /** `YYYY-MM-DD`. Os dias de outros meses são ignorados, por isso pode passar-se a lista do ano inteiro. */\r\n date: string\r\n tone: MonthCalendarTone\r\n /** O que o dia tem (ex: \"Férias · Autorizado\"); vai para o `data-tip` e para os leitores de ecrã. */\r\n label?: string\r\n}\r\n\r\nexport interface MonthCalendarProps extends Omit<React.HTMLAttributes<HTMLElement>, 'children' | 'title'> {\r\n year: number\r\n /** 1 = janeiro … 12 = dezembro. */\r\n month: number\r\n /** Dias marcados. Vários na mesma data: fica a cor do primeiro e juntam-se as etiquetas. */\r\n days?: MonthCalendarDay[]\r\n /** Título por cima dos dias. Por omissão o nome do mês; `null` para nenhum. */\r\n title?: React.ReactNode | null\r\n /** Nomes do mês e dos dias da semana. */\r\n locale?: string\r\n /** 1 = segunda (Portugal), 0 = domingo. */\r\n weekStartsOn?: 0 | 1\r\n /** Dia com contorno. Por omissão o de hoje; `null` para nenhum. */\r\n today?: string | null\r\n}\r\n\r\nconst pad = (n: number) => String(n).padStart(2, '0')\r\nconst hoje = () => {\r\n const d = new Date()\r\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`\r\n}\r\n\r\n/**\r\n * Um mês, com os dias pintados por tom (ex: férias, feriados, ausências).\r\n * Para o ano inteiro, um por mês numa grelha (ver a story \"Ano inteiro\").\r\n *\r\n * Genérico: não sabe o que são férias. A app decide a cor e a etiqueta de cada\r\n * dia; o calendário só desenha. A legenda faz-se com `Badge`, nos mesmos tons.\r\n *\r\n * **Aceita os atributos HTML do `<section>`** (ex: `id`, `className`).\r\n */\r\nexport default function MonthCalendar({\r\n year,\r\n month,\r\n days = [],\r\n title,\r\n locale = 'pt-PT',\r\n weekStartsOn = 1,\r\n today,\r\n className = '',\r\n ...props\r\n}: MonthCalendarProps) {\r\n const prefixo = `${year}-${pad(month)}-`\r\n\r\n const marcados = React.useMemo(() => {\r\n const m = new Map<string, { tone: MonthCalendarTone; labels: string[] }>()\r\n for (const d of days) {\r\n if (!d.date.startsWith(prefixo)) continue\r\n const atual = m.get(d.date)\r\n if (atual) {\r\n if (d.label) atual.labels.push(d.label)\r\n } else {\r\n m.set(d.date, { tone: d.tone, labels: d.label ? [d.label] : [] })\r\n }\r\n }\r\n return m\r\n }, [days, prefixo])\r\n\r\n const nomes = React.useMemo(() => {\r\n const utc = { timeZone: 'UTC' } as const\r\n // 05/01/2026 é segunda; 04/01/2026 domingo.\r\n const primeiro = weekStartsOn === 1 ? 5 : 4\r\n const semana = new Intl.DateTimeFormat(locale, { ...utc, weekday: 'narrow' })\r\n return {\r\n mes: new Intl.DateTimeFormat(locale, { ...utc, month: 'long' }).format(new Date(Date.UTC(year, month - 1, 1))),\r\n semana: Array.from({ length: 7 }, (_, i) => semana.format(new Date(Date.UTC(2026, 0, primeiro + i)))),\r\n longo: new Intl.DateTimeFormat(locale, { ...utc, day: 'numeric', month: 'long' }),\r\n }\r\n }, [locale, weekStartsOn, year, month])\r\n\r\n const total = new Date(Date.UTC(year, month, 0)).getUTCDate()\r\n const inicio = (new Date(Date.UTC(year, month - 1, 1)).getUTCDay() - weekStartsOn + 7) % 7\r\n const diaHoje = today === undefined ? hoje() : today\r\n const cabecalho = title === undefined ? nomes.mes : title\r\n\r\n // Mínimo de 7 dias × 2rem: num sítio estreito o mês não encolhe ao ponto de os números se sobreporem.\r\n return (\r\n <section {...props} aria-label={`${nomes.mes} ${year}`} className={`min-w-[16rem] rounded-lg border border-border bg-card p-3 ${className}`}>\r\n {cabecalho !== null && (\r\n <h3 className=\"mb-2 text-center text-sm font-semibold capitalize text-foreground\">{cabecalho}</h3>\r\n )}\r\n <div className=\"grid grid-cols-[repeat(7,minmax(2rem,1fr))] gap-0.5 text-center text-xs tabular-nums\">\r\n {nomes.semana.map((s, i) => (\r\n <span key={i} aria-hidden className=\"pb-1 font-medium uppercase text-muted-foreground\">{s}</span>\r\n ))}\r\n {Array.from({ length: inicio }, (_, i) => <span key={`v${i}`} aria-hidden />)}\r\n {Array.from({ length: total }, (_, i) => {\r\n const dia = i + 1\r\n const data = `${prefixo}${pad(dia)}`\r\n const coluna = (inicio + i) % 7\r\n const fimDeSemana = weekStartsOn === 1 ? coluna >= 5 : coluna === 0 || coluna === 6\r\n const marca = marcados.get(data)\r\n const texto = marca?.labels.join(' · ')\r\n return (\r\n <span\r\n key={data}\r\n data-date={data}\r\n data-tip={texto || undefined}\r\n aria-label={texto ? `${nomes.longo.format(new Date(Date.UTC(year, month - 1, dia)))}: ${texto}` : undefined}\r\n className={[\r\n 'flex h-7 items-center justify-center rounded-md',\r\n marca ? `${BADGE_TONE_CLASSES[marca.tone]} font-semibold` : fimDeSemana ? 'text-muted-foreground' : 'text-foreground',\r\n data === diaHoje ? 'ring-1 ring-primary dark:ring-sky-300' : '',\r\n texto ? 'cursor-help' : '',\r\n ].join(' ')}\r\n >\r\n {dia}\r\n </span>\r\n )\r\n })}\r\n </div>\r\n </section>\r\n )\r\n}\r\n\r\nexport { MonthCalendar }\r\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- const TONES = {
2
+ const BADGE_TONE_CLASSES = {
3
3
  ok: "bg-ok-bg text-ok",
4
4
  risco: "bg-risco-bg text-risco",
5
5
  incumprido: "bg-incumprido-bg text-incumprido",
@@ -12,12 +12,13 @@ const TONES = {
12
12
  pink: "bg-pink-50 text-pink-700 dark:bg-pink-500/15 dark:text-pink-300"
13
13
  };
14
14
  function Badge({ tone = "neutral", dot = true, icon, children, className = "", ...props }) {
15
- return /* @__PURE__ */ jsxs("span", { ...props, className: `inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium whitespace-nowrap ${TONES[tone]} ${className}`, children: [
15
+ return /* @__PURE__ */ jsxs("span", { ...props, className: `inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium whitespace-nowrap ${BADGE_TONE_CLASSES[tone]} ${className}`, children: [
16
16
  icon ?? (dot && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "h-1.5 w-1.5 rounded-full bg-current" })),
17
17
  children
18
18
  ] });
19
19
  }
20
20
  export {
21
+ BADGE_TONE_CLASSES,
21
22
  Badge
22
23
  };
23
24
  //# sourceMappingURL=Badge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Badge.js","sources":["../../../../../../src/v1/primitives/display/Badge/Badge.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n/**\r\n * Estados com significado: `ok` (verde), `risco` (âmbar), `incumprido` (vermelho).\r\n * Sem estado: `neutral` (cinzento) e `accent` (azul).\r\n * Classificações (distinguir valores que não são bons nem maus, ex: origem, tipo,\r\n * área): `blue`, `violet`, `cyan`, `orange`, `pink` — sem significado, só para os\r\n * valores se reconhecerem de relance. Não usar verde/âmbar/vermelho para isto.\r\n */\r\nexport type BadgeTone = 'ok' | 'risco' | 'incumprido' | 'neutral' | 'accent' | 'blue' | 'violet' | 'cyan' | 'orange' | 'pink'\r\n\r\n/** Aceita os atributos do `<span>` (ex: `data-tip`, `title`, `aria-label`). */\r\nexport interface BadgeProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'> {\r\n tone?: BadgeTone\r\n /** Ponto à esquerda. Por omissão sim; sem ponto para etiquetas que não são um estado (ex: categoria). */\r\n dot?: boolean\r\n /** Ícone no lugar do ponto (ex: `<Eye size={11} />`). */\r\n icon?: React.ReactNode\r\n children: React.ReactNode\r\n}\r\n\r\nconst TONES: Record<BadgeTone, string> = {\r\n ok: 'bg-ok-bg text-ok',\r\n risco: 'bg-risco-bg text-risco',\r\n incumprido: 'bg-incumprido-bg text-incumprido',\r\n neutral: 'bg-neutral-bg text-neutral',\r\n accent: 'bg-proef-blue/10 text-proef-navy dark:bg-sky-500/15 dark:text-sky-300',\r\n blue: 'bg-blue-50 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300',\r\n violet: 'bg-violet-50 text-violet-700 dark:bg-violet-500/15 dark:text-violet-300',\r\n cyan: 'bg-cyan-50 text-cyan-700 dark:bg-cyan-500/15 dark:text-cyan-300',\r\n orange: 'bg-orange-50 text-orange-700 dark:bg-orange-500/15 dark:text-orange-300',\r\n pink: 'bg-pink-50 text-pink-700 dark:bg-pink-500/15 dark:text-pink-300',\r\n}\r\n\r\n/**\r\n * Etiqueta em pílula: o estado de um registo (ex: \"Publicado\", \"Em atraso\") ou\r\n * uma classificação (ex: a categoria, a origem). As cores dos estados vêm dos\r\n * tokens, por isso acompanham o tema escuro.\r\n */\r\nexport default function Badge({ tone = 'neutral', dot = true, icon, children, className = '', ...props }: BadgeProps) {\r\n return (\r\n <span {...props} className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium whitespace-nowrap ${TONES[tone]} ${className}`}>\r\n {icon ?? (dot && <span aria-hidden className=\"h-1.5 w-1.5 rounded-full bg-current\" />)}\r\n {children}\r\n </span>\r\n )\r\n}\r\n\r\nexport { Badge }\r\n"],"names":[],"mappings":";AAqBA,MAAM,QAAmC;AAAA,EACvC,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;AAOA,SAAwB,MAAM,EAAE,OAAO,WAAW,MAAM,MAAM,MAAM,UAAU,YAAY,IAAI,GAAG,MAAA,GAAqB;AACpH,SACE,qBAAC,QAAA,EAAM,GAAG,OAAO,WAAW,qGAAqG,MAAM,IAAI,CAAC,IAAI,SAAS,IACtJ,UAAA;AAAA,IAAA,SAAS,OAAO,oBAAC,QAAA,EAAK,eAAW,MAAC,WAAU,sCAAA,CAAsC;AAAA,IAClF;AAAA,EAAA,GACH;AAEJ;"}
1
+ {"version":3,"file":"Badge.js","sources":["../../../../../../src/v1/primitives/display/Badge/Badge.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n/**\r\n * Estados com significado: `ok` (verde), `risco` (âmbar), `incumprido` (vermelho).\r\n * Sem estado: `neutral` (cinzento) e `accent` (azul).\r\n * Classificações (distinguir valores que não são bons nem maus, ex: origem, tipo,\r\n * área): `blue`, `violet`, `cyan`, `orange`, `pink` — sem significado, só para os\r\n * valores se reconhecerem de relance. Não usar verde/âmbar/vermelho para isto.\r\n */\r\nexport type BadgeTone = 'ok' | 'risco' | 'incumprido' | 'neutral' | 'accent' | 'blue' | 'violet' | 'cyan' | 'orange' | 'pink'\r\n\r\n/** Aceita os atributos do `<span>` (ex: `data-tip`, `title`, `aria-label`). */\r\nexport interface BadgeProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'> {\r\n tone?: BadgeTone\r\n /** Ponto à esquerda. Por omissão sim; sem ponto para etiquetas que não são um estado (ex: categoria). */\r\n dot?: boolean\r\n /** Ícone no lugar do ponto (ex: `<Eye size={11} />`). */\r\n icon?: React.ReactNode\r\n children: React.ReactNode\r\n}\r\n\r\n/** Fundo e texto de cada tom; também os usa o `YearCalendar` para os dias marcados. */\r\nexport const BADGE_TONE_CLASSES: Record<BadgeTone, string> = {\r\n ok: 'bg-ok-bg text-ok',\r\n risco: 'bg-risco-bg text-risco',\r\n incumprido: 'bg-incumprido-bg text-incumprido',\r\n neutral: 'bg-neutral-bg text-neutral',\r\n accent: 'bg-proef-blue/10 text-proef-navy dark:bg-sky-500/15 dark:text-sky-300',\r\n blue: 'bg-blue-50 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300',\r\n violet: 'bg-violet-50 text-violet-700 dark:bg-violet-500/15 dark:text-violet-300',\r\n cyan: 'bg-cyan-50 text-cyan-700 dark:bg-cyan-500/15 dark:text-cyan-300',\r\n orange: 'bg-orange-50 text-orange-700 dark:bg-orange-500/15 dark:text-orange-300',\r\n pink: 'bg-pink-50 text-pink-700 dark:bg-pink-500/15 dark:text-pink-300',\r\n}\r\n\r\n/**\r\n * Etiqueta em pílula: o estado de um registo (ex: \"Publicado\", \"Em atraso\") ou\r\n * uma classificação (ex: a categoria, a origem). As cores dos estados vêm dos\r\n * tokens, por isso acompanham o tema escuro.\r\n */\r\nexport default function Badge({ tone = 'neutral', dot = true, icon, children, className = '', ...props }: BadgeProps) {\r\n return (\r\n <span {...props} className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium whitespace-nowrap ${BADGE_TONE_CLASSES[tone]} ${className}`}>\r\n {icon ?? (dot && <span aria-hidden className=\"h-1.5 w-1.5 rounded-full bg-current\" />)}\r\n {children}\r\n </span>\r\n )\r\n}\r\n\r\nexport { Badge }\r\n"],"names":[],"mappings":";AAsBO,MAAM,qBAAgD;AAAA,EAC3D,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;AAOA,SAAwB,MAAM,EAAE,OAAO,WAAW,MAAM,MAAM,MAAM,UAAU,YAAY,IAAI,GAAG,MAAA,GAAqB;AACpH,SACE,qBAAC,QAAA,EAAM,GAAG,OAAO,WAAW,qGAAqG,mBAAmB,IAAI,CAAC,IAAI,SAAS,IACnK,UAAA;AAAA,IAAA,SAAS,OAAO,oBAAC,QAAA,EAAK,eAAW,MAAC,WAAU,sCAAA,CAAsC;AAAA,IAClF;AAAA,EAAA,GACH;AAEJ;"}
@@ -0,0 +1,31 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ const VALUE_TONES = {
3
+ default: "text-foreground",
4
+ brand: "text-proef-blue dark:text-sky-300",
5
+ ok: "text-ok",
6
+ risco: "text-risco",
7
+ incumprido: "text-incumprido",
8
+ muted: "text-muted-foreground"
9
+ };
10
+ function DescriptionList({ items, dense = false, divided = false, className = "", ...props }) {
11
+ return /* @__PURE__ */ jsx("dl", { ...props, className: `min-w-0 text-control ${className}`, children: items.map((item, i) => /* @__PURE__ */ jsxs(
12
+ "div",
13
+ {
14
+ className: [
15
+ "flex items-baseline justify-between gap-4",
16
+ dense ? "py-1" : "py-2",
17
+ divided && i > 0 && !item.strong ? "border-t border-border" : "",
18
+ item.strong ? "mt-1 border-t border-foreground/20 font-semibold" : ""
19
+ ].join(" "),
20
+ children: [
21
+ /* @__PURE__ */ jsx("dt", { "data-tip": item.hint, className: `min-w-0 text-muted-foreground ${item.hint ? "cursor-help" : ""}`, children: item.label }),
22
+ /* @__PURE__ */ jsx("dd", { className: `m-0 shrink-0 text-right tabular-nums ${VALUE_TONES[item.tone ?? "default"]} ${item.strong ? "font-semibold" : "font-medium"}`, children: item.value })
23
+ ]
24
+ },
25
+ i
26
+ )) });
27
+ }
28
+ export {
29
+ DescriptionList
30
+ };
31
+ //# sourceMappingURL=DescriptionList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DescriptionList.js","sources":["../../../../../../src/v1/primitives/display/DescriptionList/DescriptionList.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n/** Cor do valor: os estados (`ok`, `risco`, `incumprido`) seguem os do `Badge`; `brand` é o azul da marca. */\r\nexport type DescriptionListTone = 'default' | 'brand' | 'ok' | 'risco' | 'incumprido' | 'muted'\r\n\r\nexport interface DescriptionListItem {\r\n label: React.ReactNode\r\n value: React.ReactNode\r\n tone?: DescriptionListTone\r\n /** Linha de total: valor a negrito, com uma linha por cima. */\r\n strong?: boolean\r\n /** Explicação ao passar o rato no rótulo (camada `data-tip` do `TooltipLayer`). */\r\n hint?: string\r\n}\r\n\r\nexport interface DescriptionListProps extends Omit<React.HTMLAttributes<HTMLDListElement>, 'children'> {\r\n items: DescriptionListItem[]\r\n /** Linhas mais baixas, para listas longas num painel lateral. */\r\n dense?: boolean\r\n /** Linha fina entre os itens. */\r\n divided?: boolean\r\n}\r\n\r\nconst VALUE_TONES: Record<DescriptionListTone, string> = {\r\n default: 'text-foreground',\r\n brand: 'text-proef-blue dark:text-sky-300',\r\n ok: 'text-ok',\r\n risco: 'text-risco',\r\n incumprido: 'text-incumprido',\r\n muted: 'text-muted-foreground',\r\n}\r\n\r\n/**\r\n * Lista de rótulo e valor, um por linha (ex: os totais de férias, os dados de um\r\n * registo). Para um número de destaque isolado usa-se o `Kpi`.\r\n *\r\n * Desenha um `<dl>`: os leitores de ecrã leem cada rótulo com o seu valor.\r\n *\r\n * **Aceita os atributos HTML do `<dl>`** (ex: `id`, `aria-label`).\r\n */\r\nexport default function DescriptionList({ items, dense = false, divided = false, className = '', ...props }: DescriptionListProps) {\r\n return (\r\n <dl {...props} className={`min-w-0 text-control ${className}`}>\r\n {items.map((item, i) => (\r\n <div\r\n key={i}\r\n className={[\r\n 'flex items-baseline justify-between gap-4',\r\n dense ? 'py-1' : 'py-2',\r\n divided && i > 0 && !item.strong ? 'border-t border-border' : '',\r\n item.strong ? 'mt-1 border-t border-foreground/20 font-semibold' : '',\r\n ].join(' ')}\r\n >\r\n <dt data-tip={item.hint} className={`min-w-0 text-muted-foreground ${item.hint ? 'cursor-help' : ''}`}>\r\n {item.label}\r\n </dt>\r\n <dd className={`m-0 shrink-0 text-right tabular-nums ${VALUE_TONES[item.tone ?? 'default']} ${item.strong ? 'font-semibold' : 'font-medium'}`}>\r\n {item.value}\r\n </dd>\r\n </div>\r\n ))}\r\n </dl>\r\n )\r\n}\r\n\r\nexport { DescriptionList }\r\n"],"names":[],"mappings":";AAuBA,MAAM,cAAmD;AAAA,EACvD,SAAS;AAAA,EACT,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,OAAO;AACT;AAUA,SAAwB,gBAAgB,EAAE,OAAO,QAAQ,OAAO,UAAU,OAAO,YAAY,IAAI,GAAG,SAA+B;AACjI,SACE,oBAAC,MAAA,EAAI,GAAG,OAAO,WAAW,wBAAwB,SAAS,IACxD,UAAA,MAAM,IAAI,CAAC,MAAM,MAChB;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,WAAW;AAAA,QACT;AAAA,QACA,QAAQ,SAAS;AAAA,QACjB,WAAW,IAAI,KAAK,CAAC,KAAK,SAAS,2BAA2B;AAAA,QAC9D,KAAK,SAAS,qDAAqD;AAAA,MAAA,EACnE,KAAK,GAAG;AAAA,MAEV,UAAA;AAAA,QAAA,oBAAC,MAAA,EAAG,YAAU,KAAK,MAAM,WAAW,iCAAiC,KAAK,OAAO,gBAAgB,EAAE,IAChG,UAAA,KAAK,OACR;AAAA,4BACC,MAAA,EAAG,WAAW,wCAAwC,YAAY,KAAK,QAAQ,SAAS,CAAC,IAAI,KAAK,SAAS,kBAAkB,aAAa,IACxI,eAAK,MAAA,CACR;AAAA,MAAA;AAAA,IAAA;AAAA,IAbK;AAAA,EAAA,CAeR,GACH;AAEJ;"}
@@ -0,0 +1,24 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ const SIZES = { sm: "h-4 w-4", md: "h-6 w-6", lg: "h-10 w-10" };
3
+ function Spinner({ size = "md", inline = false, label = "A carregar…", className = "", ...props }) {
4
+ return /* @__PURE__ */ jsxs(
5
+ "span",
6
+ {
7
+ ...props,
8
+ role: "status",
9
+ className: `${inline ? "inline-flex" : "flex w-full justify-center py-2 text-primary dark:text-sky-300"} items-center ${className}`,
10
+ children: [
11
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, className: `${SIZES[size]} animate-spin`, children: [
12
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9.5", stroke: "currentColor", strokeWidth: "3", className: "opacity-20" }),
13
+ /* @__PURE__ */ jsx("path", { d: "M21.5 12A9.5 9.5 0 0 0 12 2.5", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round" })
14
+ ] }),
15
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label })
16
+ ]
17
+ }
18
+ );
19
+ }
20
+ export {
21
+ Spinner,
22
+ Spinner as default
23
+ };
24
+ //# sourceMappingURL=Spinner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Spinner.js","sources":["../../../../../../src/v1/primitives/feedback/Spinner/Spinner.tsx"],"sourcesContent":["import React from 'react'\r\n\r\nexport type SpinnerSize = 'sm' | 'md' | 'lg'\r\n\r\nexport interface SpinnerProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'> {\r\n /** `sm` 16px (num botão ou numa linha), `md` 24px, `lg` 40px (página inteira). */\r\n size?: SpinnerSize\r\n /** Na linha do texto e na cor dele (ex: dentro de um botão). Sem isto ocupa a largura toda, ao centro, na cor da marca. */\r\n inline?: boolean\r\n /** Texto para leitores de ecrã. */\r\n label?: string\r\n}\r\n\r\nconst SIZES: Record<SpinnerSize, string> = { sm: 'h-4 w-4', md: 'h-6 w-6', lg: 'h-10 w-10' }\r\n\r\n/**\r\n * Indicador de \"a carregar\". Por omissão fica no lugar do bloco que ainda não\r\n * chegou (uma tabela, um calendário); com `inline`, dentro de um botão ou de uma frase.\r\n *\r\n * Substitui o `Spinner` legacy (que é este, com os valores por omissão).\r\n *\r\n * **Aceita os atributos HTML do `<span>`** (ex: `id`, `className`).\r\n */\r\nexport default function Spinner({ size = 'md', inline = false, label = 'A carregar…', className = '', ...props }: SpinnerProps) {\r\n return (\r\n <span\r\n {...props}\r\n role=\"status\"\r\n className={`${inline ? 'inline-flex' : 'flex w-full justify-center py-2 text-primary dark:text-sky-300'} items-center ${className}`}\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden className={`${SIZES[size]} animate-spin`}>\r\n <circle cx=\"12\" cy=\"12\" r=\"9.5\" stroke=\"currentColor\" strokeWidth=\"3\" className=\"opacity-20\" />\r\n <path d=\"M21.5 12A9.5 9.5 0 0 0 12 2.5\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" />\r\n </svg>\r\n <span className=\"sr-only\">{label}</span>\r\n </span>\r\n )\r\n}\r\n\r\nexport { Spinner }\r\n"],"names":[],"mappings":";AAaA,MAAM,QAAqC,EAAE,IAAI,WAAW,IAAI,WAAW,IAAI,YAAA;AAU/E,SAAwB,QAAQ,EAAE,OAAO,MAAM,SAAS,OAAO,QAAQ,eAAe,YAAY,IAAI,GAAG,MAAA,GAAuB;AAC9H,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAK;AAAA,MACL,WAAW,GAAG,SAAS,gBAAgB,gEAAgE,iBAAiB,SAAS;AAAA,MAEjI,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,SAAQ,aAAY,MAAK,QAAO,eAAW,MAAC,WAAW,GAAG,MAAM,IAAI,CAAC,iBACxE,UAAA;AAAA,UAAA,oBAAC,UAAA,EAAO,IAAG,MAAK,IAAG,MAAK,GAAE,OAAM,QAAO,gBAAe,aAAY,KAAI,WAAU,cAAa;AAAA,UAC7F,oBAAC,UAAK,GAAE,iCAAgC,QAAO,gBAAe,aAAY,KAAI,eAAc,QAAA,CAAQ;AAAA,QAAA,GACtG;AAAA,QACA,oBAAC,QAAA,EAAK,WAAU,WAAW,UAAA,MAAA,CAAM;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGvC;"}
@@ -87,7 +87,8 @@ function DateInput({
87
87
  if (typeof el.showPicker === "function") el.showPicker();
88
88
  else el.click();
89
89
  };
90
- return /* @__PURE__ */ jsxs("span", { className: `relative inline-flex w-full items-center ${className}`, children: [
90
+ const width = /(^|\s)w-/.test(className) ? "" : "w-full";
91
+ return /* @__PURE__ */ jsxs("span", { className: `relative inline-flex items-center ${width} ${className}`, children: [
91
92
  /* @__PURE__ */ jsx(
92
93
  "input",
93
94
  {
@@ -114,6 +115,7 @@ function DateInput({
114
115
  {
115
116
  type: "button",
116
117
  tabIndex: -1,
118
+ onMouseDown: (e) => e.preventDefault(),
117
119
  onClick: openCalendar,
118
120
  "aria-label": calendarLabel,
119
121
  "data-tip": calendarLabel,
@@ -1 +1 @@
1
- {"version":3,"file":"DateInput.js","sources":["../../../../../../src/v1/primitives/forms/DateInput/DateInput.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react'\r\nimport { fieldClasses, readOnlyClasses, useField } from '../field'\r\n\r\nexport interface DateInputProps {\r\n /** Data em ISO (`aaaa-mm-dd`) ou `''` (vazio). Controlado. */\r\n value?: string\r\n /** Valor inicial, sem controlar (ISO). */\r\n defaultValue?: string\r\n /** Recebe só datas completas e válidas, em ISO; `''` quando se apaga. Enquanto se escreve, não é chamado. */\r\n onChange?: (value: string) => void\r\n /** Ao terminar a edição: sair do campo ou escolher no calendário. */\r\n onBlur?: () => void\r\n onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void\r\n /** Limites, em ISO (só no calendário). */\r\n min?: string\r\n max?: string\r\n disabled?: boolean\r\n readOnly?: boolean\r\n required?: boolean\r\n autoFocus?: boolean\r\n id?: string\r\n /** Marca o campo a vermelho (ex: obrigatório por preencher ao submeter). */\r\n invalid?: boolean\r\n placeholder?: string\r\n /** Nome do campo para leitores de ecrã, quando não há etiqueta. */\r\n 'aria-label'?: string\r\n /** Texto do botão do calendário. */\r\n calendarLabel?: string\r\n /** Classes da caixa à volta (ex: largura). */\r\n className?: string\r\n}\r\n\r\nconst isoToBr = (iso: string): string => {\r\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(iso ?? '')\r\n return m ? `${m[3]}/${m[2]}/${m[1]}` : ''\r\n}\r\n\r\nconst brToIso = (br: string): string => {\r\n const m = /^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/.exec(br.trim())\r\n if (!m) return ''\r\n const [, dd, mm, yyyy] = m\r\n const d = new Date(`${yyyy}-${mm}-${dd}T00:00:00`)\r\n // Rejeita 31/02 e afins: o Date normaliza para 03/03 em vez de falhar.\r\n if (Number.isNaN(d.getTime()) || d.getDate() !== Number(dd) || d.getMonth() + 1 !== Number(mm)) return ''\r\n return `${yyyy}-${mm}-${dd}`\r\n}\r\n\r\n/** Mascara o que se escreve, deixando só dígitos e pondo as barras no sítio. */\r\nconst mask = (raw: string): string => {\r\n const d = raw.replace(/\\D/g, '').slice(0, 8)\r\n if (d.length <= 2) return d\r\n if (d.length <= 4) return `${d.slice(0, 2)}/${d.slice(2)}`\r\n return `${d.slice(0, 2)}/${d.slice(2, 4)}/${d.slice(4)}`\r\n}\r\n\r\nfunction CalendarIcon() {\r\n return (\r\n <svg aria-hidden viewBox=\"0 0 24 24\" width={13} height={13} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" />\r\n <path d=\"M16 2v4M8 2v4M3 10h18\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Campo de data, sempre em **dd/mm/aaaa**.\r\n *\r\n * O `<input type=\"date\">` do browser mostra a data no idioma do BROWSER, não no\r\n * da página (num Chrome em inglês aparece mm/dd/aaaa), e não há atributo que o\r\n * force. Aqui o texto é escrito pela biblioteca, com máscara; o calendário\r\n * continua a ser o nativo (acessível e traduzido), aberto pelo ícone.\r\n *\r\n * O valor entra e sai em ISO (`aaaa-mm-dd`). Dentro de um\r\n * [FormField](?path=/docs/v1-components-forms-formfield--docs) fica ligado à\r\n * etiqueta automaticamente.\r\n */\r\nexport default function DateInput({\r\n value,\r\n defaultValue,\r\n onChange,\r\n onBlur,\r\n onKeyDown,\r\n min,\r\n max,\r\n disabled,\r\n readOnly,\r\n required,\r\n autoFocus,\r\n id,\r\n invalid,\r\n placeholder = 'dd/mm/aaaa',\r\n 'aria-label': ariaLabel,\r\n calendarLabel = 'Abrir calendário',\r\n className = '',\r\n}: DateInputProps) {\r\n const field = useField()\r\n const controlled = value !== undefined\r\n const [iso, setIso] = useState(value ?? defaultValue ?? '')\r\n const [text, setText] = useState(isoToBr(value ?? defaultValue ?? ''))\r\n const nativeRef = useRef<HTMLInputElement>(null)\r\n\r\n // Segue o valor de fora, exceto enquanto o campo tem uma data a meio: senão\r\n // um re-render durante a escrita apagava os dígitos já introduzidos.\r\n useEffect(() => {\r\n if (!controlled) return\r\n const outside = value ?? ''\r\n if (outside === brToIso(text)) return\r\n setIso(outside)\r\n setText(isoToBr(outside))\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [value, controlled])\r\n\r\n // Escolher no calendário conta como terminar a edição (quem grava está quase\r\n // sempre no `onBlur`). O aviso espera que o valor novo volte em `value`:\r\n // chamado logo, o `onBlur` de fora ainda lia o valor anterior.\r\n const onBlurRef = useRef(onBlur)\r\n useEffect(() => {\r\n onBlurRef.current = onBlur\r\n })\r\n const pendingCommit = useRef(false)\r\n useEffect(() => {\r\n if (!pendingCommit.current) return\r\n pendingCommit.current = false\r\n onBlurRef.current?.()\r\n }, [value])\r\n\r\n const emit = (next: string) => {\r\n setIso(next)\r\n onChange?.(next)\r\n }\r\n\r\n const type = (raw: string) => {\r\n const masked = mask(raw)\r\n setText(masked)\r\n if (masked === '') return emit('')\r\n const next = brToIso(masked)\r\n if (next) emit(next)\r\n }\r\n\r\n // Ao sair, uma data incompleta ou impossível volta ao último valor bom.\r\n const leave = () => {\r\n if (text !== '' && !brToIso(text)) setText(isoToBr(iso))\r\n onBlur?.()\r\n }\r\n\r\n const openCalendar = () => {\r\n const el = nativeRef.current\r\n if (!el || disabled || readOnly) return\r\n // showPicker() é o caminho normal; sem ele (browsers antigos) resta o clique.\r\n if (typeof (el as HTMLInputElement & { showPicker?: () => void }).showPicker === 'function') el.showPicker()\r\n else el.click()\r\n }\r\n\r\n return (\r\n <span className={`relative inline-flex w-full items-center ${className}`}>\r\n <input\r\n id={id ?? field.id}\r\n type=\"text\"\r\n inputMode=\"numeric\"\r\n autoComplete=\"off\"\r\n aria-label={ariaLabel}\r\n aria-invalid={invalid || undefined}\r\n placeholder={placeholder}\r\n value={text}\r\n onChange={(e) => type(e.target.value)}\r\n onBlur={leave}\r\n onKeyDown={onKeyDown}\r\n autoFocus={autoFocus}\r\n disabled={disabled}\r\n readOnly={readOnly}\r\n required={required ?? field.required}\r\n className={`h-control ${fieldClasses} ${readOnlyClasses} pr-8 tabular-nums`}\r\n />\r\n {!disabled && !readOnly && (\r\n <button\r\n type=\"button\"\r\n tabIndex={-1}\r\n onClick={openCalendar}\r\n aria-label={calendarLabel}\r\n data-tip={calendarLabel}\r\n className=\"absolute right-1.5 flex h-6 w-6 cursor-pointer items-center justify-center rounded border-0 bg-transparent p-0 text-muted-foreground hover:text-foreground\"\r\n >\r\n <CalendarIcon />\r\n </button>\r\n )}\r\n {/* O nativo só serve de calendário: nunca se vê, por isso o idioma dele não\r\n chega ao ecrã. `sr-only` em vez de display:none — o showPicker() recusa\r\n abrir num campo escondido. */}\r\n <input\r\n ref={nativeRef}\r\n type=\"date\"\r\n tabIndex={-1}\r\n aria-hidden\r\n value={iso}\r\n min={min}\r\n max={max}\r\n onChange={(e) => {\r\n const v = e.target.value\r\n setText(isoToBr(v))\r\n emit(v)\r\n // Sem `value` de fora não há nada por que esperar.\r\n if (controlled) pendingCommit.current = true\r\n else onBlurRef.current?.()\r\n }}\r\n className=\"sr-only absolute right-1.5 bottom-0 h-0 w-0 border-0 p-0\"\r\n />\r\n </span>\r\n )\r\n}\r\n\r\nexport { DateInput }\r\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DateInput.js","sources":["../../../../../../src/v1/primitives/forms/DateInput/DateInput.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react'\r\nimport { fieldClasses, readOnlyClasses, useField } from '../field'\r\n\r\nexport interface DateInputProps {\r\n /** Data em ISO (`aaaa-mm-dd`) ou `''` (vazio). Controlado. */\r\n value?: string\r\n /** Valor inicial, sem controlar (ISO). */\r\n defaultValue?: string\r\n /** Recebe só datas completas e válidas, em ISO; `''` quando se apaga. Enquanto se escreve, não é chamado. */\r\n onChange?: (value: string) => void\r\n /** Ao terminar a edição: sair do campo ou escolher no calendário. */\r\n onBlur?: () => void\r\n onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void\r\n /** Limites, em ISO (só no calendário). */\r\n min?: string\r\n max?: string\r\n disabled?: boolean\r\n readOnly?: boolean\r\n required?: boolean\r\n autoFocus?: boolean\r\n id?: string\r\n /** Marca o campo a vermelho (ex: obrigatório por preencher ao submeter). */\r\n invalid?: boolean\r\n placeholder?: string\r\n /** Nome do campo para leitores de ecrã, quando não há etiqueta. */\r\n 'aria-label'?: string\r\n /** Texto do botão do calendário. */\r\n calendarLabel?: string\r\n /** Classes da caixa à volta (ex: largura). */\r\n className?: string\r\n}\r\n\r\nconst isoToBr = (iso: string): string => {\r\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(iso ?? '')\r\n return m ? `${m[3]}/${m[2]}/${m[1]}` : ''\r\n}\r\n\r\nconst brToIso = (br: string): string => {\r\n const m = /^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/.exec(br.trim())\r\n if (!m) return ''\r\n const [, dd, mm, yyyy] = m\r\n const d = new Date(`${yyyy}-${mm}-${dd}T00:00:00`)\r\n // Rejeita 31/02 e afins: o Date normaliza para 03/03 em vez de falhar.\r\n if (Number.isNaN(d.getTime()) || d.getDate() !== Number(dd) || d.getMonth() + 1 !== Number(mm)) return ''\r\n return `${yyyy}-${mm}-${dd}`\r\n}\r\n\r\n/** Mascara o que se escreve, deixando só dígitos e pondo as barras no sítio. */\r\nconst mask = (raw: string): string => {\r\n const d = raw.replace(/\\D/g, '').slice(0, 8)\r\n if (d.length <= 2) return d\r\n if (d.length <= 4) return `${d.slice(0, 2)}/${d.slice(2)}`\r\n return `${d.slice(0, 2)}/${d.slice(2, 4)}/${d.slice(4)}`\r\n}\r\n\r\nfunction CalendarIcon() {\r\n return (\r\n <svg aria-hidden viewBox=\"0 0 24 24\" width={13} height={13} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" />\r\n <path d=\"M16 2v4M8 2v4M3 10h18\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Campo de data, sempre em **dd/mm/aaaa**.\r\n *\r\n * O `<input type=\"date\">` do browser mostra a data no idioma do BROWSER, não no\r\n * da página (num Chrome em inglês aparece mm/dd/aaaa), e não há atributo que o\r\n * force. Aqui o texto é escrito pela biblioteca, com máscara; o calendário\r\n * continua a ser o nativo (acessível e traduzido), aberto pelo ícone.\r\n *\r\n * O valor entra e sai em ISO (`aaaa-mm-dd`). Dentro de um\r\n * [FormField](?path=/docs/v1-components-forms-formfield--docs) fica ligado à\r\n * etiqueta automaticamente.\r\n */\r\nexport default function DateInput({\r\n value,\r\n defaultValue,\r\n onChange,\r\n onBlur,\r\n onKeyDown,\r\n min,\r\n max,\r\n disabled,\r\n readOnly,\r\n required,\r\n autoFocus,\r\n id,\r\n invalid,\r\n placeholder = 'dd/mm/aaaa',\r\n 'aria-label': ariaLabel,\r\n calendarLabel = 'Abrir calendário',\r\n className = '',\r\n}: DateInputProps) {\r\n const field = useField()\r\n const controlled = value !== undefined\r\n const [iso, setIso] = useState(value ?? defaultValue ?? '')\r\n const [text, setText] = useState(isoToBr(value ?? defaultValue ?? ''))\r\n const nativeRef = useRef<HTMLInputElement>(null)\r\n\r\n // Segue o valor de fora, exceto enquanto o campo tem uma data a meio: senão\r\n // um re-render durante a escrita apagava os dígitos já introduzidos.\r\n useEffect(() => {\r\n if (!controlled) return\r\n const outside = value ?? ''\r\n if (outside === brToIso(text)) return\r\n setIso(outside)\r\n setText(isoToBr(outside))\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [value, controlled])\r\n\r\n // Escolher no calendário conta como terminar a edição (quem grava está quase\r\n // sempre no `onBlur`). O aviso espera que o valor novo volte em `value`:\r\n // chamado logo, o `onBlur` de fora ainda lia o valor anterior.\r\n const onBlurRef = useRef(onBlur)\r\n useEffect(() => {\r\n onBlurRef.current = onBlur\r\n })\r\n const pendingCommit = useRef(false)\r\n useEffect(() => {\r\n if (!pendingCommit.current) return\r\n pendingCommit.current = false\r\n onBlurRef.current?.()\r\n }, [value])\r\n\r\n const emit = (next: string) => {\r\n setIso(next)\r\n onChange?.(next)\r\n }\r\n\r\n const type = (raw: string) => {\r\n const masked = mask(raw)\r\n setText(masked)\r\n if (masked === '') return emit('')\r\n const next = brToIso(masked)\r\n if (next) emit(next)\r\n }\r\n\r\n // Ao sair, uma data incompleta ou impossível volta ao último valor bom.\r\n const leave = () => {\r\n if (text !== '' && !brToIso(text)) setText(isoToBr(iso))\r\n onBlur?.()\r\n }\r\n\r\n const openCalendar = () => {\r\n const el = nativeRef.current\r\n if (!el || disabled || readOnly) return\r\n // showPicker() é o caminho normal; sem ele (browsers antigos) resta o clique.\r\n if (typeof (el as HTMLInputElement & { showPicker?: () => void }).showPicker === 'function') el.showPicker()\r\n else el.click()\r\n }\r\n\r\n // A largura pedida em `className` (ex: `w-36`) ganha; sem ela, ocupa a linha.\r\n // As duas juntas não davam: no CSS o `w-full` vinha depois e ganhava sempre.\r\n const width = /(^|\\s)w-/.test(className) ? '' : 'w-full'\r\n\r\n return (\r\n <span className={`relative inline-flex items-center ${width} ${className}`}>\r\n <input\r\n id={id ?? field.id}\r\n type=\"text\"\r\n inputMode=\"numeric\"\r\n autoComplete=\"off\"\r\n aria-label={ariaLabel}\r\n aria-invalid={invalid || undefined}\r\n placeholder={placeholder}\r\n value={text}\r\n onChange={(e) => type(e.target.value)}\r\n onBlur={leave}\r\n onKeyDown={onKeyDown}\r\n autoFocus={autoFocus}\r\n disabled={disabled}\r\n readOnly={readOnly}\r\n required={required ?? field.required}\r\n className={`h-control ${fieldClasses} ${readOnlyClasses} pr-8 tabular-nums`}\r\n />\r\n {!disabled && !readOnly && (\r\n <button\r\n type=\"button\"\r\n tabIndex={-1}\r\n // O clique não tira o foco ao texto: quem fecha o campo no `onBlur`\r\n // (edição no sítio) fechava-o antes de o calendário abrir.\r\n onMouseDown={(e) => e.preventDefault()}\r\n onClick={openCalendar}\r\n aria-label={calendarLabel}\r\n data-tip={calendarLabel}\r\n className=\"absolute right-1.5 flex h-6 w-6 cursor-pointer items-center justify-center rounded border-0 bg-transparent p-0 text-muted-foreground hover:text-foreground\"\r\n >\r\n <CalendarIcon />\r\n </button>\r\n )}\r\n {/* O nativo só serve de calendário: nunca se vê, por isso o idioma dele não\r\n chega ao ecrã. `sr-only` em vez de display:none — o showPicker() recusa\r\n abrir num campo escondido. */}\r\n <input\r\n ref={nativeRef}\r\n type=\"date\"\r\n tabIndex={-1}\r\n aria-hidden\r\n value={iso}\r\n min={min}\r\n max={max}\r\n onChange={(e) => {\r\n const v = e.target.value\r\n setText(isoToBr(v))\r\n emit(v)\r\n // Sem `value` de fora não há nada por que esperar.\r\n if (controlled) pendingCommit.current = true\r\n else onBlurRef.current?.()\r\n }}\r\n className=\"sr-only absolute right-1.5 bottom-0 h-0 w-0 border-0 p-0\"\r\n />\r\n </span>\r\n )\r\n}\r\n\r\nexport { DateInput }\r\n"],"names":[],"mappings":"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"}