@proef/common-ui 2.2.3 → 2.2.5
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 +30 -24
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/legacy/components/atoms/Switch/CustomSwitch.js +5 -22
- package/dist/esm/legacy/components/atoms/Switch/CustomSwitch.js.map +1 -1
- package/dist/esm/legacy/components/ui/Ui.js +4 -6
- package/dist/esm/legacy/components/ui/Ui.js.map +1 -1
- package/dist/esm/v1/primitives/display/AvatarStack/AvatarStack.js +28 -0
- package/dist/esm/v1/primitives/display/AvatarStack/AvatarStack.js.map +1 -0
- package/dist/esm/v1/primitives/display/PeopleFilter/PeopleFilter.js +59 -0
- package/dist/esm/v1/primitives/display/PeopleFilter/PeopleFilter.js.map +1 -0
- package/dist/esm/v1/primitives/forms/Switch/Switch.js +36 -0
- package/dist/esm/v1/primitives/forms/Switch/Switch.js.map +1 -0
- package/dist/esm/v1/primitives/forms/field.js +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/atoms/Switch/CustomSwitch.d.ts +5 -2
- package/dist/types/legacy/components/ui/Ui.d.ts +4 -3
- package/dist/types/v1/index.d.ts +6 -0
- package/dist/types/v1/primitives/display/AvatarStack/AvatarStack.d.ts +32 -0
- package/dist/types/v1/primitives/display/PeopleFilter/PeopleFilter.d.ts +36 -0
- package/dist/types/v1/primitives/forms/Switch/Switch.d.ts +22 -0
- package/package.json +1 -1
- package/dist/esm/legacy/components/atoms/Switch/CustomSwitch.css +0 -36
package/dist/esm/index.js
CHANGED
|
@@ -73,22 +73,24 @@ import { default as default69 } from "./legacy/components/molecules/Sidebar/Side
|
|
|
73
73
|
import { default as default70 } from "./legacy/components/layout/Spinner/Spinner.js";
|
|
74
74
|
import { default as default71 } from "./legacy/components/atoms/Stack/Stack.js";
|
|
75
75
|
import { default as default72 } from "./legacy/components/organisms/StatusMatrix/StatusMatrix.js";
|
|
76
|
-
import { default as default73 } from "./
|
|
77
|
-
import { default as default74 } from "./
|
|
78
|
-
import { default as default75 } from "./v1/
|
|
79
|
-
import { default as default76 } from "./v1/primitives/
|
|
80
|
-
import { default as default77 } from "./v1/primitives/forms/
|
|
81
|
-
import { default as default78 } from "./
|
|
82
|
-
import { default as default79
|
|
83
|
-
import { default as default80 } from "./v1/components/
|
|
84
|
-
import { default as default81 } from "./v1/components/overlays/Tooltip/
|
|
85
|
-
import { default as default82 } from "./
|
|
86
|
-
import { default as default83 } from "./legacy/components/layout/
|
|
87
|
-
import { default as default84 } from "./legacy/components/
|
|
76
|
+
import { default as default73 } from "./v1/primitives/forms/Switch/Switch.js";
|
|
77
|
+
import { default as default74 } from "./legacy/components/atoms/Button/variants/TabButton.js";
|
|
78
|
+
import { default as default75 } from "./v1/components/tables/Table/Table.js";
|
|
79
|
+
import { default as default76 } from "./v1/primitives/navigation/Tabs/Tabs.js";
|
|
80
|
+
import { default as default77 } from "./v1/primitives/forms/TextArea/TextArea.js";
|
|
81
|
+
import { default as default78 } from "./v1/primitives/forms/TextInput/TextInput.js";
|
|
82
|
+
import { default as default79 } from "./legacy/components/ui/TextoAuto.js";
|
|
83
|
+
import { default as default80, useToast } from "./v1/components/feedback/Toast/Toast.js";
|
|
84
|
+
import { default as default81 } from "./v1/components/overlays/Tooltip/Tooltip.js";
|
|
85
|
+
import { default as default82 } from "./v1/components/overlays/Tooltip/TooltipLayer.js";
|
|
86
|
+
import { default as default83 } from "./legacy/components/layout/TopBar/TopBar.js";
|
|
87
|
+
import { default as default84 } from "./legacy/components/layout/TopNavLayout/TopNavLayout.js";
|
|
88
|
+
import { default as default85 } from "./legacy/components/molecules/UserAvatar/UserAvatar.js";
|
|
88
89
|
import { Anel, Barras, BarrasEmpilhadas, BarrasHorizontais, Bolhas, Linha, Linhas, PALETA, cor } from "./legacy/components/charts/Charts.js";
|
|
89
90
|
import { ApiProvider } from "./legacy/contexts/apiContext.js";
|
|
90
91
|
import { AuthProvider, useAuth } from "./legacy/contexts/authContext.js";
|
|
91
92
|
import { Avatar, initials } from "./v1/primitives/display/Avatar/Avatar.js";
|
|
93
|
+
import { AvatarStack } from "./v1/primitives/display/AvatarStack/AvatarStack.js";
|
|
92
94
|
import { AvisosProvider, useAvisos } from "./legacy/components/ui/Avisos.js";
|
|
93
95
|
import { Badge } from "./v1/primitives/display/Badge/Badge.js";
|
|
94
96
|
import { BadgeSla, BarraFiltros, Btn, BtnGroup, Campo, CampoInterruptor, CampoPesquisa, CampoSelect, ChipEstadoInterno, Estado, LinhaAlerta, ListaCampos, Modal, SectionGroup } from "./legacy/components/ui/Ui.js";
|
|
@@ -103,6 +105,7 @@ import { Kpi } from "./v1/primitives/display/Kpi/Kpi.js";
|
|
|
103
105
|
import { LazyLoader } from "./legacy/components/organisms/LazyLoader/LazyLoader.js";
|
|
104
106
|
import { Notice } from "./v1/primitives/display/Notice/Notice.js";
|
|
105
107
|
import { Pagination } from "./v1/components/data/Pagination/Pagination.js";
|
|
108
|
+
import { PeopleFilter } from "./v1/primitives/display/PeopleFilter/PeopleFilter.js";
|
|
106
109
|
import { Person } from "./v1/primitives/display/Person/Person.js";
|
|
107
110
|
import { Selector } from "./legacy/components/organisms/Selector/Selector.js";
|
|
108
111
|
import { SocketProvider, useSocket } from "./legacy/contexts/socketContext.js";
|
|
@@ -123,6 +126,7 @@ export {
|
|
|
123
126
|
default6 as AppShell,
|
|
124
127
|
AuthProvider,
|
|
125
128
|
Avatar,
|
|
129
|
+
AvatarStack,
|
|
126
130
|
AvisosProvider,
|
|
127
131
|
Badge,
|
|
128
132
|
BadgeSla,
|
|
@@ -239,6 +243,7 @@ export {
|
|
|
239
243
|
PanelOpenIcon,
|
|
240
244
|
default57 as Passos,
|
|
241
245
|
default58 as PdfPreview,
|
|
246
|
+
PeopleFilter,
|
|
242
247
|
Person,
|
|
243
248
|
default59 as PesquisaRapida,
|
|
244
249
|
default60 as PhotoAvatar,
|
|
@@ -270,20 +275,21 @@ export {
|
|
|
270
275
|
default72 as StatusMatrix,
|
|
271
276
|
StrikethroughIcon,
|
|
272
277
|
SunIcon,
|
|
278
|
+
default73 as Switch,
|
|
273
279
|
THEME_STORAGE_KEY,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
280
|
+
default74 as TabButton,
|
|
281
|
+
default75 as Table,
|
|
282
|
+
default76 as Tabs,
|
|
283
|
+
default77 as TextArea,
|
|
284
|
+
default78 as TextInput,
|
|
285
|
+
default79 as TextoAuto,
|
|
286
|
+
default80 as ToastProvider,
|
|
287
|
+
default81 as Tooltip,
|
|
288
|
+
default82 as TooltipLayer,
|
|
289
|
+
default83 as TopBar,
|
|
290
|
+
default84 as TopNavLayout,
|
|
285
291
|
UnderlineIcon,
|
|
286
|
-
|
|
292
|
+
default85 as UserAvatar,
|
|
287
293
|
WarningIcon,
|
|
288
294
|
applyColumnLayout,
|
|
289
295
|
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,26 +1,9 @@
|
|
|
1
1
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
-
import Switch from "
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
onChange,
|
|
8
|
-
theme
|
|
9
|
-
}) => {
|
|
10
|
-
return /* @__PURE__ */ jsxs("div", { className: `cui-container cui-switch-size-${theme.size}`, children: [
|
|
11
|
-
/* @__PURE__ */ jsx("label", { className: "cui-label", children: label }),
|
|
12
|
-
/* @__PURE__ */ jsx(
|
|
13
|
-
Switch,
|
|
14
|
-
{
|
|
15
|
-
onChange,
|
|
16
|
-
checked: activated,
|
|
17
|
-
offColor: "#888",
|
|
18
|
-
onColor: "#010060",
|
|
19
|
-
uncheckedIcon: false,
|
|
20
|
-
checkedIcon: false,
|
|
21
|
-
className: "cui-switch"
|
|
22
|
-
}
|
|
23
|
-
)
|
|
2
|
+
import Switch from "../../../../v1/primitives/forms/Switch/Switch.js";
|
|
3
|
+
const CustomSwitch = ({ label, activated, onChange }) => {
|
|
4
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 text-control text-foreground", children: [
|
|
5
|
+
/* @__PURE__ */ jsx("span", { children: label }),
|
|
6
|
+
/* @__PURE__ */ jsx(Switch, { checked: activated, onChange: () => onChange(), "aria-label": label })
|
|
24
7
|
] });
|
|
25
8
|
};
|
|
26
9
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CustomSwitch.js","sources":["../../../../../../src/legacy/components/atoms/Switch/CustomSwitch.tsx"],"sourcesContent":["import React from 'react'\r\nimport
|
|
1
|
+
{"version":3,"file":"CustomSwitch.js","sources":["../../../../../../src/legacy/components/atoms/Switch/CustomSwitch.tsx"],"sourcesContent":["import React from 'react'\r\nimport { CustomSwitchTheme } from '../../../types/themes/CustomSwitchThemes'\r\nimport Switch from '../../../../v1/primitives/forms/Switch/Switch'\r\n\r\ntype CustomSwitchProps = {\r\n label: string\r\n activated: boolean\r\n onChange: () => void\r\n /** Já não muda nada: o interruptor tem um tamanho só (o do V1). */\r\n theme: CustomSwitchTheme\r\n}\r\n\r\n/**\r\n * Adaptador: mostra o `Switch` do V1, com o texto antes.\r\n * @deprecated Legacy — não usar em código novo. Usar o `Switch` (V1).\r\n */\r\nconst CustomSwitch: React.FC<CustomSwitchProps> = ({ label, activated, onChange }) => {\r\n return (\r\n <span className=\"inline-flex items-center gap-2 text-control text-foreground\">\r\n <span>{label}</span>\r\n <Switch checked={activated} onChange={() => onChange()} aria-label={label} />\r\n </span>\r\n )\r\n}\r\n\r\nexport default CustomSwitch\r\n"],"names":[],"mappings":";;AAgBA,MAAM,eAA4C,CAAC,EAAE,OAAO,WAAW,eAAe;AACpF,SACE,qBAAC,QAAA,EAAK,WAAU,+DACd,UAAA;AAAA,IAAA,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,IACb,oBAAC,UAAO,SAAS,WAAW,UAAU,MAAM,SAAA,GAAY,cAAY,MAAA,CAAO;AAAA,EAAA,GAC7E;AAEJ;"}
|
|
@@ -2,6 +2,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import Escolha from "./Escolha.js";
|
|
3
3
|
import SegmentedControl from "../../../v1/primitives/buttons/SegmentedControl/SegmentedControl.js";
|
|
4
4
|
import Button from "../../../v1/primitives/buttons/Button/Button.js";
|
|
5
|
+
import Switch from "../../../v1/primitives/forms/Switch/Switch.js";
|
|
5
6
|
import { Badge } from "../../../v1/primitives/display/Badge/Badge.js";
|
|
6
7
|
function IconX({ className = "" }) {
|
|
7
8
|
return /* @__PURE__ */ jsxs(
|
|
@@ -106,13 +107,13 @@ function Estado({
|
|
|
106
107
|
if (vazio) return /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-sm text-slate-400", children: labels?.empty ?? "No data" });
|
|
107
108
|
return null;
|
|
108
109
|
}
|
|
109
|
-
function Modal({ open, onClose, title, children, wide }) {
|
|
110
|
+
function Modal({ open, onClose, title, children, wide, fecharAoClicarFora = true }) {
|
|
110
111
|
if (!open) return null;
|
|
111
112
|
return /* @__PURE__ */ jsx(
|
|
112
113
|
"div",
|
|
113
114
|
{
|
|
114
115
|
onClick: (e) => {
|
|
115
|
-
if (e.target === e.currentTarget) onClose();
|
|
116
|
+
if (fecharAoClicarFora && e.target === e.currentTarget) onClose();
|
|
116
117
|
},
|
|
117
118
|
className: "fixed inset-0 bg-[rgba(20,30,50,0.45)] z-50 flex items-center justify-center p-4",
|
|
118
119
|
children: /* @__PURE__ */ jsxs("div", { className: `bg-white dark:bg-slate-900 rounded-xl w-full ${wide ? "max-w-[min(1600px,95vw)]" : "max-w-[720px]"} max-h-[85vh] flex flex-col shadow-2xl`, children: [
|
|
@@ -182,10 +183,7 @@ function CampoInterruptor({
|
|
|
182
183
|
checked,
|
|
183
184
|
onChange
|
|
184
185
|
}) {
|
|
185
|
-
return /* @__PURE__ */
|
|
186
|
-
/* @__PURE__ */ jsx("input", { type: "checkbox", checked, onChange: (e) => onChange(e.target.checked) }),
|
|
187
|
-
label
|
|
188
|
-
] });
|
|
186
|
+
return /* @__PURE__ */ jsx(Switch, { checked, onChange, label, className: "pb-1.5" });
|
|
189
187
|
}
|
|
190
188
|
function BarraFiltros({ children }) {
|
|
191
189
|
return /* @__PURE__ */ jsx("div", { className: "bg-white border border-slate-200 dark:bg-slate-900 dark:border-slate-700 rounded-xl px-4 py-3 flex flex-wrap items-end gap-3", children });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Ui.js","sources":["../../../../../src/legacy/components/ui/Ui.tsx"],"sourcesContent":["import React, { type ReactNode } from 'react'\r\nimport Escolha from './Escolha'\r\nimport SegmentedControl from '../../../v1/primitives/buttons/SegmentedControl/SegmentedControl'\r\nimport Button from '../../../v1/primitives/buttons/Button/Button'\r\n\r\n/** Visual UI primitives (proef design system). Prefer these over repeating classes. */\r\n\r\n/**\r\n * SLA status accepted by BadgeSla.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport type StatusSla = 'ok' | 'risco' | 'incumprido'\r\n\r\nfunction IconX({ className = '' }: { className?: string }) {\r\n return (\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth={2}\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n className={className}\r\n aria-hidden=\"true\"\r\n >\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\r\n </svg>\r\n )\r\n}\r\n\r\n// Migrado para o V1: o Card é o do V1 (aceita o nome antigo `extra`). Reexportado daqui para o código legacy.\r\nimport { Card } from '../../../v1/primitives/display/Card/Card'\r\nexport { Card }\r\n\r\n/**\r\n * Colour-bordered group — the overview sections in the prototype.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function SectionGroup({\r\n title,\r\n tone = 'accent',\r\n children,\r\n}: {\r\n title: string\r\n tone?: 'accent' | 'ok' | 'blue' | 'red' | 'purple' | 'yellow'\r\n children: ReactNode\r\n}) {\r\n const tones: Record<string, [string, string]> = {\r\n accent: ['border-slate-200 dark:border-slate-700', 'text-proef-navy dark:text-slate-100'],\r\n ok: ['border-ok', 'text-ok'],\r\n blue: ['border-[#4FA8FF]', 'text-[#2E8CE0]'],\r\n red: ['border-incumprido', 'text-incumprido'],\r\n purple: ['border-[#8B5CF6]', 'text-[#7C3AED]'],\r\n yellow: ['border-[#D9A400]', 'text-[#B78400]'],\r\n }\r\n const [borda, texto] = tones[tone]!\r\n return (\r\n <div className={`border-[1.5px] ${borda} rounded-2xl p-4 pb-1 mb-5 bg-[rgba(24,34,56,0.015)] dark:bg-slate-900/40`}>\r\n <div className={`text-[15px] font-bold text-center tracking-wide mb-3.5 ${texto}`}>{title}</div>\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// Migrado para o V1: o Kpi é o do V1 (aceita os nomes antigos `delta`, `bgTint`, `barra`, `rodape`, `denso`;\r\n// `hint` passou de `title` para a camada `data-tip`). Reexportado daqui para o código legacy que o importa.\r\nimport { Kpi, type KpiTone } from '../../../v1/primitives/display/Kpi/Kpi'\r\nexport { Kpi, type KpiTone }\r\n\r\n// Migrado para o V1: o Badge é o do V1 (mesmo nome e API; ganhou `dot` e `icon`). Reexportado daqui para\r\n// o código legacy que o importa deste ficheiro.\r\nimport { Badge, type BadgeTone } from '../../../v1/primitives/display/Badge/Badge'\r\nexport { Badge, type BadgeTone }\r\n\r\n/**\r\n * SLA badge — picks the tone and label from `status`. Labels overridable via `labels`.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function BadgeSla({ status, labels }: { status: StatusSla; labels?: { ok?: string; risco?: string; incumprido?: string } }) {\r\n const tone: BadgeTone = status === 'ok' ? 'ok' : status === 'risco' ? 'risco' : 'incumprido'\r\n const defaults: Record<StatusSla, string> = { ok: 'On time', risco: 'At risk', incumprido: 'Breached' }\r\n const label = labels?.[status] ?? defaults[status]\r\n return <Badge tone={tone}>{label}</Badge>\r\n}\r\n\r\n/** Internal-state chip — prototype colours (ESTADO_INTERNO_COLORS). */\r\nconst CORES_ESTADO_INTERNO: Record<string, [string, string]> = {\r\n 'Concluído': ['#1F8A55', '#E5F6EC'],\r\n 'Aferição': ['#2E6DA4', '#E5F0FA'],\r\n 'Para Tratamento': ['#1868B0', '#E5F0FA'],\r\n 'Aguarda Validação LS': ['#2E6DA4', '#E5F0FA'],\r\n 'Aguarda Produção LS': ['#6E8CAD', '#EDF2F7'],\r\n 'Incompleto/Devolvido': ['#C2255C', '#FBE5EF'],\r\n 'Falta de Documentação': ['#B96E00', '#FCF0DC'],\r\n Ignorar: ['#767C88', '#EEF0F2'],\r\n Cancelado: ['#767C88', '#EEF0F2'],\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function ChipEstadoInterno({ estado }: { estado: string | null }) {\r\n if (!estado) return <span className=\"text-slate-400\">—</span>\r\n const cor = CORES_ESTADO_INTERNO[estado]\r\n if (!cor) return <span>{estado}</span>\r\n return (\r\n <span className=\"px-2 py-0.5 rounded text-[11px] font-medium inline-block\" style={{ color: cor[0], background: cor[1] }}>\r\n {estado}\r\n </span>\r\n )\r\n}\r\n\r\nconst VARIANTE_V1 = { default: 'secondary', accent: 'primary', danger: 'danger', ghost: 'ghost' } as const\r\n\r\n/**\r\n * Migrado para o V1: é o `Button` do V1 (mesmo aspeto); isto só mantém a API antiga.\r\n * @deprecated Legacy — usar `Button` do V1.\r\n */\r\nexport function Btn({\r\n children,\r\n onClick,\r\n variant = 'default',\r\n active = false,\r\n disabled = false,\r\n title,\r\n type = 'button',\r\n}: {\r\n children: ReactNode\r\n onClick?: () => void\r\n variant?: 'default' | 'accent' | 'danger' | 'ghost'\r\n active?: boolean\r\n disabled?: boolean\r\n title?: string\r\n type?: 'button' | 'submit'\r\n}) {\r\n return (\r\n <Button\r\n type={type}\r\n onClick={onClick}\r\n disabled={disabled}\r\n title={title}\r\n variant={VARIANTE_V1[variant]}\r\n // Só o `default` tinha estado ligado; nos outros o `active` não fazia nada.\r\n active={variant === 'default' && active ? true : undefined}\r\n >\r\n {children}\r\n </Button>\r\n )\r\n}\r\n\r\n/**\r\n * Mutually exclusive button group (granularity, dimension…).\r\n * Migrado para o V1: é o `SegmentedControl` do V1; isto só mantém a API antiga.\r\n * @deprecated Legacy — usar `SegmentedControl` do V1.\r\n */\r\nexport function BtnGroup<T extends string>({\r\n value,\r\n options,\r\n onChange,\r\n label,\r\n}: {\r\n value: T\r\n options: { value: T; label: string }[]\r\n onChange: (v: T) => void\r\n /** Rótulo opcional à esquerda (ex: \"Situação\"), com espaçamento próprio. */\r\n label?: string\r\n}) {\r\n return <SegmentedControl<T> value={value} options={options} onChange={onChange} label={label} />\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Estado({\r\n carregando,\r\n erro,\r\n vazio,\r\n labels,\r\n}: {\r\n carregando?: boolean\r\n erro?: boolean\r\n vazio?: boolean\r\n labels?: { loading?: string; error?: string; empty?: string }\r\n}) {\r\n if (carregando) return <div className=\"py-10 text-center text-sm text-slate-400\">{labels?.loading ?? 'Loading…'}</div>\r\n if (erro) return <div className=\"py-10 text-center text-sm text-incumprido\">{labels?.error ?? 'Failed to load'}</div>\r\n if (vazio) return <div className=\"py-10 text-center text-sm text-slate-400\">{labels?.empty ?? 'No data'}</div>\r\n return null\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Modal({ open, onClose, title, children, wide }: { open: boolean; onClose: () => void; title: ReactNode; children: ReactNode; wide?: boolean }) {\r\n if (!open) return null\r\n return (\r\n <div\r\n onClick={(e) => {\r\n if (e.target === e.currentTarget) onClose()\r\n }}\r\n className=\"fixed inset-0 bg-[rgba(20,30,50,0.45)] z-50 flex items-center justify-center p-4\"\r\n >\r\n {/*\r\n `wide` acompanha o ecrã em vez de parar num número fixo.\r\n\r\n 1200px era estreito num ecrã grande: um modal com mapa e tabela lado a\r\n lado ficava com a tabela cortada e o mapa pequeno, enquanto meio ecrã\r\n ficava vazio. O tecto de 1600px existe para o texto não ficar a\r\n atravessar o monitor todo, e os 95vw para o modal nunca encostar às\r\n margens.\r\n */}\r\n <div className={`bg-white dark:bg-slate-900 rounded-xl w-full ${wide ? 'max-w-[min(1600px,95vw)]' : 'max-w-[720px]'} max-h-[85vh] flex flex-col shadow-2xl`}>\r\n <div className=\"flex items-center justify-between gap-4 px-5 py-4 border-b border-slate-200 dark:border-slate-700\">\r\n <div className=\"text-[15px] font-bold text-proef-navy dark:text-slate-100\">{title}</div>\r\n <button onClick={onClose} aria-label=\"Close\" className=\"text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-100\">\r\n <IconX className=\"w-5 h-5\" />\r\n </button>\r\n </div>\r\n <div className=\"overflow-auto thin-scroll px-5 py-4\">{children}</div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\n// ─── Filtros ────────────────────────────────────────────────────────────────\r\n// O kit não tinha campos de formulário, e cada ecrã acabava a repetir o mesmo\r\n// <label> com <select> e as mesmas classes. Estes três resolvem a barra de\r\n// filtros que praticamente todos os ecrãs de listagem têm.\r\n\r\n/**\r\n * Rótulo pequeno em maiúsculas por cima do campo.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function Campo({ label, children, className = '' }: { label: string; children: ReactNode; className?: string }) {\r\n return (\r\n <label className={`flex flex-col gap-1 ${className}`}>\r\n <span className=\"text-[10px] font-black uppercase tracking-wider text-slate-400 leading-normal\">{label}</span>\r\n {children}\r\n </label>\r\n )\r\n}\r\n\r\n/**\r\n * Select com opção vazia à cabeça.\r\n *\r\n * A opção vazia é obrigatória e não opcional: sem ela não há como desfazer uma\r\n * escolha, e o utilizador fica preso ao primeiro valor que carregou.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function CampoSelect({\r\n label,\r\n value,\r\n options,\r\n onChange,\r\n placeholderVazio,\r\n disabled = false,\r\n className = '',\r\n}: {\r\n label: string\r\n value: string\r\n options: readonly string[] | readonly { value: string; label: string }[]\r\n onChange: (v: string) => void\r\n placeholderVazio: string\r\n disabled?: boolean\r\n className?: string\r\n}) {\r\n const normalizadas = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o))\r\n return (\r\n <Campo label={label} className={className}>\r\n {/* Mesma lista suspensa em todos os portais (Escolha), em vez do <select> nativo. */}\r\n <Escolha\r\n valor={value}\r\n opcoes={normalizadas.map((o) => ({ valor: o.value, rotulo: o.label }))}\r\n onMudar={onChange}\r\n rotulo={label}\r\n opcaoVazia={placeholderVazio}\r\n placeholder={placeholderVazio}\r\n disabled={disabled}\r\n className=\"min-w-[10rem] max-w-56\"\r\n />\r\n </Campo>\r\n )\r\n}\r\n\r\n/**\r\n * Campo de pesquisa que só dispara ao confirmar.\r\n *\r\n * Deliberadamente NÃO pesquisa a cada tecla: sobre dezenas de milhares de\r\n * registos isso seria um pedido por letra. Confirma-se com Enter ou ao sair do\r\n * campo.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function CampoPesquisa({\r\n label,\r\n valorInicial = '',\r\n onConfirmar,\r\n placeholder,\r\n className = '',\r\n}: {\r\n label: string\r\n valorInicial?: string\r\n onConfirmar: (v: string | undefined) => void\r\n placeholder?: string\r\n className?: string\r\n}) {\r\n const confirmar = (v: string): void => onConfirmar(v.trim() || undefined)\r\n return (\r\n <Campo label={label} className={className}>\r\n <input\r\n type=\"search\"\r\n defaultValue={valorInicial}\r\n placeholder={placeholder}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter') confirmar((e.target as HTMLInputElement).value)\r\n }}\r\n onBlur={(e) => confirmar(e.target.value)}\r\n className=\"h-8 border border-slate-300 rounded-lg px-2.5 text-sm w-64 bg-white dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100 dark:placeholder:text-slate-500\"\r\n />\r\n </Campo>\r\n )\r\n}\r\n\r\n/**\r\n * Caixa de interruptor com rótulo à direita, alinhada com os campos ao lado.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function CampoInterruptor({\r\n label,\r\n checked,\r\n onChange,\r\n}: {\r\n label: string\r\n checked: boolean\r\n onChange: (v: boolean) => void\r\n}) {\r\n return (\r\n <label className=\"flex items-center gap-2 text-sm pb-1.5 cursor-pointer select-none\">\r\n <input type=\"checkbox\" checked={checked} onChange={(e) => onChange(e.target.checked)} />\r\n {label}\r\n </label>\r\n )\r\n}\r\n\r\n/**\r\n * Barra onde os campos de filtro assentam.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function BarraFiltros({ children }: { children: ReactNode }) {\r\n return (\r\n <div className=\"bg-white border border-slate-200 dark:bg-slate-900 dark:border-slate-700 rounded-xl px-4 py-3 flex flex-wrap items-end gap-3\">\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// ─── Listas ─────────────────────────────────────────────────────────────────\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport type SeveridadeAlerta = 'critico' | 'grave' | 'atencao' | 'info'\r\n\r\n/**\r\n * Linha de uma lista de alertas.\r\n *\r\n * A severidade está na barra lateral colorida e não no fundo: uma lista de\r\n * cinquenta linhas com fundos saturados é ilegível, e o que se quer é poder\r\n * varrer a coluna da esquerda e saber onde parar.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function LinhaAlerta({\r\n severidade,\r\n icone,\r\n titulo,\r\n detalhe,\r\n referencia,\r\n onReferenciaClick,\r\n quando,\r\n}: {\r\n severidade: SeveridadeAlerta\r\n icone?: ReactNode\r\n titulo: ReactNode\r\n detalhe?: ReactNode\r\n /** Identificador clicável à direita (ex: número da ordem). */\r\n referencia?: string\r\n onReferenciaClick?: () => void\r\n quando?: ReactNode\r\n}) {\r\n const barras: Record<SeveridadeAlerta, string> = {\r\n critico: 'border-l-incumprido',\r\n grave: 'border-l-orange-500',\r\n atencao: 'border-l-risco',\r\n info: 'border-l-sky-400',\r\n }\r\n const textos: Record<SeveridadeAlerta, string> = {\r\n critico: 'text-incumprido',\r\n grave: 'text-orange-600 dark:text-orange-400',\r\n atencao: 'text-risco',\r\n info: 'text-sky-600 dark:text-sky-400',\r\n }\r\n\r\n return (\r\n <div className={`bg-white border border-slate-200 dark:bg-slate-900 dark:border-y-slate-700 dark:border-r-slate-700 border-l-4 ${barras[severidade]} rounded-lg px-4 py-2.5 flex items-start gap-3`}>\r\n {icone && <span className={`${textos[severidade]} shrink-0 mt-0.5`}>{icone}</span>}\r\n <div className=\"min-w-0 flex-1\">\r\n <div className=\"text-sm font-semibold text-slate-800 dark:text-slate-100\">{titulo}</div>\r\n {detalhe && <div className=\"text-xs text-slate-500 dark:text-slate-400\">{detalhe}</div>}\r\n </div>\r\n {referencia && (\r\n <button\r\n type=\"button\"\r\n onClick={onReferenciaClick}\r\n className=\"text-xs font-mono text-proef-navy dark:text-sky-300 hover:underline shrink-0\"\r\n >\r\n {referencia}\r\n </button>\r\n )}\r\n {quando && <span className=\"text-xs text-slate-400 shrink-0 tabular-nums\">{quando}</span>}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Grelha de pares rótulo/valor, para painéis de detalhe.\r\n *\r\n * Os campos vazios são omitidos por omissão: num registo vindo de várias\r\n * origens, metade dos campos costuma não existir, e uma coluna de travessões\r\n * não informa ninguém. `mostrarVazios` repõe-os quando a ausência é o que\r\n * interessa ver.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function ListaCampos({\r\n campos,\r\n colunas = 2,\r\n mostrarVazios = false,\r\n}: {\r\n campos: { label: string; valor: ReactNode }[]\r\n colunas?: 1 | 2 | 3\r\n mostrarVazios?: boolean\r\n}) {\r\n const vazio = (v: ReactNode): boolean => v == null || v === '' || v === '—'\r\n const visiveis = mostrarVazios ? campos : campos.filter((c) => !vazio(c.valor))\r\n if (!visiveis.length) return null\r\n\r\n const grelha = { 1: '', 2: 'md:grid-cols-2', 3: 'md:grid-cols-3' }[colunas]\r\n\r\n return (\r\n <dl className={`grid ${grelha} gap-x-6 gap-y-1`}>\r\n {visiveis.map((c) => (\r\n <div key={c.label} className=\"flex justify-between gap-3 border-b border-slate-100 dark:border-slate-800 py-1\">\r\n <dt className=\"text-xs text-slate-500 dark:text-slate-400\">{c.label}</dt>\r\n <dd className=\"text-xs font-medium text-slate-800 dark:text-slate-100 text-right\">{c.valor}</dd>\r\n </div>\r\n ))}\r\n </dl>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Ui.js","sources":["../../../../../src/legacy/components/ui/Ui.tsx"],"sourcesContent":["import React, { type ReactNode } from 'react'\r\nimport Escolha from './Escolha'\r\nimport SegmentedControl from '../../../v1/primitives/buttons/SegmentedControl/SegmentedControl'\r\nimport Button from '../../../v1/primitives/buttons/Button/Button'\r\nimport Switch from '../../../v1/primitives/forms/Switch/Switch'\r\n\r\n/** Visual UI primitives (proef design system). Prefer these over repeating classes. */\r\n\r\n/**\r\n * SLA status accepted by BadgeSla.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport type StatusSla = 'ok' | 'risco' | 'incumprido'\r\n\r\nfunction IconX({ className = '' }: { className?: string }) {\r\n return (\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth={2}\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n className={className}\r\n aria-hidden=\"true\"\r\n >\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\r\n </svg>\r\n )\r\n}\r\n\r\n// Migrado para o V1: o Card é o do V1 (aceita o nome antigo `extra`). Reexportado daqui para o código legacy.\r\nimport { Card } from '../../../v1/primitives/display/Card/Card'\r\nexport { Card }\r\n\r\n/**\r\n * Colour-bordered group — the overview sections in the prototype.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function SectionGroup({\r\n title,\r\n tone = 'accent',\r\n children,\r\n}: {\r\n title: string\r\n tone?: 'accent' | 'ok' | 'blue' | 'red' | 'purple' | 'yellow'\r\n children: ReactNode\r\n}) {\r\n const tones: Record<string, [string, string]> = {\r\n accent: ['border-slate-200 dark:border-slate-700', 'text-proef-navy dark:text-slate-100'],\r\n ok: ['border-ok', 'text-ok'],\r\n blue: ['border-[#4FA8FF]', 'text-[#2E8CE0]'],\r\n red: ['border-incumprido', 'text-incumprido'],\r\n purple: ['border-[#8B5CF6]', 'text-[#7C3AED]'],\r\n yellow: ['border-[#D9A400]', 'text-[#B78400]'],\r\n }\r\n const [borda, texto] = tones[tone]!\r\n return (\r\n <div className={`border-[1.5px] ${borda} rounded-2xl p-4 pb-1 mb-5 bg-[rgba(24,34,56,0.015)] dark:bg-slate-900/40`}>\r\n <div className={`text-[15px] font-bold text-center tracking-wide mb-3.5 ${texto}`}>{title}</div>\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// Migrado para o V1: o Kpi é o do V1 (aceita os nomes antigos `delta`, `bgTint`, `barra`, `rodape`, `denso`;\r\n// `hint` passou de `title` para a camada `data-tip`). Reexportado daqui para o código legacy que o importa.\r\nimport { Kpi, type KpiTone } from '../../../v1/primitives/display/Kpi/Kpi'\r\nexport { Kpi, type KpiTone }\r\n\r\n// Migrado para o V1: o Badge é o do V1 (mesmo nome e API; ganhou `dot` e `icon`). Reexportado daqui para\r\n// o código legacy que o importa deste ficheiro.\r\nimport { Badge, type BadgeTone } from '../../../v1/primitives/display/Badge/Badge'\r\nexport { Badge, type BadgeTone }\r\n\r\n/**\r\n * SLA badge — picks the tone and label from `status`. Labels overridable via `labels`.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function BadgeSla({ status, labels }: { status: StatusSla; labels?: { ok?: string; risco?: string; incumprido?: string } }) {\r\n const tone: BadgeTone = status === 'ok' ? 'ok' : status === 'risco' ? 'risco' : 'incumprido'\r\n const defaults: Record<StatusSla, string> = { ok: 'On time', risco: 'At risk', incumprido: 'Breached' }\r\n const label = labels?.[status] ?? defaults[status]\r\n return <Badge tone={tone}>{label}</Badge>\r\n}\r\n\r\n/** Internal-state chip — prototype colours (ESTADO_INTERNO_COLORS). */\r\nconst CORES_ESTADO_INTERNO: Record<string, [string, string]> = {\r\n 'Concluído': ['#1F8A55', '#E5F6EC'],\r\n 'Aferição': ['#2E6DA4', '#E5F0FA'],\r\n 'Para Tratamento': ['#1868B0', '#E5F0FA'],\r\n 'Aguarda Validação LS': ['#2E6DA4', '#E5F0FA'],\r\n 'Aguarda Produção LS': ['#6E8CAD', '#EDF2F7'],\r\n 'Incompleto/Devolvido': ['#C2255C', '#FBE5EF'],\r\n 'Falta de Documentação': ['#B96E00', '#FCF0DC'],\r\n Ignorar: ['#767C88', '#EEF0F2'],\r\n Cancelado: ['#767C88', '#EEF0F2'],\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function ChipEstadoInterno({ estado }: { estado: string | null }) {\r\n if (!estado) return <span className=\"text-slate-400\">—</span>\r\n const cor = CORES_ESTADO_INTERNO[estado]\r\n if (!cor) return <span>{estado}</span>\r\n return (\r\n <span className=\"px-2 py-0.5 rounded text-[11px] font-medium inline-block\" style={{ color: cor[0], background: cor[1] }}>\r\n {estado}\r\n </span>\r\n )\r\n}\r\n\r\nconst VARIANTE_V1 = { default: 'secondary', accent: 'primary', danger: 'danger', ghost: 'ghost' } as const\r\n\r\n/**\r\n * Migrado para o V1: é o `Button` do V1 (mesmo aspeto); isto só mantém a API antiga.\r\n * @deprecated Legacy — usar `Button` do V1.\r\n */\r\nexport function Btn({\r\n children,\r\n onClick,\r\n variant = 'default',\r\n active = false,\r\n disabled = false,\r\n title,\r\n type = 'button',\r\n}: {\r\n children: ReactNode\r\n onClick?: () => void\r\n variant?: 'default' | 'accent' | 'danger' | 'ghost'\r\n active?: boolean\r\n disabled?: boolean\r\n title?: string\r\n type?: 'button' | 'submit'\r\n}) {\r\n return (\r\n <Button\r\n type={type}\r\n onClick={onClick}\r\n disabled={disabled}\r\n title={title}\r\n variant={VARIANTE_V1[variant]}\r\n // Só o `default` tinha estado ligado; nos outros o `active` não fazia nada.\r\n active={variant === 'default' && active ? true : undefined}\r\n >\r\n {children}\r\n </Button>\r\n )\r\n}\r\n\r\n/**\r\n * Mutually exclusive button group (granularity, dimension…).\r\n * Migrado para o V1: é o `SegmentedControl` do V1; isto só mantém a API antiga.\r\n * @deprecated Legacy — usar `SegmentedControl` do V1.\r\n */\r\nexport function BtnGroup<T extends string>({\r\n value,\r\n options,\r\n onChange,\r\n label,\r\n}: {\r\n value: T\r\n options: { value: T; label: string }[]\r\n onChange: (v: T) => void\r\n /** Rótulo opcional à esquerda (ex: \"Situação\"), com espaçamento próprio. */\r\n label?: string\r\n}) {\r\n return <SegmentedControl<T> value={value} options={options} onChange={onChange} label={label} />\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Estado({\r\n carregando,\r\n erro,\r\n vazio,\r\n labels,\r\n}: {\r\n carregando?: boolean\r\n erro?: boolean\r\n vazio?: boolean\r\n labels?: { loading?: string; error?: string; empty?: string }\r\n}) {\r\n if (carregando) return <div className=\"py-10 text-center text-sm text-slate-400\">{labels?.loading ?? 'Loading…'}</div>\r\n if (erro) return <div className=\"py-10 text-center text-sm text-incumprido\">{labels?.error ?? 'Failed to load'}</div>\r\n if (vazio) return <div className=\"py-10 text-center text-sm text-slate-400\">{labels?.empty ?? 'No data'}</div>\r\n return null\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\n// `fecharAoClicarFora={false}` em modais com formulário: um clique no fundo\r\n// fechava a janela e perdia-se tudo o que estava preenchido.\r\nexport function Modal({ open, onClose, title, children, wide, fecharAoClicarFora = true }: { open: boolean; onClose: () => void; title: ReactNode; children: ReactNode; wide?: boolean; fecharAoClicarFora?: boolean }) {\r\n if (!open) return null\r\n return (\r\n <div\r\n onClick={(e) => {\r\n if (fecharAoClicarFora && e.target === e.currentTarget) onClose()\r\n }}\r\n className=\"fixed inset-0 bg-[rgba(20,30,50,0.45)] z-50 flex items-center justify-center p-4\"\r\n >\r\n {/*\r\n `wide` acompanha o ecrã em vez de parar num número fixo.\r\n\r\n 1200px era estreito num ecrã grande: um modal com mapa e tabela lado a\r\n lado ficava com a tabela cortada e o mapa pequeno, enquanto meio ecrã\r\n ficava vazio. O tecto de 1600px existe para o texto não ficar a\r\n atravessar o monitor todo, e os 95vw para o modal nunca encostar às\r\n margens.\r\n */}\r\n <div className={`bg-white dark:bg-slate-900 rounded-xl w-full ${wide ? 'max-w-[min(1600px,95vw)]' : 'max-w-[720px]'} max-h-[85vh] flex flex-col shadow-2xl`}>\r\n <div className=\"flex items-center justify-between gap-4 px-5 py-4 border-b border-slate-200 dark:border-slate-700\">\r\n <div className=\"text-[15px] font-bold text-proef-navy dark:text-slate-100\">{title}</div>\r\n <button onClick={onClose} aria-label=\"Close\" className=\"text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-100\">\r\n <IconX className=\"w-5 h-5\" />\r\n </button>\r\n </div>\r\n <div className=\"overflow-auto thin-scroll px-5 py-4\">{children}</div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\n// ─── Filtros ────────────────────────────────────────────────────────────────\r\n// O kit não tinha campos de formulário, e cada ecrã acabava a repetir o mesmo\r\n// <label> com <select> e as mesmas classes. Estes três resolvem a barra de\r\n// filtros que praticamente todos os ecrãs de listagem têm.\r\n\r\n/**\r\n * Rótulo pequeno em maiúsculas por cima do campo.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function Campo({ label, children, className = '' }: { label: string; children: ReactNode; className?: string }) {\r\n return (\r\n <label className={`flex flex-col gap-1 ${className}`}>\r\n <span className=\"text-[10px] font-black uppercase tracking-wider text-slate-400 leading-normal\">{label}</span>\r\n {children}\r\n </label>\r\n )\r\n}\r\n\r\n/**\r\n * Select com opção vazia à cabeça.\r\n *\r\n * A opção vazia é obrigatória e não opcional: sem ela não há como desfazer uma\r\n * escolha, e o utilizador fica preso ao primeiro valor que carregou.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function CampoSelect({\r\n label,\r\n value,\r\n options,\r\n onChange,\r\n placeholderVazio,\r\n disabled = false,\r\n className = '',\r\n}: {\r\n label: string\r\n value: string\r\n options: readonly string[] | readonly { value: string; label: string }[]\r\n onChange: (v: string) => void\r\n placeholderVazio: string\r\n disabled?: boolean\r\n className?: string\r\n}) {\r\n const normalizadas = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o))\r\n return (\r\n <Campo label={label} className={className}>\r\n {/* Mesma lista suspensa em todos os portais (Escolha), em vez do <select> nativo. */}\r\n <Escolha\r\n valor={value}\r\n opcoes={normalizadas.map((o) => ({ valor: o.value, rotulo: o.label }))}\r\n onMudar={onChange}\r\n rotulo={label}\r\n opcaoVazia={placeholderVazio}\r\n placeholder={placeholderVazio}\r\n disabled={disabled}\r\n className=\"min-w-[10rem] max-w-56\"\r\n />\r\n </Campo>\r\n )\r\n}\r\n\r\n/**\r\n * Campo de pesquisa que só dispara ao confirmar.\r\n *\r\n * Deliberadamente NÃO pesquisa a cada tecla: sobre dezenas de milhares de\r\n * registos isso seria um pedido por letra. Confirma-se com Enter ou ao sair do\r\n * campo.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function CampoPesquisa({\r\n label,\r\n valorInicial = '',\r\n onConfirmar,\r\n placeholder,\r\n className = '',\r\n}: {\r\n label: string\r\n valorInicial?: string\r\n onConfirmar: (v: string | undefined) => void\r\n placeholder?: string\r\n className?: string\r\n}) {\r\n const confirmar = (v: string): void => onConfirmar(v.trim() || undefined)\r\n return (\r\n <Campo label={label} className={className}>\r\n <input\r\n type=\"search\"\r\n defaultValue={valorInicial}\r\n placeholder={placeholder}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter') confirmar((e.target as HTMLInputElement).value)\r\n }}\r\n onBlur={(e) => confirmar(e.target.value)}\r\n className=\"h-8 border border-slate-300 rounded-lg px-2.5 text-sm w-64 bg-white dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100 dark:placeholder:text-slate-500\"\r\n />\r\n </Campo>\r\n )\r\n}\r\n\r\n/**\r\n * Interruptor com rótulo à direita, alinhado com os campos ao lado. Adaptador do `Switch` do V1.\r\n * @deprecated Legacy — não usar em código novo. Usar o `Switch` (V1).\r\n */\r\nexport function CampoInterruptor({\r\n label,\r\n checked,\r\n onChange,\r\n}: {\r\n label: string\r\n checked: boolean\r\n onChange: (v: boolean) => void\r\n}) {\r\n return <Switch checked={checked} onChange={onChange} label={label} className=\"pb-1.5\" />\r\n}\r\n\r\n/**\r\n * Barra onde os campos de filtro assentam.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function BarraFiltros({ children }: { children: ReactNode }) {\r\n return (\r\n <div className=\"bg-white border border-slate-200 dark:bg-slate-900 dark:border-slate-700 rounded-xl px-4 py-3 flex flex-wrap items-end gap-3\">\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// ─── Listas ─────────────────────────────────────────────────────────────────\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport type SeveridadeAlerta = 'critico' | 'grave' | 'atencao' | 'info'\r\n\r\n/**\r\n * Linha de uma lista de alertas.\r\n *\r\n * A severidade está na barra lateral colorida e não no fundo: uma lista de\r\n * cinquenta linhas com fundos saturados é ilegível, e o que se quer é poder\r\n * varrer a coluna da esquerda e saber onde parar.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function LinhaAlerta({\r\n severidade,\r\n icone,\r\n titulo,\r\n detalhe,\r\n referencia,\r\n onReferenciaClick,\r\n quando,\r\n}: {\r\n severidade: SeveridadeAlerta\r\n icone?: ReactNode\r\n titulo: ReactNode\r\n detalhe?: ReactNode\r\n /** Identificador clicável à direita (ex: número da ordem). */\r\n referencia?: string\r\n onReferenciaClick?: () => void\r\n quando?: ReactNode\r\n}) {\r\n const barras: Record<SeveridadeAlerta, string> = {\r\n critico: 'border-l-incumprido',\r\n grave: 'border-l-orange-500',\r\n atencao: 'border-l-risco',\r\n info: 'border-l-sky-400',\r\n }\r\n const textos: Record<SeveridadeAlerta, string> = {\r\n critico: 'text-incumprido',\r\n grave: 'text-orange-600 dark:text-orange-400',\r\n atencao: 'text-risco',\r\n info: 'text-sky-600 dark:text-sky-400',\r\n }\r\n\r\n return (\r\n <div className={`bg-white border border-slate-200 dark:bg-slate-900 dark:border-y-slate-700 dark:border-r-slate-700 border-l-4 ${barras[severidade]} rounded-lg px-4 py-2.5 flex items-start gap-3`}>\r\n {icone && <span className={`${textos[severidade]} shrink-0 mt-0.5`}>{icone}</span>}\r\n <div className=\"min-w-0 flex-1\">\r\n <div className=\"text-sm font-semibold text-slate-800 dark:text-slate-100\">{titulo}</div>\r\n {detalhe && <div className=\"text-xs text-slate-500 dark:text-slate-400\">{detalhe}</div>}\r\n </div>\r\n {referencia && (\r\n <button\r\n type=\"button\"\r\n onClick={onReferenciaClick}\r\n className=\"text-xs font-mono text-proef-navy dark:text-sky-300 hover:underline shrink-0\"\r\n >\r\n {referencia}\r\n </button>\r\n )}\r\n {quando && <span className=\"text-xs text-slate-400 shrink-0 tabular-nums\">{quando}</span>}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Grelha de pares rótulo/valor, para painéis de detalhe.\r\n *\r\n * Os campos vazios são omitidos por omissão: num registo vindo de várias\r\n * origens, metade dos campos costuma não existir, e uma coluna de travessões\r\n * não informa ninguém. `mostrarVazios` repõe-os quando a ausência é o que\r\n * interessa ver.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function ListaCampos({\r\n campos,\r\n colunas = 2,\r\n mostrarVazios = false,\r\n}: {\r\n campos: { label: string; valor: ReactNode }[]\r\n colunas?: 1 | 2 | 3\r\n mostrarVazios?: boolean\r\n}) {\r\n const vazio = (v: ReactNode): boolean => v == null || v === '' || v === '—'\r\n const visiveis = mostrarVazios ? campos : campos.filter((c) => !vazio(c.valor))\r\n if (!visiveis.length) return null\r\n\r\n const grelha = { 1: '', 2: 'md:grid-cols-2', 3: 'md:grid-cols-3' }[colunas]\r\n\r\n return (\r\n <dl className={`grid ${grelha} gap-x-6 gap-y-1`}>\r\n {visiveis.map((c) => (\r\n <div key={c.label} className=\"flex justify-between gap-3 border-b border-slate-100 dark:border-slate-800 py-1\">\r\n <dt className=\"text-xs text-slate-500 dark:text-slate-400\">{c.label}</dt>\r\n <dd className=\"text-xs font-medium text-slate-800 dark:text-slate-100 text-right\">{c.valor}</dd>\r\n </div>\r\n ))}\r\n </dl>\r\n )\r\n}\r\n"],"names":[],"mappings":";;;;;;AAcA,SAAS,MAAM,EAAE,YAAY,MAA8B;AACzD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,QAAO;AAAA,MACP,aAAa;AAAA,MACb,eAAc;AAAA,MACd,gBAAe;AAAA,MACf;AAAA,MACA,eAAY;AAAA,MAEZ,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,IAAG,MAAK,IAAG,KAAI,IAAG,KAAI,IAAG,KAAA,CAAK;AAAA,QACpC,oBAAC,UAAK,IAAG,KAAI,IAAG,KAAI,IAAG,MAAK,IAAG,KAAA,CAAK;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG1C;AAUO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAIG;AACD,QAAM,QAA0C;AAAA,IAC9C,QAAQ,CAAC,0CAA0C,qCAAqC;AAAA,IACxF,IAAI,CAAC,aAAa,SAAS;AAAA,IAC3B,MAAM,CAAC,oBAAoB,gBAAgB;AAAA,IAC3C,KAAK,CAAC,qBAAqB,iBAAiB;AAAA,IAC5C,QAAQ,CAAC,oBAAoB,gBAAgB;AAAA,IAC7C,QAAQ,CAAC,oBAAoB,gBAAgB;AAAA,EAAA;AAE/C,QAAM,CAAC,OAAO,KAAK,IAAI,MAAM,IAAI;AACjC,SACE,qBAAC,OAAA,EAAI,WAAW,kBAAkB,KAAK,6EACrC,UAAA;AAAA,IAAA,oBAAC,OAAA,EAAI,WAAW,0DAA0D,KAAK,IAAK,UAAA,OAAM;AAAA,IACzF;AAAA,EAAA,GACH;AAEJ;AAgBO,SAAS,SAAS,EAAE,QAAQ,UAAgG;AACjI,QAAM,OAAkB,WAAW,OAAO,OAAO,WAAW,UAAU,UAAU;AAChF,QAAM,WAAsC,EAAE,IAAI,WAAW,OAAO,WAAW,YAAY,WAAA;AAC3F,QAAM,QAAQ,SAAS,MAAM,KAAK,SAAS,MAAM;AACjD,SAAO,oBAAC,OAAA,EAAM,MAAa,UAAA,MAAA,CAAM;AACnC;AAGA,MAAM,uBAAyD;AAAA,EAC7D,aAAa,CAAC,WAAW,SAAS;AAAA,EAClC,YAAY,CAAC,WAAW,SAAS;AAAA,EACjC,mBAAmB,CAAC,WAAW,SAAS;AAAA,EACxC,wBAAwB,CAAC,WAAW,SAAS;AAAA,EAC7C,uBAAuB,CAAC,WAAW,SAAS;AAAA,EAC5C,wBAAwB,CAAC,WAAW,SAAS;AAAA,EAC7C,yBAAyB,CAAC,WAAW,SAAS;AAAA,EAC9C,SAAS,CAAC,WAAW,SAAS;AAAA,EAC9B,WAAW,CAAC,WAAW,SAAS;AAClC;AAGO,SAAS,kBAAkB,EAAE,UAAqC;AACvE,MAAI,CAAC,OAAQ,4BAAQ,QAAA,EAAK,WAAU,kBAAiB,UAAA,KAAC;AACtD,QAAM,MAAM,qBAAqB,MAAM;AACvC,MAAI,CAAC,IAAK,QAAO,oBAAC,UAAM,UAAA,QAAO;AAC/B,SACE,oBAAC,QAAA,EAAK,WAAU,4DAA2D,OAAO,EAAE,OAAO,IAAI,CAAC,GAAG,YAAY,IAAI,CAAC,EAAA,GACjH,UAAA,QACH;AAEJ;AAEA,MAAM,cAAc,EAAE,SAAS,aAAa,QAAQ,WAAW,QAAQ,UAAU,OAAO,QAAA;AAMjF,SAAS,IAAI;AAAA,EAClB;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW;AAAA,EACX;AAAA,EACA,OAAO;AACT,GAQG;AACD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,YAAY,OAAO;AAAA,MAE5B,QAAQ,YAAY,aAAa,SAAS,OAAO;AAAA,MAEhD;AAAA,IAAA;AAAA,EAAA;AAGP;AAOO,SAAS,SAA2B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SAAO,oBAAC,kBAAA,EAAoB,OAAc,SAAkB,UAAoB,OAAc;AAChG;AAGO,SAAS,OAAO;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,MAAI,mBAAmB,oBAAC,OAAA,EAAI,WAAU,4CAA4C,UAAA,QAAQ,WAAW,WAAA,CAAW;AAChH,MAAI,aAAa,oBAAC,OAAA,EAAI,WAAU,6CAA6C,UAAA,QAAQ,SAAS,iBAAA,CAAiB;AAC/G,MAAI,cAAc,oBAAC,OAAA,EAAI,WAAU,4CAA4C,UAAA,QAAQ,SAAS,UAAA,CAAU;AACxG,SAAO;AACT;AAKO,SAAS,MAAM,EAAE,MAAM,SAAS,OAAO,UAAU,MAAM,qBAAqB,QAAqI;AACtN,MAAI,CAAC,KAAM,QAAO;AAClB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,SAAS,CAAC,MAAM;AACd,YAAI,sBAAsB,EAAE,WAAW,EAAE,cAAe,SAAA;AAAA,MAC1D;AAAA,MACA,WAAU;AAAA,MAWV,+BAAC,OAAA,EAAI,WAAW,gDAAgD,OAAO,6BAA6B,eAAe,0CACjH,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,qGACb,UAAA;AAAA,UAAA,oBAAC,OAAA,EAAI,WAAU,6DAA6D,UAAA,OAAM;AAAA,UAClF,oBAAC,UAAA,EAAO,SAAS,SAAS,cAAW,SAAQ,WAAU,qFACrD,UAAA,oBAAC,OAAA,EAAM,WAAU,UAAA,CAAU,EAAA,CAC7B;AAAA,QAAA,GACF;AAAA,QACA,oBAAC,OAAA,EAAI,WAAU,uCAAuC,SAAA,CAAS;AAAA,MAAA,EAAA,CACjE;AAAA,IAAA;AAAA,EAAA;AAGN;AAWO,SAAS,MAAM,EAAE,OAAO,UAAU,YAAY,MAAkE;AACrH,SACE,qBAAC,SAAA,EAAM,WAAW,uBAAuB,SAAS,IAChD,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAK,WAAU,iFAAiF,UAAA,OAAM;AAAA,IACtG;AAAA,EAAA,GACH;AAEJ;AASO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,YAAY;AACd,GAQG;AACD,QAAM,eAAe,QAAQ,IAAI,CAAC,MAAO,OAAO,MAAM,WAAW,EAAE,OAAO,GAAG,OAAO,EAAA,IAAM,CAAE;AAC5F,SACE,oBAAC,OAAA,EAAM,OAAc,WAEnB,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,OAAO;AAAA,MACP,QAAQ,aAAa,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,MAAA,EAAQ;AAAA,MACrE,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,aAAa;AAAA,MACb;AAAA,MACA,WAAU;AAAA,IAAA;AAAA,EAAA,GAEd;AAEJ;AAUO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA,YAAY;AACd,GAMG;AACD,QAAM,YAAY,CAAC,MAAoB,YAAY,EAAE,KAAA,KAAU,MAAS;AACxE,SACE,oBAAC,OAAA,EAAM,OAAc,WACnB,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAc;AAAA,MACd;AAAA,MACA,WAAW,CAAC,MAAM;AAChB,YAAI,EAAE,QAAQ,QAAS,WAAW,EAAE,OAA4B,KAAK;AAAA,MACvE;AAAA,MACA,QAAQ,CAAC,MAAM,UAAU,EAAE,OAAO,KAAK;AAAA,MACvC,WAAU;AAAA,IAAA;AAAA,EAAA,GAEd;AAEJ;AAMO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,6BAAQ,QAAA,EAAO,SAAkB,UAAoB,OAAc,WAAU,UAAS;AACxF;AAMO,SAAS,aAAa,EAAE,YAAqC;AAClE,SACE,oBAAC,OAAA,EAAI,WAAU,gIACZ,SAAA,CACH;AAEJ;AAeO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,QAAM,SAA2C;AAAA,IAC/C,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,EAAA;AAER,QAAM,SAA2C;AAAA,IAC/C,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,EAAA;AAGR,8BACG,OAAA,EAAI,WAAW,iHAAiH,OAAO,UAAU,CAAC,kDAChJ,UAAA;AAAA,IAAA,SAAS,oBAAC,UAAK,WAAW,GAAG,OAAO,UAAU,CAAC,oBAAqB,UAAA,MAAA,CAAM;AAAA,IAC3E,qBAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,MAAA,oBAAC,OAAA,EAAI,WAAU,4DAA4D,UAAA,QAAO;AAAA,MACjF,WAAW,oBAAC,OAAA,EAAI,WAAU,8CAA8C,UAAA,QAAA,CAAQ;AAAA,IAAA,GACnF;AAAA,IACC,cACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,WAAU;AAAA,QAET,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAGJ,UAAU,oBAAC,QAAA,EAAK,WAAU,gDAAgD,UAAA,OAAA,CAAO;AAAA,EAAA,GACpF;AAEJ;AAWO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,UAAU;AAAA,EACV,gBAAgB;AAClB,GAIG;AACD,QAAM,QAAQ,CAAC,MAA0B,KAAK,QAAQ,MAAM,MAAM,MAAM;AACxE,QAAM,WAAW,gBAAgB,SAAS,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;AAC9E,MAAI,CAAC,SAAS,OAAQ,QAAO;AAE7B,QAAM,SAAS,EAAE,GAAG,IAAI,GAAG,kBAAkB,GAAG,iBAAA,EAAmB,OAAO;AAE1E,SACE,oBAAC,MAAA,EAAG,WAAW,QAAQ,MAAM,oBAC1B,UAAA,SAAS,IAAI,CAAC,MACb,qBAAC,OAAA,EAAkB,WAAU,mFAC3B,UAAA;AAAA,IAAA,oBAAC,MAAA,EAAG,WAAU,8CAA8C,UAAA,EAAE,OAAM;AAAA,IACpE,oBAAC,MAAA,EAAG,WAAU,qEAAqE,YAAE,MAAA,CAAM;AAAA,EAAA,EAAA,GAFnF,EAAE,KAGZ,CACD,GACH;AAEJ;"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Avatar } from "../Avatar/Avatar.js";
|
|
3
|
+
function AvatarStack({ people, max = 4, size = "md", online, moreTip, className = "" }) {
|
|
4
|
+
if (people.length === 0) return null;
|
|
5
|
+
const visiveis = people.slice(0, max);
|
|
6
|
+
const escondidas = people.slice(max);
|
|
7
|
+
return /* @__PURE__ */ jsxs("div", { className: `inline-flex items-center ${className}`, children: [
|
|
8
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center -space-x-1.5", children: visiveis.map((p) => /* @__PURE__ */ jsxs("span", { className: "relative flex rounded-full ring-2 ring-card", children: [
|
|
9
|
+
/* @__PURE__ */ jsx(Avatar, { name: p.name, photo: p.photo, photoLarge: p.photoLarge, email: p.email, size }),
|
|
10
|
+
online && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "absolute right-0 bottom-0 size-2 rounded-full bg-emerald-400 ring-1 ring-card" })
|
|
11
|
+
] }, p.id)) }),
|
|
12
|
+
escondidas.length > 0 && /* @__PURE__ */ jsxs(
|
|
13
|
+
"span",
|
|
14
|
+
{
|
|
15
|
+
className: "ml-1 text-[10px] font-bold whitespace-nowrap text-muted-foreground",
|
|
16
|
+
"data-tip": moreTip ?? escondidas.map((p) => p.name).join(", "),
|
|
17
|
+
children: [
|
|
18
|
+
"+",
|
|
19
|
+
escondidas.length
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
] });
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
AvatarStack
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=AvatarStack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AvatarStack.js","sources":["../../../../../../src/v1/primitives/display/AvatarStack/AvatarStack.tsx"],"sourcesContent":["import React from 'react'\r\nimport Avatar, { type AvatarSize } from '../Avatar/Avatar'\r\n\r\nexport interface AvatarStackPerson {\r\n /** Identificador único (para a lista). */\r\n id: string\r\n name: string\r\n /** URL da fotografia. Sem ela ficam as iniciais. */\r\n photo?: string | null\r\n /** Fotografia maior para o cartão (por omissão, a mesma). */\r\n photoLarge?: string | null\r\n email?: string | null\r\n}\r\n\r\nexport interface AvatarStackProps {\r\n people: AvatarStackPerson[]\r\n /** Quantos avatares mostrar antes do \"+N\". */\r\n max?: number\r\n size?: Exclude<AvatarSize, 'none'>\r\n /** Um ponto verde em cada avatar (ex: quem está a ver a página agora). */\r\n online?: boolean\r\n /** Texto ao passar o rato no \"+N\" (ex: os nomes que não cabem). Por omissão, os nomes escondidos. */\r\n moreTip?: string\r\n className?: string\r\n}\r\n\r\n/**\r\n * Várias pessoas em pouco espaço: avatares sobrepostos e \"+N\" para as que não\r\n * cabem. Cada avatar mostra o cartão da pessoa ao passar o rato.\r\n *\r\n * Usar para \"quem está a ver\" (com `online`), membros de um grupo numa tabela,\r\n * recursos de um projeto, etc. Para escolher uma pessoa, usar o `PeopleFilter`.\r\n */\r\nexport default function AvatarStack({ people, max = 4, size = 'md', online, moreTip, className = '' }: AvatarStackProps) {\r\n if (people.length === 0) return null\r\n const visiveis = people.slice(0, max)\r\n const escondidas = people.slice(max)\r\n return (\r\n <div className={`inline-flex items-center ${className}`}>\r\n <div className=\"flex items-center -space-x-1.5\">\r\n {visiveis.map((p) => (\r\n <span key={p.id} className=\"relative flex rounded-full ring-2 ring-card\">\r\n <Avatar name={p.name} photo={p.photo} photoLarge={p.photoLarge} email={p.email} size={size} />\r\n {online && <span aria-hidden className=\"absolute right-0 bottom-0 size-2 rounded-full bg-emerald-400 ring-1 ring-card\" />}\r\n </span>\r\n ))}\r\n </div>\r\n {escondidas.length > 0 && (\r\n <span\r\n className=\"ml-1 text-[10px] font-bold whitespace-nowrap text-muted-foreground\"\r\n data-tip={moreTip ?? escondidas.map((p) => p.name).join(', ')}\r\n >\r\n +{escondidas.length}\r\n </span>\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\nexport { AvatarStack }\r\n"],"names":[],"mappings":";;AAiCA,SAAwB,YAAY,EAAE,QAAQ,MAAM,GAAG,OAAO,MAAM,QAAQ,SAAS,YAAY,GAAA,GAAwB;AACvH,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,QAAM,WAAW,OAAO,MAAM,GAAG,GAAG;AACpC,QAAM,aAAa,OAAO,MAAM,GAAG;AACnC,SACE,qBAAC,OAAA,EAAI,WAAW,4BAA4B,SAAS,IACnD,UAAA;AAAA,IAAA,oBAAC,OAAA,EAAI,WAAU,kCACZ,UAAA,SAAS,IAAI,CAAC,MACb,qBAAC,QAAA,EAAgB,WAAU,+CACzB,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAO,MAAM,EAAE,MAAM,OAAO,EAAE,OAAO,YAAY,EAAE,YAAY,OAAO,EAAE,OAAO,MAAY;AAAA,MAC3F,UAAU,oBAAC,QAAA,EAAK,eAAW,MAAC,WAAU,gFAAA,CAAgF;AAAA,IAAA,EAAA,GAF9G,EAAE,EAGb,CACD,GACH;AAAA,IACC,WAAW,SAAS,KACnB;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAU;AAAA,QACV,YAAU,WAAW,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI;AAAA,QAC7D,UAAA;AAAA,UAAA;AAAA,UACG,WAAW;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EACf,GAEJ;AAEJ;"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Avatar } from "../Avatar/Avatar.js";
|
|
3
|
+
import Button from "../../buttons/Button/Button.js";
|
|
4
|
+
import { CloseIcon } from "../../../foundations/icons/icons.js";
|
|
5
|
+
function PeopleFilter({
|
|
6
|
+
people,
|
|
7
|
+
value,
|
|
8
|
+
onChange,
|
|
9
|
+
label,
|
|
10
|
+
clearable = true,
|
|
11
|
+
clearLabel = "Clear filter",
|
|
12
|
+
size = "sm",
|
|
13
|
+
className = ""
|
|
14
|
+
}) {
|
|
15
|
+
if (people.length === 0) return null;
|
|
16
|
+
return /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": label, className: `inline-flex items-center gap-1 ${className}`, children: [
|
|
17
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center -space-x-1.5", children: people.map((p) => {
|
|
18
|
+
const on = value === p.id;
|
|
19
|
+
return /* @__PURE__ */ jsx(
|
|
20
|
+
"button",
|
|
21
|
+
{
|
|
22
|
+
type: "button",
|
|
23
|
+
"aria-pressed": on,
|
|
24
|
+
"aria-label": p.name,
|
|
25
|
+
"data-tip": p.name,
|
|
26
|
+
onClick: () => onChange(on ? clearable ? null : p.id : p.id),
|
|
27
|
+
className: `relative flex cursor-pointer appearance-none rounded-full border-0 bg-transparent p-0 transition focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring ${on ? "z-10" : value ? "opacity-40 hover:z-10 hover:opacity-100" : "hover:z-10"}`,
|
|
28
|
+
children: /* @__PURE__ */ jsx(
|
|
29
|
+
Avatar,
|
|
30
|
+
{
|
|
31
|
+
name: p.name,
|
|
32
|
+
photo: p.photo,
|
|
33
|
+
size,
|
|
34
|
+
card: false,
|
|
35
|
+
className: `ring-2 ${on ? "ring-proef-turquoise" : "ring-card"}`
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
},
|
|
39
|
+
p.id
|
|
40
|
+
);
|
|
41
|
+
}) }),
|
|
42
|
+
clearable && /* @__PURE__ */ jsx(
|
|
43
|
+
Button,
|
|
44
|
+
{
|
|
45
|
+
variant: "ghost",
|
|
46
|
+
size: "sm",
|
|
47
|
+
icon: /* @__PURE__ */ jsx(CloseIcon, { size: 13 }),
|
|
48
|
+
onClick: () => onChange(null),
|
|
49
|
+
"aria-label": clearLabel,
|
|
50
|
+
"data-tip": clearLabel,
|
|
51
|
+
className: value ? "" : "invisible"
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
] });
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
PeopleFilter
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=PeopleFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PeopleFilter.js","sources":["../../../../../../src/v1/primitives/display/PeopleFilter/PeopleFilter.tsx"],"sourcesContent":["import React from 'react'\r\nimport Avatar from '../Avatar/Avatar'\r\nimport Button from '../../buttons/Button/Button'\r\nimport { CloseIcon } from '../../../foundations/icons/icons'\r\n\r\nexport interface PeopleFilterPerson {\r\n id: string\r\n name: string\r\n /** URL da fotografia. Sem ela ficam as iniciais. */\r\n photo?: string | null\r\n}\r\n\r\nexport interface PeopleFilterProps {\r\n people: PeopleFilterPerson[]\r\n /** A pessoa escolhida (`id`), ou `null` para nenhuma. */\r\n value: string | null\r\n onChange: (id: string | null) => void\r\n /** Nome do grupo, para leitores de ecrã (ex: \"Recurso\"). */\r\n label?: string\r\n /**\r\n * Por omissão é um filtro: clicar na escolhida desmarca-a e há um × para limpar.\r\n * `false` para escolher sempre uma pessoa (ex: num formulário).\r\n */\r\n clearable?: boolean\r\n /** Nome do botão ×. */\r\n clearLabel?: string\r\n /** `sm` 24px (barras de filtros), `md` 28px (formulários). */\r\n size?: 'sm' | 'md'\r\n className?: string\r\n}\r\n\r\n/**\r\n * Escolher uma pessoa pela cara: avatares sobrepostos, a escolhida com o\r\n * contorno turquesa dos filtros e as outras esbatidas. O nome aparece ao passar\r\n * o rato.\r\n *\r\n * Usar para filtrar uma lista por pessoa (ex: por recurso ou responsável) ou,\r\n * com `clearable={false}`, para escolher a pessoa num formulário. O × fica\r\n * sempre com o seu lugar, para os avatares não saltarem quando aparece.\r\n */\r\nexport default function PeopleFilter({\r\n people,\r\n value,\r\n onChange,\r\n label,\r\n clearable = true,\r\n clearLabel = 'Clear filter',\r\n size = 'sm',\r\n className = '',\r\n}: PeopleFilterProps) {\r\n if (people.length === 0) return null\r\n return (\r\n <div role=\"group\" aria-label={label} className={`inline-flex items-center gap-1 ${className}`}>\r\n <div className=\"flex items-center -space-x-1.5\">\r\n {people.map((p) => {\r\n const on = value === p.id\r\n return (\r\n <button\r\n key={p.id}\r\n type=\"button\"\r\n aria-pressed={on}\r\n aria-label={p.name}\r\n data-tip={p.name}\r\n onClick={() => onChange(on ? (clearable ? null : p.id) : p.id)}\r\n className={`relative flex cursor-pointer appearance-none rounded-full border-0 bg-transparent p-0 transition focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring ${\r\n on ? 'z-10' : value ? 'opacity-40 hover:z-10 hover:opacity-100' : 'hover:z-10'\r\n }`}\r\n >\r\n <Avatar\r\n name={p.name}\r\n photo={p.photo}\r\n size={size}\r\n card={false}\r\n className={`ring-2 ${on ? 'ring-proef-turquoise' : 'ring-card'}`}\r\n />\r\n </button>\r\n )\r\n })}\r\n </div>\r\n {clearable && (\r\n <Button\r\n variant=\"ghost\"\r\n size=\"sm\"\r\n icon={<CloseIcon size={13} />}\r\n onClick={() => onChange(null)}\r\n aria-label={clearLabel}\r\n data-tip={clearLabel}\r\n className={value ? '' : 'invisible'}\r\n />\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\nexport { PeopleFilter }\r\n"],"names":[],"mappings":";;;;AAwCA,SAAwB,aAAa;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,OAAO;AAAA,EACP,YAAY;AACd,GAAsB;AACpB,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SACE,qBAAC,SAAI,MAAK,SAAQ,cAAY,OAAO,WAAW,kCAAkC,SAAS,IACzF,UAAA;AAAA,IAAA,oBAAC,SAAI,WAAU,kCACZ,UAAA,OAAO,IAAI,CAAC,MAAM;AACjB,YAAM,KAAK,UAAU,EAAE;AACvB,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,MAAK;AAAA,UACL,gBAAc;AAAA,UACd,cAAY,EAAE;AAAA,UACd,YAAU,EAAE;AAAA,UACZ,SAAS,MAAM,SAAS,KAAM,YAAY,OAAO,EAAE,KAAM,EAAE,EAAE;AAAA,UAC7D,WAAW,sLACT,KAAK,SAAS,QAAQ,4CAA4C,YACpE;AAAA,UAEA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAM,EAAE;AAAA,cACR,OAAO,EAAE;AAAA,cACT;AAAA,cACA,MAAM;AAAA,cACN,WAAW,UAAU,KAAK,yBAAyB,WAAW;AAAA,YAAA;AAAA,UAAA;AAAA,QAChE;AAAA,QAhBK,EAAE;AAAA,MAAA;AAAA,IAmBb,CAAC,EAAA,CACH;AAAA,IACC,aACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,MAAM,oBAAC,WAAA,EAAU,MAAM,GAAA,CAAI;AAAA,QAC3B,SAAS,MAAM,SAAS,IAAI;AAAA,QAC5B,cAAY;AAAA,QACZ,YAAU;AAAA,QACV,WAAW,QAAQ,KAAK;AAAA,MAAA;AAAA,IAAA;AAAA,EAC1B,GAEJ;AAEJ;"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { useField } from "../field.js";
|
|
4
|
+
const Switch = forwardRef(function Switch2({ checked, onChange, label, disabled, id, className = "", ...props }, ref) {
|
|
5
|
+
const field = useField();
|
|
6
|
+
const interruptor = /* @__PURE__ */ jsx(
|
|
7
|
+
"button",
|
|
8
|
+
{
|
|
9
|
+
ref,
|
|
10
|
+
type: "button",
|
|
11
|
+
role: "switch",
|
|
12
|
+
id: id ?? field.id,
|
|
13
|
+
"aria-checked": checked,
|
|
14
|
+
disabled,
|
|
15
|
+
onClick: () => onChange(!checked),
|
|
16
|
+
className: "relative inline-flex h-5 w-9 flex-none cursor-pointer items-center rounded-full border-0 p-0 outline-none transition-colors duration-150 focus-visible:ring-3 focus-visible:ring-primary/20 disabled:cursor-not-allowed disabled:opacity-50 " + (checked ? "bg-primary dark:bg-proef-blue" : "bg-slate-300 dark:bg-slate-600"),
|
|
17
|
+
...props,
|
|
18
|
+
children: /* @__PURE__ */ jsx(
|
|
19
|
+
"span",
|
|
20
|
+
{
|
|
21
|
+
"aria-hidden": true,
|
|
22
|
+
className: `pointer-events-none inline-block size-4 rounded-full bg-white shadow-sm transition-transform duration-150 ${checked ? "translate-x-[18px]" : "translate-x-0.5"}`
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
if (!label) return className ? /* @__PURE__ */ jsx("span", { className: `inline-flex ${className}`, children: interruptor }) : interruptor;
|
|
28
|
+
return /* @__PURE__ */ jsxs("label", { className: `inline-flex cursor-pointer items-center gap-2 text-control text-foreground select-none ${disabled ? "cursor-not-allowed opacity-60" : ""} ${className}`, children: [
|
|
29
|
+
interruptor,
|
|
30
|
+
/* @__PURE__ */ jsx("span", { children: label })
|
|
31
|
+
] });
|
|
32
|
+
});
|
|
33
|
+
export {
|
|
34
|
+
Switch as default
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=Switch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Switch.js","sources":["../../../../../../src/v1/primitives/forms/Switch/Switch.tsx"],"sourcesContent":["import React, { forwardRef } from 'react'\r\nimport { useField } from '../field'\r\n\r\nexport interface SwitchProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onChange' | 'type' | 'role'> {\r\n /** Ligado ou desligado. A app guarda este estado. */\r\n checked: boolean\r\n /** O valor novo, ao clicar (ou com Espaço/Enter). */\r\n onChange: (checked: boolean) => void\r\n /** Texto à direita (ex: \"Ativo\"). Clicar nele também muda o interruptor. */\r\n label?: React.ReactNode\r\n}\r\n\r\n/**\r\n * Interruptor de ligar/desligar, para uma opção que tem efeito logo (ex: \"Grupo CAB\",\r\n * \"Mostrar inativos\"). Para marcar itens numa lista ou aceitar termos, usar a\r\n * [Checkbox](?path=/docs/v1-primitives-forms-checkbox--docs).\r\n *\r\n * Dentro de um [FormField](?path=/docs/v1-components-forms-formfield--docs) fica\r\n * ligado à etiqueta automaticamente. Fora dele, dar-lhe um `label` ou um `aria-label`.\r\n *\r\n * **Aceita os atributos HTML do [<button>](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button)** (`disabled`, `name`, `ref`, `aria-*`, ...).\r\n * Diferenças: o `className` aplica-se ao conjunto (interruptor e texto).\r\n */\r\nconst Switch = forwardRef<HTMLButtonElement, SwitchProps>(function Switch({ checked, onChange, label, disabled, id, className = '', ...props }, ref) {\r\n const field = useField()\r\n const interruptor = (\r\n <button\r\n ref={ref}\r\n type=\"button\"\r\n role=\"switch\"\r\n id={id ?? field.id}\r\n aria-checked={checked}\r\n disabled={disabled}\r\n onClick={() => onChange(!checked)}\r\n className={\r\n 'relative inline-flex h-5 w-9 flex-none cursor-pointer items-center rounded-full border-0 p-0 outline-none transition-colors duration-150 ' +\r\n 'focus-visible:ring-3 focus-visible:ring-primary/20 disabled:cursor-not-allowed disabled:opacity-50 ' +\r\n (checked ? 'bg-primary dark:bg-proef-blue' : 'bg-slate-300 dark:bg-slate-600')\r\n }\r\n {...props}\r\n >\r\n <span\r\n aria-hidden\r\n className={`pointer-events-none inline-block size-4 rounded-full bg-white shadow-sm transition-transform duration-150 ${checked ? 'translate-x-[18px]' : 'translate-x-0.5'}`}\r\n />\r\n </button>\r\n )\r\n if (!label) return className ? <span className={`inline-flex ${className}`}>{interruptor}</span> : interruptor\r\n return (\r\n <label className={`inline-flex cursor-pointer items-center gap-2 text-control text-foreground select-none ${disabled ? 'cursor-not-allowed opacity-60' : ''} ${className}`}>\r\n {interruptor}\r\n <span>{label}</span>\r\n </label>\r\n )\r\n})\r\n\r\nexport default Switch\r\n"],"names":["Switch"],"mappings":";;;AAuBA,MAAM,SAAS,WAA2C,SAASA,QAAO,EAAE,SAAS,UAAU,OAAO,UAAU,IAAI,YAAY,IAAI,GAAG,MAAA,GAAS,KAAK;AACnJ,QAAM,QAAQ,SAAA;AACd,QAAM,cACJ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,MAAK;AAAA,MACL,IAAI,MAAM,MAAM;AAAA,MAChB,gBAAc;AAAA,MACd;AAAA,MACA,SAAS,MAAM,SAAS,CAAC,OAAO;AAAA,MAChC,WACE,kPAEC,UAAU,kCAAkC;AAAA,MAE9C,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,eAAW;AAAA,UACX,WAAW,6GAA6G,UAAU,uBAAuB,iBAAiB;AAAA,QAAA;AAAA,MAAA;AAAA,IAC5K;AAAA,EAAA;AAGJ,MAAI,CAAC,MAAO,QAAO,YAAY,oBAAC,QAAA,EAAK,WAAW,eAAe,SAAS,IAAK,UAAA,YAAA,CAAY,IAAU;AACnG,SACE,qBAAC,WAAM,WAAW,0FAA0F,WAAW,kCAAkC,EAAE,IAAI,SAAS,IACrK,UAAA;AAAA,IAAA;AAAA,IACD,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,EAAA,GACf;AAEJ,CAAC;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
2
|
const fieldBoxClasses = "rounded-lg border border-border bg-card px-2.5 text-control text-foreground outline-none dark:border-slate-600 dark:bg-slate-900 transition-[border-color,box-shadow] duration-150 placeholder:text-muted-foreground/70 focus:border-primary/40 focus:ring-3 focus:ring-primary/10 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/15 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground";
|
|
3
3
|
const fieldClasses = "block w-full min-w-0 " + fieldBoxClasses;
|
|
4
4
|
const readOnlyClasses = "read-only:cursor-default read-only:bg-muted";
|