@nextelco/common-ui 2.1.105 → 2.1.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/esm/v1/components/forms/FormField/FormField.js.map +1 -1
  2. package/dist/esm/v1/components/forms/FormGrid/FormGrid.js.map +1 -1
  3. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js +4 -4
  4. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js.map +1 -1
  5. package/dist/esm/v1/components/overlays/Dialog/Dialog.js.map +1 -1
  6. package/dist/esm/v1/components/tables/PaginatedTable/PaginatedTable.js.map +1 -1
  7. package/dist/esm/v1/components/tables/Table/Table.js.map +1 -1
  8. package/dist/esm/v1/primitives/buttons/Button/Button.js.map +1 -1
  9. package/dist/esm/v1/primitives/forms/Checkbox/Checkbox.js +10 -5
  10. package/dist/esm/v1/primitives/forms/Checkbox/Checkbox.js.map +1 -1
  11. package/dist/esm/v1/primitives/forms/LookupInput/LookupInput.js.map +1 -1
  12. package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js.map +1 -1
  13. package/dist/esm/v1/primitives/forms/TextArea/TextArea.js.map +1 -1
  14. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js +2 -2
  15. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js.map +1 -1
  16. package/dist/style.components.css +1 -1
  17. package/dist/style.css +1 -1
  18. package/dist/style.layered.css +1 -1
  19. package/dist/types/v1/components/forms/FormField/FormField.d.ts +3 -3
  20. package/dist/types/v1/components/forms/FormGrid/FormGrid.d.ts +2 -0
  21. package/dist/types/v1/components/lists/ListFinder/ListFinder.d.ts +1 -1
  22. package/dist/types/v1/components/overlays/Dialog/Dialog.d.ts +3 -1
  23. package/dist/types/v1/components/tables/PaginatedTable/PaginatedTable.d.ts +1 -1
  24. package/dist/types/v1/components/tables/Table/Table.d.ts +1 -1
  25. package/dist/types/v1/primitives/buttons/Button/Button.d.ts +2 -0
  26. package/dist/types/v1/primitives/forms/Checkbox/Checkbox.d.ts +7 -0
  27. package/dist/types/v1/primitives/forms/LookupInput/LookupInput.d.ts +3 -0
  28. package/dist/types/v1/primitives/forms/SelectInput/SelectInput.d.ts +3 -0
  29. package/dist/types/v1/primitives/forms/TextArea/TextArea.d.ts +3 -0
  30. package/dist/types/v1/primitives/lists/ListItem/ListItem.d.ts +2 -1
  31. package/package.json +1 -1
  32. package/dist/esm/v1/primitives/lists/ListItem/ListCheckbox.js +0 -28
  33. package/dist/esm/v1/primitives/lists/ListItem/ListCheckbox.js.map +0 -1
  34. package/dist/types/v1/primitives/lists/ListItem/ListCheckbox.d.ts +0 -14
@@ -1 +1 @@
1
- {"version":3,"file":"FormField.js","sources":["../../../../../../src/v1/components/forms/FormField/FormField.tsx"],"sourcesContent":["import React, { useId } from 'react'\r\nimport { FieldContext } from '../../../primitives/forms/field'\r\nimport { useFormGrid } from '../FormGrid/FormGrid'\r\n\r\nexport interface FormFieldProps {\r\n label: string\r\n /** Asterisco vermelho na etiqueta; o campo lá dentro fica `required`. */\r\n required?: boolean\r\n /** Texto pequeno por baixo da etiqueta. */\r\n hint?: string\r\n /**\r\n * Marcas antes da etiqueta, a dizer de onde veio o valor (ex: lido do QR,\r\n * identificado pela IA). Usar [FieldMark](#fieldmark).\r\n */\r\n marks?: React.ReactNode\r\n /**\r\n * Lugares reservados para marcas, mesmo sem nenhuma. Assim a etiqueta não\r\n * salta quando uma marca aparece depois (ex: ao ler o QR).\r\n */\r\n markSlots?: number\r\n /** Algo junto à etiqueta, depois das marcas (ex: o valor máximo permitido). */\r\n aside?: React.ReactNode\r\n /** Depois do campo, na mesma linha (ex: cadeado, ícone de informação). */\r\n end?: React.ReactNode\r\n /** Ocupa a linha toda (ex: observações, anexo). */\r\n wide?: boolean\r\n /** Etiqueta alinhada ao topo em vez de ao meio (campos altos, como o TextArea). */\r\n alignTop?: boolean\r\n /** O campo: TextInput, SelectInput, TextArea ou outro. */\r\n children: React.ReactNode\r\n}\r\n\r\nconst MARK = 17\r\nconst MARK_GAP = 4\r\n\r\n/**\r\n * Uma linha do formulário: etiqueta e campo. Vai dentro de um\r\n * [FormGrid](?path=/docs/v1-components-forms-formgrid--docs).\r\n *\r\n * Liga a etiqueta ao campo (clicar na etiqueta foca o campo) e passa-lhe o\r\n * `required`, se o campo for TextInput, SelectInput ou TextArea.\r\n */\r\nexport default function FormField({ label, required, hint, marks, markSlots, aside, end, wide, alignTop, children }: FormFieldProps) {\r\n const id = useId()\r\n const { columns } = useFormGrid()\r\n const marksCount = React.Children.count(marks)\r\n const slots = Math.max(markSlots ?? 0, marksCount)\r\n\r\n return (\r\n <FieldContext.Provider value={{ id, required }}>\r\n <div\r\n className={`flex flex-col items-end pr-3 text-right @max-form-sm:items-start @max-form-sm:pr-0 @max-form-sm:pt-0 @max-form-sm:text-left ${alignTop ? 'self-start pt-[7px]' : ''}`}\r\n >\r\n {/* As marcas ficam fora do <label>, para não entrarem no nome do campo lido pelos leitores de ecrã. */}\r\n <div className=\"flex items-center justify-end @max-form-sm:justify-start\">\r\n {slots > 0 && (\r\n <span className=\"mr-[5px] inline-flex flex-none items-center justify-end gap-1\" style={{ width: slots * MARK + (slots - 1) * MARK_GAP }}>\r\n {marks}\r\n </span>\r\n )}\r\n {aside && <span className=\"mr-1.5 flex-none\">{aside}</span>}\r\n <label\r\n htmlFor={id}\r\n className=\"m-0 text-xs leading-[18px] font-medium whitespace-nowrap text-muted-foreground @max-form-md:whitespace-normal\"\r\n >\r\n {label}\r\n {required && (\r\n <span aria-hidden className=\"ml-0.5 text-destructive\">\r\n *\r\n </span>\r\n )}\r\n </label>\r\n </div>\r\n {hint && <span className=\"text-micro text-muted-foreground\">{hint}</span>}\r\n </div>\r\n <div className={`min-w-0 ${wide && columns === 2 ? 'col-span-3 @max-form-sm:col-span-1' : ''} ${end ? 'flex items-center gap-1.5' : ''}`}>\r\n {children}\r\n {end}\r\n </div>\r\n </FieldContext.Provider>\r\n )\r\n}\r\n\r\nexport interface FieldMarkProps {\r\n /** De onde veio o valor (ex: \"Lido do QR Code\"). Mostrado ao passar o rato e lido pelos leitores de ecrã. */\r\n title: string\r\n /** O ícone, a 11 px (ex: `<QrCodeIcon size={11} />`). */\r\n icon: React.ReactNode\r\n}\r\n\r\n/** Marca pequena antes da etiqueta de um FormField, a dizer de onde veio o valor. */\r\nexport function FieldMark({ title, icon }: FieldMarkProps) {\r\n return (\r\n <span\r\n role=\"img\"\r\n aria-label={title}\r\n title={title}\r\n className=\"inline-flex h-[17px] w-[17px] flex-none cursor-help items-center justify-center rounded-[5px] border border-primary/20 bg-primary/5 text-primary\"\r\n >\r\n {icon}\r\n </span>\r\n )\r\n}\r\n"],"names":[],"mappings":";;;;AAgCA,MAAM,OAAO;AACb,MAAM,WAAW;AASjB,SAAwB,UAAU,EAAE,OAAO,UAAU,MAAM,OAAO,WAAW,OAAO,KAAK,MAAM,UAAU,SAAA,GAA4B;AACnI,QAAM,KAAK,MAAA;AACX,QAAM,EAAE,QAAA,IAAY,YAAA;AACpB,QAAM,aAAa,MAAM,SAAS,MAAM,KAAK;AAC7C,QAAM,QAAQ,KAAK,IAAI,aAAa,GAAG,UAAU;AAEjD,SACE,qBAAC,aAAa,UAAb,EAAsB,OAAO,EAAE,IAAI,YAClC,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,+HAA+H,WAAW,wBAAwB,EAAE;AAAA,QAG/K,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,4DACZ,UAAA;AAAA,YAAA,QAAQ,KACP,oBAAC,QAAA,EAAK,WAAU,iEAAgE,OAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ,KAAK,YAC1H,UAAA,OACH;AAAA,YAED,SAAS,oBAAC,QAAA,EAAK,WAAU,oBAAoB,UAAA,OAAM;AAAA,YACpD;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAS;AAAA,gBACT,WAAU;AAAA,gBAET,UAAA;AAAA,kBAAA;AAAA,kBACA,YACC,oBAAC,QAAA,EAAK,eAAW,MAAC,WAAU,2BAA0B,UAAA,IAAA,CAEtD;AAAA,gBAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UAEJ,GACF;AAAA,UACC,QAAQ,oBAAC,QAAA,EAAK,WAAU,oCAAoC,UAAA,KAAA,CAAK;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEpE,qBAAC,OAAA,EAAI,WAAW,WAAW,QAAQ,YAAY,IAAI,uCAAuC,EAAE,IAAI,MAAM,8BAA8B,EAAE,IACnI,UAAA;AAAA,MAAA;AAAA,MACA;AAAA,IAAA,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;AAUO,SAAS,UAAU,EAAE,OAAO,QAAwB;AACzD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ;AAAA,MACA,WAAU;AAAA,MAET,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;"}
1
+ {"version":3,"file":"FormField.js","sources":["../../../../../../src/v1/components/forms/FormField/FormField.tsx"],"sourcesContent":["import React, { useId } from 'react'\r\nimport { FieldContext } from '../../../primitives/forms/field'\r\nimport { useFormGrid } from '../FormGrid/FormGrid'\r\n\r\nexport interface FormFieldProps {\r\n label: string\r\n /** Asterisco vermelho na etiqueta; o campo lá dentro fica `required`. */\r\n required?: boolean\r\n /** Texto pequeno por baixo da etiqueta. */\r\n hint?: string\r\n /**\r\n * Marcas antes da etiqueta, a dizer de onde veio o valor (ex: lido do QR,\r\n * identificado pela IA). Usar [FieldMark](#fieldmark).\r\n */\r\n marks?: React.ReactNode\r\n /**\r\n * Lugares reservados para marcas, mesmo sem nenhuma. Assim a etiqueta não\r\n * salta quando uma marca aparece depois (ex: ao ler o QR).\r\n */\r\n markSlots?: number\r\n /** Algo junto à etiqueta, depois das marcas (ex: o valor máximo permitido). */\r\n aside?: React.ReactNode\r\n /** Depois do campo, na mesma linha (ex: cadeado, ícone de informação). */\r\n end?: React.ReactNode\r\n /** Ocupa a linha toda (ex: observações, anexo). */\r\n wide?: boolean\r\n /** Etiqueta alinhada ao topo em vez de ao meio (campos altos, como o [TextArea](?path=/docs/v1-primitives-forms-textarea--docs)). */\r\n alignTop?: boolean\r\n /** O campo: [TextInput](?path=/docs/v1-primitives-forms-textinput--docs), [SelectInput](?path=/docs/v1-primitives-forms-selectinput--docs), [TextArea](?path=/docs/v1-primitives-forms-textarea--docs), [LookupInput](?path=/docs/v1-primitives-forms-lookupinput--docs) ou outro. */\r\n children: React.ReactNode\r\n}\r\n\r\nconst MARK = 17\r\nconst MARK_GAP = 4\r\n\r\n/**\r\n * Uma linha do formulário: etiqueta e campo. Vai dentro de um\r\n * [FormGrid](?path=/docs/v1-components-forms-formgrid--docs).\r\n *\r\n * Liga a etiqueta ao campo (clicar na etiqueta foca o campo) e passa-lhe o\r\n * `required`, se o campo for [TextInput](?path=/docs/v1-primitives-forms-textinput--docs), [SelectInput](?path=/docs/v1-primitives-forms-selectinput--docs), [TextArea](?path=/docs/v1-primitives-forms-textarea--docs) ou [LookupInput](?path=/docs/v1-primitives-forms-lookupinput--docs).\r\n */\r\nexport default function FormField({ label, required, hint, marks, markSlots, aside, end, wide, alignTop, children }: FormFieldProps) {\r\n const id = useId()\r\n const { columns } = useFormGrid()\r\n const marksCount = React.Children.count(marks)\r\n const slots = Math.max(markSlots ?? 0, marksCount)\r\n\r\n return (\r\n <FieldContext.Provider value={{ id, required }}>\r\n <div\r\n className={`flex flex-col items-end pr-3 text-right @max-form-sm:items-start @max-form-sm:pr-0 @max-form-sm:pt-0 @max-form-sm:text-left ${alignTop ? 'self-start pt-[7px]' : ''}`}\r\n >\r\n {/* As marcas ficam fora do <label>, para não entrarem no nome do campo lido pelos leitores de ecrã. */}\r\n <div className=\"flex items-center justify-end @max-form-sm:justify-start\">\r\n {slots > 0 && (\r\n <span className=\"mr-[5px] inline-flex flex-none items-center justify-end gap-1\" style={{ width: slots * MARK + (slots - 1) * MARK_GAP }}>\r\n {marks}\r\n </span>\r\n )}\r\n {aside && <span className=\"mr-1.5 flex-none\">{aside}</span>}\r\n <label\r\n htmlFor={id}\r\n className=\"m-0 text-xs leading-[18px] font-medium whitespace-nowrap text-muted-foreground @max-form-md:whitespace-normal\"\r\n >\r\n {label}\r\n {required && (\r\n <span aria-hidden className=\"ml-0.5 text-destructive\">\r\n *\r\n </span>\r\n )}\r\n </label>\r\n </div>\r\n {hint && <span className=\"text-micro text-muted-foreground\">{hint}</span>}\r\n </div>\r\n <div className={`min-w-0 ${wide && columns === 2 ? 'col-span-3 @max-form-sm:col-span-1' : ''} ${end ? 'flex items-center gap-1.5' : ''}`}>\r\n {children}\r\n {end}\r\n </div>\r\n </FieldContext.Provider>\r\n )\r\n}\r\n\r\nexport interface FieldMarkProps {\r\n /** De onde veio o valor (ex: \"Lido do QR Code\"). Mostrado ao passar o rato e lido pelos leitores de ecrã. */\r\n title: string\r\n /** O ícone, a 11 px (ex: `<QrCodeIcon size={11} />`). */\r\n icon: React.ReactNode\r\n}\r\n\r\n/** Marca pequena antes da etiqueta de um FormField, a dizer de onde veio o valor. */\r\nexport function FieldMark({ title, icon }: FieldMarkProps) {\r\n return (\r\n <span\r\n role=\"img\"\r\n aria-label={title}\r\n title={title}\r\n className=\"inline-flex h-[17px] w-[17px] flex-none cursor-help items-center justify-center rounded-[5px] border border-primary/20 bg-primary/5 text-primary\"\r\n >\r\n {icon}\r\n </span>\r\n )\r\n}\r\n"],"names":[],"mappings":";;;;AAgCA,MAAM,OAAO;AACb,MAAM,WAAW;AASjB,SAAwB,UAAU,EAAE,OAAO,UAAU,MAAM,OAAO,WAAW,OAAO,KAAK,MAAM,UAAU,SAAA,GAA4B;AACnI,QAAM,KAAK,MAAA;AACX,QAAM,EAAE,QAAA,IAAY,YAAA;AACpB,QAAM,aAAa,MAAM,SAAS,MAAM,KAAK;AAC7C,QAAM,QAAQ,KAAK,IAAI,aAAa,GAAG,UAAU;AAEjD,SACE,qBAAC,aAAa,UAAb,EAAsB,OAAO,EAAE,IAAI,YAClC,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,+HAA+H,WAAW,wBAAwB,EAAE;AAAA,QAG/K,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,4DACZ,UAAA;AAAA,YAAA,QAAQ,KACP,oBAAC,QAAA,EAAK,WAAU,iEAAgE,OAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ,KAAK,YAC1H,UAAA,OACH;AAAA,YAED,SAAS,oBAAC,QAAA,EAAK,WAAU,oBAAoB,UAAA,OAAM;AAAA,YACpD;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAS;AAAA,gBACT,WAAU;AAAA,gBAET,UAAA;AAAA,kBAAA;AAAA,kBACA,YACC,oBAAC,QAAA,EAAK,eAAW,MAAC,WAAU,2BAA0B,UAAA,IAAA,CAEtD;AAAA,gBAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UAEJ,GACF;AAAA,UACC,QAAQ,oBAAC,QAAA,EAAK,WAAU,oCAAoC,UAAA,KAAA,CAAK;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEpE,qBAAC,OAAA,EAAI,WAAW,WAAW,QAAQ,YAAY,IAAI,uCAAuC,EAAE,IAAI,MAAM,8BAA8B,EAAE,IACnI,UAAA;AAAA,MAAA;AAAA,MACA;AAAA,IAAA,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;AAUO,SAAS,UAAU,EAAE,OAAO,QAAwB;AACzD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ;AAAA,MACA,WAAU;AAAA,MAET,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;"}
@@ -1 +1 @@
1
- {"version":3,"file":"FormGrid.js","sources":["../../../../../../src/v1/components/forms/FormGrid/FormGrid.tsx"],"sourcesContent":["import React, { createContext, useContext } from 'react'\r\n\r\nexport interface FormGridProps {\r\n /** Campos por linha: 2 (etiqueta · campo · etiqueta · campo) ou 1. */\r\n columns?: 1 | 2\r\n className?: string\r\n children: React.ReactNode\r\n}\r\n\r\nconst GridContext = createContext<{ columns: 1 | 2 }>({ columns: 2 })\r\nexport const useFormGrid = () => useContext(GridContext)\r\n\r\nconst COLUMNS = {\r\n 2: 'grid-cols-[var(--spacing-form-label)_minmax(0,1fr)_var(--spacing-form-label)_minmax(0,1fr)] @max-form-md:grid-cols-[var(--spacing-form-label-narrow)_minmax(0,1fr)_var(--spacing-form-label-narrow)_minmax(0,1fr)]',\r\n 1: 'grid-cols-[var(--spacing-form-label)_minmax(0,1fr)] @max-form-md:grid-cols-[var(--spacing-form-label-narrow)_minmax(0,1fr)]',\r\n}\r\n\r\n/**\r\n * Grelha de um formulário: etiquetas à esquerda, alinhadas à direita, e os\r\n * campos ao lado. Os filhos são [FormField](?path=/docs/v1-components-forms-formfield--docs)\r\n * e, entre grupos, `FormDivider`.\r\n *\r\n * Adapta-se à largura do sítio onde está (não à do ecrã): abaixo de 680 px as\r\n * etiquetas estreitam; abaixo de 500 px cada etiqueta fica por cima do campo.\r\n */\r\nexport default function FormGrid({ columns = 2, className = '', children }: FormGridProps) {\r\n return (\r\n <GridContext.Provider value={{ columns }}>\r\n <div className={`@container ${className}`}>\r\n <div className={`grid items-center gap-x-3.5 gap-y-[13px] @max-form-sm:grid-cols-1 @max-form-sm:gap-y-1.5 ${COLUMNS[columns]}`}>{children}</div>\r\n </div>\r\n </GridContext.Provider>\r\n )\r\n}\r\n\r\n/** Linha de separação entre grupos de campos, a toda a largura. */\r\nexport function FormDivider() {\r\n return <div role=\"separator\" className=\"col-span-full my-0.5 h-px bg-border @max-form-sm:my-2\" />\r\n}\r\n"],"names":[],"mappings":";;AASA,MAAM,cAAc,cAAkC,EAAE,SAAS,GAAG;AAC7D,MAAM,cAAc,MAAM,WAAW,WAAW;AAEvD,MAAM,UAAU;AAAA,EACd,GAAG;AAAA,EACH,GAAG;AACL;AAUA,SAAwB,SAAS,EAAE,UAAU,GAAG,YAAY,IAAI,YAA2B;AACzF,SACE,oBAAC,YAAY,UAAZ,EAAqB,OAAO,EAAE,WAC7B,UAAA,oBAAC,OAAA,EAAI,WAAW,cAAc,SAAS,IACrC,UAAA,oBAAC,OAAA,EAAI,WAAW,4FAA4F,QAAQ,OAAO,CAAC,IAAK,SAAA,CAAS,EAAA,CAC5I,EAAA,CACF;AAEJ;AAGO,SAAS,cAAc;AAC5B,SAAO,oBAAC,OAAA,EAAI,MAAK,aAAY,WAAU,yDAAwD;AACjG;"}
1
+ {"version":3,"file":"FormGrid.js","sources":["../../../../../../src/v1/components/forms/FormGrid/FormGrid.tsx"],"sourcesContent":["import React, { createContext, useContext } from 'react'\r\n\r\nexport interface FormGridProps {\r\n /** Campos por linha: 2 (etiqueta · campo · etiqueta · campo) ou 1. */\r\n columns?: 1 | 2\r\n className?: string\r\n children: React.ReactNode\r\n}\r\n\r\nconst GridContext = createContext<{ columns: 1 | 2 }>({ columns: 2 })\r\nexport const useFormGrid = () => useContext(GridContext)\r\n\r\nconst COLUMNS = {\r\n 2: 'grid-cols-[var(--spacing-form-label)_minmax(0,1fr)_var(--spacing-form-label)_minmax(0,1fr)] @max-form-md:grid-cols-[var(--spacing-form-label-narrow)_minmax(0,1fr)_var(--spacing-form-label-narrow)_minmax(0,1fr)]',\r\n 1: 'grid-cols-[var(--spacing-form-label)_minmax(0,1fr)] @max-form-md:grid-cols-[var(--spacing-form-label-narrow)_minmax(0,1fr)]',\r\n}\r\n\r\n/**\r\n * Grelha de um formulário: etiquetas à esquerda, alinhadas à direita, e os\r\n * campos ao lado. Os filhos são [FormField](?path=/docs/v1-components-forms-formfield--docs)\r\n * e, entre grupos, `FormDivider`.\r\n *\r\n * Adapta-se à largura do sítio onde está (não à do ecrã): abaixo de 680 px as\r\n * etiquetas estreitam; abaixo de 500 px cada etiqueta fica por cima do campo.\r\n *\r\n * **Exemplo completo:** [Formulário em modal](?path=/docs/v1-patterns-forms-formulário-em-modal--docs).\r\n */\r\nexport default function FormGrid({ columns = 2, className = '', children }: FormGridProps) {\r\n return (\r\n <GridContext.Provider value={{ columns }}>\r\n <div className={`@container ${className}`}>\r\n <div className={`grid items-center gap-x-3.5 gap-y-[13px] @max-form-sm:grid-cols-1 @max-form-sm:gap-y-1.5 ${COLUMNS[columns]}`}>{children}</div>\r\n </div>\r\n </GridContext.Provider>\r\n )\r\n}\r\n\r\n/** Linha de separação entre grupos de campos, a toda a largura. */\r\nexport function FormDivider() {\r\n return <div role=\"separator\" className=\"col-span-full my-0.5 h-px bg-border @max-form-sm:my-2\" />\r\n}\r\n"],"names":[],"mappings":";;AASA,MAAM,cAAc,cAAkC,EAAE,SAAS,GAAG;AAC7D,MAAM,cAAc,MAAM,WAAW,WAAW;AAEvD,MAAM,UAAU;AAAA,EACd,GAAG;AAAA,EACH,GAAG;AACL;AAYA,SAAwB,SAAS,EAAE,UAAU,GAAG,YAAY,IAAI,YAA2B;AACzF,SACE,oBAAC,YAAY,UAAZ,EAAqB,OAAO,EAAE,WAC7B,UAAA,oBAAC,OAAA,EAAI,WAAW,cAAc,SAAS,IACrC,UAAA,oBAAC,OAAA,EAAI,WAAW,4FAA4F,QAAQ,OAAO,CAAC,IAAK,SAAA,CAAS,EAAA,CAC5I,EAAA,CACF;AAEJ;AAGO,SAAS,cAAc;AAC5B,SAAO,oBAAC,OAAA,EAAI,MAAK,aAAY,WAAU,yDAAwD;AACjG;"}
@@ -2,7 +2,7 @@ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { useState, useMemo, useEffect } from "react";
3
3
  import { ChevronLeftIcon, SearchIcon } from "../../../foundations/icons/icons.js";
4
4
  import ListItem from "../../../primitives/lists/ListItem/ListItem.js";
5
- import { ListCheckbox } from "../../../primitives/lists/ListItem/ListCheckbox.js";
5
+ import Checkbox from "../../../primitives/forms/Checkbox/Checkbox.js";
6
6
  import { OverlayScrollbar } from "../../tables/Table/OverlayScrollbar.js";
7
7
  import { DEFAULT_LIST_FINDER_LABELS } from "./labels.js";
8
8
  const semAcentos = (s) => s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
@@ -127,12 +127,12 @@ function ListFinder({
127
127
  ] }),
128
128
  marcaveis && visiveis.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 border-b border-slate-100 bg-slate-50 px-3 py-1.5 text-caption text-slate-500", children: [
129
129
  /* @__PURE__ */ jsx(
130
- ListCheckbox,
130
+ Checkbox,
131
131
  {
132
132
  checked: todasMarcadas,
133
- indeterminate: nMarcadasVisiveis > 0,
133
+ indeterminate: nMarcadasVisiveis > 0 && !todasMarcadas,
134
134
  onChange: marcarVisiveis,
135
- label: L.selectVisible(visiveis.length)
135
+ "aria-label": L.selectVisible(visiveis.length)
136
136
  }
137
137
  ),
138
138
  /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: nMarcadasVisiveis > 0 ? L.selectedCount(nMarcadasVisiveis, visiveis.length) : L.selectVisible(visiveis.length) }),
@@ -1 +1 @@
1
- {"version":3,"file":"ListFinder.js","sources":["../../../../../../src/v1/components/lists/ListFinder/ListFinder.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from 'react'\r\nimport { ChevronLeftIcon, SearchIcon } from '../../../foundations/icons/icons'\r\nimport ListItem, { type ListItemContent } from '../../../primitives/lists/ListItem/ListItem'\r\nimport { ListCheckbox } from '../../../primitives/lists/ListItem/ListCheckbox'\r\nimport { OverlayScrollbar } from '../../tables/Table/OverlayScrollbar'\r\nimport { DEFAULT_LIST_FINDER_LABELS, type ListFinderLabels } from './labels'\r\n\r\nexport interface ListFinderProps<T> {\r\n /** As linhas: todas, ou só a página atual quando o servidor pagina (com `total`). */\r\n items: T[]\r\n /** Uma chave única por linha (ex: o id). */\r\n itemKey: (item: T) => string\r\n /** O que cada linha mostra: as partes do [ListItem](?path=/docs/v1-primitives-lists-listitem--docs). */\r\n itemContent: (item: T) => ListItemContent\r\n /** A linha aberta (fica realçada). */\r\n activeKey?: string | null\r\n /** Ao clicar numa linha: normalmente, abri-la ao lado. */\r\n onItemClick?: (item: T) => void\r\n /** As marcadas. Só há caixas de marcar com `onSelectionChange`. */\r\n selectedKeys?: ReadonlySet<string>\r\n onSelectionChange?: (keys: Set<string>) => void\r\n /** Filtros ao lado do voltar, por cima da pesquisa (ex: um ou dois SelectInput). */\r\n filters?: React.ReactNode\r\n /** Com isto aparece um ‹ à esquerda, para fechar ou esconder a lista. */\r\n onBack?: () => void\r\n /** O texto da pesquisa. Sem ele, a pesquisa guarda o seu. */\r\n search?: string\r\n onSearchChange?: (search: string) => void\r\n /**\r\n * O texto onde a pesquisa procura, em cada linha. Com isto, a lista filtra-se\r\n * sozinha (sem acentos nem maiúsculas); sem isto, quem filtra é a app (ex: o\r\n * servidor, com `onSearchChange`). Uma lista colada (uma por linha, ou\r\n * separadas por vírgulas) mostra as que tiverem qualquer uma delas.\r\n */\r\n searchText?: (item: T) => string\r\n /** O servidor pagina: quantas linhas há lá ao todo. `items` é só a página atual. */\r\n total?: number\r\n /** A página atual (1, 2, ...), quando o servidor pagina. */\r\n page?: number\r\n onPageChange?: (page: number) => void\r\n /** Quantas linhas por página. Com o servidor a paginar, quantas ele manda. */\r\n pageSize?: number\r\n /** A carregar: sem linhas ainda, mostra o esqueleto delas. */\r\n loading?: boolean\r\n labels?: Partial<ListFinderLabels>\r\n className?: string\r\n}\r\n\r\nconst semAcentos = (s: string) => s.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase()\r\n\r\n/** Os números das páginas numa coluna estreita: no máximo cinco lugares. */\r\nfunction numerosPagina(atual: number, total: number): (number | '…')[] {\r\n if (total <= 5) return Array.from({ length: total }, (_, i) => i + 1)\r\n if (atual <= 2) return [1, 2, 3, '…', total]\r\n if (atual >= total - 1) return [1, '…', total - 2, total - 1, total]\r\n return [1, '…', atual, '…', total]\r\n}\r\n\r\n/**\r\n * Uma lista para encontrar e escolher: filtros e pesquisa em cima, as linhas\r\n * ([ListItem](?path=/docs/v1-primitives-lists-listitem--docs)) a rolar no meio e\r\n * as páginas em baixo. Pensada para a coluna ao lado do que se abre (a lista à\r\n * esquerda, o detalhe da linha escolhida à direita).\r\n *\r\n * Enche a altura do pai. O topo, a barra de \"selecionar todos\" e o rodapé\r\n * ficam sempre no sítio: só as linhas rolam.\r\n *\r\n * - **Pesquisa**: com `searchText` filtra sozinha; sem ele, a app filtra\r\n * (`search`/`onSearchChange`).\r\n * - **Páginas**: pagina sozinha (`pageSize`), ou é o servidor (`total`, `page`,\r\n * `onPageChange`).\r\n * - **Marcar vários**: com `onSelectionChange`. O \"selecionar todos\" só marca\r\n * as linhas à vista, nunca as que não se veem.\r\n */\r\nexport default function ListFinder<T>({\r\n items, itemKey, itemContent, activeKey, onItemClick, selectedKeys, onSelectionChange, filters, onBack,\r\n search: pesquisaDeFora, onSearchChange, searchText, total, page, onPageChange, pageSize = 50, loading = false,\r\n labels, className = '',\r\n}: ListFinderProps<T>) {\r\n const L = { ...DEFAULT_LIST_FINDER_LABELS, ...labels }\r\n const [pesquisaPropria, setPesquisaPropria] = useState('')\r\n const pesquisa = pesquisaDeFora ?? pesquisaPropria\r\n const pesquisar = (s: string) => { setPesquisaPropria(s); onSearchChange?.(s) }\r\n // Uma caixa de uma linha deita fora as quebras de linha: uma coluna colada\r\n // de uma folha de cálculo chegava como um número só, colado. Passam a vírgulas.\r\n const colar = (e: React.ClipboardEvent<HTMLInputElement>) => {\r\n const texto = e.clipboardData.getData('text')\r\n if (!/\\r?\\n/.test(texto.trim())) return\r\n e.preventDefault()\r\n const lista = texto.split(/\\r?\\n/).map(t => t.trim()).filter(Boolean).join(', ')\r\n const el = e.currentTarget\r\n pesquisar(el.value.slice(0, el.selectionStart ?? el.value.length) + lista + el.value.slice(el.selectionEnd ?? el.value.length))\r\n }\r\n\r\n const filtradas = useMemo(() => {\r\n if (!searchText || !pesquisa.trim()) return items\r\n const termos = pesquisa.split(/[\\n,;\\t]+/).map(t => semAcentos(t.trim())).filter(Boolean)\r\n return items.filter(it => {\r\n const texto = semAcentos(searchText(it))\r\n return termos.some(t => texto.includes(t))\r\n })\r\n }, [items, pesquisa, searchText])\r\n\r\n // Paginação: do servidor (com `total`), ou feita aqui.\r\n const peloServidor = total !== undefined\r\n const [paginaPropria, setPaginaPropria] = useState(1)\r\n const totalLinhas = peloServidor ? total : filtradas.length\r\n const totalPaginas = Math.max(1, Math.ceil(totalLinhas / pageSize))\r\n const pagina = Math.min(peloServidor ? (page ?? 1) : paginaPropria, totalPaginas)\r\n // Uma pesquisa nova volta à primeira página: a terceira de antes já não é a mesma lista.\r\n useEffect(() => { if (!peloServidor) setPaginaPropria(1) }, [pesquisa, peloServidor])\r\n const irPara = (p: number) => { if (peloServidor) onPageChange?.(p); else setPaginaPropria(p) }\r\n const visiveis = peloServidor ? filtradas : filtradas.slice((pagina - 1) * pageSize, pagina * pageSize)\r\n\r\n // Ao mudar de página, a lista volta ao topo.\r\n const [caixa, setCaixa] = useState<HTMLDivElement | null>(null)\r\n useEffect(() => { caixa?.scrollTo?.({ top: 0 }) }, [pagina, caixa])\r\n\r\n const marcadas = selectedKeys ?? new Set<string>()\r\n const marcaveis = !!onSelectionChange\r\n const nMarcadasVisiveis = visiveis.filter(it => marcadas.has(itemKey(it))).length\r\n const todasMarcadas = visiveis.length > 0 && nMarcadasVisiveis === visiveis.length\r\n const marcar = (key: string, sim: boolean) => {\r\n const n = new Set(marcadas)\r\n if (sim) n.add(key); else n.delete(key)\r\n onSelectionChange?.(n)\r\n }\r\n const marcarVisiveis = () => {\r\n const n = new Set(marcadas)\r\n for (const it of visiveis) { if (todasMarcadas) n.delete(itemKey(it)); else n.add(itemKey(it)) }\r\n onSelectionChange?.(n)\r\n }\r\n\r\n const aCarregar = loading && visiveis.length === 0\r\n const esqueleto = <span className=\"inline-block h-2 w-20 rounded-sm bg-slate-200 animate-pulse motion-reduce:animate-none\" aria-hidden />\r\n\r\n return (\r\n <div className={`v1-reset flex h-full min-h-0 flex-col border border-slate-200 bg-white text-control text-slate-800 ${className}`}>\r\n <div className=\"space-y-2 border-b border-slate-100 px-3 py-2.5\">\r\n {(onBack || filters) && (\r\n <div className=\"flex items-center gap-1.5\">\r\n {onBack && (\r\n <button type=\"button\" onClick={onBack} aria-label={L.back} title={L.back}\r\n className=\"-ml-1 flex h-7 w-6 shrink-0 items-center justify-center rounded text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700\">\r\n <ChevronLeftIcon size={14} />\r\n </button>\r\n )}\r\n <div className=\"flex min-w-0 flex-1 items-center gap-1.5\">{filters}</div>\r\n </div>\r\n )}\r\n <div className=\"relative\">\r\n <SearchIcon size={13} className=\"pointer-events-none absolute top-1/2 left-2.5 -translate-y-1/2 text-slate-400\" />\r\n <input\r\n type=\"search\"\r\n value={pesquisa}\r\n onChange={e => pesquisar(e.target.value)}\r\n onPaste={colar}\r\n placeholder={L.searchPlaceholder}\r\n aria-label={L.search}\r\n className=\"block h-control w-full min-w-0 rounded-lg border border-border bg-card pr-2.5 pl-8 text-control text-foreground outline-none transition-[border-color,box-shadow] duration-150 placeholder:text-muted-foreground/70 focus:border-primary/40 focus:ring-3 focus:ring-primary/10\"\r\n />\r\n </div>\r\n </div>\r\n\r\n {/* Fora da área que rola: é a cabeça da lista, e é a descer a lista que\r\n se decide marcá-la toda. */}\r\n {marcaveis && visiveis.length > 0 && (\r\n <div className=\"flex items-center gap-2.5 border-b border-slate-100 bg-slate-50 px-3 py-1.5 text-caption text-slate-500\">\r\n <ListCheckbox\r\n checked={todasMarcadas}\r\n indeterminate={nMarcadasVisiveis > 0}\r\n onChange={marcarVisiveis}\r\n label={L.selectVisible(visiveis.length)}\r\n />\r\n <span aria-hidden>{nMarcadasVisiveis > 0 ? L.selectedCount(nMarcadasVisiveis, visiveis.length) : L.selectVisible(visiveis.length)}</span>\r\n {marcadas.size > 0 && (\r\n <button type=\"button\" onClick={() => onSelectionChange?.(new Set())}\r\n className=\"ml-auto font-medium text-proef-blue hover:underline\">\r\n {L.clearSelection}\r\n </button>\r\n )}\r\n </div>\r\n )}\r\n\r\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\r\n <div ref={setCaixa} className=\"min-h-0 flex-1 overflow-y-auto scrollbar-none\">\r\n {aCarregar ? (\r\n // A forma das linhas já diz que vem ali a lista. Mais do que cabem,\r\n // cortadas pela caixa: chega ao fundo em qualquer altura.\r\n <div role=\"status\" aria-busy=\"true\" className=\"h-full overflow-hidden\">\r\n <span className=\"sr-only\">{L.loading}</span>\r\n {['w-11/12', 'w-3/4', 'w-5/6', 'w-2/3', 'w-4/5', 'w-7/12'].flatMap(l => [l, l, l, l]).map((largura, i) => (\r\n <div key={i} className=\"flex items-start gap-2.5 border-b border-slate-100 px-3 py-2.5 animate-pulse motion-reduce:animate-none\">\r\n {marcaveis && <div className=\"mt-0.5 h-4 w-4 shrink-0 border border-slate-200 bg-slate-100\" />}\r\n <div className=\"min-w-0 flex-1 space-y-1.5\">\r\n <div className=\"h-3 w-12 bg-slate-200\" />\r\n <div className=\"h-1.5 w-16 bg-slate-100\" />\r\n <div className={`h-2.5 ${largura} bg-slate-200`} />\r\n <div className=\"h-1.5 w-1/3 bg-slate-100\" />\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n ) : visiveis.length === 0 ? (\r\n <p className=\"m-0 px-3 py-6 text-center text-caption text-slate-400\">{L.empty}</p>\r\n ) : (\r\n <ul className=\"m-0 list-none p-0\">\r\n {visiveis.map(it => {\r\n const key = itemKey(it)\r\n return (\r\n <ListItem\r\n key={key}\r\n {...itemContent(it)}\r\n active={activeKey === key}\r\n onClick={onItemClick ? () => onItemClick(it) : undefined}\r\n checked={marcadas.has(key)}\r\n onCheckedChange={marcaveis ? sim => marcar(key, sim) : undefined}\r\n checkboxLabel={L.checkItem(key)}\r\n />\r\n )\r\n })}\r\n </ul>\r\n )}\r\n </div>\r\n <OverlayScrollbar alvo={caixa} borda={0} />\r\n </div>\r\n\r\n {/* Sempre à vista e com a mesma altura, carregada ou não: aparecer do\r\n nada fazia saltar o fundo do painel. */}\r\n <div className=\"flex h-11 shrink-0 items-center justify-between gap-2 border-t border-slate-100 bg-slate-50 px-3 text-caption text-slate-500\">\r\n <span className=\"whitespace-nowrap\">\r\n {aCarregar ? esqueleto : <><span className=\"font-semibold text-slate-800 tabular-nums\">{totalLinhas.toLocaleString('pt-PT')}</span> {L.itemsNoun(totalLinhas)}</>}\r\n </span>\r\n {!aCarregar && totalPaginas > 1 && (\r\n <nav aria-label={L.pagination} className=\"flex items-center gap-0.5\">\r\n <span className=\"sr-only\">{L.page} {pagina} / {totalPaginas}</span>\r\n {numerosPagina(pagina, totalPaginas).map((p, i) => p === '…' ? (\r\n <span key={`r${i}`} aria-hidden className=\"flex h-7 min-w-4 items-center justify-center text-slate-300\">…</span>\r\n ) : (\r\n <button\r\n key={p}\r\n type=\"button\"\r\n onClick={() => irPara(p)}\r\n aria-current={p === pagina ? 'page' : undefined}\r\n className={`h-7 min-w-7 rounded-md px-1.5 font-semibold tabular-nums transition-colors ${\r\n p === pagina ? 'cursor-default bg-primary text-white' : 'text-slate-500 hover:bg-slate-200/70 hover:text-slate-800'}`}\r\n >\r\n {p.toLocaleString('pt-PT')}\r\n </button>\r\n ))}\r\n </nav>\r\n )}\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ListFinder.js","sources":["../../../../../../src/v1/components/lists/ListFinder/ListFinder.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from 'react'\r\nimport { ChevronLeftIcon, SearchIcon } from '../../../foundations/icons/icons'\r\nimport ListItem, { type ListItemContent } from '../../../primitives/lists/ListItem/ListItem'\r\nimport Checkbox from '../../../primitives/forms/Checkbox/Checkbox'\r\nimport { OverlayScrollbar } from '../../tables/Table/OverlayScrollbar'\r\nimport { DEFAULT_LIST_FINDER_LABELS, type ListFinderLabels } from './labels'\r\n\r\nexport interface ListFinderProps<T> {\r\n /** As linhas: todas, ou só a página atual quando o servidor pagina (com `total`). */\r\n items: T[]\r\n /** Uma chave única por linha (ex: o id). */\r\n itemKey: (item: T) => string\r\n /** O que cada linha mostra: as partes do [ListItem](?path=/docs/v1-primitives-lists-listitem--docs). */\r\n itemContent: (item: T) => ListItemContent\r\n /** A linha aberta (fica realçada). */\r\n activeKey?: string | null\r\n /** Ao clicar numa linha: normalmente, abri-la ao lado. */\r\n onItemClick?: (item: T) => void\r\n /** As marcadas. Só há caixas de marcar com `onSelectionChange`. */\r\n selectedKeys?: ReadonlySet<string>\r\n onSelectionChange?: (keys: Set<string>) => void\r\n /** Filtros ao lado do voltar, por cima da pesquisa (ex: um ou dois [SelectInput](?path=/docs/v1-primitives-forms-selectinput--docs)). */\r\n filters?: React.ReactNode\r\n /** Com isto aparece um ‹ à esquerda, para fechar ou esconder a lista. */\r\n onBack?: () => void\r\n /** O texto da pesquisa. Sem ele, a pesquisa guarda o seu. */\r\n search?: string\r\n onSearchChange?: (search: string) => void\r\n /**\r\n * O texto onde a pesquisa procura, em cada linha. Com isto, a lista filtra-se\r\n * sozinha (sem acentos nem maiúsculas); sem isto, quem filtra é a app (ex: o\r\n * servidor, com `onSearchChange`). Uma lista colada (uma por linha, ou\r\n * separadas por vírgulas) mostra as que tiverem qualquer uma delas.\r\n */\r\n searchText?: (item: T) => string\r\n /** O servidor pagina: quantas linhas há lá ao todo. `items` é só a página atual. */\r\n total?: number\r\n /** A página atual (1, 2, ...), quando o servidor pagina. */\r\n page?: number\r\n onPageChange?: (page: number) => void\r\n /** Quantas linhas por página. Com o servidor a paginar, quantas ele manda. */\r\n pageSize?: number\r\n /** A carregar: sem linhas ainda, mostra o esqueleto delas. */\r\n loading?: boolean\r\n labels?: Partial<ListFinderLabels>\r\n className?: string\r\n}\r\n\r\nconst semAcentos = (s: string) => s.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase()\r\n\r\n/** Os números das páginas numa coluna estreita: no máximo cinco lugares. */\r\nfunction numerosPagina(atual: number, total: number): (number | '…')[] {\r\n if (total <= 5) return Array.from({ length: total }, (_, i) => i + 1)\r\n if (atual <= 2) return [1, 2, 3, '…', total]\r\n if (atual >= total - 1) return [1, '…', total - 2, total - 1, total]\r\n return [1, '…', atual, '…', total]\r\n}\r\n\r\n/**\r\n * Uma lista para encontrar e escolher: filtros e pesquisa em cima, as linhas\r\n * ([ListItem](?path=/docs/v1-primitives-lists-listitem--docs)) a rolar no meio e\r\n * as páginas em baixo. Pensada para a coluna ao lado do que se abre (a lista à\r\n * esquerda, o detalhe da linha escolhida à direita).\r\n *\r\n * Enche a altura do pai. O topo, a barra de \"selecionar todos\" e o rodapé\r\n * ficam sempre no sítio: só as linhas rolam.\r\n *\r\n * - **Pesquisa**: com `searchText` filtra sozinha; sem ele, a app filtra\r\n * (`search`/`onSearchChange`).\r\n * - **Páginas**: pagina sozinha (`pageSize`), ou é o servidor (`total`, `page`,\r\n * `onPageChange`).\r\n * - **Marcar vários**: com `onSelectionChange`. O \"selecionar todos\" só marca\r\n * as linhas à vista, nunca as que não se veem.\r\n */\r\nexport default function ListFinder<T>({\r\n items, itemKey, itemContent, activeKey, onItemClick, selectedKeys, onSelectionChange, filters, onBack,\r\n search: pesquisaDeFora, onSearchChange, searchText, total, page, onPageChange, pageSize = 50, loading = false,\r\n labels, className = '',\r\n}: ListFinderProps<T>) {\r\n const L = { ...DEFAULT_LIST_FINDER_LABELS, ...labels }\r\n const [pesquisaPropria, setPesquisaPropria] = useState('')\r\n const pesquisa = pesquisaDeFora ?? pesquisaPropria\r\n const pesquisar = (s: string) => { setPesquisaPropria(s); onSearchChange?.(s) }\r\n // Uma caixa de uma linha deita fora as quebras de linha: uma coluna colada\r\n // de uma folha de cálculo chegava como um número só, colado. Passam a vírgulas.\r\n const colar = (e: React.ClipboardEvent<HTMLInputElement>) => {\r\n const texto = e.clipboardData.getData('text')\r\n if (!/\\r?\\n/.test(texto.trim())) return\r\n e.preventDefault()\r\n const lista = texto.split(/\\r?\\n/).map(t => t.trim()).filter(Boolean).join(', ')\r\n const el = e.currentTarget\r\n pesquisar(el.value.slice(0, el.selectionStart ?? el.value.length) + lista + el.value.slice(el.selectionEnd ?? el.value.length))\r\n }\r\n\r\n const filtradas = useMemo(() => {\r\n if (!searchText || !pesquisa.trim()) return items\r\n const termos = pesquisa.split(/[\\n,;\\t]+/).map(t => semAcentos(t.trim())).filter(Boolean)\r\n return items.filter(it => {\r\n const texto = semAcentos(searchText(it))\r\n return termos.some(t => texto.includes(t))\r\n })\r\n }, [items, pesquisa, searchText])\r\n\r\n // Paginação: do servidor (com `total`), ou feita aqui.\r\n const peloServidor = total !== undefined\r\n const [paginaPropria, setPaginaPropria] = useState(1)\r\n const totalLinhas = peloServidor ? total : filtradas.length\r\n const totalPaginas = Math.max(1, Math.ceil(totalLinhas / pageSize))\r\n const pagina = Math.min(peloServidor ? (page ?? 1) : paginaPropria, totalPaginas)\r\n // Uma pesquisa nova volta à primeira página: a terceira de antes já não é a mesma lista.\r\n useEffect(() => { if (!peloServidor) setPaginaPropria(1) }, [pesquisa, peloServidor])\r\n const irPara = (p: number) => { if (peloServidor) onPageChange?.(p); else setPaginaPropria(p) }\r\n const visiveis = peloServidor ? filtradas : filtradas.slice((pagina - 1) * pageSize, pagina * pageSize)\r\n\r\n // Ao mudar de página, a lista volta ao topo.\r\n const [caixa, setCaixa] = useState<HTMLDivElement | null>(null)\r\n useEffect(() => { caixa?.scrollTo?.({ top: 0 }) }, [pagina, caixa])\r\n\r\n const marcadas = selectedKeys ?? new Set<string>()\r\n const marcaveis = !!onSelectionChange\r\n const nMarcadasVisiveis = visiveis.filter(it => marcadas.has(itemKey(it))).length\r\n const todasMarcadas = visiveis.length > 0 && nMarcadasVisiveis === visiveis.length\r\n const marcar = (key: string, sim: boolean) => {\r\n const n = new Set(marcadas)\r\n if (sim) n.add(key); else n.delete(key)\r\n onSelectionChange?.(n)\r\n }\r\n const marcarVisiveis = () => {\r\n const n = new Set(marcadas)\r\n for (const it of visiveis) { if (todasMarcadas) n.delete(itemKey(it)); else n.add(itemKey(it)) }\r\n onSelectionChange?.(n)\r\n }\r\n\r\n const aCarregar = loading && visiveis.length === 0\r\n const esqueleto = <span className=\"inline-block h-2 w-20 rounded-sm bg-slate-200 animate-pulse motion-reduce:animate-none\" aria-hidden />\r\n\r\n return (\r\n <div className={`v1-reset flex h-full min-h-0 flex-col border border-slate-200 bg-white text-control text-slate-800 ${className}`}>\r\n <div className=\"space-y-2 border-b border-slate-100 px-3 py-2.5\">\r\n {(onBack || filters) && (\r\n <div className=\"flex items-center gap-1.5\">\r\n {onBack && (\r\n <button type=\"button\" onClick={onBack} aria-label={L.back} title={L.back}\r\n className=\"-ml-1 flex h-7 w-6 shrink-0 items-center justify-center rounded text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700\">\r\n <ChevronLeftIcon size={14} />\r\n </button>\r\n )}\r\n <div className=\"flex min-w-0 flex-1 items-center gap-1.5\">{filters}</div>\r\n </div>\r\n )}\r\n <div className=\"relative\">\r\n <SearchIcon size={13} className=\"pointer-events-none absolute top-1/2 left-2.5 -translate-y-1/2 text-slate-400\" />\r\n <input\r\n type=\"search\"\r\n value={pesquisa}\r\n onChange={e => pesquisar(e.target.value)}\r\n onPaste={colar}\r\n placeholder={L.searchPlaceholder}\r\n aria-label={L.search}\r\n className=\"block h-control w-full min-w-0 rounded-lg border border-border bg-card pr-2.5 pl-8 text-control text-foreground outline-none transition-[border-color,box-shadow] duration-150 placeholder:text-muted-foreground/70 focus:border-primary/40 focus:ring-3 focus:ring-primary/10\"\r\n />\r\n </div>\r\n </div>\r\n\r\n {/* Fora da área que rola: é a cabeça da lista, e é a descer a lista que\r\n se decide marcá-la toda. */}\r\n {marcaveis && visiveis.length > 0 && (\r\n <div className=\"flex items-center gap-2.5 border-b border-slate-100 bg-slate-50 px-3 py-1.5 text-caption text-slate-500\">\r\n <Checkbox\r\n checked={todasMarcadas}\r\n indeterminate={nMarcadasVisiveis > 0 && !todasMarcadas}\r\n onChange={marcarVisiveis}\r\n aria-label={L.selectVisible(visiveis.length)}\r\n />\r\n <span aria-hidden>{nMarcadasVisiveis > 0 ? L.selectedCount(nMarcadasVisiveis, visiveis.length) : L.selectVisible(visiveis.length)}</span>\r\n {marcadas.size > 0 && (\r\n <button type=\"button\" onClick={() => onSelectionChange?.(new Set())}\r\n className=\"ml-auto font-medium text-proef-blue hover:underline\">\r\n {L.clearSelection}\r\n </button>\r\n )}\r\n </div>\r\n )}\r\n\r\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\r\n <div ref={setCaixa} className=\"min-h-0 flex-1 overflow-y-auto scrollbar-none\">\r\n {aCarregar ? (\r\n // A forma das linhas já diz que vem ali a lista. Mais do que cabem,\r\n // cortadas pela caixa: chega ao fundo em qualquer altura.\r\n <div role=\"status\" aria-busy=\"true\" className=\"h-full overflow-hidden\">\r\n <span className=\"sr-only\">{L.loading}</span>\r\n {['w-11/12', 'w-3/4', 'w-5/6', 'w-2/3', 'w-4/5', 'w-7/12'].flatMap(l => [l, l, l, l]).map((largura, i) => (\r\n <div key={i} className=\"flex items-start gap-2.5 border-b border-slate-100 px-3 py-2.5 animate-pulse motion-reduce:animate-none\">\r\n {marcaveis && <div className=\"mt-0.5 h-4 w-4 shrink-0 border border-slate-200 bg-slate-100\" />}\r\n <div className=\"min-w-0 flex-1 space-y-1.5\">\r\n <div className=\"h-3 w-12 bg-slate-200\" />\r\n <div className=\"h-1.5 w-16 bg-slate-100\" />\r\n <div className={`h-2.5 ${largura} bg-slate-200`} />\r\n <div className=\"h-1.5 w-1/3 bg-slate-100\" />\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n ) : visiveis.length === 0 ? (\r\n <p className=\"m-0 px-3 py-6 text-center text-caption text-slate-400\">{L.empty}</p>\r\n ) : (\r\n <ul className=\"m-0 list-none p-0\">\r\n {visiveis.map(it => {\r\n const key = itemKey(it)\r\n return (\r\n <ListItem\r\n key={key}\r\n {...itemContent(it)}\r\n active={activeKey === key}\r\n onClick={onItemClick ? () => onItemClick(it) : undefined}\r\n checked={marcadas.has(key)}\r\n onCheckedChange={marcaveis ? sim => marcar(key, sim) : undefined}\r\n checkboxLabel={L.checkItem(key)}\r\n />\r\n )\r\n })}\r\n </ul>\r\n )}\r\n </div>\r\n <OverlayScrollbar alvo={caixa} borda={0} />\r\n </div>\r\n\r\n {/* Sempre à vista e com a mesma altura, carregada ou não: aparecer do\r\n nada fazia saltar o fundo do painel. */}\r\n <div className=\"flex h-11 shrink-0 items-center justify-between gap-2 border-t border-slate-100 bg-slate-50 px-3 text-caption text-slate-500\">\r\n <span className=\"whitespace-nowrap\">\r\n {aCarregar ? esqueleto : <><span className=\"font-semibold text-slate-800 tabular-nums\">{totalLinhas.toLocaleString('pt-PT')}</span> {L.itemsNoun(totalLinhas)}</>}\r\n </span>\r\n {!aCarregar && totalPaginas > 1 && (\r\n <nav aria-label={L.pagination} className=\"flex items-center gap-0.5\">\r\n <span className=\"sr-only\">{L.page} {pagina} / {totalPaginas}</span>\r\n {numerosPagina(pagina, totalPaginas).map((p, i) => p === '…' ? (\r\n <span key={`r${i}`} aria-hidden className=\"flex h-7 min-w-4 items-center justify-center text-slate-300\">…</span>\r\n ) : (\r\n <button\r\n key={p}\r\n type=\"button\"\r\n onClick={() => irPara(p)}\r\n aria-current={p === pagina ? 'page' : undefined}\r\n className={`h-7 min-w-7 rounded-md px-1.5 font-semibold tabular-nums transition-colors ${\r\n p === pagina ? 'cursor-default bg-primary text-white' : 'text-slate-500 hover:bg-slate-200/70 hover:text-slate-800'}`}\r\n >\r\n {p.toLocaleString('pt-PT')}\r\n </button>\r\n ))}\r\n </nav>\r\n )}\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.js","sources":["../../../../../../src/v1/components/overlays/Dialog/Dialog.tsx"],"sourcesContent":["import React, { createContext, useContext, useEffect, useId, useRef, useState } from 'react'\r\nimport { createPortal } from 'react-dom'\r\n\r\nconst DialogContainerContext = createContext<HTMLElement | null>(null)\r\nconst DialogDepthContext = createContext(0)\r\n\r\n/**\r\n * O elemento da janela, para usar como destino de `createPortal` (ex: avisos,\r\n * confirmações, sobreposições) quando o conteúdo deve ficar junto dela. Um\r\n * portal para `document.body` também funciona, desde que fique por cima\r\n * (`z-index` acima de 200). Fora de um Dialog devolve `null`.\r\n *\r\n * `createPortal(conteudo, useDialogContainer() ?? document.body)`\r\n */\r\nexport const useDialogContainer = () => useContext(DialogContainerContext)\r\n\r\nexport interface DialogProps {\r\n /** Aberta ou fechada. A app guarda este estado. */\r\n open: boolean\r\n /** Pedido para fechar (×, Esc ou clique fora): a app põe `open` a `false`. */\r\n onClose: () => void\r\n title: string\r\n /** Linha por baixo do título (ex: nome · departamento). */\r\n subtitle?: string\r\n /** Botões no rodapé, sempre visíveis (ex: Confirmar, Fechar). */\r\n footer?: React.ReactNode\r\n /** Largura máxima: `sm` 480px, `md` 720px, `lg` 1000px. Nunca passa o ecrã. */\r\n size?: 'sm' | 'md' | 'lg'\r\n /**\r\n * Altura: `auto` (por omissão) cresce com o conteúdo; `full` ocupa sempre a\r\n * altura disponível (útil quando o conteúdo muda de tamanho e a janela não\r\n * deve saltar). Em ambos, se o conteúdo não couber, o corpo faz scroll.\r\n */\r\n height?: 'auto' | 'full'\r\n /** Fechar ao clicar fora da janela. Por omissão sim; num formulário longo pode valer a pena desligar. */\r\n closeOnBackdrop?: boolean\r\n /** Nome do botão ×, para leitores de ecrã. Por omissão: \"Fechar\". */\r\n closeLabel?: string\r\n /**\r\n * Corpo sem margens e sem scroll: o conteúdo ocupa-o todo e gere o seu próprio\r\n * scroll (ex: passos que deslizam, rodapé próprio de cada passo).\r\n */\r\n flush?: boolean\r\n children: React.ReactNode\r\n}\r\n\r\nconst WIDTHS = { sm: 'max-w-dialog-sm', md: 'max-w-dialog-md', lg: 'max-w-dialog-lg' }\r\n\r\ninterface Aberta {\r\n root: HTMLElement\r\n janela: HTMLElement | null\r\n depth: number\r\n}\r\n\r\nconst abertas: Aberta[] = []\r\nconst silenciados = new Set<Element>()\r\n\r\nconst doTopo = () => abertas[abertas.length - 1]\r\n\r\nfunction silenciar() {\r\n for (const el of silenciados) el.removeAttribute('inert')\r\n silenciados.clear()\r\n const topo = doTopo()\r\n if (!topo) return\r\n for (const el of Array.from(document.body.children)) {\r\n if (el === topo.root || el.hasAttribute('inert')) continue\r\n el.setAttribute('inert', '')\r\n silenciados.add(el)\r\n }\r\n}\r\n\r\n// Uma janela aberta dentro de outra fica sempre por cima dela, mesmo que abram juntas.\r\nfunction empilhar(aberta: Aberta) {\r\n const acima = abertas.findIndex((a) => a.depth > aberta.depth)\r\n if (acima === -1) abertas.push(aberta)\r\n else abertas.splice(acima, 0, aberta)\r\n}\r\n\r\n/**\r\n * Janela por cima da página: cabeçalho navy com título, corpo que faz scroll e\r\n * rodapé com os botões.\r\n *\r\n * É a janela modal da V1 (substitui o `Modal` legacy).\r\n *\r\n * Fundo escurecido, Esc para fechar e, ao fechar, o foco volta ao sítio onde\r\n * estava. Enquanto está aberta, o resto da página fica\r\n * [inert](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inert):\r\n * nem rato, nem teclado, nem leitores de ecrã lhe chegam. Os portais abertos\r\n * depois dela (seletores, confirmações) continuam a responder.\r\n *\r\n * O conteúdo fica montado mesmo com a janela fechada, para poder carregar antes\r\n * de ela abrir.\r\n *\r\n * O tamanho adapta-se ao ecrã só com CSS: nunca passa a largura nem a altura\r\n * da janela do browser (com uma margem), e acompanha-a quando é\r\n * redimensionada. O que não couber faz scroll no corpo; o cabeçalho e o\r\n * rodapé ficam sempre visíveis.\r\n */\r\nexport default function Dialog({\r\n open,\r\n onClose,\r\n title,\r\n subtitle,\r\n footer,\r\n size = 'md',\r\n height = 'auto',\r\n closeOnBackdrop = true,\r\n closeLabel = 'Fechar',\r\n flush = false,\r\n children,\r\n}: DialogProps) {\r\n const [root, setRoot] = useState<HTMLDivElement | null>(null)\r\n const janela = useRef<HTMLDivElement>(null)\r\n const fundo = useRef<HTMLDivElement>(null)\r\n const titleId = useId()\r\n const depth = useContext(DialogDepthContext)\r\n const fechar = useRef(onClose)\r\n fechar.current = onClose\r\n // Clique fora: só conta se começou e acabou no fundo (arrastar a partir de um\r\n // campo para fora não fecha a janela).\r\n const pressedBackdrop = useRef(false)\r\n\r\n useEffect(() => {\r\n if (!open || !root) return\r\n const anterior = document.activeElement as HTMLElement | null\r\n const aberta = { root, janela: janela.current, depth }\r\n empilhar(aberta)\r\n silenciar()\r\n const topo = doTopo()\r\n const dentro = topo.janela?.contains(document.activeElement)\r\n if (!dentro) topo.janela?.focus()\r\n\r\n const onKeyDown = (e: KeyboardEvent) => {\r\n if (e.key !== 'Escape' || e.defaultPrevented) return\r\n if (doTopo() !== aberta) return\r\n fechar.current()\r\n }\r\n document.addEventListener('keydown', onKeyDown)\r\n\r\n return () => {\r\n document.removeEventListener('keydown', onKeyDown)\r\n abertas.splice(abertas.indexOf(aberta), 1)\r\n silenciar()\r\n const voltar = anterior && !anterior.closest('[inert]')\r\n if (voltar) anterior.focus()\r\n else doTopo()?.janela?.focus()\r\n }\r\n }, [open, root, depth])\r\n\r\n // A página por trás não faz scroll enquanto a janela está aberta.\r\n useEffect(() => {\r\n if (!open) return\r\n const html = document.documentElement\r\n const before = html.style.overflow\r\n html.style.overflow = 'hidden'\r\n return () => {\r\n html.style.overflow = before\r\n }\r\n }, [open])\r\n\r\n const noFundo = (e: React.SyntheticEvent) => e.target === fundo.current || e.target === e.currentTarget\r\n\r\n return createPortal(\r\n <div\r\n ref={setRoot}\r\n hidden={!open}\r\n onPointerDown={(e) => {\r\n pressedBackdrop.current = noFundo(e)\r\n }}\r\n onClick={(e) => {\r\n if (closeOnBackdrop && pressedBackdrop.current && noFundo(e)) onClose()\r\n }}\r\n className={`fixed inset-0 z-[200] items-center justify-center p-dialog-margin ${open ? 'flex' : 'hidden'}`}\r\n >\r\n <div ref={fundo} className=\"absolute inset-0 bg-black/40 motion-safe:animate-backdrop-in\" />\r\n <div\r\n ref={janela}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby={titleId}\r\n tabIndex={-1}\r\n className={\r\n `relative flex max-h-full w-full ${WIDTHS[size]} flex-col overflow-hidden rounded-2xl bg-card text-foreground shadow-modal outline-none ` +\r\n `motion-safe:animate-dialog-in ${height === 'full' ? 'h-full' : ''}`\r\n }\r\n >\r\n <header className=\"flex flex-none items-center justify-between gap-3 bg-gradient-to-br from-primary to-primary-strong px-5 py-3.5\">\r\n <div className=\"min-w-0\">\r\n <h2 id={titleId} className=\"m-0 truncate text-title font-bold text-primary-foreground\">\r\n {title}\r\n </h2>\r\n {subtitle && <p className=\"m-0 mt-0.5 truncate text-caption text-primary-foreground/75\">{subtitle}</p>}\r\n </div>\r\n <button\r\n type=\"button\"\r\n aria-label={closeLabel}\r\n title={closeLabel}\r\n onClick={onClose}\r\n className=\"flex h-7 w-7 flex-none cursor-pointer items-center justify-center rounded-md border border-primary-foreground/25 bg-primary-foreground/15 text-base leading-none text-primary-foreground transition-colors hover:bg-primary-foreground/25 focus-visible:ring-2 focus-visible:ring-primary-foreground/60 focus-visible:outline-none\"\r\n >\r\n ×\r\n </button>\r\n </header>\r\n\r\n <DialogContainerContext.Provider value={root}>\r\n <DialogDepthContext.Provider value={depth + 1}>\r\n <div className={flush ? 'flex min-h-0 flex-1 flex-col overflow-hidden bg-canvas' : 'min-h-0 flex-1 overflow-y-auto bg-canvas px-5 py-4'}>{children}</div>\r\n\r\n {footer && <footer className=\"flex flex-none flex-wrap justify-end gap-2 border-t border-border bg-muted/40 px-5 py-[18px]\">{footer}</footer>}\r\n </DialogDepthContext.Provider>\r\n </DialogContainerContext.Provider>\r\n </div>\r\n </div>,\r\n document.body,\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Dialog.js","sources":["../../../../../../src/v1/components/overlays/Dialog/Dialog.tsx"],"sourcesContent":["import React, { createContext, useContext, useEffect, useId, useRef, useState } from 'react'\r\nimport { createPortal } from 'react-dom'\r\n\r\nconst DialogContainerContext = createContext<HTMLElement | null>(null)\r\nconst DialogDepthContext = createContext(0)\r\n\r\n/**\r\n * O elemento da janela, para usar como destino de `createPortal` (ex: avisos,\r\n * confirmações, sobreposições) quando o conteúdo deve ficar junto dela. Um\r\n * portal para `document.body` também funciona, desde que fique por cima\r\n * (`z-index` acima de 200). Fora de um Dialog devolve `null`.\r\n *\r\n * `createPortal(conteudo, useDialogContainer() ?? document.body)`\r\n */\r\nexport const useDialogContainer = () => useContext(DialogContainerContext)\r\n\r\nexport interface DialogProps {\r\n /** Aberta ou fechada. A app guarda este estado. */\r\n open: boolean\r\n /** Pedido para fechar (×, Esc ou clique fora): a app põe `open` a `false`. */\r\n onClose: () => void\r\n title: string\r\n /** Linha por baixo do título (ex: nome · departamento). */\r\n subtitle?: string\r\n /** Botões no rodapé, sempre visíveis (ex: Confirmar, Fechar). */\r\n footer?: React.ReactNode\r\n /** Largura máxima: `sm` 480px, `md` 720px, `lg` 1000px. Nunca passa o ecrã. */\r\n size?: 'sm' | 'md' | 'lg'\r\n /**\r\n * Altura: `auto` (por omissão) cresce com o conteúdo; `full` ocupa sempre a\r\n * altura disponível (útil quando o conteúdo muda de tamanho e a janela não\r\n * deve saltar). Em ambos, se o conteúdo não couber, o corpo faz scroll.\r\n */\r\n height?: 'auto' | 'full'\r\n /** Fechar ao clicar fora da janela. Por omissão sim; num formulário longo pode valer a pena desligar. */\r\n closeOnBackdrop?: boolean\r\n /** Nome do botão ×, para leitores de ecrã. Por omissão: \"Fechar\". */\r\n closeLabel?: string\r\n /**\r\n * Corpo sem margens e sem scroll: o conteúdo ocupa-o todo e gere o seu próprio\r\n * scroll (ex: passos que deslizam, rodapé próprio de cada passo).\r\n */\r\n flush?: boolean\r\n children: React.ReactNode\r\n}\r\n\r\nconst WIDTHS = { sm: 'max-w-dialog-sm', md: 'max-w-dialog-md', lg: 'max-w-dialog-lg' }\r\n\r\ninterface Aberta {\r\n root: HTMLElement\r\n janela: HTMLElement | null\r\n depth: number\r\n}\r\n\r\nconst abertas: Aberta[] = []\r\nconst silenciados = new Set<Element>()\r\n\r\nconst doTopo = () => abertas[abertas.length - 1]\r\n\r\nfunction silenciar() {\r\n for (const el of silenciados) el.removeAttribute('inert')\r\n silenciados.clear()\r\n const topo = doTopo()\r\n if (!topo) return\r\n for (const el of Array.from(document.body.children)) {\r\n if (el === topo.root || el.hasAttribute('inert')) continue\r\n el.setAttribute('inert', '')\r\n silenciados.add(el)\r\n }\r\n}\r\n\r\n// Uma janela aberta dentro de outra fica sempre por cima dela, mesmo que abram juntas.\r\nfunction empilhar(aberta: Aberta) {\r\n const acima = abertas.findIndex((a) => a.depth > aberta.depth)\r\n if (acima === -1) abertas.push(aberta)\r\n else abertas.splice(acima, 0, aberta)\r\n}\r\n\r\n/**\r\n * Janela por cima da página: cabeçalho navy com título, corpo que faz scroll e\r\n * rodapé com os botões ([Button](?path=/docs/v1-primitives-buttons-button--docs)).\r\n *\r\n * É a janela modal da V1 (substitui o `Modal` legacy).\r\n *\r\n * Fundo escurecido, Esc para fechar e, ao fechar, o foco volta ao sítio onde\r\n * estava. Enquanto está aberta, o resto da página fica\r\n * [inert](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inert):\r\n * nem rato, nem teclado, nem leitores de ecrã lhe chegam. Os portais abertos\r\n * depois dela (seletores, confirmações) continuam a responder.\r\n *\r\n * O conteúdo fica montado mesmo com a janela fechada, para poder carregar antes\r\n * de ela abrir.\r\n *\r\n * O tamanho adapta-se ao ecrã só com CSS: nunca passa a largura nem a altura\r\n * da janela do browser (com uma margem), e acompanha-a quando é\r\n * redimensionada. O que não couber faz scroll no corpo; o cabeçalho e o\r\n * rodapé ficam sempre visíveis.\r\n *\r\n * **Exemplo completo:** [Formulário em modal](?path=/docs/v1-patterns-forms-formulário-em-modal--docs).\r\n */\r\nexport default function Dialog({\r\n open,\r\n onClose,\r\n title,\r\n subtitle,\r\n footer,\r\n size = 'md',\r\n height = 'auto',\r\n closeOnBackdrop = true,\r\n closeLabel = 'Fechar',\r\n flush = false,\r\n children,\r\n}: DialogProps) {\r\n const [root, setRoot] = useState<HTMLDivElement | null>(null)\r\n const janela = useRef<HTMLDivElement>(null)\r\n const fundo = useRef<HTMLDivElement>(null)\r\n const titleId = useId()\r\n const depth = useContext(DialogDepthContext)\r\n const fechar = useRef(onClose)\r\n fechar.current = onClose\r\n // Clique fora: só conta se começou e acabou no fundo (arrastar a partir de um\r\n // campo para fora não fecha a janela).\r\n const pressedBackdrop = useRef(false)\r\n\r\n useEffect(() => {\r\n if (!open || !root) return\r\n const anterior = document.activeElement as HTMLElement | null\r\n const aberta = { root, janela: janela.current, depth }\r\n empilhar(aberta)\r\n silenciar()\r\n const topo = doTopo()\r\n const dentro = topo.janela?.contains(document.activeElement)\r\n if (!dentro) topo.janela?.focus()\r\n\r\n const onKeyDown = (e: KeyboardEvent) => {\r\n if (e.key !== 'Escape' || e.defaultPrevented) return\r\n if (doTopo() !== aberta) return\r\n fechar.current()\r\n }\r\n document.addEventListener('keydown', onKeyDown)\r\n\r\n return () => {\r\n document.removeEventListener('keydown', onKeyDown)\r\n abertas.splice(abertas.indexOf(aberta), 1)\r\n silenciar()\r\n const voltar = anterior && !anterior.closest('[inert]')\r\n if (voltar) anterior.focus()\r\n else doTopo()?.janela?.focus()\r\n }\r\n }, [open, root, depth])\r\n\r\n // A página por trás não faz scroll enquanto a janela está aberta.\r\n useEffect(() => {\r\n if (!open) return\r\n const html = document.documentElement\r\n const before = html.style.overflow\r\n html.style.overflow = 'hidden'\r\n return () => {\r\n html.style.overflow = before\r\n }\r\n }, [open])\r\n\r\n const noFundo = (e: React.SyntheticEvent) => e.target === fundo.current || e.target === e.currentTarget\r\n\r\n return createPortal(\r\n <div\r\n ref={setRoot}\r\n hidden={!open}\r\n onPointerDown={(e) => {\r\n pressedBackdrop.current = noFundo(e)\r\n }}\r\n onClick={(e) => {\r\n if (closeOnBackdrop && pressedBackdrop.current && noFundo(e)) onClose()\r\n }}\r\n className={`fixed inset-0 z-[200] items-center justify-center p-dialog-margin ${open ? 'flex' : 'hidden'}`}\r\n >\r\n <div ref={fundo} className=\"absolute inset-0 bg-black/40 motion-safe:animate-backdrop-in\" />\r\n <div\r\n ref={janela}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby={titleId}\r\n tabIndex={-1}\r\n className={\r\n `relative flex max-h-full w-full ${WIDTHS[size]} flex-col overflow-hidden rounded-2xl bg-card text-foreground shadow-modal outline-none ` +\r\n `motion-safe:animate-dialog-in ${height === 'full' ? 'h-full' : ''}`\r\n }\r\n >\r\n <header className=\"flex flex-none items-center justify-between gap-3 bg-gradient-to-br from-primary to-primary-strong px-5 py-3.5\">\r\n <div className=\"min-w-0\">\r\n <h2 id={titleId} className=\"m-0 truncate text-title font-bold text-primary-foreground\">\r\n {title}\r\n </h2>\r\n {subtitle && <p className=\"m-0 mt-0.5 truncate text-caption text-primary-foreground/75\">{subtitle}</p>}\r\n </div>\r\n <button\r\n type=\"button\"\r\n aria-label={closeLabel}\r\n title={closeLabel}\r\n onClick={onClose}\r\n className=\"flex h-7 w-7 flex-none cursor-pointer items-center justify-center rounded-md border border-primary-foreground/25 bg-primary-foreground/15 text-base leading-none text-primary-foreground transition-colors hover:bg-primary-foreground/25 focus-visible:ring-2 focus-visible:ring-primary-foreground/60 focus-visible:outline-none\"\r\n >\r\n ×\r\n </button>\r\n </header>\r\n\r\n <DialogContainerContext.Provider value={root}>\r\n <DialogDepthContext.Provider value={depth + 1}>\r\n <div className={flush ? 'flex min-h-0 flex-1 flex-col overflow-hidden bg-canvas' : 'min-h-0 flex-1 overflow-y-auto bg-canvas px-5 py-4'}>{children}</div>\r\n\r\n {footer && <footer className=\"flex flex-none flex-wrap justify-end gap-2 border-t border-border bg-muted/40 px-5 py-[18px]\">{footer}</footer>}\r\n </DialogDepthContext.Provider>\r\n </DialogContainerContext.Provider>\r\n </div>\r\n </div>,\r\n document.body,\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PaginatedTable.js","sources":["../../../../../../src/v1/components/tables/PaginatedTable/PaginatedTable.tsx"],"sourcesContent":["import React, { useState } from 'react'\r\nimport { TableBase, type TableProps } from '../Table/Table'\r\n\r\nexport interface PaginatedTableProps<T> extends TableProps<T> {\r\n /**\r\n * Total de linhas no servidor, quando é ele que pagina (`page` e\r\n * `onPageChange`). É o número da contagem e dá o número de páginas.\r\n */\r\n total?: number\r\n /**\r\n * Página atual (a contar de 1), quando é o servidor que pagina: a app pede a\r\n * página e dá-a em `data`. Sem isto, a tabela pagina-se a si mesma com todas\r\n * as linhas de `data`.\r\n */\r\n page?: number\r\n onPageChange?: (page: number) => void\r\n /** Tamanhos de página à escolha (ex: `[60, 120, 250, 500]`). Sem isto não há escolha. */\r\n pageSizeOptions?: number[]\r\n /**\r\n * A pessoa escolheu outro tamanho de página. Quando é o servidor que pagina,\r\n * a app volta à página 1 (ficar na 7.ª com 500 por página caía fora da lista).\r\n * Sem isto, a tabela guarda a escolha ela própria.\r\n */\r\n onPageSizeChange?: (pageSize: number) => void\r\n}\r\n\r\n/**\r\n * A `Table` para listas de página inteira: a paginação fica EM CIMA, numa\r\n * barra com a contagem de linhas, o tamanho de página e os números de página,\r\n * em vez de no fundo.\r\n *\r\n * Funciona de duas maneiras:\r\n * - **O servidor pagina** (`page`, `onPageChange`, `total`): `data` é a\r\n * página atual. Com filtros, ordenação ou agrupamento no cabeçalho, a tabela\r\n * passa a paginar o conjunto completo (`allData`) e a barra mostra as\r\n * páginas dela.\r\n * - **Todas as linhas de uma vez** (só `data`): a tabela pagina-se a si mesma.\r\n *\r\n * Por omissão ocupa a altura do pai (`fillHeight`), com as barras de scroll\r\n * por cima das linhas, linhas-esqueleto a carregar e a aresta direita\r\n * esbatida. Qualquer uma pode ser desligada.\r\n */\r\nexport default function PaginatedTable<T>({\r\n total,\r\n page,\r\n onPageChange,\r\n pageSizeOptions,\r\n onPageSizeChange,\r\n pageSize,\r\n data,\r\n allData,\r\n alwaysClient,\r\n fillHeight = true,\r\n overlayScrollbars = true,\r\n horizontalScrollbarBelow = true,\r\n skeleton = true,\r\n fadeRight = true,\r\n ...resto\r\n}: PaginatedTableProps<T>) {\r\n const [tamanhoProprio, setTamanhoProprio] = useState(pageSize ?? pageSizeOptions?.[0] ?? 50)\r\n const tamanho = onPageSizeChange ? (pageSize ?? pageSizeOptions?.[0] ?? 50) : tamanhoProprio\r\n // Sem paginação do servidor, todas as linhas são o conjunto completo e a tabela pagina-as.\r\n const peloServidor = !!onPageChange && page !== undefined\r\n return (\r\n <TableBase\r\n {...resto}\r\n data={data}\r\n allData={peloServidor ? allData : (allData ?? data)}\r\n alwaysClient={peloServidor ? alwaysClient : true}\r\n pageSize={tamanho}\r\n fillHeight={fillHeight}\r\n overlayScrollbars={overlayScrollbars}\r\n horizontalScrollbarBelow={horizontalScrollbarBelow}\r\n skeleton={skeleton}\r\n fadeRight={fadeRight}\r\n paginacaoNoTopo={{\r\n total,\r\n page,\r\n onPageChange,\r\n pageSizeOptions,\r\n onPageSizeChange: onPageSizeChange ?? (pageSizeOptions ? setTamanhoProprio : undefined),\r\n }}\r\n />\r\n )\r\n}\r\n"],"names":[],"mappings":";;;AA0CA,SAAwB,eAAkB;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,oBAAoB;AAAA,EACpB,2BAA2B;AAAA,EAC3B,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,GAAG;AACL,GAA2B;AACzB,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,YAAY,kBAAkB,CAAC,KAAK,EAAE;AAC3F,QAAM,UAAU,mBAAoB,YAAY,kBAAkB,CAAC,KAAK,KAAM;AAE9E,QAAM,eAAe,CAAC,CAAC,gBAAgB,SAAS;AAChD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ;AAAA,MACA,SAAS,eAAe,UAAW,WAAW;AAAA,MAC9C,cAAc,eAAe,eAAe;AAAA,MAC5C,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,iBAAiB;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,kBAAkB,qBAAqB,kBAAkB,oBAAoB;AAAA,MAAA;AAAA,IAC/E;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"PaginatedTable.js","sources":["../../../../../../src/v1/components/tables/PaginatedTable/PaginatedTable.tsx"],"sourcesContent":["import React, { useState } from 'react'\r\nimport { TableBase, type TableProps } from '../Table/Table'\r\n\r\nexport interface PaginatedTableProps<T> extends TableProps<T> {\r\n /**\r\n * Total de linhas no servidor, quando é ele que pagina (`page` e\r\n * `onPageChange`). É o número da contagem e dá o número de páginas.\r\n */\r\n total?: number\r\n /**\r\n * Página atual (a contar de 1), quando é o servidor que pagina: a app pede a\r\n * página e dá-a em `data`. Sem isto, a tabela pagina-se a si mesma com todas\r\n * as linhas de `data`.\r\n */\r\n page?: number\r\n onPageChange?: (page: number) => void\r\n /** Tamanhos de página à escolha (ex: `[60, 120, 250, 500]`). Sem isto não há escolha. */\r\n pageSizeOptions?: number[]\r\n /**\r\n * A pessoa escolheu outro tamanho de página. Quando é o servidor que pagina,\r\n * a app volta à página 1 (ficar na 7.ª com 500 por página caía fora da lista).\r\n * Sem isto, a tabela guarda a escolha ela própria.\r\n */\r\n onPageSizeChange?: (pageSize: number) => void\r\n}\r\n\r\n/**\r\n * A [Table](?path=/docs/v1-components-tables-table--docs) para listas de página inteira: a paginação fica EM CIMA, numa\r\n * barra com a contagem de linhas, o tamanho de página e os números de página,\r\n * em vez de no fundo.\r\n *\r\n * Funciona de duas maneiras:\r\n * - **O servidor pagina** (`page`, `onPageChange`, `total`): `data` é a\r\n * página atual. Com filtros, ordenação ou agrupamento no cabeçalho, a tabela\r\n * passa a paginar o conjunto completo (`allData`) e a barra mostra as\r\n * páginas dela.\r\n * - **Todas as linhas de uma vez** (só `data`): a tabela pagina-se a si mesma.\r\n *\r\n * Por omissão ocupa a altura do pai (`fillHeight`), com as barras de scroll\r\n * por cima das linhas, linhas-esqueleto a carregar e a aresta direita\r\n * esbatida. Qualquer uma pode ser desligada.\r\n */\r\nexport default function PaginatedTable<T>({\r\n total,\r\n page,\r\n onPageChange,\r\n pageSizeOptions,\r\n onPageSizeChange,\r\n pageSize,\r\n data,\r\n allData,\r\n alwaysClient,\r\n fillHeight = true,\r\n overlayScrollbars = true,\r\n horizontalScrollbarBelow = true,\r\n skeleton = true,\r\n fadeRight = true,\r\n ...resto\r\n}: PaginatedTableProps<T>) {\r\n const [tamanhoProprio, setTamanhoProprio] = useState(pageSize ?? pageSizeOptions?.[0] ?? 50)\r\n const tamanho = onPageSizeChange ? (pageSize ?? pageSizeOptions?.[0] ?? 50) : tamanhoProprio\r\n // Sem paginação do servidor, todas as linhas são o conjunto completo e a tabela pagina-as.\r\n const peloServidor = !!onPageChange && page !== undefined\r\n return (\r\n <TableBase\r\n {...resto}\r\n data={data}\r\n allData={peloServidor ? allData : (allData ?? data)}\r\n alwaysClient={peloServidor ? alwaysClient : true}\r\n pageSize={tamanho}\r\n fillHeight={fillHeight}\r\n overlayScrollbars={overlayScrollbars}\r\n horizontalScrollbarBelow={horizontalScrollbarBelow}\r\n skeleton={skeleton}\r\n fadeRight={fadeRight}\r\n paginacaoNoTopo={{\r\n total,\r\n page,\r\n onPageChange,\r\n pageSizeOptions,\r\n onPageSizeChange: onPageSizeChange ?? (pageSizeOptions ? setTamanhoProprio : undefined),\r\n }}\r\n />\r\n )\r\n}\r\n"],"names":[],"mappings":";;;AA0CA,SAAwB,eAAkB;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,oBAAoB;AAAA,EACpB,2BAA2B;AAAA,EAC3B,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,GAAG;AACL,GAA2B;AACzB,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,YAAY,kBAAkB,CAAC,KAAK,EAAE;AAC3F,QAAM,UAAU,mBAAoB,YAAY,kBAAkB,CAAC,KAAK,KAAM;AAE9E,QAAM,eAAe,CAAC,CAAC,gBAAgB,SAAS;AAChD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ;AAAA,MACA,SAAS,eAAe,UAAW,WAAW;AAAA,MAC9C,cAAc,eAAe,eAAe;AAAA,MAC5C,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,iBAAiB;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,kBAAkB,qBAAqB,kBAAkB,oBAAoB;AAAA,MAAA;AAAA,IAC/E;AAAA,EAAA;AAGN;"}