@aurea-uds/native 0.7.0 → 0.8.1

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/README.md CHANGED
@@ -6,9 +6,18 @@ Pacote **irmão** do `@aurea-uds/react`, não um wrapper dele: componentes próp
6
6
  `View`/`Text`/`Pressable`, consumindo `@aurea-uds/tokens`. Só os tokens atravessam — decisão de
7
7
  18/07/2026, registrada no `ROADMAP.md` Fase 7.
8
8
 
9
- > **Estado: O ALVO NATIVO ESTÁ COMPLETO — 46 componentes, e agora sem exceção.** O Lote 6 fechou
10
- > o plano em 09/09/2026 (`Timeline`, `DataList`, `Table`), e o **`Chart`** — a única peça que
11
- > tinha ficado de fora — entrou no mesmo dia pela
9
+ > **Estado: 51 componentes.** O **Lote 7** entrou em 11/09/2026 e é o primeiro que **não sai do
10
+ > plano** — ele saiu do APP. O consumidor mediu três lacunas na primeira tela que a pessoa toca, e
11
+ > elas viraram cinco componentes: **`Combobox`, `SearchField`, `NumberField`, `Image` e
12
+ > `Gallery`**, mais a prop `formatOnBlur` no `Input`. **Zero dependência nova.**
13
+ > Ver a seção **Buscar, número e mídia** abaixo, e o `NATIVE.md` §8.
14
+ >
15
+ > ⚠ **O `Combobox` DIVERGE da web de propósito** — ele aceita busca REMOTA em seleção única, que
16
+ > lá não existe ([ADR-0043](../../decisions/0043-o-combobox-nativo-diverge-da-web-e-a-presenca-da-prop-e-a-chave.md)).
17
+ > ⚠ **Os cinco do Lote 7 ainda não rodaram em aparelho** — só em teste.
18
+ >
19
+ > **O PLANO fechou no Lote 6**, em 09/09/2026 (`Timeline`, `DataList`, `Table`), e o **`Chart`** —
20
+ > a única peça que tinha ficado de fora — entrou no mesmo dia pela
12
21
  > [ADR-0041](../../decisions/0041-o-motor-de-grafico-do-nativo-e-nosso-sobre-react-native-svg.md).
13
22
  >
14
23
  > ⚠ **O `Chart` é o único componente deste pacote cuja GEOMETRIA é nossa.** Na web ele é 79 linhas
@@ -219,6 +228,129 @@ leitor anuncia.
219
228
  O `behavior` muda com a plataforma — `padding` no iOS, `height` no Android — e a Aurea escolhe
220
229
  por você. Um só nos dois lados deixa metade dos aparelhos com o botão de salvar sob o teclado.
221
230
 
231
+ ## Buscar, número e mídia
232
+
233
+ O **Lote 7** (11/09/2026). Três lacunas que o app mediu, e não o plano — `NATIVE.md` §8.
234
+
235
+ ### `Combobox` — digitar para achar um item
236
+
237
+ ⚠ **Não confunda com o `Select`.** A escolha entre os dois é de TAMANHO DE LISTA, e a linha é
238
+ dura: o `Select` monta todos os itens num `ScrollView` — serve para unidades, estados e tipos.
239
+ **Um catálogo de milhares de linhas ali trava.** O `Combobox` usa `FlatList` e aceita busca remota.
240
+
241
+ ```tsx
242
+ import {Combobox, Field} from "@aurea-uds/native";
243
+
244
+ // ① catálogo REMOTO — a Aurea não filtra, o app busca
245
+ <Field label="Item">
246
+ <Combobox
247
+ items={resultados} // a lista JÁ BUSCADA
248
+ value={escolhido} // o ITEM inteiro, não o id
249
+ onValueChange={setEscolhido}
250
+ onSearchChange={buscar} // ← a presença desta prop DESLIGA o filtro da Aurea
251
+ loading={buscando}
252
+ onEndReached={proximaPagina}
253
+ />
254
+ </Field>
255
+
256
+ // ② lista NA MÃO — a Aurea filtra, ignorando acento e caixa
257
+ <Combobox items={unidades} value={unidade} onValueChange={setUnidade} />
258
+ ```
259
+
260
+ **A regra em uma linha: `onSearchChange` presente = a Aurea não filtra.** É presença de prop, não
261
+ configuração — e ela existe porque filtrar de novo o que o servidor devolveu esconde resultado.
262
+
263
+ | | |
264
+ |---|---|
265
+ | espera antes de chamar `onSearchChange` | **250 ms**. `searchDelay={0}` desliga |
266
+ | onde se digita | **dentro da folha**, não no gatilho — o teclado ocupa metade da tela |
267
+ | `value` | o **item inteiro**: numa busca remota a lista some debaixo da escolha |
268
+ | ícones a registrar | `chevron--down`, `search`, `close` |
269
+
270
+ ### `SearchField` — filtrar o que já está na tela
271
+
272
+ Outro papel: o `Combobox` **escolhe** de um catálogo e devolve a escolha; este **filtra** e
273
+ devolve texto.
274
+
275
+ ```tsx
276
+ <SearchField placeholder="Filtrar" onSearchChange={setFiltro} />
277
+ ```
278
+
279
+ ### `NumberField` — moeda, medida e contador
280
+
281
+ ```tsx
282
+ <Field label="Valor">
283
+ <NumberField value={valor} onValueChange={setValor}
284
+ format={{style: "currency", currency: "BRL"}} locale="pt-BR" />
285
+ </Field>
286
+
287
+ <Field label="Litros">
288
+ <NumberField value={litros} onValueChange={setLitros}
289
+ format={{maximumFractionDigits: 1}} locale="pt-BR" min={0} />
290
+ </Field>
291
+
292
+ <Field label="Quantidade">
293
+ <NumberField value={qtd} onValueChange={setQtd} min={0} step={1} />
294
+ </Field>
295
+ ```
296
+
297
+ ⚠ **O que sai por `onValueChange` é `number`, sempre** — `1234.5`, nunca `"R$ 1.234,50"`.
298
+
299
+ ⚠ **Formata no BLUR, e só no blur** ([ADR-0024](../../decisions/0024-mascara-de-campo-e-o-momento-nao-o-formato.md)
300
+ e [ADR-0042](../../decisions/0042-o-intl-e-o-formatador-do-nativo-e-a-volta-e-nossa.md)). Enquanto
301
+ o campo tem foco, ele mostra exatamente o que foi digitado. Máscara ao vivo é o defeito que o
302
+ USWDS publicou com reprovação WCAG registrada e que o MUI abandonou na v6.
303
+
304
+ ⚠ **`notation: "compact"` é recusado** — quebrado no motor JS do RN nos dois sistemas. Sai aviso em
305
+ `__DEV__` e a opção é ignorada; o resto do formato sobrevive.
306
+
307
+ Os três auxiliares são públicos, porque o app tem o mesmo problema fora do campo:
308
+
309
+ ```tsx
310
+ import {formatarNumero, lerNumero, separadoresDoLocale} from "@aurea-uds/native";
311
+
312
+ formatarNumero(1234.5, "pt-BR", {style: "currency", currency: "BRL"}); // "R$ 1.234,50"
313
+ lerNumero("R$ 1.234,50", "pt-BR"); // 1234.5
314
+ lerNumero("", "pt-BR"); // null ← não é 0
315
+ ```
316
+
317
+ Para **texto** (placa, documento, telefone) o momento é o mesmo e o formato é seu:
318
+
319
+ ```tsx
320
+ <Input value={placa} onChangeText={setPlaca} formatOnBlur={(v) => v.toUpperCase()} />
321
+ ```
322
+
323
+ ⚠ `formatOnBlur` exige o campo **controlado** — o valor formatado sai por `onChangeText`.
324
+
325
+ ### `Image` e `Gallery` — mostrar foto
326
+
327
+ ⚠ **`Image` NÃO é o `Image` do React Native com outra pele.** Ele faz as duas coisas que o
328
+ primitivo cru não faz: **reserva a caixa** antes dos bytes (sem isso o layout salta) e **cai para
329
+ um substituto** quando eles não vêm (sem isso uma URL quebrada deixa buraco).
330
+
331
+ ```tsx
332
+ <Image source={foto.uri} alt="Frente do item" ratio="4/3" />
333
+
334
+ <Gallery items={fotos} zoom selected={atual} onSelect={setAtual} />
335
+ ```
336
+
337
+ | | |
338
+ |---|---|
339
+ | `ratio` | aceita `4/3`, `"4/3"`, `"16:9"` ou o número. A web usa string; o RN quer número |
340
+ | `alt` | **obrigatório**. Decorativa de verdade se escreve `alt=""`, explícito |
341
+ | ampliar | é o **`Dialog`** que já existe, não uma superfície nova |
342
+ | legenda | com legenda, o nome vai para o LADRILHO e a imagem sai da árvore de acessibilidade |
343
+ | ícone a registrar | `image` (o substituto) |
344
+
345
+ Quem já usa `expo-image` por cache de disco troca o elemento sem perder a pele:
346
+
347
+ ```tsx
348
+ <Image render={<ExpoImage contentFit="cover" transition={150} />} source={u} alt="…" />
349
+ ```
350
+
351
+ ⚠ `fit` **não** é traduzido para o elemento passado — `resizeMode` é do `Image` do RN e o
352
+ `expo-image` chama a mesma coisa de `contentFit`. Quem passa o elemento escreve a prop dele.
353
+
222
354
  ## Data e câmera
223
355
 
224
356
  Estes dois **não saem do barril** — são o único ponto do pacote com importação própria:
@@ -0,0 +1,126 @@
1
+ import * as React from "react";
2
+ import { type StyleProp, type ViewStyle } from "react-native";
3
+ import { type IconName } from "./icon.js";
4
+ import { type AureaFieldSize } from "./inputs.js";
5
+ /** Uma linha do catálogo. **Mesma forma da web** (`ComboboxOption`), mais o `disabled` que o `Select` daqui já tinha. */
6
+ export interface AureaComboboxItem {
7
+ value: string;
8
+ label: string;
9
+ disabled?: boolean;
10
+ }
11
+ export interface ComboboxProps {
12
+ /**
13
+ * O que a folha mostra. **Com `onSearchChange`, é a lista JÁ BUSCADA** — a Aurea não filtra e
14
+ * não guarda; ela desenha o que chegou.
15
+ */
16
+ items: AureaComboboxItem[];
17
+ /**
18
+ * O escolhido, como ITEM inteiro e não como `value` — mesma escolha da web, e ela tem razão
19
+ * prática aqui: numa busca remota o item escolhido pode não estar mais em `items` (a pessoa
20
+ * digitou outra coisa depois de escolher), e o gatilho ainda precisa saber que rótulo mostrar.
21
+ * Guardar só o `value` deixaria o campo em branco no instante seguinte à escolha.
22
+ */
23
+ value?: AureaComboboxItem | null;
24
+ onValueChange?: (v: AureaComboboxItem | null) => void;
25
+ /**
26
+ * O que foi digitado, já com espera. **A presença desta prop DESLIGA o filtro da Aurea** — é a
27
+ * chave entre catálogo remoto e lista na mão, e é presença, não configuração.
28
+ */
29
+ onSearchChange?: (query: string) => void;
30
+ /**
31
+ * A espera antes de chamar `onSearchChange`, em ms. Padrão **250**.
32
+ *
33
+ * Não é enfeite de performance: sem ela, "cadeira" são sete chamadas ao servidor e seis
34
+ * respostas descartadas — e a última a chegar pode não ser a última pedida. `0` desliga.
35
+ */
36
+ searchDelay?: number;
37
+ /** Mostra o anel no lugar da lista. Use enquanto a busca remota não voltou. */
38
+ loading?: boolean;
39
+ /** Fim da lista alcançado — é por aqui que o catálogo remoto pagina. */
40
+ onEndReached?: () => void;
41
+ /** O que o gatilho mostra sem escolha. */
42
+ placeholder?: string;
43
+ /** O que o campo da folha mostra vazio. Sem ele, a frase `comboboxSearch` do provider. */
44
+ searchPlaceholder?: string;
45
+ /** O que a folha mostra sem resultado. Sem ele, a frase `comboboxEmpty` do provider. */
46
+ empty?: React.ReactNode;
47
+ /** Mostra o botão de limpar no gatilho quando há escolha. Padrão **true**. */
48
+ clearable?: boolean;
49
+ /**
50
+ * Arrastar o cabeçalho da folha para baixo fecha. Ligado.
51
+ *
52
+ * ⚠ **É a ÚNICA saída por gesto no iOS** — lá não há botão VOLTAR, então sem isto restaria só
53
+ * tocar no fundo. Mesmo gesto, mesmo limiar e mesma curva do `BottomSheet` do Lote 5.
54
+ */
55
+ draggable?: boolean;
56
+ disabled?: boolean;
57
+ size?: AureaFieldSize;
58
+ /** O glifo da seta. Registre-o, ou passe `false`. */
59
+ chevron?: IconName | false;
60
+ /** O glifo da lupa no campo da folha. Registre-o, ou passe `false`. */
61
+ searchIcon?: IconName | false;
62
+ style?: StyleProp<ViewStyle>;
63
+ testID?: string;
64
+ }
65
+ /**
66
+ * O campo em que se DIGITA para achar um item.
67
+ *
68
+ * ```tsx
69
+ * // catálogo remoto — a Aurea não filtra, o app busca
70
+ * <Field label="Item">
71
+ * <Combobox
72
+ * items={resultados}
73
+ * value={escolhido}
74
+ * onValueChange={setEscolhido}
75
+ * onSearchChange={buscarNoServidor}
76
+ * loading={buscando}
77
+ * onEndReached={proximaPagina}
78
+ * />
79
+ * </Field>
80
+ *
81
+ * // lista na mão — a Aurea filtra
82
+ * <Combobox items={unidades} value={unidade} onValueChange={setUnidade} />
83
+ * ```
84
+ *
85
+ * ⚠ **O gatilho é `button`, não `combobox`** — e desta vez a razão é medida, não herdada do
86
+ * `Select`: `role="combobox"` mapeia no Android (`roleDescription`) e **não mapeia no iOS**; e o
87
+ * gatilho, de qualquer forma, não aceita digitação. Quem digita é o campo da folha, que leva
88
+ * `role="search"` — esse mapeia nos dois. A tabela com arquivo e linha está no topo deste módulo.
89
+ *
90
+ * ⚠ **Ele não guarda a escolha nem o texto buscado.** `value` e o que se digita são do app, como
91
+ * na web e pela mesma razão do achado I1 da auditoria: um componente com a sua própria cópia do
92
+ * estado é a segunda fonte de verdade que ninguém sabe que existe.
93
+ */
94
+ export declare function Combobox({ items, value, onValueChange, onSearchChange, searchDelay, loading, onEndReached, placeholder, searchPlaceholder, empty, clearable, draggable, disabled, size, chevron, searchIcon, style, testID, }: ComboboxProps): React.JSX.Element;
95
+ export interface SearchFieldProps {
96
+ value?: string;
97
+ onChangeText?: (v: string) => void;
98
+ /** Chamado com o texto já esperado. Mesma espera do `Combobox`, e mesmo padrão de 250 ms. */
99
+ onSearchChange?: (query: string) => void;
100
+ searchDelay?: number;
101
+ placeholder?: string;
102
+ disabled?: boolean;
103
+ size?: AureaFieldSize;
104
+ /** O glifo da lupa. Padrão `search`; `false` tira. */
105
+ icon?: IconName | false;
106
+ /** Mostra o botão de limpar quando há texto. Padrão **true**. */
107
+ clearable?: boolean;
108
+ onSubmit?: () => void;
109
+ style?: StyleProp<ViewStyle>;
110
+ testID?: string;
111
+ }
112
+ /**
113
+ * A lupa + o campo, numa peça só.
114
+ *
115
+ * ⚠ **Não é o mesmo papel do `Combobox`, e confundir os dois é o erro comum:** o `Combobox`
116
+ * ESCOLHE um item de um catálogo e devolve a escolha; este FILTRA o que já está na tela e devolve
117
+ * texto. Um cadastro pede o primeiro; uma lista com muitas linhas pede o segundo.
118
+ *
119
+ * ⚠ **`role="search"`, e ele mapeia nos dois sistemas** — conferido no fonte do RN 0.87.1, com
120
+ * arquivo e linha no topo deste módulo. É a diferença entre este componente e o `Combobox`, cujo
121
+ * gatilho não pôde levar `combobox` porque o iOS não tem o trait.
122
+ *
123
+ * ⚠ **A borda mora no GRUPO, não no campo** — é o `.input-group` da web (`aurea.css:741`), e é o
124
+ * que deixa lupa, campo e botão de limpar dentro de uma caixa só em vez de três.
125
+ */
126
+ export declare function SearchField({ value, onChangeText, onSearchChange, searchDelay, placeholder, disabled, size, icon, clearable, onSubmit, style, testID, }: SearchFieldProps): React.JSX.Element;