@aurea-uds/native 0.16.1 → 0.18.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/feedback.d.ts +22 -4
- package/dist/feedback.js +61 -5
- package/dist/index.d.ts +3 -3
- package/dist/layout.d.ts +11 -4
- package/dist/layout.js +3 -1
- package/dist/navigation.d.ts +17 -2
- package/dist/navigation.js +130 -37
- package/package.json +2 -2
package/dist/feedback.d.ts
CHANGED
|
@@ -43,20 +43,38 @@ export interface SkeletonProps {
|
|
|
43
43
|
* aqui): anunciar "caixa" três vezes enquanto a tela carrega não ajuda ninguém.
|
|
44
44
|
*/
|
|
45
45
|
export declare function Skeleton({ width, height, radius, style, testID }: SkeletonProps): React.JSX.Element;
|
|
46
|
+
/** O tom da barra: a mesma lista fechada do `tone` do `Button`. Pausado é `neutral`; falha é `danger`. */
|
|
47
|
+
export type AureaProgressTone = "brand" | "neutral" | "success" | "warning" | "danger" | "info";
|
|
46
48
|
export interface ProgressProps {
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
+
/**
|
|
50
|
+
* 0 a 100. Fora disso é grampeado, como na web (`Math.max(0, Math.min(100, value))`).
|
|
51
|
+
* **Sem `value`, a barra é indeterminada** (AN-07): o total ainda não se sabe, e um pedaço corre
|
|
52
|
+
* pelo trilho, no lugar de um 0% que parece parado.
|
|
53
|
+
*/
|
|
54
|
+
value?: number;
|
|
49
55
|
label?: string;
|
|
56
|
+
/**
|
|
57
|
+
* O texto de apoio, no alto à direita — o `ProgressBar.Output` do HeroUI: velocidade, tempo que
|
|
58
|
+
* falta, bytes. Em texto, ele vai junto no `accessibilityValue`. É texto: a barra é um elemento
|
|
59
|
+
* só para o leitor de tela, e coisa tocável dentro dela some no iPhone (`check 43`).
|
|
60
|
+
*/
|
|
61
|
+
detail?: React.ReactNode;
|
|
62
|
+
tone?: AureaProgressTone;
|
|
50
63
|
style?: StyleProp<ViewStyle>;
|
|
51
64
|
testID?: string;
|
|
52
65
|
}
|
|
53
66
|
/**
|
|
54
|
-
* A barra determinada
|
|
67
|
+
* A barra de progresso — determinada, ou indeterminada quando não há `value` (AN-07, 03/10/2026).
|
|
55
68
|
*
|
|
56
69
|
* O papel é `progressbar` e o valor vai no `accessibilityValue`: sem isso o leitor de tela
|
|
57
70
|
* anuncia que existe uma barra e não diz em quanto ela está, que é a única informação que ela tem.
|
|
71
|
+
* Sem total, não há `now` — a barra não finge um 0%.
|
|
72
|
+
*
|
|
73
|
+
* Sem total, um pedaço de 2/5 corre de -100% a 350% da própria largura em 1,5 s, na curva do
|
|
74
|
+
* `ProgressBar` do HeroUI 3.2.6 — os mesmos números da web. Quando a pessoa pede menos movimento
|
|
75
|
+
* ele não corre: a barra inteira, apagada, e nunca um pedaço parado, que leria como 40% feito.
|
|
58
76
|
*/
|
|
59
|
-
export declare function Progress({ value, label, style, testID }: ProgressProps): React.JSX.Element;
|
|
77
|
+
export declare function Progress({ value, label, detail, tone, style, testID }: ProgressProps): React.JSX.Element;
|
|
60
78
|
export type AureaAlertVariant = "info" | "success" | "warning" | "danger";
|
|
61
79
|
/** Mesmos quatro glifos do `feedback-client.tsx:43`. */
|
|
62
80
|
export declare const ICONE_DA_VARIANTE: Readonly<Record<AureaAlertVariant, IconName>>;
|
package/dist/feedback.js
CHANGED
|
@@ -43,6 +43,11 @@ const folha = criarFolha((t) => ({
|
|
|
43
43
|
// ── Progress ───────────────────────────────────────────────────────────────────────────────
|
|
44
44
|
trilho: { height: 8, borderRadius: t.size.radiusFull, backgroundColor: t.color.surface3, overflow: "hidden" },
|
|
45
45
|
preenchimento: { height: "100%", borderRadius: t.size.radiusFull, backgroundColor: t.color.primary },
|
|
46
|
+
// AN-07: a caixa com o texto de apoio em cima — a grade do HeroUI, `gap-1` até o trilho, a saída
|
|
47
|
+
// à direita em algarismos de largura igual.
|
|
48
|
+
campoProgresso: { gap: t.size.space1 },
|
|
49
|
+
apoioProgresso: { alignSelf: "flex-end" },
|
|
50
|
+
algarismos: { fontVariant: ["tabular-nums"] },
|
|
46
51
|
// ── Alert ──────────────────────────────────────────────────────────────────────────────────
|
|
47
52
|
// A web usa GRADE de três trilhas (ícone, corpo, ação). Aqui é linha de flex com o corpo
|
|
48
53
|
// `flex:1` — mesmo resultado, e `display:grid` não existe no RN (a mesma nota do `Grid`).
|
|
@@ -152,16 +157,67 @@ export function Skeleton({ width, height, radius, style, testID }) {
|
|
|
152
157
|
style,
|
|
153
158
|
] }));
|
|
154
159
|
}
|
|
160
|
+
const COR_DO_TOM = {
|
|
161
|
+
brand: "primary", neutral: "mutedForeground", success: "success",
|
|
162
|
+
warning: "warning", danger: "destructive", info: "info",
|
|
163
|
+
};
|
|
155
164
|
/**
|
|
156
|
-
* A barra determinada
|
|
165
|
+
* A barra de progresso — determinada, ou indeterminada quando não há `value` (AN-07, 03/10/2026).
|
|
157
166
|
*
|
|
158
167
|
* O papel é `progressbar` e o valor vai no `accessibilityValue`: sem isso o leitor de tela
|
|
159
168
|
* anuncia que existe uma barra e não diz em quanto ela está, que é a única informação que ela tem.
|
|
169
|
+
* Sem total, não há `now` — a barra não finge um 0%.
|
|
170
|
+
*
|
|
171
|
+
* Sem total, um pedaço de 2/5 corre de -100% a 350% da própria largura em 1,5 s, na curva do
|
|
172
|
+
* `ProgressBar` do HeroUI 3.2.6 — os mesmos números da web. Quando a pessoa pede menos movimento
|
|
173
|
+
* ele não corre: a barra inteira, apagada, e nunca um pedaço parado, que leria como 40% feito.
|
|
160
174
|
*/
|
|
161
|
-
export function Progress({ value, label, style, testID }) {
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
|
|
175
|
+
export function Progress({ value, label, detail, tone = "brand", style, testID }) {
|
|
176
|
+
const t = useAureaTokens();
|
|
177
|
+
const s = folha(t);
|
|
178
|
+
const reduzir = useReduceMotion();
|
|
179
|
+
const semTotal = value == null || Number.isNaN(value);
|
|
180
|
+
const pct = semTotal ? undefined : Math.max(0, Math.min(100, value));
|
|
181
|
+
const cor = t.color[COR_DO_TOM[tone]];
|
|
182
|
+
const temApoio = detail != null && detail !== false && detail !== "";
|
|
183
|
+
const emTexto = temApoio && (typeof detail === "string" || typeof detail === "number") ? String(detail) : undefined;
|
|
184
|
+
// A largura do trilho, para o pedaço saber quanto andar. O `translateX` do RN não aceita
|
|
185
|
+
// porcentagem, e a do CSS é da própria peça — aqui ela vira conta: 2/5 do trilho.
|
|
186
|
+
const [largura, setLargura] = React.useState(0);
|
|
187
|
+
const corrida = React.useRef(new Animated.Value(0)).current;
|
|
188
|
+
const corre = semTotal && reduzir === false && largura > 0;
|
|
189
|
+
React.useEffect(() => {
|
|
190
|
+
// `reduzir === false`, e não `!reduzir`: enquanto é `null` o sistema ainda não respondeu.
|
|
191
|
+
if (!corre)
|
|
192
|
+
return;
|
|
193
|
+
corrida.setValue(0);
|
|
194
|
+
const laco = Animated.loop(Animated.timing(corrida, {
|
|
195
|
+
toValue: 1, duration: 1500, easing: Easing.bezier(0.65, 0, 0.35, 1), useNativeDriver: driverNativo(),
|
|
196
|
+
}));
|
|
197
|
+
laco.start();
|
|
198
|
+
return () => laco.stop();
|
|
199
|
+
}, [corrida, corre, largura]);
|
|
200
|
+
const pedaco = largura * 0.4;
|
|
201
|
+
const preenchimento = !semTotal
|
|
202
|
+
? _jsx(View, { style: [s.preenchimento, { width: `${pct}%`, backgroundColor: cor }] })
|
|
203
|
+
: corre
|
|
204
|
+
? _jsx(Animated.View, { style: [s.preenchimento, { width: pedaco, backgroundColor: cor },
|
|
205
|
+
{ transform: [{ translateX: corrida.interpolate({ inputRange: [0, 1], outputRange: [-pedaco, pedaco * 3.5] }) }] }] })
|
|
206
|
+
: _jsx(View, { style: [s.preenchimento, { width: "100%", backgroundColor: cor, opacity: t.size.opacityDisabled }] });
|
|
207
|
+
const valor = semTotal
|
|
208
|
+
? (emTexto != null ? { text: emTexto } : undefined)
|
|
209
|
+
: { min: 0, max: 100, now: pct, ...(emTexto != null && { text: `${pct}%, ${emTexto}` }) };
|
|
210
|
+
const papel = {
|
|
211
|
+
testID, accessible: true, accessibilityRole: "progressbar",
|
|
212
|
+
accessibilityLabel: label, accessibilityValue: valor,
|
|
213
|
+
};
|
|
214
|
+
const medir = semTotal ? (e) => setLargura(e.nativeEvent.layout.width) : undefined;
|
|
215
|
+
if (!temApoio) {
|
|
216
|
+
return _jsx(View, { ...papel, onLayout: medir, style: [s.trilho, style], children: preenchimento });
|
|
217
|
+
}
|
|
218
|
+
return (_jsxs(View, { ...papel, style: [s.campoProgresso, style], children: [emTexto != null
|
|
219
|
+
? _jsx(Text, { type: "body-sm", weight: 500, tone: "muted", style: [s.apoioProgresso, s.algarismos], children: emTexto })
|
|
220
|
+
: _jsx(View, { style: s.apoioProgresso, children: detail }), _jsx(View, { onLayout: medir, style: s.trilho, children: preenchimento })] }));
|
|
165
221
|
}
|
|
166
222
|
/** Mesmos quatro glifos do `feedback-client.tsx:43`. */
|
|
167
223
|
export const ICONE_DA_VARIANTE = {
|
package/dist/index.d.ts
CHANGED
|
@@ -11,11 +11,11 @@ export type { IconProps, IconName, PhosphorIconName, AureaIconNames, IconWeight,
|
|
|
11
11
|
export { Screen } from "./screen.js";
|
|
12
12
|
export type { ScreenProps, AureaScreenBackground, AureaScreenEdge } from "./screen.js";
|
|
13
13
|
export { Stack, Cluster, Grid, Card, Separator } from "./layout.js";
|
|
14
|
-
export type { StackProps, ClusterProps, AureaStackAlign, AureaClusterAlign, AureaClusterJustify, GridProps, CardProps, SeparatorProps, AureaCardVariant, } from "./layout.js";
|
|
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
16
|
export type { ButtonProps, IconButtonProps, LinkButtonProps, ThemeToggleProps, AureaButtonAppearance, AureaButtonSize, AureaButtonTone, } from "./actions.js";
|
|
17
17
|
export { Spinner, Skeleton, Progress, Alert, EmptyState, DataState, ICONE_DA_VARIANTE } from "./feedback.js";
|
|
18
|
-
export type { SpinnerProps, SkeletonProps, ProgressProps, AlertProps, EmptyStateProps, DataStateProps, AureaSpinnerSize, AureaAlertVariant, AureaDataStateValue, } from "./feedback.js";
|
|
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
20
|
export type { BadgeProps, StatusProps, AvatarProps, KPIProps, AureaBadgeAnchor, AureaBadgeEmphasis, AureaBadgeFit, AureaBadgeSize, AureaBadgeTone, AureaStatusVariant, AureaAvatarSize, } from "./display.js";
|
|
21
21
|
export { useAureaStrings } from "./theme.js";
|
|
@@ -23,7 +23,7 @@ export { defaultStrings, ptBR, gravidadeDoEstado, AUREA_UNIVERSAL_STATES } from
|
|
|
23
23
|
export type { AureaStrings, AureaUniversalState } from "./strings.js";
|
|
24
24
|
export { useReduceMotion } from "./movimento.js";
|
|
25
25
|
export { BottomNav, Topbar, NavList, Stepper, Tabs } from "./navigation.js";
|
|
26
|
-
export type { BottomNavProps, TopbarProps, NavListProps, StepperProps, AureaNavItem, AureaNavListItem, AureaStepItem, AureaStepState, AureaBottomNavVariant, AureaBottomNavIndicator, AureaBottomNavWidth, AureaTopbarVariant, AureaTopbarInset, TabsProps, AureaTabItem, AureaTabsVariant, } from "./navigation.js";
|
|
26
|
+
export type { BottomNavProps, TopbarProps, NavListProps, StepperProps, AureaNavItem, AureaNavListItem, AureaNavListIndicator, AureaStepItem, AureaStepState, AureaBottomNavVariant, AureaBottomNavIndicator, AureaBottomNavWidth, AureaTopbarVariant, AureaTopbarInset, TabsProps, AureaTabItem, AureaTabsVariant, } from "./navigation.js";
|
|
27
27
|
export { Field, Label, Input, Textarea, Select, Switch, Checkbox, Radio, RadioGroup, SegmentedControl, Form, KeyboardAvoiding, useCampo, } from "./inputs.js";
|
|
28
28
|
export type { FieldProps, LabelProps, InputProps, TextareaProps, SelectProps, SwitchProps, CheckboxProps, RadioProps, RadioGroupProps, RadioGroupItemProps, AureaRadioIndicatorPlacement, SegmentedControlProps, FormProps, KeyboardAvoidingProps, AureaFieldSize, } from "./inputs.js";
|
|
29
29
|
export { InputGroup, InputGroupAddon, PasswordField, useGrupoDeCampo } from "./inputs.js";
|
package/dist/layout.d.ts
CHANGED
|
@@ -33,12 +33,19 @@ export interface ClusterProps extends Omit<StackProps, "align"> {
|
|
|
33
33
|
* Sem nenhuma das três, nada muda.
|
|
34
34
|
*/
|
|
35
35
|
export declare function Cluster({ align, justify, wrap, style, ...rest }: ClusterProps): React.JSX.Element;
|
|
36
|
+
/**
|
|
37
|
+
* A largura mínima da coluna, por nome — AN-08 (03/10/2026), os mesmos quatro da web (`.grid-min-*`
|
|
38
|
+
* no `aurea.css`): `xs` 8rem, `sm` 12rem, `md` 15rem (o padrão) e `lg` 20rem. Em rem, porque é rem
|
|
39
|
+
* na web; o `remInDp` do token faz a conta.
|
|
40
|
+
*/
|
|
41
|
+
export type AureaGridMin = "xs" | "sm" | "md" | "lg";
|
|
36
42
|
export interface GridProps extends ViewProps {
|
|
37
43
|
/**
|
|
38
|
-
* Largura mínima de cada coluna, em dp
|
|
39
|
-
* `15rem` — e `1rem = 16dp`, medido, então
|
|
44
|
+
* Largura mínima de cada coluna, em dp, ou por nome (`xs`, `sm`, `md`, `lg` — AN-08). O
|
|
45
|
+
* equivalente do `--grid-min` da web, cujo padrão é `15rem` — e `1rem = 16dp`, medido, então
|
|
46
|
+
* **240**.
|
|
40
47
|
*/
|
|
41
|
-
minColumnWidth?: number;
|
|
48
|
+
minColumnWidth?: number | AureaGridMin;
|
|
42
49
|
children?: React.ReactNode;
|
|
43
50
|
}
|
|
44
51
|
/**
|
|
@@ -61,7 +68,7 @@ export interface GridProps extends ViewProps {
|
|
|
61
68
|
* O `min(…, 100%)` da web tem par aqui: `maxWidth: "100%"` no filho, para a coluna não estourar o
|
|
62
69
|
* contêiner quando o texto cresce — a mesma lição que a Fase 11 registrou no CSS.
|
|
63
70
|
*/
|
|
64
|
-
export declare function Grid({ minColumnWidth, style, children, onLayout, ...rest }: GridProps): React.JSX.Element;
|
|
71
|
+
export declare function Grid({ minColumnWidth: pedido, style, children, onLayout, ...rest }: GridProps): React.JSX.Element;
|
|
65
72
|
/** Qual superfície o cartão é. Mesmos seis nomes da ficha da web. */
|
|
66
73
|
export interface SeparatorProps extends ViewProps {
|
|
67
74
|
orientation?: "horizontal" | "vertical";
|
package/dist/layout.js
CHANGED
|
@@ -86,6 +86,7 @@ export function Cluster({ align, justify, wrap, style, ...rest }) {
|
|
|
86
86
|
style,
|
|
87
87
|
], ...rest }));
|
|
88
88
|
}
|
|
89
|
+
const REM_DA_COLUNA = { xs: 8, sm: 12, md: 15, lg: 20 };
|
|
89
90
|
/**
|
|
90
91
|
* Colunas que se acomodam sem breakpoint.
|
|
91
92
|
*
|
|
@@ -106,9 +107,10 @@ export function Cluster({ align, justify, wrap, style, ...rest }) {
|
|
|
106
107
|
* O `min(…, 100%)` da web tem par aqui: `maxWidth: "100%"` no filho, para a coluna não estourar o
|
|
107
108
|
* contêiner quando o texto cresce — a mesma lição que a Fase 11 registrou no CSS.
|
|
108
109
|
*/
|
|
109
|
-
export function Grid({ minColumnWidth = 240, style, children, onLayout, ...rest }) {
|
|
110
|
+
export function Grid({ minColumnWidth: pedido = 240, style, children, onLayout, ...rest }) {
|
|
110
111
|
const t = useAureaTokens();
|
|
111
112
|
const s = folha(t);
|
|
113
|
+
const minColumnWidth = typeof pedido === "number" ? pedido : REM_DA_COLUNA[pedido] * t.remInDp;
|
|
112
114
|
const [largura, setLargura] = React.useState(null);
|
|
113
115
|
const plano = (StyleSheet.flatten([s.grid, style]) ?? {});
|
|
114
116
|
const vao = numero(plano.columnGap) ?? numero(plano.gap) ?? t.size.space4;
|
package/dist/navigation.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { type ViewProps } from "react-native";
|
|
2
|
+
import { type ImageSourcePropType, type ViewProps } from "react-native";
|
|
3
3
|
import { type AureaIcon } from "./icon.js";
|
|
4
4
|
import { type AureaFilaJustify } from "./rolagem.js";
|
|
5
5
|
export interface AureaNavItem {
|
|
@@ -14,7 +14,7 @@ export interface AureaNavItem {
|
|
|
14
14
|
export type AureaBottomNavVariant = "floating" | "edge";
|
|
15
15
|
/** Quanto a pílula ocupa: a tela menos a margem (`full`, o de sempre) ou só as abas (`content`). */
|
|
16
16
|
export type AureaBottomNavWidth = "full" | "content";
|
|
17
|
-
export type AureaBottomNavIndicator = "none" | "subtle" | "pill" | "circle" | "circle-raised" | "circle-outline" | "circle-bold";
|
|
17
|
+
export type AureaBottomNavIndicator = "none" | "subtle" | "pill" | "circle" | "circle-raised" | "circle-outline" | "circle-bold" | "capsule" | "expand";
|
|
18
18
|
export interface BottomNavProps extends ViewProps {
|
|
19
19
|
items: AureaNavItem[];
|
|
20
20
|
/** O `id` do item corrente. */
|
|
@@ -136,6 +136,11 @@ export interface TopbarProps extends ViewProps {
|
|
|
136
136
|
* e quem nomeia a tela é o conteúdo dela.
|
|
137
137
|
*/
|
|
138
138
|
export declare function Topbar({ variant, inset, brand, children, style, ...rest }: TopbarProps): React.JSX.Element;
|
|
139
|
+
/** Um sinal da linha (AN-04): fixada, silenciada. O `label` não aparece; é o que o leitor de tela lê. */
|
|
140
|
+
export interface AureaNavListIndicator {
|
|
141
|
+
icon: AureaIcon;
|
|
142
|
+
label: string;
|
|
143
|
+
}
|
|
139
144
|
export interface AureaNavListItem {
|
|
140
145
|
id: string;
|
|
141
146
|
label: React.ReactNode;
|
|
@@ -145,6 +150,16 @@ export interface AureaNavListItem {
|
|
|
145
150
|
icon?: AureaIcon;
|
|
146
151
|
onPress?: () => void;
|
|
147
152
|
disabled?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* A foto da linha (AN-04, 03/10/2026) — o formato do `Avatar` daqui (`source`, `fallback`), no
|
|
155
|
+
* tamanho dele de sempre, o mesmo da web. Toma o lugar do `icon`.
|
|
156
|
+
*/
|
|
157
|
+
avatar?: {
|
|
158
|
+
source?: ImageSourcePropType | string;
|
|
159
|
+
fallback?: React.ReactNode;
|
|
160
|
+
};
|
|
161
|
+
/** Os sinais ao lado do `value` (AN-04): pequenos e apagados, cada um com nome. */
|
|
162
|
+
indicators?: AureaNavListIndicator[];
|
|
148
163
|
}
|
|
149
164
|
export interface NavListProps extends ViewProps {
|
|
150
165
|
items: AureaNavListItem[];
|
package/dist/navigation.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { Pressable, View } from "react-native";
|
|
3
3
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
4
4
|
import { useAnotarBottomNav } from "./barranav.js";
|
|
5
|
-
import { Badge } from "./display.js";
|
|
5
|
+
import { Avatar, Badge } from "./display.js";
|
|
6
6
|
import { comOpacidade, criarFolha, estadoAcessivel, fioDoEscolhido } from "./estilos.js";
|
|
7
7
|
import { Icon } from "./icon.js";
|
|
8
8
|
import { Card } from "./layout.js";
|
|
@@ -80,7 +80,18 @@ const folha = criarFolha((t) => ({
|
|
|
80
80
|
flex: 1, minWidth: 0, alignItems: "center", justifyContent: "center",
|
|
81
81
|
// `space05` e não `space1` — a barra mais baixa (30/09/2026, pedido do Victor, aprovado pela
|
|
82
82
|
// imagem). Com a moldura de 32 e o vão de 4 eram 9 entre o ícone e o rótulo; ver `marca`.
|
|
83
|
-
|
|
83
|
+
// O NOME COLADO NO ÍCONE — 03/10/2026, pedido do Victor (*"o texto pode ficar mais próximo do
|
|
84
|
+
// ícone"*): vão ZERO, como no Telegram, em que o nome começa onde a caixa do ícone acaba. O
|
|
85
|
+
// `space05` daqui (30/09/2026) somava 2 aos 2 de folga da própria linha do nome; medido na
|
|
86
|
+
// bancada, eram 9 de tinta a tinta no `pill` e 13 no `circle-bold`.
|
|
87
|
+
gap: 0, minHeight: t.size.controlHLg,
|
|
88
|
+
// 🔴 A ALTURA DO TELEGRAM — 03/10/2026, pedido do Victor: *"ainda acho ele muito largo
|
|
89
|
+
// comparado a bottomnav como do telegram"*, e "largo" é a grossura. Medido no fonte do
|
|
90
|
+
// Telegram para Android 12.10.6 (`DialogsActivity.MAIN_TABS_HEIGHT = 56`, `GlassTabView`): a
|
|
91
|
+
// pílula tem 56; o botão, 48; o ícone, 24; o nome, 12. A nossa media 73 no `circle-bold`. O
|
|
92
|
+
// recheio de cima e de baixo cai de `space1` para `space05`, e o nome sai em 12 com a
|
|
93
|
+
// entrelinha justa (ver o `Text` do rótulo). Igual na web (`aurea.css`).
|
|
94
|
+
paddingHorizontal: t.size.space1, paddingVertical: t.size.space05,
|
|
84
95
|
borderRadius: t.size.radiusControl,
|
|
85
96
|
},
|
|
86
97
|
// Com a pílula do tamanho do conteúdo, `flex: 1` MATA as abas: no RN ele é base ZERO, e a soma
|
|
@@ -100,6 +111,57 @@ const folha = criarFolha((t) => ({
|
|
|
100
111
|
width: 32, height: 32, borderRadius: t.size.radiusControl },
|
|
101
112
|
marcaRedonda: { borderRadius: t.size.radiusFull },
|
|
102
113
|
contador: { position: "absolute", top: -t.size.space05, right: -t.size.space05 },
|
|
114
|
+
// ── A BARRA FICA PARADA QUANDO SE TROCA DE ABA — pedido do Victor, 03/10/2026 ──────────────
|
|
115
|
+
// *"me incomoda o bottom nav todo se mexer [...] quero ele estático, apenas os botões
|
|
116
|
+
// dinâmicos"*. Medido na bancada com o `react-native-web`: no `circle-bold` com
|
|
117
|
+
// `width="content"` a barra ia de 249 a 257 de largura conforme a aba escolhida, e por estar no
|
|
118
|
+
// centro, andava inteira. A regra daqui para baixo: NENHUMA medida depende de o item estar
|
|
119
|
+
// escolhido. Só a pintura muda. Quem cobra: `tests/unit/native-bottomnav-parada.test.tsx`.
|
|
120
|
+
//
|
|
121
|
+
// `circle-bold`: o disco amarelo é uma caixa DENTRO do item, de 56 (o `--bottomnav-bold` do
|
|
122
|
+
// `aurea.css`), em TODOS os itens. Antes o disco ERA o item escolhido: ele media 56 e os outros
|
|
123
|
+
// mediam o conteúdo, então a soma mudava com a escolha. O recheio passa do item para o disco,
|
|
124
|
+
// e a altura da barra não muda.
|
|
125
|
+
aba_disco: { paddingHorizontal: 0, paddingVertical: 0 },
|
|
126
|
+
// Sem a moldura de 32 (é o disco que desenha a forma, e não ela) e sem vão, o nome fica colado
|
|
127
|
+
// no ícone; o recheio de cima e de baixo volta a `space1` para o disco continuar quase redondo
|
|
128
|
+
// (56 × 48).
|
|
129
|
+
disco: {
|
|
130
|
+
alignItems: "center", justifyContent: "center", gap: 0,
|
|
131
|
+
width: 56, minHeight: t.size.controlHLg, borderRadius: t.size.radiusFull,
|
|
132
|
+
paddingHorizontal: t.size.space1, paddingVertical: t.size.space1,
|
|
133
|
+
},
|
|
134
|
+
// `capsule` (a "A" da bancada, o Material 3 Expressive): a moldura do ícone vira a cápsula de
|
|
135
|
+
// 56 × 32 do indicador ativo dele — os mesmos `--bottomnav-bold` e `--bottomnav-ring` do
|
|
136
|
+
// `aurea.css`, em todos os itens. O rótulo fica embaixo, fora dela.
|
|
137
|
+
marcaCapsula: { width: 56, height: 32, borderRadius: t.size.radiusFull },
|
|
138
|
+
// O contador fica no canto do ÍCONE, e não da cápsula: sem a cápsula pintada (item não
|
|
139
|
+
// escolhido) ele ficaria solto, longe do sino. São os (56 − 32) / 2 a mais para dentro.
|
|
140
|
+
contadorCapsula: { right: (56 - 32) / 2 - t.size.space05 },
|
|
141
|
+
// No `content` o item tem a largura da cápsula mais o recheio de sempre (`space1`), e não o
|
|
142
|
+
// `space3` do `aba_content`: com ele cada item teria 80 e a barra, 340.
|
|
143
|
+
aba_capsula_content: { paddingHorizontal: t.size.space1 },
|
|
144
|
+
// `expand` (a "C" da bancada): os outros itens mostram só o ícone; o escolhido vira cápsula
|
|
145
|
+
// amarela com o nome ao lado. É o ÚNICO em que o botão escolhido cresce — os botões andam, e a
|
|
146
|
+
// BARRA não: a vaga do escolhido é FIXA, e não do tamanho do nome. Ícone (24) + vão (`space2`)
|
|
147
|
+
// + lugar do nome (`space20`, 80) + recheio (`space3` dos dois lados) = 136. Nome maior que 80
|
|
148
|
+
// termina em "…". Medir o nome de cada aba daria uma barra que muda com a lista; esta não muda.
|
|
149
|
+
aba_expande: {
|
|
150
|
+
flexDirection: "row", gap: t.size.space2, paddingVertical: 0, paddingHorizontal: 0,
|
|
151
|
+
minHeight: t.size.targetMin, minWidth: t.size.targetMin,
|
|
152
|
+
},
|
|
153
|
+
aba_expande_outro_content: { flexGrow: 0, flexShrink: 0, flexBasis: "auto", width: t.size.targetMin },
|
|
154
|
+
aba_expande_escolhido: {
|
|
155
|
+
flexGrow: 0, flexShrink: 0, flexBasis: "auto",
|
|
156
|
+
width: t.size.iconLg + t.size.space2 + t.size.space20 + t.size.space3 * 2,
|
|
157
|
+
paddingHorizontal: t.size.space3, backgroundColor: t.color.primary,
|
|
158
|
+
},
|
|
159
|
+
// A moldura do ícone no `expand` tem a largura do ícone: o contador pendura nele e o nome vem
|
|
160
|
+
// logo ao lado — com 32 de moldura sobrava folga dos dois lados do ícone dentro da cápsula.
|
|
161
|
+
marcaExpande: { width: t.size.iconLg },
|
|
162
|
+
// O nome do item que NÃO é texto, nos não escolhidos do `expand`: fora da tela e sem ocupar
|
|
163
|
+
// lugar, mas na árvore — o texto simples vira `accessibilityLabel` do item.
|
|
164
|
+
rotuloEscondido: { position: "absolute", opacity: 0 },
|
|
103
165
|
// ── NavList ────────────────────────────────────────────────────────────────────────────────
|
|
104
166
|
lista: { gap: t.size.space05 },
|
|
105
167
|
linha: {
|
|
@@ -113,6 +175,8 @@ const folha = criarFolha((t) => ({
|
|
|
113
175
|
// Nenhuma medida nova: `flex`, `minWidth` e o MESMO `gap` da moldura.
|
|
114
176
|
linhaMiolo: { flex: 1, minWidth: 0, flexDirection: "row", alignItems: "center", gap: t.size.space3 },
|
|
115
177
|
linhaTexto: { flex: 1, minWidth: 0 },
|
|
178
|
+
// AN-04: os sinais, com o vão de dentro do texto (`space1`), como o `.nav-list-indicators` da web.
|
|
179
|
+
sinais: { flexDirection: "row", alignItems: "center", gap: t.size.space1 },
|
|
116
180
|
// ── Tabs ───────────────────────────────────────────────────────────────────────────────────
|
|
117
181
|
// A cápsula é a MESMA do `SegmentedControl`, e o CSS diz isso em voz alta no comentário de
|
|
118
182
|
// cima do `.tabs` (`aurea.css:1114`): *"Mesma regra da .segmented"*. Os três números vêm de
|
|
@@ -241,6 +305,9 @@ export function BottomNav({ items, current, variant = "floating", indicator = "n
|
|
|
241
305
|
const strings = useAureaStrings();
|
|
242
306
|
const inset = useSafeAreaInsets();
|
|
243
307
|
const redondo = indicator.startsWith("circle");
|
|
308
|
+
const disco = indicator === "circle-bold";
|
|
309
|
+
const capsula = indicator === "capsule";
|
|
310
|
+
const expande = indicator === "expand";
|
|
244
311
|
const anotar = useAnotarBottomNav();
|
|
245
312
|
const flutua = variant !== "edge";
|
|
246
313
|
const justa = flutua && width === "content";
|
|
@@ -270,48 +337,72 @@ export function BottomNav({ items, current, variant = "floating", indicator = "n
|
|
|
270
337
|
const ativo = it.id === current;
|
|
271
338
|
const corDoTexto = !ativo ? t.color.mutedForeground
|
|
272
339
|
: indicator === "none" ? t.color.primaryEmphasis
|
|
273
|
-
: indicator === "circle-bold" ? t.color.primaryForeground
|
|
340
|
+
: indicator === "circle-bold" || indicator === "expand" ? t.color.primaryForeground
|
|
274
341
|
: indicator === "subtle" || indicator === "pill" ? t.color.primaryEmphasis
|
|
275
342
|
: redondo ? t.color.primaryEmphasis
|
|
276
343
|
: t.color.foreground;
|
|
277
|
-
|
|
344
|
+
// Na `capsule` o ícone está SOBRE o amarelo e o rótulo não: cada um tem a sua cor.
|
|
345
|
+
const corDoIcone = ativo && capsula ? t.color.primaryForeground : corDoTexto;
|
|
346
|
+
// No `expand` o nome só aparece no escolhido; nos outros ele vira o nome do item para o
|
|
347
|
+
// leitor de tela (o texto simples) ou fica na árvore sem ocupar lugar (o que não é texto).
|
|
348
|
+
const nomeVisivel = !expande || ativo;
|
|
349
|
+
const marca = (_jsxs(View, { style: [
|
|
350
|
+
// A moldura de 32 só serve aos indicadores redondos, em que ela VIRA o círculo. Nos
|
|
351
|
+
// outros ela era folga vazia em volta do ícone de 24: fica da altura dele. A largura
|
|
352
|
+
// continua 32, que é o canto do contador. Igual na web (`aurea.css`).
|
|
353
|
+
// No `circle-bold` a moldura NÃO é o círculo (é o disco, em volta do ícone E do nome):
|
|
354
|
+
// ela fica da altura do ícone, e o nome encosta nele.
|
|
355
|
+
s.marca, redondo && !disco ? s.marcaRedonda : { height: t.size.iconLg },
|
|
356
|
+
capsula && s.marcaCapsula, expande && s.marcaExpande,
|
|
357
|
+
ativo && indicator === "circle" && { backgroundColor: comOpacidade(t.color.primary, 0.14) },
|
|
358
|
+
ativo && indicator === "circle-raised" && {
|
|
359
|
+
backgroundColor: t.color.popover, boxShadow: [t.shadow.shadowMd],
|
|
360
|
+
},
|
|
361
|
+
ativo && indicator === "circle-outline" && {
|
|
362
|
+
borderWidth: t.size.borderWidth, borderColor: t.color.primaryEmphasis,
|
|
363
|
+
},
|
|
364
|
+
ativo && capsula && { backgroundColor: t.color.primary },
|
|
365
|
+
], children: [it.icon && _jsx(Icon, { name: it.icon, size: "lg", color: corDoIcone, weight: ativo ? "fill" : undefined }), it.badge != null && it.badge !== false && (_jsx(View, { style: [s.contador, capsula && s.contadorCapsula], children: typeof it.badge === "number"
|
|
366
|
+
? _jsx(Badge, { tone: "danger", emphasis: "solid", size: "xs", count: it.badge })
|
|
367
|
+
: _jsx(Badge, { tone: "danger", emphasis: "solid", size: "xs", dot: true }) }))] }));
|
|
368
|
+
// 🔴 O PESO DO RÓTULO NÃO MUDA COM A ESCOLHA (03/10/2026). Era 500 no escolhido e 400 nos
|
|
369
|
+
// outros, e texto mais grosso é texto mais LARGO: no `content` a barra mudava de largura a
|
|
370
|
+
// cada toque, em todos os indicadores. Agora é 500 em todos — o "Label medium" do Material
|
|
371
|
+
// 3, que também deixou de engrossar o escolhido no Expressive pela mesma razão. O que marca
|
|
372
|
+
// a escolha é a cor, o ícone cheio e a forma. No `expand` o nome é `sm`: ao lado do ícone,
|
|
373
|
+
// e não embaixo dele.
|
|
374
|
+
const rotulo = typeof it.label === "string"
|
|
375
|
+
? (nomeVisivel
|
|
376
|
+
// 12 numa linha de 16, o do Telegram — e o `text-xs` da web. Antes era o `xs` do `Text`
|
|
377
|
+
// nativo, que sobe para 14 (ADR-0050) com linha de 21: era o que mais engrossava a
|
|
378
|
+
// barra. A linha é o tamanho mais `space1`, como na web: com a entrelinha justa (14,4)
|
|
379
|
+
// a perna do "j" passava da caixa. No `expand` o nome vai ao lado do ícone, em 14.
|
|
380
|
+
? _jsx(Text, { type: expande ? "body-sm" : "body-xs", weight: 500, numberOfLines: 1, style: [{ color: corDoTexto, lineHeight: (expande ? t.size.textSm : t.size.textXs) + t.size.space1 },
|
|
381
|
+
expande && { flexShrink: 1 }], children: it.label })
|
|
382
|
+
: null)
|
|
383
|
+
: (nomeVisivel ? it.label : _jsx(View, { style: s.rotuloEscondido, children: it.label }));
|
|
384
|
+
return (_jsx(Pressable, { onPress: it.onPress, disabled: it.disabled,
|
|
278
385
|
// `link` e não `tab`: uma aba troca um painel DESTA tela; isto troca de tela.
|
|
279
|
-
accessibilityRole: "link", ...estadoAcessivel({ selected: ativo, disabled: !!it.disabled }), style: [
|
|
386
|
+
accessibilityRole: "link", accessibilityLabel: !nomeVisivel && typeof it.label === "string" ? it.label : undefined, ...estadoAcessivel({ selected: ativo, disabled: !!it.disabled }), style: [
|
|
280
387
|
s.aba, justa && s.aba_content,
|
|
281
388
|
indicator === "subtle" && { borderRadius: t.size.radiusCard - t.size.space1 },
|
|
282
389
|
ativo && (indicator === "subtle" || indicator === "pill")
|
|
283
390
|
&& { backgroundColor: comOpacidade(t.color.primary, 0.12) },
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// 🔴
|
|
391
|
+
disco && s.aba_disco,
|
|
392
|
+
capsula && justa && s.aba_capsula_content,
|
|
393
|
+
// 🔴 Larguras por EXTENSO, e não `flex: 0` — defeito visto pelo Victor no app rodando
|
|
287
394
|
// no navegador (02/10/2026): o `react-native-web` 0.21.3 passa `flex: 0` cru para o
|
|
288
|
-
// CSS, onde ele vale `0 1 0%`, e
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
},
|
|
298
|
-
|
|
299
|
-
// A moldura de 32 só serve aos indicadores redondos, em que ela VIRA o círculo. Nos
|
|
300
|
-
// outros ela era folga vazia em volta do ícone de 24: fica da altura dele. A largura
|
|
301
|
-
// continua 32, que é o canto do contador. Igual na web (`aurea.css`).
|
|
302
|
-
s.marca, redondo ? s.marcaRedonda : { height: t.size.iconLg },
|
|
303
|
-
ativo && indicator === "circle" && { backgroundColor: comOpacidade(t.color.primary, 0.14) },
|
|
304
|
-
ativo && indicator === "circle-raised" && {
|
|
305
|
-
backgroundColor: t.color.popover, boxShadow: [t.shadow.shadowMd],
|
|
306
|
-
},
|
|
307
|
-
ativo && indicator === "circle-outline" && {
|
|
308
|
-
borderWidth: t.size.borderWidth, borderColor: t.color.primaryEmphasis,
|
|
309
|
-
},
|
|
310
|
-
], children: [it.icon && _jsx(Icon, { name: it.icon, size: "lg", color: corDoTexto, weight: ativo ? "fill" : undefined }), it.badge != null && it.badge !== false && (_jsx(View, { style: s.contador, children: typeof it.badge === "number"
|
|
311
|
-
? _jsx(Badge, { tone: "danger", emphasis: "solid", size: "xs", count: it.badge })
|
|
312
|
-
: _jsx(Badge, { tone: "danger", emphasis: "solid", size: "xs", dot: true }) }))] }), typeof it.label === "string"
|
|
313
|
-
? _jsx(Text, { size: "xs", weight: ativo ? 500 : 400, numberOfLines: 1, style: { color: corDoTexto }, children: it.label })
|
|
314
|
-
: it.label] }, it.id));
|
|
395
|
+
// CSS, onde ele vale `0 1 0%`, e a peça encolhia até o recheio. No Yoga `flex: 0` é
|
|
396
|
+
// "inflexível"; as três por extenso valem o mesmo nos dois.
|
|
397
|
+
// Quem cobra: `tests/unit/native-flex-navegador.test.tsx`.
|
|
398
|
+
expande && s.aba_expande,
|
|
399
|
+
expande && !ativo && justa && s.aba_expande_outro_content,
|
|
400
|
+
expande && ativo && s.aba_expande_escolhido,
|
|
401
|
+
], children: disco
|
|
402
|
+
// O disco do `circle-bold`: 56 de largura em todos os itens, amarelo só no escolhido.
|
|
403
|
+
// É o `.bottom-nav-disc` da web.
|
|
404
|
+
? _jsxs(View, { style: [s.disco, ativo && { backgroundColor: t.color.primary }], children: [marca, rotulo] })
|
|
405
|
+
: _jsxs(_Fragment, { children: [marca, rotulo] }) }, it.id));
|
|
315
406
|
}) }));
|
|
316
407
|
}
|
|
317
408
|
/**
|
|
@@ -373,10 +464,12 @@ export function NavList({ items, chevron = "caret-right", style, ...rest }) {
|
|
|
373
464
|
// • valor em COMPONENTE fica FORA, irmão do miolo, porque pode ser tocável.
|
|
374
465
|
const temAcao = typeof it.onPress === "function";
|
|
375
466
|
const valorEhTexto = typeof it.value === "string" || typeof it.value === "number";
|
|
376
|
-
const miolo = (_jsxs(_Fragment, { children: [it.
|
|
467
|
+
const miolo = (_jsxs(_Fragment, { children: [it.avatar
|
|
468
|
+
? _jsx(Avatar, { source: it.avatar.source, fallback: it.avatar.fallback })
|
|
469
|
+
: it.icon && _jsx(Icon, { name: it.icon, size: "md", color: t.color.foreground }), _jsxs(View, { style: s.linhaTexto, children: [typeof it.label === "string"
|
|
377
470
|
? _jsx(Text, { size: "sm", numberOfLines: 1, children: it.label }) : it.label, it.description != null && (typeof it.description === "string"
|
|
378
471
|
? _jsx(Text, { size: "xs", tone: "muted", numberOfLines: 1, children: it.description })
|
|
379
|
-
: it.description)] }), valorEhTexto && _jsx(Text, { size: "sm", tone: "muted", children: it.value }), temAcao && chevron
|
|
472
|
+
: it.description)] }), it.indicators && it.indicators.length > 0 && (_jsx(View, { style: s.sinais, children: it.indicators.map((x) => (_jsx(View, { accessibilityLabel: x.label, children: _jsx(Icon, { name: x.icon, size: "sm", color: t.color.mutedForeground }) }, x.label))) })), valorEhTexto && _jsx(Text, { size: "sm", tone: "muted", children: it.value }), temAcao && chevron
|
|
380
473
|
&& _jsx(Icon, { name: chevron, size: "sm", color: t.color.mutedForeground })] }));
|
|
381
474
|
return (_jsxs(View, { style: [s.linha, it.disabled && { opacity: t.size.opacityDisabled }], children: [temAcao ? (_jsx(Pressable, { onPress: it.onPress, disabled: it.disabled, accessibilityRole: "link", ...estadoAcessivel({ disabled: !!it.disabled }), style: s.linhaMiolo, children: miolo })) : (_jsx(View, { accessible: true, ...estadoAcessivel({ disabled: !!it.disabled }), style: s.linhaMiolo, children: miolo })), !valorEhTexto && it.value != null ? it.value : null] }, it.id));
|
|
382
475
|
}) }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurea-uds/native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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.18.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@react-native-community/datetimepicker": ">=8",
|