@aurea-uds/native 0.8.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/dist/busca.d.ts +8 -1
- package/dist/busca.js +66 -11
- package/dist/numero.d.ts +9 -1
- package/dist/numero.js +44 -8
- package/package.json +2 -2
package/dist/busca.d.ts
CHANGED
|
@@ -46,6 +46,13 @@ export interface ComboboxProps {
|
|
|
46
46
|
empty?: React.ReactNode;
|
|
47
47
|
/** Mostra o botão de limpar no gatilho quando há escolha. Padrão **true**. */
|
|
48
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;
|
|
49
56
|
disabled?: boolean;
|
|
50
57
|
size?: AureaFieldSize;
|
|
51
58
|
/** O glifo da seta. Registre-o, ou passe `false`. */
|
|
@@ -84,7 +91,7 @@ export interface ComboboxProps {
|
|
|
84
91
|
* na web e pela mesma razão do achado I1 da auditoria: um componente com a sua própria cópia do
|
|
85
92
|
* estado é a segunda fonte de verdade que ninguém sabe que existe.
|
|
86
93
|
*/
|
|
87
|
-
export declare function Combobox({ items, value, onValueChange, onSearchChange, searchDelay, loading, onEndReached, placeholder, searchPlaceholder, empty, clearable, disabled, size, chevron, searchIcon, style, testID, }: ComboboxProps): React.JSX.Element;
|
|
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;
|
|
88
95
|
export interface SearchFieldProps {
|
|
89
96
|
value?: string;
|
|
90
97
|
onChangeText?: (v: string) => void;
|
package/dist/busca.js
CHANGED
|
@@ -67,12 +67,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
67
67
|
// aceita digitação, então `button` é o que ele É nos dois sistemas. Quem digita é o campo da
|
|
68
68
|
// folha, e ele leva `search` — que mapeia nos dois.
|
|
69
69
|
import * as React from "react";
|
|
70
|
-
import { FlatList, Modal, Pressable, TextInput, View, } from "react-native";
|
|
70
|
+
import { Animated, Easing, FlatList, Modal, PanResponder, Pressable, TextInput, View, } from "react-native";
|
|
71
71
|
import { IconButton } from "./actions.js";
|
|
72
72
|
import { criarFolha } from "./estilos.js";
|
|
73
73
|
import { Spinner } from "./feedback.js";
|
|
74
74
|
import { Icon } from "./icon.js";
|
|
75
|
-
import { useCampo } from "./inputs.js";
|
|
75
|
+
import { KeyboardAvoiding, useCampo } from "./inputs.js";
|
|
76
|
+
import { useReduceMotion } from "./movimento.js";
|
|
76
77
|
import { Text } from "./text.js";
|
|
77
78
|
import { useAureaStrings, useAureaTokens } from "./theme.js";
|
|
78
79
|
// As mesmas contas do `inputs.tsx`, e elas são repetidas AQUI de propósito: exportá-las de lá
|
|
@@ -80,7 +81,23 @@ import { useAureaStrings, useAureaTokens } from "./theme.js";
|
|
|
80
81
|
// módulos irmãos é o começo de um acoplamento que ninguém declara. São três linhas.
|
|
81
82
|
const alturaDoTamanho = (t, s) => s === "sm" ? t.size.controlHSm : s === "lg" ? t.size.controlHLg : t.size.controlHMd;
|
|
82
83
|
const fonteDoTamanho = (t, s) => s === "sm" ? t.size.textXs : s === "lg" ? t.size.textBase : t.size.textMd;
|
|
84
|
+
// ⚠ O `13` do tamanho `md` NÃO é número inventado: é o `padding-inline` literal do `.input`
|
|
85
|
+
// (`aurea.css:685`), e é o mesmo valor que o `inputs.tsx:60` usa. Não há token de 13 — a escala
|
|
86
|
+
// tem `space3`=12 e `space4`=16 —, e é por isso que o CSS o escreve cru dos dois lados.
|
|
83
87
|
const respiroDoTamanho = (t, s) => s === "sm" ? t.size.space3 : s === "lg" ? t.size.space4 : 13;
|
|
88
|
+
// ── AS DUAS CONSTANTES DE INTERAÇÃO, e elas NÃO são geometria ────────────────────────────────
|
|
89
|
+
// Ficam nomeadas e no topo porque número solto no meio do componente é o que o `CLAUDE.md`
|
|
90
|
+
// passou a proibir em 12/09/2026. **Nenhuma das duas tem linha de `aurea.css` atrás, e não
|
|
91
|
+
// deveria ter:** uma é tempo de digitação, a outra é fração de viewport do motor de lista.
|
|
92
|
+
//
|
|
93
|
+
// 250 ms é o intervalo entre teclas de quem digita corrido. Abaixo disso a espera não junta
|
|
94
|
+
// letra nenhuma e cada tecla vira uma ida ao servidor; muito acima, a lista demora a reagir e a
|
|
95
|
+
// pessoa acha que o campo travou. `searchDelay` existe para o app discordar.
|
|
96
|
+
const ESPERA_PADRAO = 250;
|
|
97
|
+
// Fração da altura visível que sobra abaixo antes de pedir a próxima página. Meia tela de
|
|
98
|
+
// antecedência: menos que isso e a lista chega ao fim antes da resposta; mais, e o app pagina
|
|
99
|
+
// páginas que ninguém vai ver. É prop do `FlatList`, não medida de desenho.
|
|
100
|
+
const ANTECEDENCIA_DE_PAGINA = 0.5;
|
|
84
101
|
const folha = criarFolha((t) => ({
|
|
85
102
|
// O gatilho é o `.input` da web (`aurea.css:685`), igual ao do `Select` — mesma altura, mesma
|
|
86
103
|
// borda, o mesmo raio de controle.
|
|
@@ -94,20 +111,28 @@ const folha = criarFolha((t) => ({
|
|
|
94
111
|
desabilitado: { opacity: 0.5 },
|
|
95
112
|
// As ações do gatilho — limpar e a seta. O `.combobox-actions` da web (`aurea.css:1673`) é
|
|
96
113
|
// absoluto sobre o campo; aqui é um irmão na linha, porque não há campo por baixo.
|
|
97
|
-
|
|
114
|
+
// `space05` mede 2 — o mesmo valor, agora com nome. O `.combobox-actions` da web
|
|
115
|
+
// (`aurea.css:1673`) usa `gap:2px` literal; aqui o token existe, então é ele que entra.
|
|
116
|
+
acoes: { flexDirection: "row", alignItems: "center", gap: t.size.space05 },
|
|
98
117
|
// A folha, com a mesma anatomia da do `Select` (`inputs.tsx:98-113`) — e é de propósito que
|
|
99
118
|
// sejam iguais: são o mesmo gesto, e duas folhas diferentes para o mesmo gesto é como uma
|
|
100
119
|
// biblioteca deixa de ter linguagem.
|
|
101
120
|
fundoDaLista: { flex: 1, justifyContent: "flex-end", backgroundColor: "#00000080" },
|
|
102
121
|
fundoDeToque: { position: "absolute", top: 0, right: 0, bottom: 0, left: 0 },
|
|
122
|
+
// 🔴 OS 85% E OS 45% ERAM INVENTADOS, e saíram em 12/09/2026. **A casa já tinha a medida:**
|
|
123
|
+
// o `folhaBaixo` do `BottomSheet` do Lote 5 (`overlays.tsx:104`) usa `maxHeight: "90%"`, e
|
|
124
|
+
// duas folhas que sobem do rodapé com alturas diferentes é como uma biblioteca deixa de ter
|
|
125
|
+
// linguagem. O `minHeight` sumiu: folha abraça o conteúdo, e uma altura mínima só servia para
|
|
126
|
+
// desenhar vazio.
|
|
103
127
|
lista: {
|
|
104
|
-
|
|
105
|
-
// uma folha de 60% com teclado aberto deixa três linhas visíveis. O `KeyboardAvoidingView`
|
|
106
|
-
// não entra — dentro de um `Modal` do RN ele não recebe as métricas do teclado no Android.
|
|
107
|
-
maxHeight: "85%", minHeight: "45%",
|
|
128
|
+
maxHeight: "90%",
|
|
108
129
|
borderTopLeftRadius: t.size.radiusCard, borderTopRightRadius: t.size.radiusCard,
|
|
109
130
|
backgroundColor: t.color.popover, borderWidth: t.size.borderWidth, borderColor: t.color.border,
|
|
110
131
|
},
|
|
132
|
+
// O puxador, copiado do `BottomSheet` (`overlays.tsx:111-112`) — mesma medida, mesmo token.
|
|
133
|
+
puxadorArea: { alignItems: "center", paddingVertical: t.size.space2 },
|
|
134
|
+
puxador: { width: 40, height: 4, borderRadius: t.size.radiusFull,
|
|
135
|
+
backgroundColor: t.color.borderStrong },
|
|
111
136
|
// O campo de digitar, no topo da folha. É o `.input-group` da web (`aurea.css:741`): a borda
|
|
112
137
|
// mora no GRUPO e o campo dentro dele é transparente e sem borda.
|
|
113
138
|
grupo: {
|
|
@@ -160,7 +185,7 @@ const folha = criarFolha((t) => ({
|
|
|
160
185
|
* na web e pela mesma razão do achado I1 da auditoria: um componente com a sua própria cópia do
|
|
161
186
|
* estado é a segunda fonte de verdade que ninguém sabe que existe.
|
|
162
187
|
*/
|
|
163
|
-
export function Combobox({ items, value, onValueChange, onSearchChange, searchDelay =
|
|
188
|
+
export function Combobox({ items, value, onValueChange, onSearchChange, searchDelay = ESPERA_PADRAO, loading, onEndReached, placeholder, searchPlaceholder, empty, clearable = true, draggable = true, disabled, size, chevron = "chevron--down", searchIcon = "search", style, testID, }) {
|
|
164
189
|
const t = useAureaTokens();
|
|
165
190
|
const s = folha(t);
|
|
166
191
|
const strings = useAureaStrings();
|
|
@@ -169,6 +194,7 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
|
|
|
169
194
|
const inativo = disabled ?? campo?.disabled;
|
|
170
195
|
const [aberto, setAberto] = React.useState(false);
|
|
171
196
|
const [texto, setTexto] = React.useState("");
|
|
197
|
+
const reduzir = useReduceMotion();
|
|
172
198
|
// ── A ESPERA ────────────────────────────────────────────────────────────────────────────────
|
|
173
199
|
// O timer vive num `ref` e é limpo em três lugares: à digitação seguinte, ao fechar a folha e
|
|
174
200
|
// ao desmontar. O terceiro é o que importa e o que mais se esquece — sem ele, sair da tela com
|
|
@@ -206,6 +232,35 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
|
|
|
206
232
|
setAberto(false);
|
|
207
233
|
setTexto("");
|
|
208
234
|
}, [cancelar]);
|
|
235
|
+
// ── O ARRASTO, copiado do `BottomSheet` do Lote 5 (`overlays.tsx:427-451`) ──────────────────
|
|
236
|
+
// Os números são os DE LÁ, não novos: 6dp para reivindicar, limiar de 80dp ou um terço da
|
|
237
|
+
// altura, velocidade 1.2. Copiar em vez de reinventar é o que mantém as duas folhas com o
|
|
238
|
+
// mesmo tato — e é a regra do `BUILDING.md` sobre não criar escala nova no meio de um
|
|
239
|
+
// componente.
|
|
240
|
+
const arrasto = React.useRef(new Animated.Value(0)).current;
|
|
241
|
+
const altura = React.useRef(0);
|
|
242
|
+
React.useEffect(() => { if (!aberto)
|
|
243
|
+
arrasto.setValue(0); }, [aberto, arrasto]);
|
|
244
|
+
const gestos = React.useMemo(() => PanResponder.create({
|
|
245
|
+
onMoveShouldSetPanResponder: (_e, g) => draggable && g.dy > 6,
|
|
246
|
+
onPanResponderMove: (_e, g) => { if (g.dy > 0)
|
|
247
|
+
arrasto.setValue(g.dy); },
|
|
248
|
+
onPanResponderRelease: (_e, g) => {
|
|
249
|
+
const limiar = Math.max(80, altura.current / 3);
|
|
250
|
+
if (g.dy > limiar || g.vy > 1.2) {
|
|
251
|
+
fechar();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (reduzir !== false) {
|
|
255
|
+
arrasto.setValue(0);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
Animated.timing(arrasto, {
|
|
259
|
+
toValue: 0, duration: t.size.durationSlow, useNativeDriver: true,
|
|
260
|
+
easing: Easing.bezier(...t.easing.easeEmphasized),
|
|
261
|
+
}).start();
|
|
262
|
+
},
|
|
263
|
+
}), [draggable, fechar, arrasto, reduzir, t.size.durationSlow, t.easing.easeEmphasized]);
|
|
209
264
|
// ── O FILTRO, QUANDO ELE É NOSSO ────────────────────────────────────────────────────────────
|
|
210
265
|
const filtrados = React.useMemo(() => {
|
|
211
266
|
if (onSearchChange)
|
|
@@ -222,7 +277,7 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
|
|
|
222
277
|
campo?.invalido && s.invalido,
|
|
223
278
|
inativo && s.desabilitado,
|
|
224
279
|
style,
|
|
225
|
-
], children: [_jsx(Text, { size: tam === "sm" ? "xs" : tam === "lg" ? "base" : "md", tone: value ? "default" : "subtle", numberOfLines: 1, style: { flex: 1, minWidth: 0 }, children: value?.label ?? placeholder ?? "" }), _jsxs(View, { style: s.acoes, children: [clearable && value != null && !inativo && (_jsx(IconButton, { name: "close", label: strings.comboboxClear, appearance: "ghost", size: "sm", onPress: () => onValueChange?.(null), testID: testID ? `${testID}-limpar` : undefined })), chevron && _jsx(Icon, { name: chevron, size: "sm", color: t.color.subtleForeground })] })] }), _jsx(Modal, { visible: aberto, transparent: true, animationType: "slide", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: fechar, children: _jsxs(
|
|
280
|
+
], children: [_jsx(Text, { size: tam === "sm" ? "xs" : tam === "lg" ? "base" : "md", tone: value ? "default" : "subtle", numberOfLines: 1, style: { flex: 1, minWidth: 0 }, children: value?.label ?? placeholder ?? "" }), _jsxs(View, { style: s.acoes, children: [clearable && value != null && !inativo && (_jsx(IconButton, { name: "close", label: strings.comboboxClear, appearance: "ghost", size: "sm", onPress: () => onValueChange?.(null), testID: testID ? `${testID}-limpar` : undefined })), chevron && _jsx(Icon, { name: chevron, size: "sm", color: t.color.subtleForeground })] })] }), _jsx(Modal, { visible: aberto, transparent: true, animationType: reduzir !== false ? "none" : "slide", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: fechar, children: _jsxs(KeyboardAvoiding, { style: s.fundoDaLista, children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: fechar, accessible: false, testID: testID ? `${testID}-fundo` : undefined }), _jsxs(Animated.View, { style: [s.lista, { transform: [{ translateY: arrasto }] }], onLayout: (e) => { altura.current = e.nativeEvent.layout.height; }, onStartShouldSetResponder: () => true, children: [_jsx(View, { style: s.puxadorArea, ...(draggable ? gestos.panHandlers : null), accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", children: _jsx(View, { style: s.puxador }) }), _jsxs(View, { style: [s.grupo, { height: alturaDoTamanho(t, tam) }], ...(draggable ? gestos.panHandlers : null), children: [searchIcon && _jsx(Icon, { name: searchIcon, size: "sm", color: t.color.subtleForeground }), _jsx(TextInput, { testID: testID ? `${testID}-busca` : undefined, value: texto, onChangeText: digitar, placeholder: searchPlaceholder ?? strings.comboboxSearch, placeholderTextColor: t.color.subtleForeground,
|
|
226
281
|
// ⚠ `autoFocus` é o que faz a folha valer a pena: abrir um campo de busca e
|
|
227
282
|
// exigir um segundo toque para o teclado subir é um toque a mais em cada
|
|
228
283
|
// cadastro. O foco entra com a folha; o teclado vem junto.
|
|
@@ -238,7 +293,7 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
|
|
|
238
293
|
color: t.color.foreground },
|
|
239
294
|
] }), texto.length > 0 && (_jsx(IconButton, { name: "close", label: strings.searchClear, appearance: "ghost", size: "sm", onPress: () => digitar(""), testID: testID ? `${testID}-busca-limpar` : undefined }))] }), loading && (_jsx(View, { style: s.carregando, children: _jsx(Spinner, { label: strings.comboboxLoading }) })), nada && (_jsx(View, { style: s.vazio, children: typeof empty === "string" || empty == null
|
|
240
295
|
? _jsx(Text, { size: "sm", tone: "muted", children: empty ?? strings.comboboxEmpty })
|
|
241
|
-
: empty })), _jsx(FlatList, { data: filtrados, keyExtractor: (i) => i.value, keyboardShouldPersistTaps: "handled", onEndReached: onEndReached, onEndReachedThreshold:
|
|
296
|
+
: empty })), _jsx(FlatList, { data: filtrados, keyExtractor: (i) => i.value, keyboardShouldPersistTaps: "handled", onEndReached: onEndReached, onEndReachedThreshold: ANTECEDENCIA_DE_PAGINA, renderItem: ({ item }) => (_jsx(Pressable, { disabled: item.disabled, onPress: () => { onValueChange?.(item); fechar(); }, accessibilityRole: "menuitem", accessibilityState: {
|
|
242
297
|
selected: item.value === value?.value, disabled: !!item.disabled,
|
|
243
298
|
}, style: [
|
|
244
299
|
s.opcao,
|
|
@@ -283,7 +338,7 @@ const dobrar = (v) => {
|
|
|
283
338
|
* ⚠ **A borda mora no GRUPO, não no campo** — é o `.input-group` da web (`aurea.css:741`), e é o
|
|
284
339
|
* que deixa lupa, campo e botão de limpar dentro de uma caixa só em vez de três.
|
|
285
340
|
*/
|
|
286
|
-
export function SearchField({ value, onChangeText, onSearchChange, searchDelay =
|
|
341
|
+
export function SearchField({ value, onChangeText, onSearchChange, searchDelay = ESPERA_PADRAO, placeholder, disabled, size, icon = "search", clearable = true, onSubmit, style, testID, }) {
|
|
287
342
|
const t = useAureaTokens();
|
|
288
343
|
const s = folha(t);
|
|
289
344
|
const strings = useAureaStrings();
|
package/dist/numero.d.ts
CHANGED
|
@@ -52,6 +52,14 @@ export interface NumberFieldProps {
|
|
|
52
52
|
/** Mostra o valor e não deixa editar — os botões também somem. */
|
|
53
53
|
readOnly?: boolean;
|
|
54
54
|
size?: AureaFieldSize;
|
|
55
|
+
/**
|
|
56
|
+
* Ocupa a largura disponível em vez de abraçar o conteúdo. Padrão **false**.
|
|
57
|
+
*
|
|
58
|
+
* O padrão segue o `.number-field` da web, que é `inline-flex` (`aurea.css:704`) — e o mesmo
|
|
59
|
+
* eixo existe na HeroUI (`fullWidth`, medido no inventário). **Ligue para moeda:** o campo em
|
|
60
|
+
* repouso tem `--space-16` (64dp), que cabe um contador e não cabe `R$ 1.234,50`.
|
|
61
|
+
*/
|
|
62
|
+
fullWidth?: boolean;
|
|
55
63
|
/** O nome para o leitor de tela quando não há `Field` em volta. */
|
|
56
64
|
label?: string;
|
|
57
65
|
placeholder?: string;
|
|
@@ -96,5 +104,5 @@ type IconNameLocal = string;
|
|
|
96
104
|
* ⚠ **Formata no blur, e só no blur.** Enquanto o campo tem foco, ele mostra exatamente o que foi
|
|
97
105
|
* digitado — essa é a decisão inteira da ADR-0024, e o teste que a cobra está no lote.
|
|
98
106
|
*/
|
|
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;
|
|
107
|
+
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;
|
|
100
108
|
export {};
|
package/dist/numero.js
CHANGED
|
@@ -53,18 +53,32 @@ import { useAureaStrings, useAureaTokens } from "./theme.js";
|
|
|
53
53
|
const alturaDoTamanho = (t, s) => s === "sm" ? t.size.controlHSm : s === "lg" ? t.size.controlHLg : t.size.controlHMd;
|
|
54
54
|
const fonteDoTamanho = (t, s) => s === "sm" ? t.size.textXs : s === "lg" ? t.size.textBase : t.size.textMd;
|
|
55
55
|
const folha = criarFolha((t) => ({
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
// O `.number-field-
|
|
59
|
-
//
|
|
60
|
-
// um campo
|
|
56
|
+
// 🔴 ELE ESTICAVA, E NÃO DEVIA — achado em 12/09/2026, olhando a primeira imagem da vitrine.
|
|
57
|
+
//
|
|
58
|
+
// O `.number-field` da web é `inline-flex` (`aurea.css:704`) e o `.number-field-group` também
|
|
59
|
+
// (`:705`): os dois **ABRAÇAM o conteúdo**. A primeira versão daqui punha `flex: 1` no campo,
|
|
60
|
+
// que PREENCHE — e o resultado é um campo enorme com o `−` e o `+` jogados nas pontas. Era
|
|
61
|
+
// divergência do nosso próprio CSS, entregue sem declarar, e ela só apareceu quando houve
|
|
62
|
+
// imagem para olhar.
|
|
63
|
+
//
|
|
64
|
+
// ⚠ **A saída é o eixo da HeroUI, e não uma invenção minha:** o `number-field` deles publica
|
|
65
|
+
// `fullWidth: [base, false, group, true]` (`INVENTORY-HEROUI.json`, medido em 22/08/2026).
|
|
66
|
+
// Esticar ou abraçar é DECISÃO DE USO, então vira prop — com o padrão em abraçar, que é o que
|
|
67
|
+
// o nosso CSS já dizia.
|
|
68
|
+
grupo: { flexDirection: "row", alignItems: "center", gap: t.size.space1,
|
|
69
|
+
alignSelf: "flex-start" },
|
|
70
|
+
grupoLargo: { alignSelf: "stretch" },
|
|
71
|
+
// `--space-16` = 64dp, medido. É a largura do `.number-field-input` (`aurea.css:706`), e ela
|
|
72
|
+
// serve ao caso que o componente foi feito para: contador e medida curta. **Moeda formatada
|
|
73
|
+
// não cabe em 64dp — nem aqui nem na web**, e é para isso que existe o `fullWidth`.
|
|
61
74
|
campo: {
|
|
62
|
-
|
|
75
|
+
width: t.size.space16, textAlign: "center",
|
|
63
76
|
borderWidth: t.size.borderWidth, borderColor: t.color.borderStrong,
|
|
64
77
|
borderRadius: t.size.radiusControl, backgroundColor: t.color.fieldBg,
|
|
65
78
|
// A mesma correção de Android do `Input` do Lote 4 — ver `inputs.tsx:280-296`.
|
|
66
79
|
paddingVertical: 0, textAlignVertical: "center",
|
|
67
80
|
},
|
|
81
|
+
campoLargo: { width: undefined, flex: 1, minWidth: t.size.space16 },
|
|
68
82
|
invalido: { borderColor: t.color.danger400 ?? t.color.destructive },
|
|
69
83
|
desabilitado: { opacity: 0.5 },
|
|
70
84
|
}));
|
|
@@ -218,7 +232,7 @@ const paraEdicao = (n, locale) => {
|
|
|
218
232
|
* ⚠ **Formata no blur, e só no blur.** Enquanto o campo tem foco, ele mostra exatamente o que foi
|
|
219
233
|
* digitado — essa é a decisão inteira da ADR-0024, e o teste que a cobra está no lote.
|
|
220
234
|
*/
|
|
221
|
-
export function NumberField({ value, defaultValue, onValueChange, min, max, step = 1, format, locale, disabled, readOnly, size, label, placeholder, keyboardType, icons = { increment: "add", decrement: "subtract" }, style, testID, }) {
|
|
235
|
+
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, }) {
|
|
222
236
|
const t = useAureaTokens();
|
|
223
237
|
const s = folha(t);
|
|
224
238
|
const strings = useAureaStrings();
|
|
@@ -279,13 +293,35 @@ export function NumberField({ value, defaultValue, onValueChange, min, max, step
|
|
|
279
293
|
return direcao === 1 ? (max != null && base >= max) : (min != null && base <= min);
|
|
280
294
|
};
|
|
281
295
|
const mostrarBotoes = icons !== false && !readOnly;
|
|
282
|
-
|
|
296
|
+
// 🔴 ACHADO PELA REFERÊNCIA, e não por mim — 12/09/2026. O inventário da HeroUI que já morava
|
|
297
|
+
// no repositório (`audit/activity-2/INVENTORY-HEROUI.json`, medido em 22/08 sobre
|
|
298
|
+
// `@heroui/react@3.2.4`) lista os estados do `number-field` deles:
|
|
299
|
+
//
|
|
300
|
+
// disabled · focus-visible · FOCUS-WITHIN · hovered · invalid · pressed
|
|
301
|
+
//
|
|
302
|
+
// Este componente nasceu **sem estado de foco nenhum** — e era o ÚNICO campo de texto do pacote
|
|
303
|
+
// sem ele: o `Input` do Lote 4 marca (`inputs.tsx:344`), o `Textarea` herda, o `SearchField`
|
|
304
|
+
// marca (`busca.tsx:520`). Inconsistência dentro da própria biblioteca, e invisível em teste
|
|
305
|
+
// até alguém comparar com uma referência.
|
|
306
|
+
//
|
|
307
|
+
// ⚠ **A BORDA vai no CAMPO, e não no grupo — e aqui a Aurea diverge da HeroUI de propósito.**
|
|
308
|
+
// Lá o `Group` carrega a caixa e o `Input` fica nu dentro dela. Aqui não: o `.number-field-group`
|
|
309
|
+
// do nosso CSS (`aurea.css:705`) é só `inline-flex` + `gap`, e quem tem borda é o `.input`
|
|
310
|
+
// (`aurea.css:685`), com os dois botões FORA dela. O estado que faltava é o deles; a geometria
|
|
311
|
+
// continua sendo a nossa.
|
|
312
|
+
const [focado, setFocado] = React.useState(false);
|
|
313
|
+
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: setEmEdicao, onFocus: () => { setFocado(true); setEmEdicao(paraEdicao(numero, locale)); }, onBlur: () => { setFocado(false); confirmar(); }, editable: !inativo && !readOnly, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, keyboardType: keyboardType ?? tecladoPadrao,
|
|
283
314
|
// O nome vem do `Field`, como em todo controle deste pacote — e `label` cobre quem usa o
|
|
284
315
|
// campo solto. Sem um dos dois, é um nó anônimo para o leitor de tela.
|
|
285
316
|
accessibilityLabel: label ?? campo?.label, accessibilityHint: campo?.hint, accessibilityState: { disabled: !!inativo }, "aria-invalid": campo?.invalido, style: [
|
|
286
317
|
s.campo,
|
|
318
|
+
fullWidth && s.campoLargo,
|
|
287
319
|
{ height: alturaDoTamanho(t, tam), fontSize: fonteDoTamanho(t, tam),
|
|
288
320
|
fontFamily: t.font.ui[400], color: t.color.foreground },
|
|
289
321
|
campo?.invalido && s.invalido,
|
|
322
|
+
// A ORDEM IMPORTA e é a mesma do `Input` do Lote 4: o inválido vem antes, o foco
|
|
323
|
+
// depois. Um campo inválido que está sendo corrigido tem de mostrar que está ativo —
|
|
324
|
+
// invertendo, a pessoa digita sem pista nenhuma de onde o teclado está batendo.
|
|
325
|
+
focado && { borderColor: t.color.focusStrong },
|
|
290
326
|
] }), mostrarBotoes && (_jsx(IconButton, { name: icons.increment, label: strings.increment, appearance: "ghost", size: tam, disabled: inativo || noLimite(1), onPress: () => empurrar(1), testID: testID ? `${testID}-mais` : undefined }))] }));
|
|
291
327
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurea-uds/native",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
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.1"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@react-native-community/datetimepicker": ">=8",
|