@aurea-uds/native 0.8.7 → 0.8.10
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/barranav.d.ts +52 -0
- package/dist/barranav.js +81 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/inputs.d.ts +9 -1
- package/dist/inputs.js +35 -9
- package/dist/layout.js +23 -1
- package/dist/navigation.d.ts +49 -14
- package/dist/navigation.js +87 -19
- package/dist/numero.d.ts +11 -0
- package/dist/numero.js +45 -1
- package/dist/screen.js +13 -1
- package/dist/text.js +30 -0
- package/package.json +2 -2
|
@@ -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/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
|
@@ -327,16 +327,23 @@ export function PasswordField({ defaultVisible = false, size, disabled, autoCapi
|
|
|
327
327
|
const inativo = disabled ?? campo?.disabled;
|
|
328
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 }) })] }));
|
|
329
329
|
}
|
|
330
|
-
function ControleMarcado({ papel, label, description, checked, onChange, disabled, size, style, testID, }) {
|
|
330
|
+
function ControleMarcado({ papel, label, description, checked, onChange, disabled, size, style, testID, accessibilityLabel, }) {
|
|
331
331
|
const t = useAureaTokens();
|
|
332
332
|
const s = folha(t);
|
|
333
333
|
const campo = useCampo();
|
|
334
334
|
const tam = size ?? campo?.size ?? "md";
|
|
335
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;
|
|
336
339
|
// A marca é METADE da altura do controle — `aurea.css:871`, e é o que faz ela crescer junto
|
|
337
340
|
// com a densidade sem token próprio.
|
|
338
341
|
const lado = alturaDoTamanho(t, tam) / 2;
|
|
339
|
-
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: [
|
|
340
347
|
s.marcaBase,
|
|
341
348
|
{ width: lado, height: lado,
|
|
342
349
|
borderRadius: papel === "radio" ? t.size.radiusFull : 5 },
|
|
@@ -355,8 +362,8 @@ function ControleMarcado({ papel, label, description, checked, onChange, disable
|
|
|
355
362
|
borderLeftWidth: 2, borderBottomWidth: 2,
|
|
356
363
|
borderColor: t.color.controlSelectedForeground,
|
|
357
364
|
transform: [{ rotate: "-45deg" }],
|
|
358
|
-
} })) }), _jsxs(View, { style: { flex: 1 }, children: [typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label, description != null && (typeof description === "string"
|
|
359
|
-
? _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)] }))] }));
|
|
360
367
|
}
|
|
361
368
|
/** A caixa que marca. `accessibilityRole="checkbox"` + `state.checked`. */
|
|
362
369
|
export function Checkbox(p) { return _jsx(ControleMarcado, { ...p, papel: "checkbox" }); }
|
|
@@ -378,13 +385,14 @@ export function Radio(p) { return _jsx(ControleMarcado, { ...p, papel: "radio" }
|
|
|
378
385
|
* ⚠ **E ele respeita "menos movimento"**: com a preferência ligada, o polegar salta em vez de
|
|
379
386
|
* deslizar. O estado continua o mesmo — o que some é a animação, não a informação.
|
|
380
387
|
*/
|
|
381
|
-
export function Switch({ label, description, checked, onChange, disabled, size, style, testID, }) {
|
|
388
|
+
export function Switch({ label, description, checked, onChange, disabled, size, style, testID, accessibilityLabel, }) {
|
|
382
389
|
const t = useAureaTokens();
|
|
383
390
|
const s = folha(t);
|
|
384
391
|
const campo = useCampo();
|
|
385
392
|
const tam = size ?? campo?.size ?? "md";
|
|
386
393
|
const inativo = disabled ?? campo?.disabled;
|
|
387
394
|
const reduzir = useReduceMotion();
|
|
395
|
+
const temTexto = label != null || description != null;
|
|
388
396
|
const h = alturaDoTamanho(t, tam);
|
|
389
397
|
const larguraDoTrilho = h * 7 / 6;
|
|
390
398
|
const alturaDoTrilho = h * 2 / 3;
|
|
@@ -403,10 +411,28 @@ export function Switch({ label, description, checked, onChange, disabled, size,
|
|
|
403
411
|
a.start();
|
|
404
412
|
return () => a.stop();
|
|
405
413
|
}, [checked, pos, reduzir]);
|
|
406
|
-
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),
|
|
407
417
|
// Mesma regra do `ControleMarcado`: a descrição vira DICA quando é texto. Sem isto ela
|
|
408
418
|
// desenha na tela e não existe para quem usa leitor de tela.
|
|
409
|
-
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: [
|
|
410
436
|
s.trilho,
|
|
411
437
|
{ width: larguraDoTrilho, height: alturaDoTrilho },
|
|
412
438
|
checked && { backgroundColor: t.color.controlSelected, borderColor: t.color.controlSelected },
|
|
@@ -415,8 +441,8 @@ export function Switch({ label, description, checked, onChange, disabled, size,
|
|
|
415
441
|
{ width: ladoDoPolegar, height: ladoDoPolegar,
|
|
416
442
|
backgroundColor: checked ? t.color.controlSelectedForeground : t.color.mutedForeground,
|
|
417
443
|
transform: [{ translateX: pos.interpolate({ inputRange: [0, 1], outputRange: [0, curso] }) }] },
|
|
418
|
-
] }) }), _jsxs(View, { style: { flex: 1 }, children: [typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label, description != null && (typeof description === "string"
|
|
419
|
-
? _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)] }))] }));
|
|
420
446
|
}
|
|
421
447
|
/**
|
|
422
448
|
* Um de poucos, lado a lado.
|
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.
|
package/dist/navigation.d.ts
CHANGED
|
@@ -33,31 +33,66 @@ export interface BottomNavProps extends ViewProps {
|
|
|
33
33
|
* ]} />
|
|
34
34
|
* ```
|
|
35
35
|
*
|
|
36
|
-
* ⚠ **Ela respeita a
|
|
37
|
-
* `
|
|
38
|
-
*
|
|
39
|
-
*
|
|
36
|
+
* ⚠ **Ela respeita a área do sistema sozinha, e desde 19/09/2026 POR FORA da pílula.** No
|
|
37
|
+
* `floating` a folga vira `marginBottom`: a borda de baixo fica exatamente `space4` acima do topo
|
|
38
|
+
* da área do sistema, seja ela a faixa dos três botões do Android (≈48), a linha de gesto (≈16)
|
|
39
|
+
* ou o indicador do iPhone. No `edge`, que encosta na borda, ela continua sendo recheio por
|
|
40
|
+
* dentro — igual à web, onde `.bottom-nav-edge` faz exatamente isso.
|
|
40
41
|
*
|
|
41
42
|
* ⚠ **Não é `Tabs`, e isso é decisão registrada**, não esquecimento — ver o bloco acima.
|
|
42
43
|
*
|
|
43
|
-
*
|
|
44
|
-
* barra é IRMÃ da tela, não filha: é assim que se põe uma barra fixa no RN. Só que o `Screen`
|
|
45
|
-
* pinta o tema **na própria raiz**, então o que aparece atrás desta barra é a raiz do SEU app —
|
|
46
|
-
* e raiz sem cor, no Android, é **BRANCA**. Num app de tema escuro isso é uma faixa branca no pé
|
|
47
|
-
* da tela. Envolva os dois num `View` com `flex: 1` e `backgroundColor: tokens.color.background`:
|
|
44
|
+
* ── COMO MONTAR AS ABAS, com `expo-router` ou `react-navigation` ─────────────────────────────
|
|
48
45
|
*
|
|
49
46
|
* ```tsx
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
47
|
+
* import {Tabs} from "expo-router";
|
|
48
|
+
* import {BottomNav, BottomNavProvider} from "@aurea-uds/native";
|
|
49
|
+
*
|
|
50
|
+
* const ROTAS = [
|
|
51
|
+
* {name: "index", titulo: "Início", icone: "home"},
|
|
52
|
+
* {name: "perfil", titulo: "Perfil", icone: "user"},
|
|
53
|
+
* ];
|
|
54
|
+
*
|
|
55
|
+
* <BottomNavProvider>
|
|
56
|
+
* <Tabs
|
|
57
|
+
* screenOptions={{headerShown: false}}
|
|
58
|
+
* tabBar={({state, navigation}) => (
|
|
59
|
+
* <BottomNav
|
|
60
|
+
* label="Menu principal"
|
|
61
|
+
* current={state.routes[state.index].name}
|
|
62
|
+
* items={ROTAS.map((r) => ({
|
|
63
|
+
* id: r.name, label: r.titulo, icon: r.icone,
|
|
64
|
+
* onPress: () => navigation.navigate(r.name),
|
|
65
|
+
* }))} />
|
|
66
|
+
* )} />
|
|
67
|
+
* </BottomNavProvider>
|
|
55
68
|
* ```
|
|
56
69
|
*
|
|
70
|
+
* E as telas, sem conta nenhuma de respiro: `<Screen scroll edges={["top"]}>`.
|
|
71
|
+
*
|
|
72
|
+
* 🔴 **O `BottomNav` VAI SOLTO NO `tabBar`, SEM `View` EM VOLTA — e esta linha já mandou o
|
|
73
|
+
* contrário.** Até a `0.8.7` a documentação aqui mandava embrulhar a barra num `View` pintado com
|
|
74
|
+
* o fundo do tema, porque a barra ficava no FLUXO e o que aparecia atrás dela era a raiz do app,
|
|
75
|
+
* que no Android é branca. O consumidor seguiu, e o resultado em aparelho foi uma **caixa cinza
|
|
76
|
+
* em volta da pílula**: ela não flutuava, e a tela terminava acima dela.
|
|
77
|
+
*
|
|
78
|
+
* ✅ **Hoje o `floating` é `position: absolute`** — ele sai do fluxo e passa por cima da tela,
|
|
79
|
+
* que volta a ocupar a altura inteira. Não há nada atrás da pílula para pintar, então **o
|
|
80
|
+
* embrulho não é mais necessário e atrapalha**: ele é quem desenha a faixa.
|
|
81
|
+
*
|
|
82
|
+
* ⚠ **O `edge` continua no fluxo**, e para ELE o conselho antigo continua valendo: embrulhe os
|
|
83
|
+
* dois num `View` com `flex: 1` e `backgroundColor: t.color.background`, senão sobra a faixa
|
|
84
|
+
* branca do Android no pé da tela.
|
|
85
|
+
*
|
|
57
86
|
* ⚠ **E o sintoma engana.** Visto em foto no aparelho em 10/09/2026: com a faixa branca a barra
|
|
58
87
|
* *parece* flutuando longe do pé, que é exatamente o sinal de que o respiro virou SOMA em vez de
|
|
59
88
|
* `Math.max`. A conta estava certa; o branco é que empurrava a leitura. **Antes de acusar a
|
|
60
89
|
* aritmética, pinte o fundo.**
|
|
90
|
+
*
|
|
91
|
+
* 🔴 **FLUTUAR TEM UM PREÇO, E A BIBLIOTECA É QUEM PAGA:** o fim da rolagem fica atrás da pílula.
|
|
92
|
+
* É para isso que o `BottomNavProvider` existe — a barra mede a própria altura e conta para ele,
|
|
93
|
+
* e o `Screen scroll` reserva o espaço sozinho. **O app não calcula nada.** Para uma tela que não
|
|
94
|
+
* usa `Screen` (uma fileira de botões presa no pé, por exemplo), o número está em
|
|
95
|
+
* `useBottomNavSpace()`. Fora de um provedor os dois valem zero, e nada muda.
|
|
61
96
|
*/
|
|
62
97
|
export declare function BottomNav({ items, current, variant, indicator, label, style, ...rest }: BottomNavProps): React.JSX.Element;
|
|
63
98
|
export type AureaTopbarVariant = "floating" | "flush" | "pill";
|
package/dist/navigation.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Pressable, View } from "react-native";
|
|
3
3
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
4
|
+
import { useAnotarBottomNav } from "./barranav.js";
|
|
4
5
|
import { Badge } from "./display.js";
|
|
5
6
|
import { comOpacidade, criarFolha } from "./estilos.js";
|
|
6
7
|
import { Icon } from "./icon.js";
|
|
@@ -32,14 +33,28 @@ const folha = criarFolha((t) => ({
|
|
|
32
33
|
borderRadius: t.size.radiusControl, backgroundColor: t.color.card,
|
|
33
34
|
},
|
|
34
35
|
// ── BottomNav ──────────────────────────────────────────────────────────────────────────────
|
|
36
|
+
// 🔴 `floating` FLUTUA POR CIMA DA TELA, e isto é conserto de 19/09/2026 — a variante prometia
|
|
37
|
+
// isso no nome e não fazia. `position: absolute` a tira do fluxo: a tela passa a ocupar a altura
|
|
38
|
+
// inteira e rola POR BAIXO da pílula. É o mesmo que a web faz desde sempre com
|
|
39
|
+
// `position: sticky; bottom: 0` (`aurea.css:270`) — a divergência era só deste lado.
|
|
40
|
+
//
|
|
41
|
+
// ⚠ **As margens são o que deixa o toque passar.** Com `left/right/bottom: 0` e `margin`, a
|
|
42
|
+
// caixa deste `View` É a pílula: o que sobra em volta não pertence a ela, e o dedo encontra a
|
|
43
|
+
// tela de baixo. Se a barra fosse uma faixa de largura cheia com a pílula centralizada dentro,
|
|
44
|
+
// a faixa comeria o toque em silêncio.
|
|
45
|
+
//
|
|
46
|
+
// `zSticky` é token (20), o mesmo `--z-sticky` que a web usa na mesma peça.
|
|
35
47
|
barra: {
|
|
48
|
+
position: "absolute", left: 0, right: 0, bottom: 0, zIndex: t.size.zSticky,
|
|
36
49
|
flexDirection: "row", gap: t.size.space1, margin: t.size.space4, padding: t.size.space1,
|
|
37
50
|
borderWidth: t.size.borderWidth, borderColor: t.color.border,
|
|
38
51
|
borderRadius: t.size.radiusControl, backgroundColor: t.color.card,
|
|
39
52
|
boxShadow: [t.shadow.shadowMd],
|
|
40
53
|
},
|
|
41
|
-
// `edge` encosta na borda: sem margem, sem raio, só a linha de cima
|
|
54
|
+
// `edge` encosta na borda: sem margem, sem raio, só a linha de cima — e continua NO FLUXO,
|
|
55
|
+
// porque uma barra encostada que flutuasse esconderia o fim da tela sem nada reservar.
|
|
42
56
|
barra_edge: {
|
|
57
|
+
position: "relative", left: undefined, right: undefined, bottom: undefined, zIndex: undefined,
|
|
43
58
|
margin: 0, paddingHorizontal: 0, borderRadius: 0, backgroundColor: t.color.background,
|
|
44
59
|
borderWidth: 0, borderTopWidth: t.size.borderWidth, boxShadow: undefined,
|
|
45
60
|
},
|
|
@@ -112,31 +127,66 @@ const folha = criarFolha((t) => ({
|
|
|
112
127
|
* ]} />
|
|
113
128
|
* ```
|
|
114
129
|
*
|
|
115
|
-
* ⚠ **Ela respeita a
|
|
116
|
-
* `
|
|
117
|
-
*
|
|
118
|
-
*
|
|
130
|
+
* ⚠ **Ela respeita a área do sistema sozinha, e desde 19/09/2026 POR FORA da pílula.** No
|
|
131
|
+
* `floating` a folga vira `marginBottom`: a borda de baixo fica exatamente `space4` acima do topo
|
|
132
|
+
* da área do sistema, seja ela a faixa dos três botões do Android (≈48), a linha de gesto (≈16)
|
|
133
|
+
* ou o indicador do iPhone. No `edge`, que encosta na borda, ela continua sendo recheio por
|
|
134
|
+
* dentro — igual à web, onde `.bottom-nav-edge` faz exatamente isso.
|
|
119
135
|
*
|
|
120
136
|
* ⚠ **Não é `Tabs`, e isso é decisão registrada**, não esquecimento — ver o bloco acima.
|
|
121
137
|
*
|
|
122
|
-
*
|
|
123
|
-
* barra é IRMÃ da tela, não filha: é assim que se põe uma barra fixa no RN. Só que o `Screen`
|
|
124
|
-
* pinta o tema **na própria raiz**, então o que aparece atrás desta barra é a raiz do SEU app —
|
|
125
|
-
* e raiz sem cor, no Android, é **BRANCA**. Num app de tema escuro isso é uma faixa branca no pé
|
|
126
|
-
* da tela. Envolva os dois num `View` com `flex: 1` e `backgroundColor: tokens.color.background`:
|
|
138
|
+
* ── COMO MONTAR AS ABAS, com `expo-router` ou `react-navigation` ─────────────────────────────
|
|
127
139
|
*
|
|
128
140
|
* ```tsx
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
141
|
+
* import {Tabs} from "expo-router";
|
|
142
|
+
* import {BottomNav, BottomNavProvider} from "@aurea-uds/native";
|
|
143
|
+
*
|
|
144
|
+
* const ROTAS = [
|
|
145
|
+
* {name: "index", titulo: "Início", icone: "home"},
|
|
146
|
+
* {name: "perfil", titulo: "Perfil", icone: "user"},
|
|
147
|
+
* ];
|
|
148
|
+
*
|
|
149
|
+
* <BottomNavProvider>
|
|
150
|
+
* <Tabs
|
|
151
|
+
* screenOptions={{headerShown: false}}
|
|
152
|
+
* tabBar={({state, navigation}) => (
|
|
153
|
+
* <BottomNav
|
|
154
|
+
* label="Menu principal"
|
|
155
|
+
* current={state.routes[state.index].name}
|
|
156
|
+
* items={ROTAS.map((r) => ({
|
|
157
|
+
* id: r.name, label: r.titulo, icon: r.icone,
|
|
158
|
+
* onPress: () => navigation.navigate(r.name),
|
|
159
|
+
* }))} />
|
|
160
|
+
* )} />
|
|
161
|
+
* </BottomNavProvider>
|
|
134
162
|
* ```
|
|
135
163
|
*
|
|
164
|
+
* E as telas, sem conta nenhuma de respiro: `<Screen scroll edges={["top"]}>`.
|
|
165
|
+
*
|
|
166
|
+
* 🔴 **O `BottomNav` VAI SOLTO NO `tabBar`, SEM `View` EM VOLTA — e esta linha já mandou o
|
|
167
|
+
* contrário.** Até a `0.8.7` a documentação aqui mandava embrulhar a barra num `View` pintado com
|
|
168
|
+
* o fundo do tema, porque a barra ficava no FLUXO e o que aparecia atrás dela era a raiz do app,
|
|
169
|
+
* que no Android é branca. O consumidor seguiu, e o resultado em aparelho foi uma **caixa cinza
|
|
170
|
+
* em volta da pílula**: ela não flutuava, e a tela terminava acima dela.
|
|
171
|
+
*
|
|
172
|
+
* ✅ **Hoje o `floating` é `position: absolute`** — ele sai do fluxo e passa por cima da tela,
|
|
173
|
+
* que volta a ocupar a altura inteira. Não há nada atrás da pílula para pintar, então **o
|
|
174
|
+
* embrulho não é mais necessário e atrapalha**: ele é quem desenha a faixa.
|
|
175
|
+
*
|
|
176
|
+
* ⚠ **O `edge` continua no fluxo**, e para ELE o conselho antigo continua valendo: embrulhe os
|
|
177
|
+
* dois num `View` com `flex: 1` e `backgroundColor: t.color.background`, senão sobra a faixa
|
|
178
|
+
* branca do Android no pé da tela.
|
|
179
|
+
*
|
|
136
180
|
* ⚠ **E o sintoma engana.** Visto em foto no aparelho em 10/09/2026: com a faixa branca a barra
|
|
137
181
|
* *parece* flutuando longe do pé, que é exatamente o sinal de que o respiro virou SOMA em vez de
|
|
138
182
|
* `Math.max`. A conta estava certa; o branco é que empurrava a leitura. **Antes de acusar a
|
|
139
183
|
* aritmética, pinte o fundo.**
|
|
184
|
+
*
|
|
185
|
+
* 🔴 **FLUTUAR TEM UM PREÇO, E A BIBLIOTECA É QUEM PAGA:** o fim da rolagem fica atrás da pílula.
|
|
186
|
+
* É para isso que o `BottomNavProvider` existe — a barra mede a própria altura e conta para ele,
|
|
187
|
+
* e o `Screen scroll` reserva o espaço sozinho. **O app não calcula nada.** Para uma tela que não
|
|
188
|
+
* usa `Screen` (uma fileira de botões presa no pé, por exemplo), o número está em
|
|
189
|
+
* `useBottomNavSpace()`. Fora de um provedor os dois valem zero, e nada muda.
|
|
140
190
|
*/
|
|
141
191
|
export function BottomNav({ items, current, variant = "floating", indicator = "none", label, style, ...rest }) {
|
|
142
192
|
const t = useAureaTokens();
|
|
@@ -144,11 +194,29 @@ export function BottomNav({ items, current, variant = "floating", indicator = "n
|
|
|
144
194
|
const strings = useAureaStrings();
|
|
145
195
|
const inset = useSafeAreaInsets();
|
|
146
196
|
const redondo = indicator.startsWith("circle");
|
|
147
|
-
|
|
197
|
+
const anotar = useAnotarBottomNav();
|
|
198
|
+
const flutua = variant !== "edge";
|
|
199
|
+
return (_jsx(View, { accessibilityLabel: label ?? strings.bottomNavLabel,
|
|
200
|
+
// 🔴 A BARRA CONTA A PRÓPRIA ALTURA — é o que permite o `Screen` reservar o fim da rolagem
|
|
201
|
+
// sem o app calcular nada. A conta é a pílula (o que o layout mede) mais as DUAS margens
|
|
202
|
+
// mais a folga do sistema; `edge` conta zero, porque ela fica no fluxo e já encurta a tela.
|
|
203
|
+
onLayout: anotar == null ? undefined : (e) => anotar(flutua ? e.nativeEvent.layout.height + t.size.space4 * 2 + inset.bottom : 0), style: [
|
|
148
204
|
s.barra, variant === "edge" && s.barra_edge,
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
|
|
205
|
+
// 🔴 NO `floating` A FOLGA DO SISTEMA VAI POR FORA — conserto de 19/09/2026, visto em
|
|
206
|
+
// aparelho Samsung com os três botões (|||, ○, <).
|
|
207
|
+
//
|
|
208
|
+
// Antes, os ≈48 pontos da faixa do sistema entravam como RECHEIO dentro da pílula: ela
|
|
209
|
+
// crescia para baixo e a barriga dela ficava atrás dos botões. A `margin: space4` não
|
|
210
|
+
// compensava, porque o que cresceu foi o miolo.
|
|
211
|
+
//
|
|
212
|
+
// Agora: recheio de baixo igual aos outros lados, e a folga empurra a pílula para cima.
|
|
213
|
+
// A borda de baixo fica exatamente `space4` acima do topo da área do sistema, em aparelho
|
|
214
|
+
// de botões, de gesto ou com o indicador do iPhone — os três caem na mesma conta.
|
|
215
|
+
flutua
|
|
216
|
+
? { paddingBottom: t.size.space1, marginBottom: t.size.space4 + inset.bottom }
|
|
217
|
+
// `edge` NÃO MUDA: encostada na borda, a folga é recheio mesmo, senão sobraria uma
|
|
218
|
+
// faixa de fundo entre a barra e o pé da tela. `max` e não soma, como sempre foi.
|
|
219
|
+
: { paddingBottom: Math.max(t.size.space1, inset.bottom) },
|
|
152
220
|
style,
|
|
153
221
|
], ...rest, children: items.map((it) => {
|
|
154
222
|
const ativo = it.id === current;
|
package/dist/numero.d.ts
CHANGED
|
@@ -103,6 +103,17 @@ type IconNameLocal = string;
|
|
|
103
103
|
*
|
|
104
104
|
* ⚠ **Formata no blur, e só no blur.** Enquanto o campo tem foco, ele mostra exatamente o que foi
|
|
105
105
|
* digitado — essa é a decisão inteira da ADR-0024, e o teste que a cobra está no lote.
|
|
106
|
+
*
|
|
107
|
+
* ⚠ **`onValueChange` dispara a CADA TECLA**, com o número lido pelas regras do `locale` — "12,"
|
|
108
|
+
* em pt-BR entrega `12`, "1.234,5" entrega `1234.5`, apagar tudo entrega `null`. Texto que ainda
|
|
109
|
+
* não é número (só "," ou só "-") não dispara nada. **`min`/`max` NÃO prendem durante a
|
|
110
|
+
* digitação**, só no blur — e se prender mudar o número, ele é emitido de novo.
|
|
111
|
+
*
|
|
112
|
+
* ⚠ **Com FOCO, quem manda é quem digita.** Se o pai trocar o `value` por outro número enquanto o
|
|
113
|
+
* campo está sendo editado, o texto na tela **não muda** — inclusive quando o pai só devolve o
|
|
114
|
+
* mesmo número que acabou de receber, que é o caso comum de um `useState` controlado. Sem foco, o
|
|
115
|
+
* campo obedece ao `value` e mostra o número novo formatado, que é o que faz um campo calculado a
|
|
116
|
+
* partir de outros dois funcionar.
|
|
106
117
|
*/
|
|
107
118
|
export declare function NumberField({ value, defaultValue, onValueChange, min, max, step, format, locale, disabled, readOnly, size, fullWidth, label, placeholder, keyboardType, icons, style, testID, }: NumberFieldProps): React.JSX.Element;
|
|
108
119
|
export {};
|
package/dist/numero.js
CHANGED
|
@@ -231,6 +231,17 @@ const paraEdicao = (n, locale) => {
|
|
|
231
231
|
*
|
|
232
232
|
* ⚠ **Formata no blur, e só no blur.** Enquanto o campo tem foco, ele mostra exatamente o que foi
|
|
233
233
|
* digitado — essa é a decisão inteira da ADR-0024, e o teste que a cobra está no lote.
|
|
234
|
+
*
|
|
235
|
+
* ⚠ **`onValueChange` dispara a CADA TECLA**, com o número lido pelas regras do `locale` — "12,"
|
|
236
|
+
* em pt-BR entrega `12`, "1.234,5" entrega `1234.5`, apagar tudo entrega `null`. Texto que ainda
|
|
237
|
+
* não é número (só "," ou só "-") não dispara nada. **`min`/`max` NÃO prendem durante a
|
|
238
|
+
* digitação**, só no blur — e se prender mudar o número, ele é emitido de novo.
|
|
239
|
+
*
|
|
240
|
+
* ⚠ **Com FOCO, quem manda é quem digita.** Se o pai trocar o `value` por outro número enquanto o
|
|
241
|
+
* campo está sendo editado, o texto na tela **não muda** — inclusive quando o pai só devolve o
|
|
242
|
+
* mesmo número que acabou de receber, que é o caso comum de um `useState` controlado. Sem foco, o
|
|
243
|
+
* campo obedece ao `value` e mostra o número novo formatado, que é o que faz um campo calculado a
|
|
244
|
+
* partir de outros dois funcionar.
|
|
234
245
|
*/
|
|
235
246
|
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
247
|
const t = useAureaTokens();
|
|
@@ -277,8 +288,41 @@ export function NumberField({ value, defaultValue, onValueChange, min, max, step
|
|
|
277
288
|
emitir(null);
|
|
278
289
|
return;
|
|
279
290
|
}
|
|
291
|
+
// ⚠ **Só aqui é que `min`/`max` prendem**, e é no blur de propósito — ver `digitar` abaixo.
|
|
292
|
+
// Se prender mudou o número, o pai recebe o preso: quem ouviu "1" durante a digitação precisa
|
|
293
|
+
// ouvir o "10" final, senão fica com um valor que o campo não mostra mais.
|
|
280
294
|
emitir(prender(lido, min, max));
|
|
281
295
|
}, [emEdicao, locale, emitir, min, max]);
|
|
296
|
+
// 🔴 CADA TECLA ENTREGA O NÚMERO — e isto é conserto de defeito que GRAVA DADO ERRADO, não
|
|
297
|
+
// refinamento. Achado pelo consumidor em aparelho, 19/09/2026.
|
|
298
|
+
//
|
|
299
|
+
// Antes, `onChangeText` só guardava o texto aqui dentro e o pai só era avisado no blur. Num
|
|
300
|
+
// cartão com o campo e o botão de confirmar lado a lado, o `Screen` tem
|
|
301
|
+
// `keyboardShouldPersistTaps="handled"` (`screen.tsx:154`) — então o toque no botão CHEGA ao
|
|
302
|
+
// botão sem passar pelo blur. O `onPress` roda com o valor ANTIGO.
|
|
303
|
+
//
|
|
304
|
+
// ⚠ **A cara disso na tela é "o campo está vazio"**, e o dano real é maior e mudo: num
|
|
305
|
+
// formulário que corrige um número já salvo, tocar em Salvar com o teclado aberto regrava o
|
|
306
|
+
// número velho, sem erro e sem aviso.
|
|
307
|
+
//
|
|
308
|
+
// O que NÃO muda com isto, e é a ADR-0024 inteira: o campo continua mostrando o que foi
|
|
309
|
+
// digitado enquanto tem foco (`emEdicao` é quem manda em `mostrar`), e continua formatando só
|
|
310
|
+
// no blur. **Digitar nunca é interrompido** — nem pelo formato, nem pelo eco do pai: enquanto
|
|
311
|
+
// `emEdicao` não é `null`, nada que venha por `value` reescreve o texto.
|
|
312
|
+
const digitar = React.useCallback((texto) => {
|
|
313
|
+
setEmEdicao(texto);
|
|
314
|
+
const lido = lerNumero(texto, locale);
|
|
315
|
+
// Apagar tudo é VAZIO, que é diferente de ilegível: o vazio é uma intenção e vira `null`.
|
|
316
|
+
if (lido == null) {
|
|
317
|
+
if (texto.trim() === "")
|
|
318
|
+
emitir(null);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
// ⚠ **NÃO prende aqui.** Com `min={10}`, prender durante a digitação faria o "1" virar 10 na
|
|
322
|
+
// cara de quem ainda ia digitar o "5" de 15 — o campo escreveria por cima da pessoa. Prender
|
|
323
|
+
// é do blur, e só.
|
|
324
|
+
emitir(lido);
|
|
325
|
+
}, [locale, emitir]);
|
|
282
326
|
const empurrar = React.useCallback((direcao) => {
|
|
283
327
|
// Se o campo está sendo editado, o que vale é o que está escrito — empurrar por cima do valor
|
|
284
328
|
// antigo descartaria a digitação em silêncio.
|
|
@@ -311,7 +355,7 @@ export function NumberField({ value, defaultValue, onValueChange, min, max, step
|
|
|
311
355
|
// (`aurea.css:685`), com os dois botões FORA dela. O estado que faltava é o deles; a geometria
|
|
312
356
|
// continua sendo a nossa.
|
|
313
357
|
const [focado, setFocado] = React.useState(false);
|
|
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:
|
|
358
|
+
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: digitar, 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,
|
|
315
359
|
// O nome vem do `Field`, como em todo controle deste pacote — e `label` cobre quem usa o
|
|
316
360
|
// campo solto. Sem um dos dois, é um nó anônimo para o leitor de tela.
|
|
317
361
|
accessibilityLabel: label ?? campo?.label, accessibilityHint: campo?.hint, accessibilityState: { disabled: !!inativo }, "aria-invalid": campo?.invalido, style: [
|
package/dist/screen.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { RefreshControl, ScrollView } from "react-native";
|
|
3
3
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
4
|
+
import { useBottomNavSpace } from "./barranav.js";
|
|
4
5
|
import { criarFolha } from "./estilos.js";
|
|
5
6
|
import { useAureaTokens } from "./theme.js";
|
|
6
7
|
const folha = criarFolha((t) => ({
|
|
@@ -43,7 +44,18 @@ export function Screen({ edges = TODAS_AS_BORDAS, padded = true, scroll = false,
|
|
|
43
44
|
const t = useAureaTokens();
|
|
44
45
|
const s = folha(t);
|
|
45
46
|
const fundo = s[`fundo_${background}`];
|
|
46
|
-
|
|
47
|
+
// 🔴 O ESPAÇO DA BARRA DE ABAS, quando ela flutua por cima desta tela — ver `barranav.tsx`.
|
|
48
|
+
// **Zero** fora de um `BottomNavProvider`, que é o caso de toda tela cheia: sem abas, nada muda.
|
|
49
|
+
const respiroDaBarra = useBottomNavSpace();
|
|
50
|
+
return (_jsx(SafeAreaView, { style: [s.raiz, fundo, !scroll && padded && s.respiro, style], edges: edges, ...rest, children: scroll ? (_jsx(ScrollView, { style: s.raiz,
|
|
51
|
+
// ⚠ O respiro da barra SOMA ao do tema em vez de substituí-lo: escrever
|
|
52
|
+
// `paddingBottom` depois de `s.respiro` apagaria os 16 do tema e colaria o último
|
|
53
|
+
// item na pílula. A conta é explícita porque a folha não soma sozinha.
|
|
54
|
+
contentContainerStyle: [
|
|
55
|
+
s.conteudo, padded && s.respiro,
|
|
56
|
+
respiroDaBarra > 0
|
|
57
|
+
&& { paddingBottom: (padded ? t.size.space4 : 0) + respiroDaBarra },
|
|
58
|
+
],
|
|
47
59
|
// A rosca do sistema é a única aparência da plataforma que a Aurea aceita aqui, e a
|
|
48
60
|
// razão é que ela NÃO É desenho nosso: é o gesto do sistema operacional, com a física
|
|
49
61
|
// que a pessoa já conhece. O que dá para pintar, pintamos — a cor é do tema.
|
package/dist/text.js
CHANGED
|
@@ -23,6 +23,36 @@ import * as React from "react";
|
|
|
23
23
|
import { Text as TextRN } from "react-native";
|
|
24
24
|
import { criarFolha } from "./estilos.js";
|
|
25
25
|
import { useAureaTokens, useSobreAMarca } from "./theme.js";
|
|
26
|
+
// 🔴 A ESCALA TEM CINCO DEGRAUS QUE SE ENXERGAM, E DEZ NOMES — ordem do Victor, 19/09/2026,
|
|
27
|
+
// olhando o app no aparelho: *"o tamanho das fontes: existem variações demais, fica muito
|
|
28
|
+
// estranho"*.
|
|
29
|
+
//
|
|
30
|
+
// Medido no token antes de mexer: **12 · 13 · 14 · 16** eram quatro degraus em quatro pontos.
|
|
31
|
+
// Lado a lado, isso não lê como hierarquia — lê como erro. Uma tela só (a de Início) mostrava de
|
|
32
|
+
// 12 a 36 em SETE tamanhos.
|
|
33
|
+
//
|
|
34
|
+
// A escala nova sobe 25% por degrau, que é o mínimo para dois vizinhos serem distinguíveis a olho
|
|
35
|
+
// nu, e **nada encolhe** — o que importa, porque o Victor tem baixa visão:
|
|
36
|
+
//
|
|
37
|
+
// legenda, apoio .............. 13 (era 12)
|
|
38
|
+
// texto corrido, controle ..... 16 (era 13 e 14)
|
|
39
|
+
// nome de cartão .............. 20 (era 16 e 18)
|
|
40
|
+
// título de tela .............. 25 (era 20 e 24)
|
|
41
|
+
// número em destaque .......... 31 (era 30)
|
|
42
|
+
//
|
|
43
|
+
// ⚠ **Os DEZ nomes continuam existindo, e alguns apontam para o mesmo número** — `sm`, `md` e
|
|
44
|
+
// `base` são 16; `lg` e `xl` são 20. Isso é de propósito: `size` é API pública dos dois alvos, e
|
|
45
|
+
// apagar nome quebraria consumidor sem ganhar nada. O que encolheu foi a lista de VALORES, que é
|
|
46
|
+
// o que a pessoa vê. Por isso nenhum componente do pacote precisou trocar de degrau: os degraus é
|
|
47
|
+
// que se juntaram.
|
|
48
|
+
//
|
|
49
|
+
// ⚠ **O topo (36 e 48) não foi tocado.** A queixa é do pé da escala, e ali as razões já eram boas
|
|
50
|
+
// (1,2 e 1,33). Mexer sem queixa é inventar trabalho.
|
|
51
|
+
//
|
|
52
|
+
// ⚠ **O limite, medido e declarado:** com a letra do sistema em 1,3× o corpo de 16 vira ~21 e a
|
|
53
|
+
// linha ~29. Isso cabe em toda cápsula, MENOS o controle `sm` na densidade compacta (28 de
|
|
54
|
+
// altura), que estoura por 1 ponto. Em 1,5× vários estouram — e isso já era verdade na escala
|
|
55
|
+
// antiga.
|
|
26
56
|
const TAMANHO = {
|
|
27
57
|
xs: "textXs", sm: "textSm", md: "textMd", base: "textBase", lg: "textLg",
|
|
28
58
|
xl: "textXl", "2xl": "text2xl", "3xl": "text3xl", "4xl": "text4xl", "5xl": "text5xl",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurea-uds/native",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.10",
|
|
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.10"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@react-native-community/datetimepicker": ">=8",
|