feat(nav): add icon+label mobile nav with active pill and full-width buttons (#178)

## Why

### Problem
The mobile bottom navigation bar showed only icons with no labels,
making it hard to identify each section at a glance — especially for new
users.

## What

### Changes
- Each nav item now renders its icon above a short text label
- Active item gets a subtle pill background (`bg-primary/15`) matching
the reference design
- Each button uses `flex-1` so items stretch to fill all available width
evenly
- Added `mobileTitle` to all nav items with short, space-conscious
labels: `Panel`, `Flujo`, `Cuentas`, `Movim.`, `Presup.`
- Removed the `__()` translation dependency from the mobile nav to avoid
a runtime crash that was causing icons to disappear entirely
This commit is contained in:
Víctor Falcón 2026-03-01 18:43:18 +00:00 committed by GitHub
parent 0388705c12
commit efd86bc8d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 26 deletions

View File

@ -201,9 +201,9 @@
"CSV, XLS, XLSX files": "Archivos CSV, XLS, XLSX",
"Can I export or delete my data?": "¿Puedo exportar o eliminar mis datos?",
"Cancel": "Cancelar",
"Carry Over": "Arrastrar Saldo",
"Cashflow": "Flujo de Efectivo",
"Cashflow Trend": "Tendencia del Flujo de Efectivo",
"Carry Over": "Acumular",
"Cashflow": "Flujo de Caja",
"Cashflow Trend": "Tendencia del Flujo de Caja",
"Cashflow at a Glance": "Flujo de Caja de un Vistazo",
"Cashflow visualization": "Visualización del flujo de caja",
"Categories": "Categorías",
@ -312,10 +312,10 @@
"DELETE": "ELIMINAR",
"Daily": "Diario",
"Daily spending and transactions": "Gastos diarios y transacciones",
"Dark": "Oscuro",
"Dashboard": "Panel",
"Day of the month when the period starts (1-31)": "Día del mes en que comienza el período (1-31)",
"Day of week (0=Sunday, 6=Saturday)": "Día de la semana (0=Domingo, 6=Sábado)",
"Dark": "Oscuro",
"Dashboard": "Panel",
"Discord": "Discord",
"Data Accuracy:": "Precisión de Datos:",
"Data Imported": "Datos Importados",
@ -621,7 +621,7 @@
"Max": "Máx",
"Michael R.": "Michael R.",
"Min": "Mín",
"Money Flow": "Flujo de Dinero",
"Money Flow": "Flujo de Caja",
"Money coming into an account from a source (e.g., salary, refunds, interest). Increases your balance.": "Dinero que entra en una cuenta desde una fuente (ej., salario, reembolsos, intereses). Aumenta tu balance.",
"Money going out of an account to pay for something (e.g., groceries, rent, subscriptions). Decreases your balance.": "Dinero que sale de una cuenta para pagar algo (ej., supermercado, alquiler, suscripciones). Disminuye tu balance.",
"Month": "Mes",
@ -642,7 +642,7 @@
"Need help? Join our Discord": "¿Necesitas ayuda? Únete a nuestro Discord",
"Net": "Neto",
"Neutral": "Neutro",
"Net Cashflow": "Flujo de Efectivo Neto",
"Net Cashflow": "Flujo Neto",
"Net Worth Evolution": "Evolución del Patrimonio Neto",
"Never": "Nunca",
"New": "Nuevo",
@ -780,13 +780,13 @@
"Register": "Registrarse",
"Regular Security Audits:": "Auditorías de Seguridad Regulares:",
"Remaining": "Restante",
"Remaining balance carries over to next period": "El saldo restante se arrastra al siguiente período",
"Remaining balance carries over to next period": "El saldo restante se acumula al siguiente período",
"Remaining balance returns to available money pool": "El saldo restante vuelve al fondo de dinero disponible",
"Remember me": "Recuérdame",
"Remove all labels": "Eliminar todas las etiquetas",
"Resend verification email": "Reenviar correo de verificación",
"Reset password": "Restablecer contraseña",
"Reset/Pool": "Reiniciar/Acumular",
"Reset/Pool": "Reiniciar/Fondo",
"Retirement": "Jubilación",
"Retirement / Pension": "Jubilación / Pensión",
"Retry": "Reintentar",
@ -905,12 +905,12 @@
"Spent": "Gastado",
"Spent:": "Gastado:",
"Square images only (max": "Solo imágenes cuadradas (máx",
"Start Day": "Día de Inicio",
"Start Day of Month": "Día de Inicio del Mes",
"Start My Financial Journey": "Comenzar Mi Viaje Financiero",
"Start Day": "Día de inicio",
"Start Day of Month": "Día de inicio del mes",
"Starting day": "Día de inicio",
"Start Your Financial Journey": "Comienza Tu Viaje Financiero",
"Start managing your money privately. No credit card required.": "Empieza a gestionar tu dinero de forma privada. No se requiere tarjeta de crédito.",
"Starting day": "Día de inicio",
"Status": "Estado",
"Stay on Track": "Mantente en el Camino",
"Step 1: Export from your bank": "Paso 1: Exporta desde tu banco",
@ -1020,8 +1020,8 @@
"Tracking": "Seguimiento",
"Tracking spending for": "Rastreando gastos para",
"Tracking:": "Seguimiento:",
"Transaction": "Transacción",
"Transaction Date": "Fecha transaccion",
"Transaction": "Transacción",
"Transaction created successfully": "Transacción creada exitosamente",
"Transaction created, but failed to update balance": "Transacción creada, pero no se pudo actualizar el balance",
"Transaction description": "Descripción",
@ -1059,8 +1059,8 @@
"Unlock encryption key": "Desbloquear clave de encriptación",
"Unlock encryption to add transactions": "Desbloquea la encriptación crear una transacción",
"Unlock encryption to import transactions": "Desbloquea la encriptación para importar transacciones",
"Unused budget will carry over to the next period.": "El presupuesto no utilizado se arrastrará al siguiente período.",
"Update": "Actualizar",
"Unused budget will carry over to the next period.": "El presupuesto no utilizado se acumulará al siguiente período.",
"Update Credentials": "Actualizar Credenciales",
"Update account balance": "Actualizar balance de cuenta",
"Update balance": "Actualizar balance",
@ -1220,8 +1220,8 @@
"You may terminate your account at any time by deleting your account through the application settings or by contacting us. Upon termination, your right to use the service will immediately cease.": "Puedes cancelar tu cuenta en cualquier momento eliminándola a través de la configuración de la aplicación o contactándonos. Al cancelar, tu derecho a usar el servicio cesará inmediatamente.",
"You may terminate your account at any time by\\n deleting your account through the application\\n settings or by contacting us. Upon termination,\\n your right to use the service will immediately\\n cease.": "Puedes terminar tu cuenta en cualquier momento eliminándola a través de la configuración de la aplicación o contactándonos. Tras la terminación, tu derecho a usar el servicio cesará de inmediato.",
"You must be at least 16 years old to use this service. By using Whisper Money, you represent and warrant that you meet this age requirement.": "Debes tener al menos 16 años para usar este servicio. Al usar Whisper Money, declaras y garantizas que cumples con este requisito de edad.",
"You must be at least 16 years old to use this\\n service. By using Whisper Money, you represent\\n and warrant that you meet this age requirement.": "Debes tener al menos 16 años para usar este servicio. Al usar Whisper Money, declaras y garantizas que cumples con este requisito de edad.",
"You must select either a category or a label.": "Debes seleccionar una categoría o una etiqueta.",
"You must be at least 16 years old to use this\\n service. By using Whisper Money, you represent\\n and warrant that you meet this age requirement.": "Debes tener al menos 16 años para usar este servicio. Al usar Whisper Money, declaras y garantizas que cumples con este requisito de edad.",
"You now have full access to all Whisper Money features. Thank you for supporting us!": "¡Ahora tienes acceso completo a todas las funciones de Whisper Money. Gracias por apoyarnos!",
"You retain all ownership rights to the financial data and information you enter into Whisper Money. We do not claim any ownership over your personal financial information.": "Conservas todos los derechos de propiedad sobre los datos financieros e información que ingresas en Whisper Money. No reclamamos ninguna propiedad sobre tu información financiera personal.",
"You retain all\\n ownership rights to the financial data and\\n information you enter into Whisper Money. We do\\n not claim any ownership over your personal\\n financial information.": "Conservas todos los derechos de propiedad sobre los datos financieros e información que introduces en Whisper Money. No reclamamos ningún derecho de propiedad sobre tu información financiera personal.",

View File

@ -18,36 +18,40 @@ import {
import { dashboard } from '@/routes';
import { SharedData } from '@/types';
import { Link, usePage } from '@inertiajs/react';
import { LucideIcon } from 'lucide-react';
import { useMemo } from 'react';
import AppLogo from './app-logo';
export function AppSidebar() {
const page = usePage<SharedData>();
const mainNavItems = useMemo(
() => getMainNavItems(page.props.features),
[page.props.features],
() => getMainNavItems(page.props.features, page.props.locale),
[page.props.features, page.props.locale],
);
return (
<>
<div className="fixed right-4 bottom-6 left-4 z-50 flex items-center justify-evenly gap-2 rounded-full border border-border/75 bg-sidebar/50 px-4 py-4 shadow-lg shadow-black/20 backdrop-blur md:hidden">
<div className="fixed right-4 bottom-6 left-4 z-50 flex items-center justify-evenly gap-1 rounded-full border border-border/75 bg-sidebar/50 px-2 py-2 shadow-lg shadow-black/20 backdrop-blur md:hidden">
{mainNavItems.map((item) => {
const isActive = page.url.startsWith(resolveUrl(item.href));
const label = item.mobileTitle ?? item.title;
const Icon = item.icon as LucideIcon | null;
return (
<Link
key={item.title}
href={item.href}
className={cn([
'flex flex-col items-center justify-center text-primary',
'transtion-all duration-200',
'flex flex-1 flex-col items-center justify-center gap-1 rounded-full px-3 py-2 transition-all duration-200',
{
'opacity-100': isActive,
'opacity-50': !isActive,
'bg-primary/5 dark:bg-primary/15': isActive,
'opacity-50 hover:opacity-75': !isActive,
},
'hover:opacity-75',
])}
>
<item.icon className="size-6 text-primary" />
{Icon && <Icon className="size-5 text-primary" />}
<span className="text-[10px] leading-none font-medium text-primary">
{label}
</span>
</Link>
);
})}

View File

@ -12,11 +12,33 @@ import {
TrendingUp,
} from 'lucide-react';
export function getMainNavItems(features: Features): NavItem[] {
const mobileLabels: Record<string, Record<string, string>> = {
en: {
dashboard: 'Home',
cashflow: 'Cashflow',
accounts: 'Accounts',
transactions: 'Movements',
budgets: 'Budget',
},
es: {
dashboard: 'Inicio',
cashflow: 'Flujo',
accounts: 'Cuentas',
transactions: 'Movim.',
budgets: 'Presup.',
},
};
function getMobileLabel(key: string, locale: string): string {
return (mobileLabels[locale] ?? mobileLabels['en'])[key];
}
export function getMainNavItems(features: Features, locale: string): NavItem[] {
const items: NavItem[] = [
{
type: 'nav-item',
title: 'Dashboard',
mobileTitle: getMobileLabel('dashboard', locale),
href: dashboard(),
icon: LayoutGrid,
},
@ -26,7 +48,7 @@ export function getMainNavItems(features: Features): NavItem[] {
items.push({
type: 'nav-item',
title: 'Cashflow',
mobileTitle: 'Flujo',
mobileTitle: getMobileLabel('cashflow', locale),
href: cashflow(),
icon: TrendingUp,
});
@ -36,18 +58,21 @@ export function getMainNavItems(features: Features): NavItem[] {
{
type: 'nav-item',
title: 'Accounts',
mobileTitle: getMobileLabel('accounts', locale),
href: accountsIndex(),
icon: CreditCard,
},
{
type: 'nav-item',
title: 'Transactions',
mobileTitle: getMobileLabel('transactions', locale),
href: transactionsIndex(),
icon: Receipt,
},
{
type: 'nav-item',
title: 'Budgets',
mobileTitle: getMobileLabel('budgets', locale),
href: budgetsIndex(),
icon: PiggyBank,
},