11<script setup lang="ts">
2- import { PinboardShell , languages } from ' @pinboard/ui'
2+ import { PinboardShell , PinboardComposables , languages } from ' @pinboard/ui'
33import ' @pinboard/ui/style.css'
4+ import { BottomSheet } from ' @phila/phila-ui-bottom-sheet'
5+ import { CloseButton } from ' @phila/phila-ui-button'
6+ import { onBeforeUnmount , onMounted , ref , watch } from ' vue'
7+ import { useRoute , useRouter } from ' vue-router'
48import { useI18n } from ' vue-i18n'
59import { useLocale } from ' ./composables/useLocale'
610
@@ -10,6 +14,73 @@ init()
1014
1115const feedbackHref =
1216 ' https://www.phila.gov/departments/department-of-public-health/about-us/contact-us/#send-us-a-message'
17+
18+ const infoSheetOpen = ref (false )
19+
20+ function closeInfoSheet() {
21+ infoSheetOpen .value = false
22+ dragY .value = 0
23+ }
24+
25+ /* Drag-down-to-dismiss. BottomSheet's built-in drag is snap-point based
26+ * and a single snap point ([60]) clamps it to no movement, so we layer
27+ * our own pointer tracking on top: translate the sheet to follow the
28+ * pointer, dismiss past DRAG_DISMISS_THRESHOLD on release, otherwise
29+ * spring back. Clicks (zero delta) pass through. */
30+ const DRAG_DISMISS_THRESHOLD = 160
31+ const dragY = ref (0 )
32+ const isDraggingSheet = ref (false )
33+ let dragStartY = 0
34+
35+ function onSheetPointerDown(e : PointerEvent ) {
36+ dragStartY = e .clientY
37+ dragY .value = 0
38+ isDraggingSheet .value = true
39+ document .addEventListener (' pointermove' , onSheetPointerMove )
40+ document .addEventListener (' pointerup' , onSheetPointerUp )
41+ document .addEventListener (' pointercancel' , onSheetPointerUp )
42+ }
43+
44+ function onSheetPointerMove(e : PointerEvent ) {
45+ if (! isDraggingSheet .value ) return
46+ dragY .value = Math .max (0 , e .clientY - dragStartY )
47+ }
48+
49+ function onSheetPointerUp() {
50+ if (! isDraggingSheet .value ) return
51+ isDraggingSheet .value = false
52+ document .removeEventListener (' pointermove' , onSheetPointerMove )
53+ document .removeEventListener (' pointerup' , onSheetPointerUp )
54+ document .removeEventListener (' pointercancel' , onSheetPointerUp )
55+ if (dragY .value > DRAG_DISMISS_THRESHOLD ) {
56+ closeInfoSheet ()
57+ } else {
58+ dragY .value = 0
59+ }
60+ }
61+
62+ const isMobile = PinboardComposables .useIsMobile ()
63+
64+ watch (isMobile , (mobile ) => {
65+ if (! mobile ) infoSheetOpen .value = false
66+ })
67+
68+ const route = useRoute ()
69+ const router = useRouter ()
70+
71+ onMounted (async () => {
72+ await router .isReady ()
73+
74+ if (route .path === ' /' && isMobile .value ) {
75+ infoSheetOpen .value = true
76+ }
77+ })
78+
79+ onBeforeUnmount (() => {
80+ document .removeEventListener (' pointermove' , onSheetPointerMove )
81+ document .removeEventListener (' pointerup' , onSheetPointerUp )
82+ document .removeEventListener (' pointercancel' , onSheetPointerUp )
83+ })
1384 </script >
1485
1586<template >
@@ -31,6 +102,28 @@ const feedbackHref =
31102 >
32103 <RouterView />
33104 </PinboardShell >
105+
106+ <Teleport to="body">
107+ <Transition name="scrim-fade">
108+ <div v-if =" infoSheetOpen" class =" info-sheet-scrim" @click =" closeInfoSheet" />
109+ </Transition >
110+ <BottomSheet
111+ v-if =" infoSheetOpen "
112+ v-model =" infoSheetOpen "
113+ class="info-sheet"
114+ :class =" { ' info-sheet--dragging' : isDraggingSheet } "
115+ :style =" { zIndex: 101 , ' --drag-y' : ` ${dragY }px ` } "
116+ :snap-points =" [60 ] "
117+ @pointerdown =" onSheetPointerDown "
118+ >
119+ <CloseButton class="info-sheet-close" @click =" closeInfoSheet " />
120+ <h2 class =" has-text-heading-5" >{{ t('callout.title') }}</h2 >
121+ <span class =" has-text-body-small" >
122+ {{ t('callout.message') }}
123+ <RouterLink to="/info" @click =" closeInfoSheet " >{{ t('callout.linkText') }}</RouterLink >
124+ </span >
125+ </BottomSheet >
126+ </Teleport >
34127</template >
35128
36129<style >
@@ -49,4 +142,46 @@ const feedbackHref =
49142 overflow : visible !important ;
50143 overflow-x : clip !important ;
51144}
145+
146+ .info-sheet-scrim {
147+ position : fixed ;
148+ inset : 0 ;
149+ z-index : 100 ;
150+ background : rgba (0 , 0 , 0 , 0.25 );
151+ }
152+
153+ /* Sheet sizes to its content; snap-points value is ignored visually.
154+ * --drag-y is set inline by the drag handler; transform-only transition
155+ * springs the sheet back when the user releases under threshold, while
156+ * keeping height static (animating to auto doesn't work cleanly). */
157+ .info-sheet .bottom-sheet {
158+ height : auto !important ;
159+ max-height : 90 dvh;
160+ padding : 0 var (--spacing-m ) 50px ;
161+ transform : translateY (var (--drag-y , 0px ));
162+ transition : transform 0.25s ease-out !important ;
163+ }
164+
165+ .info-sheet.info-sheet--dragging .bottom-sheet {
166+ transition : none !important ;
167+ }
168+
169+ .scrim-fade-leave-active {
170+ transition : opacity 0.25s ease-out ;
171+ pointer-events : none ;
172+ }
173+
174+ .scrim-fade-leave-to {
175+ opacity : 0 ;
176+ }
177+
178+ .info-sheet-close {
179+ position : absolute ;
180+ top : 8px ;
181+ right : 12px ;
182+ }
183+
184+ .info-sheet h2 {
185+ margin-bottom : var (--spacing-s );
186+ }
52187 </style >
0 commit comments