WIP PWA for Grain
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add bottom navigation bar with home button

Instagram-style fixed bottom nav with home and profile icons.
Clicking home returns to the main timeline from any page.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+91 -2
+5 -1
src/components/atoms/grain-icon.js
··· 6 6 heartFilled: 'fa-solid fa-heart', 7 7 comment: 'fa-regular fa-comment', 8 8 share: 'fa-solid fa-paper-plane', 9 - back: 'fa-solid fa-arrow-left' 9 + back: 'fa-solid fa-arrow-left', 10 + home: 'fa-solid fa-house', 11 + homeLine: 'fa-regular fa-house', 12 + user: 'fa-regular fa-user', 13 + userFilled: 'fa-solid fa-user' 10 14 }; 11 15 12 16 export class GrainIcon extends LitElement {
+80
src/components/organisms/grain-bottom-nav.js
··· 1 + import { LitElement, html, css } from 'lit'; 2 + import { router } from '../../router.js'; 3 + import '../atoms/grain-icon.js'; 4 + 5 + export class GrainBottomNav extends LitElement { 6 + static properties = { 7 + active: { type: String } 8 + }; 9 + 10 + static styles = css` 11 + :host { 12 + display: block; 13 + position: fixed; 14 + bottom: 0; 15 + left: 0; 16 + right: 0; 17 + background: var(--color-bg-primary); 18 + border-top: 1px solid var(--color-border); 19 + padding-bottom: env(safe-area-inset-bottom); 20 + z-index: 100; 21 + } 22 + nav { 23 + display: flex; 24 + justify-content: space-around; 25 + align-items: center; 26 + max-width: var(--feed-max-width); 27 + margin: 0 auto; 28 + padding: var(--space-sm) 0; 29 + } 30 + button { 31 + display: flex; 32 + align-items: center; 33 + justify-content: center; 34 + background: none; 35 + border: none; 36 + padding: var(--space-sm); 37 + cursor: pointer; 38 + color: var(--color-text-secondary); 39 + } 40 + button.active { 41 + color: var(--color-text-primary); 42 + } 43 + `; 44 + 45 + constructor() { 46 + super(); 47 + this.active = 'home'; 48 + } 49 + 50 + #handleHome() { 51 + router.push('/'); 52 + } 53 + 54 + #handleProfile() { 55 + // For now, just go to a sample profile 56 + // Later this would go to the logged-in user's profile 57 + router.push('/'); 58 + } 59 + 60 + render() { 61 + return html` 62 + <nav> 63 + <button 64 + class=${this.active === 'home' ? 'active' : ''} 65 + @click=${this.#handleHome} 66 + > 67 + <grain-icon name=${this.active === 'home' ? 'home' : 'homeLine'} size="24"></grain-icon> 68 + </button> 69 + <button 70 + class=${this.active === 'profile' ? 'active' : ''} 71 + @click=${this.#handleProfile} 72 + > 73 + <grain-icon name=${this.active === 'profile' ? 'userFilled' : 'user'} size="24"></grain-icon> 74 + </button> 75 + </nav> 76 + `; 77 + } 78 + } 79 + 80 + customElements.define('grain-bottom-nav', GrainBottomNav);
+5 -1
src/components/pages/grain-app.js
··· 5 5 import './grain-timeline.js'; 6 6 import './grain-profile.js'; 7 7 import './grain-gallery-detail.js'; 8 + import '../organisms/grain-bottom-nav.js'; 8 9 9 10 export class GrainApp extends LitElement { 10 11 static styles = css` ··· 33 34 } 34 35 35 36 render() { 36 - return html`<div id="outlet"></div>`; 37 + return html` 38 + <div id="outlet"></div> 39 + <grain-bottom-nav></grain-bottom-nav> 40 + `; 37 41 } 38 42 } 39 43
+1
src/components/templates/grain-feed-layout.js
··· 8 8 margin: 0 auto; 9 9 min-height: 100vh; 10 10 min-height: 100dvh; 11 + padding-bottom: calc(56px + env(safe-area-inset-bottom)); 11 12 background: var(--color-bg-primary); 12 13 } 13 14 `;