fix: responsive header with hamburger menu for mobile

- Desktop: single row with logo, nav links, QuickAdd, New note button
- Mobile: logo + QuickAdd + hamburger icon → dropdown menu
- Improved QuickAdd sizing for small screens

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 17:10:25 -03:00
parent 6cc5f3793a
commit d5c418c84f
6 changed files with 85 additions and 18 deletions

View File

@@ -9,7 +9,8 @@
"Bash(node:*)", "Bash(node:*)",
"Bash(curl:*)", "Bash(curl:*)",
"Bash(npx tsc:*)", "Bash(npx tsc:*)",
"Bash(npm list:*)" "Bash(npm list:*)",
"Bash(npx jest:*)"
] ]
} }
} }

Binary file not shown.

View File

@@ -148,6 +148,11 @@ model Backlink {
- Registro de origen de creación (form/quick/import) - Registro de origen de creación (form/quick/import)
- Feature flags configurables - Feature flags configurables
### Mejoras UI Recientes
- Header responsive con menú hamburguesa en móvil
- Desktop: una fila con logo, nav links, QuickAdd y botón Nueva nota
- Mobile: logo + QuickAdd + hamburguesa → dropdown con nav links y botón Nueva nota
## Algoritmo de Scoring ## Algoritmo de Scoring
```typescript ```typescript

View File

@@ -1,18 +1,21 @@
'use client' 'use client'
import { useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Plus, FileText, Settings } from 'lucide-react' import { Plus, FileText, Settings, Menu, X } from 'lucide-react'
import { QuickAdd } from '@/components/quick-add' import { QuickAdd } from '@/components/quick-add'
export function Header() { export function Header() {
const pathname = usePathname() const pathname = usePathname()
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
return ( return (
<header className="sticky top-0 z-40 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> <header className="sticky top-0 z-40 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container mx-auto px-4 flex h-14 items-center justify-between"> <div className="container mx-auto px-2 sm:px-4">
<div className="flex items-center gap-4"> {/* Desktop: single row */}
<div className="hidden sm:flex h-14 items-center justify-between gap-2">
<Link href="/" className="flex items-center gap-2"> <Link href="/" className="flex items-center gap-2">
<span className="text-xl font-bold">Recall</span> <span className="text-xl font-bold">Recall</span>
</Link> </Link>
@@ -38,8 +41,7 @@ export function Header() {
</Button> </Button>
</Link> </Link>
</nav> </nav>
</div> <div className="flex items-center gap-2">
<div className="flex items-center gap-3">
<QuickAdd /> <QuickAdd />
<Link href="/new"> <Link href="/new">
<Button size="sm" className="gap-1.5"> <Button size="sm" className="gap-1.5">
@@ -49,6 +51,65 @@ export function Header() {
</Link> </Link>
</div> </div>
</div> </div>
{/* Mobile: hamburger + logo */}
<div className="flex sm:hidden h-14 items-center justify-between gap-2">
<Link href="/" className="flex items-center gap-2">
<span className="text-lg font-bold">Recall</span>
</Link>
<div className="flex items-center gap-2">
<QuickAdd />
<Button
variant="ghost"
size="sm"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="p-2"
>
{mobileMenuOpen ? (
<X className="h-5 w-5" />
) : (
<Menu className="h-5 w-5" />
)}
</Button>
</div>
</div>
{/* Mobile dropdown menu */}
{mobileMenuOpen && (
<div className="sm:hidden py-3 border-t flex flex-col gap-2">
<nav className="flex flex-col gap-1">
<Link href="/notes" onClick={() => setMobileMenuOpen(false)}>
<Button
variant={pathname === '/notes' ? 'secondary' : 'ghost'}
size="sm"
className="w-full justify-start gap-2"
>
<FileText className="h-4 w-4" />
Notas
</Button>
</Link>
<Link href="/settings" onClick={() => setMobileMenuOpen(false)}>
<Button
variant={pathname === '/settings' ? 'secondary' : 'ghost'}
size="sm"
className="w-full justify-start gap-2"
>
<Settings className="h-4 w-4" />
Configuración
</Button>
</Link>
</nav>
<div className="border-t pt-2">
<Link href="/new" onClick={() => setMobileMenuOpen(false)}>
<Button size="sm" className="w-full justify-start gap-2">
<Plus className="h-4 w-4" />
Nueva nota
</Button>
</Link>
</div>
</div>
)}
</div>
</header> </header>
) )
} }

View File

@@ -188,8 +188,8 @@ export function QuickAdd() {
onFocus={() => setIsExpanded(true)} onFocus={() => setIsExpanded(true)}
onPaste={handlePaste} onPaste={handlePaste}
className={cn( className={cn(
'w-48 transition-all duration-200', 'w-24 xs:w-32 sm:w-48 transition-all duration-200',
isExpanded && 'w-72' isExpanded && 'w-40 xs:w-48 sm:w-72'
)} )}
disabled={isLoading} disabled={isLoading}
/> />
@@ -202,29 +202,29 @@ export function QuickAdd() {
type="button" type="button"
onClick={toggleMultiline} onClick={toggleMultiline}
className={cn( className={cn(
'inline-flex items-center justify-center rounded-lg border bg-background p-2', 'inline-flex items-center justify-center rounded-lg border bg-background p-1.5 sm:p-2',
'hover:bg-accent hover:text-accent-foreground', 'hover:bg-accent hover:text-accent-foreground',
'transition-colors', 'transition-colors',
isMultiline && 'bg-accent text-accent-foreground' isMultiline && 'bg-accent text-accent-foreground'
)} )}
title={isMultiline ? 'Modo línea' : 'Modo multilínea'} title={isMultiline ? 'Modo línea' : 'Modo multilínea'}
> >
<Text className="h-4 w-4" /> <Text className="h-3 w-3 sm:h-4 sm:w-4" />
</button> </button>
<button <button
type="submit" type="submit"
disabled={!value.trim() || isLoading} disabled={!value.trim() || isLoading}
className={cn( className={cn(
'inline-flex items-center justify-center rounded-lg border bg-background p-2', 'inline-flex items-center justify-center rounded-lg border bg-background p-1.5 sm:p-2',
'hover:bg-accent hover:text-accent-foreground', 'hover:bg-accent hover:text-accent-foreground',
'disabled:pointer-events-none disabled:opacity-50', 'disabled:pointer-events-none disabled:opacity-50',
'transition-colors' 'transition-colors'
)} )}
> >
{isLoading ? ( {isLoading ? (
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="h-3 w-3 sm:h-4 sm:w-4 animate-spin" />
) : ( ) : (
<Plus className="h-4 w-4" /> <Plus className="h-3 w-3 sm:h-4 sm:w-4" />
)} )}
</button> </button>
</form> </form>