fix(events): pin the sticky buy bar flush above the nav (--bottom-nav-height) #150
4 changed files with 38 additions and 10 deletions
|
|
@ -96,6 +96,10 @@
|
||||||
/* Default palette: Catppuccin (Latte for light, Mocha for dark).
|
/* Default palette: Catppuccin (Latte for light, Mocha for dark).
|
||||||
Other palettes are scoped via :root[data-theme="<name>"] in themes/*.css. */
|
Other palettes are scoped via :root[data-theme="<name>"] in themes/*.css. */
|
||||||
:root {
|
:root {
|
||||||
|
/* Rendered height of the fixed BottomNav (h-14 + safe-area + border), kept
|
||||||
|
current by BottomNav.vue. Pin anything that must sit flush above the nav
|
||||||
|
with `bottom: var(--bottom-nav-height)`; 0 when no nav is mounted. */
|
||||||
|
--bottom-nav-height: 0px;
|
||||||
--background: oklch(0.9578 0.0058 264.5321);
|
--background: oklch(0.9578 0.0058 264.5321);
|
||||||
--foreground: oklch(0.4355 0.0430 279.3250);
|
--foreground: oklch(0.4355 0.0430 279.3250);
|
||||||
--card: oklch(1.0000 0 0);
|
--card: oklch(1.0000 0 0);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import type { Component } from 'vue'
|
import type { Component } from 'vue'
|
||||||
|
import { useResizeObserver } from '@vueuse/core'
|
||||||
|
|
||||||
export interface BottomTab {
|
export interface BottomTab {
|
||||||
/** Translated label shown under the icon. */
|
/** Translated label shown under the icon. */
|
||||||
|
|
@ -33,6 +35,22 @@ interface Props {
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
// Publish the nav's rendered height (h-14 + safe-area padding + border,
|
||||||
|
// whatever the device makes of it) as `--bottom-nav-height` on <html>, so
|
||||||
|
// anything that needs to sit flush above the nav — sticky action bars,
|
||||||
|
// toasts — can use `bottom: var(--bottom-nav-height)` instead of
|
||||||
|
// re-deriving the CSS and drifting when insets or font scaling differ.
|
||||||
|
const navRef = ref<HTMLElement | null>(null)
|
||||||
|
function publishHeight() {
|
||||||
|
const h = navRef.value?.getBoundingClientRect().height ?? 0
|
||||||
|
document.documentElement.style.setProperty('--bottom-nav-height', `${Math.round(h)}px`)
|
||||||
|
}
|
||||||
|
useResizeObserver(navRef, publishHeight)
|
||||||
|
onMounted(publishHeight)
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
document.documentElement.style.setProperty('--bottom-nav-height', '0px')
|
||||||
|
})
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
function onTabClick(tab: BottomTab) {
|
function onTabClick(tab: BottomTab) {
|
||||||
|
|
@ -51,6 +69,7 @@ function isTabActive(tab: BottomTab): boolean {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nav
|
<nav
|
||||||
|
ref="navRef"
|
||||||
class="fixed bottom-0 left-0 right-0 z-50 border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
|
class="fixed bottom-0 left-0 right-0 z-50 border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
|
||||||
style="padding-bottom: env(safe-area-inset-bottom)"
|
style="padding-bottom: env(safe-area-inset-bottom)"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -451,8 +451,13 @@ function goToMyTickets() {
|
||||||
@update:is-open="showReturnDialog = $event"
|
@update:is-open="showReturnDialog = $event"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Sticky buy bar — see showStickyCta. Sits above the bottom nav
|
<!-- Sticky buy bar — see showStickyCta. Pinned flush above the
|
||||||
(h-14 + safe-area inset) and mirrors the inline CTA. -->
|
bottom nav via the height it publishes (--bottom-nav-height,
|
||||||
|
BottomNav.vue) and mirrors the inline CTA. -->
|
||||||
|
<!-- Teleported to <body>: inside the page the bar would inherit the
|
||||||
|
parent's space-y margin (margins offset fixed boxes too) and
|
||||||
|
float 24px above the nav. -->
|
||||||
|
<Teleport to="body">
|
||||||
<Transition
|
<Transition
|
||||||
enter-active-class="transition duration-200 ease-out"
|
enter-active-class="transition duration-200 ease-out"
|
||||||
enter-from-class="translate-y-4 opacity-0"
|
enter-from-class="translate-y-4 opacity-0"
|
||||||
|
|
@ -460,9 +465,9 @@ function goToMyTickets() {
|
||||||
leave-to-class="translate-y-4 opacity-0"
|
leave-to-class="translate-y-4 opacity-0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="showStickyCta"
|
v-if="showStickyCta && event"
|
||||||
class="fixed left-0 right-0 z-40 border-t border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80"
|
class="fixed inset-x-0 z-40 border-t border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80"
|
||||||
style="bottom: calc(3.5rem + env(safe-area-inset-bottom))"
|
style="bottom: var(--bottom-nav-height)"
|
||||||
role="region"
|
role="region"
|
||||||
:aria-label="t('events.detail.tickets')"
|
:aria-label="t('events.detail.tickets')"
|
||||||
>
|
>
|
||||||
|
|
@ -497,6 +502,7 @@ function goToMyTickets() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
</Teleport>
|
||||||
|
|
||||||
<!-- External references -->
|
<!-- External references -->
|
||||||
<div v-if="event.tags.length > 0" class="space-y-2">
|
<div v-if="event.tags.length > 0" class="space-y-2">
|
||||||
|
|
|
||||||
|
|
@ -198,13 +198,12 @@ function back() {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Sticky bottom bar with qty stepper + add-to-cart.
|
<!-- Sticky bottom bar with qty stepper + add-to-cart.
|
||||||
Sits flush above BottomNav (h-14 + safe-area inset) on every
|
Sits flush above BottomNav on every breakpoint via the height
|
||||||
breakpoint. Previously `bottom-16` left an 8px gap on mobile
|
the nav publishes (--bottom-nav-height, BottomNav.vue) — no
|
||||||
(BottomNav is h-14 = 56px, not 64px), and `sm:bottom-0` made
|
hand-derived h-14 + safe-area arithmetic to drift. -->
|
||||||
it overlap the nav on desktop. -->
|
|
||||||
<div
|
<div
|
||||||
class="fixed inset-x-0 z-20 border-t border-border bg-background/95 px-4 py-3 backdrop-blur"
|
class="fixed inset-x-0 z-20 border-t border-border bg-background/95 px-4 py-3 backdrop-blur"
|
||||||
style="bottom: calc(3.5rem + env(safe-area-inset-bottom))"
|
style="bottom: var(--bottom-nav-height)"
|
||||||
>
|
>
|
||||||
<div class="container mx-auto flex max-w-2xl items-center gap-3">
|
<div class="container mx-auto flex max-w-2xl items-center gap-3">
|
||||||
<div class="inline-flex items-center rounded-full border border-border bg-card">
|
<div class="inline-flex items-center rounded-full border border-border bg-card">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue