feat: sticky action buttons (#2622)
continuous-integration/drone/push Build is passing Details

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: #2622
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
Dominik Pschenitschni 2022-11-04 13:49:28 +00:00 committed by konrad
parent f7728e5384
commit f4bc2b94f0
3 changed files with 59 additions and 26 deletions

View File

@ -99,6 +99,9 @@ watchEffect(() => {
</script>
<style lang="scss" scoped>
$modal-margin: 4rem;
$modal-width: 1024px;
.modal-mask {
position: fixed;
z-index: 4000;
@ -147,16 +150,16 @@ watchEffect(() => {
// scrolling-content
// used e.g. for <TaskDetailViewModal>
.scrolling .modal-content {
max-width: 1024px;
max-width: $modal-width;
width: 100%;
margin: 4rem auto;
margin: $modal-margin auto;
max-height: none; // reset bulma
overflow: visible; // reset bulma
@media screen and (min-width: $tablet) {
max-height: none; // reset bulma
margin: 4rem auto; // reset bulma
margin: $modal-margin auto; // reset bulma
width: 100%;
}
@ -189,14 +192,23 @@ watchEffect(() => {
}
.close {
$close-button-min-space: 84px;
$close-button-padding: 26px;
position: fixed;
top: 5px;
right: 26px;
color: var(--white);
right: $close-button-padding;
color: var(--grey-900);
font-size: 2rem;
@media screen and (max-width: $desktop) {
color: var(--grey-900);
@media screen and (min-width: $desktop) and (max-width: calc(#{$desktop } + #{$close-button-min-space})) {
top: calc(5px + $modal-margin);
right: 50%;
// we align the close button to the modal until there is enough space outside for it
transform: translateX(calc((#{$modal-width} / 2) - #{$close-button-padding}));
}
// we can only use light color when there is enough space for the close button next to the modal
@media screen and (min-width: calc(#{$desktop } + #{$close-button-min-space})) {
color: var(--white);
}
}
</style>

View File

@ -19,7 +19,7 @@ export function useRouteWithModal() {
return
}
// logic from vue-router
// this is adapted from vue-router
// https://github.com/vuejs/vue-router-next/blob/798cab0d1e21f9b4d45a2bd12b840d2c7415f38a/src/RouterView.ts#L125
const routePropsOption = route.matched[0]?.props.default
const routeProps = routePropsOption
@ -28,7 +28,9 @@ export function useRouteWithModal() {
: typeof routePropsOption === 'function'
? routePropsOption(route)
: routePropsOption
: null
: {}
routeProps.backdropView = backdropView.value
const component = route.matched[0]?.components?.default

View File

@ -1,5 +1,12 @@
<template>
<div :class="{ 'is-loading': taskService.loading, 'visible': visible}" class="loader-container task-view-container">
<div
class="loader-container task-view-container"
:class="{
'is-loading': taskService.loading,
'visible': visible,
'is-modal': isModal,
}"
>
<div class="task-view">
<Heading v-model:task="task" :can-write="canWrite" ref="heading"/>
<h6 class="subtitle" v-if="parent && parent.namespace && parent.list">
@ -267,15 +274,7 @@
<!-- Comments -->
<comments :can-write="canWrite" :task-id="taskId"/>
</div>
<div class="column is-one-third action-buttons d-print-none" v-if="canWrite || shouldShowClosePopup">
<BaseButton
v-if="shouldShowClosePopup"
@click="$router.back()"
class="is-fullwidth is-block has-text-centered mb-4 has-text-primary"
>
<icon icon="arrow-left"/>
{{ $t('task.detail.closePopup') }}
</BaseButton>
<div class="column is-one-third action-buttons d-print-none" v-if="canWrite || isModal">
<template v-if="canWrite">
<x-button
:class="{'is-success': !task.done}"
@ -419,7 +418,7 @@
</div>
</div>
<!-- Created / Updated [by] -->
<created-updated :task="task" v-if="!canWrite && !shouldShowClosePopup"/>
<created-updated :task="task" v-if="!canWrite && !isModal"/>
</div>
<modal
@ -439,7 +438,7 @@
<script lang="ts" setup>
import {ref, reactive, toRef, shallowReactive, computed, watch, watchEffect, nextTick, type PropType} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import {useRouter, type RouteLocation} from 'vue-router'
import {useI18n} from 'vue-i18n'
import {unrefElement} from '@vueuse/core'
import cloneDeep from 'lodash.clonedeep'
@ -494,11 +493,13 @@ const props = defineProps({
type: Number as PropType<ITask['id']>,
required: true,
},
backdropView: {
type: String as PropType<RouteLocation['fullPath']>,
},
})
defineEmits(['close'])
const route = useRoute()
const router = useRouter()
const {t} = useI18n({useScope: 'global'})
@ -567,8 +568,7 @@ const color = computed(() => {
const hasAttachments = computed(() => attachmentStore.attachments.length > 0)
// HACK:
const shouldShowClosePopup = computed(() => (route.name as string).includes('kanban'))
const isModal = computed(() => Boolean(props.backdropView))
function attachmentUpload(file: File, onSuccess?: (url: string) => void) {
return uploadFile(taskId.value, file, onSuccess)
@ -799,6 +799,7 @@ $flash-background-duration: 750ms;
@media screen and (max-width: $desktop) {
padding-bottom: 0;
}
}
.subtitle {
color: var(--grey-500);
@ -965,6 +966,12 @@ $flash-background-duration: 750ms;
}
.action-buttons {
@media screen and (min-width: $tablet) {
position: sticky;
top: $navbar-height + 1.5rem;
align-self: flex-start;
}
.button {
width: 100%;
margin-bottom: .5rem;
@ -976,6 +983,18 @@ $flash-background-duration: 750ms;
}
}
.is-modal .action-buttons {
// we need same top margin for the modal close button
@media screen and (min-width: $tablet) {
top: 6.5rem;
}
// this is the moment when the fixed close button is outside the modal
// => we can fill up the space again
@media screen and (min-width: calc(#{$desktop} + 84px)) {
top: 0;
}
}
.created {
font-size: .75rem;
color: var(--grey-500);
@ -985,7 +1004,7 @@ $flash-background-duration: 750ms;
.checklist-summary {
padding-left: .25rem;
}
}
.task-view-container {
padding-bottom: 1rem;