@aurea-uds/native 0.8.4 → 0.8.6
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/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 +47 -10
- package/dist/layout.d.ts +27 -3
- package/dist/layout.js +27 -4
- package/dist/navigation.d.ts +48 -0
- package/dist/navigation.js +103 -40
- package/dist/rolagem.d.ts +16 -0
- package/dist/rolagem.js +45 -0
- package/dist/strings.d.ts +2 -0
- package/dist/strings.js +2 -0
- package/dist/text.js +11 -2
- package/dist/theme.d.ts +10 -0
- package/dist/theme.js +30 -0
- package/package.json +2 -2
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,8 @@ 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
|
+
import { FilaRolante } from "./rolagem.js";
|
|
43
44
|
import { IconButton } from "./actions.js";
|
|
44
45
|
import { Icon } from "./icon.js";
|
|
45
46
|
import { useReduceMotion } from "./movimento.js";
|
|
@@ -94,6 +95,20 @@ const folha = criarFolha((t) => ({
|
|
|
94
95
|
segmento: { flex: 1, alignItems: "center", justifyContent: "center",
|
|
95
96
|
paddingHorizontal: 14, borderRadius: t.size.radiusControl },
|
|
96
97
|
segmentoAtivo: { backgroundColor: t.color.secondary },
|
|
98
|
+
// 🔴 O FIO AMARELO DO ESCOLHIDO — ele faltava, e a falta era metade do defeito C4.
|
|
99
|
+
// O `aurea.css:958-960` põe este fio em TODOS os selecionados da casa (`.is-selected`, aba
|
|
100
|
+
// ativa, botão alternado e `.segmented button.active`), e o bloco de comentário acima dele
|
|
101
|
+
// diz por quê, com data: *"Item de nav ativo, aba ativa, item de lista lateral e botão
|
|
102
|
+
// alternado ligado mostram o MESMO sinal (…) Um usuário aprende uma vez e reconhece em todo
|
|
103
|
+
// lugar (pedido do Victor, 24/07)."*
|
|
104
|
+
// Os três números saem daquela linha, não de mim: altura 2 (fração de pixel borra), recuo 15,
|
|
105
|
+
// e o amarelo a 75% (cheio ele grita mais que o próprio rótulo).
|
|
106
|
+
// ⚠ Em segmento estreito o recuo de 15 de cada lado zera a largura e o fio some — **e é o
|
|
107
|
+
// mesmo comportamento da web**, onde `inset-inline:15px` num box estreito não desenha nada.
|
|
108
|
+
fioDoSegmento: {
|
|
109
|
+
position: "absolute", left: 15, right: 15, bottom: 0, height: 2,
|
|
110
|
+
borderRadius: t.size.radiusFull, backgroundColor: comOpacidade(t.color.primary, 0.75),
|
|
111
|
+
},
|
|
97
112
|
formulario: { gap: t.size.space5 },
|
|
98
113
|
// A folha do `Select`: no RN não existe `<select>`, então a lista é um `Modal` — que é
|
|
99
114
|
// exatamente o "motor novo sobre `Modal`" que a §5.2.4 previu.
|
|
@@ -348,7 +363,7 @@ export function Radio(p) { return _jsx(ControleMarcado, { ...p, papel: "radio" }
|
|
|
348
363
|
* ⚠ **E ele respeita "menos movimento"**: com a preferência ligada, o polegar salta em vez de
|
|
349
364
|
* deslizar. O estado continua o mesmo — o que some é a animação, não a informação.
|
|
350
365
|
*/
|
|
351
|
-
export function Switch({ label, checked, onChange, disabled, size, style, testID }) {
|
|
366
|
+
export function Switch({ label, description, checked, onChange, disabled, size, style, testID, }) {
|
|
352
367
|
const t = useAureaTokens();
|
|
353
368
|
const s = folha(t);
|
|
354
369
|
const campo = useCampo();
|
|
@@ -373,7 +388,10 @@ export function Switch({ label, checked, onChange, disabled, size, style, testID
|
|
|
373
388
|
a.start();
|
|
374
389
|
return () => a.stop();
|
|
375
390
|
}, [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,
|
|
391
|
+
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,
|
|
392
|
+
// Mesma regra do `ControleMarcado`: a descrição vira DICA quando é texto. Sem isto ela
|
|
393
|
+
// desenha na tela e não existe para quem usa leitor de tela.
|
|
394
|
+
accessibilityHint: typeof description === "string" ? description : campo?.hint, style: [{ flexDirection: "row", alignItems: "center", gap: 10 }, inativo && s.desabilitado, style], children: [_jsx(View, { style: [
|
|
377
395
|
s.trilho,
|
|
378
396
|
{ width: larguraDoTrilho, height: alturaDoTrilho },
|
|
379
397
|
checked && { backgroundColor: t.color.controlSelected, borderColor: t.color.controlSelected },
|
|
@@ -382,7 +400,8 @@ export function Switch({ label, checked, onChange, disabled, size, style, testID
|
|
|
382
400
|
{ width: ladoDoPolegar, height: ladoDoPolegar,
|
|
383
401
|
backgroundColor: checked ? t.color.controlSelectedForeground : t.color.mutedForeground,
|
|
384
402
|
transform: [{ translateX: pos.interpolate({ inputRange: [0, 1], outputRange: [0, curso] }) }] },
|
|
385
|
-
] }) }), typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label
|
|
403
|
+
] }) }), _jsxs(View, { style: { flex: 1 }, children: [typeof label === "string" ? _jsx(Text, { size: "sm", children: label }) : label, description != null && (typeof description === "string"
|
|
404
|
+
? _jsx(Text, { size: "xs", tone: "muted", children: description }) : description)] })] }));
|
|
386
405
|
}
|
|
387
406
|
/**
|
|
388
407
|
* Um de poucos, lado a lado.
|
|
@@ -397,12 +416,30 @@ export function SegmentedControl({ items, value, onChange, label, disabled, styl
|
|
|
397
416
|
const s = folha(t);
|
|
398
417
|
const campo = useCampo();
|
|
399
418
|
const inativo = disabled ?? campo?.disabled;
|
|
400
|
-
return (_jsx(View, { accessibilityRole: "radiogroup", accessibilityLabel: label ?? campo?.label, style: [s.segmentada, inativo && s.desabilitado, style], ...rest, children: items.map((it) => {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
419
|
+
return (_jsx(FilaRolante, { children: _jsx(View, { accessibilityRole: "radiogroup", accessibilityLabel: label ?? campo?.label, style: [s.segmentada, inativo && s.desabilitado, style], ...rest, children: items.map((it) => {
|
|
420
|
+
const ativo = it.value === value;
|
|
421
|
+
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"
|
|
422
|
+
? _jsx(Text, { size: "sm", weight: ativo ? 600 : 400,
|
|
423
|
+
// 🔴 ERA `foreground`, E ISSO QUEBRAVA A LINGUAGEM DE SELECIONADO.
|
|
424
|
+
// Achado pelo consumidor em 17/09/2026. A web pinta o escolhido com a
|
|
425
|
+
// MARCA (`aurea.css:937-938`), e o comentário de cima daquela regra diz
|
|
426
|
+
// que nav ativo, aba ativa, item de lista e botão ligado mostram o mesmo
|
|
427
|
+
// sinal — ordem do Victor, 24/07. O `SegmentedControl` daqui discordava
|
|
428
|
+
// da web **e do `BottomNav` deste mesmo pacote**, que já usa este token.
|
|
429
|
+
//
|
|
430
|
+
// ⚠ **`primaryEmphasis` e NÃO `primary`, e a razão está no CSS:** *"sem
|
|
431
|
+
// fundo, a marca vira TEXTO, e o amarelo puro"* não se lê no claro
|
|
432
|
+
// (`aurea.css:358`). É a mesma lição que a `0.8.3` pagou no `Button`.
|
|
433
|
+
// No escuro os dois valem o mesmo amarelo; no claro este aprofunda.
|
|
434
|
+
//
|
|
435
|
+
// ⚠ **Uma divergência declarada, não escondida:** para o `.segmented` a
|
|
436
|
+
// web usa uma mistura própria (`primary 45% + foreground`) em vez do
|
|
437
|
+
// token, enquanto para o `.bottom-nav` usa o token. São dois âmbares
|
|
438
|
+
// parecidos para a MESMA linguagem, e isso é incoerência DA WEB. Escolhi
|
|
439
|
+
// o token, que deixa os dois componentes daqui iguais entre si.
|
|
440
|
+
style: { color: ativo ? t.color.primaryEmphasis : t.color.mutedForeground }, children: it.label })
|
|
441
|
+
: it.label, ativo && _jsx(View, { style: s.fioDoSegmento })] }, it.value));
|
|
442
|
+
}) }) }));
|
|
406
443
|
}
|
|
407
444
|
/**
|
|
408
445
|
* A escolha de um de muitos.
|
package/dist/layout.d.ts
CHANGED
|
@@ -55,11 +55,34 @@ export interface SeparatorProps extends ViewProps {
|
|
|
55
55
|
* comportamento certo.
|
|
56
56
|
*/
|
|
57
57
|
export declare function Separator({ orientation, label, style, ...rest }: SeparatorProps): React.JSX.Element;
|
|
58
|
-
export type AureaCardVariant = "base" | "raised" | "interactive" | "inset" | "selected" | "danger";
|
|
59
|
-
|
|
60
|
-
variant?: AureaCardVariant;
|
|
58
|
+
export type AureaCardVariant = "base" | "raised" | "interactive" | "inset" | "selected" | "danger" | "brand";
|
|
59
|
+
interface CardBase extends ViewProps {
|
|
61
60
|
children?: React.ReactNode;
|
|
62
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* 🔴 **O `brand` EXIGE `action`, e isso é de propósito — a regra é do TIPO, não de documento.**
|
|
64
|
+
*
|
|
65
|
+
* O cartão na cor da marca entrou em 17/09/2026 e é a **primeira superfície grande preenchida
|
|
66
|
+
* com o amarelo** neste sistema. Até aqui ele só pintava coisa pequena: o ponto do contador, a
|
|
67
|
+
* pílula do passo atual, o link de pular para o conteúdo. Um cartão amarelo é precedente, e
|
|
68
|
+
* precedente sem trava vira decoração — outras telas pedem o mesmo e o amarelo deixa de
|
|
69
|
+
* significar "aja aqui".
|
|
70
|
+
*
|
|
71
|
+
* A regra combinada foi *"o cartão amarelo é só para pedir uma ação da pessoa"*. **Escrita num
|
|
72
|
+
* documento, ela apagaria** — este repositório tem registro disso em cada arquivo, e a §"Afirmação
|
|
73
|
+
* velha" do `CLAUDE.md` existe por causa desse desgaste. Então ela mora no tipo: sem `action`, o
|
|
74
|
+
* `brand` não compila. É a mesma escolha que faz o `label` do `IconButton` ser obrigatório.
|
|
75
|
+
*/
|
|
76
|
+
interface CardDaMarca extends CardBase {
|
|
77
|
+
variant: "brand";
|
|
78
|
+
/** O que a pessoa faz aqui — um `Button`, um `IconButton`, um campo. Sem isto não compila. */
|
|
79
|
+
action: React.ReactNode;
|
|
80
|
+
}
|
|
81
|
+
interface CardComum extends CardBase {
|
|
82
|
+
variant?: Exclude<AureaCardVariant, "brand">;
|
|
83
|
+
action?: never;
|
|
84
|
+
}
|
|
85
|
+
export type CardProps = CardDaMarca | CardComum;
|
|
63
86
|
/**
|
|
64
87
|
* Superfície flutuante, raio 22 (`--radius-card`) — identidade declarada INTOCÁVEL.
|
|
65
88
|
*
|
|
@@ -69,3 +92,4 @@ export interface CardProps extends ViewProps {
|
|
|
69
92
|
* que É um alvo — quem o torna tocável é a tela.
|
|
70
93
|
*/
|
|
71
94
|
export declare function Card({ variant, style, ...rest }: CardProps): React.JSX.Element;
|
|
95
|
+
export {};
|
package/dist/layout.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// Aurea nativo — os primitivos de layout: `Stack`, `Cluster`, `Grid`, `Card`.
|
|
3
3
|
//
|
|
4
4
|
// Medidos no core da web antes de escrever (passo 1 do BUILDING.md), e o que eles são lá:
|
|
@@ -19,7 +19,7 @@ import { View } from "react-native";
|
|
|
19
19
|
// `...rest` sem nada a mais.
|
|
20
20
|
import { criarFolha } from "./estilos.js";
|
|
21
21
|
import { Text } from "./text.js";
|
|
22
|
-
import { useAureaTokens } from "./theme.js";
|
|
22
|
+
import { useAureaTokens, SobreAMarca } from "./theme.js";
|
|
23
23
|
const folha = criarFolha((t) => ({
|
|
24
24
|
// A LINHA DO SISTEMA, e os números são os mesmos do `.separator` da web (`aurea.css`): cor de
|
|
25
25
|
// `border` e espessura de UM pixel. ⚠ Um pixel não sai de `--space-*` de propósito — a linha
|
|
@@ -52,6 +52,11 @@ const folha = criarFolha((t) => ({
|
|
|
52
52
|
card_inset: { backgroundColor: t.color.surfaceInset, borderColor: "transparent" },
|
|
53
53
|
card_selected: { borderColor: t.color.primaryOutline, backgroundColor: t.color.surface2 },
|
|
54
54
|
card_danger: { borderColor: t.color.danger400 ?? t.color.destructive, backgroundColor: t.color.dangerBg },
|
|
55
|
+
// O amarelo é o DE PREENCHER — `primary`, o mesmo do botão sólido. A borda some: uma superfície
|
|
56
|
+
// cheia não precisa de contorno, e um contorno sobre o amarelo mede 2,43 no claro (invisível).
|
|
57
|
+
card_brand: { backgroundColor: t.color.primary, borderColor: "transparent" },
|
|
58
|
+
// A ação fica embaixo, separada pelo mesmo respiro que o resto da casa usa entre blocos.
|
|
59
|
+
acaoDaMarca: { marginTop: t.size.space3 },
|
|
55
60
|
}));
|
|
56
61
|
/** Coluna com `--space-4` entre os filhos. **Sem prop de espaçamento, de propósito.** */
|
|
57
62
|
export function Stack({ style, ...rest }) {
|
|
@@ -112,6 +117,24 @@ export function Separator({ orientation = "horizontal", label, style, ...rest })
|
|
|
112
117
|
* que É um alvo — quem o torna tocável é a tela.
|
|
113
118
|
*/
|
|
114
119
|
export function Card({ variant = "base", style, ...rest }) {
|
|
115
|
-
const
|
|
116
|
-
|
|
120
|
+
const t = useAureaTokens();
|
|
121
|
+
const s = folha(t);
|
|
122
|
+
const { action, children, ...resto } = rest;
|
|
123
|
+
const corpo = (_jsxs(_Fragment, { children: [children, action != null && _jsx(View, { style: s.acaoDaMarca, children: action })] }));
|
|
124
|
+
const pele = _jsx(View, { style: [s.cardBase, s[`card_${variant}`], style], ...resto, children: corpo });
|
|
125
|
+
// 🔴 SOBRE O AMARELO A COR DO TEXTO NÃO É ESCOLHA, É OBRIGAÇÃO — e os números decidiram isto.
|
|
126
|
+
// Medido nos dois temas, texto contra o `primary`:
|
|
127
|
+
// texto comum ........ 10,34 no claro · **1,83 no escuro** 🔴 ilegível
|
|
128
|
+
// texto esmaecido ..... 4,24 no claro · **1,35 no escuro** 🔴 ilegível
|
|
129
|
+
// primary-foreground .. 4,54 nos DOIS ✅ único que serve
|
|
130
|
+
// Sem forçar, o cartão da marca sairia quebrado no tema escuro **sem ninguém ver no claro**.
|
|
131
|
+
// ⚠ **E no RN não há cascata de cor**: `color` num `View` não desce para o `Text` de dentro.
|
|
132
|
+
// A saída é CONTEXTO — a mesma decisão que o `Field` do Lote 4 tomou para empurrar nome e dica.
|
|
133
|
+
//
|
|
134
|
+
// ⚠ **Consequência declarada: sobre o amarelo existe UMA cor de texto, não duas.** O tom
|
|
135
|
+
// `muted` mede 1,35 no escuro, então ele não pode sobreviver aqui. A hierarquia dentro deste
|
|
136
|
+
// cartão sai de PESO e TAMANHO, não de cor — e isso é restrição do contraste, não gosto.
|
|
137
|
+
return variant === "brand"
|
|
138
|
+
? _jsx(SobreAMarca.Provider, { value: t.color.primaryForeground, children: pele })
|
|
139
|
+
: pele;
|
|
117
140
|
}
|
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,11 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Pressable, View
|
|
2
|
+
import { Pressable, View } 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";
|
|
8
|
+
import { FilaRolante } from "./rolagem.js";
|
|
7
9
|
import { Text } from "./text.js";
|
|
8
10
|
import { useAureaStrings, useAureaTokens } from "./theme.js";
|
|
9
11
|
const folha = criarFolha((t) => ({
|
|
@@ -62,7 +64,32 @@ const folha = criarFolha((t) => ({
|
|
|
62
64
|
paddingVertical: t.size.space2, paddingHorizontal: t.size.space3,
|
|
63
65
|
borderRadius: t.size.radiusCard - t.size.space1,
|
|
64
66
|
},
|
|
67
|
+
// O MIOLO da linha: ícone + texto (+ valor em texto) + seta. Ele é quem toca; o que vem
|
|
68
|
+
// depois dele, na moldura, é IRMÃO — ver o comentário no componente.
|
|
69
|
+
// Nenhuma medida nova: `flex`, `minWidth` e o MESMO `gap` da moldura.
|
|
70
|
+
linhaMiolo: { flex: 1, minWidth: 0, flexDirection: "row", alignItems: "center", gap: t.size.space3 },
|
|
65
71
|
linhaTexto: { flex: 1, minWidth: 0 },
|
|
72
|
+
// ── Tabs ───────────────────────────────────────────────────────────────────────────────────
|
|
73
|
+
// A cápsula é a MESMA do `SegmentedControl`, e o CSS diz isso em voz alta no comentário de
|
|
74
|
+
// cima do `.tabs` (`aurea.css:1114`): *"Mesma regra da .segmented"*. Os três números vêm de
|
|
75
|
+
// lá — `gap:3px; padding:3px` — e não de mim.
|
|
76
|
+
abas: {
|
|
77
|
+
flexDirection: "row", gap: 3, padding: 3,
|
|
78
|
+
minHeight: t.size.controlHMd, borderRadius: t.size.radiusControl,
|
|
79
|
+
backgroundColor: t.color.muted,
|
|
80
|
+
},
|
|
81
|
+
// `padding:0 14px` é literal no `.tab` (`aurea.css:1127`). A aba NÃO estica (nada de `flex`):
|
|
82
|
+
// na web ela é conteúdo dentro de um `inline-flex`, e é isso que deixa a lista rolar quando
|
|
83
|
+
// não cabe, em vez de espremer todas.
|
|
84
|
+
// ⚠ O nome é `abaDeTab` porque `aba` JÁ EXISTE neste arquivo — é o item do `BottomNav`
|
|
85
|
+
// (linha 78), que tem `flex: 1` e cresce. Reusar o nome teria sobrescrito um dos dois em
|
|
86
|
+
// silêncio, e o `tsc` só pegou porque são chaves do mesmo objeto.
|
|
87
|
+
abaDeTab: {
|
|
88
|
+
alignItems: "center", justifyContent: "center",
|
|
89
|
+
paddingHorizontal: 14, borderRadius: t.size.radiusControl,
|
|
90
|
+
},
|
|
91
|
+
abaDeTabAtiva: { backgroundColor: t.color.secondary },
|
|
92
|
+
painel: { marginTop: t.size.space3 },
|
|
66
93
|
// ── Stepper ────────────────────────────────────────────────────────────────────────────────
|
|
67
94
|
trilha: { flexDirection: "row" },
|
|
68
95
|
passo: { flex: 1, alignItems: "center" },
|
|
@@ -73,39 +100,6 @@ const folha = criarFolha((t) => ({
|
|
|
73
100
|
backgroundColor: t.color.surface1,
|
|
74
101
|
},
|
|
75
102
|
}));
|
|
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
103
|
/**
|
|
110
104
|
* A barra inferior — a pele da navegação, **não a navegação**.
|
|
111
105
|
*
|
|
@@ -226,11 +220,33 @@ export function Topbar({ variant = "floating", brand, children, style, ...rest }
|
|
|
226
220
|
export function NavList({ items, chevron = "chevron--right", style, ...rest }) {
|
|
227
221
|
const t = useAureaTokens();
|
|
228
222
|
const s = folha(t);
|
|
229
|
-
return (_jsx(View, { accessibilityRole: "list", style: [s.lista, style], ...rest, children: items.map((it) =>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
223
|
+
return (_jsx(View, { accessibilityRole: "list", style: [s.lista, style], ...rest, children: items.map((it) => {
|
|
224
|
+
// 🔴 DUAS COISAS QUE O CONSUMIDOR MEDIU EM 17/09/2026, e as duas moram nesta linha.
|
|
225
|
+
//
|
|
226
|
+
// 1 · **O `value` ficava DENTRO do `Pressable`.** Como ele aceita qualquer conteúdo, um
|
|
227
|
+
// `Switch` ali virava botão dentro de botão — e **no iOS o `Switch` desaparecia**
|
|
228
|
+
// para o VoiceOver (a mesma armadilha do `Combobox`, do `Alert` e do `Chart`; o
|
|
229
|
+
// `check 43` é quem guarda a família).
|
|
230
|
+
// ⚠ **O `check 43` NÃO pega este caso, e é honesto dizer:** ele lê o NOSSO código, e
|
|
231
|
+
// aqui o que é tocável chega de fora. **Slot que recebe conteúdo do consumidor é
|
|
232
|
+
// ponto cego de qualquer gate estático** — por isso o conserto é de DESENHO.
|
|
233
|
+
//
|
|
234
|
+
// 2 · **Toda linha declarava `accessibilityRole="link"`, mesmo sem `onPress`.** Uma lista
|
|
235
|
+
// só de leitura anunciava cada linha como link que não vai a lugar nenhum.
|
|
236
|
+
//
|
|
237
|
+
// O que ficou, e por que o `value` se divide por TIPO em vez de sair sempre:
|
|
238
|
+
// • valor em TEXTO fica no miolo, porque é parte da frase da linha — pô-lo fora criaria
|
|
239
|
+
// uma segunda parada dizendo só "42 km", solta;
|
|
240
|
+
// • valor em COMPONENTE fica FORA, irmão do miolo, porque pode ser tocável.
|
|
241
|
+
const temAcao = typeof it.onPress === "function";
|
|
242
|
+
const valorEhTexto = typeof it.value === "string" || typeof it.value === "number";
|
|
243
|
+
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"
|
|
244
|
+
? _jsx(Text, { size: "sm", numberOfLines: 1, children: it.label }) : it.label, it.description != null && (typeof it.description === "string"
|
|
245
|
+
? _jsx(Text, { size: "xs", tone: "muted", numberOfLines: 1, children: it.description })
|
|
246
|
+
: it.description)] }), valorEhTexto && _jsx(Text, { size: "sm", tone: "muted", children: it.value }), temAcao && chevron
|
|
247
|
+
&& _jsx(Icon, { name: chevron, size: "sm", color: t.color.mutedForeground })] }));
|
|
248
|
+
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));
|
|
249
|
+
}) }));
|
|
234
250
|
}
|
|
235
251
|
/**
|
|
236
252
|
* A trilha de passos — o onboarding de 3 passos que o consumidor já tem.
|
|
@@ -264,3 +280,50 @@ export function Stepper({ items, label, doneIcon = "checkmark", errorIcon = "err
|
|
|
264
280
|
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
281
|
}) }));
|
|
266
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* As abas DENTRO da tela — trocar o painel, não trocar de página.
|
|
285
|
+
*
|
|
286
|
+
* ⚠ **E essa frase é a fronteira com o `BottomNav`, que o Lote 3 quase errou ao contrário.** Lá
|
|
287
|
+
* a ficha da web proíbe `tab` com a razão pronta — *"a tab swaps a panel inside the page, a
|
|
288
|
+
* bottom bar changes page"* —, e há um teste cujo único trabalho é reprovar essa palavra na barra
|
|
289
|
+
* de baixo. **Aqui é o caso certo**, então `tablist`/`tab` são os papéis honestos.
|
|
290
|
+
* Medido antes de escrever: os dois estão no tipo do RN **e** no enum Kotlin
|
|
291
|
+
* (`ReactAccessibilityDelegate.kt:368,370`), que é a interseção que o `check 41` cobra.
|
|
292
|
+
*
|
|
293
|
+
* ⚠ **A fila ROLA na horizontal, e a web não rola — divergência deliberada, com fonte.** A
|
|
294
|
+
* referência máxima de desenho deste projeto tem estados de rolagem na lista de abas
|
|
295
|
+
* (`left-scroll`, `right-scroll`, `left-right-scroll` no inventário), e num telefone de 360dp
|
|
296
|
+
* quatro rótulos já não cabem. Espremer todas seria a outra saída, e ela apaga o rótulo — que é
|
|
297
|
+
* o defeito que o app mediu no `SegmentedControl`. **A cápsula, o respiro e o raio continuam
|
|
298
|
+
* sendo os do CSS**; o que muda é o transbordo.
|
|
299
|
+
*
|
|
300
|
+
* ⚠ **O painel inativo NÃO fica montado.** Na web o motor mantém os painéis no DOM e esconde;
|
|
301
|
+
* aqui montar todos custaria memória e um painel com lista longa pagaria por telas que ninguém
|
|
302
|
+
* está vendo. **A consequência está declarada e não é neutra:** o estado interno de um painel
|
|
303
|
+
* (rolagem, texto digitado) **se perde** ao trocar de aba. Quem precisar do contrário guarda o
|
|
304
|
+
* estado fora, como já faz com `value`.
|
|
305
|
+
*
|
|
306
|
+
* ⚠ **A aba escolhida NÃO usa a cor da marca, e isso é de propósito.** O `.tab.active` da web é
|
|
307
|
+
* `--foreground` sobre `--secondary`, e ele **desliga o fio amarelo**
|
|
308
|
+
* (`aurea.css:1128`, `:after { display:none }`) que o `.segmented` recebe. São dois sinais
|
|
309
|
+
* diferentes para duas coisas diferentes: escolher um valor é uma coisa, trocar de painel é
|
|
310
|
+
* outra. **Não "harmonize" isto com o `SegmentedControl`** — a diferença foi escrita à mão lá.
|
|
311
|
+
*
|
|
312
|
+
* ⚠ **Sem orientação VERTICAL.** A web tem (`aurea.css:1122-1126`), e ela resolve um problema de
|
|
313
|
+
* largura de tela grande. Não há tablet medido neste projeto, e no nativo vale demanda antes de
|
|
314
|
+
* cobertura — a mesma decisão da `Table` do Lote 6.
|
|
315
|
+
*/
|
|
316
|
+
export function Tabs({ tabs, value, onChange, label, style, ...rest }) {
|
|
317
|
+
const t = useAureaTokens();
|
|
318
|
+
const s = folha(t);
|
|
319
|
+
const strings = useAureaStrings();
|
|
320
|
+
const aberta = tabs.find((it) => it.id === value);
|
|
321
|
+
return (_jsxs(View, { style: style, ...rest, children: [_jsx(FilaRolante, { children: _jsx(View, { accessibilityRole: "tablist", accessibilityLabel: label ?? strings.tabsLabel, style: s.abas, children: tabs.map((it) => {
|
|
322
|
+
const ativa = it.id === value;
|
|
323
|
+
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"
|
|
324
|
+
? _jsx(Text, { size: "sm", weight: ativa ? 600 : 400, style: { color: ativa ? t.color.foreground : t.color.mutedForeground }, children: it.label })
|
|
325
|
+
: it.label }, it.id));
|
|
326
|
+
}) }) }), aberta
|
|
327
|
+
? (_jsx(Card, { variant: "inset", accessibilityLabel: typeof aberta.label === "string" ? aberta.label : undefined, style: s.painel, children: aberta.content }))
|
|
328
|
+
: null] }));
|
|
329
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
export interface FilaRolanteProps {
|
|
4
|
+
/** O conteúdo da fila — normalmente a cápsula com os itens dentro. */
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
/** Estilo do invólucro externo, não do conteúdo. */
|
|
7
|
+
style?: StyleProp<ViewStyle>;
|
|
8
|
+
testID?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Uma linha que rola para o lado quando não cabe, e que sabe QUANDO não cabe.
|
|
12
|
+
*
|
|
13
|
+
* O componente fica com a informação em vez de adivinhar: `onLayout` dá a largura visível e
|
|
14
|
+
* `onContentSizeChange` dá a do conteúdo. Com as duas, `transbordou` é uma conta, não um palpite.
|
|
15
|
+
*/
|
|
16
|
+
export declare function FilaRolante({ children, style, testID }: FilaRolanteProps): React.JSX.Element;
|
package/dist/rolagem.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// A FILA QUE ROLA — e por que ela existe em vez de cada componente resolver o seu.
|
|
3
|
+
//
|
|
4
|
+
// 🔴 **ISTO NÃO É DESENHO NOVO: É TRADUÇÃO QUE FALTOU.** O consumidor pediu em 17/09/2026 um
|
|
5
|
+
// grupo de opções que coubesse com seis itens, e a primeira resposta que eu ia dar era criar um
|
|
6
|
+
// componente de "chips". **Medindo a web antes, o pedido já estava resolvido lá:**
|
|
7
|
+
//
|
|
8
|
+
// aurea.css:2127, em tela de até 640px:
|
|
9
|
+
// .tabs,.pagination,.segmented { max-width:100%; overflow-x:auto; }
|
|
10
|
+
//
|
|
11
|
+
// Ou seja: **na web o segmentado e as abas JÁ rolam no telefone.** O nativo é que não traduziu.
|
|
12
|
+
// Criar um terceiro jeito de escolher teria sido inventar o que já existia — e essa briga tem
|
|
13
|
+
// nome neste repositório: o `Sidebar`.
|
|
14
|
+
//
|
|
15
|
+
// ⚠ **E o sinal de "tem mais" foi pedido junto, com razão:** quem não sabe que dá para arrastar
|
|
16
|
+
// não acha a sexta opção. A saída óbvia do React Native **não serve** — `fadingEdgeLength` é
|
|
17
|
+
// `@platform android` (medido no tipo do RN), e usá-la daria o aviso no Android e **nada no
|
|
18
|
+
// iPhone**. É a armadilha de um alvo só que este projeto já pagou várias vezes.
|
|
19
|
+
// Gradiente também não: proibido desde a Fase 0.
|
|
20
|
+
//
|
|
21
|
+
// **O que fica, e é o mesmo da web:** a opção cortada pela borda é o sinal, e ele vem do recorte.
|
|
22
|
+
// O que este módulo acrescenta é MEDIR se há transbordo, para (1) mostrar a barra de rolagem só
|
|
23
|
+
// quando ela significa alguma coisa e (2) dar ao teste um número para conferir.
|
|
24
|
+
// ⚠ **Declarado: se o corte LÊ bem é pergunta de vidro, e nenhum aparelho deste projeto rodou
|
|
25
|
+
// iOS.** O que está provado aqui é a medição, não a leitura.
|
|
26
|
+
import * as React from "react";
|
|
27
|
+
import { ScrollView } from "react-native";
|
|
28
|
+
/**
|
|
29
|
+
* Uma linha que rola para o lado quando não cabe, e que sabe QUANDO não cabe.
|
|
30
|
+
*
|
|
31
|
+
* O componente fica com a informação em vez de adivinhar: `onLayout` dá a largura visível e
|
|
32
|
+
* `onContentSizeChange` dá a do conteúdo. Com as duas, `transbordou` é uma conta, não um palpite.
|
|
33
|
+
*/
|
|
34
|
+
export function FilaRolante({ children, style, testID }) {
|
|
35
|
+
const [visivel, setVisivel] = React.useState(0);
|
|
36
|
+
const [conteudo, setConteudo] = React.useState(0);
|
|
37
|
+
const transbordou = conteudo > visivel && visivel > 0;
|
|
38
|
+
return (_jsx(ScrollView, { horizontal: true, testID: testID, onLayout: (e) => setVisivel(e.nativeEvent.layout.width), onContentSizeChange: (w) => setConteudo(w),
|
|
39
|
+
// A barra só aparece quando ela quer dizer alguma coisa. Com tudo cabendo ela seria ruído,
|
|
40
|
+
// e é assim que o `overflow-x:auto` da web se comporta: sem transbordo, sem barra.
|
|
41
|
+
showsHorizontalScrollIndicator: transbordou,
|
|
42
|
+
// Sem isto o conteúdo estica até a largura do rolador, e o respiro da cápsula aparece como
|
|
43
|
+
// faixa vazia à direita quando os itens cabem.
|
|
44
|
+
contentContainerStyle: { flexGrow: 0 }, style: style, children: children }));
|
|
45
|
+
}
|
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/dist/text.js
CHANGED
|
@@ -22,7 +22,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
22
22
|
import * as React from "react";
|
|
23
23
|
import { Text as TextRN } from "react-native";
|
|
24
24
|
import { criarFolha } from "./estilos.js";
|
|
25
|
-
import { useAureaTokens } from "./theme.js";
|
|
25
|
+
import { useAureaTokens, useSobreAMarca } from "./theme.js";
|
|
26
26
|
const TAMANHO = {
|
|
27
27
|
xs: "textXs", sm: "textSm", md: "textMd", base: "textBase", lg: "textLg",
|
|
28
28
|
xl: "textXl", "2xl": "text2xl", "3xl": "text3xl", "4xl": "text4xl", "5xl": "text5xl",
|
|
@@ -61,6 +61,7 @@ const folha = criarFolha((t) => {
|
|
|
61
61
|
export function Text({ size = "md", weight = 400, font = "ui", tone = "default", leading = "normal", italic = false, align, tracking, style, ...rest }) {
|
|
62
62
|
const t = useAureaTokens();
|
|
63
63
|
const s = folha(t);
|
|
64
|
+
const sobreAMarca = useSobreAMarca();
|
|
64
65
|
const proprio = React.useMemo(() => {
|
|
65
66
|
const fontSize = t.size[TAMANHO[size]] ?? t.size.textMd;
|
|
66
67
|
const escala = t.font[font];
|
|
@@ -79,5 +80,13 @@ export function Text({ size = "md", weight = 400, font = "ui", tone = "default",
|
|
|
79
80
|
...(align ? { textAlign: align } : null),
|
|
80
81
|
};
|
|
81
82
|
}, [t, size, weight, font, leading, italic, align, tracking]);
|
|
82
|
-
|
|
83
|
+
// 🔴 SOBRE UMA SUPERFÍCIE DA MARCA A COR É FORÇADA, e ela vence até o tom explícito.
|
|
84
|
+
// Medido contra o `primary` nos dois temas: texto comum dá **1,83 no escuro**, esmaecido
|
|
85
|
+
// **1,35**, e `danger` menos ainda. **Nenhum tom alcança os 4,5 da norma sobre o amarelo** —
|
|
86
|
+
// só o `primary-foreground`, com 4,54. Respeitar um `tone` ali dentro seria obedecer a prop e
|
|
87
|
+
// entregar texto que não se lê.
|
|
88
|
+
// ⚠ **A consequência está declarada e não é neutra:** dentro de um cartão da marca não existe
|
|
89
|
+
// texto esmaecido. A hierarquia sai de PESO e TAMANHO.
|
|
90
|
+
// ⚠ E `style` continua por último de propósito: quem passa cor à mão assume a conta.
|
|
91
|
+
return _jsx(TextRN, { style: [s[tone], sobreAMarca && { color: sobreAMarca }, proprio, style], ...rest });
|
|
83
92
|
}
|
package/dist/theme.d.ts
CHANGED
|
@@ -73,3 +73,13 @@ export declare function useAureaTokens(): AureaTokens;
|
|
|
73
73
|
* `strings.ts`.
|
|
74
74
|
*/
|
|
75
75
|
export declare function useAureaStrings(): AureaStrings;
|
|
76
|
+
/** O provedor. Quem usa é o `Card variant="brand"`. */
|
|
77
|
+
export declare const SobreAMarca: React.Context<string | null>;
|
|
78
|
+
/**
|
|
79
|
+
* A cor que o texto tem de usar quando está sobre a marca, ou `null` quando não está.
|
|
80
|
+
*
|
|
81
|
+
* ⚠ **Ela vence TODOS os tons, inclusive os explícitos.** Um `tone="danger"` sobre o amarelo
|
|
82
|
+
* mediria menos que os 4,5 da norma, então respeitá-lo entregaria texto ilegível em nome da
|
|
83
|
+
* obediência. **Sobre o amarelo existe UMA cor de texto** — a hierarquia sai de peso e tamanho.
|
|
84
|
+
*/
|
|
85
|
+
export declare function useSobreAMarca(): string | null;
|
package/dist/theme.js
CHANGED
|
@@ -109,3 +109,33 @@ export function useAureaTokens() {
|
|
|
109
109
|
export function useAureaStrings() {
|
|
110
110
|
return usar("useAureaStrings").strings;
|
|
111
111
|
}
|
|
112
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
113
|
+
// Sobre a marca — a cor de texto FORÇADA dentro de uma superfície amarela
|
|
114
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
115
|
+
//
|
|
116
|
+
// 🔴 **Isto existe porque o contraste não deixou escolha**, e os números estão no `layout.tsx`:
|
|
117
|
+
// sobre o `primary`, o texto comum mede **1,83 no tema escuro** e o esmaecido **1,35**. Os dois
|
|
118
|
+
// são ilegíveis. O único par que serve nos DOIS temas é `primary-foreground`, com 4,54.
|
|
119
|
+
//
|
|
120
|
+
// ⚠ **E no React Native não há cascata de cor** — `color` num `View` não desce para o `Text` de
|
|
121
|
+
// dentro, como desceria no CSS. Então a cor tem de viajar por CONTEXTO, que é exatamente a
|
|
122
|
+
// decisão que o `Field` do Lote 4 tomou para empurrar nome, dica e estado de inválido.
|
|
123
|
+
//
|
|
124
|
+
// ⚠ **Ele mora AQUI e não no `layout.tsx` por uma razão de estrutura:** o `layout.tsx` importa o
|
|
125
|
+
// `Text`, então se ele também exportasse o contexto o `text.tsx` teria de importar de volta — um
|
|
126
|
+
// ciclo. Este módulo é o que os dois já importam.
|
|
127
|
+
//
|
|
128
|
+
// ⚠ **Não sai pela porta da frente do pacote.** É acordo interno entre o `Card` e o `Text`.
|
|
129
|
+
const SobreAMarcaCtx = React.createContext(null);
|
|
130
|
+
/** O provedor. Quem usa é o `Card variant="brand"`. */
|
|
131
|
+
export const SobreAMarca = SobreAMarcaCtx;
|
|
132
|
+
/**
|
|
133
|
+
* A cor que o texto tem de usar quando está sobre a marca, ou `null` quando não está.
|
|
134
|
+
*
|
|
135
|
+
* ⚠ **Ela vence TODOS os tons, inclusive os explícitos.** Um `tone="danger"` sobre o amarelo
|
|
136
|
+
* mediria menos que os 4,5 da norma, então respeitá-lo entregaria texto ilegível em nome da
|
|
137
|
+
* obediência. **Sobre o amarelo existe UMA cor de texto** — a hierarquia sai de peso e tamanho.
|
|
138
|
+
*/
|
|
139
|
+
export function useSobreAMarca() {
|
|
140
|
+
return React.useContext(SobreAMarcaCtx);
|
|
141
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurea-uds/native",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
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.6"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@react-native-community/datetimepicker": ">=8",
|