feat: add UI/UX polish with navigation rail, confirm dialogs, welcome banner, and form validation

- Add NavigationRail component (desktop vertical icon sidebar + mobile bottom tab bar)
- Add ConfirmDialog with promise-based useConfirmDialog hook for async confirmation flow
- Add WelcomeBanner onboarding component (one-time display, localStorage persistence)
- Polish login form UX (shake on error, TOTP slide animation, password visibility toggle, session expired banner)
- Add inline form validation with shake animation in email composer and contacts
- Add empty state patterns for contacts (no data vs no search results with contextual actions)
- Improve toast notification system with undo action support and typed durations
- Add WCAG AA prefers-reduced-motion media query, safe area insets, sr-only live regions
- Add template settings tab and keyboard shortcut integration
- Update all 8 locale translations
This commit is contained in:
Matthieu MALVACHE
2026-02-17 02:31:50 +01:00
committed by Matthieu MALVACHE
parent 2636a88820
commit a43096485b
44 changed files with 2148 additions and 669 deletions
+8 -4
View File
@@ -50,8 +50,12 @@ export function MobileHeader({
variant="ghost"
size="icon"
onClick={handleLeftAction}
className="h-10 w-10"
className={cn(
"h-11 w-11",
!showBack && sidebarOpen && "bg-accent"
)}
aria-label={showBack ? "Go back" : "Toggle menu"}
aria-expanded={!showBack ? sidebarOpen : undefined}
>
{showBack ? (
<ArrowLeft className="h-5 w-5" />
@@ -73,7 +77,7 @@ export function MobileHeader({
variant="ghost"
size="icon"
onClick={onSearch}
className="h-10 w-10"
className="h-11 w-11"
aria-label={t('mobile.search')}
>
<Search className="h-5 w-5" />
@@ -84,7 +88,7 @@ export function MobileHeader({
variant="ghost"
size="icon"
onClick={onCompose}
className="h-10 w-10 text-primary"
className="h-11 w-11 text-primary"
aria-label={t('mobile.compose')}
>
<Plus className="h-5 w-5" />
@@ -127,7 +131,7 @@ export function MobileViewerHeader({
variant="ghost"
size="icon"
onClick={onBack}
className="h-10 w-10"
className="h-11 w-11"
aria-label={t('mobile.go_back')}
>
<ArrowLeft className="h-5 w-5" />