@aurea-uds/native 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions.d.ts +15 -1
- package/dist/actions.js +40 -17
- package/dist/busca.js +16 -14
- package/dist/display.js +17 -3
- package/dist/escolha.d.ts +121 -0
- package/dist/escolha.js +466 -0
- package/dist/exibicao.d.ts +188 -0
- package/dist/exibicao.js +457 -0
- package/dist/feedback.d.ts +48 -4
- package/dist/feedback.js +92 -28
- package/dist/icon-names.d.ts +7 -1
- package/dist/icon-names.js +2 -0
- package/dist/icon.d.ts +6 -4
- package/dist/icon.js +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.js +10 -1
- package/dist/inputs.d.ts +12 -3
- package/dist/inputs.js +71 -25
- package/dist/layout.d.ts +1 -1
- package/dist/layout.js +3 -0
- package/dist/menu.d.ts +99 -0
- package/dist/menu.js +452 -0
- package/dist/navigation.d.ts +1 -1
- package/dist/navigation.js +35 -18
- package/dist/numero.d.ts +2 -2
- package/dist/numero.js +6 -2
- package/dist/overlays.js +35 -9
- package/dist/sistema.js +15 -10
- package/dist/strings.d.ts +4 -0
- package/dist/strings.js +4 -0
- package/dist/text.js +7 -1
- package/dist/toast.js +9 -12
- package/icons/index.d.ts +1 -0
- package/icons/index.js +2 -1
- package/icons/x-bold.d.ts +6 -0
- package/icons/x-bold.js +10 -0
- package/package.json +2 -2
package/dist/exibicao.js
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Aurea nativo — quatro peças que acompanham o conteúdo: `Accordion`, `ScrollShadow`,
|
|
3
|
+
// `SkeletonGroup` e `PressableFeedback` (0.32.0, passo 9b).
|
|
4
|
+
//
|
|
5
|
+
// As quatro são as do pacote nativo da referência — anatomia, medidas, estados, comportamento e
|
|
6
|
+
// acessibilidade lidos no pacote publicado dela —, com o que é da Aurea por cima: as cores do tema,
|
|
7
|
+
// o canto 22 do cartão e a cápsula, a Atkinson, os ícones Phosphor, sem degradê, "menos movimento".
|
|
8
|
+
//
|
|
9
|
+
// Medido no pacote dela antes de escrito (a classe vira dp: `px-3` = 12, `py-4` = 16, `gap-4` = 16):
|
|
10
|
+
//
|
|
11
|
+
// accordion gatilho linha, 16 em cima e embaixo, 12 dos lados (20 no `surface`), 16 entre as partes
|
|
12
|
+
// seta 16, gira de 0 a −180° numa mola (amortecimento 140, rigidez 1000, massa 4)
|
|
13
|
+
// resposta 12 dos lados (20 no `surface`), 16 embaixo; entra esmaecendo em 200 ms
|
|
14
|
+
// fio entre os itens, nenhum depois do último; no `surface`, 12 de recuo dos lados
|
|
15
|
+
// surface o cartão: fundo, canto 3xl (aqui o 22 da Aurea) e a sombra de superfície
|
|
16
|
+
// scroll-shadow um degradê de 50 nas pontas — TROCADO por um fio (decisão do Victor, abaixo)
|
|
17
|
+
// skeleton-group um provedor: `isLoading` e a animação para os esqueletos de dentro
|
|
18
|
+
// pressable-feedback escala 0,985 numa peça de 300 de largura (proporcional), 300 ms
|
|
19
|
+
// realce: camada cheia que vai a 10% em 200 ms
|
|
20
|
+
// onda: círculo de 1,25 × a diagonal que nasce no dedo, de 750 a 2000 ms
|
|
21
|
+
//
|
|
22
|
+
// ⚠ O QUE NÃO VEIO, e por quê:
|
|
23
|
+
// • a mola de ALTURA do accordion (os vizinhos deslizando ao abrir). Altura não roda no motor
|
|
24
|
+
// nativo de animação, e toda animação da Aurea roda nele (`driverNativo()`, E13). A resposta
|
|
25
|
+
// entra esmaecendo, como a dela; os vizinhos descem de uma vez, e fechar é na hora.
|
|
26
|
+
// • o degradê do scroll-shadow e o da onda — a Aurea não tem degradê (CLAUDE.md §5).
|
|
27
|
+
// • `asChild` — o encaixe sem caixa própria dela não existe neste pacote.
|
|
28
|
+
import * as React from "react";
|
|
29
|
+
import { Animated, Easing, Pressable, View, } from "react-native";
|
|
30
|
+
import { canto, comOpacidade, criarFolha, estadoAcessivel, REACAO_AO_TOQUE } from "./estilos.js";
|
|
31
|
+
import { GrupoDeEsqueleto, Skeleton, usePulsoDoEsqueleto } from "./feedback.js";
|
|
32
|
+
import { Icon } from "./icon.js";
|
|
33
|
+
import { driverNativo, useReduceMotion } from "./movimento.js";
|
|
34
|
+
import { Text } from "./text.js";
|
|
35
|
+
import { useAureaTokens } from "./theme.js";
|
|
36
|
+
const folha = criarFolha((t) => ({
|
|
37
|
+
// ── Accordion ──────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
accordion: { flexDirection: "column" },
|
|
39
|
+
// O `surface` da referência: o cartão com a sombra de superfície (a `shadowSm` inteira, como o
|
|
40
|
+
// `Alert`). O canto é o 22 da Aurea, no lugar do 24 dela.
|
|
41
|
+
accordionSurface: { ...canto(t.size.radiusCard), backgroundColor: t.color.card, boxShadow: [...t.shadowLayers.shadowSm] },
|
|
42
|
+
// O gatilho tem o canto da cápsula sempre: é o desenho do realce de quando o dedo está em cima
|
|
43
|
+
// (decisão do Victor para a web, 11/10/2026: *"a seleção é quadrada deles quero nossa como capsula"*).
|
|
44
|
+
gatilho: {
|
|
45
|
+
flexDirection: "row", alignItems: "center", justifyContent: "space-between",
|
|
46
|
+
paddingVertical: t.size.space4, paddingHorizontal: t.size.space3, gap: t.size.space4,
|
|
47
|
+
...canto(t.size.radiusControl),
|
|
48
|
+
},
|
|
49
|
+
gatilhoSurface: { paddingHorizontal: t.size.space5 },
|
|
50
|
+
titulo: { flex: 1, minWidth: 0 },
|
|
51
|
+
seta: { alignItems: "center", justifyContent: "center" },
|
|
52
|
+
resposta: { paddingHorizontal: t.size.space3, paddingBottom: t.size.space4 },
|
|
53
|
+
respostaSurface: { paddingHorizontal: t.size.space5 },
|
|
54
|
+
// O fio dela é o `hairlineWidth` (um pixel do aparelho: 0,33 num telefone de 3×). Aqui é a borda
|
|
55
|
+
// da Aurea, 1 — a mesma linha da web (`--border-width`), e uma que se enxerga.
|
|
56
|
+
fio: { height: t.size.borderWidth, backgroundColor: t.color.border },
|
|
57
|
+
fioSurface: { marginHorizontal: t.size.space3 },
|
|
58
|
+
inativo: { opacity: t.size.opacityDisabled },
|
|
59
|
+
// ── ScrollShadow ───────────────────────────────────────────────────────────────────────────
|
|
60
|
+
// A superfície (o padrão, 12/10/2026): a MESMA pele do `Accordion` no cartão — fundo do cartão, canto
|
|
61
|
+
// 22 e a sombra de superfície —, com o recheio dos lados dele (20) e 12 em cima e embaixo.
|
|
62
|
+
superficieDaLista: { ...canto(t.size.radiusCard), backgroundColor: t.color.card, boxShadow: [...t.shadowLayers.shadowSm],
|
|
63
|
+
paddingHorizontal: t.size.space5, paddingVertical: t.size.space3 },
|
|
64
|
+
// Dentro da superfície a janela da lista já está longe dos cantos: o fio vai de lado a lado dela.
|
|
65
|
+
fioDeCimaNaSuperficie: { left: 0, right: 0 },
|
|
66
|
+
fioDaEsquerdaNaSuperficie: { top: 0, bottom: 0 },
|
|
67
|
+
// Cresce com a caixa: quem dá altura ao `ScrollShadow` (um `flex: 1`) a dá também à lista de dentro.
|
|
68
|
+
miolo: { position: "relative", flexGrow: 1, flexShrink: 1 },
|
|
69
|
+
// No `plain` (sem caixa), o fio NÃO vai de ponta a ponta: recua o raio do cartão (22) dos dois lados e tem as pontas em
|
|
70
|
+
// cápsula (escolha do Victor, 12/10/2026, opção "A"). De ponta a ponta ele desenhava uma quina, que
|
|
71
|
+
// a Aurea não tem; recuado, ele para onde o canto de um cartão em volta começa a curvar.
|
|
72
|
+
fioDeCima: { position: "absolute", top: 0, left: t.size.radiusCard, right: t.size.radiusCard,
|
|
73
|
+
height: t.size.borderWidth, ...canto(t.size.radiusControl), backgroundColor: t.color.border },
|
|
74
|
+
fioDeBaixo: { position: "absolute", bottom: 0, left: t.size.radiusCard, right: t.size.radiusCard,
|
|
75
|
+
height: t.size.borderWidth, ...canto(t.size.radiusControl), backgroundColor: t.color.border },
|
|
76
|
+
fioDaEsquerda: { position: "absolute", left: 0, top: t.size.radiusCard, bottom: t.size.radiusCard,
|
|
77
|
+
width: t.size.borderWidth, ...canto(t.size.radiusControl), backgroundColor: t.color.border },
|
|
78
|
+
fioDaDireita: { position: "absolute", right: 0, top: t.size.radiusCard, bottom: t.size.radiusCard,
|
|
79
|
+
width: t.size.borderWidth, ...canto(t.size.radiusControl), backgroundColor: t.color.border },
|
|
80
|
+
// ── PressableFeedback ──────────────────────────────────────────────────────────────────────
|
|
81
|
+
// `overflow: hidden` é o da raiz dela: o realce e a onda ficam dentro do canto de quem usa.
|
|
82
|
+
raizDoToque: { overflow: "hidden" },
|
|
83
|
+
camada: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 },
|
|
84
|
+
onda: { position: "absolute", top: 0, left: 0 },
|
|
85
|
+
}));
|
|
86
|
+
/**
|
|
87
|
+
* Leva um valor animado ao `destino` quando o destino MUDA — com a animação de `criar`, ou de um
|
|
88
|
+
* salto quando não há movimento (a pessoa pediu menos, o sistema ainda não respondeu). No primeiro
|
|
89
|
+
* desenho não faz nada: o valor já nasce no lugar. E a resposta do sistema chegando depois não
|
|
90
|
+
* dispara animação nenhuma — só a troca de destino dispara.
|
|
91
|
+
*/
|
|
92
|
+
function useLevarA(valor, destino, semMovimento, criar) {
|
|
93
|
+
const ultimo = React.useRef(destino);
|
|
94
|
+
const corrente = React.useRef(null);
|
|
95
|
+
React.useEffect(() => () => corrente.current?.stop(), []);
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
if (ultimo.current === destino)
|
|
98
|
+
return;
|
|
99
|
+
ultimo.current = destino;
|
|
100
|
+
corrente.current?.stop();
|
|
101
|
+
corrente.current = null;
|
|
102
|
+
if (semMovimento) {
|
|
103
|
+
valor.setValue(destino);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const a = criar(destino);
|
|
107
|
+
a.start();
|
|
108
|
+
corrente.current = a;
|
|
109
|
+
// `criar` fica de fora de propósito: ele nasce a cada desenho, e o efeito só tem de rodar na troca.
|
|
110
|
+
}, [valor, destino, semMovimento]);
|
|
111
|
+
}
|
|
112
|
+
/** O `Easing.out(Easing.ease)` da referência, escrito como a curva que ele é: o `ease` do RN é
|
|
113
|
+
* `bezier(0.42, 0, 1, 1)`, e o `out` o espelha. */
|
|
114
|
+
const SAIDA_SUAVE = Easing.bezier(0, 0, 0.58, 1);
|
|
115
|
+
// A mola da seta, a da referência. Passa do amortecimento crítico (2·√(1000·4) ≈ 126), então não
|
|
116
|
+
// balança: assenta em cerca de 0,4 s.
|
|
117
|
+
const MOLA_DA_SETA = { damping: 140, stiffness: 1000, mass: 4 };
|
|
118
|
+
const ESMAECER_DA_RESPOSTA = 200;
|
|
119
|
+
/**
|
|
120
|
+
* Seções que abrem e fecham — o accordion da referência, com a API da web (`items`, `variant`,
|
|
121
|
+
* `multiple`, `defaultValue`).
|
|
122
|
+
*
|
|
123
|
+
* Um de cada vez por padrão, e o aberto fecha no toque (o `isCollapsible` dela, ligado). O
|
|
124
|
+
* gatilho é um botão que diz se está aberto (`expanded`) dentro de um título (`header`), como o
|
|
125
|
+
* dela; a resposta fechada não está na árvore, e o leitor de tela não a lê.
|
|
126
|
+
*/
|
|
127
|
+
export function Accordion({ items, variant = "default", multiple = false, defaultValue, value, onValueChange, style, testID, }) {
|
|
128
|
+
const t = useAureaTokens();
|
|
129
|
+
const s = folha(t);
|
|
130
|
+
const [interno, setInterno] = React.useState(defaultValue ?? []);
|
|
131
|
+
const abertos = value !== undefined ? value : interno;
|
|
132
|
+
const surface = variant === "surface";
|
|
133
|
+
const alternar = (id) => {
|
|
134
|
+
const aberto = abertos.includes(id);
|
|
135
|
+
const novo = multiple
|
|
136
|
+
? (aberto ? abertos.filter((x) => x !== id) : [...abertos, id])
|
|
137
|
+
: (aberto ? [] : [id]);
|
|
138
|
+
if (value === undefined)
|
|
139
|
+
setInterno(novo);
|
|
140
|
+
onValueChange?.(novo);
|
|
141
|
+
};
|
|
142
|
+
return (_jsx(View, { testID: testID, style: [s.accordion, surface && s.accordionSurface, style], children: items.map((item, i) => (_jsxs(React.Fragment, { children: [i > 0 ? _jsx(View, { style: [s.fio, surface && s.fioSurface] }) : null, _jsx(ItemDoAccordion, { item: item, aberto: abertos.includes(item.id), surface: surface, onAlternar: alternar, testID: testID ? `${testID}-${item.id}` : undefined })] }, item.id))) }));
|
|
143
|
+
}
|
|
144
|
+
function ItemDoAccordion({ item, aberto, surface, onAlternar, testID }) {
|
|
145
|
+
const t = useAureaTokens();
|
|
146
|
+
const s = folha(t);
|
|
147
|
+
const reduzir = useReduceMotion();
|
|
148
|
+
const inativo = !!item.disabled;
|
|
149
|
+
const giro = React.useRef(new Animated.Value(aberto ? 1 : 0)).current;
|
|
150
|
+
useLevarA(giro, aberto ? 1 : 0, reduzir !== false, (destino) => Animated.spring(giro, { toValue: destino, ...MOLA_DA_SETA, useNativeDriver: driverNativo() }));
|
|
151
|
+
// A resposta esmaece ao ABRIR. Um valor novo a cada troca, que já nasce em 0 quando vai esmaecer:
|
|
152
|
+
// zerar depois, num efeito, deixaria passar um quadro com a resposta cheia. No primeiro desenho
|
|
153
|
+
// ela nasce em 1 — o item que começa aberto não "abre".
|
|
154
|
+
const montado = React.useRef(false);
|
|
155
|
+
const entrada = React.useMemo(() => {
|
|
156
|
+
const esmaecer = montado.current && aberto && reduzir === false;
|
|
157
|
+
return { valor: new Animated.Value(esmaecer ? 0 : 1), esmaecer };
|
|
158
|
+
}, [aberto]); // só a troca: a resposta do "menos movimento" chegando depois não esmaece de novo
|
|
159
|
+
React.useEffect(() => { montado.current = true; }, []);
|
|
160
|
+
React.useEffect(() => {
|
|
161
|
+
if (!entrada.esmaecer)
|
|
162
|
+
return;
|
|
163
|
+
const a = Animated.timing(entrada.valor, {
|
|
164
|
+
toValue: 1, duration: ESMAECER_DA_RESPOSTA, easing: SAIDA_SUAVE, useNativeDriver: driverNativo(),
|
|
165
|
+
});
|
|
166
|
+
a.start();
|
|
167
|
+
return () => a.stop();
|
|
168
|
+
}, [entrada]);
|
|
169
|
+
// O realce do dedo é o da web: 3% da cor do texto, ou o cinza neutro no `surface`; e só no
|
|
170
|
+
// fechado, como lá (`:not([open])`).
|
|
171
|
+
const realce = surface ? t.color.secondary : comOpacidade(t.color.foreground, 0.03);
|
|
172
|
+
const emTexto = (n) => typeof n === "string" || typeof n === "number";
|
|
173
|
+
return (_jsxs(View, { children: [_jsx(View, { accessibilityRole: "header", children: _jsxs(Pressable, { testID: testID, disabled: inativo,
|
|
174
|
+
// A guarda repete o `disabled` de propósito, como o gatilho da referência: o toque que chega
|
|
175
|
+
// por outro caminho (o leitor de tela, um teste) também não abre o item desligado.
|
|
176
|
+
onPress: () => { if (!inativo)
|
|
177
|
+
onAlternar(item.id); }, accessibilityRole: "button", ...estadoAcessivel({ expanded: aberto, disabled: inativo }), style: ({ pressed }) => [
|
|
178
|
+
s.gatilho, surface && s.gatilhoSurface,
|
|
179
|
+
pressed && !aberto && { backgroundColor: realce },
|
|
180
|
+
inativo && s.inativo,
|
|
181
|
+
], children: [_jsx(View, { style: s.titulo, children: emTexto(item.title) ? _jsx(Text, { size: "sm", weight: 500, children: item.title }) : item.title }), _jsx(Animated.View, { style: [s.seta, { transform: [{ rotate: giro.interpolate({
|
|
182
|
+
inputRange: [0, 1], outputRange: ["0deg", "-180deg"],
|
|
183
|
+
}) }] }], children: _jsx(Icon, { name: "caret-down", size: "sm", color: t.color.foreground }) })] }) }), aberto ? (_jsx(Animated.View, { testID: testID ? `${testID}-content` : undefined, style: [s.resposta, surface && s.respostaSurface, { opacity: entrada.valor }], children: emTexto(item.content)
|
|
184
|
+
? _jsx(Text, { size: "sm", tone: "muted", leading: "relaxed", children: item.content })
|
|
185
|
+
: item.content })) : null] }));
|
|
186
|
+
}
|
|
187
|
+
const ESMAECER_DO_FIO = 200;
|
|
188
|
+
// O da referência: um aviso de rolagem por quadro.
|
|
189
|
+
const AVISO_DE_ROLAGEM = 16;
|
|
190
|
+
/**
|
|
191
|
+
* "Tem mais para lá" — o scroll-shadow da referência, com um FIO no lugar do degradê.
|
|
192
|
+
*
|
|
193
|
+
* 🔴 **Decisão do Victor:** a referência esmaece a ponta com um degradê de 50; a Aurea não tem
|
|
194
|
+
* degradê (CLAUDE.md §5). Aqui é uma linha de 1 (a borda da Aurea, na cor de borda do tema), com as
|
|
195
|
+
* pontas em cápsula, na ponta onde há mais conteúdo escondido, e SÓ lá: no topo da lista não há fio
|
|
196
|
+
* em cima; no fim, não há embaixo. Na horizontal, à esquerda e à direita.
|
|
197
|
+
*
|
|
198
|
+
* 🔴 **E a lista mora numa superfície da Aurea** (Victor, 12/10/2026: *"o ScrollShadow não é [arredondado]
|
|
199
|
+
* como todos da Aurea"*, apontando o `Accordion` no cartão): por padrão, fundo do cartão, canto 22 e a
|
|
200
|
+
* sombra de superfície — a referência não tem caixa nenhuma. `variant="plain"` tira a caixa, para dentro
|
|
201
|
+
* de um cartão; aí o fio recua 22 dos lados, para não fazer quina.
|
|
202
|
+
*
|
|
203
|
+
* Ele mede como o dela, pelos três avisos do rolador (`onLayout`, `onContentSizeChange` e
|
|
204
|
+
* `onScroll`), e entrega ao rolador os avisos que o app já tinha posto, depois dos dele. O
|
|
205
|
+
* `inverted` (a conversa, de baixo para cima) troca as pontas. O fio entra e sai esmaecendo em
|
|
206
|
+
* 200 ms; com "menos movimento", aparece e some de uma vez.
|
|
207
|
+
*
|
|
208
|
+
* ⚠ Um `onScroll` que não é função — o `Animated.event` com o motor nativo — não é repassado: o
|
|
209
|
+
* aviso dele vive no motor, e não há como chamá-lo daqui.
|
|
210
|
+
*/
|
|
211
|
+
export function ScrollShadow({ children, orientation, visibility = "auto", isEnabled = true, variant = "surface", style, testID, }) {
|
|
212
|
+
const t = useAureaTokens();
|
|
213
|
+
const s = folha(t);
|
|
214
|
+
const reduzir = useReduceMotion();
|
|
215
|
+
const filho = React.isValidElement(children) ? children : null;
|
|
216
|
+
const p = filho?.props ?? {};
|
|
217
|
+
const horizontal = orientation ? orientation === "horizontal" : !!p.horizontal;
|
|
218
|
+
const invertido = !!p.inverted;
|
|
219
|
+
const medidas = React.useRef({ deslocamento: 0, visivel: 0, conteudo: 0 });
|
|
220
|
+
const [mais, setMais] = React.useState({ antes: false, depois: false });
|
|
221
|
+
const recalcular = () => {
|
|
222
|
+
const { deslocamento, visivel, conteudo } = medidas.current;
|
|
223
|
+
// A sobra menor que a espessura do próprio fio não conta: o arredondamento de pixel deixa o
|
|
224
|
+
// deslocamento em 0,33 com a lista parada no topo, e o fio acenderia sem nada escondido.
|
|
225
|
+
const folga = t.size.borderWidth;
|
|
226
|
+
const antes = deslocamento > folga;
|
|
227
|
+
const depois = visivel > 0 && conteudo - (deslocamento + visivel) > folga;
|
|
228
|
+
setMais((a) => (a.antes === antes && a.depois === depois ? a : { antes, depois }));
|
|
229
|
+
};
|
|
230
|
+
const comecoPode = visibility !== "none" && visibility !== "bottom" && visibility !== "right";
|
|
231
|
+
const fimPode = visibility !== "none" && visibility !== "top" && visibility !== "left";
|
|
232
|
+
const noComeco = isEnabled && comecoPode && (invertido ? mais.depois : mais.antes);
|
|
233
|
+
const noFim = isEnabled && fimPode && (invertido ? mais.antes : mais.depois);
|
|
234
|
+
const opComeco = React.useRef(new Animated.Value(0)).current;
|
|
235
|
+
const opFim = React.useRef(new Animated.Value(0)).current;
|
|
236
|
+
const esmaecer = (v) => (destino) => Animated.timing(v, { toValue: destino, duration: ESMAECER_DO_FIO, useNativeDriver: driverNativo() });
|
|
237
|
+
useLevarA(opComeco, noComeco ? 1 : 0, reduzir !== false, esmaecer(opComeco));
|
|
238
|
+
useLevarA(opFim, noFim ? 1 : 0, reduzir !== false, esmaecer(opFim));
|
|
239
|
+
if (!filho)
|
|
240
|
+
return null;
|
|
241
|
+
const rolador = React.cloneElement(filho, {
|
|
242
|
+
scrollEventThrottle: p.scrollEventThrottle ?? AVISO_DE_ROLAGEM,
|
|
243
|
+
onLayout: (e) => {
|
|
244
|
+
const { width, height } = e.nativeEvent.layout;
|
|
245
|
+
medidas.current.visivel = horizontal ? width : height;
|
|
246
|
+
recalcular();
|
|
247
|
+
p.onLayout?.(e);
|
|
248
|
+
},
|
|
249
|
+
onContentSizeChange: (w, h) => {
|
|
250
|
+
medidas.current.conteudo = horizontal ? w : h;
|
|
251
|
+
recalcular();
|
|
252
|
+
p.onContentSizeChange?.(w, h);
|
|
253
|
+
},
|
|
254
|
+
onScroll: (e) => {
|
|
255
|
+
const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent;
|
|
256
|
+
const m = medidas.current;
|
|
257
|
+
m.deslocamento = horizontal ? contentOffset.x : contentOffset.y;
|
|
258
|
+
if (layoutMeasurement)
|
|
259
|
+
m.visivel = horizontal ? layoutMeasurement.width : layoutMeasurement.height;
|
|
260
|
+
if (contentSize)
|
|
261
|
+
m.conteudo = horizontal ? contentSize.width : contentSize.height;
|
|
262
|
+
recalcular();
|
|
263
|
+
if (typeof p.onScroll === "function")
|
|
264
|
+
p.onScroll(e);
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
// Os fios são enfeite: fora do toque e fora do leitor de tela — quem lê a lista já ouve onde está.
|
|
268
|
+
const decorativo = {
|
|
269
|
+
pointerEvents: "none",
|
|
270
|
+
accessibilityElementsHidden: true,
|
|
271
|
+
importantForAccessibility: "no-hide-descendants",
|
|
272
|
+
};
|
|
273
|
+
const superficie = variant === "surface";
|
|
274
|
+
const noLado = superficie && (horizontal ? s.fioDaEsquerdaNaSuperficie : s.fioDeCimaNaSuperficie);
|
|
275
|
+
const fios = (_jsxs(_Fragment, { children: [_jsx(Animated.View, { ...decorativo, testID: testID ? `${testID}-start` : undefined, style: [horizontal ? s.fioDaEsquerda : s.fioDeCima, noLado, { opacity: opComeco }] }), _jsx(Animated.View, { ...decorativo, testID: testID ? `${testID}-end` : undefined, style: [horizontal ? s.fioDaDireita : s.fioDeBaixo, noLado, { opacity: opFim }] })] }));
|
|
276
|
+
if (!superficie) {
|
|
277
|
+
return _jsxs(View, { testID: testID, style: style, children: [rolador, fios] });
|
|
278
|
+
}
|
|
279
|
+
// Na superfície, os fios ficam na borda da JANELA da lista (o miolo), e não na da caixa.
|
|
280
|
+
return (_jsx(View, { testID: testID, style: [s.superficieDaLista, style], children: _jsxs(View, { style: s.miolo, children: [rolador, fios] }) }));
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Um comando para vários esqueletos — o skeleton-group da referência. O `isLoading` e a `animation`
|
|
284
|
+
* descem para todo `Skeleton` de dentro (a qualquer profundidade), e cada um pode trocar o seu.
|
|
285
|
+
*
|
|
286
|
+
* O PULSO é um só, do grupo: as caixas sobem e descem juntas, em vez de cada uma no tempo em que
|
|
287
|
+
* montou. `SkeletonGroup.Item` é o próprio `Skeleton` — o nome existe porque a referência o tem.
|
|
288
|
+
*/
|
|
289
|
+
function SkeletonGroupRaiz({ children, isLoading = true, isSkeletonOnly = false, animation = "pulse", style, testID, }) {
|
|
290
|
+
const pulso = usePulsoDoEsqueleto(isLoading && animation === "pulse");
|
|
291
|
+
const valor = React.useMemo(() => ({ isLoading, animation, pulso }), [isLoading, animation, pulso]);
|
|
292
|
+
if (isSkeletonOnly && !isLoading)
|
|
293
|
+
return null;
|
|
294
|
+
return (_jsx(GrupoDeEsqueleto.Provider, { value: valor, children: _jsx(View, { testID: testID, style: style, children: children }) }));
|
|
295
|
+
}
|
|
296
|
+
export const SkeletonGroup = Object.assign(SkeletonGroupRaiz, { Item: Skeleton });
|
|
297
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
298
|
+
// PressableFeedback
|
|
299
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
300
|
+
// Os números da referência.
|
|
301
|
+
const ESCALA_PRESSIONADA = 0.985;
|
|
302
|
+
const LARGURA_DE_REFERENCIA = 300; // a peça de 300 encolhe 0,985; a de 600, metade disso
|
|
303
|
+
const DURACAO_DA_ESCALA = 300;
|
|
304
|
+
const OPACIDADE_DO_REALCE = 0.1;
|
|
305
|
+
const DURACAO_DO_REALCE = 200;
|
|
306
|
+
const FATOR_DO_RAIO = 1.25; // o círculo da onda: 1,25 × a diagonal da peça
|
|
307
|
+
const DIAGONAL_DE_REFERENCIA = 450; // a onda de 1000 ms é a de uma peça com 450 de diagonal
|
|
308
|
+
const DURACAO_DA_ONDA = 1000;
|
|
309
|
+
const DURACAO_MINIMA_DA_ONDA = 750;
|
|
310
|
+
/** O quanto a peça encolhe: `escala` numa peça de 300; mais larga, menos; mais estreita, mais. */
|
|
311
|
+
const alvoDaEscala = (escala, largura) => 1 - (1 - escala) * (largura > 0 ? LARGURA_DE_REFERENCIA / largura : 1);
|
|
312
|
+
const Toque = React.createContext(null);
|
|
313
|
+
function useToque(peca) {
|
|
314
|
+
const v = React.useContext(Toque);
|
|
315
|
+
if (!v)
|
|
316
|
+
throw new Error(`Aurea ${peca}: use dentro de um PressableFeedback.`);
|
|
317
|
+
return v;
|
|
318
|
+
}
|
|
319
|
+
// A raiz é o `Pressable` ANIMADO, como o dela: a escala vai no próprio alvo do toque, com o fundo e
|
|
320
|
+
// a borda de quem usa junto. Criado no primeiro desenho, e não ao importar o módulo — importar o
|
|
321
|
+
// pacote não pode depender de uma função de animação existir (o dublê dos testes não a tem).
|
|
322
|
+
const criarPressionavel = () => Animated.createAnimatedComponent(Pressable);
|
|
323
|
+
let pressionavelAnimado = null;
|
|
324
|
+
/**
|
|
325
|
+
* Um alvo de toque que responde — o pressable-feedback da referência. Por padrão a peça inteira
|
|
326
|
+
* encolhe um pouco com o dedo em cima (0,985 numa peça de 300, proporcional à largura, em 300 ms).
|
|
327
|
+
* Dentro dele, três partes que se combinam: `PressableFeedback.Highlight` (a camada que acende),
|
|
328
|
+
* `PressableFeedback.Ripple` (a onda que nasce no dedo) e `PressableFeedback.Scale` (a escala num
|
|
329
|
+
* filho só).
|
|
330
|
+
*
|
|
331
|
+
* É um botão para o leitor de tela (`accessibilityRole` troca). Com "menos movimento" a escala e o
|
|
332
|
+
* realce SALTAM em vez de deslizar — como o `Button` da Aurea, que salta para 0,97 —, e a onda vira
|
|
333
|
+
* uma camada parada.
|
|
334
|
+
*
|
|
335
|
+
* ⚠ Ele é UM elemento para o leitor de tela: não ponha botão dentro (no iPhone, o de dentro some —
|
|
336
|
+
* o `check 43`). O que vai dentro é conteúdo.
|
|
337
|
+
*/
|
|
338
|
+
function PressableFeedbackRaiz({ children, isDisabled = false, animation = true, scale = ESCALA_PRESSIONADA, style, onLayout, onPressIn, onPressOut, accessibilityRole, accessibilityState, ...resto }) {
|
|
339
|
+
const s = folha(useAureaTokens());
|
|
340
|
+
const reduzir = useReduceMotion();
|
|
341
|
+
const [pressionado, setPressionado] = React.useState(false);
|
|
342
|
+
const [medida, setMedida] = React.useState({ largura: 0, altura: 0 });
|
|
343
|
+
const tudoParado = animation === "disable-all";
|
|
344
|
+
const comEscala = animation !== false && !tudoParado;
|
|
345
|
+
const escala = React.useRef(new Animated.Value(1)).current;
|
|
346
|
+
useLevarA(escala, pressionado && comEscala ? alvoDaEscala(scale, medida.largura) : 1, reduzir !== false, (destino) => Animated.timing(escala, {
|
|
347
|
+
toValue: destino, duration: DURACAO_DA_ESCALA, easing: SAIDA_SUAVE, useNativeDriver: driverNativo(),
|
|
348
|
+
}));
|
|
349
|
+
const contexto = React.useMemo(() => ({ pressionado, largura: medida.largura, altura: medida.altura, tudoParado }), [pressionado, medida, tudoParado]);
|
|
350
|
+
const Raiz = (pressionavelAnimado ??= criarPressionavel());
|
|
351
|
+
return (_jsx(Toque.Provider, { value: contexto, children: _jsx(Raiz, { ...resto, disabled: isDisabled, accessibilityRole: accessibilityRole ?? "button", ...estadoAcessivel({ ...accessibilityState, disabled: isDisabled }), onLayout: (e) => {
|
|
352
|
+
const { width, height } = e.nativeEvent.layout;
|
|
353
|
+
setMedida((m) => (m.largura === width && m.altura === height ? m : { largura: width, altura: height }));
|
|
354
|
+
onLayout?.(e);
|
|
355
|
+
}, onPressIn: (e) => { setPressionado(true); onPressIn?.(e); }, onPressOut: (e) => { setPressionado(false); onPressOut?.(e); }, style: [s.raizDoToque, isDisabled && REACAO_AO_TOQUE.inerte,
|
|
356
|
+
comEscala && { transform: [{ scale: escala }] }, style], children: children }) }));
|
|
357
|
+
}
|
|
358
|
+
/** A escala num filho só — com `animation={false}` na raiz, para não encolher duas vezes. */
|
|
359
|
+
function EscalaDoToque({ children, scale = ESCALA_PRESSIONADA, animation = true, style, testID }) {
|
|
360
|
+
const toque = useToque("PressableFeedback.Scale");
|
|
361
|
+
const reduzir = useReduceMotion();
|
|
362
|
+
const ligado = animation && !toque.tudoParado;
|
|
363
|
+
const valor = React.useRef(new Animated.Value(1)).current;
|
|
364
|
+
useLevarA(valor, toque.pressionado && ligado ? alvoDaEscala(scale, toque.largura) : 1, reduzir !== false, (destino) => Animated.timing(valor, {
|
|
365
|
+
toValue: destino, duration: DURACAO_DA_ESCALA, easing: SAIDA_SUAVE, useNativeDriver: driverNativo(),
|
|
366
|
+
}));
|
|
367
|
+
return (_jsx(Animated.View, { testID: testID, style: [ligado && { transform: [{ scale: valor }] }, style], children: children }));
|
|
368
|
+
}
|
|
369
|
+
/** A camada que acende com o dedo em cima (o toque do iPhone): cobre a peça e vai a 10% em 200 ms. */
|
|
370
|
+
function RealceDoToque({ color, opacity = OPACIDADE_DO_REALCE, animation = true, style, testID }) {
|
|
371
|
+
const t = useAureaTokens();
|
|
372
|
+
const s = folha(t);
|
|
373
|
+
const toque = useToque("PressableFeedback.Highlight");
|
|
374
|
+
const reduzir = useReduceMotion();
|
|
375
|
+
const ligado = animation && !toque.tudoParado;
|
|
376
|
+
const valor = React.useRef(new Animated.Value(0)).current;
|
|
377
|
+
useLevarA(valor, toque.pressionado && ligado ? opacity : 0, reduzir !== false, (destino) => Animated.timing(valor, { toValue: destino, duration: DURACAO_DO_REALCE, useNativeDriver: driverNativo() }));
|
|
378
|
+
if (!ligado)
|
|
379
|
+
return null;
|
|
380
|
+
return (_jsx(Animated.View, { testID: testID, pointerEvents: "none", accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", style: [s.camada, { backgroundColor: color ?? t.color.foreground, opacity: valor }, style] }));
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* A onda que nasce no dedo (o toque do Android): um círculo de 1,25 × a diagonal da peça, que
|
|
384
|
+
* cresce de 0 até cobrir tudo e some ao soltar. Duas camadas que se revezam, como na referência: o
|
|
385
|
+
* toque novo começa numa camada limpa enquanto a anterior termina de sumir, e a onda não pisca.
|
|
386
|
+
*
|
|
387
|
+
* Círculo cheio, de UMA cor — a dela é um degradê do centro para fora, e a Aurea não tem degradê.
|
|
388
|
+
* Com "menos movimento" ela não cresce: acende inteira ao tocar e apaga ao soltar.
|
|
389
|
+
*/
|
|
390
|
+
function OndaDoToque({ color, opacity = OPACIDADE_DO_REALCE, animation = true, style, testID }) {
|
|
391
|
+
const t = useAureaTokens();
|
|
392
|
+
const s = folha(t);
|
|
393
|
+
const toque = useToque("PressableFeedback.Ripple");
|
|
394
|
+
const reduzir = useReduceMotion();
|
|
395
|
+
const ligado = animation && !toque.tudoParado;
|
|
396
|
+
const camadas = React.useRef([0, 1].map(() => ({
|
|
397
|
+
x: new Animated.Value(0), y: new Animated.Value(0), progresso: new Animated.Value(0), viva: false, animacao: null,
|
|
398
|
+
}))).current;
|
|
399
|
+
const ativa = React.useRef(0);
|
|
400
|
+
React.useEffect(() => () => camadas.forEach((c) => c.animacao?.stop()), [camadas]);
|
|
401
|
+
const diagonal = Math.hypot(toque.largura, toque.altura);
|
|
402
|
+
const raio = diagonal * FATOR_DO_RAIO;
|
|
403
|
+
// A duração acompanha o tamanho: 1000 ms numa diagonal de 450, nunca menos de 750 nem mais de 2000.
|
|
404
|
+
const duracao = Math.min(Math.max(DURACAO_DA_ONDA * (diagonal > 0 ? diagonal / DIAGONAL_DE_REFERENCIA : 1), DURACAO_MINIMA_DA_ONDA), DURACAO_DA_ONDA * 2);
|
|
405
|
+
const levar = (c, destino) => {
|
|
406
|
+
c.animacao?.stop();
|
|
407
|
+
c.animacao = null;
|
|
408
|
+
if (reduzir !== false) {
|
|
409
|
+
c.progresso.setValue(destino);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const a = Animated.timing(c.progresso, { toValue: destino, duration: duracao, useNativeDriver: driverNativo() });
|
|
413
|
+
a.start();
|
|
414
|
+
c.animacao = a;
|
|
415
|
+
};
|
|
416
|
+
const tocou = (e) => {
|
|
417
|
+
if (!ligado)
|
|
418
|
+
return;
|
|
419
|
+
const anterior = camadas[ativa.current];
|
|
420
|
+
// Só a onda que ainda está no ar termina de sumir; a camada parada em 0 iria a 2 passando pelo
|
|
421
|
+
// pico, e piscaria no lugar antigo. A que já foi solta segue o próprio caminho até 2.
|
|
422
|
+
if (anterior.viva) {
|
|
423
|
+
anterior.viva = false;
|
|
424
|
+
levar(anterior, 2);
|
|
425
|
+
}
|
|
426
|
+
ativa.current = 1 - ativa.current;
|
|
427
|
+
const nova = camadas[ativa.current];
|
|
428
|
+
nova.animacao?.stop();
|
|
429
|
+
nova.x.setValue(e.nativeEvent.locationX - raio);
|
|
430
|
+
nova.y.setValue(e.nativeEvent.locationY - raio);
|
|
431
|
+
nova.progresso.setValue(0);
|
|
432
|
+
nova.viva = true;
|
|
433
|
+
levar(nova, 1);
|
|
434
|
+
};
|
|
435
|
+
const soltou = () => {
|
|
436
|
+
const c = camadas[ativa.current];
|
|
437
|
+
if (ligado && c.viva) {
|
|
438
|
+
c.viva = false;
|
|
439
|
+
levar(c, 2);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
if (!ligado)
|
|
443
|
+
return null;
|
|
444
|
+
return (_jsx(View, { testID: testID, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", onTouchStart: tocou, onTouchEnd: soltou, onTouchCancel: soltou, style: [s.camada, style], children: camadas.map((c, i) => (_jsx(Animated.View, { pointerEvents: "none", style: [s.onda, {
|
|
445
|
+
width: raio * 2, height: raio * 2, ...canto(raio), backgroundColor: color ?? t.color.foreground,
|
|
446
|
+
opacity: c.progresso.interpolate({ inputRange: [0, 1, 2], outputRange: [0, opacity, 0] }),
|
|
447
|
+
transform: [
|
|
448
|
+
{ translateX: c.x }, { translateY: c.y },
|
|
449
|
+
{ scale: c.progresso.interpolate({ inputRange: [0, 1, 2], outputRange: [0, 1, 1] }) },
|
|
450
|
+
],
|
|
451
|
+
}] }, i))) }));
|
|
452
|
+
}
|
|
453
|
+
export const PressableFeedback = Object.assign(PressableFeedbackRaiz, {
|
|
454
|
+
Scale: EscalaDoToque,
|
|
455
|
+
Highlight: RealceDoToque,
|
|
456
|
+
Ripple: OndaDoToque,
|
|
457
|
+
});
|
package/dist/feedback.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { type StyleProp, type ViewProps, type ViewStyle } from "react-native";
|
|
2
|
+
import { Animated, type StyleProp, type ViewProps, type ViewStyle } from "react-native";
|
|
3
3
|
import { type IconName, type AureaIcon } from "./icon.js";
|
|
4
4
|
import { type AureaUniversalState } from "./strings.js";
|
|
5
5
|
export type AureaSpinnerSize = "sm" | "md" | "lg";
|
|
6
6
|
export interface SpinnerProps {
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* 16 · 24 · 32, e o padrão é o `md` (24) — a escala do pacote nativo da referência (0.32.0).
|
|
9
|
+
* Até a 0.31.0 era 16 · 20 · 24, com o `sm` de padrão, como a web.
|
|
10
|
+
*/
|
|
8
11
|
size?: AureaSpinnerSize;
|
|
9
12
|
/** O que o leitor de tela anuncia. Sem ele, a frase `loading` do provider. */
|
|
10
13
|
label?: string;
|
|
@@ -22,6 +25,9 @@ export interface SpinnerProps {
|
|
|
22
25
|
* "algo está acontecendo" continua vendo o rótulo.
|
|
23
26
|
*/
|
|
24
27
|
export declare function Spinner({ size, label, decorative, style, testID }: SpinnerProps): React.JSX.Element;
|
|
28
|
+
/** O pulso (`"pulse"`, o padrão) ou a caixa parada (`"none"`) — os nomes da web (0.32.0). O brilho que
|
|
29
|
+
* corre da referência ("shimmer") é degradê, e degradê a Aurea não tem. */
|
|
30
|
+
export type AureaSkeletonAnimation = "pulse" | "none";
|
|
25
31
|
export interface SkeletonProps {
|
|
26
32
|
/** Padrão: ocupa a largura disponível. */
|
|
27
33
|
width?: number | `${number}%`;
|
|
@@ -30,7 +36,36 @@ export interface SkeletonProps {
|
|
|
30
36
|
radius?: number;
|
|
31
37
|
style?: StyleProp<ViewStyle>;
|
|
32
38
|
testID?: string;
|
|
39
|
+
/**
|
|
40
|
+
* 0.32.0: `false` troca a caixa pelo conteúdo de verdade (`children`) — o `isLoading` da referência.
|
|
41
|
+
* Padrão: `true`, a caixa de sempre. Dentro de um `SkeletonGroup`, sem este, vale o do grupo.
|
|
42
|
+
*/
|
|
43
|
+
isLoading?: boolean;
|
|
44
|
+
/** O conteúdo que aparece no lugar da caixa quando `isLoading` é `false`. */
|
|
45
|
+
children?: React.ReactNode;
|
|
46
|
+
/** `"none"` deixa a caixa parada, como na web. Dentro de um `SkeletonGroup`, sem este, vale o do grupo. */
|
|
47
|
+
animation?: AureaSkeletonAnimation;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* O que o `SkeletonGroup` manda para os esqueletos de dentro: o `isLoading` e a `animation` do grupo,
|
|
51
|
+
* e o PULSO — um valor só, e por isso todos sobem e descem juntos, em vez de cada um no seu tempo.
|
|
52
|
+
*
|
|
53
|
+
* Mora aqui, ao lado do `Skeleton`, porque é ele quem lê. O grupo (`exibicao.tsx`) importa daqui;
|
|
54
|
+
* o contrário fecharia um ciclo de importação entre os dois arquivos. Ferramenta de dentro: não sai
|
|
55
|
+
* pela porta da frente do pacote.
|
|
56
|
+
*/
|
|
57
|
+
export interface GrupoDeEsqueletoValor {
|
|
58
|
+
isLoading: boolean;
|
|
59
|
+
animation: AureaSkeletonAnimation;
|
|
60
|
+
pulso: Animated.Value;
|
|
33
61
|
}
|
|
62
|
+
export declare const GrupoDeEsqueleto: React.Context<GrupoDeEsqueletoValor | null>;
|
|
63
|
+
/**
|
|
64
|
+
* O pulso do esqueleto: liga só quando `ativo`, e só depois de o sistema dizer que pode mexer.
|
|
65
|
+
* Desligado, a opacidade volta a 1 — um laço parado no meio deixaria a caixa meio apagada.
|
|
66
|
+
* O `SkeletonGroup` usa o mesmo, uma vez para o grupo inteiro.
|
|
67
|
+
*/
|
|
68
|
+
export declare function usePulsoDoEsqueleto(ativo: boolean): Animated.Value;
|
|
34
69
|
/**
|
|
35
70
|
* A caixa cinza que pulsa enquanto o dado não chega.
|
|
36
71
|
*
|
|
@@ -41,8 +76,13 @@ export interface SkeletonProps {
|
|
|
41
76
|
*
|
|
42
77
|
* Ele é **invisível para o leitor de tela** (`aria-hidden` na web, `accessibilityElementsHidden`
|
|
43
78
|
* aqui): anunciar "caixa" três vezes enquanto a tela carrega não ajuda ninguém.
|
|
79
|
+
*
|
|
80
|
+
* 0.32.0: o `isLoading` e os `children` da referência. Com `isLoading={false}` a caixa sai e o
|
|
81
|
+
* conteúdo entra, num esmaecer da duração lenta da Aurea (a referência usa o esmaecer padrão do
|
|
82
|
+
* motor dela); com "menos movimento", ou no primeiro desenho, ele entra sem esmaecer. Sem as props
|
|
83
|
+
* novas, nada muda: é a caixa de sempre.
|
|
44
84
|
*/
|
|
45
|
-
export declare function Skeleton({ width, height, radius, style, testID }: SkeletonProps): React.JSX.Element;
|
|
85
|
+
export declare function Skeleton({ width, height, radius, style, testID, isLoading, children, animation }: SkeletonProps): React.JSX.Element;
|
|
46
86
|
/** O tom da barra: a mesma lista fechada do `tone` do `Button`. Pausado é `neutral`; falha é `danger`. */
|
|
47
87
|
export type AureaProgressTone = "brand" | "neutral" | "success" | "warning" | "danger" | "info";
|
|
48
88
|
export interface ProgressProps {
|
|
@@ -137,6 +177,10 @@ export interface DataStateProps extends Omit<ViewProps, "children"> {
|
|
|
137
177
|
action?: React.ReactNode;
|
|
138
178
|
/** Função para o conteúdo caro não ser construído enquanto o estado o esconde. */
|
|
139
179
|
children: React.ReactNode | (() => React.ReactNode);
|
|
180
|
+
/** A barreira de erro (MNT-25): o que quebrar ao desenhar o conteúdo vira a cara de erro, com "Tentar de novo". */
|
|
181
|
+
catchErrors?: boolean;
|
|
182
|
+
/** Recebe o erro que a barreira pegou — para o app registrar. */
|
|
183
|
+
onCatch?: (error: unknown, info: React.ErrorInfo) => void;
|
|
140
184
|
}
|
|
141
185
|
/**
|
|
142
186
|
* A região que troca de cara. **Ela não desenha nada próprio** — empilha o que já existe.
|
|
@@ -146,4 +190,4 @@ export interface DataStateProps extends Omit<ViewProps, "children"> {
|
|
|
146
190
|
* dado está aí e tem ressalva — esconder o dado seria trocar informação parcial por informação
|
|
147
191
|
* nenhuma. Só `loading`, `error` e `empty` tomam o lugar.
|
|
148
192
|
*/
|
|
149
|
-
export declare function DataState({
|
|
193
|
+
export declare function DataState({ catchErrors, onCatch, ...props }: DataStateProps): React.JSX.Element;
|