@aurea-uds/native 0.8.1 → 0.8.2

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 CHANGED
@@ -3,7 +3,7 @@ import { type PressableProps } from "react-native";
3
3
  import { type AureaIconRegistry, type IconName } from "./icon.js";
4
4
  /** Quanto peso a caixa tem. */
5
5
  export type AureaButtonAppearance = "solid" | "outline" | "ghost";
6
- /** O que a cor significa. Mesmos nomes da web (ADR-0016). */
6
+ /** O que a cor significa. Mesmos nomes da web (ADR-0044). */
7
7
  export type AureaButtonTone = "neutral" | "brand" | "danger" | "success" | "warning" | "info";
8
8
  /** As cinco alturas de controle, todas de token de densidade. */
9
9
  export type AureaButtonSize = "xs" | "sm" | "md" | "lg" | "xl";
@@ -14,6 +14,26 @@ export interface ButtonProps extends Omit<PressableProps, "children" | "style">
14
14
  size?: AureaButtonSize;
15
15
  leadingIcon?: IconName;
16
16
  trailingIcon?: IconName;
17
+ /**
18
+ * Um desenho QUALQUER na frente do texto, para quando a marca não pode ser um ícone nosso.
19
+ *
20
+ * ⚠ **Ele existe por causa de MARCA REGISTRADA, não por conveniência.** O botão "Entrar com
21
+ * Google" e o "Entrar com Apple" exigem o desenho oficial de cada um, e nenhum dos dois pode
22
+ * viver dentro desta biblioteca: o Google proíbe redesenhar e manda usar o arquivo do pacote
23
+ * dele; a Apple proíbe usar o logo sem licença escrita. **Então a marca entra pelo app**, e o
24
+ * que a Aurea dá é a cápsula em volta.
25
+ *
26
+ * <Button appearance="outline" leading={<RNImage source={logoGoogle} style={{width: 18, height: 18}} />}>
27
+ * Entrar com Google
28
+ * </Button>
29
+ *
30
+ * ⚠ **A cor do texto NÃO atravessa para cá** — o que entra desenha a própria cor, e é assim
31
+ * que tem de ser: a marca do Google tem cor fixa, e tingi-la seria justamente o que a regra
32
+ * dele proíbe. Um `leadingIcon` nosso continua herdando a cor do botão.
33
+ */
34
+ leading?: React.ReactNode;
35
+ /** O mesmo, do outro lado. */
36
+ trailing?: React.ReactNode;
17
37
  /** Registro de ícones, quando não há um em contexto. Ver `criarRegistroDeIcones`. */
18
38
  icons?: AureaIconRegistry;
19
39
  fullWidth?: boolean;
@@ -29,7 +49,7 @@ export interface ButtonProps extends Omit<PressableProps, "children" | "style">
29
49
  * descreve: lá, um botão desabilitado some da ordem de foco e a explicação pendurada nele não é
30
50
  * lida por ninguém. Aqui não há o dilema, então não há o par `aria-disabled` — um só basta.
31
51
  */
32
- export declare function Button({ children, appearance, tone, size, leadingIcon, trailingIcon, icons, fullWidth, pressed, disabled, accessibilityLabel, ...rest }: ButtonProps): React.JSX.Element;
52
+ export declare function Button({ children, appearance, tone, size, leadingIcon, trailingIcon, leading, trailing, icons, fullWidth, pressed, disabled, accessibilityLabel, ...rest }: ButtonProps): React.JSX.Element;
33
53
  export interface IconButtonProps extends Omit<ButtonProps, "children" | "leadingIcon" | "trailingIcon" | "fullWidth"> {
34
54
  name: IconName;
35
55
  /** **Obrigatório**: um botão que só tem glifo não tem texto para o leitor de tela anunciar. */
package/dist/actions.js CHANGED
@@ -5,7 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  // Medida a ficha da web antes de escrever (passo 1 do BUILDING.md). Lá existem TRÊS props que
6
6
  // pintam a mesma coisa, e a própria ficha diz qual é qual: *"`variant` é atalho para o par
7
7
  // (appearance, tone). Os treze nomes continuam valendo e pintam exatamente o mesmo; a API de dois
8
- // eixos é appearance + tone"* — a ADR-0016.
8
+ // eixos é appearance + tone"* — a ADR-0044.
9
9
  //
10
10
  // O `variant` existe lá porque a Aurea **está publicada** e aqueles treze nomes são contrato com
11
11
  // quem instalou. **Aqui não há nada publicado**, e nascer com o atalho seria nascer com a dívida:
@@ -88,7 +88,7 @@ const folha = criarFolha((t) => ({
88
88
  * descreve: lá, um botão desabilitado some da ordem de foco e a explicação pendurada nele não é
89
89
  * lida por ninguém. Aqui não há o dilema, então não há o par `aria-disabled` — um só basta.
90
90
  */
91
- export function Button({ children, appearance = "solid", tone = "neutral", size = "md", leadingIcon, trailingIcon, icons, fullWidth = false, pressed, disabled, accessibilityLabel, ...rest }) {
91
+ export function Button({ children, appearance = "solid", tone = "neutral", size = "md", leadingIcon, trailingIcon, leading, trailing, icons, fullWidth = false, pressed, disabled, accessibilityLabel, ...rest }) {
92
92
  const t = useAureaTokens();
93
93
  const s = folha(t);
94
94
  const cor = pintar(t, tone);
@@ -104,9 +104,9 @@ export function Button({ children, appearance = "solid", tone = "neutral", size
104
104
  return (_jsx(Pressable, { disabled: disabled, accessibilityRole: "button", accessibilityLabel: accessibilityLabel, accessibilityState: { disabled: !!disabled, ...(pressed === undefined ? null : { checked: pressed }) }, style: ({ pressed: tocando }) => [
105
105
  s.alvo, fullWidth && s.alvoLargura,
106
106
  tocando && s.pressionado, disabled && s.inerte,
107
- ], ...rest, children: _jsxs(View, { style: [s.caixa, caixa], children: [leadingIcon ? _jsx(Icon, { name: leadingIcon, size: ICONE[size], color: corDoTexto, icons: icons }) : null, typeof children === "string"
107
+ ], ...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"
108
108
  ? _jsx(Text, { size: FONTE[size], weight: 500, leading: "none", style: { color: corDoTexto }, children: children })
109
- : children, trailingIcon ? _jsx(Icon, { name: trailingIcon, size: ICONE[size], color: corDoTexto, icons: icons }) : null] }) }));
109
+ : children, trailingIcon ? _jsx(Icon, { name: trailingIcon, size: ICONE[size], color: corDoTexto, icons: icons }) : null, trailing ?? null] }) }));
110
110
  }
111
111
  /**
112
112
  * Botão quadrado, só glifo.
package/dist/display.d.ts CHANGED
@@ -10,7 +10,7 @@ export declare const formatarContagem: (count: number, max?: number) => string;
10
10
  export interface BadgeProps extends ViewProps {
11
11
  /**
12
12
  * O tom. Chama-se `tone` e não `variant`, como no `Button` do Lote 1 — o vocabulário de cor
13
- * com significado é `tone` em todo o alvo nativo (ADR-0016 é quem separa os dois na web).
13
+ * com significado é `tone` em todo o alvo nativo (ADR-0044 é quem separa os dois na web).
14
14
  */
15
15
  tone?: AureaBadgeTone;
16
16
  emphasis?: AureaBadgeEmphasis;
package/dist/index.d.ts CHANGED
@@ -25,6 +25,8 @@ export { BottomNav, Topbar, NavList, Stepper } from "./navigation.js";
25
25
  export type { BottomNavProps, TopbarProps, NavListProps, StepperProps, AureaNavItem, AureaNavListItem, AureaStepItem, AureaStepState, AureaBottomNavVariant, AureaBottomNavIndicator, AureaTopbarVariant, } from "./navigation.js";
26
26
  export { Field, Label, Input, Textarea, Select, Switch, Checkbox, Radio, SegmentedControl, Form, KeyboardAvoiding, useCampo, } from "./inputs.js";
27
27
  export type { FieldProps, LabelProps, InputProps, TextareaProps, SelectProps, SwitchProps, CheckboxProps, RadioProps, SegmentedControlProps, FormProps, KeyboardAvoidingProps, AureaFieldSize, } from "./inputs.js";
28
+ export { InputGroup, InputGroupAddon, PasswordField, useGrupoDeCampo } from "./inputs.js";
29
+ export type { InputGroupProps, InputGroupAddonProps, PasswordFieldProps } from "./inputs.js";
28
30
  export { Dialog, ConfirmDialog, Drawer, BottomSheet } from "./overlays.js";
29
31
  export type { DialogProps, ConfirmDialogProps, DrawerProps, BottomSheetProps, AureaDrawerSide, } from "./overlays.js";
30
32
  export { ToastHost, Toast, useToast } from "./toast.js";
package/dist/index.js CHANGED
@@ -54,6 +54,11 @@ export { BottomNav, Topbar, NavList, Stepper } from "./navigation.js";
54
54
  // nome, a dica e o estado de inválido para o controle por CONTEXTO. É a tradução que decidiu o
55
55
  // lote — o RN não tem `id` nem `htmlFor`.
56
56
  export { Field, Label, Input, Textarea, Select, Switch, Checkbox, Radio, SegmentedControl, Form, KeyboardAvoiding, useCampo, } from "./inputs.js";
57
+ // ⚠ O GRUPO e o campo de SENHA entraram depois do Lote 4, medidos pelo app na tela de entrar
58
+ // (15/09/2026): o campo não tinha onde encaixar glifo, e `secureTextEntry` esconde a senha sem
59
+ // dar botão para mostrá-la. **Os dois já existiam na web desde 29/08/2026** — a falta era só
60
+ // deste lado, e o desenho daqui é o de lá traduzido, não um desenho novo.
61
+ export { InputGroup, InputGroupAddon, PasswordField, useGrupoDeCampo } from "./inputs.js";
57
62
  // ⚠ `DatePicker` e `PhotoInput` NÃO saem daqui, e é de propósito: eles vivem em
58
63
  // `@aurea-uds/native/system`, pela mesma razão que os ícones vivem em `./icons/*` (ADR-0038).
59
64
  // Sair por este barril traria os dois módulos NATIVOS ao grafo de todo app — inclusive de quem
package/dist/inputs.d.ts CHANGED
@@ -11,6 +11,14 @@ type ContextoDeCampo = {
11
11
  };
12
12
  /** O que os controles leem do `Field` acima deles. Público porque um controle novo vai precisar. */
13
13
  export declare function useCampo(): ContextoDeCampo | null;
14
+ interface ContextoDeGrupo {
15
+ size: AureaFieldSize;
16
+ disabled: boolean;
17
+ /** O campo avisa o grupo quando ganha e perde o foco — no RN não existe `:focus-within`. */
18
+ aoFocar: (v: boolean) => void;
19
+ }
20
+ /** O grupo em volta do campo, quando existe um. `null` quando o campo está sozinho. */
21
+ export declare function useGrupoDeCampo(): ContextoDeGrupo | null;
14
22
  export interface FieldProps extends ViewProps {
15
23
  label?: string;
16
24
  hint?: React.ReactNode;
@@ -120,6 +128,53 @@ interface ControleProps {
120
128
  style?: StyleProp<ViewStyle>;
121
129
  testID?: string;
122
130
  }
131
+ export interface InputGroupProps extends ViewProps {
132
+ /** O degrau do sistema. Cai no `Field` em volta quando não vem, como no `Input`. */
133
+ size?: AureaFieldSize;
134
+ disabled?: boolean;
135
+ testID?: string;
136
+ }
137
+ /**
138
+ * A caixa de campo que aceita coisas dentro — glifo na frente, botão atrás, os dois.
139
+ *
140
+ * <InputGroup>
141
+ * <InputGroupAddon><Icon name="email" size="sm" /></InputGroupAddon>
142
+ * <Input value={email} onChangeText={setEmail} keyboardType="email-address" />
143
+ * </InputGroup>
144
+ *
145
+ * ⚠ **O `Input` lá dentro para de desenhar a própria caixa** — quem desenha borda, fundo, altura,
146
+ * foco e inválido passa a ser o grupo. Isso é automático: o campo enxerga o grupo por contexto.
147
+ */
148
+ export declare function InputGroup({ size, disabled, children, style, testID, ...rest }: InputGroupProps): React.JSX.Element;
149
+ export interface InputGroupAddonProps extends ViewProps {
150
+ testID?: string;
151
+ }
152
+ /**
153
+ * Um encaixe dentro do `InputGroup`. **A posição é a do JSX** — antes do campo fica na frente,
154
+ * depois fica atrás. Não há prop de lado, e o porquê está no bloco do grupo lá em cima.
155
+ */
156
+ export declare function InputGroupAddon({ children, style, testID, ...rest }: InputGroupAddonProps): React.JSX.Element;
157
+ /**
158
+ * O campo de senha, com o botão que mostra e esconde.
159
+ *
160
+ * <Field label="Senha"><PasswordField value={senha} onChangeText={setSenha} /></Field>
161
+ *
162
+ * ⚠ **`secureTextEntry` não entra**: um campo de senha que aceitasse essa prop deixaria de ser
163
+ * um. É a mesma razão pela qual o `type` não atravessa no `PasswordField` da web.
164
+ *
165
+ * ⚠ **O rótulo do botão é o anúncio do estado** — ele troca entre "Mostrar senha" e "Ocultar
166
+ * senha". Não há estado de pressionado junto, e isso é decisão copiada da web: com os dois, o
167
+ * leitor de tela lê a mesma informação duas vezes.
168
+ *
169
+ * ⚠ **`autoCapitalize` nasce em `none`.** No Android o teclado maiúsculiza a primeira letra por
170
+ * padrão, e numa senha isso é o erro de digitação mais caro que existe: o campo está escondido,
171
+ * então ninguém vê o que deu errado.
172
+ */
173
+ export interface PasswordFieldProps extends Omit<InputProps, "secureTextEntry" | "multiline" | "keyboardType"> {
174
+ /** Começa visível. Padrão: escondida. */
175
+ defaultVisible?: boolean;
176
+ }
177
+ export declare function PasswordField({ defaultVisible, size, disabled, autoCapitalize, testID, style, ...rest }: PasswordFieldProps): React.JSX.Element;
123
178
  export type CheckboxProps = ControleProps;
124
179
  export type RadioProps = ControleProps;
125
180
  /** A caixa que marca. `accessibilityRole="checkbox"` + `state.checked`. */
package/dist/inputs.js CHANGED
@@ -40,10 +40,11 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
40
40
  import * as React from "react";
41
41
  import { Animated, KeyboardAvoidingView as KeyboardAvoidingViewRN, Modal, Platform, Pressable, ScrollView, TextInput, View, } from "react-native";
42
42
  import { criarFolha } from "./estilos.js";
43
+ import { IconButton } from "./actions.js";
43
44
  import { Icon } from "./icon.js";
44
45
  import { useReduceMotion } from "./movimento.js";
45
46
  import { Text } from "./text.js";
46
- import { useAureaTokens } from "./theme.js";
47
+ import { useAureaStrings, useAureaTokens } from "./theme.js";
47
48
  const alturaDoTamanho = (t, s) => s === "sm" ? t.size.controlHSm : s === "lg" ? t.size.controlHLg : t.size.controlHMd;
48
49
  const fonteDoTamanho = (t, s) => s === "sm" ? t.size.textXs : s === "lg" ? t.size.textBase : t.size.textMd;
49
50
  const respiroDoTamanho = (t, s) => s === "sm" ? t.size.space3 : s === "lg" ? t.size.space4 : 13;
@@ -61,6 +62,21 @@ const folha = criarFolha((t) => ({
61
62
  areaDeTexto: { minHeight: 104, paddingVertical: t.size.space3, borderRadius: t.size.radiusLg },
62
63
  invalido: { borderColor: t.color.danger400 ?? t.color.destructive },
63
64
  desabilitado: { opacity: 0.5 },
65
+ // O GRUPO é a mesma caixa do campo virada em LINHA, e os números são os mesmos: mesma borda,
66
+ // mesmo raio de controle, mesmo fundo. A altura e o respiro horizontal entram por tamanho, como
67
+ // no `Input`, porque é o grupo que passa a desenhar a caixa quando existe um.
68
+ grupo: {
69
+ flexDirection: "row", alignItems: "center", width: "100%", minWidth: 0,
70
+ borderWidth: t.size.borderWidth, borderColor: t.color.borderStrong,
71
+ borderRadius: t.size.radiusControl, backgroundColor: t.color.fieldBg,
72
+ gap: t.size.space1,
73
+ },
74
+ // Dentro do grupo o campo perde a caixa e vira só o texto — a borda passou a ser do grupo.
75
+ // ⚠ As DUAS últimas linhas continuam obrigatórias: são o conserto de Android de 09/09/2026
76
+ // (`paddingVertical: 0` desliga a injeção do tema, `textAlignVertical` tira o texto do topo).
77
+ // É o mesmo par que o `campoDeTexto` do `busca.tsx` carrega pela mesma razão.
78
+ campoNoGrupo: { flex: 1, minWidth: 0, paddingVertical: 0, textAlignVertical: "center" },
79
+ encaixe: { flexDirection: "row", alignItems: "center", flexShrink: 0, gap: t.size.space1 },
64
80
  marcaBase: {
65
81
  alignItems: "center", justifyContent: "center", marginTop: 1,
66
82
  borderWidth: t.size.borderWidth, borderColor: t.color.borderStrong,
@@ -96,6 +112,11 @@ const CampoCtx = React.createContext(null);
96
112
  export function useCampo() {
97
113
  return React.useContext(CampoCtx);
98
114
  }
115
+ const GrupoDeCampo = React.createContext(null);
116
+ /** O grupo em volta do campo, quando existe um. `null` quando o campo está sozinho. */
117
+ export function useGrupoDeCampo() {
118
+ return React.useContext(GrupoDeCampo);
119
+ }
99
120
  /**
100
121
  * Rótulo, controle, dica e erro — na ordem, com o respiro do CSS.
101
122
  *
@@ -151,11 +172,13 @@ export function Input({ value, defaultValue, onChangeText, placeholder, disabled
151
172
  const t = useAureaTokens();
152
173
  const s = folha(t);
153
174
  const campo = useCampo();
154
- const tam = size ?? campo?.size ?? "md";
155
- const inativo = disabled ?? campo?.disabled;
175
+ const grupo = useGrupoDeCampo();
176
+ const tam = size ?? grupo?.size ?? campo?.size ?? "md";
177
+ const inativo = disabled ?? grupo?.disabled ?? campo?.disabled;
156
178
  const [focado, setFocado] = React.useState(false);
157
- return (_jsx(TextInput, { testID: testID, value: value, defaultValue: defaultValue, onChangeText: onChangeText, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, editable: !inativo, keyboardType: keyboardType, secureTextEntry: secureTextEntry, autoCapitalize: autoCapitalize, multiline: multiline, onFocus: () => { setFocado(true); onFocus?.(); }, onBlur: () => {
179
+ return (_jsx(TextInput, { testID: testID, value: value, defaultValue: defaultValue, onChangeText: onChangeText, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, editable: !inativo, keyboardType: keyboardType, secureTextEntry: secureTextEntry, autoCapitalize: autoCapitalize, multiline: multiline, onFocus: () => { setFocado(true); grupo?.aoFocar(true); onFocus?.(); }, onBlur: () => {
158
180
  setFocado(false);
181
+ grupo?.aoFocar(false);
159
182
  // Só avisa quando o texto MUDOU: emitir o mesmo valor a cada saída de foco faria o app
160
183
  // re-renderizar sem motivo, e num formulário de dez campos isso é dez renders por
161
184
  // preenchimento. Mesma guarda da web (`inputs-client.tsx:194`).
@@ -169,30 +192,36 @@ export function Input({ value, defaultValue, onChangeText, placeholder, disabled
169
192
  // O nome e a dica vêm do `Field` — ver o bloco acima sobre por que a ligação da web não
170
193
  // atravessa. Sem isto, o campo é um nó anônimo para o leitor de tela.
171
194
  accessibilityLabel: campo?.label, accessibilityHint: campo?.hint, accessibilityState: { disabled: !!inativo }, "aria-invalid": campo?.invalido, style: [
172
- s.caixa,
173
- { height: alturaDoTamanho(t, tam), paddingHorizontal: respiroDoTamanho(t, tam),
174
- fontSize: fonteDoTamanho(t, tam), fontFamily: t.font.ui[400], color: t.color.foreground,
175
- // ⚠ AS DUAS LINHAS ABAIXO NÃO SÃO ENFEITE — sem elas o texto sobe e é CORTADO no
176
- // Android, e foi assim que este campo chegou ao vidro em 09/09/2026.
177
- //
178
- // `padding-block:0` é LITERAL na web (`aurea.css:685`) e não atravessou. No RN a falta
179
- // dele não é "sem padding": o `AndroidTextInputComponentDescriptor.h:101-107` INJETA o
180
- // padding do tema do Android no nó do Yoga sempre que as props não trazem `padding`,
181
- // `paddingTop`/`paddingBottom` nem `paddingVertical` — e o `hasPaddingVertical` é
182
- // exatamente o que desliga a injeção. Com `height` fixa, esse padding come a altura por
183
- // dentro e a caixa de texto fica menor que a linha: daí o corte.
184
- //
185
- // O `textAlignVertical` é o outro lado da mesma conta. Na web, um campo com
186
- // `block-size` centraliza a linha sozinho; no Android o `setTextAlignVertical` do
187
- // `ReactTextInputManager.kt:571` traduz `null`/`auto` para `Gravity.NO_GRAVITY`, que
188
- // deixa o `EditText` no topo. `center` é o que reproduz a web — e o `Textarea` o
189
- // sobrescreve para `top`, que é o que uma caixa alta faz nos dois lados.
190
- paddingVertical: 0, textAlignVertical: "center" },
191
- campo?.invalido && s.invalido,
195
+ // DENTRO DE UM GRUPO o campo não desenha caixa nenhuma: borda, fundo, altura, respiro,
196
+ // inválido, foco e desabilitado passam a ser do grupo. Manter qualquer um dos dois
197
+ // desenhando daria borda dentro de borda — que é exatamente o defeito que o `.input-group
198
+ // > .input` da web zera à mão (`aurea.css:751`).
199
+ grupo ? s.campoNoGrupo : s.caixa,
200
+ grupo
201
+ ? { fontSize: fonteDoTamanho(t, tam), fontFamily: t.font.ui[400], color: t.color.foreground }
202
+ : { height: alturaDoTamanho(t, tam), paddingHorizontal: respiroDoTamanho(t, tam),
203
+ fontSize: fonteDoTamanho(t, tam), fontFamily: t.font.ui[400], color: t.color.foreground,
204
+ // ⚠ AS DUAS LINHAS ABAIXO NÃO SÃO ENFEITE — sem elas o texto sobe e é CORTADO no
205
+ // Android, e foi assim que este campo chegou ao vidro em 09/09/2026.
206
+ //
207
+ // `padding-block:0` é LITERAL na web (`aurea.css:685`) e não atravessou. No RN a falta
208
+ // dele não é "sem padding": o `AndroidTextInputComponentDescriptor.h:101-107` INJETA o
209
+ // padding do tema do Android no nó do Yoga sempre que as props não trazem `padding`,
210
+ // `paddingTop`/`paddingBottom` nem `paddingVertical` — e o `hasPaddingVertical` é
211
+ // exatamente o que desliga a injeção. Com `height` fixa, esse padding come a altura por
212
+ // dentro e a caixa de texto fica menor que a linha: daí o corte.
213
+ //
214
+ // O `textAlignVertical` é o outro lado da mesma conta. Na web, um campo com
215
+ // `block-size` centraliza a linha sozinho; no Android o `setTextAlignVertical` do
216
+ // `ReactTextInputManager.kt:571` traduz `null`/`auto` para `Gravity.NO_GRAVITY`, que
217
+ // deixa o `EditText` no topo. `center` é o que reproduz a web — e o `Textarea` o
218
+ // sobrescreve para `top`, que é o que uma caixa alta faz nos dois lados.
219
+ paddingVertical: 0, textAlignVertical: "center" },
220
+ !grupo && campo?.invalido && s.invalido,
192
221
  // O foco é o único estado que sobra do trio da web (hover/focus/active), e ele importa
193
222
  // mais aqui: no telefone é a única pista de qual campo o teclado está alimentando.
194
- focado && { borderColor: t.color.focusStrong },
195
- inativo && s.desabilitado,
223
+ !grupo && focado && { borderColor: t.color.focusStrong },
224
+ !grupo && inativo && s.desabilitado,
196
225
  style,
197
226
  ] }));
198
227
  }
@@ -211,6 +240,55 @@ export function Textarea({ rows, size, style, ...rest }) {
211
240
  style,
212
241
  ], multiline: true }));
213
242
  }
243
+ /**
244
+ * A caixa de campo que aceita coisas dentro — glifo na frente, botão atrás, os dois.
245
+ *
246
+ * <InputGroup>
247
+ * <InputGroupAddon><Icon name="email" size="sm" /></InputGroupAddon>
248
+ * <Input value={email} onChangeText={setEmail} keyboardType="email-address" />
249
+ * </InputGroup>
250
+ *
251
+ * ⚠ **O `Input` lá dentro para de desenhar a própria caixa** — quem desenha borda, fundo, altura,
252
+ * foco e inválido passa a ser o grupo. Isso é automático: o campo enxerga o grupo por contexto.
253
+ */
254
+ export function InputGroup({ size, disabled, children, style, testID, ...rest }) {
255
+ const t = useAureaTokens();
256
+ const s = folha(t);
257
+ const campo = useCampo();
258
+ const tam = size ?? campo?.size ?? "md";
259
+ const inativo = disabled ?? campo?.disabled ?? false;
260
+ const [focado, setFocado] = React.useState(false);
261
+ // O contexto muda de identidade a cada render se for objeto literal, e isso re-renderiza todo
262
+ // campo de todo formulário a cada tecla. Mesma memoização do `Field`.
263
+ const ctx = React.useMemo(() => ({ size: tam, disabled: inativo, aoFocar: setFocado }), [tam, inativo]);
264
+ return (_jsx(View, { testID: testID, style: [
265
+ s.grupo,
266
+ { height: alturaDoTamanho(t, tam), paddingHorizontal: respiroDoTamanho(t, tam) },
267
+ campo?.invalido && s.invalido,
268
+ // O foco vem do campo lá dentro, por contexto: no React Native não existe `:focus-within`,
269
+ // e sem isto a única pista de qual campo o teclado alimenta sumiria ao entrar num grupo.
270
+ focado && { borderColor: t.color.focusStrong },
271
+ inativo && s.desabilitado,
272
+ style,
273
+ ], ...rest, children: _jsx(GrupoDeCampo.Provider, { value: ctx, children: children }) }));
274
+ }
275
+ /**
276
+ * Um encaixe dentro do `InputGroup`. **A posição é a do JSX** — antes do campo fica na frente,
277
+ * depois fica atrás. Não há prop de lado, e o porquê está no bloco do grupo lá em cima.
278
+ */
279
+ export function InputGroupAddon({ children, style, testID, ...rest }) {
280
+ const t = useAureaTokens();
281
+ const s = folha(t);
282
+ return _jsx(View, { testID: testID, style: [s.encaixe, style], ...rest, children: children });
283
+ }
284
+ export function PasswordField({ defaultVisible = false, size, disabled, autoCapitalize = "none", testID, style, ...rest }) {
285
+ const strings = useAureaStrings();
286
+ const campo = useCampo();
287
+ const [visivel, setVisivel] = React.useState(defaultVisible);
288
+ const tam = size ?? campo?.size ?? "md";
289
+ const inativo = disabled ?? campo?.disabled;
290
+ return (_jsxs(InputGroup, { size: tam, disabled: inativo, testID: testID, children: [_jsx(Input, { ...rest, size: tam, disabled: inativo, autoCapitalize: autoCapitalize, secureTextEntry: !visivel, style: style, testID: testID ? `${testID}-campo` : undefined }), _jsx(InputGroupAddon, { children: _jsx(IconButton, { name: visivel ? "view--off" : "view", label: visivel ? strings.passwordHide : strings.passwordShow, appearance: "ghost", size: tam === "lg" ? "md" : "sm", disabled: inativo, onPress: () => setVisivel(v => !v), testID: testID ? `${testID}-olho` : undefined }) })] }));
291
+ }
214
292
  function ControleMarcado({ papel, label, description, checked, onChange, disabled, size, style, testID, }) {
215
293
  const t = useAureaTokens();
216
294
  const s = folha(t);
package/dist/strings.d.ts CHANGED
@@ -32,6 +32,14 @@ export interface AureaStrings {
32
32
  openSettings: string;
33
33
  /** O rótulo do botão que tira uma foto anexada. */
34
34
  photoRemove: string;
35
+ /**
36
+ * O botão que revela a senha no `PasswordField`, e o que a esconde de volta. Mesmos nomes da
37
+ * web (`pure.tsx`), porque é a mesma peça com a mesma decisão: **o rótulo é que anuncia o
38
+ * estado**, e não um `accessibilityState` de pressionado — com os dois, o leitor de tela diz
39
+ * a mesma coisa duas vezes.
40
+ */
41
+ passwordShow: string;
42
+ passwordHide: string;
35
43
  /** O botão de fechar do `Dialog` e do `Drawer`. Mesmo nome da web. */
36
44
  close: string;
37
45
  /** A saída do `ConfirmDialog`. Mesmo nome da web. */
package/dist/strings.js CHANGED
@@ -37,6 +37,8 @@ export const defaultStrings = {
37
37
  cameraDenied: "Camera access is off. Turn it on in Settings to attach a photo.",
38
38
  openSettings: "Open Settings",
39
39
  photoRemove: "Remove photo",
40
+ passwordShow: "Show password",
41
+ passwordHide: "Hide password",
40
42
  close: "Close",
41
43
  confirmCancel: "Cancel",
42
44
  confirmProceed: "Continue",
@@ -72,6 +74,8 @@ export const ptBR = {
72
74
  cameraDenied: "O acesso à câmera está desligado. Ligue nas configurações para anexar uma foto.",
73
75
  openSettings: "Abrir configurações",
74
76
  photoRemove: "Remover foto",
77
+ passwordShow: "Mostrar senha",
78
+ passwordHide: "Ocultar senha",
75
79
  close: "Fechar",
76
80
  confirmCancel: "Cancelar",
77
81
  confirmProceed: "Continuar",
package/dist/text.d.ts CHANGED
@@ -6,7 +6,7 @@ export type AureaTextSize = "xs" | "sm" | "md" | "base" | "lg" | "xl" | "2xl" |
6
6
  export type AureaTextWeight = 400 | 500 | 600 | 700;
7
7
  /** Papel de tipografia — os três `--font-*` da Aurea. */
8
8
  export type AureaTextFont = "ui" | "editorial" | "code";
9
- /** O que a cor SIGNIFICA. Mesmo vocabulário do `tone` do Button (ADR-0016). */
9
+ /** O que a cor SIGNIFICA. Mesmo vocabulário do `tone` do Button (ADR-0044). */
10
10
  export type AureaTextTone = "default" | "muted" | "subtle" | "primary" | "danger" | "success" | "warning" | "info";
11
11
  export type AureaTextLeading = "none" | "tight" | "normal" | "relaxed";
12
12
  export interface TextProps extends TextPropsRN {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurea-uds/native",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "React Native target for Aurea UDS: the theme x density provider, the first components and the Carbon icons, drawn on react-native-svg.",
5
5
  "keywords": [
6
6
  "aurea",
@@ -47,7 +47,7 @@
47
47
  "NOTICE"
48
48
  ],
49
49
  "dependencies": {
50
- "@aurea-uds/tokens": "^0.8.1"
50
+ "@aurea-uds/tokens": "^0.8.2"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@react-native-community/datetimepicker": ">=8",