🚸 feat(shared-ui): improve PillNavigation mobile responsiveness

- Position nav at bottom of screen on mobile devices
- Dropdowns open upward when nav is at bottom
- Sidebar mode renders as bottom sheet on mobile with drag handle
- FAB (collapsed state) positioned at bottom-left on mobile
- Added mobile detection with resize listener
- Safe area padding for iPhone home indicator
This commit is contained in:
Till-JS 2025-12-01 15:40:33 +01:00
parent 1e2cc037d5
commit a51e0651e5

View file

@ -187,6 +187,22 @@
onCollapsedChange !== undefined ? (externalCollapsed ?? false) : internalCollapsed
);
// Mobile detection for dropdown direction
let isMobile = $state(false);
$effect(() => {
if (typeof window !== 'undefined') {
const checkMobile = () => {
isMobile = window.innerWidth <= 768;
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}
});
// Dropdown direction: up on mobile (nav at bottom), down on desktop/sidebar
const dropdownDirection = $derived<'up' | 'down'>(isMobile && !isSidebarMode ? 'up' : 'down');
function toggleSidebarMode() {
const newValue = !isSidebarMode;
if (onModeChange) {
@ -277,7 +293,7 @@
{#if showAppSwitcher && appItems.length > 0}
<PillDropdown
items={createAppDropdownItems(appItems, allAppsHref, allAppsLabel)}
direction="down"
direction={dropdownDirection}
label={appName}
icon="grid"
/>
@ -367,7 +383,7 @@
{#if showThemeVariants && themeVariantItems.length > 0}
<PillDropdown
items={themeVariantItems}
direction="down"
direction={dropdownDirection}
label={currentThemeVariantLabel}
icon="palette"
>
@ -522,7 +538,7 @@
]
: []),
]}
direction="down"
direction={dropdownDirection}
label={truncateEmail(userEmail)}
icon="user"
/>
@ -628,6 +644,15 @@
pointer-events: none;
}
/* Mobile: position at bottom */
@media (max-width: 768px) {
.pill-nav:not(.sidebar-mode) {
top: auto;
bottom: 0;
padding: 1rem 0 calc(env(safe-area-inset-bottom, 0px) + 0.75rem);
}
}
.pill-nav-container {
display: flex;
align-items: center;
@ -775,6 +800,47 @@
border: none;
}
/* Mobile: Sidebar as bottom sheet */
@media (max-width: 768px) {
.pill-nav.sidebar-mode {
top: auto;
left: 0;
right: 0;
bottom: 0;
width: 100%;
max-height: 70vh;
padding: 1.5rem 0 calc(env(safe-area-inset-bottom, 0px) + 0.75rem);
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 1.5rem 1.5rem 0 0;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
}
/* Drag handle */
.pill-nav.sidebar-mode::before {
content: '';
position: absolute;
top: 0.625rem;
left: 50%;
transform: translateX(-50%);
width: 2.5rem;
height: 0.25rem;
background: rgba(0, 0, 0, 0.2);
border-radius: 9999px;
}
:global(.dark) .pill-nav.sidebar-mode {
background: rgba(30, 30, 30, 0.95);
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
:global(.dark) .pill-nav.sidebar-mode::before {
background: rgba(255, 255, 255, 0.3);
}
}
.sidebar-container {
flex-direction: column;
align-items: stretch;
@ -785,6 +851,21 @@
height: 100%;
}
/* Mobile: Sidebar container adjustments */
@media (max-width: 768px) {
.sidebar-container {
padding: 1rem 1.5rem 1rem;
gap: 0.5rem;
height: auto;
max-height: calc(70vh - 2rem);
}
/* Hide spacer on mobile - not needed in bottom sheet */
.sidebar-container .sidebar-spacer {
display: none;
}
}
.sidebar-container .pill {
justify-content: flex-start;
width: 100%;
@ -807,7 +888,7 @@
flex: 1;
}
/* Transparent pills in sidebar mode */
/* Transparent pills in sidebar mode (desktop) */
.sidebar-container .glass-pill,
.sidebar-container :global(.pill-dropdown .trigger-button) {
background: transparent;
@ -831,6 +912,33 @@
border-color: rgba(255, 255, 255, 0.1);
}
/* Mobile: Visible pills in sidebar/bottom-sheet mode */
@media (max-width: 768px) {
.sidebar-container .glass-pill,
.sidebar-container :global(.pill-dropdown .trigger-button) {
background: rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.08);
}
.sidebar-container .glass-pill:hover,
.sidebar-container :global(.pill-dropdown .trigger-button:hover) {
background: rgba(0, 0, 0, 0.1);
border-color: rgba(0, 0, 0, 0.15);
}
:global(.dark) .sidebar-container .glass-pill,
:global(.dark) .sidebar-container :global(.pill-dropdown .trigger-button) {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.1);
}
:global(.dark) .sidebar-container .glass-pill:hover,
:global(.dark) .sidebar-container :global(.pill-dropdown .trigger-button:hover) {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.2);
}
}
/* Keep active state visible */
.sidebar-container .pill.active {
background: color-mix(
@ -949,6 +1057,17 @@
border: none;
}
/* Mobile: FAB at bottom left */
@media (max-width: 768px) {
.nav-fab {
top: auto;
bottom: 0;
left: 0;
border-radius: 0 1rem 0 0;
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 0.875rem);
}
}
/* Transitions */
.pill-nav {
transition: all 0.3s ease;