|
| 1 | +--- |
| 2 | +interface NavItem { |
| 3 | + href: string; |
| 4 | + label: string; |
| 5 | + active?: boolean; |
| 6 | +} |
| 7 | +
|
| 8 | +interface Props { |
| 9 | + pageTitle: string; |
| 10 | + topbarTitle?: string; |
| 11 | + topbarMeta?: string; |
| 12 | + navItems?: NavItem[]; |
| 13 | + showThemeToggle?: boolean; |
| 14 | + showLoginSlot?: boolean; |
| 15 | + legalLabel?: string; |
| 16 | + shellClass?: string; |
| 17 | +} |
| 18 | +
|
| 19 | +const { |
| 20 | + pageTitle, |
| 21 | + topbarTitle = pageTitle, |
| 22 | + topbarMeta = '', |
| 23 | + navItems = [], |
| 24 | + showThemeToggle = true, |
| 25 | + showLoginSlot = false, |
| 26 | + legalLabel = 'Yasal bağlantılar', |
| 27 | + shellClass = '', |
| 28 | +} = Astro.props; |
| 29 | +--- |
| 30 | + |
| 31 | +<section class={`app-shell ${shellClass}`.trim()}> |
| 32 | + <aside class="left-rail card"> |
| 33 | + <div> |
| 34 | + <p class="rail-brand">Samet Başbuğ</p> |
| 35 | + <h1>{pageTitle}</h1> |
| 36 | + </div> |
| 37 | + |
| 38 | + <nav class="feed-nav"> |
| 39 | + {navItems.map((item) => ( |
| 40 | + <a href={item.href} class={item.active ? 'active' : undefined}>{item.label}</a> |
| 41 | + ))} |
| 42 | + </nav> |
| 43 | + |
| 44 | + {showThemeToggle && ( |
| 45 | + <button id="rail-theme-toggle" class="rail-theme-toggle" type="button" aria-label="Temayı Değiştir"> |
| 46 | + <span class="icon" data-theme-icon>🌙</span> |
| 47 | + <span data-theme-label>Koyu Tema</span> |
| 48 | + </button> |
| 49 | + )} |
| 50 | + |
| 51 | + {showLoginSlot && ( |
| 52 | + <div class="feed-login-slot"> |
| 53 | + <slot name="left-login" /> |
| 54 | + </div> |
| 55 | + )} |
| 56 | + |
| 57 | + <div class="feed-legal-links" aria-label={legalLabel}> |
| 58 | + <a href="/gizlilik-politikasi">Gizlilik Politikası</a> |
| 59 | + <a href="/kullanim-sartlari">Kullanım Şartları</a> |
| 60 | + <a href="/cerez-politikasi">Çerez Politikası</a> |
| 61 | + <a href="/topluluk-kurallari">Topluluk Kuralları</a> |
| 62 | + <p class="feed-legal-copy">© 2026 Samet Başbuğ</p> |
| 63 | + <a href="/rss.xml" class="feed-rss-link">RSS Feed</a> |
| 64 | + </div> |
| 65 | + </aside> |
| 66 | + |
| 67 | + <main class="center-feed card"> |
| 68 | + <div class="feed-topbar"> |
| 69 | + <strong>{topbarTitle}</strong> |
| 70 | + <span>{topbarMeta}</span> |
| 71 | + </div> |
| 72 | + |
| 73 | + <div class="mobile-hub" aria-label="Mobil üst alan"> |
| 74 | + <div class="mobile-topline" aria-label="Mobil üst bar"> |
| 75 | + <details class="mobile-drawer"> |
| 76 | + <summary aria-label="Menüyü aç"> |
| 77 | + <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6h16v2H4V6Zm0 5h16v2H4v-2Zm0 5h16v2H4v-2Z"/></svg> |
| 78 | + </summary> |
| 79 | + <button class="drawer-backdrop" type="button" aria-label="Menüyü kapat"></button> |
| 80 | + <div class="mobile-drawer-panel"> |
| 81 | + <p class="drawer-title">Menü</p> |
| 82 | + {navItems.map((item) => ( |
| 83 | + <a href={item.href}>{item.label}</a> |
| 84 | + ))} |
| 85 | + {showLoginSlot && <div class="mobile-drawer-login"><slot name="mobile-login" /></div>} |
| 86 | + <div class="feed-legal-links mobile-legal-links" aria-label={legalLabel}> |
| 87 | + <a href="/gizlilik-politikasi">Gizlilik Politikası</a> |
| 88 | + <a href="/kullanim-sartlari">Kullanım Şartları</a> |
| 89 | + <a href="/cerez-politikasi">Çerez Politikası</a> |
| 90 | + <a href="/topluluk-kurallari">Topluluk Kuralları</a> |
| 91 | + <p class="feed-legal-copy">© 2026 Samet Başbuğ</p> |
| 92 | + <a href="/rss.xml" class="feed-rss-link">RSS Feed</a> |
| 93 | + </div> |
| 94 | + </div> |
| 95 | + </details> |
| 96 | + |
| 97 | + <strong class="mobile-brand">{pageTitle}</strong> |
| 98 | + |
| 99 | + {showThemeToggle && ( |
| 100 | + <button class="mobile-top-theme-toggle mobile-theme-toggle" type="button" aria-label="Temayı Değiştir"> |
| 101 | + <span data-theme-icon>🌙</span> |
| 102 | + <span data-theme-label>Koyu Tema</span> |
| 103 | + </button> |
| 104 | + )} |
| 105 | + </div> |
| 106 | + |
| 107 | + <slot name="mobile-hub" /> |
| 108 | + </div> |
| 109 | + |
| 110 | + <slot /> |
| 111 | + </main> |
| 112 | + |
| 113 | + <aside class="right-rail card"> |
| 114 | + <slot name="right-rail" /> |
| 115 | + </aside> |
| 116 | +</section> |
| 117 | + |
| 118 | +<script> |
| 119 | + function initAppShellDrawer() { |
| 120 | + const drawer = document.querySelector('.mobile-drawer'); |
| 121 | + if (!drawer || drawer.dataset.bound === '1') return; |
| 122 | + drawer.dataset.bound = '1'; |
| 123 | + |
| 124 | + document.addEventListener('click', (event) => { |
| 125 | + if (!drawer.open) return; |
| 126 | + const target = event.target; |
| 127 | + if (!(target instanceof Node)) return; |
| 128 | + if (!drawer.contains(target)) drawer.removeAttribute('open'); |
| 129 | + }); |
| 130 | + |
| 131 | + drawer.querySelectorAll('a, .drawer-backdrop, .mobile-theme-toggle, [data-auth-login], [data-auth-logout]').forEach((el) => { |
| 132 | + el.addEventListener('click', () => drawer.removeAttribute('open')); |
| 133 | + }); |
| 134 | + } |
| 135 | + |
| 136 | + function applyAppShellMobileMode() { |
| 137 | + const isMobile = window.matchMedia('(max-width: 820px), (hover: none) and (pointer: coarse)').matches; |
| 138 | + document.documentElement.classList.toggle('feed-mobile-mode', isMobile); |
| 139 | + document.documentElement.classList.toggle('force-mobile-ui', isMobile); |
| 140 | + } |
| 141 | + |
| 142 | + function initAppShellThemeToggle() { |
| 143 | + const buttons = Array.from(document.querySelectorAll('#rail-theme-toggle, .mobile-theme-toggle')); |
| 144 | + if (buttons.length === 0) return; |
| 145 | + |
| 146 | + const syncThemeButtons = () => { |
| 147 | + const theme = document.documentElement.getAttribute('data-theme') || 'dark'; |
| 148 | + const isDark = theme === 'dark'; |
| 149 | + buttons.forEach((btn) => { |
| 150 | + const iconEl = btn.querySelector('[data-theme-icon]'); |
| 151 | + const labelEl = btn.querySelector('[data-theme-label]'); |
| 152 | + if (iconEl) iconEl.textContent = isDark ? '🌙' : '☀️'; |
| 153 | + if (labelEl) labelEl.textContent = isDark ? 'Koyu Tema' : 'Açık Tema'; |
| 154 | + }); |
| 155 | + }; |
| 156 | + |
| 157 | + syncThemeButtons(); |
| 158 | + buttons.forEach((btn) => { |
| 159 | + if (btn.dataset.bound === '1') return; |
| 160 | + btn.dataset.bound = '1'; |
| 161 | + btn.addEventListener('click', () => { |
| 162 | + const current = document.documentElement.getAttribute('data-theme') || 'dark'; |
| 163 | + const next = current === 'dark' ? 'light' : 'dark'; |
| 164 | + document.documentElement.setAttribute('data-theme', next); |
| 165 | + try { localStorage.setItem('theme', next); } catch {} |
| 166 | + syncThemeButtons(); |
| 167 | + }); |
| 168 | + }); |
| 169 | + } |
| 170 | + |
| 171 | + applyAppShellMobileMode(); |
| 172 | + initAppShellDrawer(); |
| 173 | + initAppShellThemeToggle(); |
| 174 | + window.addEventListener('resize', applyAppShellMobileMode); |
| 175 | + document.addEventListener('astro:page-load', () => { |
| 176 | + applyAppShellMobileMode(); |
| 177 | + initAppShellDrawer(); |
| 178 | + initAppShellThemeToggle(); |
| 179 | + }); |
| 180 | +</script> |
| 181 | + |
| 182 | +<style> |
| 183 | + :global(html), |
| 184 | + :global(body) { |
| 185 | + overflow-x: hidden; |
| 186 | + } |
| 187 | + |
| 188 | + :global(main) { |
| 189 | + max-width: 1240px; |
| 190 | + padding-top: 1.1rem; |
| 191 | + overflow-x: hidden; |
| 192 | + } |
| 193 | + |
| 194 | + .app-shell { display: grid; grid-template-columns: 240px minmax(0, 1fr) 280px; gap: 1rem; align-items: start; margin-top: 0.2rem; min-width: 0; width: 100%; max-width: 100%; } |
| 195 | + .card { background: transparent; border: 0; border-radius: 0; box-shadow: none; } |
| 196 | + .left-rail,.right-rail { position: sticky; top: 1.1rem; padding: 0.9rem 1rem 1rem; } |
| 197 | + .left-rail { padding-left: 0.8rem; } |
| 198 | + .rail-brand { color: var(--accent); font-weight: 700; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.08em; } |
| 199 | + .left-rail h1 { margin-top: 0.2rem; margin-bottom: 0.9rem; font-size: 1.3rem; } |
| 200 | + .feed-nav { display:flex; flex-direction:column; align-items:flex-start; gap:0.5rem; margin-bottom:1rem; text-align:left; } |
| 201 | + .feed-nav a { color:var(--secondary); text-decoration:none; padding:0.5rem 0; font-weight:600; transition:color .18s ease; } |
| 202 | + .feed-nav a:hover,.feed-nav a.active { color:var(--text); } |
| 203 | + .rail-theme-toggle { margin-top:.5rem; display:inline-flex; align-items:center; gap:.45rem; background:transparent; border:1px solid color-mix(in srgb, var(--border) 70%, transparent); color:var(--secondary); padding:.5rem .7rem; border-radius:999px; cursor:pointer; font:inherit; font-size:.88rem; } |
| 204 | + .rail-theme-toggle:hover { color:var(--text); border-color:var(--accent); } |
| 205 | + .feed-login-slot { margin-top:.65rem; display:flex; justify-content:flex-start; } |
| 206 | + .feed-legal-links { margin-top:1rem; padding-top:.95rem; border-top:1px solid color-mix(in srgb, var(--border) 48%, transparent); display:grid; grid-template-columns:max-content max-content; gap:.45rem .95rem; align-items:start; justify-content:start; max-width:100%; } |
| 207 | + .feed-legal-links a { color:var(--secondary); text-decoration:none; font-size:.77rem; line-height:1.25; width:fit-content; white-space:nowrap; } |
| 208 | + .feed-legal-links a:hover { color:var(--text); } |
| 209 | + .feed-legal-copy { grid-column:1 / -1; margin:0.18rem 0 0; color:color-mix(in srgb, var(--secondary) 92%, transparent); font-size:.76rem; line-height:1.4; } |
| 210 | + .feed-rss-link { grid-column:1 / -1; width:fit-content; margin-top:.15rem; color:var(--text) !important; font-size:.82rem !important; font-weight:800; letter-spacing:.02em; } |
| 211 | + .feed-rss-link:hover { color:var(--accent) !important; } |
| 212 | + .center-feed { overflow:hidden; border-left:1px solid color-mix(in srgb, var(--border) 70%, transparent); border-right:1px solid color-mix(in srgb, var(--border) 70%, transparent); min-width:0; width:100%; max-width:100%; } |
| 213 | + .feed-topbar { display:flex; justify-content:space-between; gap:1rem; border-bottom:1px solid color-mix(in srgb, var(--border) 70%, transparent); padding:.9rem 1rem; font-size:.92rem; color:var(--secondary); background:transparent; } |
| 214 | + .feed-topbar strong { color:var(--text); } |
| 215 | + .mobile-topline,.mobile-hub { display:none; } |
| 216 | + @media (max-width: 820px) { |
| 217 | + :global(main) { max-width:100%!important; width:100%!important; margin:0!important; padding:0!important; } |
| 218 | + .app-shell { grid-template-columns:1fr!important; width:100%!important; max-width:100%!important; margin:0!important; gap:0!important; } |
| 219 | + .left-rail,.right-rail,.feed-topbar { display:none!important; } |
| 220 | + .mobile-hub { display:block!important; padding:.75rem; border-bottom:1px solid color-mix(in srgb, var(--border) 60%, transparent); background:transparent; } |
| 221 | + .center-feed { border-left:0; border-right:0; overflow:visible; } |
| 222 | + .mobile-topline { display:flex!important; align-items:center; gap:.7rem; margin-bottom:.7rem; } |
| 223 | + .mobile-brand { font-size:1rem; font-weight:800; } |
| 224 | + .mobile-top-theme-toggle { margin-left:auto; height:32px; border-radius:999px; border:1px solid color-mix(in srgb, var(--border) 70%, transparent); background:transparent; color:var(--secondary); display:inline-flex; align-items:center; justify-content:center; gap:.3rem; padding:0 .6rem; font-size:.78rem; } |
| 225 | + .mobile-drawer { position:relative; } |
| 226 | + .mobile-drawer summary { list-style:none; display:grid; place-items:center; width:34px; height:34px; border-radius:.65rem; border:1px solid var(--border); background:var(--muted); cursor:pointer; } |
| 227 | + .mobile-drawer summary::-webkit-details-marker { display:none; } |
| 228 | + .mobile-drawer summary svg { width:18px; height:18px; fill:currentColor; } |
| 229 | + .drawer-backdrop { display:none; } |
| 230 | + .mobile-drawer[open] .drawer-backdrop { display:block; position:fixed; inset:0; background:rgba(2,6,23,.28); border:0; z-index:18; } |
| 231 | + .mobile-drawer-panel { position:absolute; top:calc(100% + .7rem); left:0; min-width:210px; z-index:50; background:rgba(11,15,25,.95); border:1px solid color-mix(in srgb, white 18%, var(--border)); border-radius:.75rem; box-shadow:0 12px 30px rgba(0,0,0,.35); backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px); padding:.35rem .6rem; } |
| 232 | + .drawer-title { font-size:.7rem; text-transform:uppercase; letter-spacing:.08em; color:var(--secondary); padding:.25rem 0; margin-bottom:.1rem; } |
| 233 | + .mobile-drawer-panel a { display:block; padding:.5rem 0; color:var(--text); text-decoration:none; font-size:.9rem; border-bottom:1px solid color-mix(in srgb, var(--border) 60%, transparent); } |
| 234 | + .mobile-drawer-login { margin-top:.2rem; padding-top:.55rem; border-top:1px solid color-mix(in srgb, var(--border) 55%, transparent); } |
| 235 | + .mobile-legal-links { max-width:none; margin-top:.7rem; padding-top:.7rem; gap:.38rem .7rem; grid-template-columns:max-content max-content; } |
| 236 | + } |
| 237 | + :global(html.force-mobile-ui) main { max-width:100%!important; width:100%!important; margin:0!important; padding:0!important; } |
| 238 | + :global(html.force-mobile-ui) .app-shell { grid-template-columns:1fr!important; width:100%!important; max-width:100%!important; margin:0!important; gap:0!important; } |
| 239 | + :global(html.force-mobile-ui) .left-rail,:global(html.force-mobile-ui) .right-rail,:global(html.force-mobile-ui) .feed-topbar { display:none!important; } |
| 240 | + :global(html.force-mobile-ui) .mobile-hub { display:block!important; } |
| 241 | + :global(html.force-mobile-ui) .mobile-topline { display:flex!important; } |
| 242 | + :global(html.force-mobile-ui) .center-feed { border-left:0!important; border-right:0!important; overflow:visible!important; } |
| 243 | +</style> |
0 commit comments