@aurea-uds/native 0.7.0 → 0.8.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/README.md +135 -3
- package/dist/busca.d.ts +119 -0
- package/dist/busca.js +338 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +21 -0
- package/dist/inputs.d.ts +31 -1
- package/dist/inputs.js +19 -2
- package/dist/midia.d.ts +98 -0
- package/dist/midia.js +181 -0
- package/dist/numero.d.ts +100 -0
- package/dist/numero.js +291 -0
- package/dist/strings.d.ts +26 -0
- package/dist/strings.js +16 -0
- package/package.json +2 -2
package/dist/inputs.d.ts
CHANGED
|
@@ -61,10 +61,34 @@ export interface InputProps {
|
|
|
61
61
|
/**
|
|
62
62
|
* O teclado que sobe. **O plano do consumidor pede `numeric` nos campos de medida e moeda**, e
|
|
63
63
|
* é a prop que faz a diferença entre digitar 12,4 em dois toques ou em oito.
|
|
64
|
+
*
|
|
65
|
+
* ⚠ **Teclado não é formato, e esta prop resolve só a metade fácil** — foi a segunda lacuna
|
|
66
|
+
* que o consumidor mediu. Moeda, medida decimal e contador são o `NumberField` (Lote 7,
|
|
67
|
+
* `numero.tsx`), que formata no blur e devolve `number`. Este campo continua devolvendo texto.
|
|
64
68
|
*/
|
|
65
69
|
keyboardType?: KeyboardTypeOptions;
|
|
66
70
|
secureTextEntry?: boolean;
|
|
67
71
|
autoCapitalize?: "none" | "sentences" | "words" | "characters";
|
|
72
|
+
/**
|
|
73
|
+
* Normaliza o texto **quando o foco sai** — placa, documento, telefone.
|
|
74
|
+
*
|
|
75
|
+
* ⚠ **A Aurea entrega o MOMENTO, não o formato**, e isso é a [ADR-0024] inteira: formatar
|
|
76
|
+
* enquanto se digita é o defeito que o USWDS publicou com reprovação WCAG registrada e que o
|
|
77
|
+
* MUI abandonou na v6. O formato é conhecimento de DOMÍNIO — a Aurea não sabe o que é um
|
|
78
|
+
* documento válido em lugar nenhum, e uma tabela de formatos por país dentro de uma biblioteca
|
|
79
|
+
* de interface envelhece sem ninguém perceber.
|
|
80
|
+
*
|
|
81
|
+
* <Input value={placa} onChangeText={setPlaca} formatOnBlur={(v) => v.toUpperCase()} />
|
|
82
|
+
*
|
|
83
|
+
* ⚠ **Exige o campo CONTROLADO** (`value` + `onChangeText`), e a diferença com a web é honesta:
|
|
84
|
+
* lá o componente escreve em `e.currentTarget.value` e o `<input>` não controlado obedece. Aqui
|
|
85
|
+
* o texto do `TextInput` não controlado vive dentro do nó nativo, e escrevê-lo de fora exigiria
|
|
86
|
+
* `setNativeProps` — API que a Nova Arquitetura desencoraja. Então o que sai daqui é o valor
|
|
87
|
+
* formatado por `onChangeText`; quem guarda o estado desenha.
|
|
88
|
+
*
|
|
89
|
+
* Para MOEDA e MEDIDA não use isto: use o `NumberField`, que já faz a conta e devolve `number`.
|
|
90
|
+
*/
|
|
91
|
+
formatOnBlur?: (value: string) => string;
|
|
68
92
|
onBlur?: () => void;
|
|
69
93
|
onFocus?: () => void;
|
|
70
94
|
/** Várias linhas. É o que o `Textarea` liga — raramente se põe à mão. */
|
|
@@ -79,7 +103,7 @@ export interface InputProps {
|
|
|
79
103
|
* (`aurea.css:686`); **não há ponteiro no telefone**. O foco continua existindo e é o que marca o
|
|
80
104
|
* campo ativo.
|
|
81
105
|
*/
|
|
82
|
-
export declare function Input({ value, defaultValue, onChangeText, placeholder, disabled, size, keyboardType, secureTextEntry, autoCapitalize, onBlur, onFocus, multiline, style, testID, }: InputProps): React.JSX.Element;
|
|
106
|
+
export declare function Input({ value, defaultValue, onChangeText, placeholder, disabled, size, keyboardType, secureTextEntry, autoCapitalize, formatOnBlur, onBlur, onFocus, multiline, style, testID, }: InputProps): React.JSX.Element;
|
|
83
107
|
export interface TextareaProps extends InputProps {
|
|
84
108
|
/** Linhas visíveis. Vira altura mínima, porque no RN não há `rows`. */
|
|
85
109
|
rows?: number;
|
|
@@ -169,6 +193,12 @@ export interface SelectProps {
|
|
|
169
193
|
* ⚠ **O papel é `button` e não `combobox`.** `combobox` promete um campo em que se DIGITA para
|
|
170
194
|
* filtrar; isto só abre uma lista. Prometer o que não se faz é o mesmo defeito do `tablist` no
|
|
171
195
|
* Lote 3.
|
|
196
|
+
*
|
|
197
|
+
* ⚠ **Quem DIGITA para filtrar é o `Combobox`** (Lote 7, `busca.tsx`), e ele nasceu desta
|
|
198
|
+
* lacuna — o consumidor bateu nela no cadastro. **A escolha entre os dois é de tamanho de
|
|
199
|
+
* lista, e a linha é dura:** este monta todos os itens num `ScrollView` (abaixo), o que serve
|
|
200
|
+
* para unidades, estados e tipos; um catálogo de milhares de linhas aqui não fica lento, trava.
|
|
201
|
+
* O `Combobox` usa `FlatList` e aceita busca remota.
|
|
172
202
|
*/
|
|
173
203
|
export declare function Select({ items, value, onChange, placeholder, disabled, size, chevron, style, testID, }: SelectProps): React.JSX.Element;
|
|
174
204
|
export interface FormProps extends ViewProps {
|
package/dist/inputs.js
CHANGED
|
@@ -147,14 +147,25 @@ export function Label({ children, trailing, style, ...rest }) {
|
|
|
147
147
|
* (`aurea.css:686`); **não há ponteiro no telefone**. O foco continua existindo e é o que marca o
|
|
148
148
|
* campo ativo.
|
|
149
149
|
*/
|
|
150
|
-
export function Input({ value, defaultValue, onChangeText, placeholder, disabled, size, keyboardType, secureTextEntry, autoCapitalize, onBlur, onFocus, multiline, style, testID, }) {
|
|
150
|
+
export function Input({ value, defaultValue, onChangeText, placeholder, disabled, size, keyboardType, secureTextEntry, autoCapitalize, formatOnBlur, onBlur, onFocus, multiline, style, testID, }) {
|
|
151
151
|
const t = useAureaTokens();
|
|
152
152
|
const s = folha(t);
|
|
153
153
|
const campo = useCampo();
|
|
154
154
|
const tam = size ?? campo?.size ?? "md";
|
|
155
155
|
const inativo = disabled ?? campo?.disabled;
|
|
156
156
|
const [focado, setFocado] = React.useState(false);
|
|
157
|
-
return (_jsx(TextInput, { testID: testID, value: value, defaultValue: defaultValue, onChangeText: onChangeText, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, editable: !inativo, keyboardType: keyboardType, secureTextEntry: secureTextEntry, autoCapitalize: autoCapitalize, multiline: multiline, onFocus: () => { setFocado(true); onFocus?.(); }, onBlur: () => {
|
|
157
|
+
return (_jsx(TextInput, { testID: testID, value: value, defaultValue: defaultValue, onChangeText: onChangeText, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, editable: !inativo, keyboardType: keyboardType, secureTextEntry: secureTextEntry, autoCapitalize: autoCapitalize, multiline: multiline, onFocus: () => { setFocado(true); onFocus?.(); }, onBlur: () => {
|
|
158
|
+
setFocado(false);
|
|
159
|
+
// Só avisa quando o texto MUDOU: emitir o mesmo valor a cada saída de foco faria o app
|
|
160
|
+
// re-renderizar sem motivo, e num formulário de dez campos isso é dez renders por
|
|
161
|
+
// preenchimento. Mesma guarda da web (`inputs-client.tsx:194`).
|
|
162
|
+
if (formatOnBlur && value != null) {
|
|
163
|
+
const formatado = formatOnBlur(value);
|
|
164
|
+
if (formatado !== value)
|
|
165
|
+
onChangeText?.(formatado);
|
|
166
|
+
}
|
|
167
|
+
onBlur?.();
|
|
168
|
+
},
|
|
158
169
|
// O nome e a dica vêm do `Field` — ver o bloco acima sobre por que a ligação da web não
|
|
159
170
|
// atravessa. Sem isto, o campo é um nó anônimo para o leitor de tela.
|
|
160
171
|
accessibilityLabel: campo?.label, accessibilityHint: campo?.hint, accessibilityState: { disabled: !!inativo }, "aria-invalid": campo?.invalido, style: [
|
|
@@ -319,6 +330,12 @@ export function SegmentedControl({ items, value, onChange, label, disabled, styl
|
|
|
319
330
|
* ⚠ **O papel é `button` e não `combobox`.** `combobox` promete um campo em que se DIGITA para
|
|
320
331
|
* filtrar; isto só abre uma lista. Prometer o que não se faz é o mesmo defeito do `tablist` no
|
|
321
332
|
* Lote 3.
|
|
333
|
+
*
|
|
334
|
+
* ⚠ **Quem DIGITA para filtrar é o `Combobox`** (Lote 7, `busca.tsx`), e ele nasceu desta
|
|
335
|
+
* lacuna — o consumidor bateu nela no cadastro. **A escolha entre os dois é de tamanho de
|
|
336
|
+
* lista, e a linha é dura:** este monta todos os itens num `ScrollView` (abaixo), o que serve
|
|
337
|
+
* para unidades, estados e tipos; um catálogo de milhares de linhas aqui não fica lento, trava.
|
|
338
|
+
* O `Combobox` usa `FlatList` e aceita busca remota.
|
|
322
339
|
*/
|
|
323
340
|
export function Select({ items, value, onChange, placeholder, disabled, size, chevron = "chevron--down", style, testID, }) {
|
|
324
341
|
const t = useAureaTokens();
|
package/dist/midia.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type ImageSourcePropType, type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
import { type IconName } from "./icon.js";
|
|
4
|
+
/** URL ou `require()` de um asset local — as duas formas do `Image` do RN, como no `Avatar`. */
|
|
5
|
+
export type AureaImageSource = ImageSourcePropType | string;
|
|
6
|
+
export interface ImageProps {
|
|
7
|
+
source: AureaImageSource;
|
|
8
|
+
/**
|
|
9
|
+
* O nome acessível. **É o `alt` da web, e é obrigatório pelo mesmo motivo que o `label` do
|
|
10
|
+
* `IconButton`:** uma foto sem texto alternativo não diz nada a quem não a vê. Decorativa de
|
|
11
|
+
* verdade se escreve `alt=""` — explícito, como manda a WAI.
|
|
12
|
+
*/
|
|
13
|
+
alt: string;
|
|
14
|
+
/** `16/9`, `"16/9"`, `"16:9"` ou `1.777…`. Sem ela, a imagem ocupa a altura que o `style` der. */
|
|
15
|
+
ratio?: number | string;
|
|
16
|
+
fit?: "cover" | "contain";
|
|
17
|
+
/** O que aparece no lugar quando o bitmap não vem. Sem ele, o glifo `image` sobre a caixa reservada. */
|
|
18
|
+
fallback?: React.ReactNode;
|
|
19
|
+
/** O glifo do substituto. Registre-o, ou passe `false`. */
|
|
20
|
+
fallbackIcon?: IconName | false;
|
|
21
|
+
/**
|
|
22
|
+
* Desenha OUTRO componente de imagem com a nossa pele — é o caso do consumidor que já usa
|
|
23
|
+
* `expo-image` por cache de disco.
|
|
24
|
+
*
|
|
25
|
+
* <Image render={<ExpoImage contentFit="cover" transition={150} />} source={u} alt="…" />
|
|
26
|
+
*
|
|
27
|
+
* Recebe `source`, `style`, `onError` e o nome acessível. **`fit` NÃO é traduzido para ele** —
|
|
28
|
+
* `resizeMode` é do `Image` do RN, e o `expo-image` chama a mesma coisa de `contentFit`; quem
|
|
29
|
+
* passa o elemento escreve a prop dele.
|
|
30
|
+
*/
|
|
31
|
+
render?: React.ReactElement;
|
|
32
|
+
onError?: () => void;
|
|
33
|
+
style?: StyleProp<ViewStyle>;
|
|
34
|
+
testID?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A foto, com a caixa reservada e um substituto quando ela não vem.
|
|
38
|
+
*
|
|
39
|
+
* ```tsx
|
|
40
|
+
* <Image source={foto.uri} alt="Frente do item" ratio="4/3" />
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* ⚠ **A caixa nasce reservada e pintada** com a mesma superfície do `Skeleton` — sem `ratio`, o
|
|
44
|
+
* layout salta quando o bitmap chega, e num telefone esse salto acontece com o dedo já a caminho
|
|
45
|
+
* do botão.
|
|
46
|
+
*
|
|
47
|
+
* ⚠ **O substituto continua sendo a imagem para quem usa leitor de tela**, com o mesmo `alt`.
|
|
48
|
+
* Trocar o bitmap por uma caixa muda o que se vê, não o que a foto É — e o `role="image"` mapeia
|
|
49
|
+
* nos dois sistemas (`ReactAccessibilityDelegate.kt:461`, `RCTConversions.h:96`).
|
|
50
|
+
*/
|
|
51
|
+
export declare function Image({ source, alt, ratio, fit, fallback, fallbackIcon, render, onError, style, testID, }: ImageProps): React.JSX.Element;
|
|
52
|
+
export interface AureaGalleryItem {
|
|
53
|
+
id: string;
|
|
54
|
+
source: AureaImageSource;
|
|
55
|
+
alt: string;
|
|
56
|
+
caption?: React.ReactNode;
|
|
57
|
+
}
|
|
58
|
+
export interface GalleryProps {
|
|
59
|
+
items: AureaGalleryItem[];
|
|
60
|
+
/** O nome da grade para o leitor de tela. Sem ele, a frase `galleryLabel` do provider. */
|
|
61
|
+
label?: string;
|
|
62
|
+
/** O `id` escolhido. **A galeria não guarda escolha** — é constante do app, como na web. */
|
|
63
|
+
selected?: string;
|
|
64
|
+
onSelect?: (id: string) => void;
|
|
65
|
+
/** Tocar abre a foto grande no `Dialog` que já existe. */
|
|
66
|
+
zoom?: boolean;
|
|
67
|
+
/** Proporção dos ladrilhos. Padrão **1** (quadrado), como o `ratio="1/1"` da web. */
|
|
68
|
+
ratio?: number | string;
|
|
69
|
+
/**
|
|
70
|
+
* Largura mínima de cada ladrilho, em dp. O `--gallery-min` da web é `8rem` — e `1rem = 16dp`,
|
|
71
|
+
* medido, então **128**.
|
|
72
|
+
*/
|
|
73
|
+
minTileWidth?: number;
|
|
74
|
+
style?: StyleProp<ViewStyle>;
|
|
75
|
+
testID?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A grade de fotos, e a foto grande quando se toca.
|
|
79
|
+
*
|
|
80
|
+
* ```tsx
|
|
81
|
+
* <Gallery items={fotos} zoom selected={atual} onSelect={setAtual} />
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* ⚠ **Ampliar é DIÁLOGO, e diálogo já existe.** É a trava que a web escreveu no item L2 e ela
|
|
85
|
+
* atravessa inteira: a foto grande abre no `Dialog` do Lote 5, com o confinamento de foco que o
|
|
86
|
+
* `Modal` do RN dá pelo sistema e a saída pelo botão VOLTAR do Android. Uma segunda superfície
|
|
87
|
+
* flutuante aqui seria uma segunda linguagem.
|
|
88
|
+
*
|
|
89
|
+
* ⚠ **Legenda visível torna a miniatura decorativa**, e quem exigiu isso na web foi o axe, não a
|
|
90
|
+
* teoria: com `alt` e legenda dizendo a mesma coisa, o leitor de tela anuncia duas vezes seguidas.
|
|
91
|
+
* Aqui a tradução é outra (não há `alt=""` no RN) mas a regra é a mesma — **com legenda, quem
|
|
92
|
+
* carrega o nome é o LADRILHO, e a imagem sai da árvore**. O `alt` não se perde: ele continua
|
|
93
|
+
* nomeando a foto ampliada, que é onde não há legenda ao lado.
|
|
94
|
+
*
|
|
95
|
+
* ⚠ **Sem `onSelect` e sem `zoom` os ladrilhos não são alvos.** Um `Pressable` que não faz nada é
|
|
96
|
+
* um alvo que engana quem navega por leitor de tela — mesma decisão da web, mesma razão.
|
|
97
|
+
*/
|
|
98
|
+
export declare function Gallery({ items, label, selected, onSelect, zoom, ratio, minTileWidth, style, testID, }: GalleryProps): React.JSX.Element;
|
package/dist/midia.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Aurea nativo — a MÍDIA: `Image` e `Gallery`.
|
|
3
|
+
//
|
|
4
|
+
// Lote 7 do `NATIVE.md` §8, terceira das três lacunas medidas pelo consumidor — e a que ele
|
|
5
|
+
// classificou como acabamento. A frase dele é exata: *"O `PhotoInput` entra foto. Nada mostra
|
|
6
|
+
// foto."* Conferido: o `sistema.tsx:184-225` devolve `AureaPhoto {uri, width, height}` e o único
|
|
7
|
+
// componente do pacote que desenha um bitmap é o `Avatar` (`display.tsx:279-297`), que é outro
|
|
8
|
+
// papel — um retrato redondo do tamanho de um controle, com iniciais por trás.
|
|
9
|
+
//
|
|
10
|
+
// ── A RESPOSTA "USA O `Image` DO RN E NÃO É LACUNA" FOI CONSIDERADA, E RECUSADA ──────────────
|
|
11
|
+
// Era uma das três saídas possíveis, e é tentadora porque o RN já tem um `Image`. Ela cai por
|
|
12
|
+
// medição, não por gosto: a ficha da web (`packages/contracts/registry/Image.json`) diz que o
|
|
13
|
+
// componente é *"uma imagem que RESERVA A CAIXA antes dos bytes chegarem, e CAI PARA UM
|
|
14
|
+
// SUBSTITUTO quando eles nunca chegam"*. O `Image` cru do RN não faz nenhuma das duas.
|
|
15
|
+
//
|
|
16
|
+
// E o custo de não fazê-las já está pago e registrado nesta casa: o `Avatar` foi medido em
|
|
17
|
+
// 31/07/2026 com `src` quebrado e **não caía no substituto**. O conserto virou o `useEffect` por
|
|
18
|
+
// `source` do `display.tsx:283-284`. Mandar cada tela do app reescrever isso é devolver ao
|
|
19
|
+
// consumidor o problema que o design system existe para resolver — e é a terceira vez que esta
|
|
20
|
+
// casa corrigiria a mesma coisa em três lugares (a regra do `CLAUDE.md` sobre correção local).
|
|
21
|
+
//
|
|
22
|
+
// ── O QUE FOI MEDIDO NO CSS, COM A LINHA ─────────────────────────────────────────────────────
|
|
23
|
+
// .image aurea.css:1434 bloco, 100% de largura, `object-fit:cover`, raio LG, fundo surface-3
|
|
24
|
+
// .image-contain :1435 `object-fit:contain`, fundo transparente
|
|
25
|
+
// .image-broken :1436 grade centrada, cor mutedForeground
|
|
26
|
+
// .gallery :1448 grade auto-fill de `--gallery-min` (8rem), gap space-3
|
|
27
|
+
// .gallery-tile :1450 grade com gap space-1, padding space-1, raio LG
|
|
28
|
+
// .gallery-tile:not(.is-selected) :1458 fundo transparente, cor mutedForeground
|
|
29
|
+
// .gallery-caption :1459 textSm
|
|
30
|
+
//
|
|
31
|
+
// ── TRÊS COISAS DA WEB NÃO ATRAVESSAM, E CADA UMA POR UM MOTIVO DIFERENTE ────────────────────
|
|
32
|
+
// `loading="lazy"` não existe no RN — a decodificação preguiçosa é do motor de lista, não da
|
|
33
|
+
// imagem. Quem quer isso usa `FlatList`, que é o que a `Gallery` faz;
|
|
34
|
+
// `ratio="16/9"` a web aceita a STRING do CSS; o `aspectRatio` do RN é **número**. Aqui as
|
|
35
|
+
// duas formas entram, e a string é convertida — quem porta uma tela da web
|
|
36
|
+
// não deveria descobrir isso por um layout de altura zero;
|
|
37
|
+
// `render` a web troca o elemento pelo `useRender` do Base UI. Aqui é `cloneElement`,
|
|
38
|
+
// e serve ao mesmo caso real: o consumidor que já usa `expo-image`.
|
|
39
|
+
import * as React from "react";
|
|
40
|
+
import { Image as ImageRN, Pressable, View, } from "react-native";
|
|
41
|
+
import { criarFolha } from "./estilos.js";
|
|
42
|
+
import { Icon } from "./icon.js";
|
|
43
|
+
import { Grid } from "./layout.js";
|
|
44
|
+
import { Dialog } from "./overlays.js";
|
|
45
|
+
import { Text } from "./text.js";
|
|
46
|
+
import { useAureaStrings, useAureaTokens } from "./theme.js";
|
|
47
|
+
const folha = criarFolha((t) => ({
|
|
48
|
+
// O fundo NÃO é enfeite: ele é o marcador de carregamento inteiro. A web decidiu isso em
|
|
49
|
+
// prosa (`media-client.tsx`, "O MARCADOR DE CARREGAMENTO NÃO TEM ESTADO") e a razão vale aqui
|
|
50
|
+
// com mais força — um `useState` por imagem numa galeria de trinta fotos é trinta renders a
|
|
51
|
+
// mais numa lista que já rola.
|
|
52
|
+
imagem: {
|
|
53
|
+
width: "100%",
|
|
54
|
+
borderRadius: t.size.radiusLg,
|
|
55
|
+
backgroundColor: t.color.surface3 ?? t.color.muted,
|
|
56
|
+
},
|
|
57
|
+
contain: { backgroundColor: "transparent" },
|
|
58
|
+
quebrada: { alignItems: "center", justifyContent: "center" },
|
|
59
|
+
ladrilho: { gap: t.size.space1, padding: t.size.space1, borderRadius: t.size.radiusLg },
|
|
60
|
+
ladrilhoEscolhido: { backgroundColor: t.color.secondary },
|
|
61
|
+
}));
|
|
62
|
+
/** `"16/9"`, `"4:3"` ou o número que o RN quer. Devolve `undefined` quando não dá para ler. */
|
|
63
|
+
function razao(v) {
|
|
64
|
+
if (v == null)
|
|
65
|
+
return undefined;
|
|
66
|
+
if (typeof v === "number")
|
|
67
|
+
return Number.isFinite(v) && v > 0 ? v : undefined;
|
|
68
|
+
// A web escreve `16/9`; alguém vai escrever `16:9`. As duas entram.
|
|
69
|
+
const partes = v.split(/[/:]/);
|
|
70
|
+
if (partes.length === 2) {
|
|
71
|
+
const a = Number(partes[0]), b = Number(partes[1]);
|
|
72
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0)
|
|
73
|
+
return a / b;
|
|
74
|
+
}
|
|
75
|
+
const n = Number(v);
|
|
76
|
+
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
77
|
+
}
|
|
78
|
+
// Interno de propósito: não sai pelo barril. O consumidor escreve `string` ou `require()` e o
|
|
79
|
+
// componente converte — expor o conversor seria superfície pública sem caso de uso.
|
|
80
|
+
const fonteDaImagem = (s) => typeof s === "string" ? { uri: s } : s;
|
|
81
|
+
/**
|
|
82
|
+
* A foto, com a caixa reservada e um substituto quando ela não vem.
|
|
83
|
+
*
|
|
84
|
+
* ```tsx
|
|
85
|
+
* <Image source={foto.uri} alt="Frente do item" ratio="4/3" />
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* ⚠ **A caixa nasce reservada e pintada** com a mesma superfície do `Skeleton` — sem `ratio`, o
|
|
89
|
+
* layout salta quando o bitmap chega, e num telefone esse salto acontece com o dedo já a caminho
|
|
90
|
+
* do botão.
|
|
91
|
+
*
|
|
92
|
+
* ⚠ **O substituto continua sendo a imagem para quem usa leitor de tela**, com o mesmo `alt`.
|
|
93
|
+
* Trocar o bitmap por uma caixa muda o que se vê, não o que a foto É — e o `role="image"` mapeia
|
|
94
|
+
* nos dois sistemas (`ReactAccessibilityDelegate.kt:461`, `RCTConversions.h:96`).
|
|
95
|
+
*/
|
|
96
|
+
export function Image({ source, alt, ratio, fit = "cover", fallback, fallbackIcon = "image", render, onError, style, testID, }) {
|
|
97
|
+
const t = useAureaTokens();
|
|
98
|
+
const s = folha(t);
|
|
99
|
+
const [quebrou, setQuebrou] = React.useState(false);
|
|
100
|
+
// A `source` nova merece uma tentativa nova — é a mesma linha do `Avatar` (`display.tsx:283`),
|
|
101
|
+
// e sem ela uma URL quebrada deixa um buraco permanente mesmo depois de o app trocar a foto.
|
|
102
|
+
React.useEffect(() => { setQuebrou(false); }, [source]);
|
|
103
|
+
const proporcao = razao(ratio);
|
|
104
|
+
const caixa = [
|
|
105
|
+
s.imagem,
|
|
106
|
+
fit === "contain" && s.contain,
|
|
107
|
+
proporcao != null && { aspectRatio: proporcao },
|
|
108
|
+
style,
|
|
109
|
+
];
|
|
110
|
+
if (quebrou) {
|
|
111
|
+
return (_jsx(View, { testID: testID, style: [caixa, s.quebrada], accessible: true, accessibilityRole: "image", accessibilityLabel: alt || undefined, children: fallback ?? (fallbackIcon
|
|
112
|
+
? _jsx(Icon, { name: fallbackIcon, size: "lg", color: t.color.mutedForeground })
|
|
113
|
+
: null) }));
|
|
114
|
+
}
|
|
115
|
+
const comuns = {
|
|
116
|
+
source: fonteDaImagem(source),
|
|
117
|
+
style: caixa,
|
|
118
|
+
onError: () => { setQuebrou(true); onError?.(); },
|
|
119
|
+
// `alt=""` é decorativo explícito: some da árvore em vez de entrar com nome vazio.
|
|
120
|
+
accessible: alt !== "",
|
|
121
|
+
accessibilityRole: alt !== "" ? "image" : undefined,
|
|
122
|
+
accessibilityLabel: alt !== "" ? alt : undefined,
|
|
123
|
+
testID,
|
|
124
|
+
};
|
|
125
|
+
// `cloneElement` e não `useRender`: o idioma do Base UI não existe aqui, e o que o caso real
|
|
126
|
+
// precisa é de um elemento pronto recebendo as nossas props. As props do consumidor vêm
|
|
127
|
+
// primeiro no objeto do elemento e as nossas depois — `source` e `onError` são o contrato
|
|
128
|
+
// deste componente, e deixá-las sobrescrevíveis seria prometer o substituto e não entregá-lo.
|
|
129
|
+
if (render)
|
|
130
|
+
return React.cloneElement(render, comuns);
|
|
131
|
+
// ⚠ O molde é `ImageStyle` e não `ViewStyle`, e a conversão é CONSCIENTE: os dois tipos só
|
|
132
|
+
// divergem em duas coisas — o `overflow` do `ViewStyle` aceita `"scroll"`, que o `ImageStyle`
|
|
133
|
+
// não tem, e o `ImageStyle` soma `resizeMode`/`tintColor`/`overlayColor`. **Nada aqui escreve
|
|
134
|
+
// nenhuma das duas**: a folha põe largura, raio, fundo e proporção, e o `resizeMode` vai como
|
|
135
|
+
// prop, ao lado. A API pública fica em `ViewStyle` de propósito — quem chama pensa em caixa, e
|
|
136
|
+
// fazer o consumidor importar `ImageStyle` para passar um raio seria vazar o primitivo.
|
|
137
|
+
return _jsx(ImageRN, { ...comuns, style: caixa, resizeMode: fit });
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A grade de fotos, e a foto grande quando se toca.
|
|
141
|
+
*
|
|
142
|
+
* ```tsx
|
|
143
|
+
* <Gallery items={fotos} zoom selected={atual} onSelect={setAtual} />
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* ⚠ **Ampliar é DIÁLOGO, e diálogo já existe.** É a trava que a web escreveu no item L2 e ela
|
|
147
|
+
* atravessa inteira: a foto grande abre no `Dialog` do Lote 5, com o confinamento de foco que o
|
|
148
|
+
* `Modal` do RN dá pelo sistema e a saída pelo botão VOLTAR do Android. Uma segunda superfície
|
|
149
|
+
* flutuante aqui seria uma segunda linguagem.
|
|
150
|
+
*
|
|
151
|
+
* ⚠ **Legenda visível torna a miniatura decorativa**, e quem exigiu isso na web foi o axe, não a
|
|
152
|
+
* teoria: com `alt` e legenda dizendo a mesma coisa, o leitor de tela anuncia duas vezes seguidas.
|
|
153
|
+
* Aqui a tradução é outra (não há `alt=""` no RN) mas a regra é a mesma — **com legenda, quem
|
|
154
|
+
* carrega o nome é o LADRILHO, e a imagem sai da árvore**. O `alt` não se perde: ele continua
|
|
155
|
+
* nomeando a foto ampliada, que é onde não há legenda ao lado.
|
|
156
|
+
*
|
|
157
|
+
* ⚠ **Sem `onSelect` e sem `zoom` os ladrilhos não são alvos.** Um `Pressable` que não faz nada é
|
|
158
|
+
* um alvo que engana quem navega por leitor de tela — mesma decisão da web, mesma razão.
|
|
159
|
+
*/
|
|
160
|
+
export function Gallery({ items, label, selected, onSelect, zoom, ratio = 1, minTileWidth = 128, style, testID, }) {
|
|
161
|
+
const s = folha(useAureaTokens());
|
|
162
|
+
const strings = useAureaStrings();
|
|
163
|
+
const [ampliado, setAmpliado] = React.useState(null);
|
|
164
|
+
const interativo = !!onSelect || !!zoom;
|
|
165
|
+
const aberto = items.find((i) => i.id === ampliado) ?? null;
|
|
166
|
+
return (_jsxs(_Fragment, { children: [_jsx(Grid, { testID: testID, minColumnWidth: minTileWidth, accessibilityRole: "list", accessibilityLabel: label ?? strings.galleryLabel, style: style, children: items.map((item) => {
|
|
167
|
+
const temLegenda = item.caption != null;
|
|
168
|
+
const miolo = (_jsxs(_Fragment, { children: [_jsx(Image, { source: item.source, alt: temLegenda ? "" : item.alt, ratio: ratio }), temLegenda && (typeof item.caption === "string"
|
|
169
|
+
? _jsx(Text, { size: "sm", numberOfLines: 2, children: item.caption })
|
|
170
|
+
: item.caption)] }));
|
|
171
|
+
if (!interativo) {
|
|
172
|
+
return _jsx(View, { style: s.ladrilho, children: miolo }, item.id);
|
|
173
|
+
}
|
|
174
|
+
return (_jsx(Pressable, { testID: testID ? `${testID}-${item.id}` : undefined, onPress: () => { onSelect?.(item.id); if (zoom)
|
|
175
|
+
setAmpliado(item.id); }, accessibilityRole: "imagebutton",
|
|
176
|
+
// Com legenda, o nome do ladrilho é a legenda e a imagem já saiu da árvore (o
|
|
177
|
+
// `alt=""` acima). Sem legenda, o nome é o `alt`.
|
|
178
|
+
accessibilityLabel: temLegenda && typeof item.caption === "string"
|
|
179
|
+
? item.caption : item.alt, accessibilityState: { selected: item.id === selected }, style: [s.ladrilho, item.id === selected && s.ladrilhoEscolhido], children: miolo }, item.id));
|
|
180
|
+
}) }), zoom && (_jsx(Dialog, { open: aberto != null, title: aberto ? (typeof aberto.caption === "string" ? aberto.caption : aberto.alt) : "", onClose: () => setAmpliado(null), testID: testID ? `${testID}-ampliada` : undefined, children: aberto && _jsx(Image, { source: aberto.source, alt: aberto.alt, fit: "contain", ratio: ratio }) }))] }));
|
|
181
|
+
}
|
package/dist/numero.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
import { type AureaFieldSize } from "./inputs.js";
|
|
4
|
+
/** Os separadores de um locale, derivados de um número-sonda. */
|
|
5
|
+
export type AureaSeparadores = {
|
|
6
|
+
decimal: string;
|
|
7
|
+
grupo: string;
|
|
8
|
+
};
|
|
9
|
+
/** Descobre o separador decimal e o de milhar do locale. Memoizado por locale. */
|
|
10
|
+
export declare function separadoresDoLocale(locale?: string): AureaSeparadores;
|
|
11
|
+
/**
|
|
12
|
+
* Formata um número para EXIBIÇÃO. Nunca para o valor que o app guarda.
|
|
13
|
+
*
|
|
14
|
+
* ⚠ Sem `Intl`, devolve o número cru com o separador decimal do locale — **não** uma moeda
|
|
15
|
+
* montada à mão. Ver a nota no topo do módulo.
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatarNumero(n: number, locale?: string, format?: Intl.NumberFormatOptions): string;
|
|
18
|
+
/**
|
|
19
|
+
* Lê de volta o que a pessoa digitou. **Devolve `null` quando não há número** — e `null` não é
|
|
20
|
+
* zero: um campo vazio e um campo com `0` são coisas diferentes num lançamento.
|
|
21
|
+
*
|
|
22
|
+
* Aceita o que a pessoa realmente digita ou cola: `R$ 1.234,50`, `1 234,50`, `-12,4`, `12.4`.
|
|
23
|
+
* A regra é simples e por isso previsível — **tudo que não é dígito, sinal ou o separador
|
|
24
|
+
* decimal DO LOCALE é lixo** e sai fora, inclusive o separador de milhar.
|
|
25
|
+
*/
|
|
26
|
+
export declare function lerNumero(texto: string, locale?: string): number | null;
|
|
27
|
+
export interface NumberFieldProps {
|
|
28
|
+
/** Controlado. `null` é **vazio**, e é diferente de `0`. */
|
|
29
|
+
value?: number | null;
|
|
30
|
+
defaultValue?: number;
|
|
31
|
+
onValueChange?: (v: number | null) => void;
|
|
32
|
+
min?: number;
|
|
33
|
+
max?: number;
|
|
34
|
+
/** Quanto os botões somam e tiram. Padrão **1**. */
|
|
35
|
+
step?: number;
|
|
36
|
+
/**
|
|
37
|
+
* As opções do `Intl.NumberFormat`. **Mesma prop, mesmo tipo e mesmo significado da web** —
|
|
38
|
+
* lá elas iam para o Base UI, aqui vão para o `Intl` direto.
|
|
39
|
+
*
|
|
40
|
+
* moeda {style: "currency", currency: "BRL"} com locale "pt-BR"
|
|
41
|
+
* medida {maximumFractionDigits: 1} 12,4
|
|
42
|
+
* contador nenhuma — o padrão já é o inteiro agrupado
|
|
43
|
+
*
|
|
44
|
+
* ⚠ **`notation: "compact"` não é aceito**, e a recusa é medida: está quebrado no motor nos
|
|
45
|
+
* dois sistemas (motor#768, motor#1035). Em `__DEV__` sai aviso; em produção a opção é
|
|
46
|
+
* ignorada, porque um "1,2 mi" errado numa tela de lançamento é pior que "1.234.567".
|
|
47
|
+
*/
|
|
48
|
+
format?: Intl.NumberFormatOptions;
|
|
49
|
+
/** O locale do `Intl`. Sem ele, o do aparelho. */
|
|
50
|
+
locale?: string;
|
|
51
|
+
disabled?: boolean;
|
|
52
|
+
/** Mostra o valor e não deixa editar — os botões também somem. */
|
|
53
|
+
readOnly?: boolean;
|
|
54
|
+
size?: AureaFieldSize;
|
|
55
|
+
/** O nome para o leitor de tela quando não há `Field` em volta. */
|
|
56
|
+
label?: string;
|
|
57
|
+
placeholder?: string;
|
|
58
|
+
/**
|
|
59
|
+
* O teclado. Sem ele a escolha é derivada: **`decimal-pad`**, ou o teclado com sinal quando
|
|
60
|
+
* `min` é negativo — porque o `decimal-pad` do iOS **não tem tecla de menos**, e um campo que
|
|
61
|
+
* aceita −5 e não deixa digitá-lo é um campo quebrado.
|
|
62
|
+
*/
|
|
63
|
+
keyboardType?: "numeric" | "decimal-pad" | "number-pad" | "numbers-and-punctuation";
|
|
64
|
+
/** Os glifos dos botões. Registre-os, ou passe `false` para tirar os dois. */
|
|
65
|
+
icons?: {
|
|
66
|
+
increment: IconNameLocal;
|
|
67
|
+
decrement: IconNameLocal;
|
|
68
|
+
} | false;
|
|
69
|
+
style?: StyleProp<ViewStyle>;
|
|
70
|
+
testID?: string;
|
|
71
|
+
}
|
|
72
|
+
type IconNameLocal = string;
|
|
73
|
+
/**
|
|
74
|
+
* O número que se digita OU se empurra de um em um.
|
|
75
|
+
*
|
|
76
|
+
* ```tsx
|
|
77
|
+
* <Field label="Valor">
|
|
78
|
+
* <NumberField value={valor} onValueChange={setValor}
|
|
79
|
+
* format={{style: "currency", currency: "BRL"}} locale="pt-BR" />
|
|
80
|
+
* </Field>
|
|
81
|
+
*
|
|
82
|
+
* <Field label="Litros">
|
|
83
|
+
* <NumberField value={litros} onValueChange={setLitros}
|
|
84
|
+
* format={{maximumFractionDigits: 1}} locale="pt-BR" min={0} />
|
|
85
|
+
* </Field>
|
|
86
|
+
*
|
|
87
|
+
* <Field label="Quantidade">
|
|
88
|
+
* <NumberField value={qtd} onValueChange={setQtd} min={0} step={1} />
|
|
89
|
+
* </Field>
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* ⚠ **O que sai por `onValueChange` é o número CRU, sempre.** `1234.5`, nunca `"R$ 1.234,50"`.
|
|
93
|
+
* É a mesma trava da ADR-0024 — lá o motor renderiza um input escondido com o valor cru; aqui não
|
|
94
|
+
* há formulário nativo para esconder nada, então o contrato É a assinatura da função.
|
|
95
|
+
*
|
|
96
|
+
* ⚠ **Formata no blur, e só no blur.** Enquanto o campo tem foco, ele mostra exatamente o que foi
|
|
97
|
+
* digitado — essa é a decisão inteira da ADR-0024, e o teste que a cobra está no lote.
|
|
98
|
+
*/
|
|
99
|
+
export declare function NumberField({ value, defaultValue, onValueChange, min, max, step, format, locale, disabled, readOnly, size, label, placeholder, keyboardType, icons, style, testID, }: NumberFieldProps): React.JSX.Element;
|
|
100
|
+
export {};
|