feat(NcAppContent): Support full width lists
Signed-off-by: greta <gretadoci@gmail.com>
This commit is contained in:
Родитель
80664bee45
Коммит
a959dfffc2
|
@ -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>
|
||||
|
|
Загрузка…
Ссылка в новой задаче