@proef/common-ui 2.2.4 → 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 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 "./legacy/components/atoms/Button/variants/TabButton.js";
77
- import { default as default74 } from "./v1/components/tables/Table/Table.js";
78
- import { default as default75 } from "./v1/primitives/navigation/Tabs/Tabs.js";
79
- import { default as default76 } from "./v1/primitives/forms/TextArea/TextArea.js";
80
- import { default as default77 } from "./v1/primitives/forms/TextInput/TextInput.js";
81
- import { default as default78 } from "./legacy/components/ui/TextoAuto.js";
82
- import { default as default79, useToast } from "./v1/components/feedback/Toast/Toast.js";
83
- import { default as default80 } from "./v1/components/overlays/Tooltip/Tooltip.js";
84
- import { default as default81 } from "./v1/components/overlays/Tooltip/TooltipLayer.js";
85
- import { default as default82 } from "./legacy/components/layout/TopBar/TopBar.js";
86
- import { default as default83 } from "./legacy/components/layout/TopNavLayout/TopNavLayout.js";
87
- import { default as default84 } from "./legacy/components/molecules/UserAvatar/UserAvatar.js";
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
- default73 as TabButton,
275
- default74 as Table,
276
- default75 as Tabs,
277
- default76 as TextArea,
278
- default77 as TextInput,
279
- default78 as TextoAuto,
280
- default79 as ToastProvider,
281
- default80 as Tooltip,
282
- default81 as TooltipLayer,
283
- default82 as TopBar,
284
- default83 as TopNavLayout,
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
- default84 as UserAvatar,
292
+ default85 as UserAvatar,
287
293
  WarningIcon,
288
294
  applyColumnLayout,
289
295
  applyTheme,
@@ -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 "react-switch";
3
- import './CustomSwitch.css';/* empty css */
4
- const CustomSwitch = ({
5
- label,
6
- activated,
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 Switch from 'react-switch'\r\nimport { CustomSwitchTheme } from '../../../types/themes/CustomSwitchThemes'\r\n\r\nimport './CustomSwitch.scss'\r\n\r\ntype CustomSwitchProps = {\r\n label: string\r\n activated: boolean\r\n onChange: () => void\r\n theme: CustomSwitchTheme\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nconst CustomSwitch: React.FC<CustomSwitchProps> = ({\r\n label,\r\n activated,\r\n onChange,\r\n theme,\r\n}) => {\r\n return (\r\n <div className={`cui-container cui-switch-size-${theme.size}`}>\r\n <label className={'cui-label'}>{label}</label>\r\n <Switch\r\n onChange={onChange}\r\n checked={activated}\r\n offColor=\"#888\"\r\n onColor=\"#010060\"\r\n uncheckedIcon={false}\r\n checkedIcon={false}\r\n className={'cui-switch'}\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default CustomSwitch\r\n"],"names":[],"mappings":";;;AAcA,MAAM,eAA4C,CAAC;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,8BACG,OAAA,EAAI,WAAW,iCAAiC,MAAM,IAAI,IACzD,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAM,WAAW,aAAc,UAAA,OAAM;AAAA,IACtC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,SAAS;AAAA,QACT,UAAS;AAAA,QACT,SAAQ;AAAA,QACR,eAAe;AAAA,QACf,aAAa;AAAA,QACb,WAAW;AAAA,MAAA;AAAA,IAAA;AAAA,EACb,GACF;AAEJ;"}
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(
@@ -182,10 +183,7 @@ function CampoInterruptor({
182
183
  checked,
183
184
  onChange
184
185
  }) {
185
- return /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm pb-1.5 cursor-pointer select-none", children: [
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\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 * 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":"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"}
@@ -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 [&lt;button&gt;](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 { useContext, createContext } from "react";
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";