WIP PWA for Grain
0
fork

Configure Feed

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

fix: move profile dialogs to page level for full-screen overlay

The action dialog and avatar fullscreen overlay were constrained by
the pull-to-refresh transform, preventing proper fixed positioning.
Moved both to the page level to cover the full viewport including header.

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

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

+77 -57
+12 -55
src/components/organisms/grain-profile-header.js
··· 7 7 import '../atoms/grain-icon.js'; 8 8 import '../atoms/grain-toast.js'; 9 9 import '../molecules/grain-profile-stats.js'; 10 - import '../organisms/grain-action-dialog.js'; 11 10 12 11 export class GrainProfileHeader extends LitElement { 13 12 static properties = { 14 13 profile: { type: Object }, 15 - _showFullscreen: { state: true }, 16 14 _user: { state: true }, 17 - _menuOpen: { state: true }, 18 15 _followLoading: { state: true } 19 16 }; 20 17 ··· 69 66 padding: 0; 70 67 cursor: pointer; 71 68 } 72 - .fullscreen-overlay { 73 - position: fixed; 74 - top: 0; 75 - bottom: 0; 76 - left: 50%; 77 - transform: translateX(-50%); 78 - width: 100%; 79 - max-width: var(--feed-max-width); 80 - background: rgba(0, 0, 0, 0.9); 81 - display: flex; 82 - align-items: center; 83 - justify-content: center; 84 - z-index: 1000; 85 - cursor: pointer; 86 - } 87 - .fullscreen-overlay img { 88 - max-width: 80%; 89 - max-height: 80%; 90 - border-radius: 50%; 91 - object-fit: cover; 92 - } 93 69 .menu-button { 94 70 background: none; 95 71 border: none; ··· 121 97 122 98 constructor() { 123 99 super(); 124 - this._showFullscreen = false; 125 100 this._user = auth.user; 126 - this._menuOpen = false; 127 101 this._followLoading = false; 128 102 } 129 103 ··· 152 126 } 153 127 154 128 #openMenu() { 155 - this._menuOpen = true; 129 + this.dispatchEvent(new CustomEvent('menu-open', { 130 + detail: { actions: this.#menuActions }, 131 + bubbles: true, 132 + composed: true 133 + })); 156 134 } 157 135 158 - #closeMenu() { 159 - this._menuOpen = false; 160 - } 161 - 162 - async #handleAction(e) { 163 - const action = e.detail.action; 164 - this._menuOpen = false; 165 - 136 + async handleAction(action) { 166 137 if (action === 'settings') { 167 138 router.push('/settings'); 168 139 } else if (action === 'share') { ··· 173 144 } 174 145 } 175 146 176 - #openFullscreen() { 177 - this._showFullscreen = true; 178 - } 179 - 180 - #closeFullscreen() { 181 - this._showFullscreen = false; 147 + #handleAvatarClick() { 148 + this.dispatchEvent(new CustomEvent('avatar-click', { 149 + bubbles: true, 150 + composed: true 151 + })); 182 152 } 183 153 184 154 async #handleFollowClick() { ··· 213 183 const { handle, displayName, description, avatarUrl, galleryCount, followerCount, followingCount } = this.profile; 214 184 215 185 return html` 216 - ${this._showFullscreen ? html` 217 - <div class="fullscreen-overlay" @click=${this.#closeFullscreen}> 218 - <img src=${avatarUrl || ''} alt=${handle || ''}> 219 - </div> 220 - ` : ''} 221 - 222 186 <div class="top-row"> 223 - <button class="avatar-button" @click=${this.#openFullscreen}> 187 + <button class="avatar-button" @click=${this.#handleAvatarClick}> 224 188 <grain-avatar 225 189 src=${avatarUrl || ''} 226 190 alt=${handle || ''} ··· 254 218 ${this.profile.viewerIsFollowing ? 'Following' : 'Follow'} 255 219 </button> 256 220 ` : ''} 257 - 258 - <grain-action-dialog 259 - ?open=${this._menuOpen} 260 - .actions=${this.#menuActions} 261 - @action=${this.#handleAction} 262 - @close=${this.#closeMenu} 263 - ></grain-action-dialog> 264 221 265 222 <grain-toast></grain-toast> 266 223 `;
+65 -2
src/components/pages/grain-profile.js
··· 6 6 import '../organisms/grain-gallery-grid.js'; 7 7 import '../molecules/grain-pull-to-refresh.js'; 8 8 import '../atoms/grain-spinner.js'; 9 + import '../organisms/grain-action-dialog.js'; 9 10 10 11 export class GrainProfile extends LitElement { 11 12 static properties = { ··· 13 14 _profile: { state: true }, 14 15 _loading: { state: true }, 15 16 _refreshing: { state: true }, 16 - _error: { state: true } 17 + _error: { state: true }, 18 + _menuOpen: { state: true }, 19 + _menuActions: { state: true }, 20 + _showAvatarFullscreen: { state: true } 17 21 }; 18 22 19 23 static styles = css` ··· 30 34 text-align: center; 31 35 color: var(--color-text-secondary); 32 36 } 37 + .fullscreen-overlay { 38 + position: fixed; 39 + inset: 0; 40 + background: rgba(0, 0, 0, 0.9); 41 + display: flex; 42 + align-items: center; 43 + justify-content: center; 44 + z-index: 1000; 45 + cursor: pointer; 46 + } 47 + .fullscreen-overlay img { 48 + max-width: calc(var(--feed-max-width) * 0.8); 49 + max-height: 80%; 50 + border-radius: 50%; 51 + object-fit: cover; 52 + } 33 53 `; 34 54 35 55 constructor() { ··· 38 58 this._loading = true; 39 59 this._refreshing = false; 40 60 this._error = null; 61 + this._menuOpen = false; 62 + this._menuActions = []; 63 + this._showAvatarFullscreen = false; 64 + } 65 + 66 + #handleMenuOpen(e) { 67 + this._menuActions = e.detail.actions; 68 + this._menuOpen = true; 69 + } 70 + 71 + #handleMenuClose() { 72 + this._menuOpen = false; 73 + } 74 + 75 + #handleMenuAction(e) { 76 + this._menuOpen = false; 77 + // Forward the action event to the profile header 78 + this.shadowRoot.querySelector('grain-profile-header')?.handleAction(e.detail.action); 79 + } 80 + 81 + #handleAvatarClick() { 82 + this._showAvatarFullscreen = true; 83 + } 84 + 85 + #closeAvatarFullscreen() { 86 + this._showAvatarFullscreen = false; 41 87 } 42 88 43 89 connectedCallback() { ··· 89 135 ` : ''} 90 136 91 137 ${!this._loading && this._profile ? html` 92 - <grain-profile-header .profile=${this._profile}></grain-profile-header> 138 + <grain-profile-header 139 + .profile=${this._profile} 140 + @menu-open=${this.#handleMenuOpen} 141 + @avatar-click=${this.#handleAvatarClick} 142 + ></grain-profile-header> 93 143 94 144 ${this._profile.galleries.length > 0 ? html` 95 145 <grain-gallery-grid ··· 102 152 ` : ''} 103 153 </grain-pull-to-refresh> 104 154 </grain-feed-layout> 155 + 156 + ${this._showAvatarFullscreen && this._profile ? html` 157 + <div class="fullscreen-overlay" @click=${this.#closeAvatarFullscreen}> 158 + <img src=${this._profile.avatarUrl || ''} alt=${this._profile.handle || ''}> 159 + </div> 160 + ` : ''} 161 + 162 + <grain-action-dialog 163 + ?open=${this._menuOpen} 164 + .actions=${this._menuActions} 165 + @action=${this.#handleMenuAction} 166 + @close=${this.#handleMenuClose} 167 + ></grain-action-dialog> 105 168 `; 106 169 } 107 170 }