@aurea-uds/native 0.8.6 → 0.8.8
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/barranav.d.ts +52 -0
- package/dist/barranav.js +81 -0
- package/dist/busca.js +32 -25
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/inputs.d.ts +9 -1
- package/dist/inputs.js +56 -12
- package/dist/layout.js +32 -2
- package/dist/navigation.d.ts +49 -14
- package/dist/navigation.js +87 -19
- package/dist/numero.d.ts +11 -0
- package/dist/numero.js +52 -2
- package/dist/overlays.js +10 -10
- package/dist/screen.js +13 -1
- package/dist/text.js +31 -1
- package/dist/theme.d.ts +58 -5
- package/dist/theme.js +51 -20
- 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,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Põe em volta do navegador de abas. O `BottomNav` mede a própria altura e conta para ele; o
|
|
4
|
+
* `Screen` com `scroll` lê e reserva o espaço no fim do conteúdo, sozinho.
|
|
5
|
+
*
|
|
6
|
+
* ```tsx
|
|
7
|
+
* <BottomNavProvider>
|
|
8
|
+
* <Tabs
|
|
9
|
+
* screenOptions={{headerShown: false}}
|
|
10
|
+
* tabBar={({state, navigation}) => (
|
|
11
|
+
* <BottomNav
|
|
12
|
+
* label="Menu principal"
|
|
13
|
+
* current={state.routes[state.index].name}
|
|
14
|
+
* items={rotas.map((r) => ({
|
|
15
|
+
* id: r.name, label: r.titulo, icon: r.icone,
|
|
16
|
+
* onPress: () => navigation.navigate(r.name),
|
|
17
|
+
* }))} />
|
|
18
|
+
* )} />
|
|
19
|
+
* </BottomNavProvider>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* ⚠ **O `BottomNav` vai SOLTO no `tabBar`, sem `View` em volta.** Um embrulho com fundo pinta uma
|
|
23
|
+
* faixa atrás da pílula e acaba com a flutuação — era o que a documentação antiga mandava fazer,
|
|
24
|
+
* e era o defeito.
|
|
25
|
+
*
|
|
26
|
+
* ⚠ **Fora daqui, `useBottomNavSpace()` devolve 0** e o `Screen` não reserva nada. É o que faz
|
|
27
|
+
* uma tela cheia, sem abas, continuar exatamente como era.
|
|
28
|
+
*/
|
|
29
|
+
export declare function BottomNavProvider({ children }: {
|
|
30
|
+
children?: React.ReactNode;
|
|
31
|
+
}): React.JSX.Element;
|
|
32
|
+
/**
|
|
33
|
+
* Quanto espaço a barra de abas ocupa no pé da tela, em pontos — pílula, margens e folga do
|
|
34
|
+
* sistema somadas. **Zero** quando não há `BottomNavProvider` em volta, ou quando a barra é
|
|
35
|
+
* `edge` (que fica no fluxo e já encurta a tela sozinha).
|
|
36
|
+
*
|
|
37
|
+
* O `Screen` com `scroll` já usa isto sozinho. Este gancho é para o resto: uma fileira de botões
|
|
38
|
+
* presa no pé da tela, por exemplo, que precisa subir acima da pílula.
|
|
39
|
+
*
|
|
40
|
+
* ```tsx
|
|
41
|
+
* const respiro = useBottomNavSpace();
|
|
42
|
+
* <View style={{position: "absolute", left: 0, right: 0, bottom: respiro}}>
|
|
43
|
+
* <Button fullWidth>Salvar</Button>
|
|
44
|
+
* </View>
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function useBottomNavSpace(): number;
|
|
48
|
+
/**
|
|
49
|
+
* Interno: como o `BottomNav` conta a própria altura. Devolve `null` fora de um provedor, e nesse
|
|
50
|
+
* caso a barra não mede nada — ninguém está ouvindo.
|
|
51
|
+
*/
|
|
52
|
+
export declare function useAnotarBottomNav(): ((n: number) => void) | null;
|
package/dist/barranav.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// Aurea nativo — quanto espaço a barra de abas ocupa, e quem precisa saber.
|
|
3
|
+
//
|
|
4
|
+
// 🔴 **ESTE MÓDULO EXISTE POR UM DEFEITO MEDIDO EM APARELHO, 19/09/2026**, e não por arquitetura.
|
|
5
|
+
// A variante `floating` do `BottomNav` passou a flutuar POR CIMA da tela — que é o que o nome
|
|
6
|
+
// dela sempre prometeu e o que a web já fazia (`.bottom-nav` é `position: sticky; bottom: 0`,
|
|
7
|
+
// `aurea.css:270`). Flutuar por cima tem um preço: **o fim da rolagem fica escondido atrás da
|
|
8
|
+
// pílula.** Alguém tem de reservar esse espaço, e a conta não pode cair no app.
|
|
9
|
+
//
|
|
10
|
+
// ⚠ **Por que a conta não pode ser do app:** ela é a altura da pílula (que depende da densidade,
|
|
11
|
+
// do tamanho da letra do sistema e de haver rótulo) mais as duas margens mais a folga do sistema
|
|
12
|
+
// — que muda entre um aparelho com três botões (≈48) e um com gesto (≈16). Deixar isso para o
|
|
13
|
+
// consumidor é pedir um número mágico que fica errado no primeiro aparelho diferente.
|
|
14
|
+
//
|
|
15
|
+
// ── POR QUE ELE MORA NUM ARQUIVO SÓ DELE ─────────────────────────────────────────────────────
|
|
16
|
+
// Quem MEDE é o `BottomNav` (`navigation.tsx`); quem LÊ é o `Screen` (`screen.tsx`). Os dois não
|
|
17
|
+
// se importam hoje, e pôr o contexto em qualquer um dos dois criaria um ciclo. É a mesma razão
|
|
18
|
+
// que fez a tinta da marca morar no `theme.tsx` — com a diferença de que ali os dois já
|
|
19
|
+
// importavam o `theme`, e aqui não há módulo comum. Então: um arquivo, sem dependência nenhuma
|
|
20
|
+
// além do React.
|
|
21
|
+
import * as React from "react";
|
|
22
|
+
const BarraCtx = React.createContext(null);
|
|
23
|
+
/**
|
|
24
|
+
* Põe em volta do navegador de abas. O `BottomNav` mede a própria altura e conta para ele; o
|
|
25
|
+
* `Screen` com `scroll` lê e reserva o espaço no fim do conteúdo, sozinho.
|
|
26
|
+
*
|
|
27
|
+
* ```tsx
|
|
28
|
+
* <BottomNavProvider>
|
|
29
|
+
* <Tabs
|
|
30
|
+
* screenOptions={{headerShown: false}}
|
|
31
|
+
* tabBar={({state, navigation}) => (
|
|
32
|
+
* <BottomNav
|
|
33
|
+
* label="Menu principal"
|
|
34
|
+
* current={state.routes[state.index].name}
|
|
35
|
+
* items={rotas.map((r) => ({
|
|
36
|
+
* id: r.name, label: r.titulo, icon: r.icone,
|
|
37
|
+
* onPress: () => navigation.navigate(r.name),
|
|
38
|
+
* }))} />
|
|
39
|
+
* )} />
|
|
40
|
+
* </BottomNavProvider>
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* ⚠ **O `BottomNav` vai SOLTO no `tabBar`, sem `View` em volta.** Um embrulho com fundo pinta uma
|
|
44
|
+
* faixa atrás da pílula e acaba com a flutuação — era o que a documentação antiga mandava fazer,
|
|
45
|
+
* e era o defeito.
|
|
46
|
+
*
|
|
47
|
+
* ⚠ **Fora daqui, `useBottomNavSpace()` devolve 0** e o `Screen` não reserva nada. É o que faz
|
|
48
|
+
* uma tela cheia, sem abas, continuar exatamente como era.
|
|
49
|
+
*/
|
|
50
|
+
export function BottomNavProvider({ children }) {
|
|
51
|
+
const [espaco, setEspaco] = React.useState(0);
|
|
52
|
+
// `setEspaco` tem identidade estável, então isto só muda quando o número muda — e o número só
|
|
53
|
+
// muda quando a barra muda de tamanho de verdade (rotação, letra do sistema, troca de variante).
|
|
54
|
+
const valor = React.useMemo(() => ({ espaco, anotar: setEspaco }), [espaco]);
|
|
55
|
+
return _jsx(BarraCtx.Provider, { value: valor, children: children });
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Quanto espaço a barra de abas ocupa no pé da tela, em pontos — pílula, margens e folga do
|
|
59
|
+
* sistema somadas. **Zero** quando não há `BottomNavProvider` em volta, ou quando a barra é
|
|
60
|
+
* `edge` (que fica no fluxo e já encurta a tela sozinha).
|
|
61
|
+
*
|
|
62
|
+
* O `Screen` com `scroll` já usa isto sozinho. Este gancho é para o resto: uma fileira de botões
|
|
63
|
+
* presa no pé da tela, por exemplo, que precisa subir acima da pílula.
|
|
64
|
+
*
|
|
65
|
+
* ```tsx
|
|
66
|
+
* const respiro = useBottomNavSpace();
|
|
67
|
+
* <View style={{position: "absolute", left: 0, right: 0, bottom: respiro}}>
|
|
68
|
+
* <Button fullWidth>Salvar</Button>
|
|
69
|
+
* </View>
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function useBottomNavSpace() {
|
|
73
|
+
return React.useContext(BarraCtx)?.espaco ?? 0;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Interno: como o `BottomNav` conta a própria altura. Devolve `null` fora de um provedor, e nesse
|
|
77
|
+
* caso a barra não mede nada — ninguém está ouvindo.
|
|
78
|
+
*/
|
|
79
|
+
export function useAnotarBottomNav() {
|
|
80
|
+
return React.useContext(BarraCtx)?.anotar ?? null;
|
|
81
|
+
}
|
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/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { AureaProvider, useAureaTheme, useAureaTokens } from "./theme.js";
|
|
2
2
|
export type { AureaProviderProps, AureaThemeControl } from "./theme.js";
|
|
3
|
+
export { BottomNavProvider, useBottomNavSpace } from "./barranav.js";
|
|
3
4
|
export { resolverTokens } from "./tokens.js";
|
|
4
5
|
export type { AureaDensity, AureaFontFamilies, AureaFontInput, AureaFontScale, AureaShadow, AureaThemeName, AureaTokens, } from "./tokens.js";
|
|
5
6
|
export { criarFolha } from "./estilos.js";
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
// e o caminho profundo é a forma documentada (ADR-0038, cláusula 1) — importar daqui traria os
|
|
25
25
|
// 2571 ao grafo do bundler.
|
|
26
26
|
export { AureaProvider, useAureaTheme, useAureaTokens } from "./theme.js";
|
|
27
|
+
// A barra de abas que flutua precisa contar a própria altura para a tela reservar o fim da
|
|
28
|
+
// rolagem. Ver `barranav.tsx` — e o `BottomNav`, que é quem mede.
|
|
29
|
+
export { BottomNavProvider, useBottomNavSpace } from "./barranav.js";
|
|
27
30
|
export { resolverTokens } from "./tokens.js";
|
|
28
31
|
// A fábrica de folhas memoizada por (tema, densidade). Pública porque o consumidor tem o mesmo
|
|
29
32
|
// problema que os componentes daqui — e a medição em aparelho mostrou que ele é real.
|
package/dist/inputs.d.ts
CHANGED
|
@@ -150,6 +150,14 @@ interface ControleProps {
|
|
|
150
150
|
size?: AureaFieldSize;
|
|
151
151
|
style?: StyleProp<ViewStyle>;
|
|
152
152
|
testID?: string;
|
|
153
|
+
/**
|
|
154
|
+
* O nome para quem usa leitor de tela, quando ele NÃO deve aparecer escrito no controle.
|
|
155
|
+
*
|
|
156
|
+
* É o caso de uma linha de `NavList`: o nome já está na linha, então repeti-lo no `label` o
|
|
157
|
+
* escreveria duas vezes — e omitir os dois deixaria o controle **MUDO** para o leitor de tela.
|
|
158
|
+
* Ele vence o `label` quando os dois vêm.
|
|
159
|
+
*/
|
|
160
|
+
accessibilityLabel?: string;
|
|
153
161
|
}
|
|
154
162
|
export interface InputGroupProps extends ViewProps {
|
|
155
163
|
/** O degrau do sistema. Cai no `Field` em volta quando não vem, como no `Input`. */
|
|
@@ -256,7 +264,7 @@ export interface SwitchProps extends ControleProps {
|
|
|
256
264
|
* ⚠ **E ele respeita "menos movimento"**: com a preferência ligada, o polegar salta em vez de
|
|
257
265
|
* deslizar. O estado continua o mesmo — o que some é a animação, não a informação.
|
|
258
266
|
*/
|
|
259
|
-
export declare function Switch({ label, description, checked, onChange, disabled, size, style, testID, }: SwitchProps): React.JSX.Element;
|
|
267
|
+
export declare function Switch({ label, description, checked, onChange, disabled, size, style, testID, accessibilityLabel, }: SwitchProps): React.JSX.Element;
|
|
260
268
|
export interface SegmentedControlProps extends ViewProps {
|
|
261
269
|
items: Array<{
|
|
262
270
|
value: string;
|
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 }) }));
|
|
@@ -312,16 +327,23 @@ export function PasswordField({ defaultVisible = false, size, disabled, autoCapi
|
|
|
312
327
|
const inativo = disabled ?? campo?.disabled;
|
|
313
328
|
return (_jsxs(InputGroup, { size: tam, disabled: inativo, testID: testID, children: [leading ? _jsx(InputGroupAddon, { children: leading }) : null, _jsx(Input, { ...rest, size: tam, disabled: inativo, autoCapitalize: autoCapitalize, secureTextEntry: !visivel, style: style, testID: testID ? `${testID}-campo` : undefined }), _jsx(InputGroupAddon, { children: _jsx(IconButton, { name: visivel ? "view--off" : "view", label: visivel ? strings.passwordHide : strings.passwordShow, appearance: "ghost", size: tam === "lg" ? "md" : "sm", disabled: inativo, icons: icons, onPress: () => setVisivel(v => !v), testID: testID ? `${testID}-olho` : undefined }) })] }));
|
|
314
329
|
}
|
|
315
|
-
function ControleMarcado({ papel, label, description, checked, onChange, disabled, size, style, testID, }) {
|
|
330
|
+
function ControleMarcado({ papel, label, description, checked, onChange, disabled, size, style, testID, accessibilityLabel, }) {
|
|
316
331
|
const t = useAureaTokens();
|
|
317
332
|
const s = folha(t);
|
|
318
333
|
const campo = useCampo();
|
|
319
334
|
const tam = size ?? campo?.size ?? "md";
|
|
320
335
|
const inativo = disabled ?? campo?.disabled;
|
|
336
|
+
// Mesma conta do `Switch`, pela mesma razão: sem texto, a coluna vazia com `flex: 1` tomaria a
|
|
337
|
+
// largura que sobra dentro de uma fileira e apagaria o que estivesse ao lado.
|
|
338
|
+
const temTexto = label != null || description != null;
|
|
321
339
|
// A marca é METADE da altura do controle — `aurea.css:871`, e é o que faz ela crescer junto
|
|
322
340
|
// com a densidade sem token próprio.
|
|
323
341
|
const lado = alturaDoTamanho(t, tam) / 2;
|
|
324
|
-
return (_jsxs(Pressable, { testID: testID, onPress: inativo ? undefined : () => onChange?.(!checked), disabled: inativo, accessibilityRole: papel, accessibilityState: { checked: !!checked, disabled: !!inativo },
|
|
342
|
+
return (_jsxs(Pressable, { testID: testID, onPress: inativo ? undefined : () => onChange?.(!checked), disabled: inativo, accessibilityRole: papel, accessibilityState: { checked: !!checked, disabled: !!inativo },
|
|
343
|
+
// ⚠ O `accessibilityLabel` de fora vence o `label` escrito, e existe para o caso em que o
|
|
344
|
+
// nome JÁ está na tela ao lado — uma linha de `NavList`, por exemplo. Sem ele, ou o nome
|
|
345
|
+
// aparece escrito duas vezes, ou o controle sobe MUDO para quem usa leitor de tela.
|
|
346
|
+
accessibilityLabel: accessibilityLabel ?? (typeof label === "string" ? label : campo?.label), accessibilityHint: typeof description === "string" ? description : campo?.hint, style: [s.linhaDeControle, inativo && s.desabilitado, style], children: [_jsx(View, { style: [
|
|
325
347
|
s.marcaBase,
|
|
326
348
|
{ width: lado, height: lado,
|
|
327
349
|
borderRadius: papel === "radio" ? t.size.radiusFull : 5 },
|
|
@@ -340,8 +362,8 @@ function ControleMarcado({ papel, label, description, checked, onChange, disable
|
|
|
340
362
|
borderLeftWidth: 2, borderBottomWidth: 2,
|
|
341
363
|
borderColor: t.color.controlSelectedForeground,
|
|
342
364
|
transform: [{ rotate: "-45deg" }],
|
|
343
|
-
} })) }), _jsxs(View, { style: { flex: 1 }, children: [typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label, description != null && (typeof description === "string"
|
|
344
|
-
? _jsx(Text, { size: "xs", tone: "muted", children: description }) : description)] })] }));
|
|
365
|
+
} })) }), temTexto && (_jsxs(View, { style: { flex: 1 }, children: [typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label, description != null && (typeof description === "string"
|
|
366
|
+
? _jsx(Text, { size: "xs", tone: "muted", children: description }) : description)] }))] }));
|
|
345
367
|
}
|
|
346
368
|
/** A caixa que marca. `accessibilityRole="checkbox"` + `state.checked`. */
|
|
347
369
|
export function Checkbox(p) { return _jsx(ControleMarcado, { ...p, papel: "checkbox" }); }
|
|
@@ -363,13 +385,14 @@ export function Radio(p) { return _jsx(ControleMarcado, { ...p, papel: "radio" }
|
|
|
363
385
|
* ⚠ **E ele respeita "menos movimento"**: com a preferência ligada, o polegar salta em vez de
|
|
364
386
|
* deslizar. O estado continua o mesmo — o que some é a animação, não a informação.
|
|
365
387
|
*/
|
|
366
|
-
export function Switch({ label, description, checked, onChange, disabled, size, style, testID, }) {
|
|
388
|
+
export function Switch({ label, description, checked, onChange, disabled, size, style, testID, accessibilityLabel, }) {
|
|
367
389
|
const t = useAureaTokens();
|
|
368
390
|
const s = folha(t);
|
|
369
391
|
const campo = useCampo();
|
|
370
392
|
const tam = size ?? campo?.size ?? "md";
|
|
371
393
|
const inativo = disabled ?? campo?.disabled;
|
|
372
394
|
const reduzir = useReduceMotion();
|
|
395
|
+
const temTexto = label != null || description != null;
|
|
373
396
|
const h = alturaDoTamanho(t, tam);
|
|
374
397
|
const larguraDoTrilho = h * 7 / 6;
|
|
375
398
|
const alturaDoTrilho = h * 2 / 3;
|
|
@@ -388,10 +411,28 @@ export function Switch({ label, description, checked, onChange, disabled, size,
|
|
|
388
411
|
a.start();
|
|
389
412
|
return () => a.stop();
|
|
390
413
|
}, [checked, pos, reduzir]);
|
|
391
|
-
return (_jsxs(Pressable, { testID: testID, onPress: inativo ? undefined : () => onChange?.(!checked), disabled: inativo, accessibilityRole: "switch", accessibilityState: { checked: !!checked, disabled: !!inativo },
|
|
414
|
+
return (_jsxs(Pressable, { testID: testID, onPress: inativo ? undefined : () => onChange?.(!checked), disabled: inativo, accessibilityRole: "switch", accessibilityState: { checked: !!checked, disabled: !!inativo },
|
|
415
|
+
// O `accessibilityLabel` de fora vence o `label` escrito — ver a prop, em `ControleProps`.
|
|
416
|
+
accessibilityLabel: accessibilityLabel ?? (typeof label === "string" ? label : campo?.label),
|
|
392
417
|
// Mesma regra do `ControleMarcado`: a descrição vira DICA quando é texto. Sem isto ela
|
|
393
418
|
// desenha na tela e não existe para quem usa leitor de tela.
|
|
394
|
-
accessibilityHint: typeof description === "string" ? description : campo?.hint,
|
|
419
|
+
accessibilityHint: typeof description === "string" ? description : campo?.hint,
|
|
420
|
+
// 🔴 SEM RÓTULO E SEM DESCRIÇÃO, A RAIZ É O TRILHO E MAIS NADA — e isto é conserto de um
|
|
421
|
+
// defeito que só aparece no ANDROID, achado pelo consumidor em 19/09/2026.
|
|
422
|
+
//
|
|
423
|
+
// A coluna de texto abaixo tem `flex: 1` e existia mesmo vazia. Dentro de uma fileira — uma
|
|
424
|
+
// linha de `NavList`, onde o valor-componente é IRMÃO do miolo (`navigation.tsx:248`) — esse
|
|
425
|
+
// `flex: 1` faz a raiz do `Switch` tomar toda a largura que sobra. O miolo, que também tem
|
|
426
|
+
// `flex: 1`, encolhe até restar só o ícone, e o NOME DA LINHA SOME.
|
|
427
|
+
//
|
|
428
|
+
// ⚠ **No navegador o defeito não aparece**, porque lá a divisão de espaço entre dois
|
|
429
|
+
// irmãos com `flex:1` parte da largura do conteúdo. É a mesma família do `padding-block` do
|
|
430
|
+
// `Input`: o que a cascata resolve de graça, aqui é conta explícita.
|
|
431
|
+
style: [
|
|
432
|
+
{ flexDirection: "row", alignItems: "center" },
|
|
433
|
+
temTexto && { gap: 10 },
|
|
434
|
+
inativo && s.desabilitado, style,
|
|
435
|
+
], children: [_jsx(View, { style: [
|
|
395
436
|
s.trilho,
|
|
396
437
|
{ width: larguraDoTrilho, height: alturaDoTrilho },
|
|
397
438
|
checked && { backgroundColor: t.color.controlSelected, borderColor: t.color.controlSelected },
|
|
@@ -400,8 +441,8 @@ export function Switch({ label, description, checked, onChange, disabled, size,
|
|
|
400
441
|
{ width: ladoDoPolegar, height: ladoDoPolegar,
|
|
401
442
|
backgroundColor: checked ? t.color.controlSelectedForeground : t.color.mutedForeground,
|
|
402
443
|
transform: [{ translateX: pos.interpolate({ inputRange: [0, 1], outputRange: [0, curso] }) }] },
|
|
403
|
-
] }) }), _jsxs(View, { style: { flex: 1 }, children: [typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label, description != null && (typeof description === "string"
|
|
404
|
-
? _jsx(Text, { size: "xs", tone: "muted", children: description }) : description)] })] }));
|
|
444
|
+
] }) }), temTexto && (_jsxs(View, { style: { flex: 1 }, children: [typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label, description != null && (typeof description === "string"
|
|
445
|
+
? _jsx(Text, { size: "xs", tone: "muted", children: description }) : description)] }))] }));
|
|
405
446
|
}
|
|
406
447
|
/**
|
|
407
448
|
* Um de poucos, lado a lado.
|
|
@@ -463,6 +504,7 @@ export function SegmentedControl({ items, value, onChange, label, disabled, styl
|
|
|
463
504
|
export function Select({ items, value, onChange, placeholder, disabled, size, chevron = "chevron--down", style, testID, }) {
|
|
464
505
|
const t = useAureaTokens();
|
|
465
506
|
const s = folha(t);
|
|
507
|
+
const peleDaMarca = usePeleSobreAMarca();
|
|
466
508
|
const campo = useCampo();
|
|
467
509
|
const tam = size ?? campo?.size ?? "md";
|
|
468
510
|
const inativo = disabled ?? campo?.disabled;
|
|
@@ -473,9 +515,11 @@ export function Select({ items, value, onChange, placeholder, disabled, size, ch
|
|
|
473
515
|
{ flexDirection: "row", alignItems: "center", justifyContent: "space-between",
|
|
474
516
|
height: alturaDoTamanho(t, tam), paddingHorizontal: respiroDoTamanho(t, tam) },
|
|
475
517
|
campo?.invalido && s.invalido,
|
|
518
|
+
// Sobre a marca a tinta vence inválido — ver `usePeleSobreAMarca`.
|
|
519
|
+
peleDaMarca,
|
|
476
520
|
inativo && s.desabilitado,
|
|
477
521
|
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))) }) })] }) })] }));
|
|
522
|
+
], 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
523
|
}
|
|
480
524
|
/**
|
|
481
525
|
* A pilha de campos, com o respiro do `--space-5`.
|
package/dist/layout.js
CHANGED
|
@@ -87,7 +87,29 @@ export function Cluster({ style, ...rest }) {
|
|
|
87
87
|
export function Grid({ minColumnWidth = 240, style, children, ...rest }) {
|
|
88
88
|
const s = folha(useAureaTokens());
|
|
89
89
|
const celula = React.useMemo(() => ({ flexGrow: 0, flexShrink: 1, flexBasis: minColumnWidth, minWidth: minColumnWidth, maxWidth: "100%" }), [minColumnWidth]);
|
|
90
|
-
return (_jsx(View, { style: [s.grid, style], ...rest, children: React.Children.map(children, (filho) =>
|
|
90
|
+
return (_jsx(View, { style: [s.grid, style], ...rest, children: React.Children.map(children, (filho) => {
|
|
91
|
+
if (!React.isValidElement(filho))
|
|
92
|
+
return filho;
|
|
93
|
+
// 🔴 O `flexGrow: 1` É O ITEM C14, e ele existe porque a célula NÃO basta.
|
|
94
|
+
//
|
|
95
|
+
// A célula já tem a altura certa: a grade é `flexWrap: "wrap"`, e o padrão do Yoga é
|
|
96
|
+
// `alignItems: Stretch` — medido no fonte do motor, `Style.h:907` —, então cada célula
|
|
97
|
+
// estica até a mais alta da LINHA. O que não acontecia é o filho preencher a célula: numa
|
|
98
|
+
// coluna, um filho sem `flexGrow` mede o próprio conteúdo, e dois `KPI` lado a lado, um
|
|
99
|
+
// com duas linhas de texto e outro com uma, saíam de alturas diferentes.
|
|
100
|
+
//
|
|
101
|
+
// ⚠ **Na web isto é de graça e por isso não tinha sido notado:** o `.grid`
|
|
102
|
+
// (`aurea.css:54`) é `display: grid`, e ali o cartão é o PRÓPRIO item da grade —
|
|
103
|
+
// `align-items` vale `stretch` por padrão e ele preenche a linha. Aqui existe uma célula
|
|
104
|
+
// no meio, e é ela que corta a herança de altura.
|
|
105
|
+
//
|
|
106
|
+
// ⚠ **O estilo vai ANTES do que o filho já tinha**, para um `style` do consumidor
|
|
107
|
+
// continuar vencendo. E um filho que não aceite `style` simplesmente não estica — fica
|
|
108
|
+
// como estava, nunca pior.
|
|
109
|
+
const proprio = filho.props.style;
|
|
110
|
+
const estica = React.cloneElement(filho, { style: [{ flexGrow: 1 }, proprio] });
|
|
111
|
+
return _jsx(View, { style: celula, children: estica });
|
|
112
|
+
}) }));
|
|
91
113
|
}
|
|
92
114
|
/**
|
|
93
115
|
* A linha de divisão do sistema. Mesma cor e mesma espessura do `.separator` da web.
|
|
@@ -134,7 +156,15 @@ export function Card({ variant = "base", style, ...rest }) {
|
|
|
134
156
|
// ⚠ **Consequência declarada: sobre o amarelo existe UMA cor de texto, não duas.** O tom
|
|
135
157
|
// `muted` mede 1,35 no escuro, então ele não pode sobreviver aqui. A hierarquia dentro deste
|
|
136
158
|
// cartão sai de PESO e TAMANHO, não de cor — e isso é restrição do contraste, não gosto.
|
|
159
|
+
//
|
|
160
|
+
// 🔴 **E ISTO NASCEU PELA METADE — o consumidor achou em UM DIA, 17/09/2026.** O parágrafo
|
|
161
|
+
// acima fala só de TEXTO, e foi só o texto que passou a ler o contexto. Medido depois, dentro
|
|
162
|
+
// deste mesmo cartão: contorno de campo **2,43/2,24**, fundo de campo **1,61** no claro, glifo
|
|
163
|
+
// de botão sem fundo **1,83** no escuro — e as QUATRO aparências do `action` que este tipo
|
|
164
|
+
// EXIGE, todas invisíveis (1,61 · 1,00 · 1,38 · 1,83).
|
|
165
|
+
// **A mesma frase valia para linha e glifo desde o primeiro dia, e eu parei no texto.**
|
|
166
|
+
// ✅ Hoje o contexto carrega o par (tinta, fundo), e quem lê está listado no `useSobreAMarca`.
|
|
137
167
|
return variant === "brand"
|
|
138
|
-
? _jsx(SobreAMarca.Provider, { value: t.color.primaryForeground, children: pele })
|
|
168
|
+
? _jsx(SobreAMarca.Provider, { value: { tinta: t.color.primaryForeground, fundo: t.color.primary }, children: pele })
|
|
139
169
|
: pele;
|
|
140
170
|
}
|