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