@aurea-uds/native 0.27.0 → 0.29.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.
package/dist/actions.d.ts CHANGED
@@ -40,6 +40,19 @@ export interface ButtonProps extends Omit<PressableProps, "children" | "style">
40
40
  fullWidth?: boolean;
41
41
  /** Estado de alternância, anunciado ao leitor de tela como `checked`. */
42
42
  pressed?: boolean;
43
+ /**
44
+ * GAR-14 (0.28.0): o botão COMPARTILHA — os mesmos nomes da web. No aparelho, abre o compartilhar do
45
+ * sistema (`Share` do React Native); o link vai DENTRO da mensagem no Android, que descarta o `url`.
46
+ * No navegador (`react-native-web`), a regra da web: detectar o recurso; sem ele, copiar e avisar
47
+ * "Link copiado" pelo `ToastHost`, e dizer ao leitor de tela.
48
+ */
49
+ share?: AureaShareData;
50
+ }
51
+ /** O que o `Button share` compartilha (GAR-14). Os nomes são os da web. */
52
+ export interface AureaShareData {
53
+ url?: string;
54
+ title?: string;
55
+ text?: string;
43
56
  }
44
57
  /**
45
58
  * Botão. `Pressable` do RN, alvo ≥ `--target-min`, pele de token.
@@ -79,6 +92,6 @@ export declare function IconButton(props: IconButtonProps): React.JSX.Element;
79
92
  */
80
93
  export declare function folgaDoToque(t: AureaTokens, size?: AureaButtonSize): number;
81
94
  /** Fechado: sem `appearance` e sem `tone`, porque a cor é a do glifo. */
82
- export interface ThemeToggleProps extends Omit<IconButtonProps, "name" | "label" | "onPress" | "appearance" | "tone"> {
95
+ export interface ThemeToggleProps extends Omit<IconButtonProps, "name" | "label" | "onPress" | "appearance" | "tone" | "share"> {
83
96
  }
84
97
  export declare function ThemeToggle(props: ThemeToggleProps): React.JSX.Element;
package/dist/actions.js CHANGED
@@ -21,11 +21,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
21
21
  // `appearance:"nav"` — é a pele do item de navegação, e quem a consome é o `BottomNav`/`NavList`
22
22
  // do Lote 3. Aparência sem consumidor é a ADR-0034 ("ter a variante não é usar").
23
23
  import * as React from "react";
24
- import { Pressable, View } from "react-native";
24
+ import { AccessibilityInfo, Platform, Pressable, Share, View } from "react-native";
25
25
  import { canto, criarFolha, REACAO_AO_TOQUE, estadoAcessivel } from "./estilos.js";
26
26
  import { Icon } from "./icon.js";
27
27
  import { Text } from "./text.js";
28
- import { useAureaStrings, useAureaTheme, useAureaTokens, useSobreAMarca } from "./theme.js";
28
+ import { AvisosContext, useAureaStrings, useAureaTheme, useAureaTokens, useSobreAMarca } from "./theme.js";
29
29
  const ALTURA = {
30
30
  xs: "controlHXs", sm: "controlHSm", md: "controlHMd", lg: "controlHLg", xl: "controlHXl",
31
31
  };
@@ -137,6 +137,58 @@ const folha = criarFolha((t) => ({
137
137
  // `Card` com `onPress` passou a reagir igual (R-04, 24/09/2026).
138
138
  ...REACAO_AO_TOQUE,
139
139
  }));
140
+ // GAR-14 · o compartilhar do nativo. Ver a prop `share`.
141
+ async function compartilhar(dados, avisos, strings) {
142
+ if (Platform.OS !== "web") {
143
+ // O iOS manda o `url` como link de verdade e o `message` como texto; o Android só lê o `message`.
144
+ const mensagem = [dados.text, dados.url].filter(Boolean).join(" ");
145
+ try {
146
+ await Share.share(Platform.OS === "ios" && dados.url
147
+ ? { url: dados.url, message: dados.text, title: dados.title }
148
+ : { message: mensagem, title: dados.title });
149
+ }
150
+ catch { /* a pessoa fechou, ou o sistema recusou: não há o que dizer */ }
151
+ return;
152
+ }
153
+ // O alvo nativo não tem os tipos do navegador: só o pedaço que se usa aqui (como no E16).
154
+ const g = globalThis;
155
+ const nav = g.navigator;
156
+ const url = dados.url ?? g.location?.href ?? "";
157
+ const pacote = { url, ...(dados.title ? { title: dados.title } : null), ...(dados.text ? { text: dados.text } : null) };
158
+ if (typeof nav?.share === "function" && (typeof nav.canShare !== "function" || nav.canShare(pacote))) {
159
+ try {
160
+ await nav.share(pacote);
161
+ return;
162
+ }
163
+ catch (e) {
164
+ if (e?.name === "AbortError")
165
+ return;
166
+ }
167
+ }
168
+ const avisar = (falhou) => {
169
+ const texto = falhou ? strings.linkCopyFailed : strings.linkCopied;
170
+ avisos?.add(falhou ? { title: texto, description: url, type: "danger", duration: 0 } : { title: texto, type: "success", duration: 1500 });
171
+ AccessibilityInfo.announceForAccessibility(texto);
172
+ };
173
+ try {
174
+ if (typeof nav?.clipboard?.writeText !== "function")
175
+ throw new Error("sem área de transferência");
176
+ await nav.clipboard.writeText(url);
177
+ avisar(false);
178
+ }
179
+ catch {
180
+ avisar(true);
181
+ }
182
+ }
183
+ /** O toque do botão com o `share`: o `onPress` do app primeiro, depois o compartilhar. O `Button` e
184
+ * o `IconButton` usam o mesmo — com um só, os dois não divergem. */
185
+ function useToqueComShare(share, onPress) {
186
+ const avisos = React.useContext(AvisosContext);
187
+ const strings = useAureaStrings();
188
+ if (!share)
189
+ return onPress;
190
+ return (e) => { onPress?.(e); void compartilhar(share, avisos, strings); };
191
+ }
140
192
  /**
141
193
  * Botão. `Pressable` do RN, alvo ≥ `--target-min`, pele de token.
142
194
  *
@@ -150,8 +202,9 @@ export function Button(props) {
150
202
  return _jsx(CorpoDoBotao, { ...props });
151
203
  }
152
204
  /** O corpo do `Button`. `semRecuo` é só do `LinkButton`: sem recuo, sem borda e sem altura. */
153
- function CorpoDoBotao({ children, appearance = "solid", tone = "neutral", size = "md", leadingIcon, trailingIcon, leading, trailing, icons, fullWidth = false, pressed, disabled, accessibilityLabel, semRecuo = false, ...rest }) {
205
+ function CorpoDoBotao({ children, appearance = "solid", tone = "neutral", size = "md", leadingIcon, trailingIcon, leading, trailing, icons, fullWidth = false, pressed, disabled, accessibilityLabel, semRecuo = false, share, onPress, ...rest }) {
154
206
  const t = useAureaTokens();
207
+ const aoTocar = useToqueComShare(share, onPress);
155
208
  const s = folha(t);
156
209
  const marca = useSobreAMarca();
157
210
  const cor = pintarSobreAMarca(pintar(t, tone), marca, tone, appearance);
@@ -168,7 +221,7 @@ function CorpoDoBotao({ children, appearance = "solid", tone = "neutral", size =
168
221
  ...(semRecuo ? { height: undefined, paddingHorizontal: 0, borderWidth: 0 } : null),
169
222
  }), [t, size, appearance, cor.solido, cor.contorno, corDaBorda, fullWidth, semRecuo]);
170
223
  const corDoTexto = appearance === "solid" ? cor.texto : cor.sobre;
171
- return (_jsx(Pressable, { disabled: disabled, accessibilityRole: "button", accessibilityLabel: accessibilityLabel, ...estadoAcessivel({ disabled: !!disabled, pressed }), style: ({ pressed: tocando }) => [
224
+ return (_jsx(Pressable, { disabled: disabled, onPress: aoTocar, accessibilityRole: "button", accessibilityLabel: accessibilityLabel, ...estadoAcessivel({ disabled: !!disabled, pressed }), style: ({ pressed: tocando }) => [
172
225
  s.alvo, fullWidth && s.alvoLargura,
173
226
  tocando && s.pressionado, disabled && s.inerte,
174
227
  ], ...rest, children: _jsxs(View, { style: [s.caixa, caixa], children: [leading ?? null, leadingIcon ? _jsx(Icon, { name: leadingIcon, size: ICONE[size], color: corDoTexto, icons: icons }) : null, typeof children === "string"
@@ -212,8 +265,9 @@ export function folgaDoToque(t, size = "md") {
212
265
  }
213
266
  /** O corpo do `IconButton`. A cor própria do ícone (`corDoIcone`) é só do `ThemeToggle`: o
214
267
  * `IconButton` público não tem cor solta, e dentro do cartão da marca ela não vale (a tinta vence). */
215
- function BotaoDeIcone({ name, label, appearance = "ghost", tone = "neutral", size = "md", icons, pressed, disabled, corDoIcone, pesoDoIcone, ...rest }) {
268
+ function BotaoDeIcone({ name, label, appearance = "ghost", tone = "neutral", size = "md", icons, pressed, disabled, corDoIcone, pesoDoIcone, share, onPress, ...rest }) {
216
269
  const t = useAureaTokens();
270
+ const aoTocar = useToqueComShare(share, onPress);
217
271
  const s = folha(t);
218
272
  const marca = useSobreAMarca();
219
273
  const cor = pintarSobreAMarca(pintar(t, tone), marca, tone, appearance);
@@ -225,7 +279,7 @@ function BotaoDeIcone({ name, label, appearance = "ghost", tone = "neutral", siz
225
279
  backgroundColor: appearance === "solid" ? cor.solido : "transparent",
226
280
  borderColor: cor.contorno ?? (appearance === "outline" ? corDaBorda : "transparent"),
227
281
  }), [t, lado, size, appearance, cor.solido, cor.contorno, corDaBorda]);
228
- return (_jsx(Pressable, { disabled: disabled, accessibilityRole: "button", accessibilityLabel: label, ...estadoAcessivel({ disabled: !!disabled, pressed }), style: ({ pressed: tocando }) => [
282
+ return (_jsx(Pressable, { disabled: disabled, onPress: aoTocar, accessibilityRole: "button", accessibilityLabel: label, ...estadoAcessivel({ disabled: !!disabled, pressed }), style: ({ pressed: tocando }) => [
229
283
  // Largura FIXA, como o só-ícone da referência: ele nunca estica,
230
284
  // nem numa coluna sem alinhamento. O alvo é o maior entre o desenho e o `targetMin`.
231
285
  s.alvo, { width: Math.max(lado, t.size.targetMin), alignItems: "center" },
package/dist/busca.js CHANGED
@@ -76,7 +76,7 @@ import { KeyboardAvoiding, useCampo } from "./inputs.js";
76
76
  import { useReduceMotion, driverNativo } from "./movimento.js";
77
77
  import { RecuoDaFolha } from "./screen.js";
78
78
  import { Text } from "./text.js";
79
- import { useAureaStrings, useAureaTokens, ForaDaMarca, usePeleSobreAMarca } from "./theme.js";
79
+ import { useAureaStrings, useAureaTokens, useTokensDoApp, ForaDaMarca, usePeleSobreAMarca } from "./theme.js";
80
80
  // As mesmas contas do `inputs.tsx`, e elas são repetidas AQUI de propósito: exportá-las de lá
81
81
  // tornaria pública uma conta interna do outro módulo, e um `import` de função privada entre
82
82
  // módulos irmãos é o começo de um acoplamento que ninguém declara. São três linhas.
@@ -215,6 +215,9 @@ const folha = criarFolha((t) => ({
215
215
  export function Combobox({ items, value, onValueChange, onSearchChange, searchDelay = ESPERA_PADRAO, loading, onEndReached, placeholder, searchPlaceholder, empty, clearable = true, draggable = true, disabled, size, chevron = "caret-down", searchIcon = "magnifying-glass", searchKeyboardType, style, testID, }) {
216
216
  const t = useAureaTokens();
217
217
  const s = folha(t);
218
+ // A folha sai do fluxo: pinta com os tokens do APP (ADR-0063 — ver `useTokensDoApp`).
219
+ const tf = useTokensDoApp();
220
+ const sf = folha(tf);
218
221
  const peleDaMarca = usePeleSobreAMarca();
219
222
  const strings = useAureaStrings();
220
223
  const campo = useCampo();
@@ -307,7 +310,7 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
307
310
  peleDaMarca,
308
311
  inativo && s.desabilitado,
309
312
  style,
310
- ], children: [_jsx(Pressable, { testID: testID, onPress: inativo ? undefined : () => setAberto(true), disabled: inativo, accessibilityRole: "button", accessibilityLabel: campo?.label, accessibilityHint: campo?.hint, accessibilityValue: { text: value?.label }, ...estadoAcessivel({ disabled: !!inativo, expanded: aberto }), style: s.gatilho, children: _jsx(Text, { size: tam === "sm" ? "xs" : tam === "lg" ? "base" : "md", tone: value ? "default" : "subtle", numberOfLines: 1, style: { flex: 1, minWidth: 0 }, children: value?.label ?? placeholder ?? "" }) }), _jsxs(View, { style: s.acoes, children: [clearable && value != null && !inativo && (_jsx(IconButton, { name: "x", label: strings.comboboxClear, appearance: "ghost", size: "sm", onPress: () => onValueChange?.(null), testID: testID ? `${testID}-limpar` : undefined })), chevron && (_jsx(Pressable, { accessible: false, disabled: inativo, style: s.setaToque, onPress: inativo ? undefined : () => setAberto(true), testID: testID ? `${testID}-seta` : undefined, children: _jsx(Icon, { name: chevron, size: "sm", color: t.color.subtleForeground }) }))] })] }), _jsx(ForaDaMarca, { children: _jsx(Modal, { visible: aberto, transparent: true, animationType: reduzir !== false ? "none" : "slide", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: fechar, children: _jsxs(KeyboardAvoiding, { style: s.fundoDaLista, children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: fechar, accessible: false, testID: testID ? `${testID}-fundo` : undefined }), _jsxs(Animated.View, { style: [s.lista, { transform: [{ translateY: arrasto }] }], onLayout: (e) => { altura.current = e.nativeEvent.layout.height; }, children: [_jsx(View, { style: s.puxadorArea, ...(draggable ? gestos.panHandlers : null), accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", children: _jsx(View, { style: s.puxador }) }), _jsxs(View, { style: [s.grupo, { height: alturaDoTamanho(t, tam) }], ...(draggable ? gestos.panHandlers : null), children: [searchIcon && _jsx(Icon, { name: searchIcon, size: "sm", color: t.color.subtleForeground }), _jsx(TextInput, { testID: testID ? `${testID}-busca` : undefined, value: texto, onChangeText: digitar, placeholder: searchPlaceholder ?? strings.comboboxSearch, placeholderTextColor: t.color.subtleForeground,
313
+ ], children: [_jsx(Pressable, { testID: testID, onPress: inativo ? undefined : () => setAberto(true), disabled: inativo, accessibilityRole: "button", accessibilityLabel: campo?.label, accessibilityHint: campo?.hint, accessibilityValue: { text: value?.label }, ...estadoAcessivel({ disabled: !!inativo, expanded: aberto }), style: s.gatilho, children: _jsx(Text, { size: tam === "sm" ? "xs" : tam === "lg" ? "base" : "md", tone: value ? "default" : "subtle", numberOfLines: 1, style: { flex: 1, minWidth: 0 }, children: value?.label ?? placeholder ?? "" }) }), _jsxs(View, { style: s.acoes, children: [clearable && value != null && !inativo && (_jsx(IconButton, { name: "x", label: strings.comboboxClear, appearance: "ghost", size: "sm", onPress: () => onValueChange?.(null), testID: testID ? `${testID}-limpar` : undefined })), chevron && (_jsx(Pressable, { accessible: false, disabled: inativo, style: s.setaToque, onPress: inativo ? undefined : () => setAberto(true), testID: testID ? `${testID}-seta` : undefined, children: _jsx(Icon, { name: chevron, size: "sm", color: t.color.subtleForeground }) }))] })] }), _jsx(ForaDaMarca, { children: _jsx(Modal, { visible: aberto, transparent: true, animationType: reduzir !== false ? "none" : "slide", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: fechar, children: _jsxs(KeyboardAvoiding, { style: sf.fundoDaLista, children: [_jsx(Pressable, { style: sf.fundoDeToque, onPress: fechar, accessible: false, testID: testID ? `${testID}-fundo` : undefined }), _jsxs(Animated.View, { style: [sf.lista, { transform: [{ translateY: arrasto }] }], onLayout: (e) => { altura.current = e.nativeEvent.layout.height; }, children: [_jsx(View, { style: sf.puxadorArea, ...(draggable ? gestos.panHandlers : null), accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", children: _jsx(View, { style: sf.puxador }) }), _jsxs(View, { style: [sf.grupo, { height: alturaDoTamanho(t, tam) }], ...(draggable ? gestos.panHandlers : null), children: [searchIcon && _jsx(Icon, { name: searchIcon, size: "sm", color: tf.color.subtleForeground }), _jsx(TextInput, { testID: testID ? `${testID}-busca` : undefined, value: texto, onChangeText: digitar, placeholder: searchPlaceholder ?? strings.comboboxSearch, placeholderTextColor: tf.color.subtleForeground,
311
314
  // ⚠ `autoFocus` é o que faz a folha valer a pena: abrir um campo de busca e
312
315
  // exigir um segundo toque para o teclado subir é um toque a mais em cada
313
316
  // cadastro. O foco entra com a folha; o teclado vem junto.
@@ -318,17 +321,17 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
318
321
  // plataforma, não decoração: diz à pessoa que aquele campo é de busca antes de
319
322
  // ela digitar a primeira letra.
320
323
  returnKeyType: "search", keyboardType: searchKeyboardType, style: [
321
- s.campoDeTexto,
322
- { fontSize: fonteDoTamanho(t, tam), fontFamily: t.font.ui[400],
323
- color: t.color.foreground },
324
- ] }), texto.length > 0 && (_jsx(IconButton, { name: "x", label: strings.searchClear, appearance: "ghost", size: "sm", onPress: () => digitar(""), testID: testID ? `${testID}-busca-limpar` : undefined }))] }), loading && (_jsx(View, { style: s.carregando, children: _jsx(Spinner, { label: strings.comboboxLoading }) })), nada && (_jsx(View, { style: s.vazio, children: typeof empty === "string" || empty == null
324
+ sf.campoDeTexto,
325
+ { fontSize: fonteDoTamanho(t, tam), fontFamily: tf.font.ui[400],
326
+ color: tf.color.foreground },
327
+ ] }), texto.length > 0 && (_jsx(IconButton, { name: "x", label: strings.searchClear, appearance: "ghost", size: "sm", onPress: () => digitar(""), testID: testID ? `${testID}-busca-limpar` : undefined }))] }), loading && (_jsx(View, { style: sf.carregando, children: _jsx(Spinner, { label: strings.comboboxLoading }) })), nada && (_jsx(View, { style: sf.vazio, children: typeof empty === "string" || empty == null
325
328
  ? _jsx(Text, { size: "sm", tone: "muted", children: empty ?? strings.comboboxEmpty })
326
329
  : empty })), _jsx(FlatList, { data: filtrados, keyExtractor: (i) => i.value, keyboardShouldPersistTaps: "handled", onEndReached: onEndReached, onEndReachedThreshold: ANTECEDENCIA_DE_PAGINA, renderItem: ({ item }) => (_jsxs(Pressable, { disabled: item.disabled, onPress: () => { onValueChange?.(item); fechar(); }, accessibilityRole: "menuitem", ...estadoAcessivel({
327
330
  selected: item.value === value?.value, disabled: !!item.disabled,
328
331
  }), accessibilityHint: item.description, style: [
329
- s.opcao,
330
- item.value === value?.value && s.opcaoEscolhida,
331
- item.disabled && s.desabilitado,
332
+ sf.opcao,
333
+ item.value === value?.value && sf.opcaoEscolhida,
334
+ item.disabled && sf.desabilitado,
332
335
  ], children: [_jsx(Text, { size: "md", weight: item.value === value?.value ? 600 : 400, children: item.label }), item.description != null && (_jsx(Text, { size: "xs", tone: "muted", children: item.description }))] })) }), _jsx(RecuoDaFolha, { comTeclado: true })] })] }) }) })] }));
333
336
  }
334
337
  // ── A DOBRA DE ACENTO, e por que ela é sondada em vez de presumida ───────────────────────────
package/dist/data.js CHANGED
@@ -233,14 +233,14 @@ export function Table({ caption, columns, rows, keyExtractor, onRowPress, empty,
233
233
  const desenharLinha = React.useCallback((linha) => {
234
234
  const corpo = (_jsxs(_Fragment, { children: [principal ? (_jsx(View, { accessible: true, children: (() => {
235
235
  const v = conteudo(principal, linha);
236
- return typeof v === "string" ? _jsx(Text, { size: "md", weight: 600, children: v }) : v;
236
+ return typeof v === "string" ? _jsx(Text, { size: "md", weight: 600, numeric: true, children: v }) : v;
237
237
  })() })) : null, secundarias.map((coluna) => {
238
238
  const v = conteudo(coluna, linha);
239
239
  const nome = typeof coluna.header === "string" && typeof v === "string"
240
240
  ? `${coluna.header}: ${v}` : undefined;
241
241
  return (_jsxs(View, { accessible: true, accessibilityLabel: nome, style: s.celula, children: [typeof coluna.header === "string"
242
242
  ? _jsx(Text, { size: "sm", tone: "muted", style: s.nomeDaColuna, children: coluna.header })
243
- : coluna.header, typeof v === "string" ? _jsx(Text, { size: "sm", style: s.valor, children: v }) : v] }, coluna.key));
243
+ : coluna.header, typeof v === "string" ? _jsx(Text, { size: "sm", numeric: true, style: s.valor, children: v }) : v] }, coluna.key));
244
244
  })] }));
245
245
  // Só vira alvo se houver o que tocar. Uma linha `Pressable` sem ação é um botão que não faz
246
246
  // nada — e o leitor de tela a anunciaria como botão.
package/dist/display.js CHANGED
@@ -301,7 +301,7 @@ export function KPI({ label, value, trend, direction, tone, directionLabel, vari
301
301
  const nome = direction && partes.every((p) => p !== null) ? partes.filter(Boolean).join(", ") : undefined;
302
302
  const Caixa = variant === "plain" ? View : Card;
303
303
  return (_jsxs(Caixa, { style: [s.kpi, style], accessible: true, accessibilityLabel: nome, ...rest, children: [typeof label === "string" ? _jsx(Text, { size: "sm", tone: "muted", children: label }) : label, typeof value === "string" || typeof value === "number"
304
- ? _jsx(Text, { size: "3xl", weight: 700, children: value }) : value, direction
304
+ ? _jsx(Text, { size: "3xl", weight: 700, numeric: true, children: value }) : value, direction
305
305
  ? _jsxs(View, { style: s.tendencia, children: [_jsx(View, { style: s.seta, children: _jsx(Icon, { name: GLIFO_DA_DIRECAO[direction], size: "sm", color: cor }) }), temTendencia && textoDaTendencia] })
306
306
  : temTendencia && textoDaTendencia] }));
307
307
  }
package/dist/estilos.d.ts CHANGED
@@ -87,6 +87,7 @@ export type TomDeCor = "neutral" | "primary" | "info" | "success" | "warning" |
87
87
  * nomeia — e o dia em que um tom novo entra numa só.
88
88
  */
89
89
  export declare const acentoDoTom: (t: AureaTokens, tom: TomDeCor) => string;
90
+ export declare const letraDoToken: (t: AureaTokens, token: string) => string | undefined;
90
91
  /**
91
92
  * O FUNDO CHEIO de um tom de estado e a letra em cima dele — o par do botão cheio (ADR-0061, 0.27.0):
92
93
  * o selo cheio é o botão cheio do mesmo tom. `primary` e `neutral` não têm o par: `undefined`, e
package/dist/estilos.js CHANGED
@@ -95,6 +95,17 @@ export const acentoDoTom = (t, tom) => tom === "primary" ? t.color.primaryEmphas
95
95
  : tom === "warning" ? t.color.warning400 ?? t.color.warning
96
96
  : tom === "danger" ? t.color.danger400 ?? t.color.destructive
97
97
  : t.color.secondaryForeground;
98
+ /**
99
+ * A cor de LETRA (e de desenho sobre a tela) de um token de cor — ADR-0061. Os quatro tokens de
100
+ * estado (`success`, `info`, `warning`, `destructive`) são o FUNDO cheio; quem escreve ou desenha
101
+ * sobre a tela usa o par `-400` de cada tema. Qualquer outro token passa como está — e é por isso
102
+ * que a lista é FECHADA: um `primary400` que nascesse amanhã não pode mudar o texto amarelo sem
103
+ * ninguém pedir. Achado na 0.28.0: o `Text` com tom e a barra do `Progress` tinham ficado de fora.
104
+ */
105
+ const PAR_DE_LETRA = {
106
+ success: "success400", info: "info400", warning: "warning400", destructive: "danger400",
107
+ };
108
+ export const letraDoToken = (t, token) => (PAR_DE_LETRA[token] ? t.color[PAR_DE_LETRA[token]] : undefined) ?? t.color[token];
98
109
  /**
99
110
  * O FUNDO CHEIO de um tom de estado e a letra em cima dele — o par do botão cheio (ADR-0061, 0.27.0):
100
111
  * o selo cheio é o botão cheio do mesmo tom. `primary` e `neutral` não têm o par: `undefined`, e
package/dist/feedback.js CHANGED
@@ -22,7 +22,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
22
22
  import * as React from "react";
23
23
  import { Animated, Easing, View } from "react-native";
24
24
  import { IconButton } from "./actions.js";
25
- import { canto, criarFolha, estadoAcessivel } from "./estilos.js";
25
+ import { canto, criarFolha, estadoAcessivel, letraDoToken } from "./estilos.js";
26
26
  import { Icon } from "./icon.js";
27
27
  import { IconeEmMoldura } from "./moldura.js";
28
28
  import { useReduceMotion, driverNativo } from "./movimento.js";
@@ -183,7 +183,9 @@ export function Progress({ value, label, detail, tone = "brand", style, testID }
183
183
  const reduzir = useReduceMotion();
184
184
  const semTotal = value == null || Number.isNaN(value);
185
185
  const pct = semTotal ? undefined : Math.max(0, Math.min(100, value));
186
- const cor = t.color[COR_DO_TOM[tone]];
186
+ // ADR-0061: a barra é desenho sobre a trilha, como a letra — usa o par `-400`, como a web
187
+ // (`.progress-tone-*`). Na 0.27.0 ela ficou com a cor de fundo cheio; achado na 0.28.0.
188
+ const cor = letraDoToken(t, COR_DO_TOM[tone]);
187
189
  const temApoio = detail != null && detail !== false && detail !== "";
188
190
  const emTexto = temApoio && (typeof detail === "string" || typeof detail === "number") ? String(detail) : undefined;
189
191
  // A largura do trilho, para o pedaço saber quanto andar. O `translateX` do RN não aceita
package/dist/index.d.ts CHANGED
@@ -11,9 +11,9 @@ export type { IconProps, IconName, PhosphorIconName, AureaIconNames, IconWeight,
11
11
  export { Screen } from "./screen.js";
12
12
  export type { ScreenProps, AureaScreenBackground, AureaScreenEdge } from "./screen.js";
13
13
  export { Stack, Cluster, Grid, Card, Separator } from "./layout.js";
14
- export type { StackProps, ClusterProps, AureaStackAlign, AureaClusterAlign, AureaClusterJustify, GridProps, CardProps, SeparatorProps, AureaCardVariant, AureaGridMin, } from "./layout.js";
14
+ export type { StackProps, ClusterProps, AureaStackAlign, AureaClusterAlign, AureaClusterJustify, GridProps, CardProps, SeparatorProps, AureaCardVariant, AureaCardAccent, AureaGridMin, } from "./layout.js";
15
15
  export { Button, IconButton, LinkButton, ThemeToggle } from "./actions.js";
16
- export type { ButtonProps, IconButtonProps, LinkButtonProps, ThemeToggleProps, AureaButtonAppearance, AureaButtonSize, AureaButtonTone, } from "./actions.js";
16
+ export type { ButtonProps, IconButtonProps, LinkButtonProps, ThemeToggleProps, AureaButtonAppearance, AureaButtonSize, AureaButtonTone, AureaShareData, } from "./actions.js";
17
17
  export { Spinner, Skeleton, Progress, Alert, EmptyState, DataState, ICONE_DA_VARIANTE } from "./feedback.js";
18
18
  export type { SpinnerProps, SkeletonProps, ProgressProps, AlertProps, EmptyStateProps, DataStateProps, AureaSpinnerSize, AureaAlertVariant, AureaDataStateValue, AureaProgressTone, } from "./feedback.js";
19
19
  export { Badge, Status, Avatar, KPI, formatarContagem } from "./display.js";
package/dist/inputs.js CHANGED
@@ -46,7 +46,7 @@ import { Icon } from "./icon.js";
46
46
  import { useReduceMotion, driverNativo } from "./movimento.js";
47
47
  import { RecuoDaFolha } from "./screen.js";
48
48
  import { Text } from "./text.js";
49
- import { useAureaStrings, useAureaTokens, usePeleSobreAMarca, ForaDaMarca } from "./theme.js";
49
+ import { useAureaStrings, useAureaTokens, useTokensDoApp, usePeleSobreAMarca, ForaDaMarca } from "./theme.js";
50
50
  // `alturaDoTamanho` e `respiroDoTamanho` saem do arquivo (não do pacote) desde 02/10/2026: o
51
51
  // `FileInput` (R-21) desenha um gatilho com a medida do campo, e copiar os números para lá é o
52
52
  // defeito que o `CLAUDE.md` nomeia.
@@ -588,6 +588,9 @@ export function SegmentedControl({ items, value, onChange, label, disabled, just
588
588
  export function Select({ items, value, onChange, placeholder, disabled, size, chevron = "caret-down", style, testID, }) {
589
589
  const t = useAureaTokens();
590
590
  const s = folha(t);
591
+ // A folha sai do fluxo: pinta com os tokens do APP (ADR-0063 — ver `useTokensDoApp`).
592
+ const tf = useTokensDoApp();
593
+ const sf = folha(tf);
591
594
  const peleDaMarca = usePeleSobreAMarca();
592
595
  const campo = useCampo();
593
596
  const tam = size ?? campo?.size ?? "md";
@@ -603,7 +606,7 @@ export function Select({ items, value, onChange, placeholder, disabled, size, ch
603
606
  peleDaMarca,
604
607
  inativo && s.desabilitado,
605
608
  style,
606
- ], children: [_jsx(Text, { size: tam === "sm" ? "xs" : tam === "lg" ? "base" : "md", tone: escolhido ? "default" : "subtle", numberOfLines: 1, children: escolhido?.label ?? placeholder ?? "" }), chevron && _jsx(Icon, { name: chevron, size: "sm", color: peleDaMarca?.color ?? t.color.subtleForeground })] }), _jsx(ForaDaMarca, { children: _jsx(Modal, { visible: aberto, transparent: true, animationType: "slide", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: () => setAberto(false), children: _jsxs(View, { style: s.fundoDaLista, children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: () => setAberto(false), accessible: false }), _jsxs(View, { style: s.lista, children: [_jsx(ScrollView, { children: items.map((it) => (_jsx(Pressable, { disabled: it.disabled, onPress: () => { onChange?.(it.value); setAberto(false); }, accessibilityRole: "menuitem", ...estadoAcessivel({ selected: it.value === value, disabled: !!it.disabled }), style: [s.opcao, it.disabled && s.desabilitado], children: _jsx(Text, { size: "md", weight: it.value === value ? 600 : 400, children: it.label }) }, it.value))) }), _jsx(RecuoDaFolha, {})] })] }) }) })] }));
609
+ ], children: [_jsx(Text, { size: tam === "sm" ? "xs" : tam === "lg" ? "base" : "md", tone: escolhido ? "default" : "subtle", numberOfLines: 1, children: escolhido?.label ?? placeholder ?? "" }), chevron && _jsx(Icon, { name: chevron, size: "sm", color: peleDaMarca?.color ?? t.color.subtleForeground })] }), _jsx(ForaDaMarca, { children: _jsx(Modal, { visible: aberto, transparent: true, animationType: "slide", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: () => setAberto(false), children: _jsxs(View, { style: sf.fundoDaLista, children: [_jsx(Pressable, { style: sf.fundoDeToque, onPress: () => setAberto(false), accessible: false }), _jsxs(View, { style: sf.lista, children: [_jsx(ScrollView, { children: items.map((it) => (_jsx(Pressable, { disabled: it.disabled, onPress: () => { onChange?.(it.value); setAberto(false); }, accessibilityRole: "menuitem", ...estadoAcessivel({ selected: it.value === value, disabled: !!it.disabled }), style: [sf.opcao, it.disabled && sf.desabilitado], children: _jsx(Text, { size: "md", weight: it.value === value ? 600 : 400, children: it.label }) }, it.value))) }), _jsx(RecuoDaFolha, {})] })] }) }) })] }));
607
610
  }
608
611
  /**
609
612
  * A pilha de campos, com o respiro do `--space-5`.
package/dist/layout.d.ts CHANGED
@@ -92,9 +92,18 @@ export interface SeparatorProps extends ViewProps {
92
92
  * comportamento certo.
93
93
  */
94
94
  export declare function Separator({ orientation, label, style, ...rest }: SeparatorProps): React.JSX.Element;
95
- export type AureaCardVariant = "base" | "raised" | "interactive" | "inset" | "selected" | "danger" | "brand";
95
+ export type AureaCardVariant = "base" | "raised" | "interactive" | "inset" | "selected" | "danger" | "brand" | "contrast";
96
+ /** O tom do traço no topo (`accent`, MT-02, ADR-0063) — os MESMOS cinco da web. */
97
+ export type AureaCardAccent = "brand" | "success" | "info" | "warning" | "danger";
96
98
  interface CardBase extends ViewProps {
97
99
  children?: React.ReactNode;
100
+ /**
101
+ * O traço de cor no topo (MT-02, ADR-0063, 10/10/2026): `--accent-width` (4), na cor CHEIA do
102
+ * tom, igual nos dois temas. É enfeite — o nome da caixa diz o assunto —, e por isso a ADR-0063
103
+ * abriu a exceção na ADR-0061 (borda com a cor de fundo). Funciona com `onPress`, com
104
+ * `accessible` e dentro da `Grid`.
105
+ */
106
+ accent?: AureaCardAccent;
98
107
  }
99
108
  /**
100
109
  * 🔴 **O `brand` EXIGE `action`, e isso é de propósito — a regra é do TIPO, não de documento.**
@@ -162,5 +171,5 @@ export type CardProps = CardDaMarca | CardComum | CardTocavel;
162
171
  * volta de um `Card` com `Button` dentro) — é o ponto em que a referência não serve.
163
172
  * Cartão com duas ações não tem `onPress`: cada ação é um botão dentro de um cartão comum.
164
173
  */
165
- export declare function Card({ variant, style, ...rest }: CardProps): React.JSX.Element;
174
+ export declare function Card(props: CardProps): React.JSX.Element;
166
175
  export {};
package/dist/layout.js CHANGED
@@ -17,9 +17,9 @@ import { Pressable, StyleSheet, View } from "react-native";
17
17
  // `@aurea-uds/react` ja' faz (o `Stack` da web e' `HTMLAttributes & RefAttributes`, sem
18
18
  // envelope). O `ViewProps`/`TextProps` do RN 0.87 ja' declaram `ref`, entao ele viaja no
19
19
  // `...rest` sem nada a mais.
20
- import { criarFolha, peleDoCartao, peleDoCartaoEscolhido, REACAO_AO_TOQUE, estadoAcessivel } from "./estilos.js";
20
+ import { canto, criarFolha, peleDoCartao, peleDoCartaoEscolhido, REACAO_AO_TOQUE, estadoAcessivel } from "./estilos.js";
21
21
  import { Text } from "./text.js";
22
- import { useAureaTokens, SobreAMarca } from "./theme.js";
22
+ import { useAureaTheme, useAureaTokens, SobreAMarca, TemaDentro } from "./theme.js";
23
23
  const folha = criarFolha((t) => ({
24
24
  // A LINHA DO SISTEMA, e os números são os mesmos do `.separator` da web (`aurea.css`): cor de
25
25
  // `border` e espessura de UM pixel. ⚠ Um pixel não sai de `--space-*` de propósito — a linha
@@ -51,6 +51,24 @@ const folha = criarFolha((t) => ({
51
51
  // O amarelo é o DE PREENCHER — `primary`, o mesmo do botão sólido. A borda some: uma superfície
52
52
  // cheia não precisa de contorno, e um contorno sobre o amarelo mede 2,43 no claro (invisível).
53
53
  card_brand: { backgroundColor: t.color.primary, borderColor: "transparent" },
54
+ // MT-01 (ADR-0063): o `contrast` é pintado DENTRO do tema escuro (`TemaDentro`), então o fundo do
55
+ // `cardBase` já é o cartão do escuro. No claro, sem linha em volta (13,3:1 contra a tela); no
56
+ // escuro, o amarelo, com a borda de sempre.
57
+ card_contrast: { borderColor: "transparent" },
58
+ contrasteNoEscuro: { borderColor: t.color.primary },
59
+ // MT-02 (ADR-0063): o traço no topo. Uma borda mais grossa só em cima, com o canto 22, desenha
60
+ // errado no Android (react-native#51926, fechada sem conserto em 24/03/2026: a borda sai do
61
+ // contorno e deixa vão). Então o traço é uma faixa de `accentWidth` numa camada que se recorta no
62
+ // canto do cartão, por cima da borda de 1 — o mesmo desenho do `border-top` da web, que afina na
63
+ // curva. A camada cobre a CAIXA DA BORDA (deslocada de `borderWidth` para fora), e o recheio de
64
+ // cima cresce o que o traço come, para o conteúdo ficar onde fica na web.
65
+ comTraco: { paddingTop: t.size.cardPad + t.size.accentWidth - t.size.borderWidth },
66
+ tracoCamada: {
67
+ position: "absolute", top: -t.size.borderWidth, left: -t.size.borderWidth,
68
+ right: -t.size.borderWidth, bottom: -t.size.borderWidth,
69
+ ...canto(t.size.radiusCard), overflow: "hidden",
70
+ },
71
+ traco: { height: t.size.accentWidth },
54
72
  // A ação fica embaixo, separada pelo mesmo respiro que o resto da casa usa entre blocos.
55
73
  acaoDaMarca: { marginTop: t.size.space3 },
56
74
  }));
@@ -194,20 +212,37 @@ export function Separator({ orientation = "horizontal", label, style, ...rest })
194
212
  * volta de um `Card` com `Button` dentro) — é o ponto em que a referência não serve.
195
213
  * Cartão com duas ações não tem `onPress`: cada ação é um botão dentro de um cartão comum.
196
214
  */
197
- export function Card({ variant, style, ...rest }) {
215
+ export function Card(props) {
216
+ // MT-01 (ADR-0063, 10/10/2026): o `contrast` é o tema escuro DENTRO do cartão, então TODO filho
217
+ // se ajusta — texto, selo, botão, campo —, e não só a lista declarada do `brand`. A marca não
218
+ // atravessa: dentro de um cartão amarelo, o `contrast` não é amarelo, e a tinta dele não vale.
219
+ if (props.variant === "contrast") {
220
+ return (_jsx(TemaDentro, { tema: "dark", children: _jsx(SobreAMarca.Provider, { value: null, children: _jsx(CorpoDoCartao, { ...props }) }) }));
221
+ }
222
+ return _jsx(CorpoDoCartao, { ...props });
223
+ }
224
+ /** A cor CHEIA de cada tom do traço — a mesma nos dois temas (ADR-0063, exceção à ADR-0061). */
225
+ const COR_DO_TRACO = {
226
+ brand: "primary", success: "success", info: "info", warning: "warning", danger: "destructive",
227
+ };
228
+ function CorpoDoCartao({ variant, accent, style, ...rest }) {
198
229
  const t = useAureaTokens();
230
+ const { theme: temaDoApp } = useAureaTheme();
199
231
  const s = folha(t);
200
232
  const { action, children, onPress, disabled, ...resto } = rest;
233
+ // O contorno do `contrast` olha o tema do APP: dentro do cartão os tokens já são os do escuro.
234
+ const contorno = variant === "contrast" && temaDoApp === "dark" ? s.contrasteNoEscuro : null;
235
+ const traco = accent == null ? null : (_jsx(View, { pointerEvents: "none", collapsable: false, style: s.tracoCamada, children: _jsx(View, { style: [s.traco, { backgroundColor: t.color[COR_DO_TRACO[accent]] }] }) }));
201
236
  if (onPress != null) {
202
237
  const pele = variant ?? "interactive";
203
- return (_jsx(Pressable, { onPress: onPress, disabled: disabled, accessibilityRole: "button", ...estadoAcessivel({ disabled: !!disabled }), style: ({ pressed: tocando }) => [
204
- s.cardBase, s[`card_${pele}`],
238
+ return (_jsxs(Pressable, { onPress: onPress, disabled: disabled, accessibilityRole: "button", ...estadoAcessivel({ disabled: !!disabled }), style: ({ pressed: tocando }) => [
239
+ s.cardBase, s[`card_${pele}`], contorno, traco != null && s.comTraco,
205
240
  tocando && REACAO_AO_TOQUE.pressionado, disabled && REACAO_AO_TOQUE.inerte,
206
241
  style,
207
- ], ...resto, children: children }));
242
+ ], ...resto, children: [traco, children] }));
208
243
  }
209
- const corpo = (_jsxs(_Fragment, { children: [children, action != null && _jsx(View, { style: s.acaoDaMarca, children: action })] }));
210
- const pele = _jsx(View, { style: [s.cardBase, s[`card_${variant ?? "base"}`], style], ...resto, children: corpo });
244
+ const corpo = (_jsxs(_Fragment, { children: [traco, children, action != null && _jsx(View, { style: s.acaoDaMarca, children: action })] }));
245
+ const pele = (_jsx(View, { style: [s.cardBase, s[`card_${variant ?? "base"}`], contorno, traco != null && s.comTraco, style], ...resto, children: corpo }));
211
246
  // 🔴 SOBRE O AMARELO A COR DO TEXTO NÃO É ESCOLHA, É OBRIGAÇÃO — e os números decidiram isto.
212
247
  // Medido nos dois temas, texto contra o `primary`:
213
248
  // texto comum ........ 10,34 no claro · **1,83 no escuro** 🔴 ilegível
package/dist/midia.d.ts CHANGED
@@ -30,6 +30,14 @@ export interface ImageProps {
30
30
  */
31
31
  render?: React.ReactElement;
32
32
  onError?: () => void;
33
+ /**
34
+ * GAR-15 (0.28.0): a captura dentro de uma moldura de celular — GENÉRICA, sem ilha, entalhe nem
35
+ * botões, como na web. Com moldura, o `style` vai para a MOLDURA (tamanho e lugar); a proporção
36
+ * (`ratio`, padrão 9/19,5) vai para a captura.
37
+ */
38
+ frame?: "phone";
39
+ /** GAR-15 (0.28.0): a captura do tema escuro. No tema escuro aparece esta; no claro, a `source`. */
40
+ sourceDark?: AureaImageSource;
33
41
  style?: StyleProp<ViewStyle>;
34
42
  testID?: string;
35
43
  }
@@ -48,7 +56,7 @@ export interface ImageProps {
48
56
  * Trocar o bitmap por uma caixa muda o que se vê, não o que a foto É — e o `role="image"` mapeia
49
57
  * nos dois sistemas (`ReactAccessibilityDelegate.kt:461`, `RCTConversions.h:96`).
50
58
  */
51
- export declare function Image({ source, alt, ratio, fit, fallback, fallbackIcon, render, onError, style, testID, }: ImageProps): React.JSX.Element;
59
+ export declare function Image({ source: fonteClara, alt, ratio, fit, fallback, fallbackIcon, render, onError, frame, sourceDark, style, testID, }: ImageProps): React.JSX.Element;
52
60
  export interface AureaGalleryItem {
53
61
  id: string;
54
62
  source: AureaImageSource;
package/dist/midia.js CHANGED
@@ -56,6 +56,16 @@ const folha = criarFolha((t) => ({
56
56
  },
57
57
  contain: { backgroundColor: "transparent" },
58
58
  quebrada: { alignItems: "center", justifyContent: "center" },
59
+ // GAR-15 (0.28.0): a moldura de celular — os mesmos tokens da web (`.image-frame-phone`): a
60
+ // superfície do cartão, a borda, a sombra média e o canto da folha de baixo; o de dentro acompanha.
61
+ moldura: {
62
+ padding: t.size.space2,
63
+ borderWidth: t.size.borderWidth, borderColor: t.color.border,
64
+ ...canto(t.size.radiusSheet),
65
+ backgroundColor: t.color.card,
66
+ ...(t.shadow.shadowMd ? { boxShadow: [t.shadow.shadowMd] } : null),
67
+ },
68
+ naMoldura: { ...canto(t.size.radiusSheet - t.size.space2), aspectRatio: 9 / 19.5 },
59
69
  ladrilho: { gap: t.size.space1, padding: t.size.space1, ...canto(t.size.radiusLg) },
60
70
  ladrilhoEscolhido: { backgroundColor: t.color.secondary },
61
71
  }));
@@ -93,10 +103,12 @@ const fonteDaImagem = (s) => typeof s === "string" ? { uri: s } : s;
93
103
  * Trocar o bitmap por uma caixa muda o que se vê, não o que a foto É — e o `role="image"` mapeia
94
104
  * nos dois sistemas (`ReactAccessibilityDelegate.kt:461`, `RCTConversions.h:96`).
95
105
  */
96
- export function Image({ source, alt, ratio, fit = "cover", fallback, fallbackIcon = "image", render, onError, style, testID, }) {
106
+ export function Image({ source: fonteClara, alt, ratio, fit = "cover", fallback, fallbackIcon = "image", render, onError, frame, sourceDark, style, testID, }) {
97
107
  const t = useAureaTokens();
98
108
  const s = folha(t);
99
109
  const [quebrou, setQuebrou] = React.useState(false);
110
+ // O tema é do provider, então aqui basta escolher a fonte — não há duas imagens como na web.
111
+ const source = t.theme === "dark" && sourceDark ? sourceDark : fonteClara;
100
112
  // A `source` nova merece uma tentativa nova — é a mesma linha do `Avatar` (`display.tsx:283`),
101
113
  // e sem ela uma URL quebrada deixa um buraco permanente mesmo depois de o app trocar a foto.
102
114
  React.useEffect(() => { setQuebrou(false); }, [source]);
@@ -104,11 +116,14 @@ export function Image({ source, alt, ratio, fit = "cover", fallback, fallbackIco
104
116
  const caixa = [
105
117
  s.imagem,
106
118
  fit === "contain" && s.contain,
119
+ frame && s.naMoldura,
107
120
  proporcao != null && { aspectRatio: proporcao },
108
- style,
121
+ !frame && style,
109
122
  ];
123
+ // Com moldura, a imagem vai dentro de uma caixa que leva o `style` e o `testID` do consumidor.
124
+ const moldar = (filho) => frame ? _jsx(View, { testID: testID, style: [s.moldura, style], children: filho }) : filho;
110
125
  if (quebrou) {
111
- return (_jsx(View, { testID: testID, style: [caixa, s.quebrada], accessible: true, accessibilityRole: "image", accessibilityLabel: alt || undefined, children: fallback ?? (fallbackIcon
126
+ return moldar(_jsx(View, { testID: frame ? undefined : testID, style: [caixa, s.quebrada], accessible: true, accessibilityRole: "image", accessibilityLabel: alt || undefined, children: fallback ?? (fallbackIcon
112
127
  ? _jsx(Icon, { name: fallbackIcon, size: "lg", color: t.color.mutedForeground })
113
128
  : null) }));
114
129
  }
@@ -120,21 +135,21 @@ export function Image({ source, alt, ratio, fit = "cover", fallback, fallbackIco
120
135
  accessible: alt !== "",
121
136
  accessibilityRole: alt !== "" ? "image" : undefined,
122
137
  accessibilityLabel: alt !== "" ? alt : undefined,
123
- testID,
138
+ testID: frame ? undefined : testID,
124
139
  };
125
140
  // `cloneElement` e não `useRender`: o idioma do Base UI não existe aqui, e o que o caso real
126
141
  // precisa é de um elemento pronto recebendo as nossas props. As props do consumidor vêm
127
142
  // primeiro no objeto do elemento e as nossas depois — `source` e `onError` são o contrato
128
143
  // deste componente, e deixá-las sobrescrevíveis seria prometer o substituto e não entregá-lo.
129
144
  if (render)
130
- return React.cloneElement(render, comuns);
145
+ return moldar(React.cloneElement(render, comuns));
131
146
  // ⚠ O molde é `ImageStyle` e não `ViewStyle`, e a conversão é CONSCIENTE: os dois tipos só
132
147
  // divergem em duas coisas — o `overflow` do `ViewStyle` aceita `"scroll"`, que o `ImageStyle`
133
148
  // não tem, e o `ImageStyle` soma `resizeMode`/`tintColor`/`overlayColor`. **Nada aqui escreve
134
149
  // nenhuma das duas**: a folha põe largura, raio, fundo e proporção, e o `resizeMode` vai como
135
150
  // prop, ao lado. A API pública fica em `ViewStyle` de propósito — quem chama pensa em caixa, e
136
151
  // fazer o consumidor importar `ImageStyle` para passar um raio seria vazar o primitivo.
137
- return _jsx(ImageRN, { ...comuns, style: caixa, resizeMode: fit });
152
+ return moldar(_jsx(ImageRN, { ...comuns, style: caixa, resizeMode: fit }));
138
153
  }
139
154
  /**
140
155
  * A grade de fotos, e a foto grande quando se toca.
package/dist/overlays.js CHANGED
@@ -62,7 +62,7 @@ import { canto, cantosDeCima, criarFolha } from "./estilos.js";
62
62
  import { IconButton } from "./actions.js";
63
63
  import { RecuoDaFolha } from "./screen.js";
64
64
  import { Text } from "./text.js";
65
- import { useAureaStrings, useAureaTokens, ForaDaMarca } from "./theme.js";
65
+ import { useAureaStrings, useTokensDoApp, ForaDaMarca } from "./theme.js";
66
66
  import { useReduceMotion, driverNativo } from "./movimento.js";
67
67
  const folha = criarFolha((t) => ({
68
68
  fundo: { flex: 1, backgroundColor: t.color.overlay },
@@ -163,7 +163,7 @@ const naoAtravessa = { onStartShouldSetResponder: () => true };
163
163
  * fica presa — o equivalente exato de um diálogo web que ignora `Escape`.
164
164
  */
165
165
  export function Dialog({ open, title, children, footer, onClose, scroll = true, style, testID, }) {
166
- const t = useAureaTokens();
166
+ const t = useTokensDoApp();
167
167
  const s = folha(t);
168
168
  const strings = useAureaStrings();
169
169
  const Corpo = scroll ? ScrollView : View;
@@ -192,7 +192,7 @@ export function Dialog({ open, title, children, footer, onClose, scroll = true,
192
192
  * separa esta caixa de um `Dialog` para quem não vê é o texto, não um atributo.
193
193
  */
194
194
  export function ConfirmDialog({ open, title, description, confirmLabel, cancelLabel, destructive, onConfirm, onCancel, testID, }) {
195
- const t = useAureaTokens();
195
+ const t = useTokensDoApp();
196
196
  const s = folha(t);
197
197
  const strings = useAureaStrings();
198
198
  return (_jsx(ForaDaMarca, { children: _jsx(Modal, { visible: open, transparent: true, animationType: "fade", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: onCancel, testID: testID, children: _jsx(View, { style: [s.fundo, s.centro], children: _jsxs(View, { ...naoAtravessa, style: [s.superficie, s.confirmar], children: [_jsx(View, { style: s.cabecalho, children: _jsx(Text, { size: "lg", weight: 600, accessibilityRole: "header", style: s.titulo, children: title }) }), _jsx(View, { style: s.corpo, children: typeof description === "string"
@@ -211,7 +211,7 @@ export function ConfirmDialog({ open, title, description, confirmLabel, cancelLa
211
211
  * cascata de `prefers-reduced-motion` que a web ganha de graça.
212
212
  */
213
213
  export function Drawer({ open, title, children, onClose, side = "right", scroll = true, style, testID, }) {
214
- const t = useAureaTokens();
214
+ const t = useTokensDoApp();
215
215
  const s = folha(t);
216
216
  const strings = useAureaStrings();
217
217
  const reduzir = useReduceMotion();
@@ -267,7 +267,7 @@ export function Drawer({ open, title, children, onClose, side = "right", scroll
267
267
  * terço da altura da folha. Fechar cedo demais é como um gesto de fechar vira perda de dado.
268
268
  */
269
269
  export function BottomSheet({ open, title, children, onClose, grabber = true, draggable = true, scroll = true, style, testID, }) {
270
- const t = useAureaTokens();
270
+ const t = useTokensDoApp();
271
271
  const s = folha(t);
272
272
  const reduzir = useReduceMotion();
273
273
  const arrasto = React.useRef(new Animated.Value(0)).current;
package/dist/strings.d.ts CHANGED
@@ -60,6 +60,10 @@ export interface AureaStrings {
60
60
  confirmProceed: string;
61
61
  /** O botão que dispensa um aviso da pilha. Mesmo nome da web. */
62
62
  dismissNotification: string;
63
+ /** GAR-14 (0.28.0): o aviso do `Button share` quando o link foi copiado. Mesmo nome da web. */
64
+ linkCopied: string;
65
+ /** GAR-14 (0.28.0): o aviso quando nem compartilhar nem copiar deu certo. Mesmo nome da web. */
66
+ linkCopyFailed: string;
63
67
  /** O nome da `Table` sem `caption`. Mesmo nome — e mesmo recuo — da web. */
64
68
  tableLabel: string;
65
69
  /** O nome do `Chart` sem `label`. Mesmo nome da web. */
package/dist/strings.js CHANGED
@@ -48,6 +48,8 @@ export const defaultStrings = {
48
48
  confirmCancel: "Cancel",
49
49
  confirmProceed: "Continue",
50
50
  dismissNotification: "Dismiss notification",
51
+ linkCopied: "Link copied",
52
+ linkCopyFailed: "Could not copy. Copy the link below.",
51
53
  tableLabel: "Table",
52
54
  chartLabel: "Chart",
53
55
  increment: "Increase",
@@ -96,6 +98,8 @@ export const ptBR = {
96
98
  confirmCancel: "Cancelar",
97
99
  confirmProceed: "Continuar",
98
100
  dismissNotification: "Dispensar notificação",
101
+ linkCopied: "Link copiado",
102
+ linkCopyFailed: "Não deu para copiar. Copie o link abaixo.",
99
103
  tableLabel: "Tabela",
100
104
  chartLabel: "Gráfico",
101
105
  increment: "Aumentar",
package/dist/text.d.ts CHANGED
@@ -2,10 +2,10 @@ import * as React from "react";
2
2
  import { type TextProps as TextPropsRN } from "react-native";
3
3
  /** A escala dos tokens (`--text-xs` … `--text-5xl`). `md` é o corpo, como no `body` da web. */
4
4
  export type AureaTextSize = "xs" | "sm" | "md" | "base" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl";
5
- /** Os quatro pesos que o pacote de fontes entrega. Ver ADR-0039: cada peso é uma FAMÍLIA. */
6
- export type AureaTextWeight = 400 | 500 | 600 | 700;
7
- /** Papel de tipografia — os três `--font-*` da Aurea. */
8
- export type AureaTextFont = "ui" | "editorial" | "code";
5
+ /** Os pesos que o pacote de fontes entrega (o 800 desde a 0.28.0, GAR-16). Ver ADR-0039: cada peso é uma FAMÍLIA. */
6
+ export type AureaTextWeight = 400 | 500 | 600 | 700 | 800;
7
+ /** Papel de tipografia — os `--font-*` da Aurea (o `heading` desde a 0.28.0). */
8
+ export type AureaTextFont = "ui" | "editorial" | "code" | "heading";
9
9
  /** O que a cor SIGNIFICA. Mesmo vocabulário do `tone` do Button (ADR-0044). */
10
10
  export type AureaTextTone = "default" | "muted" | "subtle" | "primary" | "link" | "danger" | "success" | "warning" | "info";
11
11
  export type AureaTextLeading = "none" | "tight" | "normal" | "relaxed";
@@ -31,6 +31,11 @@ export interface TextProps extends TextPropsRN {
31
31
  align?: "auto" | "left" | "right" | "center";
32
32
  /** Aplica o `tracking` do token. É RAZÃO de `em`: multiplicado pelo `fontSize` aqui dentro. */
33
33
  tracking?: "tight" | "normal" | "wide" | "wider" | "widest";
34
+ /**
35
+ * GAR-16 (0.28.0): algarismos da mesma largura (`fontVariant: ["tabular-nums"]`, iOS e Android) —
36
+ * para número que alinha em coluna ou muda na tela. Desligado por padrão, como na web.
37
+ */
38
+ numeric?: boolean;
34
39
  }
35
40
  /**
36
41
  * Texto da Aurea. Sem provider acima, `useAureaTokens` levanta — de propósito: um padrão
@@ -42,7 +47,7 @@ export interface TextProps extends TextPropsRN {
42
47
  * próprias. Pedir peso 600 por `fontWeight` devolveria o Regular sintetizado — **em silêncio**.
43
48
  * Quem escolhe a fonte aqui é o `fontFamily`, com o nome PostScript que o provider já resolveu.
44
49
  */
45
- export declare function Text({ type, size, weight: pesoPedido, font: fontePedida, tone, leading, italic, align, tracking: trackingPedido, style, ...rest }: TextProps): React.JSX.Element;
50
+ export declare function Text({ type, size, weight: pesoPedido, font: fontePedida, tone, leading, italic, align, tracking: trackingPedido, numeric, style, ...rest }: TextProps): React.JSX.Element;
46
51
  export type AureaTypographyColor = "default" | "muted";
47
52
  export type AureaTypographyWeight = "normal" | "medium" | "semibold" | "bold";
48
53
  export type AureaTypographyAlign = "start" | "center" | "end" | "justify";
package/dist/text.js CHANGED
@@ -21,7 +21,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
21
21
  // que os tokens já publicam seria uma segunda verdade sobre tipografia.
22
22
  import * as React from "react";
23
23
  import { Text as TextRN } from "react-native";
24
- import { canto, criarFolha } from "./estilos.js";
24
+ import { canto, criarFolha, letraDoToken } from "./estilos.js";
25
25
  import { useAureaTokens, useSobreAMarca } from "./theme.js";
26
26
  // 🔴 A ESCALA TEM CINCO DEGRAUS QUE SE ENXERGAM, E DEZ NOMES — ordem do Victor, 19/09/2026,
27
27
  // olhando o app no aparelho: *"o tamanho das fontes: existem variações demais, fica muito
@@ -66,7 +66,8 @@ const TAMANHO = {
66
66
  const ENTRELINHA = {
67
67
  none: "leadingNone", tight: "leadingTight", normal: "leadingNormal", relaxed: "leadingRelaxed",
68
68
  };
69
- const titulo = (tamanho) => ({ tamanho, peso: 600, entrelinha: "leadingTight", fonte: "ui", junto: true });
69
+ // O título usa o papel `heading` (GAR-16, 0.28.0) — hoje a mesma fonte do texto, no mesmo peso 600.
70
+ const titulo = (tamanho) => ({ tamanho, peso: 600, entrelinha: "leadingTight", fonte: "heading", junto: true });
70
71
  const PAPEL = {
71
72
  h1: titulo("text4xl"), h2: titulo("text3xl"), h3: titulo("text2xl"),
72
73
  h4: titulo("textXl"), h5: titulo("textLg"), h6: titulo("textBase"),
@@ -90,7 +91,10 @@ const COR = {
90
91
  const folha = criarFolha((t) => {
91
92
  const tons = {};
92
93
  for (const [tom, token] of Object.entries(COR)) {
93
- tons[tom] = { color: t.color[token] ?? t.color.foreground };
94
+ // ADR-0061: `success`, `info`, `warning` e `destructive` são a cor de FUNDO cheio; a LETRA é o
95
+ // par `-400` de cada tema. Na 0.27.0 este mapa ficou de fora e o texto de tom saía com a cor de
96
+ // fundo — no escuro, ~2,5:1. Achado na 0.28.0; `paleta-adr0061.test.tsx` cobra.
97
+ tons[tom] = { color: letraDoToken(t, token) ?? t.color.foreground };
94
98
  }
95
99
  return tons;
96
100
  });
@@ -104,7 +108,7 @@ const folha = criarFolha((t) => {
104
108
  * próprias. Pedir peso 600 por `fontWeight` devolveria o Regular sintetizado — **em silêncio**.
105
109
  * Quem escolhe a fonte aqui é o `fontFamily`, com o nome PostScript que o provider já resolveu.
106
110
  */
107
- export function Text({ type, size, weight: pesoPedido, font: fontePedida, tone = "default", leading, italic = false, align, tracking: trackingPedido, style, ...rest }) {
111
+ export function Text({ type, size, weight: pesoPedido, font: fontePedida, tone = "default", leading, italic = false, align, tracking: trackingPedido, numeric = false, style, ...rest }) {
108
112
  const t = useAureaTokens();
109
113
  const s = folha(t);
110
114
  const sobreAMarca = useSobreAMarca();
@@ -134,13 +138,14 @@ export function Text({ type, size, weight: pesoPedido, font: fontePedida, tone =
134
138
  // É a impedância que a Etapa 2 mediu e resolveu emitindo razão em vez de dp.
135
139
  ...(tracking ? { letterSpacing: fontSize * t.tracking[`tracking${tracking[0].toUpperCase()}${tracking.slice(1)}`] } : null),
136
140
  ...(align ? { textAlign: align } : null),
141
+ ...(numeric ? { fontVariant: ["tabular-nums"] } : null),
137
142
  // O código leva a pele do `code` da web: fundo, canto e um recheio pequeno (a referência).
138
143
  ...(type === "code" ? {
139
144
  alignSelf: "flex-start", backgroundColor: t.color.surface2, ...canto(t.size.radiusXs),
140
145
  paddingHorizontal: t.size.space1, paddingVertical: t.size.space05,
141
146
  } : null),
142
147
  };
143
- }, [t, type, papel, size, weight, font, leading, italic, align, tracking]);
148
+ }, [t, type, papel, size, weight, font, leading, italic, align, tracking, numeric]);
144
149
  // 🔴 SOBRE UMA SUPERFÍCIE DA MARCA A COR É FORÇADA, e ela vence até o tom explícito.
145
150
  // Medido contra o `primary` nos dois temas: texto comum dá **1,83 no escuro**, esmaecido
146
151
  // **1,35**, e `danger` menos ainda. **Nenhum tom alcança os 4,5 da norma sobre o amarelo** —
package/dist/theme.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { AureaToastManager } from "./toast.js";
1
2
  import * as React from "react";
2
3
  import { type AureaIconRegistry } from "./icon.js";
3
4
  import { type AureaStrings } from "./strings.js";
@@ -73,6 +74,12 @@ export declare function useAureaTokens(): AureaTokens;
73
74
  * As frases visíveis. Mesmo nome do hook da web (`useAureaStrings`), tabela bem menor — ver
74
75
  * `strings.ts`.
75
76
  */
77
+ /**
78
+ * O gerente de avisos do `ToastHost`, para quem não pode importar o `toast.tsx` — o `Button share`
79
+ * (GAR-14, 0.28.0): o `toast.tsx` importa o `IconButton`, e o botão importar o toast fecharia um
80
+ * ciclo. Interno: o público continua sendo o `useToast`. `null` sem `ToastHost` acima.
81
+ */
82
+ export declare const AvisosContext: React.Context<AureaToastManager | null>;
76
83
  export declare function useAureaStrings(): AureaStrings;
77
84
  export interface SobreAMarcaValor {
78
85
  /** A única cor que se pode desenhar sobre a marca: letra, contorno e glifo. 4,54 nos dois temas. */
@@ -111,6 +118,29 @@ export declare function useSobreAMarca(): SobreAMarcaValor | null;
111
118
  export declare function ForaDaMarca({ children }: {
112
119
  children?: React.ReactNode;
113
120
  }): React.JSX.Element;
121
+ /**
122
+ * O tema num pedaço da tela — o `Card variant="contrast"` (MT-01, ADR-0063, 10/10/2026). Os filhos
123
+ * leem os tokens do `tema` pedido; o resto do app não muda. É o jeito da referência principal de
124
+ * fazer um pedaço escuro (o tema aplicado num elemento), e o da web (`data-theme` no cartão).
125
+ *
126
+ * ⚠ **O `useAureaTheme()` continua o do app**: dentro do pedaço, `theme` diz o tema do APP, e o
127
+ * `setTheme` troca o do app. Só os tokens mudam — é o que pinta.
128
+ *
129
+ * Interno: o público é o `Card variant="contrast"`. O que sai do fluxo (folha, sobreposto) passa
130
+ * pelo `ForaDaMarca` e volta ao app.
131
+ */
132
+ /**
133
+ * Os tokens do APP, mesmo dentro de um pedaço com tema próprio. Quem desenha uma janela que sai do
134
+ * fluxo (a folha, o diálogo) pinta o FUNDO dela com isto: o `ForaDaMarca` devolve o contexto do app
135
+ * só para quem lê o contexto DENTRO da janela (o texto, o botão), e o fundo é calculado antes, por
136
+ * quem abre. Medido na bancada do Lote N (10/10/2026): sem isto, a lista do `Select` aberta de
137
+ * dentro do `contrast` saía com o fundo do escuro e a letra do claro. Interno.
138
+ */
139
+ export declare function useTokensDoApp(): AureaTokens;
140
+ export declare function TemaDentro({ tema, children }: {
141
+ tema: AureaThemeName;
142
+ children?: React.ReactNode;
143
+ }): React.JSX.Element;
114
144
  /**
115
145
  * A pele que um CAMPO tem de vestir quando está dentro de um `Card variant="brand"`, ou `null`
116
146
  * quando não está.
package/dist/theme.js CHANGED
@@ -1,32 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- // Aurea nativo — o provider de tema × densidade, e os dois hooks que ele alimenta.
3
- //
4
- // ADR-0037: o motor é o `StyleSheet` puro e **esta camada é nossa**. Não há Unistyles para
5
- // registrar tema, então o lugar onde os dois eixos moram é um contexto do React — e a ADR foi
6
- // honesta sobre a única aposta que isso carrega: *"se o contexto do React, sem as otimizações do
7
- // Unistyles, re-renderiza a ponto de doer na troca de tema em tela real"*. Ainda não foi medido
8
- // em aparelho.
9
- //
10
- // UM CONTEXTO, E NÃO DOIS — e vale escrever por que, porque a primeira versão tinha dois.
11
- // A ideia era que quem só troca o tema assinasse um contexto "estável" e não re-renderizasse
12
- // junto com a tela. **Não funciona, e a razão é óbvia depois de vista:** o valor de controle
13
- // contém `theme` e `density`, então ele muda exatamente quando o de tokens muda. Dois contextos
14
- // que mudam juntos não poupam render nenhum — poupam só a impressão de estarem poupando.
15
- //
16
- // O que de fato reduz custo é o `useMemo`: entre renders que NÃO trocam eixo, os dois objetos são
17
- // os mesmos por identidade, e a subárvore não é notificada. É o que está feito.
18
- //
19
- // Se um dia doer em aparelho, a saída é a que a ADR-0037 já prevê — contexto fatiado de verdade,
20
- // com as AÇÕES separadas do ESTADO. Aí a separação compra algo, porque as ações não mudam. Não
21
- // está feito hoje porque seria mais superfície pública para um ganho não medido.
22
- //
23
- // PARIDADE COM A WEB, de propósito: `useAureaTheme()` devolve o mesmo `{theme, density, setTheme,
24
- // setDensity, toggleTheme}` do hook homônimo de `@aurea-uds/react`. Uma diferença real: lá o
25
- // valor pode ser `null` (no servidor o tema é desconhecido e fingir um produz erro de
26
- // hidratação); aqui não há SSR, então nunca é nulo.
27
- //
28
- // E `useAureaTokens()` NÃO tem par na web porque lá ele não faria sentido: o CSS resolve o token
29
- // no uso. Não é hook duplicado — é o hook que só o alvo sem cascata precisa ter.
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
30
2
  import * as React from "react";
31
3
  import { useColorScheme } from "react-native";
32
4
  import { IconRegistryProvider } from "./icon.js";
@@ -73,6 +45,8 @@ export function AureaProvider({ children, theme, density, defaultTheme, defaultD
73
45
  // um contexto a mais só se paga quando ele muda em outra hora, e este muda junto — é a mesma
74
46
  // conta que o cabeçalho deste arquivo já fez para os dois contextos que viraram um.
75
47
  strings: strings ?? defaultStrings,
48
+ fontes: fontFamilies,
49
+ raiz: null,
76
50
  }), [temaEfetivo, densidadeEfetiva, setTheme, setDensity, fontFamilies, strings]);
77
51
  // O registro de ícones fica num contexto PRÓPRIO: ele não muda quando o tema muda, e juntá-lo
78
52
  // ao valor do tema faria toda árvore que só desenha ícone re-renderizar na troca de tema.
@@ -106,6 +80,12 @@ export function useAureaTokens() {
106
80
  * As frases visíveis. Mesmo nome do hook da web (`useAureaStrings`), tabela bem menor — ver
107
81
  * `strings.ts`.
108
82
  */
83
+ /**
84
+ * O gerente de avisos do `ToastHost`, para quem não pode importar o `toast.tsx` — o `Button share`
85
+ * (GAR-14, 0.28.0): o `toast.tsx` importa o `IconButton`, e o botão importar o toast fecharia um
86
+ * ciclo. Interno: o público continua sendo o `useToast`. `null` sem `ToastHost` acima.
87
+ */
88
+ export const AvisosContext = React.createContext(null);
109
89
  export function useAureaStrings() {
110
90
  return usar("useAureaStrings").strings;
111
91
  }
@@ -141,7 +121,46 @@ export function useSobreAMarca() {
141
121
  * passa por aqui.
142
122
  */
143
123
  export function ForaDaMarca({ children }) {
144
- return _jsx(SobreAMarcaCtx.Provider, { value: null, children: children });
124
+ // ✅ **E corta o tema do pedaço também (ADR-0063, 10/10/2026).** A mesma armadilha vale para o
125
+ // `Card variant="contrast"`: ele entrega os tokens do ESCURO aos filhos, e a folha de um `Select`
126
+ // aberto dali sairia escura sobre um app claro. Na web isso não acontece porque a folha vai para
127
+ // o fim da página (portal), fora do pedaço; aqui, quem sai do fluxo volta ao valor do app.
128
+ const v = React.useContext(Contexto);
129
+ const corpo = _jsx(SobreAMarcaCtx.Provider, { value: null, children: children });
130
+ return v?.raiz ? _jsx(Contexto.Provider, { value: v.raiz, children: corpo }) : corpo;
131
+ }
132
+ /**
133
+ * O tema num pedaço da tela — o `Card variant="contrast"` (MT-01, ADR-0063, 10/10/2026). Os filhos
134
+ * leem os tokens do `tema` pedido; o resto do app não muda. É o jeito da referência principal de
135
+ * fazer um pedaço escuro (o tema aplicado num elemento), e o da web (`data-theme` no cartão).
136
+ *
137
+ * ⚠ **O `useAureaTheme()` continua o do app**: dentro do pedaço, `theme` diz o tema do APP, e o
138
+ * `setTheme` troca o do app. Só os tokens mudam — é o que pinta.
139
+ *
140
+ * Interno: o público é o `Card variant="contrast"`. O que sai do fluxo (folha, sobreposto) passa
141
+ * pelo `ForaDaMarca` e volta ao app.
142
+ */
143
+ /**
144
+ * Os tokens do APP, mesmo dentro de um pedaço com tema próprio. Quem desenha uma janela que sai do
145
+ * fluxo (a folha, o diálogo) pinta o FUNDO dela com isto: o `ForaDaMarca` devolve o contexto do app
146
+ * só para quem lê o contexto DENTRO da janela (o texto, o botão), e o fundo é calculado antes, por
147
+ * quem abre. Medido na bancada do Lote N (10/10/2026): sem isto, a lista do `Select` aberta de
148
+ * dentro do `contrast` saía com o fundo do escuro e a letra do claro. Interno.
149
+ */
150
+ export function useTokensDoApp() {
151
+ const v = usar("useTokensDoApp");
152
+ return (v.raiz ?? v).tokens;
153
+ }
154
+ export function TemaDentro({ tema, children }) {
155
+ const v = usar("TemaDentro");
156
+ const valor = React.useMemo(() => ({
157
+ ...v,
158
+ tokens: resolverTokens(tema, v.controle.density, v.fontes),
159
+ raiz: v.raiz ?? v,
160
+ }), [v, tema]);
161
+ if (v.tokens.theme === tema)
162
+ return _jsx(_Fragment, { children: children });
163
+ return _jsx(Contexto.Provider, { value: valor, children: children });
145
164
  }
146
165
  /**
147
166
  * A pele que um CAMPO tem de vestir quando está dentro de um `Card variant="brand"`, ou `null`
package/dist/toast.js CHANGED
@@ -62,7 +62,7 @@ import { Icon } from "./icon.js";
62
62
  import { ICONE_DA_VARIANTE } from "./feedback.js";
63
63
  import { useReduceMotion, driverNativo } from "./movimento.js";
64
64
  import { Text } from "./text.js";
65
- import { useAureaStrings, useAureaTokens } from "./theme.js";
65
+ import { AvisosContext, useAureaStrings, useAureaTokens } from "./theme.js";
66
66
  const folha = criarFolha((t) => ({
67
67
  hospedeiro: { flex: 1 },
68
68
  pilha: {
@@ -93,7 +93,9 @@ const folha = criarFolha((t) => ({
93
93
  const DURACAO_PADRAO = 5000;
94
94
  /** A entrada, nos mesmos `.28s` do `.toast` do CSS. */
95
95
  const ENTRADA = 280;
96
- const Ctx = React.createContext(null);
96
+ // O contexto mora no `theme.tsx` desde a 0.28.0 (GAR-14): o `Button share` também o lê, e ele não
97
+ // pode importar este arquivo sem fechar um ciclo (este importa o `IconButton`).
98
+ const Ctx = AvisosContext;
97
99
  /**
98
100
  * O gerente de avisos. **Precisa de um `ToastHost` acima na árvore.**
99
101
  *
package/dist/tokens.d.ts CHANGED
@@ -15,7 +15,7 @@ export type AureaShadow = {
15
15
  *
16
16
  * O que chega de fora **não** é total — ver `AureaFontInput` e `normalizarEscala`.
17
17
  */
18
- export type AureaFontScale = Record<400 | 500 | 600 | 700, string> & {
18
+ export type AureaFontScale = Record<400 | 500 | 600 | 700 | 800, string> & {
19
19
  /** Só o papel `ui` tem itálico no pacote de fontes; nos outros é `undefined`. */
20
20
  italic?: string;
21
21
  };
@@ -23,14 +23,14 @@ export type AureaFontScale = Record<400 | 500 | 600 | 700, string> & {
23
23
  * O que o consumidor INJETA, e é parcial porque o pacote de fontes é parcial — medido em
24
24
  * 03/09/2026 no `@aurea-uds/fonts/native`:
25
25
  *
26
- * ui 400 400i 500 600 700
26
+ * ui 400 400i 500 600 700 800 ← o 800 entrou na 0.28.0 (GAR-16)
27
27
  * editorial 400 500 600 700 ← apelido do `ui` desde a ADR-0053 (sai na 1.0)
28
28
  * code 400 500 600 ← não há 700
29
29
  *
30
30
  * É a grade dos arquivos de fonte, não um esquecimento nosso. Até a 0.12.4 o `editorial` era a
31
31
  * IBM Plex Serif, sem 400; a ADR-0053 tirou a serifada e o deixou apontando para a Atkinson.
32
32
  */
33
- export type AureaFontInput = Partial<Record<"400" | "500" | "600" | "700" | "400i", string>>;
33
+ export type AureaFontInput = Partial<Record<"400" | "500" | "600" | "700" | "800" | "400i", string>>;
34
34
  export type AureaTokens = {
35
35
  theme: AureaThemeName;
36
36
  density: AureaDensity;
@@ -43,6 +43,7 @@ export type AureaTokens = {
43
43
  ui: AureaFontScale;
44
44
  editorial: AureaFontScale;
45
45
  code: AureaFontScale;
46
+ heading: AureaFontScale;
46
47
  };
47
48
  /**
48
49
  * `boxShadow` do RN 0.76+ — **a primeira camada** de cada sombra. Para a sombra INTEIRA, use
@@ -66,6 +67,9 @@ export type AureaFontFamilies = {
66
67
  ui: AureaFontInput;
67
68
  editorial: AureaFontInput;
68
69
  code: AureaFontInput;
70
+ /** O papel do título (GAR-16, 0.28.0). Opcional: sem ele, o título usa o mapa do `ui` — que é a
71
+ * mesma fonte hoje, e é o que acontece com quem ainda passa o mapa de antes da 0.28.0. */
72
+ heading?: AureaFontInput;
69
73
  };
70
74
  /**
71
75
  * Resolve os tokens para um par (tema, densidade). Puro e sem estado: o provider chama isto
package/dist/tokens.js CHANGED
@@ -23,7 +23,7 @@ const ehSombra = (v) => typeof v === "object" && v !== null && "blurRadius" in v
23
23
  function mesclar(...grupos) {
24
24
  return Object.assign({}, ...grupos);
25
25
  }
26
- const PESOS = [400, 500, 600, 700];
26
+ const PESOS = [400, 500, 600, 700, 800];
27
27
  /**
28
28
  * Fecha a grade parcial que o pacote de fontes emite numa escala TOTAL.
29
29
  *
@@ -95,6 +95,7 @@ export function resolverTokens(theme, density, fontFamilies) {
95
95
  ui: normalizarEscala(fontFamilies?.ui, texto.fontUi ?? "System"),
96
96
  editorial: normalizarEscala(fontFamilies?.editorial, texto.fontEditorial ?? "System"),
97
97
  code: normalizarEscala(fontFamilies?.code, texto.fontCode ?? "System"),
98
+ heading: normalizarEscala(fontFamilies?.heading ?? fontFamilies?.ui, texto.fontHeading ?? texto.fontUi ?? "System"),
98
99
  };
99
100
  return { theme, density, color, size, font, shadow, shadowLayers: { ...shadowLayers }, easing, tracking: { ...tracking }, remInDp: REM_EM_DP };
100
101
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurea-uds/native",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "description": "A Aurea UDS para React Native: o provider de tema e densidade, os componentes e os ícones do Phosphor sobre react-native-svg.",
5
5
  "keywords": [
6
6
  "aurea",
@@ -55,7 +55,7 @@
55
55
  "NOTICE"
56
56
  ],
57
57
  "dependencies": {
58
- "@aurea-uds/tokens": "^0.27.0"
58
+ "@aurea-uds/tokens": "^0.29.0"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@react-native-community/datetimepicker": ">=8",