archy/neode-ui/src/composables/useMobileBackButton.ts
Dorian 0bc7251e22 feat: add container security hardening and Fedimint setup wizard
Add --cap-drop=ALL, --security-opt=no-new-privileges:true to all
non-privileged containers. Per-app capability grants for apps needing
CHOWN/SETUID/SETGID. Read-only root filesystem with tmpfs for
compatible apps (searxng, grafana, uptime-kuma, filebrowser,
photoprism, vaultwarden). Add Fedimint "Create a Community" goal
with 4-step wizard. Fix deploy script cp -rf for audio directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 08:24:56 +00:00

104 lines
2.9 KiB
TypeScript

import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
/**
* Composable for mobile back button positioning
* Ensures back buttons are always 8px above the mobile tab bar
* Uses ResizeObserver to reactively update when tab bar height changes
*/
export function useMobileBackButton() {
const tabBarHeight = ref<number>(72) // Default fallback height
// Computed property for bottom position - always 16px above tab bar
const bottomPosition = computed(() => {
return `${tabBarHeight.value + 8}px`
})
// Computed property for Tailwind class (for use in class bindings)
const bottomClass = computed(() => {
// Use Tailwind arbitrary value with the computed height
return `bottom-[${tabBarHeight.value + 8}px]`
})
let resizeObserver: ResizeObserver | null = null
let mutationObserver: MutationObserver | null = null
let intervalId: ReturnType<typeof setInterval> | null = null
function updateTabBarHeight() {
if (typeof window === 'undefined') return
// Try to find the mobile tab bar element
const tabBar = document.querySelector('[data-mobile-tab-bar]') as HTMLElement
if (tabBar && tabBar.offsetHeight > 0) {
tabBarHeight.value = tabBar.offsetHeight
return
}
// Fallback: read from CSS variable if available
const cssVar = getComputedStyle(document.documentElement)
.getPropertyValue('--mobile-tab-bar-height')
.trim()
if (cssVar) {
const height = parseFloat(cssVar)
if (!isNaN(height) && height > 0) {
tabBarHeight.value = height
return
}
}
// Final fallback: keep current value (don't reset to 0)
}
onMounted(() => {
// Initial update
updateTabBarHeight()
// Watch for CSS variable changes
mutationObserver = new MutationObserver(() => {
updateTabBarHeight()
})
mutationObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['style'],
})
// Watch for tab bar size changes
const tabBar = document.querySelector('[data-mobile-tab-bar]') as HTMLElement
if (tabBar && 'ResizeObserver' in window) {
resizeObserver = new ResizeObserver(() => {
updateTabBarHeight()
})
resizeObserver.observe(tabBar)
}
// Also listen to window resize as fallback
window.addEventListener('resize', updateTabBarHeight)
// Periodic check to ensure we're always in sync (safety net)
intervalId = setInterval(() => {
updateTabBarHeight()
}, 1000)
})
onBeforeUnmount(() => {
if (mutationObserver) {
mutationObserver.disconnect()
}
if (resizeObserver) {
resizeObserver.disconnect()
}
if (intervalId) {
clearInterval(intervalId)
}
window.removeEventListener('resize', updateTabBarHeight)
})
return {
bottomPosition,
bottomClass,
tabBarHeight,
}
}