Collections module additions (#201)

* Render add new link, only render delete on isnew is false

* Add header actions buttons based on state

* Add header buttons and breadcrumbs

* Style tweaks

* Add navigation guard for single collections

* Add delete button logic

* Add ability to delete items on browse

* Add select mode to tabular layout

* Add saving / deleting logic to detail view

* remove tests (temporarily)

* Remove empty tests temporarily

* Add pagination to tabular layout if collection is large

* Add server sort

* wip table tweaks

* show shadow only on scroll, fix padding on top of private view.

* Update table

* fix header hiding the scrollbar

* Fix rAF leak

* Make pagination sticky

* fix double scroll bug

* add selfScroll prop to private view

* Last try

* Lower the default limit

* Fix tests for table / private / public view

* finish header

* remove unnessesary code

* Fix debug overflow + icon alignment

* Fix breadcrumbs

* Fix item fetching

* browse view now collapses on scroll

* Add drawer-button component

* Fix styling of drawer-button drawer-detail

* Revert "browse view now collapses on scroll"

This reverts commit a8534484d496deef01e399574126f7ba877e098c.

* Final commit for the night

* Add scroll solution for header overflow

* Render table header over shadow

* Add useScrollDistance compositoin

* Add readme for scroll distance

* Restructure header bar using sticky + margin / add shadow

* Tweak box shadow to not show up at top on scroll up

* Fix tests

Co-authored-by: Nitwel <nitwel@arcor.de>
This commit is contained in:
Rijk van Zanten
2020-03-20 17:05:55 -04:00
committed by GitHub
parent a141e3a6ea
commit 3ab97ca2b2
51 changed files with 1213 additions and 431 deletions

View File

@@ -0,0 +1,31 @@
import markdown from './readme.md';
import { defineComponent, provide, toRefs } from '@vue/composition-api';
import { withKnobs, boolean } from '@storybook/addon-knobs';
import withPadding from '../../../../../.storybook/decorators/with-padding';
import withAltColors from '../../../../../.storybook/decorators/with-alt-colors';
import DrawerButton from './drawer-button.vue';
export default {
title: 'Views / Private / Components / Drawer Button',
parameters: {
notes: markdown
},
decorators: [withKnobs, withAltColors, withPadding]
};
export const basic = () =>
defineComponent({
components: { DrawerButton },
props: {
drawerOpen: {
default: boolean('Drawer Open', true)
}
},
setup(props) {
provide('drawer-open', toRefs(props).drawerOpen);
},
template: `
<drawer-button icon="info">Close Drawer</drawer-button>
`
});

View File

@@ -0,0 +1,21 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueCompositionAPI from '@vue/composition-api';
import VIcon from '@/components/v-icon/';
import DrawerButton from './drawer-button.vue';
const localVue = createLocalVue();
localVue.use(VueCompositionAPI);
localVue.component('v-icon', VIcon);
describe('Views / Private / Components / Drawer Button', () => {
it('Does not render the title when the drawer is closed', () => {
const component = shallowMount(DrawerButton, {
localVue,
provide: {
'drawer-open': false
}
});
expect(component.find('.title').exists()).toBe(false);
});
});

View File

@@ -0,0 +1,75 @@
<template>
<component
class="drawer-button"
:is="to ? 'router-link' : 'button'"
@click="$emit('click', $event)"
>
<div class="icon">
<v-icon :name="icon" />
</div>
<div class="title" v-if="drawerOpen">
<slot />
</div>
</component>
</template>
<script lang="ts">
import { defineComponent, inject, ref } from '@vue/composition-api';
export default defineComponent({
props: {
to: {
type: String,
default: null
},
icon: {
type: String,
default: 'box'
}
},
setup() {
const drawerOpen = inject('drawer-open', ref(false));
return { drawerOpen };
}
});
</script>
<style lang="scss" scoped>
.drawer-button {
position: relative;
width: 100%;
height: 64px;
color: var(--foreground-color);
transition: background-color var(--fast) var(--transition);
&:hover {
background-color: var(--background-color-hover);
}
.icon {
display: flex;
align-items: center;
justify-content: center;
width: 64px;
height: 100%;
}
.title {
position: absolute;
top: 50%;
left: 52px;
overflow: hidden;
white-space: nowrap;
transform: translateY(-50%);
}
.fade-enter-active,
.fade-leave-active {
transition: opacity var(--medium) var(--transition);
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
}
</style>

View File

@@ -0,0 +1,4 @@
import DrawerButton from './drawer-button.vue';
export { DrawerButton };
export default DrawerButton;

View File

@@ -0,0 +1,28 @@
# Drawer Button
Looks the same as a drawer detail, but can be used as a button / router link.
## Usage
```html
<drawer-button icon="info">Close Sidebar</drawer-button>
```
## Props
| Prop | Description | Default |
|---------|----------------------------------------------------|---------|
| `icon`* | What icon to render on the left of the button | `box` |
| `to` | router-link to prop. Turns button into router-link | `null` |
## Events
| Event | Description | Value |
|---------|----------------------------------|--------------|
| `click` | When the button has been clicked | `MouseEvent` |
## Slots
| Slot | Description | Data |
|-----------|---------------------|------|
| _default_ | Title of the button | -- |
## CSS Variables
n/a

View File

@@ -44,9 +44,7 @@ export default defineComponent({
<style lang="scss" scoped>
.drawer-detail {
.toggle {
display: flex;
align-items: center;
justify-content: flex-start;
position: relative;
width: 100%;
height: 64px;
color: var(--foreground-color);
@@ -65,10 +63,17 @@ export default defineComponent({
display: flex;
align-items: center;
justify-content: center;
width: 64px;
height: 100%;
margin-right: 12px;
padding: 20px;
padding-right: 0;
}
.title {
position: absolute;
top: 50%;
left: 52px;
overflow: hidden;
white-space: nowrap;
transform: translateY(-50%);
}
.content {

View File

@@ -90,7 +90,7 @@ export default defineComponent({
.action-buttons {
> * {
display: block;
display: inherit;
}
}
}
@@ -98,7 +98,7 @@ export default defineComponent({
@include breakpoint(medium) {
.action-buttons {
> * {
display: block !important;
display: inherit !important;
}
}
}

View File

@@ -11,6 +11,15 @@ localVue.component('v-button', VButton);
localVue.component('v-icon', VIcon);
describe('Views / Private / Header Bar', () => {
const observeMock = {
observe: () => null,
disconnect: () => null // maybe not needed
};
beforeEach(() => {
(window as any).IntersectionObserver = jest.fn(() => observeMock);
});
it('Emits toggle event when toggle buttons are clicked', () => {
const component = mount(HeaderBar, {
localVue,

View File

@@ -1,5 +1,5 @@
<template>
<header class="header-bar" :class="{ dense }">
<header class="header-bar" ref="headerEl" :class="{ shadow: showShadow }">
<v-button secondary class="nav-toggle" icon rounded @click="$emit('toggle:nav')">
<v-icon name="menu" />
</v-button>
@@ -30,7 +30,7 @@
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import { defineComponent, ref, onMounted, onUnmounted } from '@vue/composition-api';
import HeaderBarActions from '../header-bar-actions';
export default defineComponent({
@@ -39,14 +39,29 @@ export default defineComponent({
title: {
type: String,
required: true
},
dense: {
type: Boolean,
default: false
}
},
setup() {
return {};
const headerEl = ref<Element>();
const showShadow = ref(false);
const observer = new IntersectionObserver(
([e]) => {
showShadow.value = e.intersectionRatio < 1;
},
{ threshold: [1] }
);
onMounted(() => {
observer.observe(headerEl.value as HTMLElement);
});
onUnmounted(() => {
observer.disconnect();
});
return { headerEl, showShadow };
}
});
</script>
@@ -56,19 +71,23 @@ export default defineComponent({
@import '@/styles/mixins/type-styles';
.header-bar {
position: relative;
position: sticky;
top: -1px;
left: 0;
z-index: 5;
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 64px;
height: 65px;
margin: 24px 0;
padding: 0 12px;
background-color: var(--background-color);
transition: height var(--medium) var(--transition);
box-shadow: 0;
transition: box-shadow var(--medium) var(--transition);
&.dense {
height: 64px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2);
&.shadow {
box-shadow: 0 4px 7px -4px rgba(0, 0, 0, 0.2);
}
.nav-toggle {
@@ -113,7 +132,6 @@ export default defineComponent({
}
@include breakpoint(small) {
height: 112px;
padding: 0 32px;
}
}

View File

@@ -15,10 +15,9 @@
</div>
</div>
</aside>
<div class="content">
<div class="content" ref="contentEl">
<header-bar
:title="title"
:dense="_headerDense"
@toggle:drawer="drawerOpen = !drawerOpen"
@toggle:nav="navOpen = !navOpen"
>
@@ -29,7 +28,8 @@
<slot :name="scopedSlotName" v-bind="slotData" />
</template>
</header-bar>
<main ref="mainContent" @scroll="onMainScroll" :class="{ 'header-dense': headerDense }">
<main>
<slot />
</main>
</div>
@@ -40,6 +40,10 @@
:class="{ 'is-open': drawerOpen }"
@click="drawerOpen = true"
>
<drawer-button class="drawer-toggle" @click.stop="drawerOpen = !drawerOpen" icon="info">
Close Drawer
</drawer-button>
<drawer-detail-group :drawer-open="drawerOpen">
<slot name="drawer" />
</drawer-detail-group>
@@ -51,68 +55,36 @@
</template>
<script lang="ts">
import { defineComponent, ref, provide, computed } from '@vue/composition-api';
import { defineComponent, ref, provide } from '@vue/composition-api';
import ModuleBar from './components/module-bar/';
import DrawerDetailGroup from './components/drawer-detail-group/';
import HeaderBar from './components/header-bar';
import ProjectChooser from './components/project-chooser';
import { throttle } from 'lodash';
import DrawerButton from './components/drawer-button/';
export default defineComponent({
components: {
ModuleBar,
DrawerDetailGroup,
HeaderBar,
ProjectChooser
ProjectChooser,
DrawerButton
},
props: {
title: {
type: String,
required: true
},
headerDense: {
type: Boolean,
default: false
},
headerDenseAuto: {
type: Boolean,
default: true
}
},
setup(props) {
const mainElement = ref<Element>(null);
setup() {
const navOpen = ref(false);
const drawerOpen = ref(false);
provide('drawer-open', drawerOpen);
const headerDenseAutoState = ref(false);
const _headerDense = computed<boolean>(() => {
if (props.headerDenseAuto === true) {
return headerDenseAutoState.value;
} else {
return props.headerDense;
}
});
const onMainScroll = throttle(event => {
const { scrollTop } = event.target;
if (scrollTop <= 5 && headerDenseAutoState.value === true) {
headerDenseAutoState.value = false;
} else if (scrollTop > 5 && headerDenseAutoState.value === false) {
headerDenseAutoState.value = true;
}
}, 50);
return {
navOpen,
drawerOpen,
_headerDense,
mainElement,
onMainScroll
drawerOpen
};
}
});
@@ -122,8 +94,6 @@ export default defineComponent({
@import '@/styles/mixins/breakpoint';
.private-view {
--private-view-content-padding: 12px;
display: flex;
width: 100%;
height: 100%;
@@ -182,30 +152,21 @@ export default defineComponent({
.content {
position: relative;
flex-grow: 1;
width: 100%;
height: 100%;
overflow: hidden;
.header-bar {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
overflow: auto;
main {
height: 100%;
padding: var(--private-view-content-padding);
padding-top: 114px;
overflow: auto;
display: contents;
}
// Offset for partially visible drawer
@include breakpoint(medium) {
padding-right: 64px;
margin-right: 64px;
}
@include breakpoint(large) {
padding-right: 0;
margin-right: 0;
}
}

View File

@@ -62,7 +62,9 @@ describe('Views / Public', () => {
describe('Background', () => {
it('Defaults background art to color when current project key is unknown', () => {
expect((component.vm as any).artStyles).toEqual({
background: '#263238'
background: '#263238',
backgroundPosition: 'center center',
backgroundSize: 'cover'
});
});
@@ -81,7 +83,9 @@ describe('Views / Public', () => {
await component.vm.$nextTick();
expect((component.vm as any).artStyles).toEqual({
background: '#4CAF50'
background: '#4CAF50',
backgroundPosition: 'center center',
backgroundSize: 'cover'
});
});
@@ -99,7 +103,9 @@ describe('Views / Public', () => {
store.state.currentProjectKey = 'my-project';
expect((component.vm as any).artStyles).toEqual({
background: '#263238'
background: '#263238',
backgroundPosition: 'center center',
backgroundSize: 'cover'
});
});
@@ -107,7 +113,9 @@ describe('Views / Public', () => {
store.state.projects = [mockProject];
store.state.currentProjectKey = 'my-project';
expect((component.vm as any).artStyles).toEqual({
background: `url(${mockProject.api.project_background?.full_url})`
background: `url(${mockProject.api.project_background?.full_url})`,
backgroundPosition: 'center center',
backgroundSize: 'cover'
});
});
});

View File

@@ -56,7 +56,9 @@ export default defineComponent({
});
const artStyles = computed(() => ({
background: backgroundStyles.value
background: backgroundStyles.value,
backgroundSize: 'cover',
backgroundPosition: 'center center'
}));
return { version, artStyles };
@@ -98,6 +100,8 @@ export default defineComponent({
display: none;
flex-grow: 1;
height: 100%;
background-position: center center;
background-size: cover;
@include breakpoint(small) {
display: block;