@aurea-uds/native 0.8.5 → 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 +34 -7
- package/dist/busca.js +32 -25
- package/dist/inputs.js +46 -27
- package/dist/layout.d.ts +27 -3
- package/dist/layout.js +35 -4
- package/dist/navigation.js +3 -5
- package/dist/numero.js +8 -2
- package/dist/overlays.js +10 -10
- package/dist/rolagem.d.ts +16 -0
- package/dist/rolagem.js +45 -0
- package/dist/text.js +11 -2
- package/dist/theme.d.ts +63 -0
- package/dist/theme.js +61 -0
- package/package.json +2 -2
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
|
|
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" ?
|
|
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
|
|
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" ?
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
@@ -40,11 +40,12 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
40
40
|
import * as React from "react";
|
|
41
41
|
import { Animated, KeyboardAvoidingView as KeyboardAvoidingViewRN, Modal, Platform, Pressable, ScrollView, TextInput, View, } from "react-native";
|
|
42
42
|
import { comOpacidade, criarFolha } from "./estilos.js";
|
|
43
|
+
import { FilaRolante } from "./rolagem.js";
|
|
43
44
|
import { IconButton } from "./actions.js";
|
|
44
45
|
import { Icon } from "./icon.js";
|
|
45
46
|
import { useReduceMotion } from "./movimento.js";
|
|
46
47
|
import { Text } from "./text.js";
|
|
47
|
-
import { useAureaStrings, useAureaTokens } from "./theme.js";
|
|
48
|
+
import { useAureaStrings, useAureaTokens, usePeleSobreAMarca, ForaDaMarca } from "./theme.js";
|
|
48
49
|
const alturaDoTamanho = (t, s) => s === "sm" ? t.size.controlHSm : s === "lg" ? t.size.controlHLg : t.size.controlHMd;
|
|
49
50
|
const fonteDoTamanho = (t, s) => s === "sm" ? t.size.textXs : s === "lg" ? t.size.textBase : t.size.textMd;
|
|
50
51
|
const respiroDoTamanho = (t, s) => s === "sm" ? t.size.space3 : s === "lg" ? t.size.space4 : 13;
|
|
@@ -189,6 +190,7 @@ export function Input({ value, defaultValue, onChangeText, placeholder, disabled
|
|
|
189
190
|
...rest }) {
|
|
190
191
|
const t = useAureaTokens();
|
|
191
192
|
const s = folha(t);
|
|
193
|
+
const peleDaMarca = usePeleSobreAMarca();
|
|
192
194
|
const campo = useCampo();
|
|
193
195
|
const grupo = useGrupoDeCampo();
|
|
194
196
|
const tam = size ?? grupo?.size ?? campo?.size ?? "md";
|
|
@@ -198,7 +200,7 @@ export function Input({ value, defaultValue, onChangeText, placeholder, disabled
|
|
|
198
200
|
// O resto vem PRIMEIRO de propósito: o que a Aurea calcula (respiro, cor, nome do `Field`,
|
|
199
201
|
// `editable`) tem de vencer, e as props que ela redefine estão no `Omit` da interface —
|
|
200
202
|
// então não chegam por aqui e não há o que sobrescrever por acidente.
|
|
201
|
-
, { ...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: () => {
|
|
202
204
|
setFocado(false);
|
|
203
205
|
grupo?.aoFocar(false);
|
|
204
206
|
// Só avisa quando o texto MUDOU: emitir o mesmo valor a cada saída de foco faria o app
|
|
@@ -243,6 +245,16 @@ export function Input({ value, defaultValue, onChangeText, placeholder, disabled
|
|
|
243
245
|
// O foco é o único estado que sobra do trio da web (hover/focus/active), e ele importa
|
|
244
246
|
// mais aqui: no telefone é a única pista de qual campo o teclado está alimentando.
|
|
245
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 },
|
|
246
258
|
!grupo && inativo && s.desabilitado,
|
|
247
259
|
style,
|
|
248
260
|
] }));
|
|
@@ -276,6 +288,7 @@ export function Textarea({ rows, size, style, ...rest }) {
|
|
|
276
288
|
export function InputGroup({ size, disabled, children, style, testID, ...rest }) {
|
|
277
289
|
const t = useAureaTokens();
|
|
278
290
|
const s = folha(t);
|
|
291
|
+
const peleDaMarca = usePeleSobreAMarca();
|
|
279
292
|
const campo = useCampo();
|
|
280
293
|
const tam = size ?? campo?.size ?? "md";
|
|
281
294
|
const inativo = disabled ?? campo?.disabled ?? false;
|
|
@@ -290,6 +303,9 @@ export function InputGroup({ size, disabled, children, style, testID, ...rest })
|
|
|
290
303
|
// O foco vem do campo lá dentro, por contexto: no React Native não existe `:focus-within`,
|
|
291
304
|
// e sem isto a única pista de qual campo o teclado alimenta sumiria ao entrar num grupo.
|
|
292
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 },
|
|
293
309
|
inativo && s.desabilitado,
|
|
294
310
|
style,
|
|
295
311
|
], ...rest, children: _jsx(GrupoDeCampo.Provider, { value: ctx, children: children }) }));
|
|
@@ -415,30 +431,30 @@ export function SegmentedControl({ items, value, onChange, label, disabled, styl
|
|
|
415
431
|
const s = folha(t);
|
|
416
432
|
const campo = useCampo();
|
|
417
433
|
const inativo = disabled ?? campo?.disabled;
|
|
418
|
-
return (_jsx(View, { accessibilityRole: "radiogroup", accessibilityLabel: label ?? campo?.label, style: [s.segmentada, inativo && s.desabilitado, style], ...rest, children: items.map((it) => {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
434
|
+
return (_jsx(FilaRolante, { children: _jsx(View, { accessibilityRole: "radiogroup", accessibilityLabel: label ?? campo?.label, style: [s.segmentada, inativo && s.desabilitado, style], ...rest, children: items.map((it) => {
|
|
435
|
+
const ativo = it.value === value;
|
|
436
|
+
return (_jsxs(Pressable, { onPress: inativo ? undefined : () => onChange?.(it.value), disabled: inativo, accessibilityRole: "radio", accessibilityState: { checked: ativo, disabled: !!inativo }, accessibilityLabel: typeof it.label === "string" ? it.label : undefined, style: [s.segmento, ativo && s.segmentoAtivo], children: [typeof it.label === "string"
|
|
437
|
+
? _jsx(Text, { size: "sm", weight: ativo ? 600 : 400,
|
|
438
|
+
// 🔴 ERA `foreground`, E ISSO QUEBRAVA A LINGUAGEM DE SELECIONADO.
|
|
439
|
+
// Achado pelo consumidor em 17/09/2026. A web pinta o escolhido com a
|
|
440
|
+
// MARCA (`aurea.css:937-938`), e o comentário de cima daquela regra diz
|
|
441
|
+
// que nav ativo, aba ativa, item de lista e botão ligado mostram o mesmo
|
|
442
|
+
// sinal — ordem do Victor, 24/07. O `SegmentedControl` daqui discordava
|
|
443
|
+
// da web **e do `BottomNav` deste mesmo pacote**, que já usa este token.
|
|
444
|
+
//
|
|
445
|
+
// ⚠ **`primaryEmphasis` e NÃO `primary`, e a razão está no CSS:** *"sem
|
|
446
|
+
// fundo, a marca vira TEXTO, e o amarelo puro"* não se lê no claro
|
|
447
|
+
// (`aurea.css:358`). É a mesma lição que a `0.8.3` pagou no `Button`.
|
|
448
|
+
// No escuro os dois valem o mesmo amarelo; no claro este aprofunda.
|
|
449
|
+
//
|
|
450
|
+
// ⚠ **Uma divergência declarada, não escondida:** para o `.segmented` a
|
|
451
|
+
// web usa uma mistura própria (`primary 45% + foreground`) em vez do
|
|
452
|
+
// token, enquanto para o `.bottom-nav` usa o token. São dois âmbares
|
|
453
|
+
// parecidos para a MESMA linguagem, e isso é incoerência DA WEB. Escolhi
|
|
454
|
+
// o token, que deixa os dois componentes daqui iguais entre si.
|
|
455
|
+
style: { color: ativo ? t.color.primaryEmphasis : t.color.mutedForeground }, children: it.label })
|
|
456
|
+
: it.label, ativo && _jsx(View, { style: s.fioDoSegmento })] }, it.value));
|
|
457
|
+
}) }) }));
|
|
442
458
|
}
|
|
443
459
|
/**
|
|
444
460
|
* A escolha de um de muitos.
|
|
@@ -462,6 +478,7 @@ export function SegmentedControl({ items, value, onChange, label, disabled, styl
|
|
|
462
478
|
export function Select({ items, value, onChange, placeholder, disabled, size, chevron = "chevron--down", style, testID, }) {
|
|
463
479
|
const t = useAureaTokens();
|
|
464
480
|
const s = folha(t);
|
|
481
|
+
const peleDaMarca = usePeleSobreAMarca();
|
|
465
482
|
const campo = useCampo();
|
|
466
483
|
const tam = size ?? campo?.size ?? "md";
|
|
467
484
|
const inativo = disabled ?? campo?.disabled;
|
|
@@ -472,9 +489,11 @@ export function Select({ items, value, onChange, placeholder, disabled, size, ch
|
|
|
472
489
|
{ flexDirection: "row", alignItems: "center", justifyContent: "space-between",
|
|
473
490
|
height: alturaDoTamanho(t, tam), paddingHorizontal: respiroDoTamanho(t, tam) },
|
|
474
491
|
campo?.invalido && s.invalido,
|
|
492
|
+
// Sobre a marca a tinta vence inválido — ver `usePeleSobreAMarca`.
|
|
493
|
+
peleDaMarca,
|
|
475
494
|
inativo && s.desabilitado,
|
|
476
495
|
style,
|
|
477
|
-
], 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))) }) })] }) }) })] }));
|
|
478
497
|
}
|
|
479
498
|
/**
|
|
480
499
|
* A pilha de campos, com o respiro do `--space-5`.
|
package/dist/layout.d.ts
CHANGED
|
@@ -55,11 +55,34 @@ export interface SeparatorProps extends ViewProps {
|
|
|
55
55
|
* comportamento certo.
|
|
56
56
|
*/
|
|
57
57
|
export declare function Separator({ orientation, label, style, ...rest }: SeparatorProps): React.JSX.Element;
|
|
58
|
-
export type AureaCardVariant = "base" | "raised" | "interactive" | "inset" | "selected" | "danger";
|
|
59
|
-
|
|
60
|
-
variant?: AureaCardVariant;
|
|
58
|
+
export type AureaCardVariant = "base" | "raised" | "interactive" | "inset" | "selected" | "danger" | "brand";
|
|
59
|
+
interface CardBase extends ViewProps {
|
|
61
60
|
children?: React.ReactNode;
|
|
62
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* 🔴 **O `brand` EXIGE `action`, e isso é de propósito — a regra é do TIPO, não de documento.**
|
|
64
|
+
*
|
|
65
|
+
* O cartão na cor da marca entrou em 17/09/2026 e é a **primeira superfície grande preenchida
|
|
66
|
+
* com o amarelo** neste sistema. Até aqui ele só pintava coisa pequena: o ponto do contador, a
|
|
67
|
+
* pílula do passo atual, o link de pular para o conteúdo. Um cartão amarelo é precedente, e
|
|
68
|
+
* precedente sem trava vira decoração — outras telas pedem o mesmo e o amarelo deixa de
|
|
69
|
+
* significar "aja aqui".
|
|
70
|
+
*
|
|
71
|
+
* A regra combinada foi *"o cartão amarelo é só para pedir uma ação da pessoa"*. **Escrita num
|
|
72
|
+
* documento, ela apagaria** — este repositório tem registro disso em cada arquivo, e a §"Afirmação
|
|
73
|
+
* velha" do `CLAUDE.md` existe por causa desse desgaste. Então ela mora no tipo: sem `action`, o
|
|
74
|
+
* `brand` não compila. É a mesma escolha que faz o `label` do `IconButton` ser obrigatório.
|
|
75
|
+
*/
|
|
76
|
+
interface CardDaMarca extends CardBase {
|
|
77
|
+
variant: "brand";
|
|
78
|
+
/** O que a pessoa faz aqui — um `Button`, um `IconButton`, um campo. Sem isto não compila. */
|
|
79
|
+
action: React.ReactNode;
|
|
80
|
+
}
|
|
81
|
+
interface CardComum extends CardBase {
|
|
82
|
+
variant?: Exclude<AureaCardVariant, "brand">;
|
|
83
|
+
action?: never;
|
|
84
|
+
}
|
|
85
|
+
export type CardProps = CardDaMarca | CardComum;
|
|
63
86
|
/**
|
|
64
87
|
* Superfície flutuante, raio 22 (`--radius-card`) — identidade declarada INTOCÁVEL.
|
|
65
88
|
*
|
|
@@ -69,3 +92,4 @@ export interface CardProps extends ViewProps {
|
|
|
69
92
|
* que É um alvo — quem o torna tocável é a tela.
|
|
70
93
|
*/
|
|
71
94
|
export declare function Card({ variant, style, ...rest }: CardProps): React.JSX.Element;
|
|
95
|
+
export {};
|
package/dist/layout.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// Aurea nativo — os primitivos de layout: `Stack`, `Cluster`, `Grid`, `Card`.
|
|
3
3
|
//
|
|
4
4
|
// Medidos no core da web antes de escrever (passo 1 do BUILDING.md), e o que eles são lá:
|
|
@@ -19,7 +19,7 @@ import { View } from "react-native";
|
|
|
19
19
|
// `...rest` sem nada a mais.
|
|
20
20
|
import { criarFolha } from "./estilos.js";
|
|
21
21
|
import { Text } from "./text.js";
|
|
22
|
-
import { useAureaTokens } from "./theme.js";
|
|
22
|
+
import { useAureaTokens, SobreAMarca } 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
|
|
@@ -52,6 +52,11 @@ const folha = criarFolha((t) => ({
|
|
|
52
52
|
card_inset: { backgroundColor: t.color.surfaceInset, borderColor: "transparent" },
|
|
53
53
|
card_selected: { borderColor: t.color.primaryOutline, backgroundColor: t.color.surface2 },
|
|
54
54
|
card_danger: { borderColor: t.color.danger400 ?? t.color.destructive, backgroundColor: t.color.dangerBg },
|
|
55
|
+
// O amarelo é o DE PREENCHER — `primary`, o mesmo do botão sólido. A borda some: uma superfície
|
|
56
|
+
// cheia não precisa de contorno, e um contorno sobre o amarelo mede 2,43 no claro (invisível).
|
|
57
|
+
card_brand: { backgroundColor: t.color.primary, borderColor: "transparent" },
|
|
58
|
+
// A ação fica embaixo, separada pelo mesmo respiro que o resto da casa usa entre blocos.
|
|
59
|
+
acaoDaMarca: { marginTop: t.size.space3 },
|
|
55
60
|
}));
|
|
56
61
|
/** Coluna com `--space-4` entre os filhos. **Sem prop de espaçamento, de propósito.** */
|
|
57
62
|
export function Stack({ style, ...rest }) {
|
|
@@ -112,6 +117,32 @@ export function Separator({ orientation = "horizontal", label, style, ...rest })
|
|
|
112
117
|
* que É um alvo — quem o torna tocável é a tela.
|
|
113
118
|
*/
|
|
114
119
|
export function Card({ variant = "base", style, ...rest }) {
|
|
115
|
-
const
|
|
116
|
-
|
|
120
|
+
const t = useAureaTokens();
|
|
121
|
+
const s = folha(t);
|
|
122
|
+
const { action, children, ...resto } = rest;
|
|
123
|
+
const corpo = (_jsxs(_Fragment, { children: [children, action != null && _jsx(View, { style: s.acaoDaMarca, children: action })] }));
|
|
124
|
+
const pele = _jsx(View, { style: [s.cardBase, s[`card_${variant}`], style], ...resto, children: corpo });
|
|
125
|
+
// 🔴 SOBRE O AMARELO A COR DO TEXTO NÃO É ESCOLHA, É OBRIGAÇÃO — e os números decidiram isto.
|
|
126
|
+
// Medido nos dois temas, texto contra o `primary`:
|
|
127
|
+
// texto comum ........ 10,34 no claro · **1,83 no escuro** 🔴 ilegível
|
|
128
|
+
// texto esmaecido ..... 4,24 no claro · **1,35 no escuro** 🔴 ilegível
|
|
129
|
+
// primary-foreground .. 4,54 nos DOIS ✅ único que serve
|
|
130
|
+
// Sem forçar, o cartão da marca sairia quebrado no tema escuro **sem ninguém ver no claro**.
|
|
131
|
+
// ⚠ **E no RN não há cascata de cor**: `color` num `View` não desce para o `Text` de dentro.
|
|
132
|
+
// A saída é CONTEXTO — a mesma decisão que o `Field` do Lote 4 tomou para empurrar nome e dica.
|
|
133
|
+
//
|
|
134
|
+
// ⚠ **Consequência declarada: sobre o amarelo existe UMA cor de texto, não duas.** O tom
|
|
135
|
+
// `muted` mede 1,35 no escuro, então ele não pode sobreviver aqui. A hierarquia dentro deste
|
|
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`.
|
|
145
|
+
return variant === "brand"
|
|
146
|
+
? _jsx(SobreAMarca.Provider, { value: { tinta: t.color.primaryForeground, fundo: t.color.primary }, children: pele })
|
|
147
|
+
: pele;
|
|
117
148
|
}
|
package/dist/navigation.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Pressable, View
|
|
2
|
+
import { Pressable, View } from "react-native";
|
|
3
3
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
4
4
|
import { Badge } from "./display.js";
|
|
5
5
|
import { comOpacidade, criarFolha } from "./estilos.js";
|
|
6
6
|
import { Icon } from "./icon.js";
|
|
7
7
|
import { Card } from "./layout.js";
|
|
8
|
+
import { FilaRolante } from "./rolagem.js";
|
|
8
9
|
import { Text } from "./text.js";
|
|
9
10
|
import { useAureaStrings, useAureaTokens } from "./theme.js";
|
|
10
11
|
const folha = criarFolha((t) => ({
|
|
@@ -317,10 +318,7 @@ export function Tabs({ tabs, value, onChange, label, style, ...rest }) {
|
|
|
317
318
|
const s = folha(t);
|
|
318
319
|
const strings = useAureaStrings();
|
|
319
320
|
const aberta = tabs.find((it) => it.id === value);
|
|
320
|
-
return (_jsxs(View, { style: style, ...rest, children: [_jsx(
|
|
321
|
-
// Sem isto o conteúdo estica até a largura do rolador e o `padding` de 3 da cápsula
|
|
322
|
-
// aparece como faixa vazia à direita quando as abas cabem.
|
|
323
|
-
contentContainerStyle: { flexGrow: 0 }, children: _jsx(View, { accessibilityRole: "tablist", accessibilityLabel: label ?? strings.tabsLabel, style: s.abas, children: tabs.map((it) => {
|
|
321
|
+
return (_jsxs(View, { style: style, ...rest, children: [_jsx(FilaRolante, { children: _jsx(View, { accessibilityRole: "tablist", accessibilityLabel: label ?? strings.tabsLabel, style: s.abas, children: tabs.map((it) => {
|
|
324
322
|
const ativa = it.id === value;
|
|
325
323
|
return (_jsx(Pressable, { onPress: it.disabled ? undefined : () => onChange?.(it.id), disabled: it.disabled, accessibilityRole: "tab", accessibilityState: { selected: ativa, disabled: !!it.disabled }, accessibilityLabel: typeof it.label === "string" ? it.label : undefined, style: [s.abaDeTab, ativa && s.abaDeTabAtiva, it.disabled && { opacity: 0.5 }], children: typeof it.label === "string"
|
|
326
324
|
? _jsx(Text, { size: "sm", weight: ativa ? 600 : 400, style: { color: ativa ? t.color.foreground : t.color.mutedForeground }, children: it.label })
|
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
|
-
|
|
190
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
export interface FilaRolanteProps {
|
|
4
|
+
/** O conteúdo da fila — normalmente a cápsula com os itens dentro. */
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
/** Estilo do invólucro externo, não do conteúdo. */
|
|
7
|
+
style?: StyleProp<ViewStyle>;
|
|
8
|
+
testID?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Uma linha que rola para o lado quando não cabe, e que sabe QUANDO não cabe.
|
|
12
|
+
*
|
|
13
|
+
* O componente fica com a informação em vez de adivinhar: `onLayout` dá a largura visível e
|
|
14
|
+
* `onContentSizeChange` dá a do conteúdo. Com as duas, `transbordou` é uma conta, não um palpite.
|
|
15
|
+
*/
|
|
16
|
+
export declare function FilaRolante({ children, style, testID }: FilaRolanteProps): React.JSX.Element;
|
package/dist/rolagem.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// A FILA QUE ROLA — e por que ela existe em vez de cada componente resolver o seu.
|
|
3
|
+
//
|
|
4
|
+
// 🔴 **ISTO NÃO É DESENHO NOVO: É TRADUÇÃO QUE FALTOU.** O consumidor pediu em 17/09/2026 um
|
|
5
|
+
// grupo de opções que coubesse com seis itens, e a primeira resposta que eu ia dar era criar um
|
|
6
|
+
// componente de "chips". **Medindo a web antes, o pedido já estava resolvido lá:**
|
|
7
|
+
//
|
|
8
|
+
// aurea.css:2127, em tela de até 640px:
|
|
9
|
+
// .tabs,.pagination,.segmented { max-width:100%; overflow-x:auto; }
|
|
10
|
+
//
|
|
11
|
+
// Ou seja: **na web o segmentado e as abas JÁ rolam no telefone.** O nativo é que não traduziu.
|
|
12
|
+
// Criar um terceiro jeito de escolher teria sido inventar o que já existia — e essa briga tem
|
|
13
|
+
// nome neste repositório: o `Sidebar`.
|
|
14
|
+
//
|
|
15
|
+
// ⚠ **E o sinal de "tem mais" foi pedido junto, com razão:** quem não sabe que dá para arrastar
|
|
16
|
+
// não acha a sexta opção. A saída óbvia do React Native **não serve** — `fadingEdgeLength` é
|
|
17
|
+
// `@platform android` (medido no tipo do RN), e usá-la daria o aviso no Android e **nada no
|
|
18
|
+
// iPhone**. É a armadilha de um alvo só que este projeto já pagou várias vezes.
|
|
19
|
+
// Gradiente também não: proibido desde a Fase 0.
|
|
20
|
+
//
|
|
21
|
+
// **O que fica, e é o mesmo da web:** a opção cortada pela borda é o sinal, e ele vem do recorte.
|
|
22
|
+
// O que este módulo acrescenta é MEDIR se há transbordo, para (1) mostrar a barra de rolagem só
|
|
23
|
+
// quando ela significa alguma coisa e (2) dar ao teste um número para conferir.
|
|
24
|
+
// ⚠ **Declarado: se o corte LÊ bem é pergunta de vidro, e nenhum aparelho deste projeto rodou
|
|
25
|
+
// iOS.** O que está provado aqui é a medição, não a leitura.
|
|
26
|
+
import * as React from "react";
|
|
27
|
+
import { ScrollView } from "react-native";
|
|
28
|
+
/**
|
|
29
|
+
* Uma linha que rola para o lado quando não cabe, e que sabe QUANDO não cabe.
|
|
30
|
+
*
|
|
31
|
+
* O componente fica com a informação em vez de adivinhar: `onLayout` dá a largura visível e
|
|
32
|
+
* `onContentSizeChange` dá a do conteúdo. Com as duas, `transbordou` é uma conta, não um palpite.
|
|
33
|
+
*/
|
|
34
|
+
export function FilaRolante({ children, style, testID }) {
|
|
35
|
+
const [visivel, setVisivel] = React.useState(0);
|
|
36
|
+
const [conteudo, setConteudo] = React.useState(0);
|
|
37
|
+
const transbordou = conteudo > visivel && visivel > 0;
|
|
38
|
+
return (_jsx(ScrollView, { horizontal: true, testID: testID, onLayout: (e) => setVisivel(e.nativeEvent.layout.width), onContentSizeChange: (w) => setConteudo(w),
|
|
39
|
+
// A barra só aparece quando ela quer dizer alguma coisa. Com tudo cabendo ela seria ruído,
|
|
40
|
+
// e é assim que o `overflow-x:auto` da web se comporta: sem transbordo, sem barra.
|
|
41
|
+
showsHorizontalScrollIndicator: transbordou,
|
|
42
|
+
// Sem isto o conteúdo estica até a largura do rolador, e o respiro da cápsula aparece como
|
|
43
|
+
// faixa vazia à direita quando os itens cabem.
|
|
44
|
+
contentContainerStyle: { flexGrow: 0 }, style: style, children: children }));
|
|
45
|
+
}
|
package/dist/text.js
CHANGED
|
@@ -22,7 +22,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
22
22
|
import * as React from "react";
|
|
23
23
|
import { Text as TextRN } from "react-native";
|
|
24
24
|
import { criarFolha } from "./estilos.js";
|
|
25
|
-
import { useAureaTokens } from "./theme.js";
|
|
25
|
+
import { useAureaTokens, useSobreAMarca } from "./theme.js";
|
|
26
26
|
const TAMANHO = {
|
|
27
27
|
xs: "textXs", sm: "textSm", md: "textMd", base: "textBase", lg: "textLg",
|
|
28
28
|
xl: "textXl", "2xl": "text2xl", "3xl": "text3xl", "4xl": "text4xl", "5xl": "text5xl",
|
|
@@ -61,6 +61,7 @@ const folha = criarFolha((t) => {
|
|
|
61
61
|
export function Text({ size = "md", weight = 400, font = "ui", tone = "default", leading = "normal", italic = false, align, tracking, style, ...rest }) {
|
|
62
62
|
const t = useAureaTokens();
|
|
63
63
|
const s = folha(t);
|
|
64
|
+
const sobreAMarca = useSobreAMarca();
|
|
64
65
|
const proprio = React.useMemo(() => {
|
|
65
66
|
const fontSize = t.size[TAMANHO[size]] ?? t.size.textMd;
|
|
66
67
|
const escala = t.font[font];
|
|
@@ -79,5 +80,13 @@ export function Text({ size = "md", weight = 400, font = "ui", tone = "default",
|
|
|
79
80
|
...(align ? { textAlign: align } : null),
|
|
80
81
|
};
|
|
81
82
|
}, [t, size, weight, font, leading, italic, align, tracking]);
|
|
82
|
-
|
|
83
|
+
// 🔴 SOBRE UMA SUPERFÍCIE DA MARCA A COR É FORÇADA, e ela vence até o tom explícito.
|
|
84
|
+
// Medido contra o `primary` nos dois temas: texto comum dá **1,83 no escuro**, esmaecido
|
|
85
|
+
// **1,35**, e `danger` menos ainda. **Nenhum tom alcança os 4,5 da norma sobre o amarelo** —
|
|
86
|
+
// só o `primary-foreground`, com 4,54. Respeitar um `tone` ali dentro seria obedecer a prop e
|
|
87
|
+
// entregar texto que não se lê.
|
|
88
|
+
// ⚠ **A consequência está declarada e não é neutra:** dentro de um cartão da marca não existe
|
|
89
|
+
// texto esmaecido. A hierarquia sai de PESO e TAMANHO.
|
|
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.tinta }, proprio, style], ...rest });
|
|
83
92
|
}
|
package/dist/theme.d.ts
CHANGED
|
@@ -73,3 +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
|
+
}
|
|
82
|
+
/** O provedor. Quem usa é o `Card variant="brand"`. */
|
|
83
|
+
export declare const SobreAMarca: React.Context<SobreAMarcaValor | null>;
|
|
84
|
+
/**
|
|
85
|
+
* O par de cores obrigatório quando se está sobre a marca, ou `null` quando não se está.
|
|
86
|
+
*
|
|
87
|
+
* ⚠ **Ele vence TODOS os tons, inclusive os explícitos.** Um `tone="danger"` sobre o amarelo
|
|
88
|
+
* mediria menos que os 4,5 da norma, então respeitá-lo entregaria texto ilegível em nome da
|
|
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.
|
|
133
|
+
*/
|
|
134
|
+
export declare function usePeleSobreAMarca(): {
|
|
135
|
+
borderColor: string;
|
|
136
|
+
backgroundColor: string;
|
|
137
|
+
color: string;
|
|
138
|
+
} | null;
|
package/dist/theme.js
CHANGED
|
@@ -109,3 +109,64 @@ export function useAureaTokens() {
|
|
|
109
109
|
export function useAureaStrings() {
|
|
110
110
|
return usar("useAureaStrings").strings;
|
|
111
111
|
}
|
|
112
|
+
const SobreAMarcaCtx = React.createContext(null);
|
|
113
|
+
/** O provedor. Quem usa é o `Card variant="brand"`. */
|
|
114
|
+
export const SobreAMarca = SobreAMarcaCtx;
|
|
115
|
+
/**
|
|
116
|
+
* O par de cores obrigatório quando se está sobre a marca, ou `null` quando não se está.
|
|
117
|
+
*
|
|
118
|
+
* ⚠ **Ele vence TODOS os tons, inclusive os explícitos.** Um `tone="danger"` sobre o amarelo
|
|
119
|
+
* mediria menos que os 4,5 da norma, então respeitá-lo entregaria texto ilegível em nome da
|
|
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.
|
|
126
|
+
*/
|
|
127
|
+
export function useSobreAMarca() {
|
|
128
|
+
return React.useContext(SobreAMarcaCtx);
|
|
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.
|
|
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.
|
|
50
|
+
"@aurea-uds/tokens": "^0.8.7"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@react-native-community/datetimepicker": ">=8",
|