reviewed epic 9 stories
This commit is contained in:
@@ -3,6 +3,12 @@
|
||||
## Epic Reference
|
||||
**Epic 9:** Design & Branding Implementation
|
||||
|
||||
## Story Dependencies
|
||||
- **Story 9.1:** Color System Implementation (provides `bg-navy`, `text-gold`, `text-cream` classes)
|
||||
- **Story 9.3:** Logo Integration (provides `<x-logo>` component with variants)
|
||||
- **Story 1.3:** Bilingual Infrastructure (provides language toggle mechanism)
|
||||
- **Story 1.4:** Base UI Navigation (provides existing header structure)
|
||||
|
||||
## User Story
|
||||
As a **user**,
|
||||
I want **professional navigation and footer styling**,
|
||||
@@ -11,94 +17,376 @@ So that **I can easily navigate the site and find information**.
|
||||
## Acceptance Criteria
|
||||
|
||||
### Navigation Bar
|
||||
- [ ] Fixed top position
|
||||
- [ ] Navy blue background
|
||||
- [ ] Fixed top position with `z-50`
|
||||
- [ ] Navy blue background (`bg-navy`)
|
||||
- [ ] Logo left (desktop), centered (mobile)
|
||||
- [ ] Gold text for links
|
||||
- [ ] Active link indicator
|
||||
- [ ] Language toggle styled
|
||||
- [ ] Gold text for links (`text-gold`)
|
||||
- [ ] Active link indicator (gold underline or background)
|
||||
- [ ] Language toggle styled consistently
|
||||
- [ ] Responsive mobile menu
|
||||
|
||||
### Mobile Menu
|
||||
- [ ] Full-width dropdown/slide
|
||||
- [ ] Navy background
|
||||
- [ ] Clear touch targets (44px+)
|
||||
- [ ] Smooth animation
|
||||
- [ ] Full-width dropdown/slide from left
|
||||
- [ ] Navy background (`bg-navy`)
|
||||
- [ ] Clear touch targets (44px+ minimum)
|
||||
- [ ] Smooth animation (200-300ms)
|
||||
- [ ] Close button visible
|
||||
|
||||
### Footer
|
||||
- [ ] Navy blue background
|
||||
- [ ] Logo and firm info
|
||||
- [ ] Contact details
|
||||
- [ ] Links to Terms/Privacy
|
||||
- [ ] Copyright notice
|
||||
- [ ] Sticky footer (always at bottom)
|
||||
- [ ] Navy blue background (`bg-navy`)
|
||||
- [ ] Logo and firm info (using `<x-logo variant="reversed">`)
|
||||
- [ ] Contact details section
|
||||
- [ ] Links to Terms/Privacy pages
|
||||
- [ ] Copyright notice with dynamic year
|
||||
- [ ] Sticky footer (always at bottom even on short pages)
|
||||
|
||||
## Technical Notes
|
||||
### Edge Cases
|
||||
- [ ] Guest navigation: Show Home, Posts, Contact, Login/Register
|
||||
- [ ] Authenticated navigation: Show Home, Dashboard, Posts, user menu
|
||||
- [ ] RTL layout: Logo moves to right, menu items reverse order
|
||||
- [ ] Empty page content: Footer stays at viewport bottom
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Files to Modify
|
||||
|
||||
| File | Action | Purpose |
|
||||
|------|--------|---------|
|
||||
| `resources/views/components/layouts/app/header.blade.php` | Modify | Update nav styling to brand colors |
|
||||
| `resources/views/components/layouts/app.blade.php` | Modify | Add footer component, ensure sticky footer |
|
||||
| `resources/views/components/layouts/auth.blade.php` | Modify | Add footer for auth pages |
|
||||
| `resources/css/app.css` | Modify | Add nav-specific utilities if needed |
|
||||
|
||||
### Files to Create
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `resources/views/components/footer.blade.php` | Reusable footer component |
|
||||
| `resources/views/components/nav-link.blade.php` | Styled navigation link with active state |
|
||||
| `resources/views/components/mobile-menu.blade.php` | Mobile navigation drawer (optional - can inline) |
|
||||
|
||||
### Color Reference (from Story 9.1)
|
||||
```
|
||||
Navy Blue: bg-navy (#0A1F44) - nav/footer background
|
||||
Gold: text-gold (#D4AF37) - links, accents
|
||||
Light Gold: text-gold-light (#F4E4B8) - hover states
|
||||
Cream: text-cream (#F9F7F4) - footer text
|
||||
```
|
||||
|
||||
### Navigation Structure
|
||||
|
||||
```blade
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed top-0 inset-x-0 bg-navy z-50">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex items-center">
|
||||
<x-logo size="small" />
|
||||
</a>
|
||||
<!-- resources/views/components/layouts/app/header.blade.php -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" dir="{{ app()->getLocale() === 'ar' ? 'rtl' : 'ltr' }}">
|
||||
<head>
|
||||
@include('partials.head')
|
||||
</head>
|
||||
<body class="min-h-screen flex flex-col bg-cream">
|
||||
<!-- Fixed Navigation -->
|
||||
<nav class="fixed top-0 inset-x-0 bg-navy z-50" x-data="{ mobileMenu: false }">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<!-- Logo -->
|
||||
<a href="{{ route('home') }}" class="flex items-center" wire:navigate>
|
||||
<x-logo size="small" />
|
||||
</a>
|
||||
|
||||
<!-- Desktop Links -->
|
||||
<div class="hidden md:flex items-center gap-6">
|
||||
<x-nav-link href="/" :active="request()->is('/')">
|
||||
{{ __('nav.home') }}
|
||||
</x-nav-link>
|
||||
<x-nav-link href="/posts" :active="request()->is('posts*')">
|
||||
{{ __('nav.posts') }}
|
||||
</x-nav-link>
|
||||
<!-- More links -->
|
||||
<!-- Desktop Links -->
|
||||
<div class="hidden md:flex items-center gap-6">
|
||||
<x-nav-link href="{{ route('home') }}" :active="request()->routeIs('home')">
|
||||
{{ __('nav.home') }}
|
||||
</x-nav-link>
|
||||
<x-nav-link href="{{ route('posts.index') }}" :active="request()->routeIs('posts.*')">
|
||||
{{ __('nav.posts') }}
|
||||
</x-nav-link>
|
||||
@guest
|
||||
<x-nav-link href="{{ route('login') }}" :active="request()->routeIs('login')">
|
||||
{{ __('nav.login') }}
|
||||
</x-nav-link>
|
||||
@else
|
||||
<x-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')">
|
||||
{{ __('nav.dashboard') }}
|
||||
</x-nav-link>
|
||||
@endguest
|
||||
|
||||
<!-- Language Toggle -->
|
||||
<x-language-toggle />
|
||||
</div>
|
||||
|
||||
<!-- Mobile Toggle -->
|
||||
<button
|
||||
class="md:hidden text-gold p-2 min-h-[44px] min-w-[44px] flex items-center justify-center"
|
||||
x-on:click="mobileMenu = !mobileMenu"
|
||||
aria-label="{{ __('Toggle menu') }}"
|
||||
>
|
||||
<flux:icon name="bars-3" class="w-6 h-6" x-show="!mobileMenu" />
|
||||
<flux:icon name="x-mark" class="w-6 h-6" x-show="mobileMenu" x-cloak />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Toggle -->
|
||||
<button class="md:hidden text-gold" x-on:click="mobileMenu = !mobileMenu">
|
||||
<flux:icon name="bars-3" class="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- Mobile Menu -->
|
||||
<div
|
||||
class="md:hidden bg-navy border-t border-gold/20"
|
||||
x-show="mobileMenu"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2"
|
||||
x-cloak
|
||||
>
|
||||
<div class="container mx-auto px-4 py-4 space-y-2">
|
||||
<x-nav-link href="{{ route('home') }}" :active="request()->routeIs('home')" mobile>
|
||||
{{ __('nav.home') }}
|
||||
</x-nav-link>
|
||||
<x-nav-link href="{{ route('posts.index') }}" :active="request()->routeIs('posts.*')" mobile>
|
||||
{{ __('nav.posts') }}
|
||||
</x-nav-link>
|
||||
@guest
|
||||
<x-nav-link href="{{ route('login') }}" :active="request()->routeIs('login')" mobile>
|
||||
{{ __('nav.login') }}
|
||||
</x-nav-link>
|
||||
<x-nav-link href="{{ route('register') }}" :active="request()->routeIs('register')" mobile>
|
||||
{{ __('nav.register') }}
|
||||
</x-nav-link>
|
||||
@else
|
||||
<x-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')" mobile>
|
||||
{{ __('nav.dashboard') }}
|
||||
</x-nav-link>
|
||||
@endguest
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Footer -->
|
||||
<!-- Spacer for fixed nav -->
|
||||
<div class="h-16"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<x-footer />
|
||||
|
||||
@fluxScripts
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Nav Link Component
|
||||
|
||||
```blade
|
||||
<!-- resources/views/components/nav-link.blade.php -->
|
||||
@props(['active' => false, 'mobile' => false])
|
||||
|
||||
@php
|
||||
$baseClasses = $mobile
|
||||
? 'block px-4 py-3 min-h-[44px] text-base font-medium transition-colors duration-150'
|
||||
: 'text-sm font-medium transition-colors duration-150';
|
||||
|
||||
$activeClasses = $active
|
||||
? 'text-gold-light'
|
||||
: 'text-gold hover:text-gold-light';
|
||||
@endphp
|
||||
|
||||
<a {{ $attributes->merge(['class' => "$baseClasses $activeClasses"]) }} wire:navigate>
|
||||
{{ $slot }}
|
||||
</a>
|
||||
```
|
||||
|
||||
### Footer Component
|
||||
|
||||
```blade
|
||||
<!-- resources/views/components/footer.blade.php -->
|
||||
<footer class="bg-navy text-cream mt-auto">
|
||||
<div class="container mx-auto px-4 py-12">
|
||||
<div class="grid md:grid-cols-3 gap-8">
|
||||
<!-- Logo & Description -->
|
||||
<div>
|
||||
<x-logo variant="reversed" />
|
||||
<p class="mt-4 text-sm opacity-80">{{ __('footer.description') }}</p>
|
||||
<p class="mt-4 text-sm opacity-80">
|
||||
{{ __('footer.description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Quick Links -->
|
||||
<div>
|
||||
<h4 class="font-semibold text-gold mb-4">{{ __('footer.links') }}</h4>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li><a href="{{ route('page.show', 'terms') }}" class="hover:text-gold">{{ __('footer.terms') }}</a></li>
|
||||
<li><a href="{{ route('page.show', 'privacy') }}" class="hover:text-gold">{{ __('footer.privacy') }}</a></li>
|
||||
<li>
|
||||
<a href="{{ route('page.show', 'terms') }}" class="hover:text-gold transition-colors duration-150" wire:navigate>
|
||||
{{ __('footer.terms') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ route('page.show', 'privacy') }}" class="hover:text-gold transition-colors duration-150" wire:navigate>
|
||||
{{ __('footer.privacy') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Contact Info -->
|
||||
<div>
|
||||
<h4 class="font-semibold text-gold mb-4">{{ __('footer.contact') }}</h4>
|
||||
<!-- Contact info -->
|
||||
<ul class="space-y-2 text-sm opacity-80">
|
||||
<li class="flex items-center gap-2 rtl:flex-row-reverse">
|
||||
<flux:icon name="envelope" class="w-4 h-4 text-gold" />
|
||||
<span>{{ config('libra.contact.email', 'info@libra.ps') }}</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 rtl:flex-row-reverse">
|
||||
<flux:icon name="phone" class="w-4 h-4 text-gold" />
|
||||
<span dir="ltr">{{ config('libra.contact.phone', '+970 2 123 4567') }}</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2 rtl:flex-row-reverse">
|
||||
<flux:icon name="map-pin" class="w-4 h-4 text-gold mt-0.5" />
|
||||
<span>{{ config('libra.contact.address', 'Ramallah, Palestine') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Copyright -->
|
||||
<div class="border-t border-cream/20 mt-8 pt-8 text-sm text-center opacity-60">
|
||||
© {{ date('Y') }} {{ __('footer.copyright') }}
|
||||
© {{ date('Y') }} {{ __('footer.copyright', ['name' => config('app.name')]) }}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
```
|
||||
|
||||
### Sticky Footer CSS (if needed)
|
||||
|
||||
```css
|
||||
/* resources/css/app.css - add if flex approach insufficient */
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
```
|
||||
|
||||
## RTL Considerations
|
||||
|
||||
1. **Logo Position:** Use `rtl:order-last` if logo should move to right in RTL
|
||||
2. **Menu Items:** Flex containers with `rtl:flex-row-reverse` or use logical properties
|
||||
3. **Icons:** Some icons (arrows, chevrons) may need flipping with `rtl:scale-x-[-1]`
|
||||
4. **Spacing:** Use `ms-*`/`me-*` (margin-start/end) instead of `ml-*`/`mr-*`
|
||||
5. **Text Alignment:** Use `text-start`/`text-end` instead of `text-left`/`text-right`
|
||||
|
||||
## Translation Keys Required
|
||||
|
||||
```php
|
||||
// resources/lang/en/nav.php
|
||||
return [
|
||||
'home' => 'Home',
|
||||
'posts' => 'Posts',
|
||||
'login' => 'Login',
|
||||
'register' => 'Register',
|
||||
'dashboard' => 'Dashboard',
|
||||
];
|
||||
|
||||
// resources/lang/en/footer.php
|
||||
return [
|
||||
'description' => 'Professional legal services in Palestine.',
|
||||
'links' => 'Quick Links',
|
||||
'terms' => 'Terms of Service',
|
||||
'privacy' => 'Privacy Policy',
|
||||
'contact' => 'Contact Us',
|
||||
'copyright' => 'All rights reserved. :name',
|
||||
];
|
||||
```
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Navigation styled correctly
|
||||
- [ ] Mobile menu works
|
||||
- [ ] Footer styled correctly
|
||||
- [ ] Sticky footer works
|
||||
- [ ] Links functional
|
||||
- [ ] RTL layout works
|
||||
- [ ] Navigation styled with navy background and gold links
|
||||
- [ ] Mobile menu works with smooth animation
|
||||
- [ ] Footer styled and positioned correctly
|
||||
- [ ] Sticky footer works on short-content pages
|
||||
- [ ] All navigation links functional
|
||||
- [ ] RTL layout mirrors correctly (logo right, reversed order)
|
||||
- [ ] Guest vs authenticated nav items display correctly
|
||||
- [ ] Language toggle integrated and styled
|
||||
- [ ] Touch targets meet 44px minimum on mobile
|
||||
- [ ] Tests pass
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
### Feature Tests
|
||||
```php
|
||||
// tests/Feature/NavigationTest.php
|
||||
test('navigation displays home link', function () {
|
||||
$this->get('/')
|
||||
->assertSee(__('nav.home'));
|
||||
});
|
||||
|
||||
test('guest sees login link in navigation', function () {
|
||||
$this->get('/')
|
||||
->assertSee(__('nav.login'));
|
||||
});
|
||||
|
||||
test('authenticated user sees dashboard link', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->get('/')
|
||||
->assertSee(__('nav.dashboard'))
|
||||
->assertDontSee(__('nav.login'));
|
||||
});
|
||||
|
||||
test('footer displays on all pages', function () {
|
||||
$this->get('/')
|
||||
->assertSee(__('footer.copyright'));
|
||||
});
|
||||
|
||||
test('footer links to terms and privacy', function () {
|
||||
$this->get('/')
|
||||
->assertSee(__('footer.terms'))
|
||||
->assertSee(__('footer.privacy'));
|
||||
});
|
||||
```
|
||||
|
||||
### Browser Tests (Pest v4)
|
||||
```php
|
||||
// tests/Browser/NavigationTest.php
|
||||
it('mobile menu toggles correctly', function () {
|
||||
visit('/')
|
||||
->resize(375, 812) // iPhone viewport
|
||||
->assertNotVisible('[x-show="mobileMenu"]')
|
||||
->click('[aria-label="Toggle menu"]')
|
||||
->assertVisible('[x-show="mobileMenu"]')
|
||||
->click('[aria-label="Toggle menu"]')
|
||||
->assertNotVisible('[x-show="mobileMenu"]');
|
||||
});
|
||||
|
||||
it('navigation renders correctly in RTL', function () {
|
||||
visit('/?lang=ar')
|
||||
->assertAttribute('html', 'dir', 'rtl');
|
||||
});
|
||||
```
|
||||
|
||||
### Manual Testing Checklist
|
||||
- [ ] Desktop: Nav links visible, hover states work
|
||||
- [ ] Mobile: Menu toggle works, touch targets adequate
|
||||
- [ ] RTL (Arabic): Layout mirrors correctly
|
||||
- [ ] LTR (English): Standard layout
|
||||
- [ ] Short page: Footer at viewport bottom
|
||||
- [ ] Long page: Footer at content bottom
|
||||
- [ ] Guest user: Login/Register visible
|
||||
- [ ] Logged in: Dashboard/user menu visible
|
||||
|
||||
## Estimation
|
||||
**Complexity:** Medium | **Effort:** 4 hours
|
||||
|
||||
## References
|
||||
- Epic 9 Definition: `docs/epics/epic-9-design-branding.md`
|
||||
- Color System: Story 9.1
|
||||
- Logo Component: Story 9.3
|
||||
- Existing Header: `resources/views/components/layouts/app/header.blade.php`
|
||||
- PRD Design Section: `docs/prd.md#design-requirements`
|
||||
|
||||
Reference in New Issue
Block a user