@aurea-uds/native 0.8.3 → 0.8.5
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.js +19 -2
- package/dist/chart.js +18 -21
- package/dist/display.d.ts +14 -1
- package/dist/display.js +3 -3
- package/dist/estilos.d.ts +15 -0
- package/dist/estilos.js +17 -0
- package/dist/feedback.d.ts +1 -1
- package/dist/feedback.js +22 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -1
- package/dist/inputs.d.ts +11 -2
- package/dist/inputs.js +43 -7
- package/dist/navigation.d.ts +48 -0
- package/dist/navigation.js +105 -40
- package/dist/strings.d.ts +2 -0
- package/dist/strings.js +2 -0
- package/package.json +2 -2
package/dist/busca.js
CHANGED
|
@@ -114,6 +114,23 @@ const folha = criarFolha((t) => ({
|
|
|
114
114
|
// `space05` mede 2 — o mesmo valor, agora com nome. O `.combobox-actions` da web
|
|
115
115
|
// (`aurea.css:1673`) usa `gap:2px` literal; aqui o token existe, então é ele que entra.
|
|
116
116
|
acoes: { flexDirection: "row", alignItems: "center", gap: t.size.space05 },
|
|
117
|
+
// 🔴 O GATILHO É IRMÃO DO X, NÃO ANCESTRAL — defeito achado pelo consumidor em 16/09/2026.
|
|
118
|
+
// O `IconButton` de limpar vivia DENTRO deste `Pressable`, e um `Pressable` nasce `accessible`
|
|
119
|
+
// (`Pressable.js:274`). No iOS esse `accessible` vira `isAccessibilityElement`
|
|
120
|
+
// (`RCTViewComponentView.mm:398`), e quem é elemento de acessibilidade NÃO expõe os filhos —
|
|
121
|
+
// então o X existia na tela e não existia para o VoiceOver.
|
|
122
|
+
// ⚠ **No Android não sumia**, e é por isso que o defeito atravessou o smoke inteiro: lá
|
|
123
|
+
// `accessible` só liga `isFocusable` (`ReactViewManager.kt:96`) e o `ReactViewGroup.kt:1048`
|
|
124
|
+
// continua entregando os filhos à árvore. Um aparelho só nunca responderia esta pergunta.
|
|
125
|
+
// ⚠ É a MESMA forma dos dois consertos que este pacote já carrega (`busca.tsx` fundo da folha,
|
|
126
|
+
// `inputs.tsx:909`): **num sistema de toque, o irmão resolve o que o ancestral quebra.**
|
|
127
|
+
// Nenhuma distância nova: `flex`, `minWidth` e `100%` são relações, não medidas.
|
|
128
|
+
gatilho: { flex: 1, minWidth: 0, height: "100%", flexDirection: "row", alignItems: "center" },
|
|
129
|
+
// A seta deixou de ser enfeite dentro do gatilho e virou toque próprio — é onde a pessoa toca
|
|
130
|
+
// para abrir. `accessible={false}` no JSX porque o gatilho JÁ anuncia tudo: um segundo botão
|
|
131
|
+
// aqui seria a mesma informação duas vezes. O `Icon` sem rótulo já se esconde sozinho
|
|
132
|
+
// (`icon.tsx:104`), então não sobra nada para o leitor de tela tropeçar.
|
|
133
|
+
setaToque: { height: "100%", justifyContent: "center" },
|
|
117
134
|
// A folha, com a mesma anatomia da do `Select` (`inputs.tsx:98-113`) — e é de propósito que
|
|
118
135
|
// sejam iguais: são o mesmo gesto, e duas folhas diferentes para o mesmo gesto é como uma
|
|
119
136
|
// biblioteca deixa de ter linguagem.
|
|
@@ -271,13 +288,13 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
|
|
|
271
288
|
return items.filter((i) => dobrar(i.label).includes(alvo));
|
|
272
289
|
}, [items, texto, onSearchChange]);
|
|
273
290
|
const nada = !loading && filtrados.length === 0;
|
|
274
|
-
return (_jsxs(_Fragment, { children: [_jsxs(
|
|
291
|
+
return (_jsxs(_Fragment, { children: [_jsxs(View, { style: [
|
|
275
292
|
s.caixa,
|
|
276
293
|
{ height: alturaDoTamanho(t, tam), paddingHorizontal: respiroDoTamanho(t, tam) },
|
|
277
294
|
campo?.invalido && s.invalido,
|
|
278
295
|
inativo && s.desabilitado,
|
|
279
296
|
style,
|
|
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,
|
|
297
|
+
], children: [_jsx(Pressable, { testID: testID, onPress: inativo ? undefined : () => setAberto(true), disabled: inativo, accessibilityRole: "button", accessibilityLabel: campo?.label, accessibilityHint: campo?.hint, accessibilityValue: { text: value?.label }, accessibilityState: { disabled: !!inativo, expanded: aberto }, style: s.gatilho, 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(Pressable, { accessible: false, disabled: inativo, style: s.setaToque, onPress: inativo ? undefined : () => setAberto(true), testID: testID ? `${testID}-seta` : undefined, children: _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,
|
|
281
298
|
// ⚠ `autoFocus` é o que faz a folha valer a pena: abrir um campo de busca e
|
|
282
299
|
// exigir um segundo toque para o teclado subir é um toque a mais em cada
|
|
283
300
|
// cadastro. O foco entra com a folha; o teclado vem junto.
|
package/dist/chart.js
CHANGED
|
@@ -169,27 +169,24 @@ export function Chart({ labels, series, label, mark = "line", height = ALTURA, y
|
|
|
169
169
|
return d.trim();
|
|
170
170
|
};
|
|
171
171
|
const tocar = (i) => { setTocado(i); onSelect?.(i); };
|
|
172
|
-
return (_jsxs(View, { accessible: true,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const v = serie.data[selecionado];
|
|
191
|
-
return v == null ? null : (_jsx(Circle, { cx: x(selecionado), cy: y(v), r: 4, fill: cores[n], stroke: t.color.card, strokeWidth: 2 }, n));
|
|
192
|
-
})] })) : null] })) : null, largura > 0 ? (_jsx(View, { style: { position: "absolute", left: CALHA.esquerda, top: CALHA.topo,
|
|
172
|
+
return (_jsxs(View, { style: [s.caixa, style], testID: testID, children: [_jsxs(View, { onLayout: (e) => setLargura(e.nativeEvent.layout.width), style: { height }, children: [largura > 0 ? (_jsx(View, { accessible: true, accessibilityLabel: nome, children: _jsxs(Svg, { width: largura, height: height, testID: testID ? `${testID}-svg` : undefined, children: [_jsx(G, { children: passos.map((v, i) => (_jsx(Line, { x1: CALHA.esquerda, y1: y(v), x2: largura - CALHA.direita, y2: y(v), stroke: t.color.border, strokeWidth: t.size.borderWidth }, `g${i}`))) }), _jsxs(G, { children: [passos.map((v, i) => (_jsx(SvgText, { x: CALHA.esquerda - 6, y: y(v) + 4, textAnchor: "end", fontSize: t.size.textXs, fontFamily: t.font.ui[400], fill: t.color.mutedForeground, children: formatValue(v) }, `ty${i}`))), labels.map((rotulo, i) => (_jsx(SvgText, { x: x(i), y: height - 6, textAnchor: "middle", fontSize: t.size.textXs, fontFamily: t.font.ui[400], fill: t.color.mutedForeground, children: rotulo }, `tx${i}`)))] }), series.map((serie, n) => {
|
|
173
|
+
const marca = serie.mark ?? mark;
|
|
174
|
+
const cor = cores[n];
|
|
175
|
+
if (marca === "bar") {
|
|
176
|
+
// As barras de várias séries dividem a faixa, com 2dp de respiro entre elas — o
|
|
177
|
+
// vão é o que impede duas cores vizinhas de virarem um bloco só.
|
|
178
|
+
const barras = series.filter((z) => (z.mark ?? mark) === "bar").length;
|
|
179
|
+
const ordem = series.filter((z) => (z.mark ?? mark) === "bar").indexOf(serie);
|
|
180
|
+
const larguraBarra = Math.max(1, (faixa * 0.7) / barras - 2);
|
|
181
|
+
return (_jsx(G, { children: serie.data.map((v, i) => v == null ? null : (_jsx(Rect, { x: x(i) - (faixa * 0.7) / 2 + ordem * (larguraBarra + 2), y: Math.min(y(v), y(0)), width: larguraBarra, height: Math.max(1, Math.abs(y(v) - y(0))),
|
|
182
|
+
// 4dp arredondado na ponta do dado, ancorado na base — a base fica reta.
|
|
183
|
+
rx: 4, fill: cor }, i))) }, n));
|
|
184
|
+
}
|
|
185
|
+
return (_jsxs(G, { children: [marca === "area" ? (_jsx(Path, { d: caminho(serie.data, true), fill: cor, fillOpacity: 0.15 })) : null, _jsx(Path, { d: caminho(serie.data, false), stroke: cor, strokeWidth: 2, fill: "none", strokeLinecap: "round", strokeLinejoin: "round" })] }, n));
|
|
186
|
+
}), selecionado != null && selecionado >= 0 && selecionado < labels.length ? (_jsxs(G, { children: [_jsx(Line, { x1: x(selecionado), y1: CALHA.topo, x2: x(selecionado), y2: CALHA.topo + areaA, stroke: t.color.borderStrong, strokeWidth: t.size.borderWidth }), series.map((serie, n) => {
|
|
187
|
+
const v = serie.data[selecionado];
|
|
188
|
+
return v == null ? null : (_jsx(Circle, { cx: x(selecionado), cy: y(v), r: 4, fill: cores[n], stroke: t.color.card, strokeWidth: 2 }, n));
|
|
189
|
+
})] })) : null] }) })) : null, largura > 0 ? (_jsx(View, { style: { position: "absolute", left: CALHA.esquerda, top: CALHA.topo,
|
|
193
190
|
width: areaL, height: areaA, flexDirection: "row" }, children: labels.map((rotulo, i) => (_jsx(Pressable, { onPress: () => tocar(i), accessibilityRole: "button", accessibilityState: { selected: selecionado === i }, accessibilityLabel: [
|
|
194
191
|
rotulo,
|
|
195
192
|
...series.map((serie) => {
|
package/dist/display.d.ts
CHANGED
|
@@ -17,6 +17,19 @@ export interface BadgeProps extends ViewProps {
|
|
|
17
17
|
size?: AureaBadgeSize;
|
|
18
18
|
/** Um ponto antes do texto. Sozinho (sem conteúdo), o selo VIRA o ponto. */
|
|
19
19
|
dot?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Conteúdo antes do texto — na prática, um glifo (`<Icon name="checkmark" size="sm" />`).
|
|
22
|
+
*
|
|
23
|
+
* ⚠ **É NÓ e não nome de ícone, e a razão é a mesma da web** (`markup.tsx:126`): o `Icon` vive
|
|
24
|
+
* noutro módulo, e receber o nome obrigaria este arquivo a importá-lo — trazendo o registro de
|
|
25
|
+
* ícones ao grafo de todo app que só quer um selo. É a cláusula 4 da ADR-0038, e é o mesmo
|
|
26
|
+
* desenho que o `Button` ganhou na `0.8.3`.
|
|
27
|
+
*
|
|
28
|
+
* ⚠ **O slot NÃO tinge o que recebe.** Quem passa o glifo escolhe a cor dele.
|
|
29
|
+
*/
|
|
30
|
+
leading?: React.ReactNode;
|
|
31
|
+
/** O mesmo, depois do texto. */
|
|
32
|
+
trailing?: React.ReactNode;
|
|
20
33
|
count?: number;
|
|
21
34
|
/** Acima disto o selo mostra `99+`. */
|
|
22
35
|
max?: number;
|
|
@@ -45,7 +58,7 @@ export interface BadgeProps extends ViewProps {
|
|
|
45
58
|
* usa selo com miniatura, e prop sem consumidor é superfície pública para manter de graça. Volta
|
|
46
59
|
* quando houver tela que peça.
|
|
47
60
|
*/
|
|
48
|
-
export declare function Badge({ tone, emphasis, size, dot, count, max, showZero, anchor, badgeContent, invisible, children, style, ...rest }: BadgeProps): React.JSX.Element;
|
|
61
|
+
export declare function Badge({ tone, emphasis, size, dot, count, max, showZero, leading, trailing, anchor, badgeContent, invisible, children, style, ...rest }: BadgeProps): React.JSX.Element;
|
|
49
62
|
export type AureaStatusVariant = "neutral" | "online" | "offline" | "busy" | "away" | "success" | "warning" | "danger" | "info";
|
|
50
63
|
export interface StatusProps extends ViewProps {
|
|
51
64
|
variant?: AureaStatusVariant;
|
package/dist/display.js
CHANGED
|
@@ -83,7 +83,7 @@ export const formatarContagem = (count, max = 99) => count > max ? `${max}+` : S
|
|
|
83
83
|
* usa selo com miniatura, e prop sem consumidor é superfície pública para manter de graça. Volta
|
|
84
84
|
* quando houver tela que peça.
|
|
85
85
|
*/
|
|
86
|
-
export function Badge({ tone = "neutral", emphasis = "soft", size = "md", dot, count, max = 99, showZero, anchor, badgeContent, invisible, children, style, ...rest }) {
|
|
86
|
+
export function Badge({ tone = "neutral", emphasis = "soft", size = "md", dot, count, max = 99, showZero, leading, trailing, anchor, badgeContent, invisible, children, style, ...rest }) {
|
|
87
87
|
const t = useAureaTokens();
|
|
88
88
|
const s = folha(t);
|
|
89
89
|
const acento = tone === "primary" ? t.color.primaryEmphasis
|
|
@@ -119,9 +119,9 @@ export function Badge({ tone = "neutral", emphasis = "soft", size = "md", dot, c
|
|
|
119
119
|
backgroundColor: acento, borderWidth: 0 },
|
|
120
120
|
style,
|
|
121
121
|
], ...(anchor ? { accessibilityElementsHidden: true,
|
|
122
|
-
importantForAccessibility: "no-hide-descendants" } : rest), children: [dot && !soPonto && _jsx(View, { style: [s.ponto, { backgroundColor: corDoTexto }] }), typeof miolo === "string" || typeof miolo === "number"
|
|
122
|
+
importantForAccessibility: "no-hide-descendants" } : rest), children: [dot && !soPonto && _jsx(View, { style: [s.ponto, { backgroundColor: corDoTexto }] }), !soPonto && leading, typeof miolo === "string" || typeof miolo === "number"
|
|
123
123
|
? _jsx(Text, { size: size === "lg" ? "sm" : "xs", weight: 500, leading: "none", style: { color: corDoTexto }, children: miolo })
|
|
124
|
-
: miolo] }));
|
|
124
|
+
: miolo, !soPonto && trailing] }));
|
|
125
125
|
if (!anchor)
|
|
126
126
|
return selo;
|
|
127
127
|
const escondido = invisible || (count === 0 && !showZero) || (miolo == null && !dot);
|
package/dist/estilos.d.ts
CHANGED
|
@@ -15,4 +15,19 @@ type NamedStyles<T> = {
|
|
|
15
15
|
[P in keyof T]: ViewStyle | TextStyle | ImageStyle;
|
|
16
16
|
};
|
|
17
17
|
export declare function criarFolha<T extends NamedStyles<T> | NamedStyles<Record<string, unknown>>>(descrever: (t: AureaTokens) => T & NamedStyles<Record<string, unknown>>): (t: AureaTokens) => T;
|
|
18
|
+
/**
|
|
19
|
+
* O `color-mix(in srgb, X 12%, transparent)` do CSS, traduzido.
|
|
20
|
+
*
|
|
21
|
+
* **Não há `color-mix` no React Native** — medido no interpretador de cor, o mesmo que a ADR-0027
|
|
22
|
+
* já tinha atravessado a pé. O que existe é hex de OITO dígitos, e `#RRGGBBAA` com o alfa certo
|
|
23
|
+
* dá o mesmo pixel sobre um fundo opaco. Cai para a cor crua se ela não for hex de seis, para
|
|
24
|
+
* nunca produzir uma string que o RN descarte em silêncio.
|
|
25
|
+
*
|
|
26
|
+
* ⚠ **Ele morava dentro do `navigation.tsx` e mudou de casa em 17/09/2026**, quando o
|
|
27
|
+
* `SegmentedControl` passou a precisar do mesmo cálculo para o fio do escolhido. Copiar a função
|
|
28
|
+
* para o segundo arquivo é exatamente o defeito que o `CLAUDE.md` nomeia — *correção local é
|
|
29
|
+
* proibida sem responder "quem mais tem esse problema?"*. **Não é exportado pela porta da frente
|
|
30
|
+
* do pacote**: é ferramenta de dentro, e o `check 39` cobraria teste de tudo que sai por lá.
|
|
31
|
+
*/
|
|
32
|
+
export declare const comOpacidade: (cor: string, pct: number) => string;
|
|
18
33
|
export {};
|
package/dist/estilos.js
CHANGED
|
@@ -29,3 +29,20 @@ export function criarFolha(descrever) {
|
|
|
29
29
|
return folha;
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* O `color-mix(in srgb, X 12%, transparent)` do CSS, traduzido.
|
|
34
|
+
*
|
|
35
|
+
* **Não há `color-mix` no React Native** — medido no interpretador de cor, o mesmo que a ADR-0027
|
|
36
|
+
* já tinha atravessado a pé. O que existe é hex de OITO dígitos, e `#RRGGBBAA` com o alfa certo
|
|
37
|
+
* dá o mesmo pixel sobre um fundo opaco. Cai para a cor crua se ela não for hex de seis, para
|
|
38
|
+
* nunca produzir uma string que o RN descarte em silêncio.
|
|
39
|
+
*
|
|
40
|
+
* ⚠ **Ele morava dentro do `navigation.tsx` e mudou de casa em 17/09/2026**, quando o
|
|
41
|
+
* `SegmentedControl` passou a precisar do mesmo cálculo para o fio do escolhido. Copiar a função
|
|
42
|
+
* para o segundo arquivo é exatamente o defeito que o `CLAUDE.md` nomeia — *correção local é
|
|
43
|
+
* proibida sem responder "quem mais tem esse problema?"*. **Não é exportado pela porta da frente
|
|
44
|
+
* do pacote**: é ferramenta de dentro, e o `check 39` cobraria teste de tudo que sai por lá.
|
|
45
|
+
*/
|
|
46
|
+
export const comOpacidade = (cor, pct) => /^#[0-9a-fA-F]{6}$/.test(cor)
|
|
47
|
+
? cor + Math.round(Math.max(0, Math.min(1, pct)) * 255).toString(16).padStart(2, "0")
|
|
48
|
+
: cor;
|
package/dist/feedback.d.ts
CHANGED
|
@@ -84,7 +84,7 @@ export interface AlertProps extends ViewProps {
|
|
|
84
84
|
* import InformationFilled from "@aurea-uds/native/icons/information--filled";
|
|
85
85
|
* const ICONES = criarRegistroDeIcones({"information--filled": InformationFilled, …});
|
|
86
86
|
*/
|
|
87
|
-
export declare function Alert({ variant, state, title, icon, onDismiss, children, style, ...rest }: AlertProps): React.JSX.Element;
|
|
87
|
+
export declare function Alert({ variant, state, title, icon, onDismiss, children, style, accessibilityLabel, accessibilityHint, ...rest }: AlertProps): React.JSX.Element;
|
|
88
88
|
export interface EmptyStateProps {
|
|
89
89
|
icon?: IconName;
|
|
90
90
|
title: React.ReactNode;
|
package/dist/feedback.js
CHANGED
|
@@ -53,6 +53,14 @@ const folha = criarFolha((t) => ({
|
|
|
53
53
|
borderWidth: t.size.borderWidth, borderRadius: t.size.radiusLg,
|
|
54
54
|
backgroundColor: t.color.secondary, borderColor: t.color.border,
|
|
55
55
|
},
|
|
56
|
+
// O GRUPO de leitura: ícone + texto, lidos como uma coisa só. Ele existe porque a moldura
|
|
57
|
+
// deixou de ser `accessible` (ver o comentário no componente) — e sem um grupo o título e o
|
|
58
|
+
// corpo virariam duas paradas separadas do leitor de tela.
|
|
59
|
+
// Nenhuma medida nova: `flex`, `minWidth` e o MESMO `gap` da moldura. O "X" fica de fora.
|
|
60
|
+
grupoDoAlerta: {
|
|
61
|
+
flex: 1, minWidth: 0,
|
|
62
|
+
flexDirection: "row", alignItems: "flex-start", gap: t.size.space3,
|
|
63
|
+
},
|
|
56
64
|
alerta_info: { backgroundColor: t.color.infoBg, borderColor: t.color.info },
|
|
57
65
|
alerta_success: { backgroundColor: t.color.successBg, borderColor: t.color.success },
|
|
58
66
|
alerta_warning: { backgroundColor: t.color.warningBg, borderColor: t.color.warning },
|
|
@@ -176,7 +184,11 @@ export const ICONE_DA_VARIANTE = {
|
|
|
176
184
|
* import InformationFilled from "@aurea-uds/native/icons/information--filled";
|
|
177
185
|
* const ICONES = criarRegistroDeIcones({"information--filled": InformationFilled, …});
|
|
178
186
|
*/
|
|
179
|
-
export function Alert({ variant, state, title, icon, onDismiss, children, style,
|
|
187
|
+
export function Alert({ variant, state, title, icon, onDismiss, children, style,
|
|
188
|
+
// 🔴 ESTES DOIS SAEM DO RESTO DE PROPÓSITO, e é consequência do conserto abaixo: a moldura
|
|
189
|
+
// deixou de ser elemento de acessibilidade, e rótulo em quem não é elemento **não faz nada no
|
|
190
|
+
// iOS**. Eles pousam no GRUPO, que é quem o leitor de tela alcança.
|
|
191
|
+
accessibilityLabel, accessibilityHint, ...rest }) {
|
|
180
192
|
const t = useAureaTokens();
|
|
181
193
|
const s = folha(t);
|
|
182
194
|
const strings = useAureaStrings();
|
|
@@ -185,11 +197,15 @@ export function Alert({ variant, state, title, icon, onDismiss, children, style,
|
|
|
185
197
|
: v === "warning" ? t.color.warning
|
|
186
198
|
: v === "danger" ? (t.color.danger400 ?? t.color.destructive)
|
|
187
199
|
: t.color.info;
|
|
188
|
-
return (_jsxs(View
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
200
|
+
return (_jsxs(View
|
|
201
|
+
// Android: a região viva é o que faz o leitor falar sem foco, e ela cobre a moldura
|
|
202
|
+
// inteira de propósito — o que muda é o conteúdo, e o "X" some junto quando o aviso fecha.
|
|
203
|
+
, {
|
|
204
|
+
// Android: a região viva é o que faz o leitor falar sem foco, e ela cobre a moldura
|
|
205
|
+
// inteira de propósito — o que muda é o conteúdo, e o "X" some junto quando o aviso fecha.
|
|
206
|
+
accessibilityLiveRegion: v === "danger" ? "assertive" : "polite", style: [s.alerta, s[`alerta_${v}`], style], ...rest, children: [_jsxs(View, { accessible: true, accessibilityLabel: accessibilityLabel, accessibilityHint: accessibilityHint, ...(v === "danger" ? { accessibilityRole: "alert" } : null), style: s.grupoDoAlerta, children: [_jsx(Icon, { name: icon ?? ICONE_DA_VARIANTE[v], size: "md", color: corDoIcone }), _jsxs(View, { style: s.corpoDoAlerta, children: [title != null && _jsx(Text, { size: "sm", weight: 600, children: title }), children != null
|
|
207
|
+
? (typeof children === "string" ? _jsx(Text, { size: "sm", tone: "muted", children: children }) : children)
|
|
208
|
+
: state ? _jsx(Text, { size: "sm", tone: "muted", children: strings.universalState[state] }) : null] })] }), onDismiss ? _jsx(IconButton, { appearance: "ghost", size: "sm", name: "close", label: strings.close, onPress: onDismiss }) : null] }));
|
|
193
209
|
}
|
|
194
210
|
/**
|
|
195
211
|
* O nada, dito com jeito.
|
package/dist/index.d.ts
CHANGED
|
@@ -21,8 +21,8 @@ export { useAureaStrings } from "./theme.js";
|
|
|
21
21
|
export { defaultStrings, ptBR, gravidadeDoEstado, AUREA_UNIVERSAL_STATES } from "./strings.js";
|
|
22
22
|
export type { AureaStrings, AureaUniversalState } from "./strings.js";
|
|
23
23
|
export { useReduceMotion } from "./movimento.js";
|
|
24
|
-
export { BottomNav, Topbar, NavList, Stepper } from "./navigation.js";
|
|
25
|
-
export type { BottomNavProps, TopbarProps, NavListProps, StepperProps, AureaNavItem, AureaNavListItem, AureaStepItem, AureaStepState, AureaBottomNavVariant, AureaBottomNavIndicator, AureaTopbarVariant, } from "./navigation.js";
|
|
24
|
+
export { BottomNav, Topbar, NavList, Stepper, Tabs } from "./navigation.js";
|
|
25
|
+
export type { BottomNavProps, TopbarProps, NavListProps, StepperProps, AureaNavItem, AureaNavListItem, AureaStepItem, AureaStepState, AureaBottomNavVariant, AureaBottomNavIndicator, AureaTopbarVariant, TabsProps, AureaTabItem, } from "./navigation.js";
|
|
26
26
|
export { Field, Label, Input, Textarea, Select, Switch, Checkbox, Radio, SegmentedControl, Form, KeyboardAvoiding, useCampo, } from "./inputs.js";
|
|
27
27
|
export type { FieldProps, LabelProps, InputProps, TextareaProps, SelectProps, SwitchProps, CheckboxProps, RadioProps, SegmentedControlProps, FormProps, KeyboardAvoidingProps, AureaFieldSize, } from "./inputs.js";
|
|
28
28
|
export { InputGroup, InputGroupAddon, PasswordField, useGrupoDeCampo } from "./inputs.js";
|
package/dist/index.js
CHANGED
|
@@ -48,7 +48,10 @@ export { useReduceMotion } from "./movimento.js";
|
|
|
48
48
|
// ── Lote 3 — a moldura de navegação ────────────────────────────────────────────────────────
|
|
49
49
|
// ⚠ A Aurea **não entrega roteamento**. Estes componentes recebem `items` e `current` e avisam
|
|
50
50
|
// por `onPress`; quem troca de tela é o app.
|
|
51
|
-
|
|
51
|
+
// ⚠ **O `Tabs` entrou em 17/09/2026, e é a única peça daqui que NÃO saiu de um lote** — ele
|
|
52
|
+
// veio do consumidor, que precisava trocar de painel dentro de uma tela e só tinha o
|
|
53
|
+
// `SegmentedControl`, que anuncia "rádio". A fronteira com o `BottomNav` está no JSDoc dele.
|
|
54
|
+
export { BottomNav, Topbar, NavList, Stepper, Tabs } from "./navigation.js";
|
|
52
55
|
// ── Lote 4 — os formulários ────────────────────────────────────────────────────────────────
|
|
53
56
|
// ⚠ O `Field` NÃO nomeia o controle por referência, como o `<label for>` da web: ele empurra o
|
|
54
57
|
// nome, a dica e o estado de inválido para o controle por CONTEXTO. É a tradução que decidiu o
|
package/dist/inputs.d.ts
CHANGED
|
@@ -234,7 +234,16 @@ export declare function Checkbox(p: CheckboxProps): React.JSX.Element;
|
|
|
234
234
|
* estado do app — quem sabe qual está escolhido é o `useState` da tela, e `checked` sai dele.
|
|
235
235
|
*/
|
|
236
236
|
export declare function Radio(p: RadioProps): React.JSX.Element;
|
|
237
|
-
|
|
237
|
+
/**
|
|
238
|
+
* ⚠ **Ele TIRAVA a `description`, e passou a aceitá-la em 17/09/2026** — pedido do consumidor
|
|
239
|
+
* (uma linha de explicação embaixo de "Desbloqueio por digital", no Perfil).
|
|
240
|
+
*
|
|
241
|
+
* ⚠ **E isto é uma frente em que o nativo vai ADIANTE da web, não atrás — declarado, não
|
|
242
|
+
* escondido.** Medido em `packages/react/src/markup.tsx`: lá o `Checkbox` tem `description`, o
|
|
243
|
+
* `Radio` **não**, e o `Switch` **não**. Aqui os três têm. A dívida passa a ser da web, e quem
|
|
244
|
+
* mexer nela deve fechar a diferença em vez de reabrir esta.
|
|
245
|
+
*/
|
|
246
|
+
export interface SwitchProps extends ControleProps {
|
|
238
247
|
}
|
|
239
248
|
/**
|
|
240
249
|
* O interruptor.
|
|
@@ -247,7 +256,7 @@ export interface SwitchProps extends Omit<ControleProps, "description"> {
|
|
|
247
256
|
* ⚠ **E ele respeita "menos movimento"**: com a preferência ligada, o polegar salta em vez de
|
|
248
257
|
* deslizar. O estado continua o mesmo — o que some é a animação, não a informação.
|
|
249
258
|
*/
|
|
250
|
-
export declare function Switch({ label, checked, onChange, disabled, size, style, testID }: SwitchProps): React.JSX.Element;
|
|
259
|
+
export declare function Switch({ label, description, checked, onChange, disabled, size, style, testID, }: SwitchProps): React.JSX.Element;
|
|
251
260
|
export interface SegmentedControlProps extends ViewProps {
|
|
252
261
|
items: Array<{
|
|
253
262
|
value: string;
|
package/dist/inputs.js
CHANGED
|
@@ -39,7 +39,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
39
39
|
// .segmented :922 linha, gap 3, padding 3, minH controlHMd, fundo muted
|
|
40
40
|
import * as React from "react";
|
|
41
41
|
import { Animated, KeyboardAvoidingView as KeyboardAvoidingViewRN, Modal, Platform, Pressable, ScrollView, TextInput, View, } from "react-native";
|
|
42
|
-
import { criarFolha } from "./estilos.js";
|
|
42
|
+
import { comOpacidade, criarFolha } from "./estilos.js";
|
|
43
43
|
import { IconButton } from "./actions.js";
|
|
44
44
|
import { Icon } from "./icon.js";
|
|
45
45
|
import { useReduceMotion } from "./movimento.js";
|
|
@@ -94,6 +94,20 @@ const folha = criarFolha((t) => ({
|
|
|
94
94
|
segmento: { flex: 1, alignItems: "center", justifyContent: "center",
|
|
95
95
|
paddingHorizontal: 14, borderRadius: t.size.radiusControl },
|
|
96
96
|
segmentoAtivo: { backgroundColor: t.color.secondary },
|
|
97
|
+
// 🔴 O FIO AMARELO DO ESCOLHIDO — ele faltava, e a falta era metade do defeito C4.
|
|
98
|
+
// O `aurea.css:958-960` põe este fio em TODOS os selecionados da casa (`.is-selected`, aba
|
|
99
|
+
// ativa, botão alternado e `.segmented button.active`), e o bloco de comentário acima dele
|
|
100
|
+
// diz por quê, com data: *"Item de nav ativo, aba ativa, item de lista lateral e botão
|
|
101
|
+
// alternado ligado mostram o MESMO sinal (…) Um usuário aprende uma vez e reconhece em todo
|
|
102
|
+
// lugar (pedido do Victor, 24/07)."*
|
|
103
|
+
// Os três números saem daquela linha, não de mim: altura 2 (fração de pixel borra), recuo 15,
|
|
104
|
+
// e o amarelo a 75% (cheio ele grita mais que o próprio rótulo).
|
|
105
|
+
// ⚠ Em segmento estreito o recuo de 15 de cada lado zera a largura e o fio some — **e é o
|
|
106
|
+
// mesmo comportamento da web**, onde `inset-inline:15px` num box estreito não desenha nada.
|
|
107
|
+
fioDoSegmento: {
|
|
108
|
+
position: "absolute", left: 15, right: 15, bottom: 0, height: 2,
|
|
109
|
+
borderRadius: t.size.radiusFull, backgroundColor: comOpacidade(t.color.primary, 0.75),
|
|
110
|
+
},
|
|
97
111
|
formulario: { gap: t.size.space5 },
|
|
98
112
|
// A folha do `Select`: no RN não existe `<select>`, então a lista é um `Modal` — que é
|
|
99
113
|
// exatamente o "motor novo sobre `Modal`" que a §5.2.4 previu.
|
|
@@ -348,7 +362,7 @@ export function Radio(p) { return _jsx(ControleMarcado, { ...p, papel: "radio" }
|
|
|
348
362
|
* ⚠ **E ele respeita "menos movimento"**: com a preferência ligada, o polegar salta em vez de
|
|
349
363
|
* deslizar. O estado continua o mesmo — o que some é a animação, não a informação.
|
|
350
364
|
*/
|
|
351
|
-
export function Switch({ label, checked, onChange, disabled, size, style, testID }) {
|
|
365
|
+
export function Switch({ label, description, checked, onChange, disabled, size, style, testID, }) {
|
|
352
366
|
const t = useAureaTokens();
|
|
353
367
|
const s = folha(t);
|
|
354
368
|
const campo = useCampo();
|
|
@@ -373,7 +387,10 @@ export function Switch({ label, checked, onChange, disabled, size, style, testID
|
|
|
373
387
|
a.start();
|
|
374
388
|
return () => a.stop();
|
|
375
389
|
}, [checked, pos, reduzir]);
|
|
376
|
-
return (_jsxs(Pressable, { testID: testID, onPress: inativo ? undefined : () => onChange?.(!checked), disabled: inativo, accessibilityRole: "switch", accessibilityState: { checked: !!checked, disabled: !!inativo }, accessibilityLabel: typeof label === "string" ? label : campo?.label,
|
|
390
|
+
return (_jsxs(Pressable, { testID: testID, onPress: inativo ? undefined : () => onChange?.(!checked), disabled: inativo, accessibilityRole: "switch", accessibilityState: { checked: !!checked, disabled: !!inativo }, accessibilityLabel: typeof label === "string" ? label : campo?.label,
|
|
391
|
+
// Mesma regra do `ControleMarcado`: a descrição vira DICA quando é texto. Sem isto ela
|
|
392
|
+
// desenha na tela e não existe para quem usa leitor de tela.
|
|
393
|
+
accessibilityHint: typeof description === "string" ? description : campo?.hint, style: [{ flexDirection: "row", alignItems: "center", gap: 10 }, inativo && s.desabilitado, style], children: [_jsx(View, { style: [
|
|
377
394
|
s.trilho,
|
|
378
395
|
{ width: larguraDoTrilho, height: alturaDoTrilho },
|
|
379
396
|
checked && { backgroundColor: t.color.controlSelected, borderColor: t.color.controlSelected },
|
|
@@ -382,7 +399,8 @@ export function Switch({ label, checked, onChange, disabled, size, style, testID
|
|
|
382
399
|
{ width: ladoDoPolegar, height: ladoDoPolegar,
|
|
383
400
|
backgroundColor: checked ? t.color.controlSelectedForeground : t.color.mutedForeground,
|
|
384
401
|
transform: [{ translateX: pos.interpolate({ inputRange: [0, 1], outputRange: [0, curso] }) }] },
|
|
385
|
-
] }) }), typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label
|
|
402
|
+
] }) }), _jsxs(View, { style: { flex: 1 }, children: [typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label, description != null && (typeof description === "string"
|
|
403
|
+
? _jsx(Text, { size: "xs", tone: "muted", children: description }) : description)] })] }));
|
|
386
404
|
}
|
|
387
405
|
/**
|
|
388
406
|
* Um de poucos, lado a lado.
|
|
@@ -399,9 +417,27 @@ export function SegmentedControl({ items, value, onChange, label, disabled, styl
|
|
|
399
417
|
const inativo = disabled ?? campo?.disabled;
|
|
400
418
|
return (_jsx(View, { accessibilityRole: "radiogroup", accessibilityLabel: label ?? campo?.label, style: [s.segmentada, inativo && s.desabilitado, style], ...rest, children: items.map((it) => {
|
|
401
419
|
const ativo = it.value === value;
|
|
402
|
-
return (
|
|
403
|
-
|
|
404
|
-
|
|
420
|
+
return (_jsxs(Pressable, { onPress: inativo ? undefined : () => onChange?.(it.value), disabled: inativo, accessibilityRole: "radio", accessibilityState: { checked: ativo, disabled: !!inativo }, accessibilityLabel: typeof it.label === "string" ? it.label : undefined, style: [s.segmento, ativo && s.segmentoAtivo], children: [typeof it.label === "string"
|
|
421
|
+
? _jsx(Text, { size: "sm", weight: ativo ? 600 : 400,
|
|
422
|
+
// 🔴 ERA `foreground`, E ISSO QUEBRAVA A LINGUAGEM DE SELECIONADO.
|
|
423
|
+
// Achado pelo consumidor em 17/09/2026. A web pinta o escolhido com a
|
|
424
|
+
// MARCA (`aurea.css:937-938`), e o comentário de cima daquela regra diz
|
|
425
|
+
// que nav ativo, aba ativa, item de lista e botão ligado mostram o mesmo
|
|
426
|
+
// sinal — ordem do Victor, 24/07. O `SegmentedControl` daqui discordava
|
|
427
|
+
// da web **e do `BottomNav` deste mesmo pacote**, que já usa este token.
|
|
428
|
+
//
|
|
429
|
+
// ⚠ **`primaryEmphasis` e NÃO `primary`, e a razão está no CSS:** *"sem
|
|
430
|
+
// fundo, a marca vira TEXTO, e o amarelo puro"* não se lê no claro
|
|
431
|
+
// (`aurea.css:358`). É a mesma lição que a `0.8.3` pagou no `Button`.
|
|
432
|
+
// No escuro os dois valem o mesmo amarelo; no claro este aprofunda.
|
|
433
|
+
//
|
|
434
|
+
// ⚠ **Uma divergência declarada, não escondida:** para o `.segmented` a
|
|
435
|
+
// web usa uma mistura própria (`primary 45% + foreground`) em vez do
|
|
436
|
+
// token, enquanto para o `.bottom-nav` usa o token. São dois âmbares
|
|
437
|
+
// parecidos para a MESMA linguagem, e isso é incoerência DA WEB. Escolhi
|
|
438
|
+
// o token, que deixa os dois componentes daqui iguais entre si.
|
|
439
|
+
style: { color: ativo ? t.color.primaryEmphasis : t.color.mutedForeground }, children: it.label })
|
|
440
|
+
: it.label, ativo && _jsx(View, { style: s.fioDoSegmento })] }, it.value));
|
|
405
441
|
}) }));
|
|
406
442
|
}
|
|
407
443
|
/**
|
package/dist/navigation.d.ts
CHANGED
|
@@ -133,3 +133,51 @@ export interface StepperProps extends ViewProps {
|
|
|
133
133
|
* vocabulário — é o vocabulário que existe, e dizer isso é melhor que fingir paridade.
|
|
134
134
|
*/
|
|
135
135
|
export declare function Stepper({ items, label, doneIcon, errorIcon, style, ...rest }: StepperProps): React.JSX.Element;
|
|
136
|
+
export interface AureaTabItem {
|
|
137
|
+
id: string;
|
|
138
|
+
label: React.ReactNode;
|
|
139
|
+
content: React.ReactNode;
|
|
140
|
+
disabled?: boolean;
|
|
141
|
+
}
|
|
142
|
+
export interface TabsProps extends ViewProps {
|
|
143
|
+
tabs: AureaTabItem[];
|
|
144
|
+
/** O `id` da aba aberta. Controlado — quem guarda o estado é o consumidor, como na web. */
|
|
145
|
+
value: string;
|
|
146
|
+
onChange?: (id: string) => void;
|
|
147
|
+
/** Nome da fila de abas para o leitor de tela. */
|
|
148
|
+
label?: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* As abas DENTRO da tela — trocar o painel, não trocar de página.
|
|
152
|
+
*
|
|
153
|
+
* ⚠ **E essa frase é a fronteira com o `BottomNav`, que o Lote 3 quase errou ao contrário.** Lá
|
|
154
|
+
* a ficha da web proíbe `tab` com a razão pronta — *"a tab swaps a panel inside the page, a
|
|
155
|
+
* bottom bar changes page"* —, e há um teste cujo único trabalho é reprovar essa palavra na barra
|
|
156
|
+
* de baixo. **Aqui é o caso certo**, então `tablist`/`tab` são os papéis honestos.
|
|
157
|
+
* Medido antes de escrever: os dois estão no tipo do RN **e** no enum Kotlin
|
|
158
|
+
* (`ReactAccessibilityDelegate.kt:368,370`), que é a interseção que o `check 41` cobra.
|
|
159
|
+
*
|
|
160
|
+
* ⚠ **A fila ROLA na horizontal, e a web não rola — divergência deliberada, com fonte.** A
|
|
161
|
+
* referência máxima de desenho deste projeto tem estados de rolagem na lista de abas
|
|
162
|
+
* (`left-scroll`, `right-scroll`, `left-right-scroll` no inventário), e num telefone de 360dp
|
|
163
|
+
* quatro rótulos já não cabem. Espremer todas seria a outra saída, e ela apaga o rótulo — que é
|
|
164
|
+
* o defeito que o app mediu no `SegmentedControl`. **A cápsula, o respiro e o raio continuam
|
|
165
|
+
* sendo os do CSS**; o que muda é o transbordo.
|
|
166
|
+
*
|
|
167
|
+
* ⚠ **O painel inativo NÃO fica montado.** Na web o motor mantém os painéis no DOM e esconde;
|
|
168
|
+
* aqui montar todos custaria memória e um painel com lista longa pagaria por telas que ninguém
|
|
169
|
+
* está vendo. **A consequência está declarada e não é neutra:** o estado interno de um painel
|
|
170
|
+
* (rolagem, texto digitado) **se perde** ao trocar de aba. Quem precisar do contrário guarda o
|
|
171
|
+
* estado fora, como já faz com `value`.
|
|
172
|
+
*
|
|
173
|
+
* ⚠ **A aba escolhida NÃO usa a cor da marca, e isso é de propósito.** O `.tab.active` da web é
|
|
174
|
+
* `--foreground` sobre `--secondary`, e ele **desliga o fio amarelo**
|
|
175
|
+
* (`aurea.css:1128`, `:after { display:none }`) que o `.segmented` recebe. São dois sinais
|
|
176
|
+
* diferentes para duas coisas diferentes: escolher um valor é uma coisa, trocar de painel é
|
|
177
|
+
* outra. **Não "harmonize" isto com o `SegmentedControl`** — a diferença foi escrita à mão lá.
|
|
178
|
+
*
|
|
179
|
+
* ⚠ **Sem orientação VERTICAL.** A web tem (`aurea.css:1122-1126`), e ela resolve um problema de
|
|
180
|
+
* largura de tela grande. Não há tablet medido neste projeto, e no nativo vale demanda antes de
|
|
181
|
+
* cobertura — a mesma decisão da `Table` do Lote 6.
|
|
182
|
+
*/
|
|
183
|
+
export declare function Tabs({ tabs, value, onChange, label, style, ...rest }: TabsProps): React.JSX.Element;
|
package/dist/navigation.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Pressable, View, } from "react-native";
|
|
2
|
+
import { Pressable, View, ScrollView } from "react-native";
|
|
3
3
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
4
4
|
import { Badge } from "./display.js";
|
|
5
|
-
import { criarFolha } from "./estilos.js";
|
|
5
|
+
import { comOpacidade, criarFolha } from "./estilos.js";
|
|
6
6
|
import { Icon } from "./icon.js";
|
|
7
|
+
import { Card } from "./layout.js";
|
|
7
8
|
import { Text } from "./text.js";
|
|
8
9
|
import { useAureaStrings, useAureaTokens } from "./theme.js";
|
|
9
10
|
const folha = criarFolha((t) => ({
|
|
@@ -62,7 +63,32 @@ const folha = criarFolha((t) => ({
|
|
|
62
63
|
paddingVertical: t.size.space2, paddingHorizontal: t.size.space3,
|
|
63
64
|
borderRadius: t.size.radiusCard - t.size.space1,
|
|
64
65
|
},
|
|
66
|
+
// O MIOLO da linha: ícone + texto (+ valor em texto) + seta. Ele é quem toca; o que vem
|
|
67
|
+
// depois dele, na moldura, é IRMÃO — ver o comentário no componente.
|
|
68
|
+
// Nenhuma medida nova: `flex`, `minWidth` e o MESMO `gap` da moldura.
|
|
69
|
+
linhaMiolo: { flex: 1, minWidth: 0, flexDirection: "row", alignItems: "center", gap: t.size.space3 },
|
|
65
70
|
linhaTexto: { flex: 1, minWidth: 0 },
|
|
71
|
+
// ── Tabs ───────────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
// A cápsula é a MESMA do `SegmentedControl`, e o CSS diz isso em voz alta no comentário de
|
|
73
|
+
// cima do `.tabs` (`aurea.css:1114`): *"Mesma regra da .segmented"*. Os três números vêm de
|
|
74
|
+
// lá — `gap:3px; padding:3px` — e não de mim.
|
|
75
|
+
abas: {
|
|
76
|
+
flexDirection: "row", gap: 3, padding: 3,
|
|
77
|
+
minHeight: t.size.controlHMd, borderRadius: t.size.radiusControl,
|
|
78
|
+
backgroundColor: t.color.muted,
|
|
79
|
+
},
|
|
80
|
+
// `padding:0 14px` é literal no `.tab` (`aurea.css:1127`). A aba NÃO estica (nada de `flex`):
|
|
81
|
+
// na web ela é conteúdo dentro de um `inline-flex`, e é isso que deixa a lista rolar quando
|
|
82
|
+
// não cabe, em vez de espremer todas.
|
|
83
|
+
// ⚠ O nome é `abaDeTab` porque `aba` JÁ EXISTE neste arquivo — é o item do `BottomNav`
|
|
84
|
+
// (linha 78), que tem `flex: 1` e cresce. Reusar o nome teria sobrescrito um dos dois em
|
|
85
|
+
// silêncio, e o `tsc` só pegou porque são chaves do mesmo objeto.
|
|
86
|
+
abaDeTab: {
|
|
87
|
+
alignItems: "center", justifyContent: "center",
|
|
88
|
+
paddingHorizontal: 14, borderRadius: t.size.radiusControl,
|
|
89
|
+
},
|
|
90
|
+
abaDeTabAtiva: { backgroundColor: t.color.secondary },
|
|
91
|
+
painel: { marginTop: t.size.space3 },
|
|
66
92
|
// ── Stepper ────────────────────────────────────────────────────────────────────────────────
|
|
67
93
|
trilha: { flexDirection: "row" },
|
|
68
94
|
passo: { flex: 1, alignItems: "center" },
|
|
@@ -73,39 +99,6 @@ const folha = criarFolha((t) => ({
|
|
|
73
99
|
backgroundColor: t.color.surface1,
|
|
74
100
|
},
|
|
75
101
|
}));
|
|
76
|
-
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
77
|
-
// A DECISÃO DE ACESSIBILIDADE QUE ATRAVESSA O MÓDULO INTEIRO
|
|
78
|
-
//
|
|
79
|
-
// A ficha do `BottomNav` na web escreve, em voz alta, o que ela NÃO adota — e a razão atravessa
|
|
80
|
-
// para cá sem uma vírgula de diferença:
|
|
81
|
-
//
|
|
82
|
-
// "What is deliberately NOT adopted is the Tabs pattern: a tab swaps a panel inside the page,
|
|
83
|
-
// a bottom bar changes page. Giving role=tablist to a menu makes the screen reader promise
|
|
84
|
-
// arrow keys that lead nowhere."
|
|
85
|
-
//
|
|
86
|
-
// No React Native existem `accessibilityRole="tab"` e `"tablist"`, e usá-los aqui seria fácil e
|
|
87
|
-
// errado pelo mesmo motivo. **Não são usados.**
|
|
88
|
-
//
|
|
89
|
-
// O que a web usa no lugar é `<nav>` + `<a>` + `aria-current="page"`. Disso, o que existe no RN:
|
|
90
|
-
//
|
|
91
|
-
// `<nav>` → NÃO existe. Não há landmarks no Android/iOS como no HTML. A barra recebe
|
|
92
|
-
// `accessibilityLabel` (o `label`, ou a frase do provider) e nada mais. Inventar
|
|
93
|
-
// `toolbar` porque sobrou seria dizer uma coisa que não é.
|
|
94
|
-
// `<a>` → `accessibilityRole="link"`, que é o mais próximo honesto de "isto leva a outro
|
|
95
|
-
// lugar" — e é o que separa a aba de um `button`, que promete ação nesta tela.
|
|
96
|
-
// current → `accessibilityState={{selected: true}}`. É o par do `aria-current` no RN.
|
|
97
|
-
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
98
|
-
/**
|
|
99
|
-
* O `color-mix(in srgb, X 12%, transparent)` do CSS, traduzido.
|
|
100
|
-
*
|
|
101
|
-
* **Não há `color-mix` no React Native** — medido no interpretador de cor, o mesmo que a ADR-0027
|
|
102
|
-
* já tinha atravessado a pé. O que existe é hex de OITO dígitos, e `#RRGGBBAA` com o alfa certo
|
|
103
|
-
* dá o mesmo pixel sobre um fundo opaco. Cai para a cor crua se ela não for hex de seis, para
|
|
104
|
-
* nunca produzir uma string que o RN descarte em silêncio.
|
|
105
|
-
*/
|
|
106
|
-
const comOpacidade = (cor, pct) => /^#[0-9a-fA-F]{6}$/.test(cor)
|
|
107
|
-
? cor + Math.round(Math.max(0, Math.min(1, pct)) * 255).toString(16).padStart(2, "0")
|
|
108
|
-
: cor;
|
|
109
102
|
/**
|
|
110
103
|
* A barra inferior — a pele da navegação, **não a navegação**.
|
|
111
104
|
*
|
|
@@ -226,11 +219,33 @@ export function Topbar({ variant = "floating", brand, children, style, ...rest }
|
|
|
226
219
|
export function NavList({ items, chevron = "chevron--right", style, ...rest }) {
|
|
227
220
|
const t = useAureaTokens();
|
|
228
221
|
const s = folha(t);
|
|
229
|
-
return (_jsx(View, { accessibilityRole: "list", style: [s.lista, style], ...rest, children: items.map((it) =>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
222
|
+
return (_jsx(View, { accessibilityRole: "list", style: [s.lista, style], ...rest, children: items.map((it) => {
|
|
223
|
+
// 🔴 DUAS COISAS QUE O CONSUMIDOR MEDIU EM 17/09/2026, e as duas moram nesta linha.
|
|
224
|
+
//
|
|
225
|
+
// 1 · **O `value` ficava DENTRO do `Pressable`.** Como ele aceita qualquer conteúdo, um
|
|
226
|
+
// `Switch` ali virava botão dentro de botão — e **no iOS o `Switch` desaparecia**
|
|
227
|
+
// para o VoiceOver (a mesma armadilha do `Combobox`, do `Alert` e do `Chart`; o
|
|
228
|
+
// `check 43` é quem guarda a família).
|
|
229
|
+
// ⚠ **O `check 43` NÃO pega este caso, e é honesto dizer:** ele lê o NOSSO código, e
|
|
230
|
+
// aqui o que é tocável chega de fora. **Slot que recebe conteúdo do consumidor é
|
|
231
|
+
// ponto cego de qualquer gate estático** — por isso o conserto é de DESENHO.
|
|
232
|
+
//
|
|
233
|
+
// 2 · **Toda linha declarava `accessibilityRole="link"`, mesmo sem `onPress`.** Uma lista
|
|
234
|
+
// só de leitura anunciava cada linha como link que não vai a lugar nenhum.
|
|
235
|
+
//
|
|
236
|
+
// O que ficou, e por que o `value` se divide por TIPO em vez de sair sempre:
|
|
237
|
+
// • valor em TEXTO fica no miolo, porque é parte da frase da linha — pô-lo fora criaria
|
|
238
|
+
// uma segunda parada dizendo só "42 km", solta;
|
|
239
|
+
// • valor em COMPONENTE fica FORA, irmão do miolo, porque pode ser tocável.
|
|
240
|
+
const temAcao = typeof it.onPress === "function";
|
|
241
|
+
const valorEhTexto = typeof it.value === "string" || typeof it.value === "number";
|
|
242
|
+
const miolo = (_jsxs(_Fragment, { children: [it.icon && _jsx(Icon, { name: it.icon, size: "md", color: t.color.foreground }), _jsxs(View, { style: s.linhaTexto, children: [typeof it.label === "string"
|
|
243
|
+
? _jsx(Text, { size: "sm", numberOfLines: 1, children: it.label }) : it.label, it.description != null && (typeof it.description === "string"
|
|
244
|
+
? _jsx(Text, { size: "xs", tone: "muted", numberOfLines: 1, children: it.description })
|
|
245
|
+
: it.description)] }), valorEhTexto && _jsx(Text, { size: "sm", tone: "muted", children: it.value }), temAcao && chevron
|
|
246
|
+
&& _jsx(Icon, { name: chevron, size: "sm", color: t.color.mutedForeground })] }));
|
|
247
|
+
return (_jsxs(View, { style: [s.linha, it.disabled && { opacity: 0.5 }], children: [temAcao ? (_jsx(Pressable, { onPress: it.onPress, disabled: it.disabled, accessibilityRole: "link", accessibilityState: { disabled: !!it.disabled }, style: s.linhaMiolo, children: miolo })) : (_jsx(View, { accessible: true, accessibilityState: { disabled: !!it.disabled }, style: s.linhaMiolo, children: miolo })), !valorEhTexto && it.value != null ? it.value : null] }, it.id));
|
|
248
|
+
}) }));
|
|
234
249
|
}
|
|
235
250
|
/**
|
|
236
251
|
* A trilha de passos — o onboarding de 3 passos que o consumidor já tem.
|
|
@@ -264,3 +279,53 @@ export function Stepper({ items, label, doneIcon = "checkmark", errorIcon = "err
|
|
|
264
279
|
return it.onPress ? (_jsx(Pressable, { onPress: it.onPress, accessibilityRole: "button", accessibilityState: { selected: st === "active" }, style: s.passo, children: miolo }, n)) : (_jsx(View, { accessible: true, accessibilityState: { selected: st === "active" }, style: s.passo, children: miolo }, n));
|
|
265
280
|
}) }));
|
|
266
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* As abas DENTRO da tela — trocar o painel, não trocar de página.
|
|
284
|
+
*
|
|
285
|
+
* ⚠ **E essa frase é a fronteira com o `BottomNav`, que o Lote 3 quase errou ao contrário.** Lá
|
|
286
|
+
* a ficha da web proíbe `tab` com a razão pronta — *"a tab swaps a panel inside the page, a
|
|
287
|
+
* bottom bar changes page"* —, e há um teste cujo único trabalho é reprovar essa palavra na barra
|
|
288
|
+
* de baixo. **Aqui é o caso certo**, então `tablist`/`tab` são os papéis honestos.
|
|
289
|
+
* Medido antes de escrever: os dois estão no tipo do RN **e** no enum Kotlin
|
|
290
|
+
* (`ReactAccessibilityDelegate.kt:368,370`), que é a interseção que o `check 41` cobra.
|
|
291
|
+
*
|
|
292
|
+
* ⚠ **A fila ROLA na horizontal, e a web não rola — divergência deliberada, com fonte.** A
|
|
293
|
+
* referência máxima de desenho deste projeto tem estados de rolagem na lista de abas
|
|
294
|
+
* (`left-scroll`, `right-scroll`, `left-right-scroll` no inventário), e num telefone de 360dp
|
|
295
|
+
* quatro rótulos já não cabem. Espremer todas seria a outra saída, e ela apaga o rótulo — que é
|
|
296
|
+
* o defeito que o app mediu no `SegmentedControl`. **A cápsula, o respiro e o raio continuam
|
|
297
|
+
* sendo os do CSS**; o que muda é o transbordo.
|
|
298
|
+
*
|
|
299
|
+
* ⚠ **O painel inativo NÃO fica montado.** Na web o motor mantém os painéis no DOM e esconde;
|
|
300
|
+
* aqui montar todos custaria memória e um painel com lista longa pagaria por telas que ninguém
|
|
301
|
+
* está vendo. **A consequência está declarada e não é neutra:** o estado interno de um painel
|
|
302
|
+
* (rolagem, texto digitado) **se perde** ao trocar de aba. Quem precisar do contrário guarda o
|
|
303
|
+
* estado fora, como já faz com `value`.
|
|
304
|
+
*
|
|
305
|
+
* ⚠ **A aba escolhida NÃO usa a cor da marca, e isso é de propósito.** O `.tab.active` da web é
|
|
306
|
+
* `--foreground` sobre `--secondary`, e ele **desliga o fio amarelo**
|
|
307
|
+
* (`aurea.css:1128`, `:after { display:none }`) que o `.segmented` recebe. São dois sinais
|
|
308
|
+
* diferentes para duas coisas diferentes: escolher um valor é uma coisa, trocar de painel é
|
|
309
|
+
* outra. **Não "harmonize" isto com o `SegmentedControl`** — a diferença foi escrita à mão lá.
|
|
310
|
+
*
|
|
311
|
+
* ⚠ **Sem orientação VERTICAL.** A web tem (`aurea.css:1122-1126`), e ela resolve um problema de
|
|
312
|
+
* largura de tela grande. Não há tablet medido neste projeto, e no nativo vale demanda antes de
|
|
313
|
+
* cobertura — a mesma decisão da `Table` do Lote 6.
|
|
314
|
+
*/
|
|
315
|
+
export function Tabs({ tabs, value, onChange, label, style, ...rest }) {
|
|
316
|
+
const t = useAureaTokens();
|
|
317
|
+
const s = folha(t);
|
|
318
|
+
const strings = useAureaStrings();
|
|
319
|
+
const aberta = tabs.find((it) => it.id === value);
|
|
320
|
+
return (_jsxs(View, { style: style, ...rest, children: [_jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false,
|
|
321
|
+
// Sem isto o conteúdo estica até a largura do rolador e o `padding` de 3 da cápsula
|
|
322
|
+
// aparece como faixa vazia à direita quando as abas cabem.
|
|
323
|
+
contentContainerStyle: { flexGrow: 0 }, children: _jsx(View, { accessibilityRole: "tablist", accessibilityLabel: label ?? strings.tabsLabel, style: s.abas, children: tabs.map((it) => {
|
|
324
|
+
const ativa = it.id === value;
|
|
325
|
+
return (_jsx(Pressable, { onPress: it.disabled ? undefined : () => onChange?.(it.id), disabled: it.disabled, accessibilityRole: "tab", accessibilityState: { selected: ativa, disabled: !!it.disabled }, accessibilityLabel: typeof it.label === "string" ? it.label : undefined, style: [s.abaDeTab, ativa && s.abaDeTabAtiva, it.disabled && { opacity: 0.5 }], children: typeof it.label === "string"
|
|
326
|
+
? _jsx(Text, { size: "sm", weight: ativa ? 600 : 400, style: { color: ativa ? t.color.foreground : t.color.mutedForeground }, children: it.label })
|
|
327
|
+
: it.label }, it.id));
|
|
328
|
+
}) }) }), aberta
|
|
329
|
+
? (_jsx(Card, { variant: "inset", accessibilityLabel: typeof aberta.label === "string" ? aberta.label : undefined, style: s.painel, children: aberta.content }))
|
|
330
|
+
: null] }));
|
|
331
|
+
}
|
package/dist/strings.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface AureaStrings {
|
|
|
22
22
|
dataEmpty: string;
|
|
23
23
|
/** O nome da barra inferior para o leitor de tela. */
|
|
24
24
|
bottomNavLabel: string;
|
|
25
|
+
/** Nome da fila de abas. Mesma chave da web (`pure.tsx`), traduzida de lá. */
|
|
26
|
+
tabsLabel: string;
|
|
25
27
|
/** O nome da trilha de passos. */
|
|
26
28
|
stepperLabel: string;
|
|
27
29
|
/** O que o `DatePicker` mostra quando não há data escolhida. */
|
package/dist/strings.js
CHANGED
|
@@ -32,6 +32,7 @@ export const defaultStrings = {
|
|
|
32
32
|
dataError: "This could not be loaded.",
|
|
33
33
|
dataEmpty: "Nothing here yet",
|
|
34
34
|
bottomNavLabel: "Main",
|
|
35
|
+
tabsLabel: "Tabs",
|
|
35
36
|
stepperLabel: "Steps",
|
|
36
37
|
datePlaceholder: "Pick a date",
|
|
37
38
|
cameraDenied: "Camera access is off. Turn it on in Settings to attach a photo.",
|
|
@@ -69,6 +70,7 @@ export const ptBR = {
|
|
|
69
70
|
dataError: "Não foi possível carregar.",
|
|
70
71
|
dataEmpty: "Ainda não há nada aqui",
|
|
71
72
|
bottomNavLabel: "Principal",
|
|
73
|
+
tabsLabel: "Abas",
|
|
72
74
|
stepperLabel: "Etapas",
|
|
73
75
|
datePlaceholder: "Escolher data",
|
|
74
76
|
cameraDenied: "O acesso à câmera está desligado. Ligue nas configurações para anexar uma foto.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurea-uds/native",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
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.5"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@react-native-community/datetimepicker": ">=8",
|