@aurea-uds/native 0.8.6 → 0.8.7

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.js CHANGED
@@ -25,7 +25,7 @@ import { Pressable, View } from "react-native";
25
25
  import { criarFolha } from "./estilos.js";
26
26
  import { Icon } from "./icon.js";
27
27
  import { Text } from "./text.js";
28
- import { useAureaTokens } from "./theme.js";
28
+ import { useAureaTokens, useSobreAMarca } from "./theme.js";
29
29
  const ALTURA = {
30
30
  xs: "controlHXs", sm: "controlHSm", md: "controlHMd", lg: "controlHLg", xl: "controlHXl",
31
31
  };
@@ -67,6 +67,29 @@ function pintar(t, tone) {
67
67
  };
68
68
  return m[tone];
69
69
  }
70
+ /**
71
+ * O mesmo par, corrigido quando a peça está DENTRO de um `Card variant="brand"`.
72
+ *
73
+ * 🔴 **Sem isto o botão não existe sobre o amarelo — medido nas quatro aparências, 17/09/2026:**
74
+ * cheio neutro **1,61** no claro · cheio da marca **1,00** (amarelo no amarelo) · contornado
75
+ * **1,38** no claro · só-texto **1,83** no escuro. O `Card variant="brand"` EXIGE `action` no
76
+ * tipo, então o cartão obrigava a ter um botão e não dava a ele nenhuma forma de aparecer.
77
+ *
78
+ * A saída não é paleta nova: **uma única cor do sistema passa de 3:1 contra o amarelo nos dois
79
+ * temas**, e é a tinta que o cartão já manda para dentro (4,54). Então aqui:
80
+ *
81
+ * - **cheio** — o fundo vira a TINTA e a letra vira o amarelo do cartão. O botão fica escuro
82
+ * sobre o amarelo, que é o único desenho que se enxerga nos dois temas;
83
+ * - **contornado e sem fundo** — contorno e letra viram a tinta.
84
+ *
85
+ * ⚠ **O tom pedido é IGNORADO aqui**, como já acontece no `Text`: um `tone="danger"` sobre o
86
+ * amarelo mede menos que a norma, e obedecer entregaria um botão ilegível em nome da obediência.
87
+ */
88
+ function pintarSobreAMarca(base, marca) {
89
+ if (marca == null)
90
+ return base;
91
+ return { solido: marca.tinta, texto: marca.fundo, sobre: marca.tinta };
92
+ }
70
93
  const folha = criarFolha((t) => ({
71
94
  // ⚠ O ALVO DE TOQUE, e é a decisão mais importante deste arquivo.
72
95
  //
@@ -108,15 +131,17 @@ const folha = criarFolha((t) => ({
108
131
  export function Button({ children, appearance = "solid", tone = "neutral", size = "md", leadingIcon, trailingIcon, leading, trailing, icons, fullWidth = false, pressed, disabled, accessibilityLabel, ...rest }) {
109
132
  const t = useAureaTokens();
110
133
  const s = folha(t);
111
- const cor = pintar(t, tone);
134
+ const marca = useSobreAMarca();
135
+ const cor = pintarSobreAMarca(pintar(t, tone), marca);
136
+ const corDaBorda = marca?.tinta ?? t.color.border;
112
137
  const caixa = React.useMemo(() => ({
113
138
  height: t.size[ALTURA[size]],
114
139
  paddingHorizontal: PADDING[size],
115
140
  gap: GAP[size],
116
141
  backgroundColor: appearance === "solid" ? cor.solido : "transparent",
117
- borderColor: appearance === "outline" ? t.color.border : "transparent",
142
+ borderColor: appearance === "outline" ? corDaBorda : "transparent",
118
143
  ...(fullWidth ? { flex: 1 } : null),
119
- }), [t, size, appearance, cor.solido, fullWidth]);
144
+ }), [t, size, appearance, cor.solido, corDaBorda, fullWidth]);
120
145
  const corDoTexto = appearance === "solid" ? cor.texto : cor.sobre;
121
146
  return (_jsx(Pressable, { disabled: disabled, accessibilityRole: "button", accessibilityLabel: accessibilityLabel, accessibilityState: { disabled: !!disabled, ...(pressed === undefined ? null : { checked: pressed }) }, style: ({ pressed: tocando }) => [
122
147
  s.alvo, fullWidth && s.alvoLargura,
@@ -137,14 +162,16 @@ export function Button({ children, appearance = "solid", tone = "neutral", size
137
162
  export function IconButton({ name, label, appearance = "ghost", tone = "neutral", size = "md", icons, pressed, disabled, ...rest }) {
138
163
  const t = useAureaTokens();
139
164
  const s = folha(t);
140
- const cor = pintar(t, tone);
165
+ const marca = useSobreAMarca();
166
+ const cor = pintarSobreAMarca(pintar(t, tone), marca);
167
+ const corDaBorda = marca?.tinta ?? t.color.border;
141
168
  const lado = t.size[ALTURA[size]];
142
169
  const caixa = React.useMemo(() => ({
143
170
  height: lado, width: lado, paddingHorizontal: 0,
144
171
  borderRadius: size === "xs" || size === "sm" ? t.size.radiusSm : t.size.radiusMd,
145
172
  backgroundColor: appearance === "solid" ? cor.solido : "transparent",
146
- borderColor: appearance === "outline" ? t.color.border : "transparent",
147
- }), [t, lado, size, appearance, cor.solido]);
173
+ borderColor: appearance === "outline" ? corDaBorda : "transparent",
174
+ }), [t, lado, size, appearance, cor.solido, corDaBorda]);
148
175
  return (_jsx(Pressable, { disabled: disabled, accessibilityRole: "button", accessibilityLabel: label, accessibilityState: { disabled: !!disabled, ...(pressed === undefined ? null : { checked: pressed }) }, style: ({ pressed: tocando }) => [
149
176
  s.alvo, { minWidth: t.size.targetMin, alignItems: "center" },
150
177
  tocando && s.pressionado, disabled && s.inerte,
package/dist/busca.js CHANGED
@@ -75,7 +75,7 @@ import { Icon } from "./icon.js";
75
75
  import { KeyboardAvoiding, useCampo } from "./inputs.js";
76
76
  import { useReduceMotion } from "./movimento.js";
77
77
  import { Text } from "./text.js";
78
- import { useAureaStrings, useAureaTokens } from "./theme.js";
78
+ import { useAureaStrings, useAureaTokens, ForaDaMarca, usePeleSobreAMarca } from "./theme.js";
79
79
  // As mesmas contas do `inputs.tsx`, e elas são repetidas AQUI de propósito: exportá-las de lá
80
80
  // tornaria pública uma conta interna do outro módulo, e um `import` de função privada entre
81
81
  // módulos irmãos é o começo de um acoplamento que ninguém declara. São três linhas.
@@ -205,6 +205,7 @@ const folha = criarFolha((t) => ({
205
205
  export function Combobox({ items, value, onValueChange, onSearchChange, searchDelay = ESPERA_PADRAO, loading, onEndReached, placeholder, searchPlaceholder, empty, clearable = true, draggable = true, disabled, size, chevron = "chevron--down", searchIcon = "search", style, testID, }) {
206
206
  const t = useAureaTokens();
207
207
  const s = folha(t);
208
+ const peleDaMarca = usePeleSobreAMarca();
208
209
  const strings = useAureaStrings();
209
210
  const campo = useCampo();
210
211
  const tam = size ?? campo?.size ?? "md";
@@ -292,31 +293,33 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
292
293
  s.caixa,
293
294
  { height: alturaDoTamanho(t, tam), paddingHorizontal: respiroDoTamanho(t, tam) },
294
295
  campo?.invalido && s.invalido,
296
+ // Sobre a marca a tinta vence inválido — ver `usePeleSobreAMarca`.
297
+ peleDaMarca,
295
298
  inativo && s.desabilitado,
296
299
  style,
297
- ], children: [_jsx(Pressable, { testID: testID, onPress: inativo ? undefined : () => setAberto(true), disabled: inativo, accessibilityRole: "button", accessibilityLabel: campo?.label, accessibilityHint: campo?.hint, accessibilityValue: { text: value?.label }, accessibilityState: { 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: "close", 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(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; }, onStartShouldSetResponder: () => true, 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,
298
- // ⚠ `autoFocus` é o que faz a folha valer a pena: abrir um campo de busca e
299
- // exigir um segundo toque para o teclado subir é um toque a mais em cada
300
- // cadastro. O foco entra com a folha; o teclado vem junto.
301
- autoFocus: true, autoCorrect: false, autoCapitalize: "none",
302
- // Mapeia nos dois sistemas — conferido no fonte do RN, tabela no topo do módulo.
303
- accessibilityRole: "search", accessibilityLabel: searchPlaceholder ?? strings.comboboxSearch,
304
- // `returnKeyType="search"` troca o "enter" do teclado pela lupa. É pista de
305
- // plataforma, não decoração: diz à pessoa que aquele campo é de busca antes de
306
- // ela digitar a primeira letra.
307
- returnKeyType: "search", style: [
308
- s.campoDeTexto,
309
- { fontSize: fonteDoTamanho(t, tam), fontFamily: t.font.ui[400],
310
- color: t.color.foreground },
311
- ] }), texto.length > 0 && (_jsx(IconButton, { name: "close", 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
312
- ? _jsx(Text, { size: "sm", tone: "muted", children: empty ?? strings.comboboxEmpty })
313
- : empty })), _jsx(FlatList, { data: filtrados, keyExtractor: (i) => i.value, keyboardShouldPersistTaps: "handled", onEndReached: onEndReached, onEndReachedThreshold: ANTECEDENCIA_DE_PAGINA, renderItem: ({ item }) => (_jsx(Pressable, { disabled: item.disabled, onPress: () => { onValueChange?.(item); fechar(); }, accessibilityRole: "menuitem", accessibilityState: {
314
- selected: item.value === value?.value, disabled: !!item.disabled,
315
- }, style: [
316
- s.opcao,
317
- item.value === value?.value && s.opcaoEscolhida,
318
- item.disabled && s.desabilitado,
319
- ], children: _jsx(Text, { size: "md", weight: item.value === value?.value ? 600 : 400, children: item.label }) })) })] })] }) })] }));
300
+ ], children: [_jsx(Pressable, { testID: testID, onPress: inativo ? undefined : () => setAberto(true), disabled: inativo, accessibilityRole: "button", accessibilityLabel: campo?.label, accessibilityHint: campo?.hint, accessibilityValue: { text: value?.label }, accessibilityState: { 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: "close", 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; }, onStartShouldSetResponder: () => true, 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,
301
+ // ⚠ `autoFocus` é o que faz a folha valer a pena: abrir um campo de busca e
302
+ // exigir um segundo toque para o teclado subir é um toque a mais em cada
303
+ // cadastro. O foco entra com a folha; o teclado vem junto.
304
+ autoFocus: true, autoCorrect: false, autoCapitalize: "none",
305
+ // Mapeia nos dois sistemas — conferido no fonte do RN, tabela no topo do módulo.
306
+ accessibilityRole: "search", accessibilityLabel: searchPlaceholder ?? strings.comboboxSearch,
307
+ // `returnKeyType="search"` troca o "enter" do teclado pela lupa. É pista de
308
+ // plataforma, não decoração: diz à pessoa que aquele campo é de busca antes de
309
+ // ela digitar a primeira letra.
310
+ returnKeyType: "search", style: [
311
+ s.campoDeTexto,
312
+ { fontSize: fonteDoTamanho(t, tam), fontFamily: t.font.ui[400],
313
+ color: t.color.foreground },
314
+ ] }), texto.length > 0 && (_jsx(IconButton, { name: "close", 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
315
+ ? _jsx(Text, { size: "sm", tone: "muted", children: empty ?? strings.comboboxEmpty })
316
+ : empty })), _jsx(FlatList, { data: filtrados, keyExtractor: (i) => i.value, keyboardShouldPersistTaps: "handled", onEndReached: onEndReached, onEndReachedThreshold: ANTECEDENCIA_DE_PAGINA, renderItem: ({ item }) => (_jsx(Pressable, { disabled: item.disabled, onPress: () => { onValueChange?.(item); fechar(); }, accessibilityRole: "menuitem", accessibilityState: {
317
+ selected: item.value === value?.value, disabled: !!item.disabled,
318
+ }, style: [
319
+ s.opcao,
320
+ item.value === value?.value && s.opcaoEscolhida,
321
+ item.disabled && s.desabilitado,
322
+ ], children: _jsx(Text, { size: "md", weight: item.value === value?.value ? 600 : 400, children: item.label }) })) })] })] }) }) })] }));
320
323
  }
321
324
  // ── A DOBRA DE ACENTO, e por que ela é sondada em vez de presumida ───────────────────────────
322
325
  // Buscar "acucar" tem de achar "açúcar" — num catálogo em português, exigir o acento certo é
@@ -358,6 +361,7 @@ const dobrar = (v) => {
358
361
  export function SearchField({ value, onChangeText, onSearchChange, searchDelay = ESPERA_PADRAO, placeholder, disabled, size, icon = "search", clearable = true, onSubmit, style, testID, }) {
359
362
  const t = useAureaTokens();
360
363
  const s = folha(t);
364
+ const peleDaMarca = usePeleSobreAMarca();
361
365
  const strings = useAureaStrings();
362
366
  const campo = useCampo();
363
367
  const tam = size ?? campo?.size ?? "md";
@@ -400,9 +404,12 @@ export function SearchField({ value, onChangeText, onSearchChange, searchDelay =
400
404
  // O foco é a única pista de campo ativo que sobra no telefone — mesma decisão do `Input`
401
405
  // do Lote 4, e ela vale igual aqui.
402
406
  focado && { borderColor: t.color.focusStrong },
407
+ // Sobre a marca a tinta vence inválido e foco; o foco vira ESPESSURA — `usePeleSobreAMarca`.
408
+ peleDaMarca,
409
+ peleDaMarca && focado && { borderWidth: t.size.borderWidth * 2 },
403
410
  inativo && s.desabilitado,
404
411
  style,
405
- ], children: [icon && _jsx(Icon, { name: icon, size: "sm", color: t.color.subtleForeground }), _jsx(TextInput, { testID: testID ? `${testID}-campo` : undefined, value: value, onChangeText: digitar, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, editable: !inativo, autoCorrect: false, autoCapitalize: "none", returnKeyType: "search", onSubmitEditing: onSubmit, onFocus: () => setFocado(true), onBlur: () => setFocado(false), accessibilityRole: "search", accessibilityLabel: campo?.label ?? placeholder, accessibilityHint: campo?.hint, accessibilityState: { disabled: !!inativo }, style: [
412
+ ], children: [icon && _jsx(Icon, { name: icon, size: "sm", color: peleDaMarca?.color ?? t.color.subtleForeground }), _jsx(TextInput, { testID: testID ? `${testID}-campo` : undefined, value: value, onChangeText: digitar, placeholder: placeholder, placeholderTextColor: peleDaMarca?.color ?? t.color.subtleForeground, editable: !inativo, autoCorrect: false, autoCapitalize: "none", returnKeyType: "search", onSubmitEditing: onSubmit, onFocus: () => setFocado(true), onBlur: () => setFocado(false), accessibilityRole: "search", accessibilityLabel: campo?.label ?? placeholder, accessibilityHint: campo?.hint, accessibilityState: { disabled: !!inativo }, style: [
406
413
  s.campoDeTexto,
407
414
  { fontSize: fonteDoTamanho(t, tam), fontFamily: t.font.ui[400],
408
415
  color: t.color.foreground },
package/dist/inputs.js CHANGED
@@ -45,7 +45,7 @@ import { IconButton } from "./actions.js";
45
45
  import { Icon } from "./icon.js";
46
46
  import { useReduceMotion } from "./movimento.js";
47
47
  import { Text } from "./text.js";
48
- import { useAureaStrings, useAureaTokens } from "./theme.js";
48
+ import { useAureaStrings, useAureaTokens, usePeleSobreAMarca, ForaDaMarca } from "./theme.js";
49
49
  const alturaDoTamanho = (t, s) => s === "sm" ? t.size.controlHSm : s === "lg" ? t.size.controlHLg : t.size.controlHMd;
50
50
  const fonteDoTamanho = (t, s) => s === "sm" ? t.size.textXs : s === "lg" ? t.size.textBase : t.size.textMd;
51
51
  const respiroDoTamanho = (t, s) => s === "sm" ? t.size.space3 : s === "lg" ? t.size.space4 : 13;
@@ -190,6 +190,7 @@ export function Input({ value, defaultValue, onChangeText, placeholder, disabled
190
190
  ...rest }) {
191
191
  const t = useAureaTokens();
192
192
  const s = folha(t);
193
+ const peleDaMarca = usePeleSobreAMarca();
193
194
  const campo = useCampo();
194
195
  const grupo = useGrupoDeCampo();
195
196
  const tam = size ?? grupo?.size ?? campo?.size ?? "md";
@@ -199,7 +200,7 @@ export function Input({ value, defaultValue, onChangeText, placeholder, disabled
199
200
  // O resto vem PRIMEIRO de propósito: o que a Aurea calcula (respiro, cor, nome do `Field`,
200
201
  // `editable`) tem de vencer, e as props que ela redefine estão no `Omit` da interface —
201
202
  // então não chegam por aqui e não há o que sobrescrever por acidente.
202
- , { ...rest, testID: testID, value: value, defaultValue: defaultValue, onChangeText: onChangeText, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, editable: !inativo, keyboardType: keyboardType, secureTextEntry: secureTextEntry, autoCapitalize: autoCapitalize, multiline: multiline, onFocus: () => { setFocado(true); grupo?.aoFocar(true); onFocus?.(); }, onBlur: () => {
203
+ , { ...rest, testID: testID, value: value, defaultValue: defaultValue, onChangeText: onChangeText, placeholder: placeholder, placeholderTextColor: peleDaMarca?.color ?? t.color.subtleForeground, editable: !inativo, keyboardType: keyboardType, secureTextEntry: secureTextEntry, autoCapitalize: autoCapitalize, multiline: multiline, onFocus: () => { setFocado(true); grupo?.aoFocar(true); onFocus?.(); }, onBlur: () => {
203
204
  setFocado(false);
204
205
  grupo?.aoFocar(false);
205
206
  // Só avisa quando o texto MUDOU: emitir o mesmo valor a cada saída de foco faria o app
@@ -244,6 +245,16 @@ export function Input({ value, defaultValue, onChangeText, placeholder, disabled
244
245
  // O foco é o único estado que sobra do trio da web (hover/focus/active), e ele importa
245
246
  // mais aqui: no telefone é a única pista de qual campo o teclado está alimentando.
246
247
  !grupo && focado && { borderColor: t.color.focusStrong },
248
+ // 🔴 DENTRO DO CARTÃO DA MARCA A TINTA VENCE TUDO — inclusive inválido e foco, e isso é
249
+ // medição, não preferência (os números estão no `usePeleSobreAMarca`): sobre o amarelo a
250
+ // borda de inválido mede **1,86** e o `focusStrong` do tema ESCURO mede **1,00**, porque
251
+ // lá ele É o amarelo. Deixá-los vencer apagaria o campo justamente quando ele mais
252
+ // precisa ser achado.
253
+ peleDaMarca && { color: peleDaMarca.color },
254
+ !grupo && peleDaMarca,
255
+ // E o foco continua existindo, por ESPESSURA em vez de cor — a única pista que sobra
256
+ // quando só há uma tinta. O dobro da borda do token, não número novo.
257
+ !grupo && peleDaMarca && focado && { borderWidth: t.size.borderWidth * 2 },
247
258
  !grupo && inativo && s.desabilitado,
248
259
  style,
249
260
  ] }));
@@ -277,6 +288,7 @@ export function Textarea({ rows, size, style, ...rest }) {
277
288
  export function InputGroup({ size, disabled, children, style, testID, ...rest }) {
278
289
  const t = useAureaTokens();
279
290
  const s = folha(t);
291
+ const peleDaMarca = usePeleSobreAMarca();
280
292
  const campo = useCampo();
281
293
  const tam = size ?? campo?.size ?? "md";
282
294
  const inativo = disabled ?? campo?.disabled ?? false;
@@ -291,6 +303,9 @@ export function InputGroup({ size, disabled, children, style, testID, ...rest })
291
303
  // O foco vem do campo lá dentro, por contexto: no React Native não existe `:focus-within`,
292
304
  // e sem isto a única pista de qual campo o teclado alimenta sumiria ao entrar num grupo.
293
305
  focado && { borderColor: t.color.focusStrong },
306
+ // Sobre a marca a tinta vence inválido e foco — ver `usePeleSobreAMarca`.
307
+ peleDaMarca,
308
+ peleDaMarca && focado && { borderWidth: t.size.borderWidth * 2 },
294
309
  inativo && s.desabilitado,
295
310
  style,
296
311
  ], ...rest, children: _jsx(GrupoDeCampo.Provider, { value: ctx, children: children }) }));
@@ -463,6 +478,7 @@ export function SegmentedControl({ items, value, onChange, label, disabled, styl
463
478
  export function Select({ items, value, onChange, placeholder, disabled, size, chevron = "chevron--down", style, testID, }) {
464
479
  const t = useAureaTokens();
465
480
  const s = folha(t);
481
+ const peleDaMarca = usePeleSobreAMarca();
466
482
  const campo = useCampo();
467
483
  const tam = size ?? campo?.size ?? "md";
468
484
  const inativo = disabled ?? campo?.disabled;
@@ -473,9 +489,11 @@ export function Select({ items, value, onChange, placeholder, disabled, size, ch
473
489
  { flexDirection: "row", alignItems: "center", justifyContent: "space-between",
474
490
  height: alturaDoTamanho(t, tam), paddingHorizontal: respiroDoTamanho(t, tam) },
475
491
  campo?.invalido && s.invalido,
492
+ // Sobre a marca a tinta vence inválido — ver `usePeleSobreAMarca`.
493
+ peleDaMarca,
476
494
  inativo && s.desabilitado,
477
495
  style,
478
- ], 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: t.color.subtleForeground })] }), _jsx(Modal, { visible: aberto, transparent: true, animationType: "slide", onRequestClose: () => setAberto(false), children: _jsxs(View, { style: s.fundoDaLista, children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: () => setAberto(false), accessible: false }), _jsx(View, { style: s.lista, onStartShouldSetResponder: () => true, children: _jsx(ScrollView, { children: items.map((it) => (_jsx(Pressable, { disabled: it.disabled, onPress: () => { onChange?.(it.value); setAberto(false); }, accessibilityRole: "menuitem", accessibilityState: { 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))) }) })] }) })] }));
496
+ ], 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", onRequestClose: () => setAberto(false), children: _jsxs(View, { style: s.fundoDaLista, children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: () => setAberto(false), accessible: false }), _jsx(View, { style: s.lista, onStartShouldSetResponder: () => true, children: _jsx(ScrollView, { children: items.map((it) => (_jsx(Pressable, { disabled: it.disabled, onPress: () => { onChange?.(it.value); setAberto(false); }, accessibilityRole: "menuitem", accessibilityState: { 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))) }) })] }) }) })] }));
479
497
  }
480
498
  /**
481
499
  * A pilha de campos, com o respiro do `--space-5`.
package/dist/layout.js CHANGED
@@ -134,7 +134,15 @@ export function Card({ variant = "base", style, ...rest }) {
134
134
  // ⚠ **Consequência declarada: sobre o amarelo existe UMA cor de texto, não duas.** O tom
135
135
  // `muted` mede 1,35 no escuro, então ele não pode sobreviver aqui. A hierarquia dentro deste
136
136
  // cartão sai de PESO e TAMANHO, não de cor — e isso é restrição do contraste, não gosto.
137
+ //
138
+ // 🔴 **E ISTO NASCEU PELA METADE — o consumidor achou em UM DIA, 17/09/2026.** O parágrafo
139
+ // acima fala só de TEXTO, e foi só o texto que passou a ler o contexto. Medido depois, dentro
140
+ // deste mesmo cartão: contorno de campo **2,43/2,24**, fundo de campo **1,61** no claro, glifo
141
+ // de botão sem fundo **1,83** no escuro — e as QUATRO aparências do `action` que este tipo
142
+ // EXIGE, todas invisíveis (1,61 · 1,00 · 1,38 · 1,83).
143
+ // **A mesma frase valia para linha e glifo desde o primeiro dia, e eu parei no texto.**
144
+ // ✅ Hoje o contexto carrega o par (tinta, fundo), e quem lê está listado no `useSobreAMarca`.
137
145
  return variant === "brand"
138
- ? _jsx(SobreAMarca.Provider, { value: t.color.primaryForeground, children: pele })
146
+ ? _jsx(SobreAMarca.Provider, { value: { tinta: t.color.primaryForeground, fundo: t.color.primary }, children: pele })
139
147
  : pele;
140
148
  }
package/dist/numero.js CHANGED
@@ -49,7 +49,7 @@ import { Platform, TextInput, View } from "react-native";
49
49
  import { IconButton } from "./actions.js";
50
50
  import { criarFolha } from "./estilos.js";
51
51
  import { useCampo } from "./inputs.js";
52
- import { useAureaStrings, useAureaTokens } from "./theme.js";
52
+ import { useAureaStrings, useAureaTokens, usePeleSobreAMarca } from "./theme.js";
53
53
  const alturaDoTamanho = (t, s) => s === "sm" ? t.size.controlHSm : s === "lg" ? t.size.controlHLg : t.size.controlHMd;
54
54
  const fonteDoTamanho = (t, s) => s === "sm" ? t.size.textXs : s === "lg" ? t.size.textBase : t.size.textMd;
55
55
  const folha = criarFolha((t) => ({
@@ -235,6 +235,7 @@ const paraEdicao = (n, locale) => {
235
235
  export function NumberField({ value, defaultValue, onValueChange, min, max, step = 1, format, locale, disabled, readOnly, size, fullWidth = false, label, placeholder, keyboardType, icons = { increment: "add", decrement: "subtract" }, style, testID, }) {
236
236
  const t = useAureaTokens();
237
237
  const s = folha(t);
238
+ const peleDaMarca = usePeleSobreAMarca();
238
239
  const strings = useAureaStrings();
239
240
  const campo = useCampo();
240
241
  const tam = size ?? campo?.size ?? "md";
@@ -310,7 +311,7 @@ export function NumberField({ value, defaultValue, onValueChange, min, max, step
310
311
  // (`aurea.css:685`), com os dois botões FORA dela. O estado que faltava é o deles; a geometria
311
312
  // continua sendo a nossa.
312
313
  const [focado, setFocado] = React.useState(false);
313
- return (_jsxs(View, { testID: testID, style: [s.grupo, fullWidth && s.grupoLargo, inativo && s.desabilitado, style], children: [mostrarBotoes && (_jsx(IconButton, { name: icons.decrement, label: strings.decrement, appearance: "ghost", size: tam, disabled: inativo || noLimite(-1), onPress: () => empurrar(-1), testID: testID ? `${testID}-menos` : undefined })), _jsx(TextInput, { testID: testID ? `${testID}-campo` : undefined, value: mostrar, onChangeText: setEmEdicao, onFocus: () => { setFocado(true); setEmEdicao(paraEdicao(numero, locale)); }, onBlur: () => { setFocado(false); confirmar(); }, editable: !inativo && !readOnly, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, keyboardType: keyboardType ?? tecladoPadrao,
314
+ return (_jsxs(View, { testID: testID, style: [s.grupo, fullWidth && s.grupoLargo, inativo && s.desabilitado, style], children: [mostrarBotoes && (_jsx(IconButton, { name: icons.decrement, label: strings.decrement, appearance: "ghost", size: tam, disabled: inativo || noLimite(-1), onPress: () => empurrar(-1), testID: testID ? `${testID}-menos` : undefined })), _jsx(TextInput, { testID: testID ? `${testID}-campo` : undefined, value: mostrar, onChangeText: setEmEdicao, onFocus: () => { setFocado(true); setEmEdicao(paraEdicao(numero, locale)); }, onBlur: () => { setFocado(false); confirmar(); }, editable: !inativo && !readOnly, placeholder: placeholder, placeholderTextColor: peleDaMarca?.color ?? t.color.subtleForeground, keyboardType: keyboardType ?? tecladoPadrao,
314
315
  // O nome vem do `Field`, como em todo controle deste pacote — e `label` cobre quem usa o
315
316
  // campo solto. Sem um dos dois, é um nó anônimo para o leitor de tela.
316
317
  accessibilityLabel: label ?? campo?.label, accessibilityHint: campo?.hint, accessibilityState: { disabled: !!inativo }, "aria-invalid": campo?.invalido, style: [
@@ -323,5 +324,10 @@ export function NumberField({ value, defaultValue, onValueChange, min, max, step
323
324
  // depois. Um campo inválido que está sendo corrigido tem de mostrar que está ativo —
324
325
  // invertendo, a pessoa digita sem pista nenhuma de onde o teclado está batendo.
325
326
  focado && { borderColor: t.color.focusStrong },
327
+ // 🔴 E DENTRO DO CARTÃO DA MARCA A TINTA VENCE OS DOIS — `usePeleSobreAMarca` tem os
328
+ // números: sobre o amarelo o inválido mede 1,86 e o foco do tema escuro mede 1,00.
329
+ // O foco continua existindo, por espessura.
330
+ peleDaMarca,
331
+ peleDaMarca && focado && { borderWidth: t.size.borderWidth * 2 },
326
332
  ] }), mostrarBotoes && (_jsx(IconButton, { name: icons.increment, label: strings.increment, appearance: "ghost", size: tam, disabled: inativo || noLimite(1), onPress: () => empurrar(1), testID: testID ? `${testID}-mais` : undefined }))] }));
327
333
  }
package/dist/overlays.js CHANGED
@@ -61,7 +61,7 @@ import { Button } from "./actions.js";
61
61
  import { criarFolha } from "./estilos.js";
62
62
  import { IconButton } from "./actions.js";
63
63
  import { Text } from "./text.js";
64
- import { useAureaStrings, useAureaTokens } from "./theme.js";
64
+ import { useAureaStrings, useAureaTokens, ForaDaMarca } from "./theme.js";
65
65
  import { useReduceMotion } from "./movimento.js";
66
66
  const folha = criarFolha((t) => ({
67
67
  fundo: { flex: 1, backgroundColor: t.color.overlay },
@@ -157,7 +157,7 @@ export function Dialog({ open, title, children, footer, onClose, scroll = true,
157
157
  const s = folha(t);
158
158
  const strings = useAureaStrings();
159
159
  const Corpo = scroll ? ScrollView : View;
160
- return (_jsx(Modal, { visible: open, transparent: true, animationType: "fade", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: onClose, testID: testID, children: _jsxs(View, { style: [s.fundo, s.centro], children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: onClose, accessible: false, testID: testID ? `${testID}-fundo` : undefined }), _jsxs(View, { ...naoAtravessa, style: [s.superficie, style], children: [_jsxs(View, { style: s.cabecalho, children: [_jsx(Text, { size: "lg", weight: 600, accessibilityRole: "header", style: s.titulo, children: title }), _jsx(IconButton, { name: "close", label: strings.close, appearance: "ghost", size: "sm", onPress: onClose })] }), _jsx(Corpo, { ...(scroll ? { contentContainerStyle: s.corpo } : { style: s.corpo }), children: children }), footer ? _jsx(View, { style: s.rodape, children: footer }) : null] })] }) }));
160
+ return (_jsx(ForaDaMarca, { children: _jsx(Modal, { visible: open, transparent: true, animationType: "fade", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: onClose, testID: testID, children: _jsxs(View, { style: [s.fundo, s.centro], children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: onClose, accessible: false, testID: testID ? `${testID}-fundo` : undefined }), _jsxs(View, { ...naoAtravessa, style: [s.superficie, style], children: [_jsxs(View, { style: s.cabecalho, children: [_jsx(Text, { size: "lg", weight: 600, accessibilityRole: "header", style: s.titulo, children: title }), _jsx(IconButton, { name: "close", label: strings.close, appearance: "ghost", size: "sm", onPress: onClose })] }), _jsx(Corpo, { ...(scroll ? { contentContainerStyle: s.corpo } : { style: s.corpo }), children: children }), footer ? _jsx(View, { style: s.rodape, children: footer }) : null] })] }) }) }));
161
161
  }
162
162
  /**
163
163
  * A decisão que não se desfaz.
@@ -185,9 +185,9 @@ export function ConfirmDialog({ open, title, description, confirmLabel, cancelLa
185
185
  const t = useAureaTokens();
186
186
  const s = folha(t);
187
187
  const strings = useAureaStrings();
188
- return (_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"
189
- ? _jsx(Text, { size: "md", tone: "muted", children: description })
190
- : description }), _jsxs(View, { style: s.rodape, children: [_jsx(Button, { appearance: "outline", onPress: onCancel, testID: testID ? `${testID}-cancelar` : undefined, children: cancelLabel ?? strings.confirmCancel }), _jsx(Button, { appearance: "solid", tone: destructive ? "danger" : "brand", onPress: onConfirm, testID: testID ? `${testID}-confirmar` : undefined, children: confirmLabel ?? strings.confirmProceed })] })] }) }) }));
188
+ 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"
189
+ ? _jsx(Text, { size: "md", tone: "muted", children: description })
190
+ : description }), _jsxs(View, { style: s.rodape, children: [_jsx(Button, { appearance: "outline", onPress: onCancel, testID: testID ? `${testID}-cancelar` : undefined, children: cancelLabel ?? strings.confirmCancel }), _jsx(Button, { appearance: "solid", tone: destructive ? "danger" : "brand", onPress: onConfirm, testID: testID ? `${testID}-confirmar` : undefined, children: confirmLabel ?? strings.confirmProceed })] })] }) }) }) }));
191
191
  }
192
192
  /**
193
193
  * O painel que entra pela borda.
@@ -229,10 +229,10 @@ export function Drawer({ open, title, children, onClose, side = "right", scroll
229
229
  inputRange: [0, 1],
230
230
  outputRange: [side === "right" ? 480 : -480, 0],
231
231
  });
232
- return (_jsx(Modal, { visible: open, transparent: true, animationType: "none", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: onClose, testID: testID, children: _jsxs(View, { style: s.fundo, children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: onClose, accessible: false, testID: testID ? `${testID}-fundo` : undefined }), _jsxs(Animated.View, { ...naoAtravessa, style: [
233
- s.gaveta, side === "right" ? s.gavetaDireita : s.gavetaEsquerda,
234
- { transform: [{ translateX: desloca }] }, style,
235
- ], children: [_jsxs(View, { style: s.cabecalho, children: [_jsx(Text, { size: "lg", weight: 600, accessibilityRole: "header", style: s.titulo, children: title }), _jsx(IconButton, { name: "close", label: strings.close, appearance: "ghost", size: "sm", onPress: onClose })] }), _jsx(Corpo, { ...(scroll ? { contentContainerStyle: s.corpo } : { style: s.corpo }), children: children })] })] }) }));
232
+ return (_jsx(ForaDaMarca, { children: _jsx(Modal, { visible: open, transparent: true, animationType: "none", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: onClose, testID: testID, children: _jsxs(View, { style: s.fundo, children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: onClose, accessible: false, testID: testID ? `${testID}-fundo` : undefined }), _jsxs(Animated.View, { ...naoAtravessa, style: [
233
+ s.gaveta, side === "right" ? s.gavetaDireita : s.gavetaEsquerda,
234
+ { transform: [{ translateX: desloca }] }, style,
235
+ ], children: [_jsxs(View, { style: s.cabecalho, children: [_jsx(Text, { size: "lg", weight: 600, accessibilityRole: "header", style: s.titulo, children: title }), _jsx(IconButton, { name: "close", label: strings.close, appearance: "ghost", size: "sm", onPress: onClose })] }), _jsx(Corpo, { ...(scroll ? { contentContainerStyle: s.corpo } : { style: s.corpo }), children: children })] })] }) }) }));
236
236
  }
237
237
  /**
238
238
  * A folha que sobe de baixo. **NÃO existe na web** — é o único componente deste lote sem ficha.
@@ -287,5 +287,5 @@ export function BottomSheet({ open, title, children, onClose, grabber = true, dr
287
287
  }).start();
288
288
  },
289
289
  }), [draggable, onClose, arrasto, reduzir, t.easing.easeEmphasized]);
290
- return (_jsx(Modal, { visible: open, transparent: true, animationType: reduzir !== false ? "none" : "slide", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: onClose, testID: testID, children: _jsxs(View, { style: s.fundo, children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: onClose, accessible: false, testID: testID ? `${testID}-fundo` : undefined }), _jsxs(Animated.View, { ...naoAtravessa, ...gestos.panHandlers, onLayout: (e) => { altura.current = e.nativeEvent.layout.height; }, style: [s.folhaBaixo, { transform: [{ translateY: arrasto }] }, style], children: [grabber ? (_jsx(View, { style: s.puxadorArea, accessible: false, importantForAccessibility: "no-hide-descendants", children: _jsx(View, { style: s.puxador }) })) : null, title ? (_jsx(View, { style: s.cabecalho, children: _jsx(Text, { size: "lg", weight: 600, accessibilityRole: "header", style: s.titulo, children: title }) })) : null, _jsx(Corpo, { ...(scroll ? { contentContainerStyle: s.corpo } : { style: s.corpo }), children: children })] })] }) }));
290
+ return (_jsx(ForaDaMarca, { children: _jsx(Modal, { visible: open, transparent: true, animationType: reduzir !== false ? "none" : "slide", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: onClose, testID: testID, children: _jsxs(View, { style: s.fundo, children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: onClose, accessible: false, testID: testID ? `${testID}-fundo` : undefined }), _jsxs(Animated.View, { ...naoAtravessa, ...gestos.panHandlers, onLayout: (e) => { altura.current = e.nativeEvent.layout.height; }, style: [s.folhaBaixo, { transform: [{ translateY: arrasto }] }, style], children: [grabber ? (_jsx(View, { style: s.puxadorArea, accessible: false, importantForAccessibility: "no-hide-descendants", children: _jsx(View, { style: s.puxador }) })) : null, title ? (_jsx(View, { style: s.cabecalho, children: _jsx(Text, { size: "lg", weight: 600, accessibilityRole: "header", style: s.titulo, children: title }) })) : null, _jsx(Corpo, { ...(scroll ? { contentContainerStyle: s.corpo } : { style: s.corpo }), children: children })] })] }) }) }));
291
291
  }
package/dist/text.js CHANGED
@@ -88,5 +88,5 @@ export function Text({ size = "md", weight = 400, font = "ui", tone = "default",
88
88
  // ⚠ **A consequência está declarada e não é neutra:** dentro de um cartão da marca não existe
89
89
  // texto esmaecido. A hierarquia sai de PESO e TAMANHO.
90
90
  // ⚠ E `style` continua por último de propósito: quem passa cor à mão assume a conta.
91
- return _jsx(TextRN, { style: [s[tone], sobreAMarca && { color: sobreAMarca }, proprio, style], ...rest });
91
+ return _jsx(TextRN, { style: [s[tone], sobreAMarca && { color: sobreAMarca.tinta }, proprio, style], ...rest });
92
92
  }
package/dist/theme.d.ts CHANGED
@@ -73,13 +73,66 @@ export declare function useAureaTokens(): AureaTokens;
73
73
  * `strings.ts`.
74
74
  */
75
75
  export declare function useAureaStrings(): AureaStrings;
76
+ export interface SobreAMarcaValor {
77
+ /** A única cor que se pode desenhar sobre a marca: letra, contorno e glifo. 4,54 nos dois temas. */
78
+ tinta: string;
79
+ /** A cor do cartão por baixo. Serve de letra para quem se pinta com a tinta INTEIRA. */
80
+ fundo: string;
81
+ }
76
82
  /** O provedor. Quem usa é o `Card variant="brand"`. */
77
- export declare const SobreAMarca: React.Context<string | null>;
83
+ export declare const SobreAMarca: React.Context<SobreAMarcaValor | null>;
78
84
  /**
79
- * A cor que o texto tem de usar quando está sobre a marca, ou `null` quando não está.
85
+ * O par de cores obrigatório quando se está sobre a marca, ou `null` quando não se está.
80
86
  *
81
- * ⚠ **Ela vence TODOS os tons, inclusive os explícitos.** Um `tone="danger"` sobre o amarelo
87
+ * ⚠ **Ele vence TODOS os tons, inclusive os explícitos.** Um `tone="danger"` sobre o amarelo
82
88
  * mediria menos que os 4,5 da norma, então respeitá-lo entregaria texto ilegível em nome da
83
- * obediência. **Sobre o amarelo existe UMA cor de texto** — a hierarquia sai de peso e tamanho.
89
+ * obediência. **Sobre o amarelo existe UMA tinta** — a hierarquia sai de peso e tamanho.
90
+ *
91
+ * ⚠ **E o alcance dele é DECLARADO, não completo.** Leem o contexto: `Text`, a família de campo,
92
+ * o `Button` e o `IconButton`. **Componente fora dessa lista continua quebrado dentro do cartão
93
+ * amarelo**, e gate estático nenhum vê isso — o que o consumidor encaixa não está no nosso
94
+ * código, exatamente o ponto cego que o `check 43` já declara.
95
+ */
96
+ export declare function useSobreAMarca(): SobreAMarcaValor | null;
97
+ /**
98
+ * Corta a marca para dentro.
99
+ *
100
+ * 🔴 **Existe por uma armadilha do React Native que é invisível na tela e óbvia no código:** o
101
+ * `Modal` desenha numa camada POR CIMA de tudo, mas em React ele continua sendo FILHO de quem o
102
+ * escreveu. Um `Select` dentro de um `Card variant="brand"` abre a folha dele **fora** do cartão
103
+ * amarelo — e a folha herdaria a tinta do amarelo mesmo assim, pintando texto marrom sobre fundo
104
+ * normal.
105
+ *
106
+ * ⚠ **Camada visual e árvore de contexto são coisas diferentes**, e é só o segundo que manda na
107
+ * cor. Toda superfície que sai do fluxo — as duas folhas de escolha e os quatro sobrepostos —
108
+ * passa por aqui.
109
+ */
110
+ export declare function ForaDaMarca({ children }: {
111
+ children?: React.ReactNode;
112
+ }): React.JSX.Element;
113
+ /**
114
+ * A pele que um CAMPO tem de vestir quando está dentro de um `Card variant="brand"`, ou `null`
115
+ * quando não está.
116
+ *
117
+ * O campo perde o fundo e passa a ser um contorno da tinta: **4,54 contra o amarelo nos dois
118
+ * temas**, contra os 2,43/2,24 do `borderStrong` e os 1,61 do `fieldBg` no tema claro. A norma
119
+ * 1.4.11 pede 3:1 para o contorno que identifica um campo, e só a tinta chega lá.
120
+ *
121
+ * ⚠ **O fundo some porque NÃO HÁ fundo possível** — medidas as três superfícies neutras do
122
+ * sistema contra o amarelo no tema claro: `card` 1,91 · `background` 1,73 · `secondary` 1,61.
123
+ * Nenhuma separa. Um contorno forte separa.
124
+ *
125
+ * 🔴 **E a tinta vence o estado de INVÁLIDO, que é o contrário do que a intuição manda.** Medido:
126
+ * a borda de inválido (`danger-400`, `#9f2330`) mede **1,86** contra o amarelo — ela é MENOS
127
+ * visível que a tinta. Pintar de vermelho ali deixaria o campo errado mais difícil de achar que o
128
+ * campo certo. Dentro deste cartão o erro é dito pela MENSAGEM do `Field` e pelo estado que o
129
+ * leitor de tela anuncia, não pela cor — que é o que a norma 1.4.1 já exige de qualquer forma.
130
+ *
131
+ * ⚠ **O texto digitado e o marcador de dica ficam da MESMA cor**, porque só existe uma tinta. É
132
+ * perda real de hierarquia, declarada e não escondida: sobre o amarelo não há segunda cor.
84
133
  */
85
- export declare function useSobreAMarca(): string | null;
134
+ export declare function usePeleSobreAMarca(): {
135
+ borderColor: string;
136
+ backgroundColor: string;
137
+ color: string;
138
+ } | null;
package/dist/theme.js CHANGED
@@ -109,33 +109,64 @@ export function useAureaTokens() {
109
109
  export function useAureaStrings() {
110
110
  return usar("useAureaStrings").strings;
111
111
  }
112
- // ─────────────────────────────────────────────────────────────────────────────────────────────
113
- // Sobre a marca — a cor de texto FORÇADA dentro de uma superfície amarela
114
- // ─────────────────────────────────────────────────────────────────────────────────────────────
115
- //
116
- // 🔴 **Isto existe porque o contraste não deixou escolha**, e os números estão no `layout.tsx`:
117
- // sobre o `primary`, o texto comum mede **1,83 no tema escuro** e o esmaecido **1,35**. Os dois
118
- // são ilegíveis. O único par que serve nos DOIS temas é `primary-foreground`, com 4,54.
119
- //
120
- // ⚠ **E no React Native não há cascata de cor** — `color` num `View` não desce para o `Text` de
121
- // dentro, como desceria no CSS. Então a cor tem de viajar por CONTEXTO, que é exatamente a
122
- // decisão que o `Field` do Lote 4 tomou para empurrar nome, dica e estado de inválido.
123
- //
124
- // ⚠ **Ele mora AQUI e não no `layout.tsx` por uma razão de estrutura:** o `layout.tsx` importa o
125
- // `Text`, então se ele também exportasse o contexto o `text.tsx` teria de importar de volta — um
126
- // ciclo. Este módulo é o que os dois já importam.
127
- //
128
- // ⚠ **Não sai pela porta da frente do pacote.** É acordo interno entre o `Card` e o `Text`.
129
112
  const SobreAMarcaCtx = React.createContext(null);
130
113
  /** O provedor. Quem usa é o `Card variant="brand"`. */
131
114
  export const SobreAMarca = SobreAMarcaCtx;
132
115
  /**
133
- * A cor que o texto tem de usar quando está sobre a marca, ou `null` quando não está.
116
+ * O par de cores obrigatório quando se está sobre a marca, ou `null` quando não se está.
134
117
  *
135
- * ⚠ **Ela vence TODOS os tons, inclusive os explícitos.** Um `tone="danger"` sobre o amarelo
118
+ * ⚠ **Ele vence TODOS os tons, inclusive os explícitos.** Um `tone="danger"` sobre o amarelo
136
119
  * mediria menos que os 4,5 da norma, então respeitá-lo entregaria texto ilegível em nome da
137
- * obediência. **Sobre o amarelo existe UMA cor de texto** — a hierarquia sai de peso e tamanho.
120
+ * obediência. **Sobre o amarelo existe UMA tinta** — a hierarquia sai de peso e tamanho.
121
+ *
122
+ * ⚠ **E o alcance dele é DECLARADO, não completo.** Leem o contexto: `Text`, a família de campo,
123
+ * o `Button` e o `IconButton`. **Componente fora dessa lista continua quebrado dentro do cartão
124
+ * amarelo**, e gate estático nenhum vê isso — o que o consumidor encaixa não está no nosso
125
+ * código, exatamente o ponto cego que o `check 43` já declara.
138
126
  */
139
127
  export function useSobreAMarca() {
140
128
  return React.useContext(SobreAMarcaCtx);
141
129
  }
130
+ /**
131
+ * Corta a marca para dentro.
132
+ *
133
+ * 🔴 **Existe por uma armadilha do React Native que é invisível na tela e óbvia no código:** o
134
+ * `Modal` desenha numa camada POR CIMA de tudo, mas em React ele continua sendo FILHO de quem o
135
+ * escreveu. Um `Select` dentro de um `Card variant="brand"` abre a folha dele **fora** do cartão
136
+ * amarelo — e a folha herdaria a tinta do amarelo mesmo assim, pintando texto marrom sobre fundo
137
+ * normal.
138
+ *
139
+ * ⚠ **Camada visual e árvore de contexto são coisas diferentes**, e é só o segundo que manda na
140
+ * cor. Toda superfície que sai do fluxo — as duas folhas de escolha e os quatro sobrepostos —
141
+ * passa por aqui.
142
+ */
143
+ export function ForaDaMarca({ children }) {
144
+ return _jsx(SobreAMarcaCtx.Provider, { value: null, children: children });
145
+ }
146
+ /**
147
+ * A pele que um CAMPO tem de vestir quando está dentro de um `Card variant="brand"`, ou `null`
148
+ * quando não está.
149
+ *
150
+ * O campo perde o fundo e passa a ser um contorno da tinta: **4,54 contra o amarelo nos dois
151
+ * temas**, contra os 2,43/2,24 do `borderStrong` e os 1,61 do `fieldBg` no tema claro. A norma
152
+ * 1.4.11 pede 3:1 para o contorno que identifica um campo, e só a tinta chega lá.
153
+ *
154
+ * ⚠ **O fundo some porque NÃO HÁ fundo possível** — medidas as três superfícies neutras do
155
+ * sistema contra o amarelo no tema claro: `card` 1,91 · `background` 1,73 · `secondary` 1,61.
156
+ * Nenhuma separa. Um contorno forte separa.
157
+ *
158
+ * 🔴 **E a tinta vence o estado de INVÁLIDO, que é o contrário do que a intuição manda.** Medido:
159
+ * a borda de inválido (`danger-400`, `#9f2330`) mede **1,86** contra o amarelo — ela é MENOS
160
+ * visível que a tinta. Pintar de vermelho ali deixaria o campo errado mais difícil de achar que o
161
+ * campo certo. Dentro deste cartão o erro é dito pela MENSAGEM do `Field` e pelo estado que o
162
+ * leitor de tela anuncia, não pela cor — que é o que a norma 1.4.1 já exige de qualquer forma.
163
+ *
164
+ * ⚠ **O texto digitado e o marcador de dica ficam da MESMA cor**, porque só existe uma tinta. É
165
+ * perda real de hierarquia, declarada e não escondida: sobre o amarelo não há segunda cor.
166
+ */
167
+ export function usePeleSobreAMarca() {
168
+ const marca = useSobreAMarca();
169
+ return marca == null
170
+ ? null
171
+ : { borderColor: marca.tinta, backgroundColor: "transparent", color: marca.tinta };
172
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurea-uds/native",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
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.6"
50
+ "@aurea-uds/tokens": "^0.8.7"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@react-native-community/datetimepicker": ">=8",