1
0

fix(views): refactor filter button slot in wrapper

Before this change, the filter button on the top right was positioned using absolute positioning and plenty of tricks, which were brittle and not really maintainable. Now, the buttons are positioned using flexbox, which should make this a lot more maintainable.
This commit is contained in:
kolaente 2024-04-02 14:02:31 +02:00
parent 13cab62d14
commit 8a72fe26f8
No known key found for this signature in database
GPG Key ID: F40E70337AB24C9B
6 changed files with 275 additions and 347 deletions

View File

@ -28,26 +28,26 @@
/> />
</span> </span>
</template> </template>
<slot /> <slot/>
</BaseButton> </BaseButton>
</template> </template>
<script lang="ts"> <script lang="ts">
const BUTTON_TYPES_MAP = { const BUTTON_TYPES_MAP = {
primary: 'is-primary', primary: 'is-primary',
secondary: 'is-outlined', secondary: 'is-outlined',
tertiary: 'is-text is-inverted underline-none', tertiary: 'is-text is-inverted underline-none',
} as const } as const
export type ButtonTypes = keyof typeof BUTTON_TYPES_MAP export type ButtonTypes = keyof typeof BUTTON_TYPES_MAP
export default { name: 'XButton' } export default {name: 'XButton'}
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import {computed, useSlots} from 'vue' import {computed, useSlots} from 'vue'
import BaseButton, {type BaseButtonProps} from '@/components/base/BaseButton.vue' import BaseButton, {type BaseButtonProps} from '@/components/base/BaseButton.vue'
import type { IconProp } from '@fortawesome/fontawesome-svg-core' import type {IconProp} from '@fortawesome/fontawesome-svg-core'
// extending the props of the BaseButton // extending the props of the BaseButton
export interface ButtonProps extends /* @vue-ignore */ BaseButtonProps { export interface ButtonProps extends /* @vue-ignore */ BaseButtonProps {
@ -76,37 +76,38 @@ const showIconOnly = computed(() => icon !== '' && typeof slots.default === 'und
<style lang="scss" scoped> <style lang="scss" scoped>
.button { .button {
transition: all $transition; transition: all $transition;
border: 0; border: 0;
text-transform: uppercase; text-transform: uppercase;
font-size: 0.85rem; font-size: 0.85rem;
font-weight: bold; font-weight: bold;
height: auto; height: auto;
min-height: $button-height; min-height: $button-height;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
display: inline-flex; display: inline-flex;
white-space: var(--button-white-space); white-space: var(--button-white-space);
line-height: 1;
&:hover { &:hover {
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
} }
&.fullheight { &.fullheight {
padding-right: 7px; padding-right: 7px;
height: 100%; height: 100%;
} }
&.is-active, &.is-active,
&.is-focused, &.is-focused,
&:active, &:active,
&:focus, &:focus,
&:focus:not(:active) { &:focus:not(:active) {
box-shadow: var(--shadow-xs) !important; box-shadow: var(--shadow-xs) !important;
} }
&.is-primary.is-outlined:hover { &.is-primary.is-outlined:hover {
color: var(--white); color: var(--white);
} }
} }
.is-small { .is-small {
@ -114,6 +115,6 @@ const showIconOnly = computed(() => icon !== '' && typeof slots.default === 'und
} }
.underline-none { .underline-none {
text-decoration: none !important; text-decoration: none !important;
} }
</style> </style>

View File

@ -149,8 +149,14 @@ function getViewTitle(view: IProjectView) {
<style lang="scss" scoped> <style lang="scss" scoped>
.switch-view-container { .switch-view-container {
min-height: $switch-view-height;
margin-bottom: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
@media screen and (max-width: $tablet) { @media screen and (max-width: $tablet) {
display: flex;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
} }
@ -162,8 +168,6 @@ function getViewTitle(view: IProjectView) {
border-radius: $radius; border-radius: $radius;
font-size: .75rem; font-size: .75rem;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
height: $switch-view-height;
margin: 0 auto 1rem;
padding: .5rem; padding: .5rem;
} }

View File

@ -4,7 +4,7 @@
:project-id="filters.projectId" :project-id="filters.projectId"
:viewId :viewId
> >
<template #header> <template #default>
<card :has-content="false"> <card :has-content="false">
<div class="gantt-options"> <div class="gantt-options">
<div class="field"> <div class="field">
@ -45,9 +45,7 @@
</Fancycheckbox> </Fancycheckbox>
</div> </div>
</card> </card>
</template>
<template #default>
<div class="gantt-chart-container"> <div class="gantt-chart-container">
<card <card
:has-content="false" :has-content="false"
@ -79,7 +77,7 @@ import {useI18n} from 'vue-i18n'
import type {RouteLocationNormalized} from 'vue-router' import type {RouteLocationNormalized} from 'vue-router'
import {useBaseStore} from '@/stores/base' import {useBaseStore} from '@/stores/base'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage' import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
import Foo from '@/components/misc/flatpickr/Flatpickr.vue' import Foo from '@/components/misc/flatpickr/Flatpickr.vue'
import ProjectWrapper from '@/components/project/ProjectWrapper.vue' import ProjectWrapper from '@/components/project/ProjectWrapper.vue'

View File

@ -6,69 +6,68 @@
> >
<template #header> <template #header>
<div class="filter-container"> <div class="filter-container">
<div class="items"> <Popup>
<Popup> <template #trigger="{toggle}">
<template #trigger="{toggle}"> <x-button
<x-button icon="th"
icon="th" variant="secondary"
variant="secondary" @click.prevent.stop="toggle()"
@click.prevent.stop="toggle()" class="mr-2"
> >
{{ $t('project.table.columns') }} {{ $t('project.table.columns') }}
</x-button> </x-button>
</template> </template>
<template #content="{isOpen}"> <template #content="{isOpen}">
<card <card
class="columns-filter" class="columns-filter"
:class="{'is-open': isOpen}" :class="{'is-open': isOpen}"
> >
<Fancycheckbox v-model="activeColumns.index"> <Fancycheckbox v-model="activeColumns.index">
# #
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.done"> <Fancycheckbox v-model="activeColumns.done">
{{ $t('task.attributes.done') }} {{ $t('task.attributes.done') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.title"> <Fancycheckbox v-model="activeColumns.title">
{{ $t('task.attributes.title') }} {{ $t('task.attributes.title') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.priority"> <Fancycheckbox v-model="activeColumns.priority">
{{ $t('task.attributes.priority') }} {{ $t('task.attributes.priority') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.labels"> <Fancycheckbox v-model="activeColumns.labels">
{{ $t('task.attributes.labels') }} {{ $t('task.attributes.labels') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.assignees"> <Fancycheckbox v-model="activeColumns.assignees">
{{ $t('task.attributes.assignees') }} {{ $t('task.attributes.assignees') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.dueDate"> <Fancycheckbox v-model="activeColumns.dueDate">
{{ $t('task.attributes.dueDate') }} {{ $t('task.attributes.dueDate') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.startDate"> <Fancycheckbox v-model="activeColumns.startDate">
{{ $t('task.attributes.startDate') }} {{ $t('task.attributes.startDate') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.endDate"> <Fancycheckbox v-model="activeColumns.endDate">
{{ $t('task.attributes.endDate') }} {{ $t('task.attributes.endDate') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.percentDone"> <Fancycheckbox v-model="activeColumns.percentDone">
{{ $t('task.attributes.percentDone') }} {{ $t('task.attributes.percentDone') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.doneAt"> <Fancycheckbox v-model="activeColumns.doneAt">
{{ $t('task.attributes.doneAt') }} {{ $t('task.attributes.doneAt') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.created"> <Fancycheckbox v-model="activeColumns.created">
{{ $t('task.attributes.created') }} {{ $t('task.attributes.created') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.updated"> <Fancycheckbox v-model="activeColumns.updated">
{{ $t('task.attributes.updated') }} {{ $t('task.attributes.updated') }}
</Fancycheckbox> </Fancycheckbox>
<Fancycheckbox v-model="activeColumns.createdBy"> <Fancycheckbox v-model="activeColumns.createdBy">
{{ $t('task.attributes.createdBy') }} {{ $t('task.attributes.createdBy') }}
</Fancycheckbox> </Fancycheckbox>
</card> </card>
</template> </template>
</Popup> </Popup>
<FilterPopup v-model="params" /> <FilterPopup v-model="params"/>
</div>
</div> </div>
</template> </template>
@ -84,175 +83,175 @@
<div class="has-horizontal-overflow"> <div class="has-horizontal-overflow">
<table class="table has-actions is-hoverable is-fullwidth mb-0"> <table class="table has-actions is-hoverable is-fullwidth mb-0">
<thead> <thead>
<tr> <tr>
<th v-if="activeColumns.index"> <th v-if="activeColumns.index">
# #
<Sort <Sort
:order="sortBy.index" :order="sortBy.index"
@click="sort('index')" @click="sort('index')"
/> />
</th> </th>
<th v-if="activeColumns.done"> <th v-if="activeColumns.done">
{{ $t('task.attributes.done') }} {{ $t('task.attributes.done') }}
<Sort <Sort
:order="sortBy.done" :order="sortBy.done"
@click="sort('done')" @click="sort('done')"
/> />
</th> </th>
<th v-if="activeColumns.title"> <th v-if="activeColumns.title">
{{ $t('task.attributes.title') }} {{ $t('task.attributes.title') }}
<Sort <Sort
:order="sortBy.title" :order="sortBy.title"
@click="sort('title')" @click="sort('title')"
/> />
</th> </th>
<th v-if="activeColumns.priority"> <th v-if="activeColumns.priority">
{{ $t('task.attributes.priority') }} {{ $t('task.attributes.priority') }}
<Sort <Sort
:order="sortBy.priority" :order="sortBy.priority"
@click="sort('priority')" @click="sort('priority')"
/> />
</th> </th>
<th v-if="activeColumns.labels"> <th v-if="activeColumns.labels">
{{ $t('task.attributes.labels') }} {{ $t('task.attributes.labels') }}
</th> </th>
<th v-if="activeColumns.assignees"> <th v-if="activeColumns.assignees">
{{ $t('task.attributes.assignees') }} {{ $t('task.attributes.assignees') }}
</th> </th>
<th v-if="activeColumns.dueDate"> <th v-if="activeColumns.dueDate">
{{ $t('task.attributes.dueDate') }} {{ $t('task.attributes.dueDate') }}
<Sort <Sort
:order="sortBy.due_date" :order="sortBy.due_date"
@click="sort('due_date')" @click="sort('due_date')"
/> />
</th> </th>
<th v-if="activeColumns.startDate"> <th v-if="activeColumns.startDate">
{{ $t('task.attributes.startDate') }} {{ $t('task.attributes.startDate') }}
<Sort <Sort
:order="sortBy.start_date" :order="sortBy.start_date"
@click="sort('start_date')" @click="sort('start_date')"
/> />
</th> </th>
<th v-if="activeColumns.endDate"> <th v-if="activeColumns.endDate">
{{ $t('task.attributes.endDate') }} {{ $t('task.attributes.endDate') }}
<Sort <Sort
:order="sortBy.end_date" :order="sortBy.end_date"
@click="sort('end_date')" @click="sort('end_date')"
/> />
</th> </th>
<th v-if="activeColumns.percentDone"> <th v-if="activeColumns.percentDone">
{{ $t('task.attributes.percentDone') }} {{ $t('task.attributes.percentDone') }}
<Sort <Sort
:order="sortBy.percent_done" :order="sortBy.percent_done"
@click="sort('percent_done')" @click="sort('percent_done')"
/> />
</th> </th>
<th v-if="activeColumns.doneAt"> <th v-if="activeColumns.doneAt">
{{ $t('task.attributes.doneAt') }} {{ $t('task.attributes.doneAt') }}
<Sort <Sort
:order="sortBy.done_at" :order="sortBy.done_at"
@click="sort('done_at')" @click="sort('done_at')"
/> />
</th> </th>
<th v-if="activeColumns.created"> <th v-if="activeColumns.created">
{{ $t('task.attributes.created') }} {{ $t('task.attributes.created') }}
<Sort <Sort
:order="sortBy.created" :order="sortBy.created"
@click="sort('created')" @click="sort('created')"
/> />
</th> </th>
<th v-if="activeColumns.updated"> <th v-if="activeColumns.updated">
{{ $t('task.attributes.updated') }} {{ $t('task.attributes.updated') }}
<Sort <Sort
:order="sortBy.updated" :order="sortBy.updated"
@click="sort('updated')" @click="sort('updated')"
/> />
</th> </th>
<th v-if="activeColumns.createdBy"> <th v-if="activeColumns.createdBy">
{{ $t('task.attributes.createdBy') }} {{ $t('task.attributes.createdBy') }}
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr
v-for="t in tasks" v-for="t in tasks"
:key="t.id" :key="t.id"
> >
<td v-if="activeColumns.index"> <td v-if="activeColumns.index">
<router-link :to="taskDetailRoutes[t.id]"> <router-link :to="taskDetailRoutes[t.id]">
<template v-if="t.identifier === ''"> <template v-if="t.identifier === ''">
#{{ t.index }} #{{ t.index }}
</template> </template>
<template v-else> <template v-else>
{{ t.identifier }} {{ t.identifier }}
</template> </template>
</router-link> </router-link>
</td> </td>
<td v-if="activeColumns.done"> <td v-if="activeColumns.done">
<Done <Done
:is-done="t.done" :is-done="t.done"
variant="small" variant="small"
/>
</td>
<td v-if="activeColumns.title">
<router-link :to="taskDetailRoutes[t.id]">
{{ t.title }}
</router-link>
</td>
<td v-if="activeColumns.priority">
<PriorityLabel
:priority="t.priority"
:done="t.done"
:show-all="true"
/>
</td>
<td v-if="activeColumns.labels">
<Labels :labels="t.labels" />
</td>
<td v-if="activeColumns.assignees">
<AssigneeList
v-if="t.assignees.length > 0"
:assignees="t.assignees"
:avatar-size="28"
class="ml-1"
:inline="true"
/>
</td>
<DateTableCell
v-if="activeColumns.dueDate"
:date="t.dueDate"
/> />
<DateTableCell </td>
v-if="activeColumns.startDate" <td v-if="activeColumns.title">
:date="t.startDate" <router-link :to="taskDetailRoutes[t.id]">
{{ t.title }}
</router-link>
</td>
<td v-if="activeColumns.priority">
<PriorityLabel
:priority="t.priority"
:done="t.done"
:show-all="true"
/> />
<DateTableCell </td>
v-if="activeColumns.endDate" <td v-if="activeColumns.labels">
:date="t.endDate" <Labels :labels="t.labels"/>
</td>
<td v-if="activeColumns.assignees">
<AssigneeList
v-if="t.assignees.length > 0"
:assignees="t.assignees"
:avatar-size="28"
class="ml-1"
:inline="true"
/> />
<td v-if="activeColumns.percentDone"> </td>
{{ t.percentDone * 100 }}% <DateTableCell
</td> v-if="activeColumns.dueDate"
<DateTableCell :date="t.dueDate"
v-if="activeColumns.doneAt" />
:date="t.doneAt" <DateTableCell
v-if="activeColumns.startDate"
:date="t.startDate"
/>
<DateTableCell
v-if="activeColumns.endDate"
:date="t.endDate"
/>
<td v-if="activeColumns.percentDone">
{{ t.percentDone * 100 }}%
</td>
<DateTableCell
v-if="activeColumns.doneAt"
:date="t.doneAt"
/>
<DateTableCell
v-if="activeColumns.created"
:date="t.created"
/>
<DateTableCell
v-if="activeColumns.updated"
:date="t.updated"
/>
<td v-if="activeColumns.createdBy">
<User
:avatar-size="27"
:show-username="false"
:user="t.createdBy"
/> />
<DateTableCell </td>
v-if="activeColumns.created" </tr>
:date="t.created"
/>
<DateTableCell
v-if="activeColumns.updated"
:date="t.updated"
/>
<td v-if="activeColumns.createdBy">
<User
:avatar-size="27"
:show-username="false"
:user="t.createdBy"
/>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -397,4 +396,8 @@ const taskDetailRoutes = computed(() => Object.fromEntries(
border: none; border: none;
box-shadow: none; box-shadow: none;
} }
.filter-container :deep(.popup) {
top: 7rem;
}
</style> </style>

View File

@ -1,5 +1,4 @@
@import "tooltip"; @import "tooltip";
@import "labels"; @import "labels";
@import "project";
@import "task"; @import "task";
@import "tasks"; @import "tasks";

View File

@ -1,77 +0,0 @@
// FIXME: should be a component <FilterContainer>
// used in
// - Kanban.vue
// - Project.vue
// - Table.vue
$filter-container-top-default: -59px;
$filter-container-top-link-share-gantt: -133px;
$filter-container-top-link-share-list: -47px;
.filter-container {
text-align: right;
width: 100%;
min-width: 400px;
max-width: 180px;
position: absolute;
right: 1.5rem;
margin-top: $filter-container-top-default;
z-index: 4;
display: flex;
justify-content: flex-end;
.button:not(:last-of-type) {
margin-right: .5rem;
}
.button {
height: $switch-view-height;
}
.card {
text-align: left;
}
@media screen and (max-width: $tablet) {
position: static;
margin: 0 0 1rem 0 !important;
max-width: 100%;
min-width: auto;
.items {
justify-content: center;
}
.search {
width: 100%;
.control:first-child {
width: 100%;
}
}
}
}
.link-share-container .gantt-chart-container .filter-container,
.gantt-chart-container .filter-container {
right: 0;
margin-top: calc(#{$filter-container-top-link-share-gantt - 2} - 7rem);
}
.link-share-container .gantt-chart-container .filter-container {
margin-top: calc(#{$filter-container-top-link-share-gantt} - 5rem);
}
.link-share-container .list-view .filter-container {
margin-top: $filter-container-top-link-share-list - 10px;
}
.link-share-container.project\.table-view,
.link-share-container.project\.list-view {
.filter-container {
right: 9rem;
margin-top: $filter-container-top-default;
}
}