|
396 | 396 |
|
397 | 397 | let overflowTriggerRef = $state<HTMLButtonElement | null>(null); |
398 | 398 |
|
| 399 | + // Sticky detection: track whether the action bar is floating (stuck) vs at its natural position |
| 400 | + let actionBarSentinelRef = $state<HTMLDivElement | null>(null); |
| 401 | + let isActionBarFloating = $state(false); |
| 402 | +
|
| 403 | + $effect(() => { |
| 404 | + if (!expanded || !actionBarSentinelRef) { |
| 405 | + isActionBarFloating = false; |
| 406 | + return; |
| 407 | + } |
| 408 | + const observer = new IntersectionObserver( |
| 409 | + ([entry]) => { |
| 410 | + // When the sentinel is visible, the action bar is at its natural position (not floating) |
| 411 | + isActionBarFloating = !entry.isIntersecting; |
| 412 | + }, |
| 413 | + { threshold: 0 } |
| 414 | + ); |
| 415 | + observer.observe(actionBarSentinelRef); |
| 416 | + return () => observer.disconnect(); |
| 417 | + }); |
| 418 | +
|
399 | 419 | function handleOverflowTag(e: MouseEvent) { |
400 | 420 | e.stopPropagation(); |
401 | 421 | overflowMenuOpen = false; |
|
635 | 655 | <div |
636 | 656 | class="article-actions-container" |
637 | 657 | class:scroll-hidden={expanded && !feedViewStore.mobileControlsVisible} |
| 658 | + class:floating={isActionBarFloating} |
638 | 659 | > |
639 | 660 | <div class="article-actions"> |
640 | 661 | {#if isShareMode} |
|
785 | 806 | {/if} |
786 | 807 | </div> |
787 | 808 | </div> |
| 809 | + {#if expanded}<div class="action-bar-sentinel" bind:this={actionBarSentinelRef}></div>{/if} |
788 | 810 |
|
789 | 811 | {#if tagMenuOpen} |
790 | 812 | <TagMenu |
|
1190 | 1212 | } |
1191 | 1213 | } |
1192 | 1214 |
|
1193 | | - /* Expanded: pop out into floating pill, sticky at bottom */ |
| 1215 | + /* Expanded: sticky at bottom, ready to float */ |
1194 | 1216 | .article-item.expanded .article-actions-container { |
1195 | 1217 | justify-content: center; |
1196 | 1218 | position: sticky; |
1197 | 1219 | bottom: 0; |
1198 | 1220 | padding: 1rem 0; |
1199 | 1221 | } |
1200 | 1222 |
|
| 1223 | + /* Sentinel element for sticky detection */ |
| 1224 | + .action-bar-sentinel { |
| 1225 | + height: 1px; |
| 1226 | + margin-top: -1px; |
| 1227 | + } |
| 1228 | +
|
1201 | 1229 | /* On mobile, position above the MobileBottomBar and hide on scroll */ |
1202 | 1230 | @media (max-width: 1000px) { |
1203 | 1231 | .article-item.expanded .article-actions-container { |
|
1214 | 1242 | } |
1215 | 1243 | } |
1216 | 1244 |
|
1217 | | - .article-item.expanded .article-actions { |
| 1245 | + /* Floating pill styles only when action bar is stuck */ |
| 1246 | + .article-item.expanded .article-actions-container.floating .article-actions { |
1218 | 1247 | justify-content: space-between; |
1219 | 1248 | width: auto; |
1220 | 1249 | gap: 0.875rem; |
|
1223 | 1252 | backdrop-filter: blur(8px); |
1224 | 1253 | border-radius: 9999px; |
1225 | 1254 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); |
| 1255 | + transition: |
| 1256 | + background 0.2s ease, |
| 1257 | + box-shadow 0.2s ease, |
| 1258 | + border-radius 0.2s ease, |
| 1259 | + padding 0.2s ease; |
1226 | 1260 | } |
1227 | 1261 |
|
1228 | 1262 | @media (prefers-color-scheme: dark) { |
1229 | | - .article-item.expanded .article-actions { |
| 1263 | + .article-item.expanded .article-actions-container.floating .article-actions { |
1230 | 1264 | background: rgba(40, 40, 40, 0.95); |
1231 | 1265 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); |
1232 | 1266 | } |
1233 | 1267 | } |
1234 | 1268 |
|
| 1269 | + /* Non-floating expanded state: normal inline look */ |
| 1270 | + .article-item.expanded .article-actions-container:not(.floating) .article-actions { |
| 1271 | + justify-content: space-between; |
| 1272 | + width: auto; |
| 1273 | + gap: 0.875rem; |
| 1274 | + transition: |
| 1275 | + background 0.2s ease, |
| 1276 | + box-shadow 0.2s ease, |
| 1277 | + border-radius 0.2s ease, |
| 1278 | + padding 0.2s ease; |
| 1279 | + } |
| 1280 | +
|
1235 | 1281 | .action-btn { |
1236 | 1282 | display: flex; |
1237 | 1283 | align-items: center; |
|
0 commit comments