feature(3331): made board, overview, stack and search result cards width behave more dynamic

Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
This commit is contained in:
Luutzen Dijkstra
2025-01-01 13:51:38 +01:00
committed by Luka Trovic
parent 6ace1867e1
commit 3115363c28
9 changed files with 170 additions and 139 deletions

View File

@@ -276,18 +276,15 @@ export default {
position: relative;
width: 100%;
height: 100%;
max-height: calc(100vh - 50px);
display: flex;
flex-direction: column;
}
.board {
padding-left: $board-spacing;
position: relative;
max-height: calc(100% - var(--default-clickable-area));
overflow: hidden;
overflow-x: auto;
overflow: auto;
flex-grow: 1;
scrollbar-gutter: stable;
}
/**
@@ -297,11 +294,14 @@ export default {
.smooth-dnd-container.horizontal {
display: flex;
align-items: stretch;
height: 100%;
gap: $board-gap;
padding: 0 $board-gap;
&:deep(.stack-draggable-wrapper.smooth-dnd-draggable-wrapper) {
display: flex;
height: auto;
flex: 0 1 $card-max-width;
min-width: $card-min-width;
.stack {
display: flex;
@@ -309,16 +309,10 @@ export default {
position: relative;
.smooth-dnd-container.vertical {
flex-grow: 1;
display: flex;
flex-direction: column;
// Margin left instead of padidng to avoid jumps on dropping a card
margin-left: $stack-spacing;
padding-right: $stack-spacing;
overflow-x: hidden;
overflow-y: auto;
padding-top: 15px;
margin-top: -10px;
gap: $stack-gap;
padding: 5px 0 $stack-gap;
scrollbar-gutter: stable;
}

View File

@@ -365,34 +365,32 @@ export default {
@import './../../css/variables';
.stack {
width: $stack-width + $stack-spacing * 3;
width: 100%;
}
.stack__header {
display: flex;
position: sticky;
top: 0;
height: var(--default-clickable-area);
z-index: 100;
padding-left: $card-spacing;
padding-right: $card-spacing;
margin: 6px;
margin-top: 0;
cursor: grab;
background-color: var(--color-main-background);
// Smooth fade out of the cards at the top
&:before {
content: ' ';
content: '';
display: block;
position: absolute;
width: calc(100% - 16px);
width: 100%;
height: 20px;
top: 30px;
left: 0px;
z-index: 99;
transition: top var(--animation-slow);
background-image: linear-gradient(180deg, var(--color-main-background) 3px, rgba(255, 255, 255, 0) 100%);
body.theme--dark & {
background-image: linear-gradient(180deg, var(--color-main-background) 3px, rgba(0, 0, 0, 0) 100%);
}
@@ -404,8 +402,10 @@ export default {
}
h3, form {
flex-grow: 1;
flex: 1 1 auto;
min-width: 0;
display: flex;
align-items: center;
cursor: inherit;
margin: 0;
@@ -418,9 +418,8 @@ export default {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc($stack-width - 60px);
border-radius: 3px;
padding: 4px 4px;
padding: $card-padding;
font-size: var(--default-font-size);
&:focus-visible {
@@ -430,7 +429,6 @@ export default {
}
form {
margin: -4px;
input {
font-weight: bold;
padding: 0 6px;
@@ -459,8 +457,6 @@ export default {
form {
display: flex;
margin-left: $stack-spacing;
margin-right: $stack-spacing;
width: 100%;
border: 2px solid var(--color-border-maxcontrast);
border-radius: var(--border-radius-large);
@@ -481,7 +477,6 @@ export default {
input {
border: none;
margin: 0;
padding: 4px;
}
}

View File

@@ -101,6 +101,8 @@ export default {
</script>
<style lang="scss" scoped>
@import './../../css/variables';
.badges {
display: flex;
width: 100%;
@@ -111,6 +113,7 @@ export default {
.icon-badge {
color: var(--color-text-maxcontrast);
display: flex;
align-items: center;
margin-right: 2px;
span,
@@ -125,6 +128,7 @@ export default {
flex-direction: row;
flex-wrap: wrap;
gap: 3px;
height: var(--default-clickable-area);
}
.badges .icon.due {

View File

@@ -77,9 +77,7 @@ export default {
.card-cover {
height: 90px;
display: flex;
margin-top: -4px;
margin-left: -4px;
margin-right: -4px;
margin: $card-image-margin $card-image-margin 0;
.image-wrapper {
flex: 1;

View File

@@ -6,7 +6,7 @@
<template>
<AttachmentDragAndDrop v-if="card" :card-id="card.id" class="drop-upload--card">
<div :ref="`card${card.id}`"
:class="{'compact': compactMode, 'current-card': currentCard, 'has-labels': card.labels && card.labels.length > 0, 'card__editable': canEdit, 'card__archived': card.archived, 'card__highlight': highlight}"
:class="{'compact': compactMode, 'current-card': currentCard, 'no-labels': !hasLabels, 'card__editable': canEdit, 'card__archived': card.archived, 'card__highlight': highlight}"
tag="div"
:tabindex="0"
class="card"
@@ -331,12 +331,12 @@ export default {
border-radius: var(--border-radius-large);
font-size: 100%;
background-color: var(--color-main-background);
margin-bottom: $card-spacing;
padding: var(--default-grid-baseline) $card-padding;
padding: $card-padding;
border: 2px solid var(--color-border-dark);
width: 100%;
display: flex;
flex-direction: column;
gap: $card-gap;
&:deep(*) {
cursor: pointer;
@@ -359,12 +359,10 @@ export default {
h4 {
font-weight: normal;
margin: 0;
padding: var(--default-grid-baseline);
flex-grow: 1;
font-size: 100%;
overflow: hidden;
word-wrap: break-word;
padding-left: 4px;
align-self: center;
:deep(a) {
@@ -374,9 +372,6 @@ export default {
&.editable {
span {
cursor: text;
padding-right: 8px;
padding-top: 3px;
padding-bottom: 3px;
&:focus, &:focus-visible {
outline: none;
@@ -385,6 +380,7 @@ export default {
&:focus-within {
outline: 2px solid var(--color-border-dark);
outline-offset: 4px;
border-radius: 3px;
}
}
@@ -427,8 +423,6 @@ export default {
.card-labels {
display: flex;
align-items: end;
padding-left: var(--default-grid-baseline);
padding-top: var(--default-grid-baseline);
.labels {
flex-wrap: wrap;
@@ -444,7 +438,7 @@ export default {
.card-related {
display: flex;
padding: 12px;
padding: 4px;
padding-bottom: 0px;
color: var(--color-text-maxcontrast);
@@ -469,8 +463,8 @@ export default {
height: 32px;
width: 32px;
}
&.has-labels {
padding-bottom: $card-padding;
&.no-labels {
padding-bottom: var(--default-grid-baseline);
}
.labels {
height: 6px;

View File

@@ -14,45 +14,18 @@
</div>
<div v-else-if="isValidFilter" class="overview">
<div class="dashboard-column">
<h3>{{ t('deck', 'Overdue') }}</h3>
<div v-for="card in sortCards('overdue')" :key="card.id">
<CardItem :id="card.id" />
<div v-for="columnProps in columnPropsList" class="dashboard-column" :key="columnProps.title">
<div class="dashboard-column__header">
<h3 class="dashboard-column__header-title">{{ t('deck', columnProps.title) }}</h3>
</div>
</div>
<div class="dashboard-column">
<h3>{{ t('deck', 'Today') }}</h3>
<div v-for="card in sortCards('today')" :key="card.id">
<CardItem :id="card.id" />
</div>
</div>
<div class="dashboard-column">
<h3>{{ t('deck', 'Tomorrow') }}</h3>
<div v-for="card in sortCards('tomorrow')" :key="card.id">
<CardItem :id="card.id" />
</div>
</div>
<div class="dashboard-column">
<h3>{{ t('deck', 'Next 7 days') }}</h3>
<div v-for="card in sortCards('nextSevenDays')" :key="card.id">
<CardItem :id="card.id" />
</div>
</div>
<div class="dashboard-column">
<h3>{{ t('deck', 'Later') }}</h3>
<div v-for="card in sortCards('later')" :key="card.id">
<CardItem :id="card.id" />
</div>
</div>
<div class="dashboard-column">
<h3>{{ t('deck', 'No due') }}</h3>
<div v-for="card in assignedCardsDashboard.nodue" :key="card.id">
<CardItem :id="card.id" />
<div class="dashboard-column__list">
<template v-if="columnProps.sort === false">
<CardItem :id="card.id" v-for="card in filterCards(columnProps.filter)" :key="card.id" />
</template>
<template v-else>
<CardItem :id="card.id" v-for="card in sortCards(filterCards(columnProps.filter))"
:key="card.id" />
</template>
</div>
</div>
</div>
@@ -73,6 +46,34 @@ const SUPPORTED_FILTERS = [
FILTER_UPCOMING,
]
const COLUMN_PROPS_LIST = [
{
title: 'Overdue',
filter: 'overdue',
},
{
title: 'Today',
filter: 'today',
},
{
title: 'Tomorrow',
filter: 'tomorrow',
},
{
title: 'Next 7 days',
filter: 'nextSevenDays',
},
{
title: 'Later',
filter: 'later',
},
{
title: 'No due',
filter: 'nodue',
sort: false,
},
]
export default {
name: 'Overview',
components: {
@@ -89,6 +90,7 @@ export default {
data() {
return {
loading: true,
columnPropsList: COLUMN_PROPS_LIST,
}
},
computed: {
@@ -125,16 +127,16 @@ export default {
}
this.loading = false
},
sortCards(when) {
const cards = this.assignedCardsDashboard[when]
filterCards(when) {
return this.assignedCardsDashboard[when];
},
sortCards(cards) {
if (!cards) {
return null
} else {
return cards.toSorted((current, next) => {
const currentDueDate = new Date(current.duedate)
const nextDueDate = new Date(next.duedate)
return currentDueDate - nextDueDate
})
}
@@ -151,38 +153,71 @@ export default {
position: relative;
width: 100%;
height: 100%;
max-height: calc(100vh - 50px);
display: flex;
flex-direction: column;
}
.overview {
position: relative;
height: calc(100% - var(--default-clickable-area));
overflow-x: scroll;
overflow: auto;
flex-grow: 1;
scrollbar-gutter: stable;
display: flex;
align-items: stretch;
padding-left: $board-spacing;
padding-right: $board-spacing;
gap: $board-gap;
padding: 0 $board-gap;
.dashboard-column {
display: flex;
flex-direction: column;
min-width: $stack-width;
width: $stack-width;
margin-left: $stack-spacing;
margin-right: $stack-spacing;
flex: 0 1 $card-max-width;
min-width: $card-min-width;
h3 {
font-size: var(--default-font-size);
margin: -6px;
margin-bottom: 12px;
padding: 6px 13px;
.dashboard-column__header {
display: flex;
position: sticky;
top: 0;
height: var(--default-clickable-area);
z-index: 100;
margin-top: 0;
background-color: var(--color-main-background);
border: 1px solid var(--color-main-background);
// Smooth fade out of the cards at the top
&:before {
content: '';
display: block;
position: absolute;
width: 100%;
height: 20px;
top: 30px;
left: 0px;
z-index: 99;
transition: top var(--animation-slow);
background-image: linear-gradient(180deg, var(--color-main-background) 3px, rgba(255, 255, 255, 0) 100%);
body.theme--dark & {
background-image: linear-gradient(180deg, var(--color-main-background) 3px, rgba(0, 0, 0, 0) 100%);
}
}
}
.dashboard-column__header-title {
display: flex;
align-items: center;
height: var(--default-clickable-area);
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 4px;
font-size: var(--default-font-size);
}
.dashboard-column__list {
display: flex;
flex-direction: column;
gap: $stack-gap;
padding: 5px 0 $stack-gap;
}
}
}

View File

@@ -4,20 +4,21 @@
-->
<template>
<div v-if="searchQuery!==''" class="global-search">
<h2>
<NcRichText :text="t('deck', 'Search for {searchQuery} in all boards')" :arguments="queryStringArgs" />
<div v-if="loading" class="icon-loading-small" />
</h2>
<NcActions>
<NcActionButton icon="icon-close" @click="$store.commit('setSearchQuery', '')" />
</NcActions>
<section v-if="searchQuery!==''" class="global-search">
<header class="search-header">
<h2>
<NcRichText
:text="t('deck', 'Search for {searchQuery} in all boards')"
:arguments="queryStringArgs" />
<span v-if="loading" class="icon-loading-small" />
</h2>
<NcActions>
<NcActionButton icon="icon-close" @click="$store.commit('setSearchQuery', '')" />
</NcActions>
</header>
<div class="search-wrapper">
<div v-if="loading || filteredResults.length > 0" class="search-results">
<CardItem v-for="card in filteredResults"
:id="card.id"
:key="card.id"
:standalone="true" />
<template v-if="loading || filteredResults.length > 0">
<CardItem v-for="card in filteredResults" :id="card.id" :key="card.id" :standalone="true" />
<Placeholder v-if="loading" />
<InfiniteLoading :identifier="searchQuery" @infinite="infiniteHandler">
<div slot="spinner" />
@@ -26,12 +27,12 @@
{{ t('deck', 'No results found') }}
</div>
</InfiniteLoading>
</div>
<div v-else>
</template>
<template v-else>
<p>{{ t('deck', 'No results found') }}</p>
</div>
</template>
</div>
</div>
</section>
</template>
<script>
@@ -159,7 +160,7 @@ export default {
.global-search {
width: 100%;
padding: $board-spacing + $stack-spacing;
padding: $board-gap;
padding-bottom: 0;
overflow: hidden;
min-height: 35vh;
@@ -169,17 +170,24 @@ export default {
border-top: 1px solid var(--color-border);
z-index: 1010;
position: relative;
display: flex;
flex-direction: column;
.action-item.icon-close {
position: absolute;
top: 10px;
right: 10px;
}
.search-wrapper {
overflow: scroll;
height: 100%;
position: relative;
padding: 10px;
.search-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
}
h2 {
margin: 0;
padding: var(--default-grid-baseline) var(--default-grid-baseline) $board-gap;
}
h2 > div {
@@ -189,23 +197,25 @@ export default {
margin-right: 20px;
}
}
h2:deep(span) {
background-color: var(--color-background-dark);
padding: 3px;
border-radius: var(--border-radius);
}
.search-results {
.search-wrapper {
overflow: auto;
height: 100%;
position: relative;
display: flex;
flex-wrap: wrap;
gap: $stack-gap;
& > div {
& > .drop-upload--card {
flex-grow: 0;
flex: 0 1 $card-max-width;
min-width: $card-min-width;
}
}
&:deep(.card) {
width: $stack-width;
margin-right: $stack-spacing;
}
}
</style>

View File

@@ -62,14 +62,13 @@ export default {
$clickable-area: var(--default-clickable-area);
.card--placeholder {
width: $stack-width;
margin-right: $stack-spacing;
min-width: $card-min-width;
max-width: $card-min-width;
padding: $card-padding;
transition: box-shadow 0.1s ease-in-out;
box-shadow: 0 0 2px 0 var(--color-box-shadow);
border-radius: var(--border-radius-large);
font-size: 100%;
margin-bottom: $card-spacing;
height: 100px;
}

View File

@@ -2,8 +2,10 @@
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
$card-spacing: 8px;
$card-padding: 4px;
$stack-spacing: 12px;
$stack-width: 280px;
$board-spacing: 16px;
$card-min-width: 216px;
$card-max-width: 300px;
$card-padding: calc(var(--default-grid-baseline) * 2) calc(var(--default-grid-baseline) * 2) var(--default-grid-baseline);
$card-gap: calc(var(--default-grid-baseline) * 3);
$card-image-margin: calc(var(--default-grid-baseline) * -1);
$stack-gap: calc(var(--default-grid-baseline) * 3);
$board-gap: calc(var(--default-grid-baseline) * 4);