@1doc/1ds-react 2.9.0 → 2.11.0

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 (172) hide show
  1. package/CHANGELOG.md +653 -0
  2. package/dist/components/Accordion/Accordion.css +13 -0
  3. package/dist/components/Accordion/Accordion.d.ts +9 -0
  4. package/dist/components/Accordion/Accordion.js +18 -5
  5. package/dist/components/AppSubheader/AppSubheader.css +10 -1
  6. package/dist/components/AttachmentCard/AttachmentCard.css +10 -8
  7. package/dist/components/AttachmentCard/AttachmentCard.d.ts +3 -0
  8. package/dist/components/AttachmentCard/AttachmentCard.js +8 -1
  9. package/dist/components/Badge/Badge.css +27 -5
  10. package/dist/components/Badge/Badge.d.ts +20 -4
  11. package/dist/components/Badge/Badge.js +6 -0
  12. package/dist/components/Button/Button.css +5 -0
  13. package/dist/components/Button/Button.d.ts +3 -0
  14. package/dist/components/Button/Button.js +1 -0
  15. package/dist/components/Calendar/Calendar.css +10 -0
  16. package/dist/components/Calendar/Calendar.d.ts +10 -6
  17. package/dist/components/Calendar/Calendar.js +6 -1
  18. package/dist/components/Coachmark/Coachmark.css +10 -0
  19. package/dist/components/Coachmark/Coachmark.d.ts +14 -1
  20. package/dist/components/Coachmark/Coachmark.js +51 -5
  21. package/dist/components/ColorPicker/ColorPicker.css +8 -1
  22. package/dist/components/ColorPicker/ColorPicker.d.ts +2 -2
  23. package/dist/components/DataTable/DataTable.css +216 -0
  24. package/dist/components/DataTable/DataTable.d.ts +70 -2
  25. package/dist/components/DataTable/DataTable.js +293 -6
  26. package/dist/components/DescriptionList/DescriptionList.css +14 -0
  27. package/dist/components/DescriptionList/DescriptionList.d.ts +3 -0
  28. package/dist/components/DescriptionList/DescriptionList.js +1 -0
  29. package/dist/components/EmptyState/EmptyState.d.ts +4 -2
  30. package/dist/components/EmptyState/EmptyState.js +2 -2
  31. package/dist/components/Input/Input.css +30 -0
  32. package/dist/components/Input/Input.d.ts +10 -0
  33. package/dist/components/Input/Input.js +3 -1
  34. package/dist/components/List/ListItem.d.ts +14 -2
  35. package/dist/components/List/ListItem.js +3 -2
  36. package/dist/components/Modal/Modal.css +19 -0
  37. package/dist/components/Modal/Modal.d.ts +7 -0
  38. package/dist/components/Modal/Modal.js +1 -0
  39. package/dist/components/RichTextEditor/RichTextEditor.css +47 -0
  40. package/dist/components/RichTextEditor/RichTextEditor.d.ts +22 -8
  41. package/dist/components/RichTextEditor/RichTextEditor.js +460 -3
  42. package/dist/components/SegmentedControl/SegmentedControl.css +70 -0
  43. package/dist/components/SegmentedControl/SegmentedControl.d.ts +5 -0
  44. package/dist/components/SegmentedControl/SegmentedControl.js +5 -1
  45. package/dist/components/Select/Select.css +122 -2
  46. package/dist/components/Select/Select.d.ts +33 -4
  47. package/dist/components/Select/Select.js +256 -35
  48. package/dist/components/StatCard/StatCard.css +73 -0
  49. package/dist/components/StatCard/StatCard.d.ts +12 -0
  50. package/dist/components/StatCard/StatCard.js +11 -1
  51. package/dist/components/Tabs/Tabs.css +11 -0
  52. package/dist/components/Tabs/Tabs.d.ts +7 -0
  53. package/dist/components/Tabs/Tabs.js +7 -1
  54. package/dist/components/Tag/Tag.css +3 -1
  55. package/dist/components/Toast/Toast.css +32 -0
  56. package/dist/components/Toast/Toast.d.ts +18 -0
  57. package/dist/components/Toast/Toast.js +14 -1
  58. package/dist/components/Toast/ToastStack.js +8 -1
  59. package/dist/teste/AccordionControlado.test.d.ts +1 -0
  60. package/dist/teste/AccordionControlado.test.js +73 -0
  61. package/dist/teste/AccordionTituloNeutro.test.d.ts +1 -0
  62. package/dist/teste/AccordionTituloNeutro.test.js +23 -0
  63. package/dist/teste/AppSubheaderRegiao.test.d.ts +1 -0
  64. package/dist/teste/AppSubheaderRegiao.test.js +13 -0
  65. package/dist/teste/AttachmentCardLista.test.d.ts +1 -0
  66. package/dist/teste/AttachmentCardLista.test.js +49 -0
  67. package/dist/teste/AttachmentCardSm.test.d.ts +1 -0
  68. package/dist/teste/AttachmentCardSm.test.js +26 -0
  69. package/dist/teste/BadgeAttachedTomEPadding.test.d.ts +1 -0
  70. package/dist/teste/BadgeAttachedTomEPadding.test.js +43 -0
  71. package/dist/teste/BadgeNeutroColado.test.d.ts +1 -0
  72. package/dist/teste/BadgeNeutroColado.test.js +11 -0
  73. package/dist/teste/BadgeTag.test.js +13 -0
  74. package/dist/teste/ButtonDensidade.test.d.ts +1 -0
  75. package/dist/teste/ButtonDensidade.test.js +28 -0
  76. package/dist/teste/CalendarCabecalho.test.d.ts +1 -0
  77. package/dist/teste/CalendarCabecalho.test.js +11 -0
  78. package/dist/teste/CalendarCompacto.test.d.ts +1 -0
  79. package/dist/teste/CalendarCompacto.test.js +29 -0
  80. package/dist/teste/CoachmarkSemVeu.test.d.ts +1 -0
  81. package/dist/teste/CoachmarkSemVeu.test.js +85 -0
  82. package/dist/teste/CoachmarkUmPasso.test.d.ts +1 -0
  83. package/dist/teste/CoachmarkUmPasso.test.js +34 -0
  84. package/dist/teste/ColorPickerXs.test.d.ts +1 -0
  85. package/dist/teste/ColorPickerXs.test.js +21 -0
  86. package/dist/teste/DataTableArvoreEVariantes.test.d.ts +1 -0
  87. package/dist/teste/DataTableArvoreEVariantes.test.js +190 -0
  88. package/dist/teste/DataTableCompactaETom.test.d.ts +1 -0
  89. package/dist/teste/DataTableCompactaETom.test.js +69 -0
  90. package/dist/teste/DescriptionListCompacta.test.d.ts +1 -0
  91. package/dist/teste/DescriptionListCompacta.test.js +22 -0
  92. package/dist/teste/EmptyStateTituloRico.test.d.ts +1 -0
  93. package/dist/teste/EmptyStateTituloRico.test.js +23 -0
  94. package/dist/teste/InputAgrupado.test.d.ts +1 -0
  95. package/dist/teste/InputAgrupado.test.js +24 -0
  96. package/dist/teste/InputGhost.test.d.ts +1 -0
  97. package/dist/teste/InputGhost.test.js +32 -0
  98. package/dist/teste/ListItemTooltipStandalone.test.d.ts +1 -0
  99. package/dist/teste/ListItemTooltipStandalone.test.js +39 -0
  100. package/dist/teste/ModalProporcoes1Doc.test.d.ts +1 -0
  101. package/dist/teste/ModalProporcoes1Doc.test.js +32 -0
  102. package/dist/teste/RichTextEditorAlinhaImagemTabela.test.d.ts +1 -0
  103. package/dist/teste/RichTextEditorAlinhaImagemTabela.test.js +212 -0
  104. package/dist/teste/RichTextEditorEstiloDeTexto.test.d.ts +1 -0
  105. package/dist/teste/RichTextEditorEstiloDeTexto.test.js +120 -0
  106. package/dist/teste/SegmentedControlContador.test.d.ts +1 -0
  107. package/dist/teste/SegmentedControlContador.test.js +39 -0
  108. package/dist/teste/SegmentedControlSuperficie.test.d.ts +1 -0
  109. package/dist/teste/SegmentedControlSuperficie.test.js +39 -0
  110. package/dist/teste/SelectAbertoControladoEFlutuante.test.d.ts +1 -0
  111. package/dist/teste/SelectAbertoControladoEFlutuante.test.js +145 -0
  112. package/dist/teste/SelectFiltroCorETamanho.test.d.ts +1 -0
  113. package/dist/teste/SelectFiltroCorETamanho.test.js +77 -0
  114. package/dist/teste/SelectItemComEscopo.test.d.ts +1 -0
  115. package/dist/teste/SelectItemComEscopo.test.js +54 -0
  116. package/dist/teste/SelectListaFiltro.test.d.ts +1 -0
  117. package/dist/teste/SelectListaFiltro.test.js +102 -0
  118. package/dist/teste/StatCardSecundariaETom.test.d.ts +1 -0
  119. package/dist/teste/StatCardSecundariaETom.test.js +45 -0
  120. package/dist/teste/StatCardTamanhoSm.test.d.ts +1 -0
  121. package/dist/teste/StatCardTamanhoSm.test.js +32 -0
  122. package/dist/teste/TabsCompacta.test.d.ts +1 -0
  123. package/dist/teste/TabsCompacta.test.js +35 -0
  124. package/dist/teste/TagPositivoTingido.test.d.ts +1 -0
  125. package/dist/teste/TagPositivoTingido.test.js +54 -0
  126. package/dist/teste/ToastAcaoEDetalhes.test.d.ts +1 -0
  127. package/dist/teste/ToastAcaoEDetalhes.test.js +94 -0
  128. package/dist/teste/lerArquivo.d.ts +1 -0
  129. package/dist/teste/lerArquivo.js +12 -0
  130. package/dist/utils/html-seguro.d.ts +19 -1
  131. package/dist/utils/html-seguro.js +113 -2
  132. package/dist/utils/toast.d.ts +10 -1
  133. package/dist/utils/toast.js +2 -1
  134. package/package.json +3 -3
  135. package/src/teste/AccordionControlado.test.tsx +87 -0
  136. package/src/teste/AccordionTituloNeutro.test.tsx +25 -0
  137. package/src/teste/AppSubheaderRegiao.test.tsx +17 -0
  138. package/src/teste/AttachmentCardLista.test.tsx +53 -0
  139. package/src/teste/AttachmentCardSm.test.tsx +29 -0
  140. package/src/teste/BadgeAttachedTomEPadding.test.tsx +60 -0
  141. package/src/teste/BadgeNeutroColado.test.tsx +15 -0
  142. package/src/teste/BadgeTag.test.tsx +21 -0
  143. package/src/teste/ButtonDensidade.test.tsx +40 -0
  144. package/src/teste/CalendarCabecalho.test.tsx +11 -0
  145. package/src/teste/CalendarCompacto.test.tsx +37 -0
  146. package/src/teste/CoachmarkSemVeu.test.tsx +101 -0
  147. package/src/teste/CoachmarkUmPasso.test.tsx +45 -0
  148. package/src/teste/ColorPickerXs.test.tsx +22 -0
  149. package/src/teste/DataTableArvoreEVariantes.test.tsx +240 -0
  150. package/src/teste/DataTableCompactaETom.test.tsx +85 -0
  151. package/src/teste/DescriptionListCompacta.test.tsx +24 -0
  152. package/src/teste/EmptyStateTituloRico.test.tsx +34 -0
  153. package/src/teste/InputAgrupado.test.tsx +26 -0
  154. package/src/teste/InputGhost.test.tsx +42 -0
  155. package/src/teste/ListItemTooltipStandalone.test.tsx +64 -0
  156. package/src/teste/ModalProporcoes1Doc.test.tsx +36 -0
  157. package/src/teste/RichTextEditorAlinhaImagemTabela.test.tsx +238 -0
  158. package/src/teste/RichTextEditorEstiloDeTexto.test.tsx +137 -0
  159. package/src/teste/SegmentedControlContador.test.tsx +43 -0
  160. package/src/teste/SegmentedControlSuperficie.test.tsx +52 -0
  161. package/src/teste/SelectAbertoControladoEFlutuante.test.tsx +180 -0
  162. package/src/teste/SelectFiltroCorETamanho.test.tsx +90 -0
  163. package/src/teste/SelectItemComEscopo.test.tsx +69 -0
  164. package/src/teste/SelectListaFiltro.test.tsx +120 -0
  165. package/src/teste/StatCardSecundariaETom.test.tsx +51 -0
  166. package/src/teste/StatCardTamanhoSm.test.tsx +42 -0
  167. package/src/teste/TabsCompacta.test.tsx +45 -0
  168. package/src/teste/TagPositivoTingido.test.tsx +60 -0
  169. package/src/teste/ToastAcaoEDetalhes.test.tsx +109 -0
  170. package/src/teste/lerArquivo.ts +13 -0
  171. package/src/utils/html-seguro.ts +105 -2
  172. package/src/utils/toast.ts +12 -2
@@ -14,8 +14,11 @@ export interface AccordionItemData {
14
14
  /**
15
15
  * Accordion component — lista de painéis expansíveis (sanfona).
16
16
  * @property {AccordionItemData[]} items - Itens do accordion (id, title, content, disabled?, defaultOpen?). Conteúdo livre por item: no React, `content` aceita nó; no Vue, o slot `#content-<id>` (recebe `{ item }`).
17
+ * @property {string[]} open - Ids dos itens abertos (modo controlado). Sem a prop, o Accordion guarda o próprio estado (começando pelos `defaultOpen`). Com ela, quem manda é o app: abrir ou fechar chama `onOpenChange` e o painel só muda quando `open` muda. Serve para lembrar a escolha entre visitas.
18
+ * @property {function} onOpenChange - Chamado ao abrir ou fechar um item, com a nova lista de ids abertos (`(openIds: string[]) => void`). Funciona também sem `open`.
17
19
  * @property {boolean} multiple - Permite manter vários painéis abertos ao mesmo tempo
18
20
  * @property {string} variant - Estilo visual: 'default' | 'bordered' | 'separated'
21
+ * @property {string} titleVariant - Título dos itens: 'default' | 'neutral'. O `neutral` é o título de texto, 14px na cor do texto, sem cor de ação (como um `Button text` neutro), para o accordion do CrisDoc.
19
22
  * @property {string} className - Classes CSS adicionais
20
23
  * @quandoUsar Conteúdo que fica RECOLHIDO até o usuário querer: FAQ, seções opcionais de formulário, detalhes secundários. Vários podem abrir juntos com `multiple`.
21
24
  * @quandoNaoUsar Para alternar entre visões equivalentes sempre disponíveis — use `Tabs`. Se o conteúdo é essencial, não esconda atrás de acordeão.
@@ -23,7 +26,13 @@ export interface AccordionItemData {
23
26
  export interface AccordionProps {
24
27
  items: AccordionItemData[];
25
28
  multiple?: boolean;
29
+ /** Ids dos itens abertos (controlado). Sem a prop, o estado é interno. */
30
+ open?: string[];
31
+ /** Abriu ou fechou um item: recebe a nova lista de ids abertos. */
32
+ onOpenChange?: (openIds: string[]) => void;
26
33
  variant?: "default" | "bordered" | "separated";
34
+ /** Título dos itens. `neutral`: 14px na cor do texto, peso médio. @default 'default' */
35
+ titleVariant?: "default" | "neutral";
27
36
  className?: string;
28
37
  }
29
38
  declare function Accordion(props: AccordionProps): import("react/jsx-runtime").JSX.Element;
@@ -9,8 +9,13 @@ function Accordion(props) {
9
9
  function itemList() {
10
10
  return props.items || [];
11
11
  }
12
+ function currentOpen() {
13
+ return props.open !== undefined && props.open !== null
14
+ ? props.open
15
+ : openIds;
16
+ }
12
17
  function isOpen(id) {
13
- return openIds.includes(id);
18
+ return currentOpen().includes(id);
14
19
  }
15
20
  function triggerId(id) {
16
21
  return `umds-accordion-trigger-${id}`;
@@ -21,20 +26,28 @@ function Accordion(props) {
21
26
  function toggle(id, disabled) {
22
27
  if (disabled)
23
28
  return;
24
- if (openIds.includes(id)) {
25
- setOpenIds(openIds.filter((x) => x !== id));
29
+ const atuais = currentOpen();
30
+ let proximos;
31
+ if (atuais.includes(id)) {
32
+ proximos = atuais.filter((x) => x !== id);
26
33
  }
27
34
  else if (props.multiple) {
28
- setOpenIds([...openIds, id]);
35
+ proximos = [...atuais, id];
29
36
  }
30
37
  else {
31
- setOpenIds([id]);
38
+ proximos = [id];
32
39
  }
40
+ // Controlado: quem muda é o app (o `open` novo). Sem `open`, o estado interno.
41
+ if (props.open === undefined || props.open === null)
42
+ setOpenIds(proximos);
43
+ if (props.onOpenChange)
44
+ props.onOpenChange(proximos);
33
45
  }
34
46
  function wrapperClasses() {
35
47
  return [
36
48
  "umds-accordion",
37
49
  `umds-accordion--${props.variant || "default"}`,
50
+ props.titleVariant === "neutral" ? "umds-accordion--title-neutral" : "",
38
51
  props.className,
39
52
  ]
40
53
  .filter(Boolean)
@@ -21,14 +21,23 @@
21
21
  }
22
22
 
23
23
  /* O contexto cede espaço e rola sozinho: uma trilha longa não pode empurrar
24
- a busca e as ações para fora da tela. */
24
+ a busca e as ações para fora da tela.
25
+ Só rola na HORIZONTAL, e só quando a trilha estoura. `overflow-x: auto` sozinho
26
+ faz o `overflow-y` computar para `auto` também, e o `line-height` do item do
27
+ Breadcrumb (12px, numa linha de 12px) deixa o conteúdo 2px acima da caixa de 12px
28
+ (medido: scrollHeight 14, clientHeight 12): com a trilha só na página atual (sem
29
+ link para focar) o axe acusa `scrollable-region-focusable`. Por isso a região
30
+ ocupa a altura da barra (`align-self: stretch`, o conteúdo segue centralizado) e o
31
+ eixo vertical é cortado: não há o que rolar na vertical. */
25
32
  .umds-app-subheader__contexto {
26
33
  flex: 1;
34
+ align-self: stretch;
27
35
  min-width: 0;
28
36
  display: flex;
29
37
  align-items: center;
30
38
  gap: var(--umds-primitive-spacing-nano);
31
39
  overflow-x: auto;
40
+ overflow-y: hidden;
32
41
  scrollbar-width: none;
33
42
  }
34
43
 
@@ -183,18 +183,10 @@
183
183
  }
184
184
 
185
185
  .umds-attachment-card__campo {
186
- display: flex;
187
- align-items: center;
188
- gap: var(--umds-primitive-spacing-nano);
189
186
  padding: var(--umds-primitive-spacing-nano) 0;
190
187
  border-top: 1px solid var(--umds-semantic-color-neutral-light-medium);
191
188
  }
192
189
 
193
- .umds-attachment-card__campo-texto {
194
- flex: 1;
195
- min-width: 0;
196
- }
197
-
198
190
  .umds-attachment-card__campo-rotulo {
199
191
  color: var(--umds-semantic-color-neutral-dark-down);
200
192
  font-size: var(--umds-primitive-font-size-xxxs);
@@ -202,7 +194,12 @@
202
194
  text-transform: uppercase;
203
195
  }
204
196
 
197
+ /* O valor e o botão de copiar na mesma linha: o botão mora dentro do <dd> (o <dl> só aceita
198
+ <dt> e <dd> dentro do <div>). */
205
199
  .umds-attachment-card__campo-valor {
200
+ display: flex;
201
+ align-items: center;
202
+ gap: var(--umds-primitive-spacing-nano);
206
203
  margin: var(--umds-primitive-spacing-micro) 0 0; /* 2px: respiro rótulo→valor */
207
204
  font-size: var(--umds-primitive-font-size-xxs);
208
205
  font-weight: var(--umds-primitive-font-weight-bold);
@@ -210,6 +207,11 @@
210
207
  overflow-wrap: anywhere;
211
208
  }
212
209
 
210
+ .umds-attachment-card__campo-valor-texto {
211
+ flex: 1;
212
+ min-width: 0;
213
+ }
214
+
213
215
  /* Alvo de toque WCAG 2.5.8: 32×32. */
214
216
  .umds-attachment-card__copiar {
215
217
  display: inline-flex;
@@ -15,6 +15,7 @@ import './AttachmentCard.css';
15
15
  * @property {boolean} closePanelOnAction - Fecha o painel e devolve o foco ao selo quando alguém aciona um botão ou link de dentro dele (o botão "Aplicar sugestão" do painel de IA). O botão de copiar um campo não conta.
16
16
  * @property {Function} onCopyField - Avisa quando um campo do painel é copiado (recebe o campo)
17
17
  * @property {IconProp} icon - Ícone central. Padrão: documento.
18
+ * @property {string} size - Tamanho do cartão: 'md' (padrão) | 'sm'. O `sm` é o compacto do CrisDoc: ícone do preview de 40px (no `md`, 48px) e ícone do rodapé de 12px (no `md`, 16px). A largura e os selos não mudam.
18
19
  * @property {Function} onOpen - Abre o anexo. Com ele o cartão inteiro vira alvo de clique, com um único ponto de foco.
19
20
  * @property {string} openAriaLabel - Nome acessível da ação de abrir. Padrão: "Abrir <tipo>: <arquivo>".
20
21
  * @property {string} className - Classes CSS adicionais
@@ -60,6 +61,8 @@ export interface AttachmentCardProps {
60
61
  closePanelOnAction?: boolean;
61
62
  onCopyField?: (field: AttachmentCardField) => void;
62
63
  icon?: IconProp;
64
+ /** Tamanho do cartão. `sm`: ícone do preview de 40px e ícone do rodapé de 12px. @default 'md' */
65
+ size?: "md" | "sm";
63
66
  onOpen?: (event?: any) => void;
64
67
  openAriaLabel?: string;
65
68
  /** Complemento lido depois do selo, para dizer de qual documento ele é. Recebe o tipo de documento (ou o nome do arquivo). Troque para traduzir. @default (subject) => 'conferência de ' + subject */
@@ -29,6 +29,7 @@ function AttachmentCard(props) {
29
29
  return [
30
30
  "umds-attachment-card",
31
31
  props.onOpen ? "umds-attachment-card--interativo" : "",
32
+ props.size === "sm" ? "umds-attachment-card--sm" : "",
32
33
  props.className,
33
34
  ]
34
35
  .filter(Boolean)
@@ -145,6 +146,12 @@ function AttachmentCard(props) {
145
146
  return faFileExcel;
146
147
  return faFile;
147
148
  }
149
+ function tamanhoIconeCentral() {
150
+ return props.size === "sm" ? "40px" : "48px";
151
+ }
152
+ function tamanhoIconeRodape() {
153
+ return props.size === "sm" ? "12px" : undefined;
154
+ }
148
155
  function iconeCentral() {
149
156
  return props.icon || faFileLines;
150
157
  }
@@ -386,6 +393,6 @@ function AttachmentCard(props) {
386
393
  desligarObservadores();
387
394
  };
388
395
  }, []);
389
- return (_jsxs("div", { className: classes(), id: props.id, children: [_jsxs("div", { className: "umds-attachment-card__topo", children: [temSelo() && !temDetalhe() ? (_jsxs("span", { className: statusClasses(), children: [seloIcone() ? (_jsx(Icon, { size: "sm", variant: "inherit", icon: seloIcone() })) : null, _jsx("span", { className: "umds-attachment-card__status-texto", children: props.statusLabel })] })) : null, temDetalhe() ? (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", "aria-haspopup": "dialog", ref: seloRef, className: statusBotaoClasses(), "aria-expanded": ariaExpandidoTexto(), "aria-controls": ariaControlsTexto(), onMouseEnter: (event) => abrir(false), onMouseLeave: (event) => agendarFechar(), onClick: (event) => alternar(), onKeyDown: (event) => teclaNoSelo(event), onBlur: (event) => focoSaiu(event), children: [seloIcone() ? (_jsx(Icon, { size: "sm", variant: "inherit", icon: seloIcone() })) : null, _jsx("span", { className: "umds-attachment-card__status-texto", children: props.statusLabel }), _jsx("span", { className: "umds-attachment-card__sr", children: seloComplemento() })] }), _jsx(Portal, { children: aberto ? (_jsx("span", { className: "umds-attachment-card__flutuante", ref: flutuanteRef, style: flutuanteEstilo(), children: _jsxs("div", { className: "umds-attachment-card__detalhe", role: "dialog", "aria-modal": "false", ref: painelRef, id: painelId(), "aria-labelledby": painelNomeadoPor(), "aria-label": painelRotulo(), tabIndex: -1, style: detalheEstilo(), onClick: (event) => cliqueNoPainel(event), onMouseEnter: (event) => cancelarFechar(), onMouseLeave: (event) => agendarFechar(), onKeyDown: (event) => teclaNoPainel(event), children: [_jsx("span", { className: "umds-attachment-card__sr", role: "status", "aria-live": "polite", children: anuncio }), props.panel ? (_jsx(_Fragment, { children: props.panel })) : (_jsx(_Fragment, { children: _jsxs(_Fragment, { children: [_jsxs("div", { className: "umds-attachment-card__detalhe-topo", children: [_jsx("span", { "aria-hidden": "true", className: detalheIconeClasses(), children: _jsx(Icon, { size: "md", variant: "inherit", icon: detalheIcone() }) }), _jsx("span", { className: "umds-attachment-card__detalhe-titulo", id: tituloId(), children: detalheTitulo() })] }), detalheDescricao() ? (_jsx("p", { className: "umds-attachment-card__detalhe-descricao", children: detalheDescricao() })) : null, detalheCampos().length > 0 ? (_jsx("dl", { className: "umds-attachment-card__campos", children: detalheCampos()?.map((campo) => (_jsxs("div", { className: "umds-attachment-card__campo", children: [_jsxs("div", { className: "umds-attachment-card__campo-texto", children: [_jsx("dt", { className: "umds-attachment-card__campo-rotulo", children: campo.label }), _jsx("dd", { className: "umds-attachment-card__campo-valor", children: campo.value })] }), campo.copyable ? (_jsx("button", { type: "button", className: "umds-attachment-card__copiar", "aria-label": rotuloCopiar(campo), onClick: (event) => copiarCampo(campo), children: _jsx(Icon, { size: "sm", variant: "inherit", icon: iconeCopiar(campo) }) })) : null] }, campo.label))) })) : null] }) }))] }) })) : null })] })) : null] }), _jsx("div", { className: "umds-attachment-card__preview", "aria-hidden": "true", children: _jsx(Icon, { customSize: "48px", variant: "inherit", icon: iconeCentral() }) }), _jsx("div", { className: "umds-attachment-card__tipo-area", children: props.documentType ? (_jsxs("span", { className: "umds-attachment-card__tipo", children: [_jsx("span", { className: "umds-attachment-card__tipo-texto", children: props.documentType }), props.required ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "umds-attachment-card__obrigatorio", "aria-hidden": "true", children: "*" }), _jsx("span", { className: "umds-attachment-card__sr", children: props.requiredLabel || "(obrigatório)" })] })) : null] })) : null }), _jsxs("div", { className: "umds-attachment-card__rodape", title: props.fileName, children: [_jsx("span", { className: "umds-attachment-card__rodape-icone", "aria-hidden": "true", children: _jsx(Icon, { size: "sm", variant: "inherit", icon: iconeArquivo() }) }), props.onOpen ? (_jsx("button", { type: "button", className: "umds-attachment-card__nome umds-attachment-card__abrir", "aria-label": rotuloAbrir(), onClick: (event) => handleOpen(event), children: props.fileName })) : (_jsx("span", { className: "umds-attachment-card__nome", children: props.fileName }))] })] }));
396
+ return (_jsxs("div", { className: classes(), id: props.id, children: [_jsxs("div", { className: "umds-attachment-card__topo", children: [temSelo() && !temDetalhe() ? (_jsxs("span", { className: statusClasses(), children: [seloIcone() ? (_jsx(Icon, { size: "sm", variant: "inherit", icon: seloIcone() })) : null, _jsx("span", { className: "umds-attachment-card__status-texto", children: props.statusLabel })] })) : null, temDetalhe() ? (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", "aria-haspopup": "dialog", ref: seloRef, className: statusBotaoClasses(), "aria-expanded": ariaExpandidoTexto(), "aria-controls": ariaControlsTexto(), onMouseEnter: (event) => abrir(false), onMouseLeave: (event) => agendarFechar(), onClick: (event) => alternar(), onKeyDown: (event) => teclaNoSelo(event), onBlur: (event) => focoSaiu(event), children: [seloIcone() ? (_jsx(Icon, { size: "sm", variant: "inherit", icon: seloIcone() })) : null, _jsx("span", { className: "umds-attachment-card__status-texto", children: props.statusLabel }), _jsx("span", { className: "umds-attachment-card__sr", children: seloComplemento() })] }), _jsx(Portal, { children: aberto ? (_jsx("span", { className: "umds-attachment-card__flutuante", ref: flutuanteRef, style: flutuanteEstilo(), children: _jsxs("div", { className: "umds-attachment-card__detalhe", role: "dialog", "aria-modal": "false", ref: painelRef, id: painelId(), "aria-labelledby": painelNomeadoPor(), "aria-label": painelRotulo(), tabIndex: -1, style: detalheEstilo(), onClick: (event) => cliqueNoPainel(event), onMouseEnter: (event) => cancelarFechar(), onMouseLeave: (event) => agendarFechar(), onKeyDown: (event) => teclaNoPainel(event), children: [_jsx("span", { className: "umds-attachment-card__sr", role: "status", "aria-live": "polite", children: anuncio }), props.panel ? (_jsx(_Fragment, { children: props.panel })) : (_jsx(_Fragment, { children: _jsxs(_Fragment, { children: [_jsxs("div", { className: "umds-attachment-card__detalhe-topo", children: [_jsx("span", { "aria-hidden": "true", className: detalheIconeClasses(), children: _jsx(Icon, { size: "md", variant: "inherit", icon: detalheIcone() }) }), _jsx("span", { className: "umds-attachment-card__detalhe-titulo", id: tituloId(), children: detalheTitulo() })] }), detalheDescricao() ? (_jsx("p", { className: "umds-attachment-card__detalhe-descricao", children: detalheDescricao() })) : null, detalheCampos().length > 0 ? (_jsx("dl", { className: "umds-attachment-card__campos", children: detalheCampos()?.map((campo) => (_jsxs("div", { className: "umds-attachment-card__campo", children: [_jsx("dt", { className: "umds-attachment-card__campo-rotulo", children: campo.label }), _jsxs("dd", { className: "umds-attachment-card__campo-valor", children: [_jsx("span", { className: "umds-attachment-card__campo-valor-texto", children: campo.value }), campo.copyable ? (_jsx("button", { type: "button", className: "umds-attachment-card__copiar", "aria-label": rotuloCopiar(campo), onClick: (event) => copiarCampo(campo), children: _jsx(Icon, { size: "sm", variant: "inherit", icon: iconeCopiar(campo) }) })) : null] })] }, campo.label))) })) : null] }) }))] }) })) : null })] })) : null] }), _jsx("div", { className: "umds-attachment-card__preview", "aria-hidden": "true", children: _jsx(Icon, { variant: "inherit", icon: iconeCentral(), customSize: tamanhoIconeCentral() }) }), _jsx("div", { className: "umds-attachment-card__tipo-area", children: props.documentType ? (_jsxs("span", { className: "umds-attachment-card__tipo", children: [_jsx("span", { className: "umds-attachment-card__tipo-texto", children: props.documentType }), props.required ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "umds-attachment-card__obrigatorio", "aria-hidden": "true", children: "*" }), _jsx("span", { className: "umds-attachment-card__sr", children: props.requiredLabel || "(obrigatório)" })] })) : null] })) : null }), _jsxs("div", { className: "umds-attachment-card__rodape", title: props.fileName, children: [_jsx("span", { className: "umds-attachment-card__rodape-icone", "aria-hidden": "true", children: _jsx(Icon, { size: "sm", variant: "inherit", icon: iconeArquivo(), customSize: tamanhoIconeRodape() }) }), props.onOpen ? (_jsx("button", { type: "button", className: "umds-attachment-card__nome umds-attachment-card__abrir", "aria-label": rotuloAbrir(), onClick: (event) => handleOpen(event), children: props.fileName })) : (_jsx("span", { className: "umds-attachment-card__nome", children: props.fileName }))] })] }));
390
397
  }
391
398
  export default AttachmentCard;
@@ -3,7 +3,7 @@
3
3
  Variantes: neutral, positive, negative, info, primary, new
4
4
  Aparências (appearance): solid, soft, outlined
5
5
  Tamanhos: 2xs, xs, sm, md, lg, pill
6
- Modificador: attached (rótulo colado no topo de um card)
6
+ Modificador: attached (rótulo colado no topo de um card), attachedSide left | right
7
7
  ============================================ */
8
8
 
9
9
  .umds-badge {
@@ -60,12 +60,12 @@
60
60
 
61
61
  /* ── Tamanhos — padding + gap por tamanho (sem height fixa) ── */
62
62
 
63
- /* 2xs: 16px de altura (10 de fonte + 2 + 2 de padding + 2 de borda). A fonte de
64
- 10px não está na escala do DS (menor degrau: 12px); é o selo compacto do
65
- CrisDoc, sem token equivalente. */
63
+ /* 2xs: 16px de altura (10 de fonte + 2 + 2 de padding + 2 de borda) e 8px de padding
64
+ lateral, como o selo do CrisDoc. A fonte de 10px não está na escala do DS (menor
65
+ degrau: 12px), sem token equivalente. */
66
66
  .umds-badge--2xs {
67
67
  font-size: 0.625rem; /* 10px: fora da escala, selo compacto */
68
- padding: var(--umds-primitive-spacing-micro) var(--umds-primitive-spacing-quarck);
68
+ padding: var(--umds-primitive-spacing-micro) var(--umds-primitive-spacing-nano);
69
69
  gap: var(--umds-primitive-spacing-micro);
70
70
  border-radius: var(--umds-primitive-border-radius-xs); /* 4px */
71
71
  }
@@ -252,5 +252,27 @@
252
252
  position: absolute;
253
253
  top: 0;
254
254
  left: 0;
255
+ /* 22px: o rótulo do CrisDoc (24 - 2). Os tamanhos menores (xs, 2xs) sobem a ele; o md já mede 22. */
256
+ min-height: calc(var(--umds-primitive-spacing-xxs) - var(--umds-primitive-spacing-micro));
255
257
  border-radius: 0 0 var(--umds-primitive-border-radius-sm) var(--umds-primitive-border-radius-sm);
256
258
  }
259
+
260
+ /* Canto direito (attachedSide="right"): o rótulo cola no canto superior direito. */
261
+ .umds-badge--attached.umds-badge--attached-right {
262
+ left: auto;
263
+ right: 0;
264
+ }
265
+
266
+ /* Neutro colado: cinza claro com texto escuro (o "Tarefa manual" do CrisDoc).
267
+ O `solid` neutro é cinza escuro com texto branco, pesado demais para um rótulo de card. */
268
+ .umds-badge--attached.umds-badge--neutral.umds-badge--solid {
269
+ background-color: var(--umds-semantic-color-neutral-light-medium);
270
+ color: var(--umds-semantic-color-neutral-dark-pure);
271
+ }
272
+
273
+ /* attachedTone="dark": o neutro escuro do CrisDoc. O ardósia do CrisDoc dá 2,9:1 com texto branco; o par
274
+ neutral-dark-down + branco dá 7,3:1 e inverte sozinho nos temas escuros. */
275
+ .umds-badge--attached.umds-badge--neutral.umds-badge--solid.umds-badge--attached-dark {
276
+ background-color: var(--umds-semantic-color-neutral-dark-down);
277
+ color: var(--umds-semantic-color-neutral-light-pure);
278
+ }
@@ -13,19 +13,35 @@ export interface BadgeProps {
13
13
  */
14
14
  variant?: "neutral" | "positive" | "negative" | "info" | "primary" | "new";
15
15
  /**
16
- * Tamanho do badge. `2xs` é o compacto (16px, fonte de 10px) para selo de
17
- * setor e de contagem dentro de linha de lista e card.
16
+ * Tamanho do badge. `2xs` é o compacto (16px, fonte de 10px, 8px de padding
17
+ * lateral) para selo de setor e de contagem dentro de linha de lista e card.
18
18
  * @default 'md'
19
19
  */
20
20
  size?: "2xs" | "xs" | "sm" | "md" | "lg" | "pill";
21
21
  /**
22
- * Cola o badge no canto superior esquerdo do card (rótulo de card, como
22
+ * Cola o badge no canto superior do card (rótulo de card, como
23
23
  * "Tarefa pendente"): cantos de baixo arredondados, os de cima retos.
24
24
  * Posiciona em `absolute`, então o pai precisa de `position: relative`
25
- * (o `Card` e o `Box` com `position` já servem).
25
+ * (o `Card` e o `Box` com `position` já servem). O canto é o esquerdo, ou o
26
+ * direito com `attachedSide="right"`. Com `variant="neutral"` e `appearance="solid"` o
27
+ * rótulo colado é o cinza claro com texto escuro (o "Tarefa manual" do CrisDoc), e não o
28
+ * cinza escuro do `solid`, pesado demais para um rótulo de card.
26
29
  * @default false
27
30
  */
28
31
  attached?: boolean;
32
+ /**
33
+ * Em que canto do card o `attached` cola: `left` (padrão) ou `right`.
34
+ * Só vale com `attached`.
35
+ * @default 'left'
36
+ */
37
+ attachedSide?: "left" | "right";
38
+ /**
39
+ * Tom do rótulo colado neutro e sólido: `light` (padrão, cinza claro com texto escuro) ou `dark` (cinza
40
+ * escuro com texto branco, o rótulo escuro do CrisDoc). Só vale com `attached`, `variant="neutral"` e
41
+ * `appearance="solid"`; as outras variantes ignoram. O rótulo colado mede 22px de altura.
42
+ * @default 'light'
43
+ */
44
+ attachedTone?: "light" | "dark";
29
45
  /**
30
46
  * Estilo visual do badge.
31
47
  * @default 'soft'
@@ -8,6 +8,12 @@ function Badge(props) {
8
8
  `umds-badge--${props.size || "md"}`,
9
9
  `umds-badge--${props.appearance || "soft"}`,
10
10
  props.attached ? "umds-badge--attached" : "",
11
+ props.attached && props.attachedSide === "right"
12
+ ? "umds-badge--attached-right"
13
+ : "",
14
+ props.attached && props.attachedTone === "dark"
15
+ ? "umds-badge--attached-dark"
16
+ : "",
11
17
  props.className,
12
18
  ]
13
19
  .filter(Boolean)
@@ -56,6 +56,11 @@
56
56
  font-size: var(--umds-primitive-font-size-xxxs); /* 12px */
57
57
  }
58
58
 
59
+ /* density="comfortable": o sm com o padding de 12px do CrisDoc. O botão só de ícone tem largura fixa e fica de fora. */
60
+ .umds-button--sm.umds-button--comfortable:not(.umds-button--icon) {
61
+ padding: 0 var(--umds-primitive-spacing-mini); /* 12px */
62
+ }
63
+
59
64
  .umds-button--md {
60
65
  height: var(--umds-primitive-spacing-sm); /* 40px */
61
66
  padding: 0 var(--umds-primitive-spacing-xxxs); /* 16px */
@@ -3,6 +3,7 @@ import './Button.css';
3
3
  * Button — botão de ação com variantes, tamanhos, ícones e estado de carregamento.
4
4
  * @property {string} variant - Variante visual: 'primary' | 'secondary' | 'neutral' | 'neutral-text' | 'text' | 'negative' | 'negative-outlined' | 'icon'. `neutral` é o contornado cinza; `neutral-text` é o texto sem borda e sem azul, para barras de ações onde nenhum botão é a ação principal.
5
5
  * @property {string} size - Tamanho do botão: 'xs' (26px, para dentro de card e linha de lista) | 'sm' (32px) | 'md' (40px) | 'lg' (48px)
6
+ * @property {string} density - 'default' (padrão) | 'comfortable'. O `comfortable` leva o padding lateral do `sm` de 8px a 12px (o botão pequeno do CrisDoc); a altura (32px) não muda. Nos outros tamanhos não faz diferença: `xs` já usa 12px, `md` 16px e `lg` 24px. Sem a prop, nenhum botão existente muda.
6
7
  * @property {boolean} disabled - Desabilita o botão
7
8
  * @property {string} type - Tipo nativo do botão: 'button' | 'submit' | 'reset'
8
9
  * @property {Function} onClick - Callback disparado no clique
@@ -27,6 +28,8 @@ import './Button.css';
27
28
  export interface ButtonProps {
28
29
  variant?: "primary" | "secondary" | "neutral" | "neutral-text" | "text" | "negative" | "negative-outlined" | "icon";
29
30
  size?: "xs" | "sm" | "md" | "lg";
31
+ /** `comfortable`: o `sm` com 12px de padding lateral (o padrão é 8px). @default 'default' */
32
+ density?: "default" | "comfortable";
30
33
  disabled?: boolean;
31
34
  type?: "button" | "submit" | "reset";
32
35
  children?: any;
@@ -9,6 +9,7 @@ function Button(props) {
9
9
  "umds-button",
10
10
  `umds-button--${props.variant || "primary"}`,
11
11
  `umds-button--${props.size || "md"}`,
12
+ props.density === "comfortable" ? "umds-button--comfortable" : "",
12
13
  props.fullWidth ? "umds-button--full-width" : "",
13
14
  props.className,
14
15
  ]
@@ -171,6 +171,16 @@
171
171
  cursor: pointer;
172
172
  }
173
173
 
174
+ /* density="compact": evento de 20px (16 + 4: o degrau do DS mais perto dos 20,5 do CrisDoc) e 4px de padding
175
+ lateral. A linha tem 16px de altura e 2px de padding em cima e embaixo. O título continua cortado com
176
+ reticências só quando passa da largura (a regra do título, abaixo). */
177
+ .umds-calendar--compact .umds-calendar__event {
178
+ min-height: calc(var(--umds-primitive-spacing-xxxs) + var(--umds-primitive-spacing-quarck)); /* 20px */
179
+ padding: var(--umds-primitive-spacing-micro) var(--umds-primitive-spacing-quarck); /* 2px 4px */
180
+ line-height: var(--umds-primitive-spacing-xxxs); /* 16px */
181
+ align-items: center;
182
+ }
183
+
174
184
  .umds-calendar__event:hover {
175
185
  filter: brightness(0.96);
176
186
  }
@@ -52,8 +52,9 @@ export interface CalendarDayCell {
52
52
  *
53
53
  * A data mostrada (`date`) e a visão (`view`) são controláveis (`onDateChange`,
54
54
  * `onViewChange`) ou internas (`defaultDate`, `defaultView`). O cabeçalho aceita
55
- * o slot `headerStart` (um filtro de escopo, à esquerda) e `headerEnd` (a
56
- * legenda, à direita). Nomes de mês e de dia saem do `Intl` no `locale` pedido;
55
+ * um filtro de escopo à esquerda e a legenda à direita: no React, as props `headerStart`
56
+ * e `headerEnd` (um nó); no Vue, os slots `#header-start` e `#header-end` (com hífen: o
57
+ * Vue lê o nome do slot como está, e `#headerStart` não chega). Nomes de mês e de dia saem do `Intl` no `locale` pedido;
57
58
  * os demais textos são props.
58
59
  * @property {CalendarEvent[]} events - Eventos (lista plana)
59
60
  * @property {string} view - Visão: 'month' | 'week' (controlada)
@@ -64,6 +65,7 @@ export interface CalendarDayCell {
64
65
  * @property {string} today - O "hoje" do calendário (`YYYY-MM-DD`; padrão a data do sistema). Útil em testes e em demonstração.
65
66
  * @property {number} weekStartsOn - Primeiro dia da semana: 0 domingo (padrão) | 1 segunda
66
67
  * @property {number} maxVisible - Eventos por dia na visão de mês antes do "+N" (padrão 3)
68
+ * @property {string} density - 'default' | 'compact'. O `compact` aperta o evento: altura de 20px (o degrau do DS mais perto dos 20,5 do CrisDoc; o padrão dá 25) e 4px de padding lateral (o padrão tem 8), para o título caber inteiro onde antes cortava com reticências. O título só é cortado (com reticências) quando de fato passa da largura da célula.
67
69
  * @property {string} locale - Idioma dos nomes de mês e de dia (padrão 'pt-BR')
68
70
  * @property {string} ariaLabel - Nome acessível da grade (padrão: o título, ex.: "Maio 2026")
69
71
  * @property {string} previousLabel - Nome do botão de voltar (padrão "Anterior")
@@ -82,8 +84,8 @@ export interface CalendarDayCell {
82
84
  * @property {function} onDateChange - Mudou a data de referência (navegação): recebe `YYYY-MM-DD`
83
85
  * @property {function} onViewChange - Mudou a visão: recebe 'month' | 'week'
84
86
  * @property {string} className - Classes CSS adicionais
85
- * @slot headerStart - Cabeçalho, à esquerda: um filtro de escopo.
86
- * @slot headerEnd - Cabeçalho, antes do seletor de visão: a legenda.
87
+ * @slot header-start - Só no Vue (`#header-start`, com hífen). Cabeçalho, à esquerda: um filtro de escopo. No React, a prop `headerStart`.
88
+ * @slot header-end - Só no Vue (`#header-end`, com hífen). Cabeçalho, antes do seletor de visão: a legenda. No React, a prop `headerEnd`.
87
89
  * @quandoUsar Mostrar prazos, vencimentos e compromissos distribuídos no tempo: agenda de documentos, de tarefas, de assinaturas.
88
90
  * @quandoNaoUsar Para escolher UMA data num campo, use `DatePicker`. Para uma sequência de acontecimentos sem grade de dias, use `Timeline`. Para uma grade de horas (agenda com horário de início e fim), este componente não serve: não tem eixo de horas.
89
91
  */
@@ -98,6 +100,8 @@ export interface CalendarProps {
98
100
  today?: string;
99
101
  weekStartsOn?: 0 | 1;
100
102
  maxVisible?: number;
103
+ /** `compact`: evento de 20px com 4px de padding lateral. @default 'default' */
104
+ density?: "default" | "compact";
101
105
  locale?: string;
102
106
  ariaLabel?: string;
103
107
  previousLabel?: string;
@@ -111,9 +115,9 @@ export interface CalendarProps {
111
115
  moreAriaLabel?: string;
112
116
  dayLabel?: (iso: string, count: number) => string;
113
117
  className?: string;
114
- /** Slot `headerStart`: à esquerda do cabeçalho. */
118
+ /** Cabeçalho, à esquerda: um filtro de escopo. React: o nó nesta prop. Vue: o slot `#header-start` (com hífen). */
115
119
  headerStart?: any;
116
- /** Slot `headerEnd`: à direita, antes do seletor de visão. */
120
+ /** Cabeçalho, à direita, antes do seletor de visão: a legenda. React: o nó nesta prop. Vue: o slot `#header-end` (com hífen). */
117
121
  headerEnd?: any;
118
122
  onDayClick?: (iso: string) => void;
119
123
  onEventClick?: (event: CalendarEvent) => void;
@@ -100,7 +100,12 @@ function Calendar(props) {
100
100
  ];
101
101
  }
102
102
  function rootClasses() {
103
- return ["umds-calendar", "umds-calendar--" + viewValue(), props.className]
103
+ return [
104
+ "umds-calendar",
105
+ "umds-calendar--" + viewValue(),
106
+ props.density === "compact" ? "umds-calendar--compact" : "",
107
+ props.className,
108
+ ]
104
109
  .filter(Boolean)
105
110
  .join(" ");
106
111
  }
@@ -107,6 +107,16 @@
107
107
  border: 0;
108
108
  }
109
109
 
110
+ /* ── modal={false}: sem véu nem recorte, a página segue clicável ──
111
+ A raiz cobre a tela só para posicionar o cartão; quem recebe o ponteiro é o cartão. */
112
+ .umds-coachmark--modeless {
113
+ pointer-events: none;
114
+ }
115
+
116
+ .umds-coachmark--modeless .umds-coachmark__card {
117
+ pointer-events: auto;
118
+ }
119
+
110
120
  @media (forced-colors: active) {
111
121
  .umds-coachmark__card {
112
122
  border: var(--umds-primitive-border-width-thin) solid CanvasText;
@@ -23,7 +23,8 @@ export interface CoachmarkStep {
23
23
  * O cartão é um diálogo modal (`role="dialog"`, `aria-modal`): o foco entra nele
24
24
  * ao abrir, o Tab fica preso nele, Esc sai do tour, as setas esquerda e direita
25
25
  * voltam e avançam, e o foco volta ao elemento de antes ao fechar. A cada passo o
26
- * leitor de tela ouve "2 de 8: título" (região `aria-live`).
26
+ * leitor de tela ouve "2 de 8: título" (região `aria-live`). Com um passo só não há contador ("1 de 1" não
27
+ * diz nada): o cartão mostra o título e o leitor de tela ouve só ele.
27
28
  *
28
29
  * O componente é CONTROLADO por `open`: o tour só existe com `open` verdadeiro, e
29
30
  * `onClose(motivo)` avisa quando ele quer fechar (`"finish"` no último
@@ -38,7 +39,13 @@ export interface CoachmarkStep {
38
39
  *
39
40
  * Limite: a página atrás fica inerte (o véu captura o clique), então o alvo
40
41
  * não é clicável durante o tour.
42
+ *
43
+ * `modal={false}` tira o véu, o recorte e o foco preso: o cartão vira um popover
44
+ * ancorado no alvo, para uma dica pontual (um passo, "Novo: agora dá para * arrastar"). A página segue clicável, o foco entra no cartão ao abrir mas o Tab
45
+ * pode sair dele, o Esc fecha de qualquer lugar da página e o cartão vira um
46
+ * `role="dialog"` com `aria-modal="false"`.
41
47
  * @property {boolean} open - Mostra o tour
48
+ * @property {boolean} modal - `true` (padrão): véu, recorte e foco preso. `false`: sem véu nem foco preso, como um popover ancorado, para dica pontual
42
49
  * @property {CoachmarkStep[]} steps - Os passos, em ordem
43
50
  * @property {number} step - Passo atual, a partir de 0 (controlado)
44
51
  * @property {number} defaultStep - Passo inicial (não controlado)
@@ -57,6 +64,12 @@ export interface CoachmarkStep {
57
64
  */
58
65
  export interface CoachmarkProps {
59
66
  open?: boolean;
67
+ /**
68
+ * `true` (padrão): véu, recorte e foco preso. `false`: o cartão é um popover
69
+ * ancorado, sem véu nem foco preso. `any` de propósito: no Vue uma prop `boolean`
70
+ * sem valor chega como `false`, e o tour modal viraria sem véu por engano.
71
+ */
72
+ modal?: any;
60
73
  steps?: CoachmarkStep[];
61
74
  step?: number;
62
75
  defaultStep?: number;
@@ -12,6 +12,7 @@ function Coachmark(props) {
12
12
  const cleanupRef = useRef(null);
13
13
  const previousFocusRef = useRef(null);
14
14
  const attachedRef = useRef(null);
15
+ const escDocRef = useRef(null);
15
16
  const [internalStep, setInternalStep] = useState(() => props.defaultStep || 0);
16
17
  const [uid, setUid] = useState(() => "");
17
18
  const [active, setActive] = useState(() => false);
@@ -61,7 +62,12 @@ function Coachmark(props) {
61
62
  .replace("{current}", String(index() + 1))
62
63
  .replace("{total}", String(stepCount()));
63
64
  }
65
+ function hasCounter() {
66
+ return stepCount() > 1;
67
+ }
64
68
  function announceText() {
69
+ if (!hasCounter())
70
+ return currentStep().title;
65
71
  return counterText() + ": " + currentStep().title;
66
72
  }
67
73
  function nextText() {
@@ -75,6 +81,14 @@ function Coachmark(props) {
75
81
  function skipText() {
76
82
  return props.skipLabel || "Sair do tour";
77
83
  }
84
+ function isModal() {
85
+ if (props.modal === undefined || props.modal === null)
86
+ return true;
87
+ return props.modal !== false && props.modal !== "false";
88
+ }
89
+ function ariaModalText() {
90
+ return isModal() ? "true" : "false";
91
+ }
78
92
  function showSkip() {
79
93
  return !props.hideSkip;
80
94
  }
@@ -85,6 +99,7 @@ function Coachmark(props) {
85
99
  return [
86
100
  "umds-coachmark",
87
101
  hasTarget ? "" : "umds-coachmark--no-target",
102
+ isModal() ? "" : "umds-coachmark--modeless",
88
103
  positioned ? "umds-coachmark--ready" : "",
89
104
  ]
90
105
  .filter(Boolean)
@@ -196,6 +211,23 @@ function Coachmark(props) {
196
211
  cleanupRef.current = null;
197
212
  }
198
213
  }
214
+ function ligarEsc() {
215
+ if (typeof document === "undefined" || escDocRef.current)
216
+ return;
217
+ const ouvir = (event) => {
218
+ // O Esc dentro do cartão já fechou (e deu preventDefault): não fecha duas vezes.
219
+ if (event.key === "Escape" && !event.defaultPrevented)
220
+ fechar("escape");
221
+ };
222
+ escDocRef.current = ouvir;
223
+ document.addEventListener("keydown", ouvir);
224
+ }
225
+ function desligarEsc() {
226
+ if (typeof document === "undefined" || !escDocRef.current)
227
+ return;
228
+ document.removeEventListener("keydown", escDocRef.current);
229
+ escDocRef.current = null;
230
+ }
199
231
  function sincronizar() {
200
232
  if (typeof document === "undefined")
201
233
  return;
@@ -210,12 +242,18 @@ function Coachmark(props) {
210
242
  if (show() && !active) {
211
243
  setActive(true);
212
244
  setTentativas(0);
213
- previousFocusRef.current = document.activeElement;
245
+ // Guarda só na primeira vez: no React o `active` leva um render para
246
+ // valer, e um segundo `sincronizar` antes disso guardaria o próprio
247
+ // cartão (já focado) como "o elemento de antes".
248
+ if (!previousFocusRef.current)
249
+ previousFocusRef.current = document.activeElement;
214
250
  ligar();
215
251
  if (cardRef.current)
216
252
  cardRef.current.focus({
217
253
  preventScroll: true,
218
254
  });
255
+ if (!isModal())
256
+ ligarEsc();
219
257
  }
220
258
  else if (show() && active && attachedRef.current !== index()) {
221
259
  ligar();
@@ -223,15 +261,22 @@ function Coachmark(props) {
223
261
  else if (!show() && active) {
224
262
  setActive(false);
225
263
  desligar();
264
+ desligarEsc();
226
265
  attachedRef.current = null;
227
266
  setHasTarget(false);
228
267
  setPositioned(false);
229
268
  if (props.step === undefined)
230
269
  setInternalStep(props.defaultStep || 0);
231
- if (previousFocusRef.current && previousFocusRef.current.focus) {
270
+ // Sem véu a pessoa pode ter ido para outro lugar da página: o foco só volta se ela não foi.
271
+ const focoLivre = isModal() ||
272
+ document.activeElement === document.body ||
273
+ !document.activeElement;
274
+ if (previousFocusRef.current &&
275
+ previousFocusRef.current.focus &&
276
+ focoLivre) {
232
277
  previousFocusRef.current.focus();
233
- previousFocusRef.current = null;
234
278
  }
279
+ previousFocusRef.current = null;
235
280
  }
236
281
  }
237
282
  function irPara(proximo) {
@@ -274,7 +319,7 @@ function Coachmark(props) {
274
319
  handlePrevious();
275
320
  return;
276
321
  }
277
- if (event.key !== "Tab" || !cardRef.current)
322
+ if (event.key !== "Tab" || !cardRef.current || !isModal())
278
323
  return;
279
324
  // Foco preso: Tab e Shift+Tab giram dentro do cartão.
280
325
  const focaveis = cardRef.current.querySelectorAll("button:not([disabled])");
@@ -314,8 +359,9 @@ function Coachmark(props) {
314
359
  useEffect(() => {
315
360
  return () => {
316
361
  desligar();
362
+ desligarEsc();
317
363
  };
318
364
  }, []);
319
- return (_jsx(_Fragment, { children: show() ? (_jsx(_Fragment, { children: _jsx(Portal, { children: _jsxs("div", { className: rootClasses(), children: [_jsx("div", { "aria-hidden": "true", className: "umds-coachmark__veil", onMouseDown: (event) => handleVeilMouseDown(event) }), hasTarget ? (_jsx("div", { "aria-hidden": "true", className: "umds-coachmark__cutout", style: cutoutStyle() })) : null, _jsxs("div", { role: "dialog", "aria-modal": "true", ref: cardRef, style: cardStyle(), "aria-labelledby": titleId(), "aria-describedby": contentId(), tabIndex: -1, onKeyDown: (event) => handleKeyDown(event), className: cardClasses(), children: [_jsx("span", { "aria-live": "polite", className: "umds-coachmark__sr", children: announceText() }), _jsx(Text, { as: "span", variant: "body2", weight: "bold", color: "primary", "aria-hidden": "true", children: counterText() }), _jsx(Text, { as: "h2", variant: "subtitle1", weight: "bold", id: titleId(), children: currentStep().title }), currentStep().content ? (_jsx(Text, { as: "p", variant: "body2", color: "secondary", id: contentId(), children: currentStep().content })) : null, _jsxs("div", { className: "umds-coachmark__footer", children: [showSkip() ? (_jsx(Button, { variant: "text", size: "sm", onClick: (event) => fechar("skip"), children: skipText() })) : null, _jsxs("span", { className: "umds-coachmark__actions", children: [!isFirst() ? (_jsx(Button, { variant: "secondary", size: "md", onClick: (event) => handlePrevious(), children: previousText() })) : null, _jsx(Button, { variant: "primary", size: "md", onClick: (event) => handleNext(), children: nextText() })] })] })] })] }) }) })) : null }));
365
+ return (_jsx(_Fragment, { children: show() ? (_jsx(_Fragment, { children: _jsx(Portal, { children: _jsxs("div", { className: rootClasses(), children: [isModal() ? (_jsx("div", { "aria-hidden": "true", className: "umds-coachmark__veil", onMouseDown: (event) => handleVeilMouseDown(event) })) : null, hasTarget && isModal() ? (_jsx("div", { "aria-hidden": "true", className: "umds-coachmark__cutout", style: cutoutStyle() })) : null, _jsxs("div", { role: "dialog", ref: cardRef, style: cardStyle(), "aria-modal": ariaModalText(), "aria-labelledby": titleId(), "aria-describedby": contentId(), tabIndex: -1, onKeyDown: (event) => handleKeyDown(event), className: cardClasses(), children: [_jsx("span", { "aria-live": "polite", className: "umds-coachmark__sr", children: announceText() }), hasCounter() ? (_jsx(Text, { as: "span", variant: "body2", weight: "bold", color: "primary", "aria-hidden": "true", children: counterText() })) : null, _jsx(Text, { as: "h2", variant: "subtitle1", weight: "bold", id: titleId(), children: currentStep().title }), currentStep().content ? (_jsx(Text, { as: "p", variant: "body2", color: "secondary", id: contentId(), children: currentStep().content })) : null, _jsxs("div", { className: "umds-coachmark__footer", children: [showSkip() ? (_jsx(Button, { variant: "text", size: "sm", onClick: (event) => fechar("skip"), children: skipText() })) : null, _jsxs("span", { className: "umds-coachmark__actions", children: [!isFirst() ? (_jsx(Button, { variant: "secondary", size: "md", onClick: (event) => handlePrevious(), children: previousText() })) : null, _jsx(Button, { variant: "primary", size: "md", onClick: (event) => handleNext(), children: nextText() })] })] })] })] }) }) })) : null }));
320
366
  }
321
367
  export default Coachmark;