Tabs e Padrões de Retorno
Última atualização: 15 de julho de 2026
Visão Geral
Este documento cobre duas coisas: a lista completa de tabs do app (o gating de três delas por flag de configuração já foi documentado em outros fluxos, aqui é só o quadro completo), e o padrão de "voltar para onde eu vim" usado quando uma tela é aberta a partir de mais de um lugar possível.
As cinco tabs
app/(tabs)/_layout.tsx define cinco tabs. Duas nunca são escondidas (home e loja), e três são condicionais por flag, cada uma já documentada no fluxo a que pertence:
| Tab | Título | Sempre visível? | Documentada em |
|---|---|---|---|
home | Início | Sim | — |
clientes | Clientes | Não, cfg-ativarcarteiradeclientes | Carteira de Clientes |
vendas | Vendas | Não, cfg-ativarlistagemdevendas | — |
catalogo | Catálogo | Não, cfg-ativarcatalogodeprodutos | Catálogo de Produtos |
loja | Perfil | Sim | — |
Toda a árvore de tabs fica dentro de TabsAuthGuard, documentado em Guardas de Rota e Inicialização. A tab "Perfil" (loja) é onde vive a troca manual de organização, documentada em Troca de Organização.
A customização visual da barra de tabs remove o efeito ripple do Android no botão de cada tab, esconde a barra quando o teclado abre, e soma o insets.bottom da área segura à altura da barra:
// app/(tabs)/_layout.tsx
screenOptions={{
animation: "none",
tabBarButton: (props) => (
<PlatformPressable {...props} android_ripple={{ color: "transparent" }} />
),
tabBarHideOnKeyboard: true,
tabBarStyle: { paddingBottom: insets.bottom, height: 49 + insets.bottom },
}}
O padrão returnTo: cada tela resolve o próprio retorno
Muitas telas no app são abertas a partir de mais de um lugar (nova venda pode começar da carteira, da home ou de dentro do próprio fluxo de vendas, por exemplo). Para saber para onde voltar depois, o padrão do projeto é passar um parâmetro de rota chamado returnTo, com o caminho de destino como string literal:
// features/home/home-controller.tsx
router.push({
pathname: "/nova-venda",
params: {
saleDraftId: session.saleDraftId,
returnTo: "/(tabs)/home",
},
} as any);
// outro lugar, mesma tela de destino, valor diferente
aiImportSession.start({ mode: "new", returnTo: "/(tabs)/home" });
Quem lê esse parâmetro e decide o que fazer com ele é a própria tela de destino, não um utilitário compartilhado. No controller de nova venda, por exemplo, o botão de voltar do cabeçalho decide entre router.dismissTo(returnTo) (se um returnTo foi informado) ou o router.back() padrão:
// features/new-sale/new-sale.controller.ts
const buttonsNavBar = useMemo(
() => [
{
label: "",
onPress: () => {
if (returnTo) {
router.dismissTo(returnTo as any);
return;
}
if (router.canGoBack()) {
router.back();
}
},
icon: { name: "arrow-left", size: 20, color: "#000" },
},
// ...
],
[returnTo, openMoreOptionsBottomSheet],
);
Não existe, hoje, um helper genérico de "voltar ou usar fallback" nem um tipo compartilhado para o valor de returnTo. Cada feature que precisa desse comportamento reimplementa a mesma lógica de leitura do parâmetro e decisão entre dismissTo/back/push, com o caminho de destino escrito como string literal no ponto de origem.
Armadilhas conhecidas
Ao escrever um novo returnTo, confira o caminho literal com atenção, não existe checagem de tipo garantindo que "/(tabs)/home" corresponda a uma rota válida (ver Deep Linking e Typed Routes sobre por que isso não é pego pelo typecheck). Se for criar uma segunda tela que também precisa desse padrão de retorno, considere extrair a lógica já existente em new-sale.controller.ts para um hook compartilhado, em vez de reimplementá-la de novo.