@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.
- package/dist/esm/index.js +34 -32
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/legacy/components/layout/Spinner/Spinner.js +1 -15
- package/dist/esm/legacy/components/layout/Spinner/Spinner.js.map +1 -1
- package/dist/esm/legacy/components/molecules/ConfirmModal/ConfirmModal.js +1 -1
- package/dist/esm/legacy/components/molecules/MultiTagPicker/MultiTagPicker.js +1 -1
- package/dist/esm/legacy/components/organisms/LazyLoader/LazyLoader.js +1 -1
- package/dist/esm/legacy/components/organisms/Selector/Selector.js +1 -1
- package/dist/esm/v1/components/data/MonthCalendar/MonthCalendar.js +83 -0
- package/dist/esm/v1/components/data/MonthCalendar/MonthCalendar.js.map +1 -0
- package/dist/esm/v1/primitives/display/Badge/Badge.js +3 -2
- package/dist/esm/v1/primitives/display/Badge/Badge.js.map +1 -1
- package/dist/esm/v1/primitives/display/DescriptionList/DescriptionList.js +31 -0
- package/dist/esm/v1/primitives/display/DescriptionList/DescriptionList.js.map +1 -0
- package/dist/esm/v1/primitives/feedback/Spinner/Spinner.js +24 -0
- package/dist/esm/v1/primitives/feedback/Spinner/Spinner.js.map +1 -0
- package/dist/esm/v1/primitives/forms/DateInput/DateInput.js +3 -1
- package/dist/esm/v1/primitives/forms/DateInput/DateInput.js.map +1 -1
- package/dist/style.components.css +1 -1
- package/dist/style.css +1 -1
- package/dist/style.layered.css +1 -1
- package/dist/types/legacy/components/layout/Spinner/Spinner.d.ts +1 -4
- package/dist/types/v1/components/data/MonthCalendar/MonthCalendar.d.ts +37 -0
- package/dist/types/v1/index.d.ts +6 -0
- package/dist/types/v1/primitives/display/Badge/Badge.d.ts +2 -0
- package/dist/types/v1/primitives/display/DescriptionList/DescriptionList.d.ts +29 -0
- package/dist/types/v1/primitives/feedback/Spinner/Spinner.d.ts +20 -0
- package/package.json +1 -1
- 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/
|
|
75
|
-
import { default as default71 } from "./legacy/components/
|
|
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
|
|
79
|
-
import { default as
|
|
80
|
-
import { default as
|
|
81
|
-
import { default as
|
|
82
|
-
import { default as
|
|
83
|
-
import { default as
|
|
84
|
-
import { default as
|
|
85
|
-
import { default as
|
|
86
|
-
import { default as
|
|
87
|
-
import { default as
|
|
88
|
-
import { default as
|
|
89
|
-
import { default as
|
|
90
|
-
import { default as
|
|
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
|
|
284
|
-
default71 as
|
|
285
|
-
default72 as StatusMatrix,
|
|
286
|
+
default70 as Stack,
|
|
287
|
+
default71 as StatusMatrix,
|
|
286
288
|
Steps,
|
|
287
289
|
StrikethroughIcon,
|
|
288
290
|
SunIcon,
|
|
289
|
-
|
|
291
|
+
default72 as Switch,
|
|
290
292
|
THEME_STORAGE_KEY,
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
305
|
+
default84 as UserAvatar,
|
|
304
306
|
WarningIcon,
|
|
305
307
|
applyColumnLayout,
|
|
306
308
|
applyTheme,
|
package/dist/esm/index.js.map
CHANGED
|
@@ -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 {
|
|
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":[
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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
|
|
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 ${
|
|
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\
|
|
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
|
-
|
|
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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"}
|