@aurea-uds/native 0.8.4 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/chart.js CHANGED
@@ -169,27 +169,24 @@ export function Chart({ labels, series, label, mark = "line", height = ALTURA, y
169
169
  return d.trim();
170
170
  };
171
171
  const tocar = (i) => { setTocado(i); onSelect?.(i); };
172
- return (_jsxs(View, { accessible: true,
173
- // ⚠ SEM papel: `group` não mapeia em plataforma nenhuma (ver o topo). `accessible` sozinho
174
- // é o que agrupa de verdade no RN, e o rótulo é o que diz o que este bloco é.
175
- accessibilityLabel: nome, style: [s.caixa, style], testID: testID, children: [_jsxs(View, { onLayout: (e) => setLargura(e.nativeEvent.layout.width), style: { height }, children: [largura > 0 ? (_jsxs(Svg, { width: largura, height: height, testID: testID ? `${testID}-svg` : undefined, children: [_jsx(G, { children: passos.map((v, i) => (_jsx(Line, { x1: CALHA.esquerda, y1: y(v), x2: largura - CALHA.direita, y2: y(v), stroke: t.color.border, strokeWidth: t.size.borderWidth }, `g${i}`))) }), _jsxs(G, { children: [passos.map((v, i) => (_jsx(SvgText, { x: CALHA.esquerda - 6, y: y(v) + 4, textAnchor: "end", fontSize: t.size.textXs, fontFamily: t.font.ui[400], fill: t.color.mutedForeground, children: formatValue(v) }, `ty${i}`))), labels.map((rotulo, i) => (_jsx(SvgText, { x: x(i), y: height - 6, textAnchor: "middle", fontSize: t.size.textXs, fontFamily: t.font.ui[400], fill: t.color.mutedForeground, children: rotulo }, `tx${i}`)))] }), series.map((serie, n) => {
176
- const marca = serie.mark ?? mark;
177
- const cor = cores[n];
178
- if (marca === "bar") {
179
- // As barras de várias séries dividem a faixa, com 2dp de respiro entre elas — o
180
- // vão é o que impede duas cores vizinhas de virarem um bloco só.
181
- const barras = series.filter((z) => (z.mark ?? mark) === "bar").length;
182
- const ordem = series.filter((z) => (z.mark ?? mark) === "bar").indexOf(serie);
183
- const larguraBarra = Math.max(1, (faixa * 0.7) / barras - 2);
184
- return (_jsx(G, { children: serie.data.map((v, i) => v == null ? null : (_jsx(Rect, { x: x(i) - (faixa * 0.7) / 2 + ordem * (larguraBarra + 2), y: Math.min(y(v), y(0)), width: larguraBarra, height: Math.max(1, Math.abs(y(v) - y(0))),
185
- // 4dp arredondado na ponta do dado, ancorado na base — a base fica reta.
186
- rx: 4, fill: cor }, i))) }, n));
187
- }
188
- return (_jsxs(G, { children: [marca === "area" ? (_jsx(Path, { d: caminho(serie.data, true), fill: cor, fillOpacity: 0.15 })) : null, _jsx(Path, { d: caminho(serie.data, false), stroke: cor, strokeWidth: 2, fill: "none", strokeLinecap: "round", strokeLinejoin: "round" })] }, n));
189
- }), selecionado != null && selecionado >= 0 && selecionado < labels.length ? (_jsxs(G, { children: [_jsx(Line, { x1: x(selecionado), y1: CALHA.topo, x2: x(selecionado), y2: CALHA.topo + areaA, stroke: t.color.borderStrong, strokeWidth: t.size.borderWidth }), series.map((serie, n) => {
190
- const v = serie.data[selecionado];
191
- return v == null ? null : (_jsx(Circle, { cx: x(selecionado), cy: y(v), r: 4, fill: cores[n], stroke: t.color.card, strokeWidth: 2 }, n));
192
- })] })) : null] })) : null, largura > 0 ? (_jsx(View, { style: { position: "absolute", left: CALHA.esquerda, top: CALHA.topo,
172
+ return (_jsxs(View, { style: [s.caixa, style], testID: testID, children: [_jsxs(View, { onLayout: (e) => setLargura(e.nativeEvent.layout.width), style: { height }, children: [largura > 0 ? (_jsx(View, { accessible: true, accessibilityLabel: nome, children: _jsxs(Svg, { width: largura, height: height, testID: testID ? `${testID}-svg` : undefined, children: [_jsx(G, { children: passos.map((v, i) => (_jsx(Line, { x1: CALHA.esquerda, y1: y(v), x2: largura - CALHA.direita, y2: y(v), stroke: t.color.border, strokeWidth: t.size.borderWidth }, `g${i}`))) }), _jsxs(G, { children: [passos.map((v, i) => (_jsx(SvgText, { x: CALHA.esquerda - 6, y: y(v) + 4, textAnchor: "end", fontSize: t.size.textXs, fontFamily: t.font.ui[400], fill: t.color.mutedForeground, children: formatValue(v) }, `ty${i}`))), labels.map((rotulo, i) => (_jsx(SvgText, { x: x(i), y: height - 6, textAnchor: "middle", fontSize: t.size.textXs, fontFamily: t.font.ui[400], fill: t.color.mutedForeground, children: rotulo }, `tx${i}`)))] }), series.map((serie, n) => {
173
+ const marca = serie.mark ?? mark;
174
+ const cor = cores[n];
175
+ if (marca === "bar") {
176
+ // As barras de várias séries dividem a faixa, com 2dp de respiro entre elas — o
177
+ // vão é o que impede duas cores vizinhas de virarem um bloco só.
178
+ const barras = series.filter((z) => (z.mark ?? mark) === "bar").length;
179
+ const ordem = series.filter((z) => (z.mark ?? mark) === "bar").indexOf(serie);
180
+ const larguraBarra = Math.max(1, (faixa * 0.7) / barras - 2);
181
+ return (_jsx(G, { children: serie.data.map((v, i) => v == null ? null : (_jsx(Rect, { x: x(i) - (faixa * 0.7) / 2 + ordem * (larguraBarra + 2), y: Math.min(y(v), y(0)), width: larguraBarra, height: Math.max(1, Math.abs(y(v) - y(0))),
182
+ // 4dp arredondado na ponta do dado, ancorado na base — a base fica reta.
183
+ rx: 4, fill: cor }, i))) }, n));
184
+ }
185
+ return (_jsxs(G, { children: [marca === "area" ? (_jsx(Path, { d: caminho(serie.data, true), fill: cor, fillOpacity: 0.15 })) : null, _jsx(Path, { d: caminho(serie.data, false), stroke: cor, strokeWidth: 2, fill: "none", strokeLinecap: "round", strokeLinejoin: "round" })] }, n));
186
+ }), selecionado != null && selecionado >= 0 && selecionado < labels.length ? (_jsxs(G, { children: [_jsx(Line, { x1: x(selecionado), y1: CALHA.topo, x2: x(selecionado), y2: CALHA.topo + areaA, stroke: t.color.borderStrong, strokeWidth: t.size.borderWidth }), series.map((serie, n) => {
187
+ const v = serie.data[selecionado];
188
+ return v == null ? null : (_jsx(Circle, { cx: x(selecionado), cy: y(v), r: 4, fill: cores[n], stroke: t.color.card, strokeWidth: 2 }, n));
189
+ })] })) : null] }) })) : null, largura > 0 ? (_jsx(View, { style: { position: "absolute", left: CALHA.esquerda, top: CALHA.topo,
193
190
  width: areaL, height: areaA, flexDirection: "row" }, children: labels.map((rotulo, i) => (_jsx(Pressable, { onPress: () => tocar(i), accessibilityRole: "button", accessibilityState: { selected: selecionado === i }, accessibilityLabel: [
194
191
  rotulo,
195
192
  ...series.map((serie) => {
package/dist/display.d.ts CHANGED
@@ -17,6 +17,19 @@ export interface BadgeProps extends ViewProps {
17
17
  size?: AureaBadgeSize;
18
18
  /** Um ponto antes do texto. Sozinho (sem conteúdo), o selo VIRA o ponto. */
19
19
  dot?: boolean;
20
+ /**
21
+ * Conteúdo antes do texto — na prática, um glifo (`<Icon name="checkmark" size="sm" />`).
22
+ *
23
+ * ⚠ **É NÓ e não nome de ícone, e a razão é a mesma da web** (`markup.tsx:126`): o `Icon` vive
24
+ * noutro módulo, e receber o nome obrigaria este arquivo a importá-lo — trazendo o registro de
25
+ * ícones ao grafo de todo app que só quer um selo. É a cláusula 4 da ADR-0038, e é o mesmo
26
+ * desenho que o `Button` ganhou na `0.8.3`.
27
+ *
28
+ * ⚠ **O slot NÃO tinge o que recebe.** Quem passa o glifo escolhe a cor dele.
29
+ */
30
+ leading?: React.ReactNode;
31
+ /** O mesmo, depois do texto. */
32
+ trailing?: React.ReactNode;
20
33
  count?: number;
21
34
  /** Acima disto o selo mostra `99+`. */
22
35
  max?: number;
@@ -45,7 +58,7 @@ export interface BadgeProps extends ViewProps {
45
58
  * usa selo com miniatura, e prop sem consumidor é superfície pública para manter de graça. Volta
46
59
  * quando houver tela que peça.
47
60
  */
48
- export declare function Badge({ tone, emphasis, size, dot, count, max, showZero, anchor, badgeContent, invisible, children, style, ...rest }: BadgeProps): React.JSX.Element;
61
+ export declare function Badge({ tone, emphasis, size, dot, count, max, showZero, leading, trailing, anchor, badgeContent, invisible, children, style, ...rest }: BadgeProps): React.JSX.Element;
49
62
  export type AureaStatusVariant = "neutral" | "online" | "offline" | "busy" | "away" | "success" | "warning" | "danger" | "info";
50
63
  export interface StatusProps extends ViewProps {
51
64
  variant?: AureaStatusVariant;
package/dist/display.js CHANGED
@@ -83,7 +83,7 @@ export const formatarContagem = (count, max = 99) => count > max ? `${max}+` : S
83
83
  * usa selo com miniatura, e prop sem consumidor é superfície pública para manter de graça. Volta
84
84
  * quando houver tela que peça.
85
85
  */
86
- export function Badge({ tone = "neutral", emphasis = "soft", size = "md", dot, count, max = 99, showZero, anchor, badgeContent, invisible, children, style, ...rest }) {
86
+ export function Badge({ tone = "neutral", emphasis = "soft", size = "md", dot, count, max = 99, showZero, leading, trailing, anchor, badgeContent, invisible, children, style, ...rest }) {
87
87
  const t = useAureaTokens();
88
88
  const s = folha(t);
89
89
  const acento = tone === "primary" ? t.color.primaryEmphasis
@@ -119,9 +119,9 @@ export function Badge({ tone = "neutral", emphasis = "soft", size = "md", dot, c
119
119
  backgroundColor: acento, borderWidth: 0 },
120
120
  style,
121
121
  ], ...(anchor ? { accessibilityElementsHidden: true,
122
- importantForAccessibility: "no-hide-descendants" } : rest), children: [dot && !soPonto && _jsx(View, { style: [s.ponto, { backgroundColor: corDoTexto }] }), typeof miolo === "string" || typeof miolo === "number"
122
+ importantForAccessibility: "no-hide-descendants" } : rest), children: [dot && !soPonto && _jsx(View, { style: [s.ponto, { backgroundColor: corDoTexto }] }), !soPonto && leading, typeof miolo === "string" || typeof miolo === "number"
123
123
  ? _jsx(Text, { size: size === "lg" ? "sm" : "xs", weight: 500, leading: "none", style: { color: corDoTexto }, children: miolo })
124
- : miolo] }));
124
+ : miolo, !soPonto && trailing] }));
125
125
  if (!anchor)
126
126
  return selo;
127
127
  const escondido = invisible || (count === 0 && !showZero) || (miolo == null && !dot);
package/dist/estilos.d.ts CHANGED
@@ -15,4 +15,19 @@ type NamedStyles<T> = {
15
15
  [P in keyof T]: ViewStyle | TextStyle | ImageStyle;
16
16
  };
17
17
  export declare function criarFolha<T extends NamedStyles<T> | NamedStyles<Record<string, unknown>>>(descrever: (t: AureaTokens) => T & NamedStyles<Record<string, unknown>>): (t: AureaTokens) => T;
18
+ /**
19
+ * O `color-mix(in srgb, X 12%, transparent)` do CSS, traduzido.
20
+ *
21
+ * **Não há `color-mix` no React Native** — medido no interpretador de cor, o mesmo que a ADR-0027
22
+ * já tinha atravessado a pé. O que existe é hex de OITO dígitos, e `#RRGGBBAA` com o alfa certo
23
+ * dá o mesmo pixel sobre um fundo opaco. Cai para a cor crua se ela não for hex de seis, para
24
+ * nunca produzir uma string que o RN descarte em silêncio.
25
+ *
26
+ * ⚠ **Ele morava dentro do `navigation.tsx` e mudou de casa em 17/09/2026**, quando o
27
+ * `SegmentedControl` passou a precisar do mesmo cálculo para o fio do escolhido. Copiar a função
28
+ * para o segundo arquivo é exatamente o defeito que o `CLAUDE.md` nomeia — *correção local é
29
+ * proibida sem responder "quem mais tem esse problema?"*. **Não é exportado pela porta da frente
30
+ * do pacote**: é ferramenta de dentro, e o `check 39` cobraria teste de tudo que sai por lá.
31
+ */
32
+ export declare const comOpacidade: (cor: string, pct: number) => string;
18
33
  export {};
package/dist/estilos.js CHANGED
@@ -29,3 +29,20 @@ export function criarFolha(descrever) {
29
29
  return folha;
30
30
  };
31
31
  }
32
+ /**
33
+ * O `color-mix(in srgb, X 12%, transparent)` do CSS, traduzido.
34
+ *
35
+ * **Não há `color-mix` no React Native** — medido no interpretador de cor, o mesmo que a ADR-0027
36
+ * já tinha atravessado a pé. O que existe é hex de OITO dígitos, e `#RRGGBBAA` com o alfa certo
37
+ * dá o mesmo pixel sobre um fundo opaco. Cai para a cor crua se ela não for hex de seis, para
38
+ * nunca produzir uma string que o RN descarte em silêncio.
39
+ *
40
+ * ⚠ **Ele morava dentro do `navigation.tsx` e mudou de casa em 17/09/2026**, quando o
41
+ * `SegmentedControl` passou a precisar do mesmo cálculo para o fio do escolhido. Copiar a função
42
+ * para o segundo arquivo é exatamente o defeito que o `CLAUDE.md` nomeia — *correção local é
43
+ * proibida sem responder "quem mais tem esse problema?"*. **Não é exportado pela porta da frente
44
+ * do pacote**: é ferramenta de dentro, e o `check 39` cobraria teste de tudo que sai por lá.
45
+ */
46
+ export const comOpacidade = (cor, pct) => /^#[0-9a-fA-F]{6}$/.test(cor)
47
+ ? cor + Math.round(Math.max(0, Math.min(1, pct)) * 255).toString(16).padStart(2, "0")
48
+ : cor;
@@ -84,7 +84,7 @@ export interface AlertProps extends ViewProps {
84
84
  * import InformationFilled from "@aurea-uds/native/icons/information--filled";
85
85
  * const ICONES = criarRegistroDeIcones({"information--filled": InformationFilled, …});
86
86
  */
87
- export declare function Alert({ variant, state, title, icon, onDismiss, children, style, ...rest }: AlertProps): React.JSX.Element;
87
+ export declare function Alert({ variant, state, title, icon, onDismiss, children, style, accessibilityLabel, accessibilityHint, ...rest }: AlertProps): React.JSX.Element;
88
88
  export interface EmptyStateProps {
89
89
  icon?: IconName;
90
90
  title: React.ReactNode;
package/dist/feedback.js CHANGED
@@ -53,6 +53,14 @@ const folha = criarFolha((t) => ({
53
53
  borderWidth: t.size.borderWidth, borderRadius: t.size.radiusLg,
54
54
  backgroundColor: t.color.secondary, borderColor: t.color.border,
55
55
  },
56
+ // O GRUPO de leitura: ícone + texto, lidos como uma coisa só. Ele existe porque a moldura
57
+ // deixou de ser `accessible` (ver o comentário no componente) — e sem um grupo o título e o
58
+ // corpo virariam duas paradas separadas do leitor de tela.
59
+ // Nenhuma medida nova: `flex`, `minWidth` e o MESMO `gap` da moldura. O "X" fica de fora.
60
+ grupoDoAlerta: {
61
+ flex: 1, minWidth: 0,
62
+ flexDirection: "row", alignItems: "flex-start", gap: t.size.space3,
63
+ },
56
64
  alerta_info: { backgroundColor: t.color.infoBg, borderColor: t.color.info },
57
65
  alerta_success: { backgroundColor: t.color.successBg, borderColor: t.color.success },
58
66
  alerta_warning: { backgroundColor: t.color.warningBg, borderColor: t.color.warning },
@@ -176,7 +184,11 @@ export const ICONE_DA_VARIANTE = {
176
184
  * import InformationFilled from "@aurea-uds/native/icons/information--filled";
177
185
  * const ICONES = criarRegistroDeIcones({"information--filled": InformationFilled, …});
178
186
  */
179
- export function Alert({ variant, state, title, icon, onDismiss, children, style, ...rest }) {
187
+ export function Alert({ variant, state, title, icon, onDismiss, children, style,
188
+ // 🔴 ESTES DOIS SAEM DO RESTO DE PROPÓSITO, e é consequência do conserto abaixo: a moldura
189
+ // deixou de ser elemento de acessibilidade, e rótulo em quem não é elemento **não faz nada no
190
+ // iOS**. Eles pousam no GRUPO, que é quem o leitor de tela alcança.
191
+ accessibilityLabel, accessibilityHint, ...rest }) {
180
192
  const t = useAureaTokens();
181
193
  const s = folha(t);
182
194
  const strings = useAureaStrings();
@@ -185,11 +197,15 @@ export function Alert({ variant, state, title, icon, onDismiss, children, style,
185
197
  : v === "warning" ? t.color.warning
186
198
  : v === "danger" ? (t.color.danger400 ?? t.color.destructive)
187
199
  : t.color.info;
188
- return (_jsxs(View, { accessible: true, ...(v === "danger" ? { accessibilityRole: "alert" } : null),
189
- // No Android o papel sozinho não faz o leitor falar sem foco; a região viva faz.
190
- accessibilityLiveRegion: v === "danger" ? "assertive" : "polite", style: [s.alerta, s[`alerta_${v}`], style], ...rest, children: [_jsx(Icon, { name: icon ?? ICONE_DA_VARIANTE[v], size: "md", color: corDoIcone }), _jsxs(View, { style: s.corpoDoAlerta, children: [title != null && _jsx(Text, { size: "sm", weight: 600, children: title }), children != null
191
- ? (typeof children === "string" ? _jsx(Text, { size: "sm", tone: "muted", children: children }) : children)
192
- : state ? _jsx(Text, { size: "sm", tone: "muted", children: strings.universalState[state] }) : null] }), onDismiss ? _jsx(IconButton, { appearance: "ghost", size: "sm", name: "close", label: strings.close, onPress: onDismiss }) : null] }));
200
+ return (_jsxs(View
201
+ // Android: a região viva é o que faz o leitor falar sem foco, e ela cobre a moldura
202
+ // inteira de propósito — o que muda é o conteúdo, e o "X" some junto quando o aviso fecha.
203
+ , {
204
+ // Android: a região viva é o que faz o leitor falar sem foco, e ela cobre a moldura
205
+ // inteira de propósito — o que muda é o conteúdo, e o "X" some junto quando o aviso fecha.
206
+ accessibilityLiveRegion: v === "danger" ? "assertive" : "polite", style: [s.alerta, s[`alerta_${v}`], style], ...rest, children: [_jsxs(View, { accessible: true, accessibilityLabel: accessibilityLabel, accessibilityHint: accessibilityHint, ...(v === "danger" ? { accessibilityRole: "alert" } : null), style: s.grupoDoAlerta, children: [_jsx(Icon, { name: icon ?? ICONE_DA_VARIANTE[v], size: "md", color: corDoIcone }), _jsxs(View, { style: s.corpoDoAlerta, children: [title != null && _jsx(Text, { size: "sm", weight: 600, children: title }), children != null
207
+ ? (typeof children === "string" ? _jsx(Text, { size: "sm", tone: "muted", children: children }) : children)
208
+ : state ? _jsx(Text, { size: "sm", tone: "muted", children: strings.universalState[state] }) : null] })] }), onDismiss ? _jsx(IconButton, { appearance: "ghost", size: "sm", name: "close", label: strings.close, onPress: onDismiss }) : null] }));
193
209
  }
194
210
  /**
195
211
  * O nada, dito com jeito.
package/dist/index.d.ts CHANGED
@@ -21,8 +21,8 @@ export { useAureaStrings } from "./theme.js";
21
21
  export { defaultStrings, ptBR, gravidadeDoEstado, AUREA_UNIVERSAL_STATES } from "./strings.js";
22
22
  export type { AureaStrings, AureaUniversalState } from "./strings.js";
23
23
  export { useReduceMotion } from "./movimento.js";
24
- export { BottomNav, Topbar, NavList, Stepper } from "./navigation.js";
25
- export type { BottomNavProps, TopbarProps, NavListProps, StepperProps, AureaNavItem, AureaNavListItem, AureaStepItem, AureaStepState, AureaBottomNavVariant, AureaBottomNavIndicator, AureaTopbarVariant, } from "./navigation.js";
24
+ export { BottomNav, Topbar, NavList, Stepper, Tabs } from "./navigation.js";
25
+ export type { BottomNavProps, TopbarProps, NavListProps, StepperProps, AureaNavItem, AureaNavListItem, AureaStepItem, AureaStepState, AureaBottomNavVariant, AureaBottomNavIndicator, AureaTopbarVariant, TabsProps, AureaTabItem, } from "./navigation.js";
26
26
  export { Field, Label, Input, Textarea, Select, Switch, Checkbox, Radio, SegmentedControl, Form, KeyboardAvoiding, useCampo, } from "./inputs.js";
27
27
  export type { FieldProps, LabelProps, InputProps, TextareaProps, SelectProps, SwitchProps, CheckboxProps, RadioProps, SegmentedControlProps, FormProps, KeyboardAvoidingProps, AureaFieldSize, } from "./inputs.js";
28
28
  export { InputGroup, InputGroupAddon, PasswordField, useGrupoDeCampo } from "./inputs.js";
package/dist/index.js CHANGED
@@ -48,7 +48,10 @@ export { useReduceMotion } from "./movimento.js";
48
48
  // ── Lote 3 — a moldura de navegação ────────────────────────────────────────────────────────
49
49
  // ⚠ A Aurea **não entrega roteamento**. Estes componentes recebem `items` e `current` e avisam
50
50
  // por `onPress`; quem troca de tela é o app.
51
- export { BottomNav, Topbar, NavList, Stepper } from "./navigation.js";
51
+ // ⚠ **O `Tabs` entrou em 17/09/2026, e é a única peça daqui que NÃO saiu de um lote** — ele
52
+ // veio do consumidor, que precisava trocar de painel dentro de uma tela e só tinha o
53
+ // `SegmentedControl`, que anuncia "rádio". A fronteira com o `BottomNav` está no JSDoc dele.
54
+ export { BottomNav, Topbar, NavList, Stepper, Tabs } from "./navigation.js";
52
55
  // ── Lote 4 — os formulários ────────────────────────────────────────────────────────────────
53
56
  // ⚠ O `Field` NÃO nomeia o controle por referência, como o `<label for>` da web: ele empurra o
54
57
  // nome, a dica e o estado de inválido para o controle por CONTEXTO. É a tradução que decidiu o
package/dist/inputs.d.ts CHANGED
@@ -234,7 +234,16 @@ export declare function Checkbox(p: CheckboxProps): React.JSX.Element;
234
234
  * estado do app — quem sabe qual está escolhido é o `useState` da tela, e `checked` sai dele.
235
235
  */
236
236
  export declare function Radio(p: RadioProps): React.JSX.Element;
237
- export interface SwitchProps extends Omit<ControleProps, "description"> {
237
+ /**
238
+ * ⚠ **Ele TIRAVA a `description`, e passou a aceitá-la em 17/09/2026** — pedido do consumidor
239
+ * (uma linha de explicação embaixo de "Desbloqueio por digital", no Perfil).
240
+ *
241
+ * ⚠ **E isto é uma frente em que o nativo vai ADIANTE da web, não atrás — declarado, não
242
+ * escondido.** Medido em `packages/react/src/markup.tsx`: lá o `Checkbox` tem `description`, o
243
+ * `Radio` **não**, e o `Switch` **não**. Aqui os três têm. A dívida passa a ser da web, e quem
244
+ * mexer nela deve fechar a diferença em vez de reabrir esta.
245
+ */
246
+ export interface SwitchProps extends ControleProps {
238
247
  }
239
248
  /**
240
249
  * O interruptor.
@@ -247,7 +256,7 @@ export interface SwitchProps extends Omit<ControleProps, "description"> {
247
256
  * ⚠ **E ele respeita "menos movimento"**: com a preferência ligada, o polegar salta em vez de
248
257
  * deslizar. O estado continua o mesmo — o que some é a animação, não a informação.
249
258
  */
250
- export declare function Switch({ label, checked, onChange, disabled, size, style, testID }: SwitchProps): React.JSX.Element;
259
+ export declare function Switch({ label, description, checked, onChange, disabled, size, style, testID, }: SwitchProps): React.JSX.Element;
251
260
  export interface SegmentedControlProps extends ViewProps {
252
261
  items: Array<{
253
262
  value: string;
package/dist/inputs.js CHANGED
@@ -39,7 +39,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
39
39
  // .segmented :922 linha, gap 3, padding 3, minH controlHMd, fundo muted
40
40
  import * as React from "react";
41
41
  import { Animated, KeyboardAvoidingView as KeyboardAvoidingViewRN, Modal, Platform, Pressable, ScrollView, TextInput, View, } from "react-native";
42
- import { criarFolha } from "./estilos.js";
42
+ import { comOpacidade, criarFolha } from "./estilos.js";
43
43
  import { IconButton } from "./actions.js";
44
44
  import { Icon } from "./icon.js";
45
45
  import { useReduceMotion } from "./movimento.js";
@@ -94,6 +94,20 @@ const folha = criarFolha((t) => ({
94
94
  segmento: { flex: 1, alignItems: "center", justifyContent: "center",
95
95
  paddingHorizontal: 14, borderRadius: t.size.radiusControl },
96
96
  segmentoAtivo: { backgroundColor: t.color.secondary },
97
+ // 🔴 O FIO AMARELO DO ESCOLHIDO — ele faltava, e a falta era metade do defeito C4.
98
+ // O `aurea.css:958-960` põe este fio em TODOS os selecionados da casa (`.is-selected`, aba
99
+ // ativa, botão alternado e `.segmented button.active`), e o bloco de comentário acima dele
100
+ // diz por quê, com data: *"Item de nav ativo, aba ativa, item de lista lateral e botão
101
+ // alternado ligado mostram o MESMO sinal (…) Um usuário aprende uma vez e reconhece em todo
102
+ // lugar (pedido do Victor, 24/07)."*
103
+ // Os três números saem daquela linha, não de mim: altura 2 (fração de pixel borra), recuo 15,
104
+ // e o amarelo a 75% (cheio ele grita mais que o próprio rótulo).
105
+ // ⚠ Em segmento estreito o recuo de 15 de cada lado zera a largura e o fio some — **e é o
106
+ // mesmo comportamento da web**, onde `inset-inline:15px` num box estreito não desenha nada.
107
+ fioDoSegmento: {
108
+ position: "absolute", left: 15, right: 15, bottom: 0, height: 2,
109
+ borderRadius: t.size.radiusFull, backgroundColor: comOpacidade(t.color.primary, 0.75),
110
+ },
97
111
  formulario: { gap: t.size.space5 },
98
112
  // A folha do `Select`: no RN não existe `<select>`, então a lista é um `Modal` — que é
99
113
  // exatamente o "motor novo sobre `Modal`" que a §5.2.4 previu.
@@ -348,7 +362,7 @@ export function Radio(p) { return _jsx(ControleMarcado, { ...p, papel: "radio" }
348
362
  * ⚠ **E ele respeita "menos movimento"**: com a preferência ligada, o polegar salta em vez de
349
363
  * deslizar. O estado continua o mesmo — o que some é a animação, não a informação.
350
364
  */
351
- export function Switch({ label, checked, onChange, disabled, size, style, testID }) {
365
+ export function Switch({ label, description, checked, onChange, disabled, size, style, testID, }) {
352
366
  const t = useAureaTokens();
353
367
  const s = folha(t);
354
368
  const campo = useCampo();
@@ -373,7 +387,10 @@ export function Switch({ label, checked, onChange, disabled, size, style, testID
373
387
  a.start();
374
388
  return () => a.stop();
375
389
  }, [checked, pos, reduzir]);
376
- return (_jsxs(Pressable, { testID: testID, onPress: inativo ? undefined : () => onChange?.(!checked), disabled: inativo, accessibilityRole: "switch", accessibilityState: { checked: !!checked, disabled: !!inativo }, accessibilityLabel: typeof label === "string" ? label : campo?.label, style: [{ flexDirection: "row", alignItems: "center", gap: 10 }, inativo && s.desabilitado, style], children: [_jsx(View, { style: [
390
+ return (_jsxs(Pressable, { testID: testID, onPress: inativo ? undefined : () => onChange?.(!checked), disabled: inativo, accessibilityRole: "switch", accessibilityState: { checked: !!checked, disabled: !!inativo }, accessibilityLabel: typeof label === "string" ? label : campo?.label,
391
+ // Mesma regra do `ControleMarcado`: a descrição vira DICA quando é texto. Sem isto ela
392
+ // desenha na tela e não existe para quem usa leitor de tela.
393
+ accessibilityHint: typeof description === "string" ? description : campo?.hint, style: [{ flexDirection: "row", alignItems: "center", gap: 10 }, inativo && s.desabilitado, style], children: [_jsx(View, { style: [
377
394
  s.trilho,
378
395
  { width: larguraDoTrilho, height: alturaDoTrilho },
379
396
  checked && { backgroundColor: t.color.controlSelected, borderColor: t.color.controlSelected },
@@ -382,7 +399,8 @@ export function Switch({ label, checked, onChange, disabled, size, style, testID
382
399
  { width: ladoDoPolegar, height: ladoDoPolegar,
383
400
  backgroundColor: checked ? t.color.controlSelectedForeground : t.color.mutedForeground,
384
401
  transform: [{ translateX: pos.interpolate({ inputRange: [0, 1], outputRange: [0, curso] }) }] },
385
- ] }) }), typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label] }));
402
+ ] }) }), _jsxs(View, { style: { flex: 1 }, children: [typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label, description != null && (typeof description === "string"
403
+ ? _jsx(Text, { size: "xs", tone: "muted", children: description }) : description)] })] }));
386
404
  }
387
405
  /**
388
406
  * Um de poucos, lado a lado.
@@ -399,9 +417,27 @@ export function SegmentedControl({ items, value, onChange, label, disabled, styl
399
417
  const inativo = disabled ?? campo?.disabled;
400
418
  return (_jsx(View, { accessibilityRole: "radiogroup", accessibilityLabel: label ?? campo?.label, style: [s.segmentada, inativo && s.desabilitado, style], ...rest, children: items.map((it) => {
401
419
  const ativo = it.value === value;
402
- return (_jsx(Pressable, { onPress: inativo ? undefined : () => onChange?.(it.value), disabled: inativo, accessibilityRole: "radio", accessibilityState: { checked: ativo, disabled: !!inativo }, accessibilityLabel: typeof it.label === "string" ? it.label : undefined, style: [s.segmento, ativo && s.segmentoAtivo], children: typeof it.label === "string"
403
- ? _jsx(Text, { size: "sm", weight: ativo ? 600 : 400, style: { color: ativo ? t.color.foreground : t.color.mutedForeground }, children: it.label })
404
- : it.label }, it.value));
420
+ return (_jsxs(Pressable, { onPress: inativo ? undefined : () => onChange?.(it.value), disabled: inativo, accessibilityRole: "radio", accessibilityState: { checked: ativo, disabled: !!inativo }, accessibilityLabel: typeof it.label === "string" ? it.label : undefined, style: [s.segmento, ativo && s.segmentoAtivo], children: [typeof it.label === "string"
421
+ ? _jsx(Text, { size: "sm", weight: ativo ? 600 : 400,
422
+ // 🔴 ERA `foreground`, E ISSO QUEBRAVA A LINGUAGEM DE SELECIONADO.
423
+ // Achado pelo consumidor em 17/09/2026. A web pinta o escolhido com a
424
+ // MARCA (`aurea.css:937-938`), e o comentário de cima daquela regra diz
425
+ // que nav ativo, aba ativa, item de lista e botão ligado mostram o mesmo
426
+ // sinal — ordem do Victor, 24/07. O `SegmentedControl` daqui discordava
427
+ // da web **e do `BottomNav` deste mesmo pacote**, que já usa este token.
428
+ //
429
+ // ⚠ **`primaryEmphasis` e NÃO `primary`, e a razão está no CSS:** *"sem
430
+ // fundo, a marca vira TEXTO, e o amarelo puro"* não se lê no claro
431
+ // (`aurea.css:358`). É a mesma lição que a `0.8.3` pagou no `Button`.
432
+ // No escuro os dois valem o mesmo amarelo; no claro este aprofunda.
433
+ //
434
+ // ⚠ **Uma divergência declarada, não escondida:** para o `.segmented` a
435
+ // web usa uma mistura própria (`primary 45% + foreground`) em vez do
436
+ // token, enquanto para o `.bottom-nav` usa o token. São dois âmbares
437
+ // parecidos para a MESMA linguagem, e isso é incoerência DA WEB. Escolhi
438
+ // o token, que deixa os dois componentes daqui iguais entre si.
439
+ style: { color: ativo ? t.color.primaryEmphasis : t.color.mutedForeground }, children: it.label })
440
+ : it.label, ativo && _jsx(View, { style: s.fioDoSegmento })] }, it.value));
405
441
  }) }));
406
442
  }
407
443
  /**
@@ -133,3 +133,51 @@ export interface StepperProps extends ViewProps {
133
133
  * vocabulário — é o vocabulário que existe, e dizer isso é melhor que fingir paridade.
134
134
  */
135
135
  export declare function Stepper({ items, label, doneIcon, errorIcon, style, ...rest }: StepperProps): React.JSX.Element;
136
+ export interface AureaTabItem {
137
+ id: string;
138
+ label: React.ReactNode;
139
+ content: React.ReactNode;
140
+ disabled?: boolean;
141
+ }
142
+ export interface TabsProps extends ViewProps {
143
+ tabs: AureaTabItem[];
144
+ /** O `id` da aba aberta. Controlado — quem guarda o estado é o consumidor, como na web. */
145
+ value: string;
146
+ onChange?: (id: string) => void;
147
+ /** Nome da fila de abas para o leitor de tela. */
148
+ label?: string;
149
+ }
150
+ /**
151
+ * As abas DENTRO da tela — trocar o painel, não trocar de página.
152
+ *
153
+ * ⚠ **E essa frase é a fronteira com o `BottomNav`, que o Lote 3 quase errou ao contrário.** Lá
154
+ * a ficha da web proíbe `tab` com a razão pronta — *"a tab swaps a panel inside the page, a
155
+ * bottom bar changes page"* —, e há um teste cujo único trabalho é reprovar essa palavra na barra
156
+ * de baixo. **Aqui é o caso certo**, então `tablist`/`tab` são os papéis honestos.
157
+ * Medido antes de escrever: os dois estão no tipo do RN **e** no enum Kotlin
158
+ * (`ReactAccessibilityDelegate.kt:368,370`), que é a interseção que o `check 41` cobra.
159
+ *
160
+ * ⚠ **A fila ROLA na horizontal, e a web não rola — divergência deliberada, com fonte.** A
161
+ * referência máxima de desenho deste projeto tem estados de rolagem na lista de abas
162
+ * (`left-scroll`, `right-scroll`, `left-right-scroll` no inventário), e num telefone de 360dp
163
+ * quatro rótulos já não cabem. Espremer todas seria a outra saída, e ela apaga o rótulo — que é
164
+ * o defeito que o app mediu no `SegmentedControl`. **A cápsula, o respiro e o raio continuam
165
+ * sendo os do CSS**; o que muda é o transbordo.
166
+ *
167
+ * ⚠ **O painel inativo NÃO fica montado.** Na web o motor mantém os painéis no DOM e esconde;
168
+ * aqui montar todos custaria memória e um painel com lista longa pagaria por telas que ninguém
169
+ * está vendo. **A consequência está declarada e não é neutra:** o estado interno de um painel
170
+ * (rolagem, texto digitado) **se perde** ao trocar de aba. Quem precisar do contrário guarda o
171
+ * estado fora, como já faz com `value`.
172
+ *
173
+ * ⚠ **A aba escolhida NÃO usa a cor da marca, e isso é de propósito.** O `.tab.active` da web é
174
+ * `--foreground` sobre `--secondary`, e ele **desliga o fio amarelo**
175
+ * (`aurea.css:1128`, `:after { display:none }`) que o `.segmented` recebe. São dois sinais
176
+ * diferentes para duas coisas diferentes: escolher um valor é uma coisa, trocar de painel é
177
+ * outra. **Não "harmonize" isto com o `SegmentedControl`** — a diferença foi escrita à mão lá.
178
+ *
179
+ * ⚠ **Sem orientação VERTICAL.** A web tem (`aurea.css:1122-1126`), e ela resolve um problema de
180
+ * largura de tela grande. Não há tablet medido neste projeto, e no nativo vale demanda antes de
181
+ * cobertura — a mesma decisão da `Table` do Lote 6.
182
+ */
183
+ export declare function Tabs({ tabs, value, onChange, label, style, ...rest }: TabsProps): React.JSX.Element;
@@ -1,9 +1,10 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Pressable, View, } from "react-native";
2
+ import { Pressable, View, ScrollView } from "react-native";
3
3
  import { useSafeAreaInsets } from "react-native-safe-area-context";
4
4
  import { Badge } from "./display.js";
5
- import { criarFolha } from "./estilos.js";
5
+ import { comOpacidade, criarFolha } from "./estilos.js";
6
6
  import { Icon } from "./icon.js";
7
+ import { Card } from "./layout.js";
7
8
  import { Text } from "./text.js";
8
9
  import { useAureaStrings, useAureaTokens } from "./theme.js";
9
10
  const folha = criarFolha((t) => ({
@@ -62,7 +63,32 @@ const folha = criarFolha((t) => ({
62
63
  paddingVertical: t.size.space2, paddingHorizontal: t.size.space3,
63
64
  borderRadius: t.size.radiusCard - t.size.space1,
64
65
  },
66
+ // O MIOLO da linha: ícone + texto (+ valor em texto) + seta. Ele é quem toca; o que vem
67
+ // depois dele, na moldura, é IRMÃO — ver o comentário no componente.
68
+ // Nenhuma medida nova: `flex`, `minWidth` e o MESMO `gap` da moldura.
69
+ linhaMiolo: { flex: 1, minWidth: 0, flexDirection: "row", alignItems: "center", gap: t.size.space3 },
65
70
  linhaTexto: { flex: 1, minWidth: 0 },
71
+ // ── Tabs ───────────────────────────────────────────────────────────────────────────────────
72
+ // A cápsula é a MESMA do `SegmentedControl`, e o CSS diz isso em voz alta no comentário de
73
+ // cima do `.tabs` (`aurea.css:1114`): *"Mesma regra da .segmented"*. Os três números vêm de
74
+ // lá — `gap:3px; padding:3px` — e não de mim.
75
+ abas: {
76
+ flexDirection: "row", gap: 3, padding: 3,
77
+ minHeight: t.size.controlHMd, borderRadius: t.size.radiusControl,
78
+ backgroundColor: t.color.muted,
79
+ },
80
+ // `padding:0 14px` é literal no `.tab` (`aurea.css:1127`). A aba NÃO estica (nada de `flex`):
81
+ // na web ela é conteúdo dentro de um `inline-flex`, e é isso que deixa a lista rolar quando
82
+ // não cabe, em vez de espremer todas.
83
+ // ⚠ O nome é `abaDeTab` porque `aba` JÁ EXISTE neste arquivo — é o item do `BottomNav`
84
+ // (linha 78), que tem `flex: 1` e cresce. Reusar o nome teria sobrescrito um dos dois em
85
+ // silêncio, e o `tsc` só pegou porque são chaves do mesmo objeto.
86
+ abaDeTab: {
87
+ alignItems: "center", justifyContent: "center",
88
+ paddingHorizontal: 14, borderRadius: t.size.radiusControl,
89
+ },
90
+ abaDeTabAtiva: { backgroundColor: t.color.secondary },
91
+ painel: { marginTop: t.size.space3 },
66
92
  // ── Stepper ────────────────────────────────────────────────────────────────────────────────
67
93
  trilha: { flexDirection: "row" },
68
94
  passo: { flex: 1, alignItems: "center" },
@@ -73,39 +99,6 @@ const folha = criarFolha((t) => ({
73
99
  backgroundColor: t.color.surface1,
74
100
  },
75
101
  }));
76
- // ─────────────────────────────────────────────────────────────────────────────────────────────
77
- // A DECISÃO DE ACESSIBILIDADE QUE ATRAVESSA O MÓDULO INTEIRO
78
- //
79
- // A ficha do `BottomNav` na web escreve, em voz alta, o que ela NÃO adota — e a razão atravessa
80
- // para cá sem uma vírgula de diferença:
81
- //
82
- // "What is deliberately NOT adopted is the Tabs pattern: a tab swaps a panel inside the page,
83
- // a bottom bar changes page. Giving role=tablist to a menu makes the screen reader promise
84
- // arrow keys that lead nowhere."
85
- //
86
- // No React Native existem `accessibilityRole="tab"` e `"tablist"`, e usá-los aqui seria fácil e
87
- // errado pelo mesmo motivo. **Não são usados.**
88
- //
89
- // O que a web usa no lugar é `<nav>` + `<a>` + `aria-current="page"`. Disso, o que existe no RN:
90
- //
91
- // `<nav>` → NÃO existe. Não há landmarks no Android/iOS como no HTML. A barra recebe
92
- // `accessibilityLabel` (o `label`, ou a frase do provider) e nada mais. Inventar
93
- // `toolbar` porque sobrou seria dizer uma coisa que não é.
94
- // `<a>` → `accessibilityRole="link"`, que é o mais próximo honesto de "isto leva a outro
95
- // lugar" — e é o que separa a aba de um `button`, que promete ação nesta tela.
96
- // current → `accessibilityState={{selected: true}}`. É o par do `aria-current` no RN.
97
- // ─────────────────────────────────────────────────────────────────────────────────────────────
98
- /**
99
- * O `color-mix(in srgb, X 12%, transparent)` do CSS, traduzido.
100
- *
101
- * **Não há `color-mix` no React Native** — medido no interpretador de cor, o mesmo que a ADR-0027
102
- * já tinha atravessado a pé. O que existe é hex de OITO dígitos, e `#RRGGBBAA` com o alfa certo
103
- * dá o mesmo pixel sobre um fundo opaco. Cai para a cor crua se ela não for hex de seis, para
104
- * nunca produzir uma string que o RN descarte em silêncio.
105
- */
106
- const comOpacidade = (cor, pct) => /^#[0-9a-fA-F]{6}$/.test(cor)
107
- ? cor + Math.round(Math.max(0, Math.min(1, pct)) * 255).toString(16).padStart(2, "0")
108
- : cor;
109
102
  /**
110
103
  * A barra inferior — a pele da navegação, **não a navegação**.
111
104
  *
@@ -226,11 +219,33 @@ export function Topbar({ variant = "floating", brand, children, style, ...rest }
226
219
  export function NavList({ items, chevron = "chevron--right", style, ...rest }) {
227
220
  const t = useAureaTokens();
228
221
  const s = folha(t);
229
- return (_jsx(View, { accessibilityRole: "list", style: [s.lista, style], ...rest, children: items.map((it) => (_jsxs(Pressable, { onPress: it.onPress, disabled: it.disabled, accessibilityRole: "link", accessibilityState: { disabled: !!it.disabled }, style: [s.linha, it.disabled && { opacity: 0.5 }], children: [it.icon && _jsx(Icon, { name: it.icon, size: "md", color: t.color.foreground }), _jsxs(View, { style: s.linhaTexto, children: [typeof it.label === "string"
230
- ? _jsx(Text, { size: "sm", numberOfLines: 1, children: it.label }) : it.label, it.description != null && (typeof it.description === "string"
231
- ? _jsx(Text, { size: "xs", tone: "muted", numberOfLines: 1, children: it.description })
232
- : it.description)] }), it.value != null && (typeof it.value === "string" || typeof it.value === "number"
233
- ? _jsx(Text, { size: "sm", tone: "muted", children: it.value }) : it.value), chevron && _jsx(Icon, { name: chevron, size: "sm", color: t.color.mutedForeground })] }, it.id))) }));
222
+ return (_jsx(View, { accessibilityRole: "list", style: [s.lista, style], ...rest, children: items.map((it) => {
223
+ // 🔴 DUAS COISAS QUE O CONSUMIDOR MEDIU EM 17/09/2026, e as duas moram nesta linha.
224
+ //
225
+ // 1 · **O `value` ficava DENTRO do `Pressable`.** Como ele aceita qualquer conteúdo, um
226
+ // `Switch` ali virava botão dentro de botão — e **no iOS o `Switch` desaparecia**
227
+ // para o VoiceOver (a mesma armadilha do `Combobox`, do `Alert` e do `Chart`; o
228
+ // `check 43` é quem guarda a família).
229
+ // ⚠ **O `check 43` NÃO pega este caso, e é honesto dizer:** ele lê o NOSSO código, e
230
+ // aqui o que é tocável chega de fora. **Slot que recebe conteúdo do consumidor é
231
+ // ponto cego de qualquer gate estático** — por isso o conserto é de DESENHO.
232
+ //
233
+ // 2 · **Toda linha declarava `accessibilityRole="link"`, mesmo sem `onPress`.** Uma lista
234
+ // só de leitura anunciava cada linha como link que não vai a lugar nenhum.
235
+ //
236
+ // O que ficou, e por que o `value` se divide por TIPO em vez de sair sempre:
237
+ // • valor em TEXTO fica no miolo, porque é parte da frase da linha — pô-lo fora criaria
238
+ // uma segunda parada dizendo só "42 km", solta;
239
+ // • valor em COMPONENTE fica FORA, irmão do miolo, porque pode ser tocável.
240
+ const temAcao = typeof it.onPress === "function";
241
+ const valorEhTexto = typeof it.value === "string" || typeof it.value === "number";
242
+ const miolo = (_jsxs(_Fragment, { children: [it.icon && _jsx(Icon, { name: it.icon, size: "md", color: t.color.foreground }), _jsxs(View, { style: s.linhaTexto, children: [typeof it.label === "string"
243
+ ? _jsx(Text, { size: "sm", numberOfLines: 1, children: it.label }) : it.label, it.description != null && (typeof it.description === "string"
244
+ ? _jsx(Text, { size: "xs", tone: "muted", numberOfLines: 1, children: it.description })
245
+ : it.description)] }), valorEhTexto && _jsx(Text, { size: "sm", tone: "muted", children: it.value }), temAcao && chevron
246
+ && _jsx(Icon, { name: chevron, size: "sm", color: t.color.mutedForeground })] }));
247
+ return (_jsxs(View, { style: [s.linha, it.disabled && { opacity: 0.5 }], children: [temAcao ? (_jsx(Pressable, { onPress: it.onPress, disabled: it.disabled, accessibilityRole: "link", accessibilityState: { disabled: !!it.disabled }, style: s.linhaMiolo, children: miolo })) : (_jsx(View, { accessible: true, accessibilityState: { disabled: !!it.disabled }, style: s.linhaMiolo, children: miolo })), !valorEhTexto && it.value != null ? it.value : null] }, it.id));
248
+ }) }));
234
249
  }
235
250
  /**
236
251
  * A trilha de passos — o onboarding de 3 passos que o consumidor já tem.
@@ -264,3 +279,53 @@ export function Stepper({ items, label, doneIcon = "checkmark", errorIcon = "err
264
279
  return it.onPress ? (_jsx(Pressable, { onPress: it.onPress, accessibilityRole: "button", accessibilityState: { selected: st === "active" }, style: s.passo, children: miolo }, n)) : (_jsx(View, { accessible: true, accessibilityState: { selected: st === "active" }, style: s.passo, children: miolo }, n));
265
280
  }) }));
266
281
  }
282
+ /**
283
+ * As abas DENTRO da tela — trocar o painel, não trocar de página.
284
+ *
285
+ * ⚠ **E essa frase é a fronteira com o `BottomNav`, que o Lote 3 quase errou ao contrário.** Lá
286
+ * a ficha da web proíbe `tab` com a razão pronta — *"a tab swaps a panel inside the page, a
287
+ * bottom bar changes page"* —, e há um teste cujo único trabalho é reprovar essa palavra na barra
288
+ * de baixo. **Aqui é o caso certo**, então `tablist`/`tab` são os papéis honestos.
289
+ * Medido antes de escrever: os dois estão no tipo do RN **e** no enum Kotlin
290
+ * (`ReactAccessibilityDelegate.kt:368,370`), que é a interseção que o `check 41` cobra.
291
+ *
292
+ * ⚠ **A fila ROLA na horizontal, e a web não rola — divergência deliberada, com fonte.** A
293
+ * referência máxima de desenho deste projeto tem estados de rolagem na lista de abas
294
+ * (`left-scroll`, `right-scroll`, `left-right-scroll` no inventário), e num telefone de 360dp
295
+ * quatro rótulos já não cabem. Espremer todas seria a outra saída, e ela apaga o rótulo — que é
296
+ * o defeito que o app mediu no `SegmentedControl`. **A cápsula, o respiro e o raio continuam
297
+ * sendo os do CSS**; o que muda é o transbordo.
298
+ *
299
+ * ⚠ **O painel inativo NÃO fica montado.** Na web o motor mantém os painéis no DOM e esconde;
300
+ * aqui montar todos custaria memória e um painel com lista longa pagaria por telas que ninguém
301
+ * está vendo. **A consequência está declarada e não é neutra:** o estado interno de um painel
302
+ * (rolagem, texto digitado) **se perde** ao trocar de aba. Quem precisar do contrário guarda o
303
+ * estado fora, como já faz com `value`.
304
+ *
305
+ * ⚠ **A aba escolhida NÃO usa a cor da marca, e isso é de propósito.** O `.tab.active` da web é
306
+ * `--foreground` sobre `--secondary`, e ele **desliga o fio amarelo**
307
+ * (`aurea.css:1128`, `:after { display:none }`) que o `.segmented` recebe. São dois sinais
308
+ * diferentes para duas coisas diferentes: escolher um valor é uma coisa, trocar de painel é
309
+ * outra. **Não "harmonize" isto com o `SegmentedControl`** — a diferença foi escrita à mão lá.
310
+ *
311
+ * ⚠ **Sem orientação VERTICAL.** A web tem (`aurea.css:1122-1126`), e ela resolve um problema de
312
+ * largura de tela grande. Não há tablet medido neste projeto, e no nativo vale demanda antes de
313
+ * cobertura — a mesma decisão da `Table` do Lote 6.
314
+ */
315
+ export function Tabs({ tabs, value, onChange, label, style, ...rest }) {
316
+ const t = useAureaTokens();
317
+ const s = folha(t);
318
+ const strings = useAureaStrings();
319
+ const aberta = tabs.find((it) => it.id === value);
320
+ return (_jsxs(View, { style: style, ...rest, children: [_jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false,
321
+ // Sem isto o conteúdo estica até a largura do rolador e o `padding` de 3 da cápsula
322
+ // aparece como faixa vazia à direita quando as abas cabem.
323
+ contentContainerStyle: { flexGrow: 0 }, children: _jsx(View, { accessibilityRole: "tablist", accessibilityLabel: label ?? strings.tabsLabel, style: s.abas, children: tabs.map((it) => {
324
+ const ativa = it.id === value;
325
+ return (_jsx(Pressable, { onPress: it.disabled ? undefined : () => onChange?.(it.id), disabled: it.disabled, accessibilityRole: "tab", accessibilityState: { selected: ativa, disabled: !!it.disabled }, accessibilityLabel: typeof it.label === "string" ? it.label : undefined, style: [s.abaDeTab, ativa && s.abaDeTabAtiva, it.disabled && { opacity: 0.5 }], children: typeof it.label === "string"
326
+ ? _jsx(Text, { size: "sm", weight: ativa ? 600 : 400, style: { color: ativa ? t.color.foreground : t.color.mutedForeground }, children: it.label })
327
+ : it.label }, it.id));
328
+ }) }) }), aberta
329
+ ? (_jsx(Card, { variant: "inset", accessibilityLabel: typeof aberta.label === "string" ? aberta.label : undefined, style: s.painel, children: aberta.content }))
330
+ : null] }));
331
+ }
package/dist/strings.d.ts CHANGED
@@ -22,6 +22,8 @@ export interface AureaStrings {
22
22
  dataEmpty: string;
23
23
  /** O nome da barra inferior para o leitor de tela. */
24
24
  bottomNavLabel: string;
25
+ /** Nome da fila de abas. Mesma chave da web (`pure.tsx`), traduzida de lá. */
26
+ tabsLabel: string;
25
27
  /** O nome da trilha de passos. */
26
28
  stepperLabel: string;
27
29
  /** O que o `DatePicker` mostra quando não há data escolhida. */
package/dist/strings.js CHANGED
@@ -32,6 +32,7 @@ export const defaultStrings = {
32
32
  dataError: "This could not be loaded.",
33
33
  dataEmpty: "Nothing here yet",
34
34
  bottomNavLabel: "Main",
35
+ tabsLabel: "Tabs",
35
36
  stepperLabel: "Steps",
36
37
  datePlaceholder: "Pick a date",
37
38
  cameraDenied: "Camera access is off. Turn it on in Settings to attach a photo.",
@@ -69,6 +70,7 @@ export const ptBR = {
69
70
  dataError: "Não foi possível carregar.",
70
71
  dataEmpty: "Ainda não há nada aqui",
71
72
  bottomNavLabel: "Principal",
73
+ tabsLabel: "Abas",
72
74
  stepperLabel: "Etapas",
73
75
  datePlaceholder: "Escolher data",
74
76
  cameraDenied: "O acesso à câmera está desligado. Ligue nas configurações para anexar uma foto.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurea-uds/native",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "React Native target for Aurea UDS: the theme x density provider, the first components and the Carbon icons, drawn on react-native-svg.",
5
5
  "keywords": [
6
6
  "aurea",
@@ -47,7 +47,7 @@
47
47
  "NOTICE"
48
48
  ],
49
49
  "dependencies": {
50
- "@aurea-uds/tokens": "^0.8.4"
50
+ "@aurea-uds/tokens": "^0.8.5"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@react-native-community/datetimepicker": ">=8",