feat(NcAppContent): Support full width lists

Signed-off-by: greta <gretadoci@gmail.com>
This commit is contained in:
greta 2024-02-02 12:58:55 +01:00 коммит произвёл Grigorii K. Shartsev
Родитель 80664bee45
Коммит a959dfffc2
3 изменённых файлов: 206 добавлений и 133 удалений

Просмотреть файл

@ -78,16 +78,18 @@ The list size must be between the min and the max width value.
<template v-if="hasList">
<!-- Mobile view does not allow resizeable panes -->
<div v-if="isMobile"
:class="showDetails ? 'app-content-wrapper--show-details' : 'app-content-wrapper--show-list'"
class="app-content-wrapper app-content-wrapper--mobile">
<NcAppDetailsToggle v-if="hasList && showDetails" @click.stop.prevent="hideDetails" />
<div v-if="isMobile || layout === 'no-split'"
class="app-content-wrapper app-content-wrapper--no-split"
:class="{
'app-content-wrapper--show-details': showDetails,
'app-content-wrapper--show-list': !showDetails,
'app-content-wrapper--mobile': isMobile,}">
<NcAppDetailsToggle v-if="showDetails" @click.stop.prevent="hideDetails" />
<slot v-if="!showDetails" name="list" />
<slot name="list" />
<slot />
<slot v-else />
</div>
<div v-else class="app-content-wrapper">
<div v-else-if="layout === 'vertical-split'" class="app-content-wrapper">
<Splitpanes class="default-theme"
@resized="handlePaneResize">
<Pane class="splitpanes__pane-list"
@ -108,9 +110,8 @@ The list size must be between the min and the max width value.
</Splitpanes>
</div>
</template>
<!-- @slot Provide the main content to the app content -->
<slot v-else />
<slot v-if="!hasList" />
</main>
</template>
@ -119,11 +120,11 @@ import NcAppDetailsToggle from './NcAppDetailsToggle.vue'
import { useIsMobile } from '../../composables/useIsMobile/index.js'
import { getBuilder } from '@nextcloud/browser-storage'
import { emit } from '@nextcloud/event-bus'
import { useSwipe } from '@vueuse/core'
import { Splitpanes, Pane } from 'splitpanes'
import 'splitpanes/dist/splitpanes.css'
import { emit } from '@nextcloud/event-bus'
const browserStorage = getBuilder('nextcloud').persist().build()
@ -139,7 +140,6 @@ export default {
Pane,
Splitpanes,
},
props: {
/**
* Allows to disable the control by swipe of the app navigation open state
@ -202,6 +202,19 @@ export default {
type: String,
default: null,
},
/**
* Content layout used when there is a list together with content:
* - `vertical-split` - a 2-column layout with list and default content separated vertically
* - `no-split` - a single column layout; List is shown when `showDetails` is `false`, otherwise the default slot content is shown with a back button to return to the list.
* On mobile screen `no-split` layout is forced.
*/
layout: {
type: String,
default: 'vertical-split',
validator(value) {
return ['no-split', 'vertical-split'].includes(value)
},
},
},
emits: [
@ -219,7 +232,7 @@ export default {
return {
contentHeight: 0,
hasList: false,
hasContent: false,
swiping: {},
listPaneSize: this.restorePaneConfig(),
}
@ -271,7 +284,7 @@ export default {
},
updated() {
this.checkListSlot()
this.checkSlots()
},
mounted() {
@ -281,7 +294,7 @@ export default {
})
}
this.checkListSlot()
this.checkSlots()
this.restorePaneConfig()
},
@ -320,11 +333,9 @@ export default {
},
// $slots is not reactive, we need to update this manually
checkListSlot() {
const hasListSlot = !!this.$slots.list
if (this.hasList !== hasListSlot) {
this.hasList = hasListSlot
}
checkSlots() {
this.hasList = !!this.$slots.list
this.hasContent = !!this.$slots.default
},
// browserStorage is not reactive, we need to update this manually
@ -370,7 +381,7 @@ export default {
}
// Mobile list/details handling
.app-content-wrapper--mobile {
.app-content-wrapper--no-split {
&.app-content-wrapper--show-list :deep() {
.app-content-list {
display: flex;
@ -431,4 +442,10 @@ export default {
}
}
}
.app-content-wrapper--show-list {
:deep(.app-content-list) {
max-width: none;
}
}
</style>

Просмотреть файл

@ -21,7 +21,11 @@
-->
<template>
<NcButton v-tooltip="title" :aria-label="title" class="app-details-toggle">
<NcButton v-tooltip="title"
type="tertiary"
:aria-label="title"
class="app-details-toggle"
:class="{ 'app-details-toggle--mobile': isMobile }">
<template #icon>
<ArrowRight :size="20" />
</template>
@ -36,6 +40,7 @@ import Tooltip from '../../directives/Tooltip/index.js'
import { emit } from '@nextcloud/event-bus'
import ArrowRight from 'vue-material-design-icons/ArrowRight.vue'
import { useIsMobile } from '../../composables/useIsMobile/index.js'
export default {
name: 'NcAppDetailsToggle',
@ -48,19 +53,30 @@ export default {
NcButton,
ArrowRight,
},
setup() {
return {
isMobile: useIsMobile(),
}
},
computed: {
title() {
return t('Go back to the list')
},
},
beforeMount() {
this.toggleAppNavigationButton(true)
watch: {
isMobile: {
immediate: true,
handler() {
this.toggleAppNavigationButton(this.isMobile)
},
},
},
beforeUnmount() {
if (this.isMobile) {
this.toggleAppNavigationButton(false)
}
},
methods: {
@ -81,7 +97,7 @@ export default {
<style lang="scss" scoped>
.app-details-toggle {
position: fixed;
position: sticky;
width: $clickable-area;
height: $clickable-area;
padding: $icon-margin;
@ -91,10 +107,19 @@ export default {
background-color: var(--color-main-background);
z-index: 2000;
top: var(--app-navigation-padding);
// Navigation Toggle button width + 2 paddings around
left: calc(var(--default-clickable-area) + var(--app-navigation-padding) * 2);
&--mobile {
// There is no NavigationToggle button
left: var(--app-navigation-padding);
}
&:active,
&:hover,
&:focus {
opacity: 1;
}
}
</style>

Просмотреть файл

@ -229,15 +229,12 @@
</template>
</NcListItem>
<NcListItem
:name="'Name of the element'"
:name="'Without subname, Name of the element'"
:bold="false"
:details="'1h'">
<template #icon>
<NcAvatar disable-menu :size="44" user="janedoe" display-name="Jane Doe" />
</template>
<template #subname>
In this slot you can put both text and other components such as icons
</template>
<template #indicator>
<!-- Color dot -->
<CheckboxBlankCircle :size="16" fill-color="#0082c9"/>
@ -260,6 +257,39 @@
```
### NcListItem one line mode
```vue
<NcListItem
:name="'This is an active element with highlighted counter'"
:bold="false"
:active="true"
:details="'1h'"
:counter-number="44"
one-line
counterType="highlighted">
<template #icon>
<NcAvatar disable-menu :size="44" user="janedoe" display-name="Jane Doe" />
</template>
<template #subname>
In this slot you can put both text and other components such as icons
</template>
<template #indicator>
<!-- Color dot -->
<CheckboxBlankCircle :size="16" fill-color="#fff" />
</template>
<template #actions>
<NcActionButton>
Button one
</NcActionButton>
<NcActionButton>
Button two
</NcActionButton>
<NcActionButton>
Button three
</NcActionButton>
</template>
</NcListItem>
```
### NcListItem compact mode
```vue
<template>
@ -345,7 +375,10 @@
v-bind="$attrs">
<div ref="list-item"
class="list-item"
:class="{ 'list-item--compact': compact }"
:class="{
'list-item--compact': compact,
'list-item--one-line': oneLine,
}"
@mouseover="handleMouseover"
@mouseleave="handleMouseleave">
<a :id="anchorId || undefined"
@ -363,46 +396,38 @@
<!-- Main content -->
<div class="list-item-content">
<div class="list-item-content__main"
:class="{ 'list-item-content__main--oneline': oneLine }">
<!-- First line, name and details -->
<div class="line-one">
<span class="line-one__name">
<div class="list-item-content__main">
<div class="list-item-content__name">
<!-- @slot Slot for the first line of the component. prop 'name' is used as a fallback is no slots are provided -->
<slot name="name">{{ name }}</slot>
</span>
<span v-if="showDetails"
class="line-one__details">
<!-- @slot This slot is used for some details in form of icon (prop `details` as a fallback) -->
<slot name="details">{{ details }}</slot>
</span>
</div>
<!-- Second line, subname and counter -->
<div class="line-two"
<div v-if="hasSubname"
class="list-item-content__subname"
:class="{'line-two--bold': bold}">
<span v-if="hasSubname" class="line-two__subname">
<!-- @slot Slot for the second line of the component -->
<slot name="subname" />
</span>
</div>
</div>
<div class="list-item-content__details">
<div v-if="showDetails" class="list-item-details__details">
<!-- @slot This slot is used for some details in form of icon (prop `details` as a fallback) -->
<slot name="details">{{ details }}</slot>
</div>
<!-- Counter and indicator -->
<span v-if="counterNumber != 0 || hasIndicator"
<div v-if="counterNumber != 0 || hasIndicator"
v-show="showAdditionalElements"
class="line-two__additional_elements">
class="list-item-details__extra">
<NcCounterBubble v-if="counterNumber != 0"
:active="isActive || active"
class="line-two__counter"
class="list-item-details__counter"
:type="counterType">
{{ counterNumber }}
</NcCounterBubble>
<span v-if="hasIndicator" class="line-two__indicator">
<span v-if="hasIndicator" class="list-item-details__indicator">
<!-- @slot This slot is used for some indicator in form of icon -->
<slot name="indicator" />
</span>
</span>
</div>
</div>
</div>
@ -560,6 +585,13 @@ export default {
type: Boolean,
default: false,
},
/**
* Show the list component layout
*/
oneLine: {
type: Boolean,
default: false,
},
},
emits: [
@ -580,10 +612,6 @@ export default {
},
computed: {
oneLine() {
return !this.hasSubname && !this.showDetails
},
showAdditionalElements() {
return !this.displayActionsOnHoverFocus || this.forceDisplayActions
},
@ -722,14 +750,34 @@ export default {
}
}
.line-one__name, .line-one__details {
.list-item-content__name,
.list-item-content__subname,
.list-item-content__details,
.list-item-details__details {
color: var(--color-primary-element-text) !important;
}
}
.list-item-content__name,
.list-item-content__subname,
.list-item-content__details,
.list-item-details__details {
white-space: nowrap;
margin: 0 auto 0 0;
overflow: hidden;
text-overflow: ellipsis;
}
}
.line-two__subname {
color: var(--color-primary-element-text) !important;
}
.list-item-content__name {
min-width: 100px;
max-width: 300px;
flex: 1 1 10%;
text-overflow: ellipsis;
}
.list-item-content__subname {
flex: 1 0;
min-width: 0;
}
// NcListItem
@ -770,12 +818,33 @@ export default {
}
}
}
.list-item-content__details {
display: flex;
flex-direction: row;
justify-content: end;
}
&--one-line {
padding: 10px;
margin: 2px;
.list-item-content__main {
display: flex;
justify-content: start;
gap: 12px;
min-width: 0;
}
.list-item-content__details {
display: flex;
flex-direction: row;
justify-content: end;
}
}
&__anchor {
display: flex;
flex: 1 0 auto;
align-items: center;
height: var(--default-clickable-area);
min-width: 0;
// This is handled by the parent container
&:focus-visible {
@ -785,12 +854,12 @@ export default {
&-content {
display: flex;
flex: 1 1 auto;
flex: 1 0;
justify-content: space-between;
padding-left: 8px;
min-width: 0;
&__main {
flex: 1 1 auto;
flex: 1 0;
width: 0;
margin: auto 0;
@ -807,62 +876,24 @@ export default {
}
}
&__extra {
margin-top: 4px;
}
}
.line-one {
display: flex;
align-items: center;
justify-content: space-between;
white-space: nowrap;
margin: 0 auto 0 0;
overflow: hidden;
&__name {
overflow: hidden;
flex-grow: 1;
cursor: pointer;
text-overflow: ellipsis;
color: var(--color-main-text);
font-weight: bold;
}
&-details {
&__details {
color: var(--color-text-maxcontrast);
margin: 0 9px;
font-weight: normal;
}
}
.line-two {
display: flex;
align-items: flex-start;
justify-content: space-between;
white-space: nowrap;
&--bold {
font-weight: bold;
}
&__subname {
overflow: hidden;
flex-grow: 1;
cursor: pointer;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--color-text-maxcontrast);
}
&__additional_elements {
&__extra {
margin: 2px 4px 0 4px;
display: flex;
align-items: center;
}
&__indicator {
margin: 0 5px;
}
}
&__extra {
margin-top: 4px;
}
}
</style>