@aurea-uds/native 0.25.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions.d.ts +14 -1
- package/dist/actions.js +65 -9
- package/dist/data.d.ts +2 -2
- package/dist/data.js +2 -2
- package/dist/display.d.ts +30 -3
- package/dist/display.js +66 -15
- package/dist/estilos.d.ts +7 -0
- package/dist/estilos.js +21 -0
- package/dist/feedback.js +10 -8
- package/dist/index.d.ts +2 -2
- package/dist/midia.d.ts +9 -1
- package/dist/midia.js +21 -6
- package/dist/sistema.js +89 -8
- package/dist/strings.d.ts +4 -0
- package/dist/strings.js +4 -0
- package/dist/text.d.ts +10 -5
- package/dist/text.js +10 -5
- package/dist/theme.d.ts +7 -0
- package/dist/theme.js +6 -28
- package/dist/toast.js +10 -8
- package/dist/tokens.d.ts +7 -3
- package/dist/tokens.js +2 -1
- package/package.json +2 -2
package/dist/actions.d.ts
CHANGED
|
@@ -40,6 +40,19 @@ export interface ButtonProps extends Omit<PressableProps, "children" | "style">
|
|
|
40
40
|
fullWidth?: boolean;
|
|
41
41
|
/** Estado de alternância, anunciado ao leitor de tela como `checked`. */
|
|
42
42
|
pressed?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* GAR-14 (0.28.0): o botão COMPARTILHA — os mesmos nomes da web. No aparelho, abre o compartilhar do
|
|
45
|
+
* sistema (`Share` do React Native); o link vai DENTRO da mensagem no Android, que descarta o `url`.
|
|
46
|
+
* No navegador (`react-native-web`), a regra da web: detectar o recurso; sem ele, copiar e avisar
|
|
47
|
+
* "Link copiado" pelo `ToastHost`, e dizer ao leitor de tela.
|
|
48
|
+
*/
|
|
49
|
+
share?: AureaShareData;
|
|
50
|
+
}
|
|
51
|
+
/** O que o `Button share` compartilha (GAR-14). Os nomes são os da web. */
|
|
52
|
+
export interface AureaShareData {
|
|
53
|
+
url?: string;
|
|
54
|
+
title?: string;
|
|
55
|
+
text?: string;
|
|
43
56
|
}
|
|
44
57
|
/**
|
|
45
58
|
* Botão. `Pressable` do RN, alvo ≥ `--target-min`, pele de token.
|
|
@@ -79,6 +92,6 @@ export declare function IconButton(props: IconButtonProps): React.JSX.Element;
|
|
|
79
92
|
*/
|
|
80
93
|
export declare function folgaDoToque(t: AureaTokens, size?: AureaButtonSize): number;
|
|
81
94
|
/** Fechado: sem `appearance` e sem `tone`, porque a cor é a do glifo. */
|
|
82
|
-
export interface ThemeToggleProps extends Omit<IconButtonProps, "name" | "label" | "onPress" | "appearance" | "tone"> {
|
|
95
|
+
export interface ThemeToggleProps extends Omit<IconButtonProps, "name" | "label" | "onPress" | "appearance" | "tone" | "share"> {
|
|
83
96
|
}
|
|
84
97
|
export declare function ThemeToggle(props: ThemeToggleProps): React.JSX.Element;
|
package/dist/actions.js
CHANGED
|
@@ -21,11 +21,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
21
21
|
// `appearance:"nav"` — é a pele do item de navegação, e quem a consome é o `BottomNav`/`NavList`
|
|
22
22
|
// do Lote 3. Aparência sem consumidor é a ADR-0034 ("ter a variante não é usar").
|
|
23
23
|
import * as React from "react";
|
|
24
|
-
import { Pressable, View } from "react-native";
|
|
24
|
+
import { AccessibilityInfo, Platform, Pressable, Share, View } from "react-native";
|
|
25
25
|
import { canto, criarFolha, REACAO_AO_TOQUE, estadoAcessivel } from "./estilos.js";
|
|
26
26
|
import { Icon } from "./icon.js";
|
|
27
27
|
import { Text } from "./text.js";
|
|
28
|
-
import { useAureaStrings, useAureaTheme, useAureaTokens, useSobreAMarca } from "./theme.js";
|
|
28
|
+
import { AvisosContext, useAureaStrings, useAureaTheme, useAureaTokens, useSobreAMarca } from "./theme.js";
|
|
29
29
|
const ALTURA = {
|
|
30
30
|
xs: "controlHXs", sm: "controlHSm", md: "controlHMd", lg: "controlHLg", xl: "controlHXl",
|
|
31
31
|
};
|
|
@@ -61,9 +61,11 @@ function pintar(t, tone) {
|
|
|
61
61
|
// invenção: o `Input` já usa esse token para a borda de inválido.
|
|
62
62
|
// O `solido` continua no `destructive`, porque ali ele é FUNDO e o texto por cima é claro.
|
|
63
63
|
danger: { solido: t.color.destructive, texto: t.color.destructiveForeground, sobre: t.color.danger400 ?? t.color.destructive },
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// `sobre` é a LETRA do tom em cima da superfície (contorno, fantasma): o par `-400`. Desde a
|
|
65
|
+
// 0.27.0 (ADR-0061) o `success`/`warning`/`info` é o fundo cheio, fundo demais para letra no escuro.
|
|
66
|
+
success: { solido: t.color.success, texto: t.color.successForeground, sobre: t.color.success400 ?? t.color.success },
|
|
67
|
+
warning: { solido: t.color.warning, texto: t.color.warningForeground, sobre: t.color.warning400 ?? t.color.warning },
|
|
68
|
+
info: { solido: t.color.info, texto: t.color.infoForeground, sobre: t.color.info400 ?? t.color.info },
|
|
67
69
|
};
|
|
68
70
|
return m[tone];
|
|
69
71
|
}
|
|
@@ -135,6 +137,58 @@ const folha = criarFolha((t) => ({
|
|
|
135
137
|
// `Card` com `onPress` passou a reagir igual (R-04, 24/09/2026).
|
|
136
138
|
...REACAO_AO_TOQUE,
|
|
137
139
|
}));
|
|
140
|
+
// GAR-14 · o compartilhar do nativo. Ver a prop `share`.
|
|
141
|
+
async function compartilhar(dados, avisos, strings) {
|
|
142
|
+
if (Platform.OS !== "web") {
|
|
143
|
+
// O iOS manda o `url` como link de verdade e o `message` como texto; o Android só lê o `message`.
|
|
144
|
+
const mensagem = [dados.text, dados.url].filter(Boolean).join(" ");
|
|
145
|
+
try {
|
|
146
|
+
await Share.share(Platform.OS === "ios" && dados.url
|
|
147
|
+
? { url: dados.url, message: dados.text, title: dados.title }
|
|
148
|
+
: { message: mensagem, title: dados.title });
|
|
149
|
+
}
|
|
150
|
+
catch { /* a pessoa fechou, ou o sistema recusou: não há o que dizer */ }
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// O alvo nativo não tem os tipos do navegador: só o pedaço que se usa aqui (como no E16).
|
|
154
|
+
const g = globalThis;
|
|
155
|
+
const nav = g.navigator;
|
|
156
|
+
const url = dados.url ?? g.location?.href ?? "";
|
|
157
|
+
const pacote = { url, ...(dados.title ? { title: dados.title } : null), ...(dados.text ? { text: dados.text } : null) };
|
|
158
|
+
if (typeof nav?.share === "function" && (typeof nav.canShare !== "function" || nav.canShare(pacote))) {
|
|
159
|
+
try {
|
|
160
|
+
await nav.share(pacote);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
if (e?.name === "AbortError")
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const avisar = (falhou) => {
|
|
169
|
+
const texto = falhou ? strings.linkCopyFailed : strings.linkCopied;
|
|
170
|
+
avisos?.add(falhou ? { title: texto, description: url, type: "danger", duration: 0 } : { title: texto, type: "success", duration: 1500 });
|
|
171
|
+
AccessibilityInfo.announceForAccessibility(texto);
|
|
172
|
+
};
|
|
173
|
+
try {
|
|
174
|
+
if (typeof nav?.clipboard?.writeText !== "function")
|
|
175
|
+
throw new Error("sem área de transferência");
|
|
176
|
+
await nav.clipboard.writeText(url);
|
|
177
|
+
avisar(false);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
avisar(true);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** O toque do botão com o `share`: o `onPress` do app primeiro, depois o compartilhar. O `Button` e
|
|
184
|
+
* o `IconButton` usam o mesmo — com um só, os dois não divergem. */
|
|
185
|
+
function useToqueComShare(share, onPress) {
|
|
186
|
+
const avisos = React.useContext(AvisosContext);
|
|
187
|
+
const strings = useAureaStrings();
|
|
188
|
+
if (!share)
|
|
189
|
+
return onPress;
|
|
190
|
+
return (e) => { onPress?.(e); void compartilhar(share, avisos, strings); };
|
|
191
|
+
}
|
|
138
192
|
/**
|
|
139
193
|
* Botão. `Pressable` do RN, alvo ≥ `--target-min`, pele de token.
|
|
140
194
|
*
|
|
@@ -148,8 +202,9 @@ export function Button(props) {
|
|
|
148
202
|
return _jsx(CorpoDoBotao, { ...props });
|
|
149
203
|
}
|
|
150
204
|
/** O corpo do `Button`. `semRecuo` é só do `LinkButton`: sem recuo, sem borda e sem altura. */
|
|
151
|
-
function CorpoDoBotao({ children, appearance = "solid", tone = "neutral", size = "md", leadingIcon, trailingIcon, leading, trailing, icons, fullWidth = false, pressed, disabled, accessibilityLabel, semRecuo = false, ...rest }) {
|
|
205
|
+
function CorpoDoBotao({ children, appearance = "solid", tone = "neutral", size = "md", leadingIcon, trailingIcon, leading, trailing, icons, fullWidth = false, pressed, disabled, accessibilityLabel, semRecuo = false, share, onPress, ...rest }) {
|
|
152
206
|
const t = useAureaTokens();
|
|
207
|
+
const aoTocar = useToqueComShare(share, onPress);
|
|
153
208
|
const s = folha(t);
|
|
154
209
|
const marca = useSobreAMarca();
|
|
155
210
|
const cor = pintarSobreAMarca(pintar(t, tone), marca, tone, appearance);
|
|
@@ -166,7 +221,7 @@ function CorpoDoBotao({ children, appearance = "solid", tone = "neutral", size =
|
|
|
166
221
|
...(semRecuo ? { height: undefined, paddingHorizontal: 0, borderWidth: 0 } : null),
|
|
167
222
|
}), [t, size, appearance, cor.solido, cor.contorno, corDaBorda, fullWidth, semRecuo]);
|
|
168
223
|
const corDoTexto = appearance === "solid" ? cor.texto : cor.sobre;
|
|
169
|
-
return (_jsx(Pressable, { disabled: disabled, accessibilityRole: "button", accessibilityLabel: accessibilityLabel, ...estadoAcessivel({ disabled: !!disabled, pressed }), style: ({ pressed: tocando }) => [
|
|
224
|
+
return (_jsx(Pressable, { disabled: disabled, onPress: aoTocar, accessibilityRole: "button", accessibilityLabel: accessibilityLabel, ...estadoAcessivel({ disabled: !!disabled, pressed }), style: ({ pressed: tocando }) => [
|
|
170
225
|
s.alvo, fullWidth && s.alvoLargura,
|
|
171
226
|
tocando && s.pressionado, disabled && s.inerte,
|
|
172
227
|
], ...rest, children: _jsxs(View, { style: [s.caixa, caixa], children: [leading ?? null, leadingIcon ? _jsx(Icon, { name: leadingIcon, size: ICONE[size], color: corDoTexto, icons: icons }) : null, typeof children === "string"
|
|
@@ -210,8 +265,9 @@ export function folgaDoToque(t, size = "md") {
|
|
|
210
265
|
}
|
|
211
266
|
/** O corpo do `IconButton`. A cor própria do ícone (`corDoIcone`) é só do `ThemeToggle`: o
|
|
212
267
|
* `IconButton` público não tem cor solta, e dentro do cartão da marca ela não vale (a tinta vence). */
|
|
213
|
-
function BotaoDeIcone({ name, label, appearance = "ghost", tone = "neutral", size = "md", icons, pressed, disabled, corDoIcone, pesoDoIcone, ...rest }) {
|
|
268
|
+
function BotaoDeIcone({ name, label, appearance = "ghost", tone = "neutral", size = "md", icons, pressed, disabled, corDoIcone, pesoDoIcone, share, onPress, ...rest }) {
|
|
214
269
|
const t = useAureaTokens();
|
|
270
|
+
const aoTocar = useToqueComShare(share, onPress);
|
|
215
271
|
const s = folha(t);
|
|
216
272
|
const marca = useSobreAMarca();
|
|
217
273
|
const cor = pintarSobreAMarca(pintar(t, tone), marca, tone, appearance);
|
|
@@ -223,7 +279,7 @@ function BotaoDeIcone({ name, label, appearance = "ghost", tone = "neutral", siz
|
|
|
223
279
|
backgroundColor: appearance === "solid" ? cor.solido : "transparent",
|
|
224
280
|
borderColor: cor.contorno ?? (appearance === "outline" ? corDaBorda : "transparent"),
|
|
225
281
|
}), [t, lado, size, appearance, cor.solido, cor.contorno, corDaBorda]);
|
|
226
|
-
return (_jsx(Pressable, { disabled: disabled, accessibilityRole: "button", accessibilityLabel: label, ...estadoAcessivel({ disabled: !!disabled, pressed }), style: ({ pressed: tocando }) => [
|
|
282
|
+
return (_jsx(Pressable, { disabled: disabled, onPress: aoTocar, accessibilityRole: "button", accessibilityLabel: label, ...estadoAcessivel({ disabled: !!disabled, pressed }), style: ({ pressed: tocando }) => [
|
|
227
283
|
// Largura FIXA, como o só-ícone da referência: ele nunca estica,
|
|
228
284
|
// nem numa coluna sem alinhamento. O alvo é o maior entre o desenho e o `targetMin`.
|
|
229
285
|
s.alvo, { width: Math.max(lado, t.size.targetMin), alignItems: "center" },
|
package/dist/data.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
-
import type { AureaBadgeTone } from "./display.js";
|
|
3
|
+
import type { AureaBadgeCategory, AureaBadgeTone } from "./display.js";
|
|
4
4
|
import type { AureaIcon } from "./icon.js";
|
|
5
5
|
type Virtualizavel = {
|
|
6
6
|
/** Vira `FlatList`. ⚠ Então este componente é o rolador — não o ponha dentro de `<Screen scroll>`. */
|
|
@@ -18,7 +18,7 @@ export type AureaTimelineItem = {
|
|
|
18
18
|
*/
|
|
19
19
|
icon?: AureaIcon;
|
|
20
20
|
/** A cor da moldura, com o vocabulário do `Badge`. Sem `icon`, não faz nada. Padrão: `neutral`. */
|
|
21
|
-
tone?: AureaBadgeTone
|
|
21
|
+
tone?: Exclude<AureaBadgeTone, AureaBadgeCategory>;
|
|
22
22
|
/** O que vai à DIREITA do título — um valor, um selo. */
|
|
23
23
|
trailing?: React.ReactNode;
|
|
24
24
|
/**
|
package/dist/data.js
CHANGED
|
@@ -233,14 +233,14 @@ export function Table({ caption, columns, rows, keyExtractor, onRowPress, empty,
|
|
|
233
233
|
const desenharLinha = React.useCallback((linha) => {
|
|
234
234
|
const corpo = (_jsxs(_Fragment, { children: [principal ? (_jsx(View, { accessible: true, children: (() => {
|
|
235
235
|
const v = conteudo(principal, linha);
|
|
236
|
-
return typeof v === "string" ? _jsx(Text, { size: "md", weight: 600, children: v }) : v;
|
|
236
|
+
return typeof v === "string" ? _jsx(Text, { size: "md", weight: 600, numeric: true, children: v }) : v;
|
|
237
237
|
})() })) : null, secundarias.map((coluna) => {
|
|
238
238
|
const v = conteudo(coluna, linha);
|
|
239
239
|
const nome = typeof coluna.header === "string" && typeof v === "string"
|
|
240
240
|
? `${coluna.header}: ${v}` : undefined;
|
|
241
241
|
return (_jsxs(View, { accessible: true, accessibilityLabel: nome, style: s.celula, children: [typeof coluna.header === "string"
|
|
242
242
|
? _jsx(Text, { size: "sm", tone: "muted", style: s.nomeDaColuna, children: coluna.header })
|
|
243
|
-
: coluna.header, typeof v === "string" ? _jsx(Text, { size: "sm", style: s.valor, children: v }) : v] }, coluna.key));
|
|
243
|
+
: coluna.header, typeof v === "string" ? _jsx(Text, { size: "sm", numeric: true, style: s.valor, children: v }) : v] }, coluna.key));
|
|
244
244
|
})] }));
|
|
245
245
|
// Só vira alvo se houver o que tocar. Uma linha `Pressable` sem ação é um botão que não faz
|
|
246
246
|
// nada — e o leitor de tela a anunciaria como botão.
|
package/dist/display.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type ImageSourcePropType, type StyleProp, type ViewProps, type ViewStyle } from "react-native";
|
|
3
|
+
import { type TomDeCor } from "./estilos.js";
|
|
3
4
|
import { type AureaUniversalState } from "./strings.js";
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* GAR-07 (ADR-0060, 09/10/2026): as oito cores de CATEGORIA — marcam grupo, não estado ("Motos",
|
|
7
|
+
* "Carros"). As mesmas da web, com os mesmos nomes; o amarelo fica de fora porque é da marca.
|
|
8
|
+
*/
|
|
9
|
+
export type AureaBadgeCategory = "red" | "orange" | "green" | "teal" | "cyan" | "blue" | "violet" | "pink";
|
|
10
|
+
export type AureaBadgeTone = TomDeCor | AureaBadgeCategory;
|
|
5
11
|
export type AureaBadgeEmphasis = "soft" | "outline" | "solid";
|
|
6
12
|
export type AureaBadgeSize = "xs" | "sm" | "md" | "lg";
|
|
7
13
|
export type AureaBadgeAnchor = "top-end" | "top-start" | "bottom-end" | "bottom-start";
|
|
@@ -93,14 +99,29 @@ export interface AvatarProps {
|
|
|
93
99
|
* `source` muda. Sem isso, uma URL quebrada deixa um buraco cinza permanente na lista.
|
|
94
100
|
*/
|
|
95
101
|
export declare function Avatar({ source, alt, fallback, size, style, testID }: AvatarProps): React.JSX.Element;
|
|
102
|
+
/** GAR-09 (ADR-0060, 09/10/2026): para onde a métrica foi. Os mesmos nomes da web. */
|
|
103
|
+
export type AureaKPIDirection = "up" | "down" | "flat";
|
|
104
|
+
export type AureaKPITone = "success" | "danger" | "neutral";
|
|
105
|
+
export type AureaKPIVariant = "card" | "plain";
|
|
96
106
|
export interface KPIProps extends ViewProps {
|
|
97
107
|
label: React.ReactNode;
|
|
98
108
|
value: React.ReactNode;
|
|
99
109
|
trend?: React.ReactNode;
|
|
110
|
+
/**
|
|
111
|
+
* Para onde foi. Desenha a seta (`trend-up`, `trend-down`, `minus` — o app registra os três, como
|
|
112
|
+
* todo glifo do nativo), pinta a tendência e põe a palavra no nome que o leitor de tela ouve.
|
|
113
|
+
*/
|
|
114
|
+
direction?: AureaKPIDirection;
|
|
115
|
+
/** A cor da tendência. Padrão: alta = `success`, baixa = `danger`, estável = `neutral`. */
|
|
116
|
+
tone?: AureaKPITone;
|
|
117
|
+
/** A palavra do leitor de tela. Padrão em inglês, como na web: "Up", "Down", "No change". */
|
|
118
|
+
directionLabel?: string;
|
|
119
|
+
/** `plain` tira o cartão: o número dentro de um cartão que já existe (MNT-04). */
|
|
120
|
+
variant?: AureaKPIVariant;
|
|
100
121
|
}
|
|
101
122
|
/**
|
|
102
123
|
* Um número com nome. **É um `Card`** — medido em `markup.tsx:105`, e não no CSS, que só mostra
|
|
103
|
-
* a coluna.
|
|
124
|
+
* a coluna. Com `variant="plain"`, é só a coluna.
|
|
104
125
|
*
|
|
105
126
|
* A ficha da web declara `role="group"`, e **o React Native não tem esse papel** — medido na lista
|
|
106
127
|
* de `accessibilityRole`, que vai de `button` a `toolbar` e não inclui `group`. O que ele tem é
|
|
@@ -109,5 +130,11 @@ export interface KPIProps extends ViewProps {
|
|
|
109
130
|
*
|
|
110
131
|
* Inventar `accessibilityRole="summary"` porque o nome parece próximo seria pior que não ter papel:
|
|
111
132
|
* `summary` tem significado próprio (o resumo de um bloco expansível) e diria uma coisa errada.
|
|
133
|
+
*
|
|
134
|
+
* Com `direction`, o nome do nó é escrito aqui (rótulo, número, palavra e tendência), porque a seta
|
|
135
|
+
* é desenho e não tem texto para o leitor juntar — a web resolve com o `.sr-only`, que o nativo não
|
|
136
|
+
* tem. Só quando as partes são texto; com nó próprio, o app dá o `accessibilityLabel`.
|
|
137
|
+
*
|
|
138
|
+
* O número em `3xl` e a tendência em `sm` desde a `0.26.0`: os mesmos da web (`0.24.1`).
|
|
112
139
|
*/
|
|
113
|
-
export declare function KPI({ label, value, trend, style, ...rest }: KPIProps): React.JSX.Element;
|
|
140
|
+
export declare function KPI({ label, value, trend, direction, tone, directionLabel, variant, style, ...rest }: KPIProps): React.JSX.Element;
|
package/dist/display.js
CHANGED
|
@@ -24,9 +24,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
24
24
|
import * as React from "react";
|
|
25
25
|
import { Image, View } from "react-native";
|
|
26
26
|
import { IconButton, folgaDoToque } from "./actions.js";
|
|
27
|
-
import { canto, acentoDoTom, criarFolha, fundoDoTom } from "./estilos.js";
|
|
27
|
+
import { canto, acentoDoTom, criarFolha, fundoDoTom, preenchimentoDoTom } from "./estilos.js";
|
|
28
28
|
import { Card } from "./layout.js";
|
|
29
29
|
import { gravidadeDoEstado } from "./strings.js";
|
|
30
|
+
import { Icon } from "./icon.js";
|
|
30
31
|
import { Text } from "./text.js";
|
|
31
32
|
import { useAureaStrings, useAureaTokens } from "./theme.js";
|
|
32
33
|
const folha = criarFolha((t) => ({
|
|
@@ -79,7 +80,11 @@ const folha = criarFolha((t) => ({
|
|
|
79
80
|
},
|
|
80
81
|
imagem: { width: "100%", height: "100%" },
|
|
81
82
|
// ── KPI ────────────────────────────────────────────────────────────────────────────────────
|
|
82
|
-
kpi: { gap:
|
|
83
|
+
kpi: { gap: t.size.space1 },
|
|
84
|
+
// A seta fica na PRIMEIRA linha (quando a tendência quebra, `center` a deixava entre as duas):
|
|
85
|
+
// a caixa dela tem a altura de uma linha do texto `sm` (que no telefone é o `textBase`, ADR-0050).
|
|
86
|
+
tendencia: { flexDirection: "row", alignItems: "flex-start", gap: t.size.space1 },
|
|
87
|
+
seta: { height: t.size.textBase * t.size.leadingNormal, justifyContent: "center" },
|
|
83
88
|
}));
|
|
84
89
|
/** `count > max ? `${max}+` : String(count)` — a mesma linha do `markup.tsx:140`. */
|
|
85
90
|
export const formatarContagem = (count, max = 99) => count > max ? `${max}+` : String(count);
|
|
@@ -107,16 +112,20 @@ const LETRA_DO_SELO = (t, size) => size === "lg" ? { fontSize: t.size.textBase,
|
|
|
107
112
|
export function Badge({ tone = "neutral", emphasis = "soft", size = "md", dot, count, max = 99, showZero, leading, trailing, fit = "auto", anchor, badgeContent, invisible, children, style, ...rest }) {
|
|
108
113
|
const t = useAureaTokens();
|
|
109
114
|
const s = folha(t);
|
|
110
|
-
const
|
|
111
|
-
const
|
|
115
|
+
const categoria = ehCategoria(tone) ? coresDaCategoria(t)[tone] : null;
|
|
116
|
+
const acento = categoria ? categoria[0] : acentoDoTom(t, tone);
|
|
117
|
+
const fundo = categoria ? categoria[1] : fundoDoTom(t, tone);
|
|
118
|
+
// O selo cheio de ESTADO é o botão cheio do mesmo tom (a cor funda e a letra dele); categoria,
|
|
119
|
+
// marca e neutro enchem com o acento, como antes.
|
|
120
|
+
const cheio = categoria ? undefined : preenchimentoDoTom(t, tone);
|
|
112
121
|
const pele = emphasis === "solid"
|
|
113
|
-
? { backgroundColor: acento, borderColor: "transparent" }
|
|
122
|
+
? { backgroundColor: cheio ? cheio[0] : acento, borderColor: "transparent" }
|
|
114
123
|
: emphasis === "outline"
|
|
115
124
|
? { backgroundColor: "transparent", borderColor: acento }
|
|
116
125
|
: tone === "neutral"
|
|
117
126
|
? {}
|
|
118
127
|
: { backgroundColor: fundo, borderColor: acento };
|
|
119
|
-
const corDoTexto = emphasis === "solid" ? t.color.background
|
|
128
|
+
const corDoTexto = emphasis === "solid" ? (cheio ? cheio[1] : t.color.background)
|
|
120
129
|
: tone === "neutral" ? t.color.secondaryForeground : acento;
|
|
121
130
|
const numero = count != null ? formatarContagem(count, max) : undefined;
|
|
122
131
|
const miolo = anchor ? (numero ?? badgeContent) : (numero ?? children);
|
|
@@ -161,6 +170,24 @@ export function Badge({ tone = "neutral", emphasis = "soft", size = "md", dot, c
|
|
|
161
170
|
const escondido = invisible || (count === 0 && !showZero) || (miolo == null && !dot);
|
|
162
171
|
return _jsxs(View, { style: s.ancora, ...rest, children: [children, !escondido && selo] });
|
|
163
172
|
}
|
|
173
|
+
const CATEGORIAS = new Set(["red", "orange", "green", "teal", "cyan", "blue", "violet", "pink"]);
|
|
174
|
+
const ehCategoria = (tom) => CATEGORIAS.has(tom);
|
|
175
|
+
/**
|
|
176
|
+
* O acento e o fundo suave de cada categoria — os tokens `--category-*` e `--category-*-bg`, os
|
|
177
|
+
* mesmos da web. Ficam AQUI, e não no `acentoDoTom`, de propósito: a moldura do ícone (R-15, R-18)
|
|
178
|
+
* usa aquele mapa, e as categorias são do selo; levá-las para lá abriria a `Timeline` a cores que
|
|
179
|
+
* a da web não tem.
|
|
180
|
+
*/
|
|
181
|
+
const coresDaCategoria = (t) => ({
|
|
182
|
+
red: [t.color.categoryRed, t.color.categoryRedBg],
|
|
183
|
+
orange: [t.color.categoryOrange, t.color.categoryOrangeBg],
|
|
184
|
+
green: [t.color.categoryGreen, t.color.categoryGreenBg],
|
|
185
|
+
teal: [t.color.categoryTeal, t.color.categoryTealBg],
|
|
186
|
+
cyan: [t.color.categoryCyan, t.color.categoryCyanBg],
|
|
187
|
+
blue: [t.color.categoryBlue, t.color.categoryBlueBg],
|
|
188
|
+
violet: [t.color.categoryViolet, t.color.categoryVioletBg],
|
|
189
|
+
pink: [t.color.categoryPink, t.color.categoryPinkBg],
|
|
190
|
+
});
|
|
164
191
|
/** O mínimo do selo preso, da referência principal: 16 no `xs` e no `sm`, 28 no `md`, 32 no `lg`. */
|
|
165
192
|
const MINIMO_PRESO = (t, size) => size === "lg" ? t.size.space8 : size === "md" ? t.size.space7 : t.size.space4;
|
|
166
193
|
/**
|
|
@@ -197,10 +224,10 @@ export function Status({ variant, state, children, style, ...rest }) {
|
|
|
197
224
|
// Mesma linha do `feedback-client.tsx:34`: `offline` é ele mesmo; o resto vira a gravidade.
|
|
198
225
|
const v = variant
|
|
199
226
|
?? (state ? (state === "offline" ? "offline" : gravidadeDoEstado(state)) : "neutral");
|
|
200
|
-
const cor = v === "online" || v === "success" ? t.color.success
|
|
201
|
-
: v === "away" || v === "warning" ? t.color.warning
|
|
227
|
+
const cor = v === "online" || v === "success" ? (t.color.success400 ?? t.color.success)
|
|
228
|
+
: v === "away" || v === "warning" ? (t.color.warning400 ?? t.color.warning)
|
|
202
229
|
: v === "busy" || v === "danger" ? (t.color.danger400 ?? t.color.destructive)
|
|
203
|
-
: v === "info" ? t.color.info
|
|
230
|
+
: v === "info" ? (t.color.info400 ?? t.color.info)
|
|
204
231
|
: v === "offline" ? t.color.subtleForeground
|
|
205
232
|
: t.color.mutedForeground;
|
|
206
233
|
const rotulo = children ?? (state ? strings.universalState[state] : null);
|
|
@@ -236,9 +263,13 @@ export function Avatar({ source, alt, fallback, size = "md", style, testID }) {
|
|
|
236
263
|
? _jsx(Text, { size: size === "sm" ? "xs" : "md", weight: 700, children: fallback })
|
|
237
264
|
: fallback }));
|
|
238
265
|
}
|
|
266
|
+
const TOM_DA_DIRECAO = { up: "success", down: "danger", flat: "neutral" };
|
|
267
|
+
const PALAVRA_DA_DIRECAO = { up: "Up", down: "Down", flat: "No change" };
|
|
268
|
+
const GLIFO_DA_DIRECAO = { up: "trend-up", down: "trend-down", flat: "minus" };
|
|
269
|
+
const comoTexto = (n) => typeof n === "string" || typeof n === "number" ? String(n) : null;
|
|
239
270
|
/**
|
|
240
271
|
* Um número com nome. **É um `Card`** — medido em `markup.tsx:105`, e não no CSS, que só mostra
|
|
241
|
-
* a coluna.
|
|
272
|
+
* a coluna. Com `variant="plain"`, é só a coluna.
|
|
242
273
|
*
|
|
243
274
|
* A ficha da web declara `role="group"`, e **o React Native não tem esse papel** — medido na lista
|
|
244
275
|
* de `accessibilityRole`, que vai de `button` a `toolbar` e não inclui `group`. O que ele tem é
|
|
@@ -247,10 +278,30 @@ export function Avatar({ source, alt, fallback, size = "md", style, testID }) {
|
|
|
247
278
|
*
|
|
248
279
|
* Inventar `accessibilityRole="summary"` porque o nome parece próximo seria pior que não ter papel:
|
|
249
280
|
* `summary` tem significado próprio (o resumo de um bloco expansível) e diria uma coisa errada.
|
|
281
|
+
*
|
|
282
|
+
* Com `direction`, o nome do nó é escrito aqui (rótulo, número, palavra e tendência), porque a seta
|
|
283
|
+
* é desenho e não tem texto para o leitor juntar — a web resolve com o `.sr-only`, que o nativo não
|
|
284
|
+
* tem. Só quando as partes são texto; com nó próprio, o app dá o `accessibilityLabel`.
|
|
285
|
+
*
|
|
286
|
+
* O número em `3xl` e a tendência em `sm` desde a `0.26.0`: os mesmos da web (`0.24.1`).
|
|
250
287
|
*/
|
|
251
|
-
export function KPI({ label, value, trend, style, ...rest }) {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
288
|
+
export function KPI({ label, value, trend, direction, tone, directionLabel, variant = "card", style, ...rest }) {
|
|
289
|
+
const t = useAureaTokens();
|
|
290
|
+
const s = folha(t);
|
|
291
|
+
const tom = direction ? (tone ?? TOM_DA_DIRECAO[direction]) : undefined;
|
|
292
|
+
const cor = tom === "success" ? (t.color.success400 ?? t.color.success)
|
|
293
|
+
: tom === "danger" ? (t.color.danger400 ?? t.color.destructive)
|
|
294
|
+
: t.color.mutedForeground;
|
|
295
|
+
const palavra = direction ? (directionLabel ?? PALAVRA_DA_DIRECAO[direction]) : undefined;
|
|
296
|
+
const temTendencia = trend != null && trend !== false && trend !== "";
|
|
297
|
+
const textoDaTendencia = typeof trend === "string" || typeof trend === "number"
|
|
298
|
+
? _jsx(Text, { size: "sm", tone: direction ? undefined : "muted", style: direction ? { color: cor } : undefined, children: trend })
|
|
299
|
+
: trend;
|
|
300
|
+
const partes = [comoTexto(label), comoTexto(value), palavra ?? "", temTendencia ? comoTexto(trend) : ""];
|
|
301
|
+
const nome = direction && partes.every((p) => p !== null) ? partes.filter(Boolean).join(", ") : undefined;
|
|
302
|
+
const Caixa = variant === "plain" ? View : Card;
|
|
303
|
+
return (_jsxs(Caixa, { style: [s.kpi, style], accessible: true, accessibilityLabel: nome, ...rest, children: [typeof label === "string" ? _jsx(Text, { size: "sm", tone: "muted", children: label }) : label, typeof value === "string" || typeof value === "number"
|
|
304
|
+
? _jsx(Text, { size: "3xl", weight: 700, numeric: true, children: value }) : value, direction
|
|
305
|
+
? _jsxs(View, { style: s.tendencia, children: [_jsx(View, { style: s.seta, children: _jsx(Icon, { name: GLIFO_DA_DIRECAO[direction], size: "sm", color: cor }) }), temTendencia && textoDaTendencia] })
|
|
306
|
+
: temTendencia && textoDaTendencia] }));
|
|
256
307
|
}
|
package/dist/estilos.d.ts
CHANGED
|
@@ -87,6 +87,13 @@ export type TomDeCor = "neutral" | "primary" | "info" | "success" | "warning" |
|
|
|
87
87
|
* nomeia — e o dia em que um tom novo entra numa só.
|
|
88
88
|
*/
|
|
89
89
|
export declare const acentoDoTom: (t: AureaTokens, tom: TomDeCor) => string;
|
|
90
|
+
export declare const letraDoToken: (t: AureaTokens, token: string) => string | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* O FUNDO CHEIO de um tom de estado e a letra em cima dele — o par do botão cheio (ADR-0061, 0.27.0):
|
|
93
|
+
* o selo cheio é o botão cheio do mesmo tom. `primary` e `neutral` não têm o par: `undefined`, e
|
|
94
|
+
* quem chama enche com o acento, como antes.
|
|
95
|
+
*/
|
|
96
|
+
export declare const preenchimentoDoTom: (t: AureaTokens, tom: TomDeCor) => [string, string] | undefined;
|
|
90
97
|
/** O fundo SUAVE de um tom (`--<tom>-bg`). `primary` e `neutral` não têm token: `undefined`. */
|
|
91
98
|
export declare const fundoDoTom: (t: AureaTokens, tom: TomDeCor) => string | undefined;
|
|
92
99
|
/**
|
package/dist/estilos.js
CHANGED
|
@@ -95,6 +95,27 @@ export const acentoDoTom = (t, tom) => tom === "primary" ? t.color.primaryEmphas
|
|
|
95
95
|
: tom === "warning" ? t.color.warning400 ?? t.color.warning
|
|
96
96
|
: tom === "danger" ? t.color.danger400 ?? t.color.destructive
|
|
97
97
|
: t.color.secondaryForeground;
|
|
98
|
+
/**
|
|
99
|
+
* A cor de LETRA (e de desenho sobre a tela) de um token de cor — ADR-0061. Os quatro tokens de
|
|
100
|
+
* estado (`success`, `info`, `warning`, `destructive`) são o FUNDO cheio; quem escreve ou desenha
|
|
101
|
+
* sobre a tela usa o par `-400` de cada tema. Qualquer outro token passa como está — e é por isso
|
|
102
|
+
* que a lista é FECHADA: um `primary400` que nascesse amanhã não pode mudar o texto amarelo sem
|
|
103
|
+
* ninguém pedir. Achado na 0.28.0: o `Text` com tom e a barra do `Progress` tinham ficado de fora.
|
|
104
|
+
*/
|
|
105
|
+
const PAR_DE_LETRA = {
|
|
106
|
+
success: "success400", info: "info400", warning: "warning400", destructive: "danger400",
|
|
107
|
+
};
|
|
108
|
+
export const letraDoToken = (t, token) => (PAR_DE_LETRA[token] ? t.color[PAR_DE_LETRA[token]] : undefined) ?? t.color[token];
|
|
109
|
+
/**
|
|
110
|
+
* O FUNDO CHEIO de um tom de estado e a letra em cima dele — o par do botão cheio (ADR-0061, 0.27.0):
|
|
111
|
+
* o selo cheio é o botão cheio do mesmo tom. `primary` e `neutral` não têm o par: `undefined`, e
|
|
112
|
+
* quem chama enche com o acento, como antes.
|
|
113
|
+
*/
|
|
114
|
+
export const preenchimentoDoTom = (t, tom) => tom === "success" ? [t.color.success, t.color.successForeground]
|
|
115
|
+
: tom === "warning" ? [t.color.warning, t.color.warningForeground]
|
|
116
|
+
: tom === "info" ? [t.color.info, t.color.infoForeground]
|
|
117
|
+
: tom === "danger" ? [t.color.destructive, t.color.destructiveForeground]
|
|
118
|
+
: undefined;
|
|
98
119
|
/** O fundo SUAVE de um tom (`--<tom>-bg`). `primary` e `neutral` não têm token: `undefined`. */
|
|
99
120
|
export const fundoDoTom = (t, tom) => tom === "info" ? t.color.infoBg
|
|
100
121
|
: tom === "success" ? t.color.successBg
|
package/dist/feedback.js
CHANGED
|
@@ -22,7 +22,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
22
22
|
import * as React from "react";
|
|
23
23
|
import { Animated, Easing, View } from "react-native";
|
|
24
24
|
import { IconButton } from "./actions.js";
|
|
25
|
-
import { canto, criarFolha, estadoAcessivel } from "./estilos.js";
|
|
25
|
+
import { canto, criarFolha, estadoAcessivel, letraDoToken } from "./estilos.js";
|
|
26
26
|
import { Icon } from "./icon.js";
|
|
27
27
|
import { IconeEmMoldura } from "./moldura.js";
|
|
28
28
|
import { useReduceMotion, driverNativo } from "./movimento.js";
|
|
@@ -72,9 +72,9 @@ const folha = criarFolha((t) => ({
|
|
|
72
72
|
flex: 1, minWidth: 0,
|
|
73
73
|
flexDirection: "row", alignItems: "flex-start", gap: t.size.space3,
|
|
74
74
|
},
|
|
75
|
-
alerta_info: { backgroundColor: t.color.infoBg, borderColor: t.color.info },
|
|
76
|
-
alerta_success: { backgroundColor: t.color.successBg, borderColor: t.color.success },
|
|
77
|
-
alerta_warning: { backgroundColor: t.color.warningBg, borderColor: t.color.warning },
|
|
75
|
+
alerta_info: { backgroundColor: t.color.infoBg, borderColor: t.color.info400 ?? t.color.info },
|
|
76
|
+
alerta_success: { backgroundColor: t.color.successBg, borderColor: t.color.success400 ?? t.color.success },
|
|
77
|
+
alerta_warning: { backgroundColor: t.color.warningBg, borderColor: t.color.warning400 ?? t.color.warning },
|
|
78
78
|
alerta_danger: { backgroundColor: t.color.dangerBg, borderColor: t.color.danger400 ?? t.color.destructive },
|
|
79
79
|
corpoDoAlerta: { flex: 1, gap: 2 },
|
|
80
80
|
// ── EmptyState ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -183,7 +183,9 @@ export function Progress({ value, label, detail, tone = "brand", style, testID }
|
|
|
183
183
|
const reduzir = useReduceMotion();
|
|
184
184
|
const semTotal = value == null || Number.isNaN(value);
|
|
185
185
|
const pct = semTotal ? undefined : Math.max(0, Math.min(100, value));
|
|
186
|
-
|
|
186
|
+
// ADR-0061: a barra é desenho sobre a trilha, como a letra — usa o par `-400`, como a web
|
|
187
|
+
// (`.progress-tone-*`). Na 0.27.0 ela ficou com a cor de fundo cheio; achado na 0.28.0.
|
|
188
|
+
const cor = letraDoToken(t, COR_DO_TOM[tone]);
|
|
187
189
|
const temApoio = detail != null && detail !== false && detail !== "";
|
|
188
190
|
const emTexto = temApoio && (typeof detail === "string" || typeof detail === "number") ? String(detail) : undefined;
|
|
189
191
|
// A largura do trilho, para o pedaço saber quanto andar. O `translateX` do RN não aceita
|
|
@@ -255,10 +257,10 @@ accessibilityLabel, accessibilityHint, ...rest }) {
|
|
|
255
257
|
const s = folha(t);
|
|
256
258
|
const strings = useAureaStrings();
|
|
257
259
|
const v = variant ?? (state ? gravidadeDoEstado(state) : "info");
|
|
258
|
-
const corDoIcone = v === "success" ? t.color.success
|
|
259
|
-
: v === "warning" ? t.color.warning
|
|
260
|
+
const corDoIcone = v === "success" ? (t.color.success400 ?? t.color.success)
|
|
261
|
+
: v === "warning" ? (t.color.warning400 ?? t.color.warning)
|
|
260
262
|
: v === "danger" ? (t.color.danger400 ?? t.color.destructive)
|
|
261
|
-
: t.color.info;
|
|
263
|
+
: (t.color.info400 ?? t.color.info);
|
|
262
264
|
return (_jsxs(View
|
|
263
265
|
// Android: a região viva é o que faz o leitor falar sem foco, e ela cobre a moldura
|
|
264
266
|
// inteira de propósito — o que muda é o conteúdo, e o "X" some junto quando o aviso fecha.
|
package/dist/index.d.ts
CHANGED
|
@@ -13,11 +13,11 @@ export type { ScreenProps, AureaScreenBackground, AureaScreenEdge } from "./scre
|
|
|
13
13
|
export { Stack, Cluster, Grid, Card, Separator } from "./layout.js";
|
|
14
14
|
export type { StackProps, ClusterProps, AureaStackAlign, AureaClusterAlign, AureaClusterJustify, GridProps, CardProps, SeparatorProps, AureaCardVariant, AureaGridMin, } from "./layout.js";
|
|
15
15
|
export { Button, IconButton, LinkButton, ThemeToggle } from "./actions.js";
|
|
16
|
-
export type { ButtonProps, IconButtonProps, LinkButtonProps, ThemeToggleProps, AureaButtonAppearance, AureaButtonSize, AureaButtonTone, } from "./actions.js";
|
|
16
|
+
export type { ButtonProps, IconButtonProps, LinkButtonProps, ThemeToggleProps, AureaButtonAppearance, AureaButtonSize, AureaButtonTone, AureaShareData, } from "./actions.js";
|
|
17
17
|
export { Spinner, Skeleton, Progress, Alert, EmptyState, DataState, ICONE_DA_VARIANTE } from "./feedback.js";
|
|
18
18
|
export type { SpinnerProps, SkeletonProps, ProgressProps, AlertProps, EmptyStateProps, DataStateProps, AureaSpinnerSize, AureaAlertVariant, AureaDataStateValue, AureaProgressTone, } from "./feedback.js";
|
|
19
19
|
export { Badge, Status, Avatar, KPI, formatarContagem } from "./display.js";
|
|
20
|
-
export type { BadgeProps, StatusProps, AvatarProps, KPIProps, AureaBadgeAnchor, AureaBadgeEmphasis, AureaBadgeFit, AureaBadgeSize, AureaBadgeTone, AureaStatusVariant, AureaAvatarSize, } from "./display.js";
|
|
20
|
+
export type { BadgeProps, StatusProps, AvatarProps, KPIProps, AureaKPIDirection, AureaKPITone, AureaKPIVariant, AureaBadgeAnchor, AureaBadgeCategory, AureaBadgeEmphasis, AureaBadgeFit, AureaBadgeSize, AureaBadgeTone, AureaStatusVariant, AureaAvatarSize, } from "./display.js";
|
|
21
21
|
export { useAureaStrings } from "./theme.js";
|
|
22
22
|
export { defaultStrings, ptBR, gravidadeDoEstado, AUREA_UNIVERSAL_STATES } from "./strings.js";
|
|
23
23
|
export type { AureaStrings, AureaUniversalState } from "./strings.js";
|
package/dist/midia.d.ts
CHANGED
|
@@ -30,6 +30,14 @@ export interface ImageProps {
|
|
|
30
30
|
*/
|
|
31
31
|
render?: React.ReactElement;
|
|
32
32
|
onError?: () => void;
|
|
33
|
+
/**
|
|
34
|
+
* GAR-15 (0.28.0): a captura dentro de uma moldura de celular — GENÉRICA, sem ilha, entalhe nem
|
|
35
|
+
* botões, como na web. Com moldura, o `style` vai para a MOLDURA (tamanho e lugar); a proporção
|
|
36
|
+
* (`ratio`, padrão 9/19,5) vai para a captura.
|
|
37
|
+
*/
|
|
38
|
+
frame?: "phone";
|
|
39
|
+
/** GAR-15 (0.28.0): a captura do tema escuro. No tema escuro aparece esta; no claro, a `source`. */
|
|
40
|
+
sourceDark?: AureaImageSource;
|
|
33
41
|
style?: StyleProp<ViewStyle>;
|
|
34
42
|
testID?: string;
|
|
35
43
|
}
|
|
@@ -48,7 +56,7 @@ export interface ImageProps {
|
|
|
48
56
|
* Trocar o bitmap por uma caixa muda o que se vê, não o que a foto É — e o `role="image"` mapeia
|
|
49
57
|
* nos dois sistemas (`ReactAccessibilityDelegate.kt:461`, `RCTConversions.h:96`).
|
|
50
58
|
*/
|
|
51
|
-
export declare function Image({ source, alt, ratio, fit, fallback, fallbackIcon, render, onError, style, testID, }: ImageProps): React.JSX.Element;
|
|
59
|
+
export declare function Image({ source: fonteClara, alt, ratio, fit, fallback, fallbackIcon, render, onError, frame, sourceDark, style, testID, }: ImageProps): React.JSX.Element;
|
|
52
60
|
export interface AureaGalleryItem {
|
|
53
61
|
id: string;
|
|
54
62
|
source: AureaImageSource;
|
package/dist/midia.js
CHANGED
|
@@ -56,6 +56,16 @@ const folha = criarFolha((t) => ({
|
|
|
56
56
|
},
|
|
57
57
|
contain: { backgroundColor: "transparent" },
|
|
58
58
|
quebrada: { alignItems: "center", justifyContent: "center" },
|
|
59
|
+
// GAR-15 (0.28.0): a moldura de celular — os mesmos tokens da web (`.image-frame-phone`): a
|
|
60
|
+
// superfície do cartão, a borda, a sombra média e o canto da folha de baixo; o de dentro acompanha.
|
|
61
|
+
moldura: {
|
|
62
|
+
padding: t.size.space2,
|
|
63
|
+
borderWidth: t.size.borderWidth, borderColor: t.color.border,
|
|
64
|
+
...canto(t.size.radiusSheet),
|
|
65
|
+
backgroundColor: t.color.card,
|
|
66
|
+
...(t.shadow.shadowMd ? { boxShadow: [t.shadow.shadowMd] } : null),
|
|
67
|
+
},
|
|
68
|
+
naMoldura: { ...canto(t.size.radiusSheet - t.size.space2), aspectRatio: 9 / 19.5 },
|
|
59
69
|
ladrilho: { gap: t.size.space1, padding: t.size.space1, ...canto(t.size.radiusLg) },
|
|
60
70
|
ladrilhoEscolhido: { backgroundColor: t.color.secondary },
|
|
61
71
|
}));
|
|
@@ -93,10 +103,12 @@ const fonteDaImagem = (s) => typeof s === "string" ? { uri: s } : s;
|
|
|
93
103
|
* Trocar o bitmap por uma caixa muda o que se vê, não o que a foto É — e o `role="image"` mapeia
|
|
94
104
|
* nos dois sistemas (`ReactAccessibilityDelegate.kt:461`, `RCTConversions.h:96`).
|
|
95
105
|
*/
|
|
96
|
-
export function Image({ source, alt, ratio, fit = "cover", fallback, fallbackIcon = "image", render, onError, style, testID, }) {
|
|
106
|
+
export function Image({ source: fonteClara, alt, ratio, fit = "cover", fallback, fallbackIcon = "image", render, onError, frame, sourceDark, style, testID, }) {
|
|
97
107
|
const t = useAureaTokens();
|
|
98
108
|
const s = folha(t);
|
|
99
109
|
const [quebrou, setQuebrou] = React.useState(false);
|
|
110
|
+
// O tema é do provider, então aqui basta escolher a fonte — não há duas imagens como na web.
|
|
111
|
+
const source = t.theme === "dark" && sourceDark ? sourceDark : fonteClara;
|
|
100
112
|
// A `source` nova merece uma tentativa nova — é a mesma linha do `Avatar` (`display.tsx:283`),
|
|
101
113
|
// e sem ela uma URL quebrada deixa um buraco permanente mesmo depois de o app trocar a foto.
|
|
102
114
|
React.useEffect(() => { setQuebrou(false); }, [source]);
|
|
@@ -104,11 +116,14 @@ export function Image({ source, alt, ratio, fit = "cover", fallback, fallbackIco
|
|
|
104
116
|
const caixa = [
|
|
105
117
|
s.imagem,
|
|
106
118
|
fit === "contain" && s.contain,
|
|
119
|
+
frame && s.naMoldura,
|
|
107
120
|
proporcao != null && { aspectRatio: proporcao },
|
|
108
|
-
style,
|
|
121
|
+
!frame && style,
|
|
109
122
|
];
|
|
123
|
+
// Com moldura, a imagem vai dentro de uma caixa que leva o `style` e o `testID` do consumidor.
|
|
124
|
+
const moldar = (filho) => frame ? _jsx(View, { testID: testID, style: [s.moldura, style], children: filho }) : filho;
|
|
110
125
|
if (quebrou) {
|
|
111
|
-
return (_jsx(View, { testID: testID, style: [caixa, s.quebrada], accessible: true, accessibilityRole: "image", accessibilityLabel: alt || undefined, children: fallback ?? (fallbackIcon
|
|
126
|
+
return moldar(_jsx(View, { testID: frame ? undefined : testID, style: [caixa, s.quebrada], accessible: true, accessibilityRole: "image", accessibilityLabel: alt || undefined, children: fallback ?? (fallbackIcon
|
|
112
127
|
? _jsx(Icon, { name: fallbackIcon, size: "lg", color: t.color.mutedForeground })
|
|
113
128
|
: null) }));
|
|
114
129
|
}
|
|
@@ -120,21 +135,21 @@ export function Image({ source, alt, ratio, fit = "cover", fallback, fallbackIco
|
|
|
120
135
|
accessible: alt !== "",
|
|
121
136
|
accessibilityRole: alt !== "" ? "image" : undefined,
|
|
122
137
|
accessibilityLabel: alt !== "" ? alt : undefined,
|
|
123
|
-
testID,
|
|
138
|
+
testID: frame ? undefined : testID,
|
|
124
139
|
};
|
|
125
140
|
// `cloneElement` e não `useRender`: o idioma do Base UI não existe aqui, e o que o caso real
|
|
126
141
|
// precisa é de um elemento pronto recebendo as nossas props. As props do consumidor vêm
|
|
127
142
|
// primeiro no objeto do elemento e as nossas depois — `source` e `onError` são o contrato
|
|
128
143
|
// deste componente, e deixá-las sobrescrevíveis seria prometer o substituto e não entregá-lo.
|
|
129
144
|
if (render)
|
|
130
|
-
return React.cloneElement(render, comuns);
|
|
145
|
+
return moldar(React.cloneElement(render, comuns));
|
|
131
146
|
// ⚠ O molde é `ImageStyle` e não `ViewStyle`, e a conversão é CONSCIENTE: os dois tipos só
|
|
132
147
|
// divergem em duas coisas — o `overflow` do `ViewStyle` aceita `"scroll"`, que o `ImageStyle`
|
|
133
148
|
// não tem, e o `ImageStyle` soma `resizeMode`/`tintColor`/`overlayColor`. **Nada aqui escreve
|
|
134
149
|
// nenhuma das duas**: a folha põe largura, raio, fundo e proporção, e o `resizeMode` vai como
|
|
135
150
|
// prop, ao lado. A API pública fica em `ViewStyle` de propósito — quem chama pensa em caixa, e
|
|
136
151
|
// fazer o consumidor importar `ImageStyle` para passar um raio seria vazar o primitivo.
|
|
137
|
-
return _jsx(ImageRN, { ...comuns, style: caixa, resizeMode: fit });
|
|
152
|
+
return moldar(_jsx(ImageRN, { ...comuns, style: caixa, resizeMode: fit }));
|
|
138
153
|
}
|
|
139
154
|
/**
|
|
140
155
|
* A grade de fotos, e a foto grande quando se toca.
|
package/dist/sistema.js
CHANGED
|
@@ -49,6 +49,9 @@ const folha = criarFolha((t) => ({
|
|
|
49
49
|
...canto(t.size.radiusControl), backgroundColor: t.color.fieldBg,
|
|
50
50
|
},
|
|
51
51
|
invalido: { borderColor: t.color.danger400 ?? t.color.destructive },
|
|
52
|
+
// No navegador o texto e o ícone vão numa caixa escondida do leitor de tela (E16): a mesma
|
|
53
|
+
// fileira do gatilho, ocupando a largura dele.
|
|
54
|
+
mioloWeb: { flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "space-between" },
|
|
52
55
|
desabilitado: { opacity: t.size.opacityDisabled },
|
|
53
56
|
// R-22 (02/10/2026), a "B" da prancha: miniatura QUADRADA de 64 — o `Avatar` `lg` da
|
|
54
57
|
// referência, e a medida que o botão de pôr foto passou a ter também (era 72, número à mão) — e o X
|
|
@@ -73,6 +76,46 @@ const folha = criarFolha((t) => ({
|
|
|
73
76
|
/** Quanto o X do `PhotoInput` sai da foto: metade do desenho (30) mais metade do alvo (44). */
|
|
74
77
|
const foraDoX = (t) => (t.size.controlHSm + Math.max(t.size.controlHSm, t.size.targetMin)) / 2;
|
|
75
78
|
const alturaDoTamanho = (t, s) => s === "sm" ? t.size.controlHSm : s === "lg" ? t.size.controlHLg : t.size.controlHMd;
|
|
79
|
+
// E16 (09/10/2026) · NO NAVEGADOR, o calendário "deles" é o do NAVEGADOR. A biblioteca de data não
|
|
80
|
+
// tem versão web: o arquivo genérico dela devolve `null` e só avisa no console — tocar no campo não
|
|
81
|
+
// abria nada (achado do app rodando no `react-native-web`). A saída é o `<input type="date">` do
|
|
82
|
+
// próprio navegador, INVISÍVEL e POR CIMA do nosso gatilho: a pele continua a nossa, o toque cai no
|
|
83
|
+
// campo do navegador, e é ele que o leitor de tela anuncia. Por cima, e não escondido e aberto por
|
|
84
|
+
// código, porque o `showPicker()` não existe no Safari do iPhone (dados de compatibilidade do MDN,
|
|
85
|
+
// 8.1.5): lá quem abre o seletor é o toque no próprio campo. No computador, o clique chama o
|
|
86
|
+
// `showPicker()` (Chrome e Edge 99, Firefox 101, Safari 17.4).
|
|
87
|
+
// E o ícone de calendário do próprio campo, esticado sobre ele inteiro: o clique vira um clique de
|
|
88
|
+
// verdade no ícone, e o navegador abre o seletor SEM o `showPicker()`. É o que vale dentro de uma
|
|
89
|
+
// página de outro endereço (moldura), onde o Chrome recusa o `showPicker()` — medido: "called from
|
|
90
|
+
// cross-origin iframe". O pseudo-elemento só existe no Chrome, no Edge e no Safari; o Firefox já
|
|
91
|
+
// abre o seletor no clique. O React 19 (o mínimo do pacote) põe a regra no topo da página UMA vez.
|
|
92
|
+
const REGRA_DO_CAMPO_WEB = "input[data-aurea-campo-data]::-webkit-calendar-picker-indicator{"
|
|
93
|
+
+ "position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;opacity:0;cursor:pointer}";
|
|
94
|
+
const doisDigitos = (n) => String(n).padStart(2, "0");
|
|
95
|
+
/** A data no formato do campo do navegador: `AAAA-MM-DD` (data) ou `HH:MM` (hora), no fuso local. */
|
|
96
|
+
const paraCampoWeb = (d, mode) => mode === "time"
|
|
97
|
+
? `${doisDigitos(d.getHours())}:${doisDigitos(d.getMinutes())}`
|
|
98
|
+
: `${d.getFullYear()}-${doisDigitos(d.getMonth() + 1)}-${doisDigitos(d.getDate())}`;
|
|
99
|
+
/**
|
|
100
|
+
* O texto do campo de volta para `Date`, mexendo só no que o campo escolhe: a data mantém a hora de
|
|
101
|
+
* `base`, e a hora mantém a data — como o seletor do aparelho, que parte do `value` (ou de agora).
|
|
102
|
+
* Texto incompleto ou vazio (a pessoa apagou) não vira data: `undefined`, e `onChange` não é chamado.
|
|
103
|
+
*/
|
|
104
|
+
const doCampoWeb = (texto, mode, base) => {
|
|
105
|
+
const d = new Date(base.getTime());
|
|
106
|
+
if (mode === "time") {
|
|
107
|
+
const m = /^(\d{2}):(\d{2})/.exec(texto);
|
|
108
|
+
if (!m)
|
|
109
|
+
return undefined;
|
|
110
|
+
d.setHours(Number(m[1]), Number(m[2]), 0, 0);
|
|
111
|
+
return d;
|
|
112
|
+
}
|
|
113
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(texto);
|
|
114
|
+
if (!m)
|
|
115
|
+
return undefined;
|
|
116
|
+
d.setFullYear(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
117
|
+
return d;
|
|
118
|
+
};
|
|
76
119
|
/**
|
|
77
120
|
* O campo de data — **a nossa pele, o calendário DELES**.
|
|
78
121
|
*
|
|
@@ -119,14 +162,52 @@ export function DatePicker({ value, onChange, mode = "date", minimumDate, maximu
|
|
|
119
162
|
setAbertoNoIOS(true);
|
|
120
163
|
}, [value, mode, minimumDate, maximumDate, receber]);
|
|
121
164
|
const mostrarSeletorIOS = abertoNoIOS && Platform.OS !== "android";
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
165
|
+
const conteudo = (_jsxs(_Fragment, { children: [_jsx(Text, { size: tam === "sm" ? "xs" : tam === "lg" ? "base" : "md", tone: value ? "default" : "subtle", numberOfLines: 1, children: texto }), icon && _jsx(Icon, { name: icon, size: "sm", color: t.color.subtleForeground })] }));
|
|
166
|
+
const pele = [
|
|
167
|
+
s.gatilho,
|
|
168
|
+
{ height: alturaDoTamanho(t, tam),
|
|
169
|
+
paddingHorizontal: tam === "sm" ? t.size.space3 : tam === "lg" ? t.size.space4 : 13 },
|
|
170
|
+
campo?.invalido && s.invalido,
|
|
171
|
+
inativo && s.desabilitado,
|
|
172
|
+
style,
|
|
173
|
+
];
|
|
174
|
+
if (Platform.OS === "web") {
|
|
175
|
+
// O gatilho vira só desenho: sem toque, fora do Tab e escondido do leitor de tela. Quem recebe
|
|
176
|
+
// o toque, o foco e o nome é o campo do navegador, por cima dele.
|
|
177
|
+
const campoDoNavegador = React.createElement("input", {
|
|
178
|
+
type: mode === "time" ? "time" : "date",
|
|
179
|
+
value: value ? paraCampoWeb(value, mode) : "",
|
|
180
|
+
min: minimumDate && mode === "date" ? paraCampoWeb(minimumDate, mode) : undefined,
|
|
181
|
+
max: maximumDate && mode === "date" ? paraCampoWeb(maximumDate, mode) : undefined,
|
|
182
|
+
disabled: inativo,
|
|
183
|
+
"aria-label": campo?.label ?? (value ? texto : placeholder ?? strings.datePlaceholder),
|
|
184
|
+
"aria-invalid": campo?.invalido || undefined,
|
|
185
|
+
"aria-description": campo?.hint,
|
|
186
|
+
"data-testid": testID ? `${testID}-campo` : undefined,
|
|
187
|
+
"data-aurea-campo-data": "",
|
|
188
|
+
onChange: (e) => {
|
|
189
|
+
const data = doCampoWeb(e.currentTarget.value, mode, value ?? new Date());
|
|
190
|
+
if (data)
|
|
191
|
+
onChange?.(data);
|
|
192
|
+
},
|
|
193
|
+
onClick: (e) => {
|
|
194
|
+
// Sem `showPicker` (Safari do iPhone) o próprio toque já abriu o seletor; e ele pode
|
|
195
|
+
// recusar (página dentro de outra), caso em que o campo continua lá, focado.
|
|
196
|
+
try {
|
|
197
|
+
e.currentTarget.showPicker?.();
|
|
198
|
+
}
|
|
199
|
+
catch { /* o toque no campo basta */ }
|
|
200
|
+
},
|
|
201
|
+
style: {
|
|
202
|
+
position: "absolute", top: 0, left: 0, width: "100%", height: "100%", margin: 0,
|
|
203
|
+
padding: 0, border: 0, opacity: 0, cursor: inativo ? "default" : "pointer",
|
|
204
|
+
// O calendário do navegador segue o tema da Aurea, e não o do sistema operacional.
|
|
205
|
+
colorScheme: t.theme,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
return (_jsxs(Pressable, { testID: testID, disabled: inativo, focusable: false, style: pele, children: [_jsx(View, { importantForAccessibility: "no-hide-descendants", style: s.mioloWeb, children: conteudo }), React.createElement("style", { href: "aurea-campo-data", precedence: "default" }, REGRA_DO_CAMPO_WEB), campoDoNavegador] }));
|
|
209
|
+
}
|
|
210
|
+
return (_jsxs(_Fragment, { children: [_jsx(Pressable, { testID: testID, onPress: inativo ? undefined : abrir, disabled: inativo, accessibilityRole: "button", accessibilityLabel: campo?.label, accessibilityHint: campo?.hint, accessibilityValue: { text: value ? texto : undefined }, ...estadoAcessivel({ disabled: !!inativo }), style: pele, children: conteudo }), mostrarSeletorIOS && (_jsx(RNDateTimePicker, { value: value ?? new Date(), mode: mode, minimumDate: minimumDate, maximumDate: maximumDate, onChange: receber }))] }));
|
|
130
211
|
}
|
|
131
212
|
// R-23 (06/10/2026) · a data em que a foto foi tirada, e NADA MAIS do EXIF.
|
|
132
213
|
//
|
package/dist/strings.d.ts
CHANGED
|
@@ -60,6 +60,10 @@ export interface AureaStrings {
|
|
|
60
60
|
confirmProceed: string;
|
|
61
61
|
/** O botão que dispensa um aviso da pilha. Mesmo nome da web. */
|
|
62
62
|
dismissNotification: string;
|
|
63
|
+
/** GAR-14 (0.28.0): o aviso do `Button share` quando o link foi copiado. Mesmo nome da web. */
|
|
64
|
+
linkCopied: string;
|
|
65
|
+
/** GAR-14 (0.28.0): o aviso quando nem compartilhar nem copiar deu certo. Mesmo nome da web. */
|
|
66
|
+
linkCopyFailed: string;
|
|
63
67
|
/** O nome da `Table` sem `caption`. Mesmo nome — e mesmo recuo — da web. */
|
|
64
68
|
tableLabel: string;
|
|
65
69
|
/** O nome do `Chart` sem `label`. Mesmo nome da web. */
|
package/dist/strings.js
CHANGED
|
@@ -48,6 +48,8 @@ export const defaultStrings = {
|
|
|
48
48
|
confirmCancel: "Cancel",
|
|
49
49
|
confirmProceed: "Continue",
|
|
50
50
|
dismissNotification: "Dismiss notification",
|
|
51
|
+
linkCopied: "Link copied",
|
|
52
|
+
linkCopyFailed: "Could not copy. Copy the link below.",
|
|
51
53
|
tableLabel: "Table",
|
|
52
54
|
chartLabel: "Chart",
|
|
53
55
|
increment: "Increase",
|
|
@@ -96,6 +98,8 @@ export const ptBR = {
|
|
|
96
98
|
confirmCancel: "Cancelar",
|
|
97
99
|
confirmProceed: "Continuar",
|
|
98
100
|
dismissNotification: "Dispensar notificação",
|
|
101
|
+
linkCopied: "Link copiado",
|
|
102
|
+
linkCopyFailed: "Não deu para copiar. Copie o link abaixo.",
|
|
99
103
|
tableLabel: "Tabela",
|
|
100
104
|
chartLabel: "Gráfico",
|
|
101
105
|
increment: "Aumentar",
|
package/dist/text.d.ts
CHANGED
|
@@ -2,10 +2,10 @@ import * as React from "react";
|
|
|
2
2
|
import { type TextProps as TextPropsRN } from "react-native";
|
|
3
3
|
/** A escala dos tokens (`--text-xs` … `--text-5xl`). `md` é o corpo, como no `body` da web. */
|
|
4
4
|
export type AureaTextSize = "xs" | "sm" | "md" | "base" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl";
|
|
5
|
-
/** Os
|
|
6
|
-
export type AureaTextWeight = 400 | 500 | 600 | 700;
|
|
7
|
-
/** Papel de tipografia — os
|
|
8
|
-
export type AureaTextFont = "ui" | "editorial" | "code";
|
|
5
|
+
/** Os pesos que o pacote de fontes entrega (o 800 desde a 0.28.0, GAR-16). Ver ADR-0039: cada peso é uma FAMÍLIA. */
|
|
6
|
+
export type AureaTextWeight = 400 | 500 | 600 | 700 | 800;
|
|
7
|
+
/** Papel de tipografia — os `--font-*` da Aurea (o `heading` desde a 0.28.0). */
|
|
8
|
+
export type AureaTextFont = "ui" | "editorial" | "code" | "heading";
|
|
9
9
|
/** O que a cor SIGNIFICA. Mesmo vocabulário do `tone` do Button (ADR-0044). */
|
|
10
10
|
export type AureaTextTone = "default" | "muted" | "subtle" | "primary" | "link" | "danger" | "success" | "warning" | "info";
|
|
11
11
|
export type AureaTextLeading = "none" | "tight" | "normal" | "relaxed";
|
|
@@ -31,6 +31,11 @@ export interface TextProps extends TextPropsRN {
|
|
|
31
31
|
align?: "auto" | "left" | "right" | "center";
|
|
32
32
|
/** Aplica o `tracking` do token. É RAZÃO de `em`: multiplicado pelo `fontSize` aqui dentro. */
|
|
33
33
|
tracking?: "tight" | "normal" | "wide" | "wider" | "widest";
|
|
34
|
+
/**
|
|
35
|
+
* GAR-16 (0.28.0): algarismos da mesma largura (`fontVariant: ["tabular-nums"]`, iOS e Android) —
|
|
36
|
+
* para número que alinha em coluna ou muda na tela. Desligado por padrão, como na web.
|
|
37
|
+
*/
|
|
38
|
+
numeric?: boolean;
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
41
|
* Texto da Aurea. Sem provider acima, `useAureaTokens` levanta — de propósito: um padrão
|
|
@@ -42,7 +47,7 @@ export interface TextProps extends TextPropsRN {
|
|
|
42
47
|
* próprias. Pedir peso 600 por `fontWeight` devolveria o Regular sintetizado — **em silêncio**.
|
|
43
48
|
* Quem escolhe a fonte aqui é o `fontFamily`, com o nome PostScript que o provider já resolveu.
|
|
44
49
|
*/
|
|
45
|
-
export declare function Text({ type, size, weight: pesoPedido, font: fontePedida, tone, leading, italic, align, tracking: trackingPedido, style, ...rest }: TextProps): React.JSX.Element;
|
|
50
|
+
export declare function Text({ type, size, weight: pesoPedido, font: fontePedida, tone, leading, italic, align, tracking: trackingPedido, numeric, style, ...rest }: TextProps): React.JSX.Element;
|
|
46
51
|
export type AureaTypographyColor = "default" | "muted";
|
|
47
52
|
export type AureaTypographyWeight = "normal" | "medium" | "semibold" | "bold";
|
|
48
53
|
export type AureaTypographyAlign = "start" | "center" | "end" | "justify";
|
package/dist/text.js
CHANGED
|
@@ -21,7 +21,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
21
21
|
// que os tokens já publicam seria uma segunda verdade sobre tipografia.
|
|
22
22
|
import * as React from "react";
|
|
23
23
|
import { Text as TextRN } from "react-native";
|
|
24
|
-
import { canto, criarFolha } from "./estilos.js";
|
|
24
|
+
import { canto, criarFolha, letraDoToken } from "./estilos.js";
|
|
25
25
|
import { useAureaTokens, useSobreAMarca } from "./theme.js";
|
|
26
26
|
// 🔴 A ESCALA TEM CINCO DEGRAUS QUE SE ENXERGAM, E DEZ NOMES — ordem do Victor, 19/09/2026,
|
|
27
27
|
// olhando o app no aparelho: *"o tamanho das fontes: existem variações demais, fica muito
|
|
@@ -66,7 +66,8 @@ const TAMANHO = {
|
|
|
66
66
|
const ENTRELINHA = {
|
|
67
67
|
none: "leadingNone", tight: "leadingTight", normal: "leadingNormal", relaxed: "leadingRelaxed",
|
|
68
68
|
};
|
|
69
|
-
|
|
69
|
+
// O título usa o papel `heading` (GAR-16, 0.28.0) — hoje a mesma fonte do texto, no mesmo peso 600.
|
|
70
|
+
const titulo = (tamanho) => ({ tamanho, peso: 600, entrelinha: "leadingTight", fonte: "heading", junto: true });
|
|
70
71
|
const PAPEL = {
|
|
71
72
|
h1: titulo("text4xl"), h2: titulo("text3xl"), h3: titulo("text2xl"),
|
|
72
73
|
h4: titulo("textXl"), h5: titulo("textLg"), h6: titulo("textBase"),
|
|
@@ -90,7 +91,10 @@ const COR = {
|
|
|
90
91
|
const folha = criarFolha((t) => {
|
|
91
92
|
const tons = {};
|
|
92
93
|
for (const [tom, token] of Object.entries(COR)) {
|
|
93
|
-
|
|
94
|
+
// ADR-0061: `success`, `info`, `warning` e `destructive` são a cor de FUNDO cheio; a LETRA é o
|
|
95
|
+
// par `-400` de cada tema. Na 0.27.0 este mapa ficou de fora e o texto de tom saía com a cor de
|
|
96
|
+
// fundo — no escuro, ~2,5:1. Achado na 0.28.0; `paleta-adr0061.test.tsx` cobra.
|
|
97
|
+
tons[tom] = { color: letraDoToken(t, token) ?? t.color.foreground };
|
|
94
98
|
}
|
|
95
99
|
return tons;
|
|
96
100
|
});
|
|
@@ -104,7 +108,7 @@ const folha = criarFolha((t) => {
|
|
|
104
108
|
* próprias. Pedir peso 600 por `fontWeight` devolveria o Regular sintetizado — **em silêncio**.
|
|
105
109
|
* Quem escolhe a fonte aqui é o `fontFamily`, com o nome PostScript que o provider já resolveu.
|
|
106
110
|
*/
|
|
107
|
-
export function Text({ type, size, weight: pesoPedido, font: fontePedida, tone = "default", leading, italic = false, align, tracking: trackingPedido, style, ...rest }) {
|
|
111
|
+
export function Text({ type, size, weight: pesoPedido, font: fontePedida, tone = "default", leading, italic = false, align, tracking: trackingPedido, numeric = false, style, ...rest }) {
|
|
108
112
|
const t = useAureaTokens();
|
|
109
113
|
const s = folha(t);
|
|
110
114
|
const sobreAMarca = useSobreAMarca();
|
|
@@ -134,13 +138,14 @@ export function Text({ type, size, weight: pesoPedido, font: fontePedida, tone =
|
|
|
134
138
|
// É a impedância que a Etapa 2 mediu e resolveu emitindo razão em vez de dp.
|
|
135
139
|
...(tracking ? { letterSpacing: fontSize * t.tracking[`tracking${tracking[0].toUpperCase()}${tracking.slice(1)}`] } : null),
|
|
136
140
|
...(align ? { textAlign: align } : null),
|
|
141
|
+
...(numeric ? { fontVariant: ["tabular-nums"] } : null),
|
|
137
142
|
// O código leva a pele do `code` da web: fundo, canto e um recheio pequeno (a referência).
|
|
138
143
|
...(type === "code" ? {
|
|
139
144
|
alignSelf: "flex-start", backgroundColor: t.color.surface2, ...canto(t.size.radiusXs),
|
|
140
145
|
paddingHorizontal: t.size.space1, paddingVertical: t.size.space05,
|
|
141
146
|
} : null),
|
|
142
147
|
};
|
|
143
|
-
}, [t, type, papel, size, weight, font, leading, italic, align, tracking]);
|
|
148
|
+
}, [t, type, papel, size, weight, font, leading, italic, align, tracking, numeric]);
|
|
144
149
|
// 🔴 SOBRE UMA SUPERFÍCIE DA MARCA A COR É FORÇADA, e ela vence até o tom explícito.
|
|
145
150
|
// Medido contra o `primary` nos dois temas: texto comum dá **1,83 no escuro**, esmaecido
|
|
146
151
|
// **1,35**, e `danger` menos ainda. **Nenhum tom alcança os 4,5 da norma sobre o amarelo** —
|
package/dist/theme.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AureaToastManager } from "./toast.js";
|
|
1
2
|
import * as React from "react";
|
|
2
3
|
import { type AureaIconRegistry } from "./icon.js";
|
|
3
4
|
import { type AureaStrings } from "./strings.js";
|
|
@@ -73,6 +74,12 @@ export declare function useAureaTokens(): AureaTokens;
|
|
|
73
74
|
* As frases visíveis. Mesmo nome do hook da web (`useAureaStrings`), tabela bem menor — ver
|
|
74
75
|
* `strings.ts`.
|
|
75
76
|
*/
|
|
77
|
+
/**
|
|
78
|
+
* O gerente de avisos do `ToastHost`, para quem não pode importar o `toast.tsx` — o `Button share`
|
|
79
|
+
* (GAR-14, 0.28.0): o `toast.tsx` importa o `IconButton`, e o botão importar o toast fecharia um
|
|
80
|
+
* ciclo. Interno: o público continua sendo o `useToast`. `null` sem `ToastHost` acima.
|
|
81
|
+
*/
|
|
82
|
+
export declare const AvisosContext: React.Context<AureaToastManager | null>;
|
|
76
83
|
export declare function useAureaStrings(): AureaStrings;
|
|
77
84
|
export interface SobreAMarcaValor {
|
|
78
85
|
/** A única cor que se pode desenhar sobre a marca: letra, contorno e glifo. 4,54 nos dois temas. */
|
package/dist/theme.js
CHANGED
|
@@ -1,32 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
// Aurea nativo — o provider de tema × densidade, e os dois hooks que ele alimenta.
|
|
3
|
-
//
|
|
4
|
-
// ADR-0037: o motor é o `StyleSheet` puro e **esta camada é nossa**. Não há Unistyles para
|
|
5
|
-
// registrar tema, então o lugar onde os dois eixos moram é um contexto do React — e a ADR foi
|
|
6
|
-
// honesta sobre a única aposta que isso carrega: *"se o contexto do React, sem as otimizações do
|
|
7
|
-
// Unistyles, re-renderiza a ponto de doer na troca de tema em tela real"*. Ainda não foi medido
|
|
8
|
-
// em aparelho.
|
|
9
|
-
//
|
|
10
|
-
// UM CONTEXTO, E NÃO DOIS — e vale escrever por que, porque a primeira versão tinha dois.
|
|
11
|
-
// A ideia era que quem só troca o tema assinasse um contexto "estável" e não re-renderizasse
|
|
12
|
-
// junto com a tela. **Não funciona, e a razão é óbvia depois de vista:** o valor de controle
|
|
13
|
-
// contém `theme` e `density`, então ele muda exatamente quando o de tokens muda. Dois contextos
|
|
14
|
-
// que mudam juntos não poupam render nenhum — poupam só a impressão de estarem poupando.
|
|
15
|
-
//
|
|
16
|
-
// O que de fato reduz custo é o `useMemo`: entre renders que NÃO trocam eixo, os dois objetos são
|
|
17
|
-
// os mesmos por identidade, e a subárvore não é notificada. É o que está feito.
|
|
18
|
-
//
|
|
19
|
-
// Se um dia doer em aparelho, a saída é a que a ADR-0037 já prevê — contexto fatiado de verdade,
|
|
20
|
-
// com as AÇÕES separadas do ESTADO. Aí a separação compra algo, porque as ações não mudam. Não
|
|
21
|
-
// está feito hoje porque seria mais superfície pública para um ganho não medido.
|
|
22
|
-
//
|
|
23
|
-
// PARIDADE COM A WEB, de propósito: `useAureaTheme()` devolve o mesmo `{theme, density, setTheme,
|
|
24
|
-
// setDensity, toggleTheme}` do hook homônimo de `@aurea-uds/react`. Uma diferença real: lá o
|
|
25
|
-
// valor pode ser `null` (no servidor o tema é desconhecido e fingir um produz erro de
|
|
26
|
-
// hidratação); aqui não há SSR, então nunca é nulo.
|
|
27
|
-
//
|
|
28
|
-
// E `useAureaTokens()` NÃO tem par na web porque lá ele não faria sentido: o CSS resolve o token
|
|
29
|
-
// no uso. Não é hook duplicado — é o hook que só o alvo sem cascata precisa ter.
|
|
30
2
|
import * as React from "react";
|
|
31
3
|
import { useColorScheme } from "react-native";
|
|
32
4
|
import { IconRegistryProvider } from "./icon.js";
|
|
@@ -106,6 +78,12 @@ export function useAureaTokens() {
|
|
|
106
78
|
* As frases visíveis. Mesmo nome do hook da web (`useAureaStrings`), tabela bem menor — ver
|
|
107
79
|
* `strings.ts`.
|
|
108
80
|
*/
|
|
81
|
+
/**
|
|
82
|
+
* O gerente de avisos do `ToastHost`, para quem não pode importar o `toast.tsx` — o `Button share`
|
|
83
|
+
* (GAR-14, 0.28.0): o `toast.tsx` importa o `IconButton`, e o botão importar o toast fecharia um
|
|
84
|
+
* ciclo. Interno: o público continua sendo o `useToast`. `null` sem `ToastHost` acima.
|
|
85
|
+
*/
|
|
86
|
+
export const AvisosContext = React.createContext(null);
|
|
109
87
|
export function useAureaStrings() {
|
|
110
88
|
return usar("useAureaStrings").strings;
|
|
111
89
|
}
|
package/dist/toast.js
CHANGED
|
@@ -62,7 +62,7 @@ import { Icon } from "./icon.js";
|
|
|
62
62
|
import { ICONE_DA_VARIANTE } from "./feedback.js";
|
|
63
63
|
import { useReduceMotion, driverNativo } from "./movimento.js";
|
|
64
64
|
import { Text } from "./text.js";
|
|
65
|
-
import { useAureaStrings, useAureaTokens } from "./theme.js";
|
|
65
|
+
import { AvisosContext, useAureaStrings, useAureaTokens } from "./theme.js";
|
|
66
66
|
const folha = criarFolha((t) => ({
|
|
67
67
|
hospedeiro: { flex: 1 },
|
|
68
68
|
pilha: {
|
|
@@ -81,9 +81,9 @@ const folha = criarFolha((t) => ({
|
|
|
81
81
|
...(t.shadow.shadowMd ? { boxShadow: [t.shadow.shadowMd] } : null),
|
|
82
82
|
},
|
|
83
83
|
texto: { flex: 1, minWidth: 0, gap: 2 },
|
|
84
|
-
aviso_info: { backgroundColor: t.color.infoBg, borderColor: t.color.info },
|
|
85
|
-
aviso_success: { backgroundColor: t.color.successBg, borderColor: t.color.success },
|
|
86
|
-
aviso_warning: { backgroundColor: t.color.warningBg, borderColor: t.color.warning },
|
|
84
|
+
aviso_info: { backgroundColor: t.color.infoBg, borderColor: t.color.info400 ?? t.color.info },
|
|
85
|
+
aviso_success: { backgroundColor: t.color.successBg, borderColor: t.color.success400 ?? t.color.success },
|
|
86
|
+
aviso_warning: { backgroundColor: t.color.warningBg, borderColor: t.color.warning400 ?? t.color.warning },
|
|
87
87
|
aviso_danger: {
|
|
88
88
|
backgroundColor: t.color.dangerBg,
|
|
89
89
|
borderColor: t.color.danger400 ?? t.color.destructive,
|
|
@@ -93,7 +93,9 @@ const folha = criarFolha((t) => ({
|
|
|
93
93
|
const DURACAO_PADRAO = 5000;
|
|
94
94
|
/** A entrada, nos mesmos `.28s` do `.toast` do CSS. */
|
|
95
95
|
const ENTRADA = 280;
|
|
96
|
-
|
|
96
|
+
// O contexto mora no `theme.tsx` desde a 0.28.0 (GAR-14): o `Button share` também o lê, e ele não
|
|
97
|
+
// pode importar este arquivo sem fechar um ciclo (este importa o `IconButton`).
|
|
98
|
+
const Ctx = AvisosContext;
|
|
97
99
|
/**
|
|
98
100
|
* O gerente de avisos. **Precisa de um `ToastHost` acima na árvore.**
|
|
99
101
|
*
|
|
@@ -193,10 +195,10 @@ export function Toast({ toast, onClose }) {
|
|
|
193
195
|
laco.start();
|
|
194
196
|
return () => laco.stop();
|
|
195
197
|
}, [reduzir, entrada, t.easing.easeEmphasized]);
|
|
196
|
-
const corDoIcone = tipo === "success" ? t.color.success
|
|
197
|
-
: tipo === "warning" ? t.color.warning
|
|
198
|
+
const corDoIcone = tipo === "success" ? (t.color.success400 ?? t.color.success)
|
|
199
|
+
: tipo === "warning" ? (t.color.warning400 ?? t.color.warning)
|
|
198
200
|
: tipo === "danger" ? (t.color.danger400 ?? t.color.destructive)
|
|
199
|
-
: t.color.info;
|
|
201
|
+
: (t.color.info400 ?? t.color.info);
|
|
200
202
|
const glifo = toast.icon === false ? null : (toast.icon ?? ICONE_DA_VARIANTE[tipo]);
|
|
201
203
|
return (_jsxs(Animated.View, { accessible: true, ...(tipo === "danger" ? { accessibilityRole: "alert" } : null), accessibilityLiveRegion: tipo === "danger" ? "assertive" : "polite", style: [
|
|
202
204
|
s.aviso, s[`aviso_${tipo}`],
|
package/dist/tokens.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type AureaShadow = {
|
|
|
15
15
|
*
|
|
16
16
|
* O que chega de fora **não** é total — ver `AureaFontInput` e `normalizarEscala`.
|
|
17
17
|
*/
|
|
18
|
-
export type AureaFontScale = Record<400 | 500 | 600 | 700, string> & {
|
|
18
|
+
export type AureaFontScale = Record<400 | 500 | 600 | 700 | 800, string> & {
|
|
19
19
|
/** Só o papel `ui` tem itálico no pacote de fontes; nos outros é `undefined`. */
|
|
20
20
|
italic?: string;
|
|
21
21
|
};
|
|
@@ -23,14 +23,14 @@ export type AureaFontScale = Record<400 | 500 | 600 | 700, string> & {
|
|
|
23
23
|
* O que o consumidor INJETA, e é parcial porque o pacote de fontes é parcial — medido em
|
|
24
24
|
* 03/09/2026 no `@aurea-uds/fonts/native`:
|
|
25
25
|
*
|
|
26
|
-
* ui 400 400i 500 600 700
|
|
26
|
+
* ui 400 400i 500 600 700 800 ← o 800 entrou na 0.28.0 (GAR-16)
|
|
27
27
|
* editorial 400 500 600 700 ← apelido do `ui` desde a ADR-0053 (sai na 1.0)
|
|
28
28
|
* code 400 500 600 ← não há 700
|
|
29
29
|
*
|
|
30
30
|
* É a grade dos arquivos de fonte, não um esquecimento nosso. Até a 0.12.4 o `editorial` era a
|
|
31
31
|
* IBM Plex Serif, sem 400; a ADR-0053 tirou a serifada e o deixou apontando para a Atkinson.
|
|
32
32
|
*/
|
|
33
|
-
export type AureaFontInput = Partial<Record<"400" | "500" | "600" | "700" | "400i", string>>;
|
|
33
|
+
export type AureaFontInput = Partial<Record<"400" | "500" | "600" | "700" | "800" | "400i", string>>;
|
|
34
34
|
export type AureaTokens = {
|
|
35
35
|
theme: AureaThemeName;
|
|
36
36
|
density: AureaDensity;
|
|
@@ -43,6 +43,7 @@ export type AureaTokens = {
|
|
|
43
43
|
ui: AureaFontScale;
|
|
44
44
|
editorial: AureaFontScale;
|
|
45
45
|
code: AureaFontScale;
|
|
46
|
+
heading: AureaFontScale;
|
|
46
47
|
};
|
|
47
48
|
/**
|
|
48
49
|
* `boxShadow` do RN 0.76+ — **a primeira camada** de cada sombra. Para a sombra INTEIRA, use
|
|
@@ -66,6 +67,9 @@ export type AureaFontFamilies = {
|
|
|
66
67
|
ui: AureaFontInput;
|
|
67
68
|
editorial: AureaFontInput;
|
|
68
69
|
code: AureaFontInput;
|
|
70
|
+
/** O papel do título (GAR-16, 0.28.0). Opcional: sem ele, o título usa o mapa do `ui` — que é a
|
|
71
|
+
* mesma fonte hoje, e é o que acontece com quem ainda passa o mapa de antes da 0.28.0. */
|
|
72
|
+
heading?: AureaFontInput;
|
|
69
73
|
};
|
|
70
74
|
/**
|
|
71
75
|
* Resolve os tokens para um par (tema, densidade). Puro e sem estado: o provider chama isto
|
package/dist/tokens.js
CHANGED
|
@@ -23,7 +23,7 @@ const ehSombra = (v) => typeof v === "object" && v !== null && "blurRadius" in v
|
|
|
23
23
|
function mesclar(...grupos) {
|
|
24
24
|
return Object.assign({}, ...grupos);
|
|
25
25
|
}
|
|
26
|
-
const PESOS = [400, 500, 600, 700];
|
|
26
|
+
const PESOS = [400, 500, 600, 700, 800];
|
|
27
27
|
/**
|
|
28
28
|
* Fecha a grade parcial que o pacote de fontes emite numa escala TOTAL.
|
|
29
29
|
*
|
|
@@ -95,6 +95,7 @@ export function resolverTokens(theme, density, fontFamilies) {
|
|
|
95
95
|
ui: normalizarEscala(fontFamilies?.ui, texto.fontUi ?? "System"),
|
|
96
96
|
editorial: normalizarEscala(fontFamilies?.editorial, texto.fontEditorial ?? "System"),
|
|
97
97
|
code: normalizarEscala(fontFamilies?.code, texto.fontCode ?? "System"),
|
|
98
|
+
heading: normalizarEscala(fontFamilies?.heading ?? fontFamilies?.ui, texto.fontHeading ?? texto.fontUi ?? "System"),
|
|
98
99
|
};
|
|
99
100
|
return { theme, density, color, size, font, shadow, shadowLayers: { ...shadowLayers }, easing, tracking: { ...tracking }, remInDp: REM_EM_DP };
|
|
100
101
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurea-uds/native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "A Aurea UDS para React Native: o provider de tema e densidade, os componentes e os ícones do Phosphor sobre react-native-svg.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aurea",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"NOTICE"
|
|
56
56
|
],
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@aurea-uds/tokens": "^0.
|
|
58
|
+
"@aurea-uds/tokens": "^0.28.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@react-native-community/datetimepicker": ">=8",
|