Skip to content

Commit 2a1b704

Browse files
committed
feat(web): adds support for swipe to close panel
1 parent 74256ee commit 2a1b704

File tree

2 files changed

+100
-54
lines changed

2 files changed

+100
-54
lines changed

src/components/AppComponent.vue

+10-54
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
11
<script setup lang="ts">
2-
import {
3-
NButton,
4-
NDialogProvider,
5-
NDrawer,
6-
NDrawerContent,
7-
NFlex,
8-
NIcon,
9-
} from 'naive-ui';
2+
import { NDialogProvider, NDrawer } from 'naive-ui';
103
import { onBeforeUnmount, onMounted, ref, Transition, watch } from 'vue';
114
import { RouterView, useRouter } from 'vue-router';
125
136
import GridBackground from '~/components/common/background/GridBackground.vue';
147
import DebugProvider from '~/components/common/debug/DebugProvider.vue';
158
import PageLoading from '~/components/common/loading/PageLoading.vue';
169
import NavbarComponent from '~/components/common/navbar/NavbarComponent.vue';
17-
import IconChevronLeft from '~/components/icons/IconChevronLeft.vue';
18-
import IconClose from '~/components/icons/IconClose.vue';
1910
import CheckinComponent from '~/components/views/checkin/CheckinComponent.vue';
11+
import PanelContent from '~/components/views/panel/PanelContent.vue';
2012
import { NavbarService } from '~/services/navbar.service';
2113
import { useAppStateStoreRefs } from '~/stores/app-state.store';
2214
import { useAuthSettingsStoreRefs } from '~/stores/settings/auth.store';
@@ -113,30 +105,14 @@ onBeforeUnmount(() => {
113105
:on-after-leave="onAfterLeave"
114106
:on-after-enter="onAfterEnter"
115107
>
116-
<NDrawerContent v-if="isAuthenticated" :native-scrollbar="false">
117-
<!-- Header -->
118-
<NFlex justify="space-between" class="panel-header">
119-
<NButton circle quaternary @click="onBack">
120-
<template #icon>
121-
<NIcon>
122-
<IconChevronLeft />
123-
</NIcon>
124-
</template>
125-
</NButton>
126-
<NButton circle quaternary @click="onClose">
127-
<template #icon>
128-
<NIcon>
129-
<IconClose />
130-
</NIcon>
131-
</template>
132-
</NButton>
133-
</NFlex>
134-
135-
<!-- Content -->
136-
<div class="panel-content">
137-
<component :is="PanelComponent ?? PageLoading" />
138-
</div>
139-
</NDrawerContent>
108+
<PanelContent
109+
v-if="isAuthenticated"
110+
:native-scrollbar="false"
111+
@on-back="onBack"
112+
@on-close="onClose"
113+
>
114+
<component :is="PanelComponent ?? PageLoading" />
115+
</PanelContent>
140116
</NDrawer>
141117
</RouterView>
142118
</aside>
@@ -215,26 +191,6 @@ onBeforeUnmount(() => {
215191
position: relative;
216192
max-height: calc(100% - #{layout.$safe-navbar-height});
217193
overflow: auto;
218-
219-
&-header {
220-
position: sticky;
221-
top: 1rem;
222-
}
223-
224-
&-content {
225-
margin-top: -1.125rem;
226-
padding: 0 3rem 1.25rem;
227-
228-
:deep(.n-skeleton) {
229-
opacity: 1;
230-
transition: opacity 0.1s ease-in 0.1s;
231-
232-
/* Adds 0.2s delay and 1s transition to loading indicator to prevent flashing */
233-
@starting-style {
234-
opacity: 0;
235-
}
236-
}
237-
}
238194
}
239195
}
240196
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<script lang="ts" setup>
2+
import { NButton, NDrawerContent, NFlex, NIcon } from 'naive-ui';
3+
4+
import { ref } from 'vue';
5+
6+
import IconChevronLeft from '~/components/icons/IconChevronLeft.vue';
7+
import IconClose from '~/components/icons/IconClose.vue';
8+
import { handleSwipeLeftRight, SwipeDirection } from '~/utils/touch.utils';
9+
10+
const emits = defineEmits<{
11+
(name: 'onBack', e: MouseEvent | TouchEvent): void;
12+
(name: 'onClose', e: MouseEvent | TouchEvent): void;
13+
}>();
14+
15+
const contentRef = ref<HTMLDivElement>();
16+
const touchStart = ref<TouchEvent>();
17+
18+
const onTouchStart = (e: TouchEvent) => {
19+
touchStart.value = e;
20+
};
21+
22+
const onTouchEnd = (e: TouchEvent) => {
23+
const _touchStart = touchStart.value?.targetTouches?.[0];
24+
const _touchEnd = e.changedTouches?.[0];
25+
if (!_touchStart) return;
26+
touchStart.value = undefined;
27+
const { clientWidth, clientHeight } = contentRef.value || {};
28+
const swipe = handleSwipeLeftRight(_touchStart, _touchEnd, {
29+
vertical: clientHeight ? Math.min(clientHeight / 2, 300) : 300,
30+
left: clientWidth ? Math.min(clientWidth / 2, 400) : 400,
31+
right: clientWidth ? Math.min(clientWidth / 2, 400) : 400,
32+
});
33+
if (swipe === SwipeDirection.Right) emits('onBack', e);
34+
};
35+
</script>
36+
37+
<template>
38+
<NDrawerContent
39+
:native-scrollbar="false"
40+
@touchstart="onTouchStart"
41+
@touchend="onTouchEnd"
42+
>
43+
<!-- Header -->
44+
<NFlex justify="space-between" class="panel-header">
45+
<NButton circle quaternary @click="e => emits('onBack', e)">
46+
<template #icon>
47+
<NIcon>
48+
<IconChevronLeft />
49+
</NIcon>
50+
</template>
51+
</NButton>
52+
<NButton circle quaternary @click="e => emits('onClose', e)">
53+
<template #icon>
54+
<NIcon>
55+
<IconClose />
56+
</NIcon>
57+
</template>
58+
</NButton>
59+
</NFlex>
60+
61+
<!-- Content -->
62+
<div ref="contentRef" class="panel-content">
63+
<slot />
64+
</div>
65+
</NDrawerContent>
66+
</template>
67+
68+
<style lang="scss" scoped>
69+
.panel {
70+
&-header {
71+
position: sticky;
72+
top: 1rem;
73+
}
74+
75+
&-content {
76+
margin-top: -1.125rem;
77+
padding: 0 3rem 1.25rem;
78+
79+
:deep(.n-skeleton) {
80+
opacity: 1;
81+
transition: opacity 0.1s ease-in 0.1s;
82+
83+
/* Adds 0.2s delay and 1s transition to loading indicator to prevent flashing */
84+
@starting-style {
85+
opacity: 0;
86+
}
87+
}
88+
}
89+
}
90+
</style>

0 commit comments

Comments
 (0)