Skip to content
This repository has been archived by the owner on Jan 3, 2024. It is now read-only.

Sidebar component #735

Merged
merged 1 commit into from
May 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog/unreleased/created-sidebar-component
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Created sidebar component

We've created a sidebar component which is to be used as an in-app navigation and which will contain the branding.

https://github.com/owncloud/owncloud-design-system/pull/735
5 changes: 5 additions & 0 deletions changelog/unreleased/deprecated-application-menu-component
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Change: Deprecated application menu component

We've deprecated the application menu component in favor of the sidebar component.

https://github.com/owncloud/owncloud-design-system/pull/735
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Created animation section in docs

We've added an animation section in the documentation to show available animations and how to use them.

https://github.com/owncloud/owncloud-design-system/pull/735
7 changes: 7 additions & 0 deletions config/docs.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ module.exports = {
usageMode: "hide",
sectionDepth: 1,
},
{
name: "Animations",
content: "../docs/animations.md",
exampleMode: "hide",
usageMode: "hide",
sectionDepth: 1,
},
{
name: "Use of ARIA",
content: "../docs/use-of-aria.md",
Expand Down
8 changes: 8 additions & 0 deletions docs/animations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## How to use animations
To see how to use animations, please follow [transitions section of Vue.js documentation](https://vuejs.org/v2/guide/transitions.html). As a name of the transition, it is possible to use any of the following animations.

## Available animations
| Name | Description |
| -------------- | ----------- |
| fade | The element fades in |
| push-right | The element slides from the left side and pushes the element to the right. It might be necessary to include a content wrapper with fixed width inside of the element to prevent deformation of the element. |
14 changes: 13 additions & 1 deletion jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
// Transform webpack require function
require("babel-plugin-require-context-hook/register")()
require("babel-plugin-require-context-hook/register")()

import Vue from "vue"

// mock for the router-link
Vue.component("RouterLink", {
props: {
tag: { type: String, default: "a" },
},
render(createElement) {
return createElement(this.tag, {}, this.$slots.default)
},
})
83 changes: 83 additions & 0 deletions src/elements/OcSidebar.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { shallowMount } from "@vue/test-utils"
import Sidebar from "./OcSidebar.vue"

const defaultProps = {
productName: 'ownCloud',
navItems: [
{ name: 'Home', route: { path: '/' }, icon: 'home' },
{ name: 'All files', route: { path: '/files' }, icon: 'folder' },
{ name: 'Shared files', route: { path: '/shared' }, icon: 'share', active: true }
]
}

describe("OcSidebar", () => {
it('displays a logo image if its source is specified', () => {
const logoSrc = 'https://owncloud.org/wp-content/themes/owncloud/img/owncloud-org-logo.svg'
const wrapper = shallowMount(Sidebar, {
propsData: {
...defaultProps,
logoImg: logoSrc
}
})
const logoImg = wrapper.find('.oc-sidebar-logo-img')

expect(wrapper.findAll('.oc-sidebar-logo-img').length).toBe(1)
expect(logoImg.attributes('src')).toMatch(logoSrc)
expect(logoImg.attributes('alt')).toMatch('ownCloud')
expect(wrapper).toMatchSnapshot()
})

it('displays nav items', () => {
const wrapper = shallowMount(Sidebar, {
propsData: defaultProps
})

expect(wrapper.findAll('.oc-sidebar-nav-item').length).toBe(3)
expect(wrapper).toMatchSnapshot()
})

it('displays upperContent and footer slots', () => {
const wrapper = shallowMount(Sidebar, {
propsData: defaultProps,
slots: {
upperContent: '<strong class="upper-slot">Files app</strong>',
footer: '<small slot="footer" class="footer-slot">Made by ownClouders</small>'
}
})

expect(wrapper.findAll('.upper-slot').length).toBe(1)
expect(wrapper.find('.upper-slot').text()).toMatch('Files app')
expect(wrapper.findAll('.footer-slot').length).toBe(1)
expect(wrapper.find('.footer-slot').text()).toMatch('Made by ownClouders')
expect(wrapper.find('.oc-sidebar-nav').classes()).toContain('uk-margin-bottom')
expect(wrapper).toMatchSnapshot()
})

it('Sets fixed position', () => {
const wrapper = shallowMount(Sidebar, {
propsData: {
...defaultProps,
fixed: true,
closeButtonLabel: 'Close sidebar'
}
})

expect(wrapper.classes()).toContain('oc-sidebar-fixed')
expect(wrapper.findAll('.oc-sidebar-button-close').length).toBe(1)
expect(wrapper.find('.oc-sidebar-button-close').attributes('arialabel')).toMatch('Close sidebar')
expect(wrapper).toMatchSnapshot()
})

it('Emits closed event', async () => {
const wrapper = shallowMount(Sidebar, {
propsData: {
...defaultProps,
fixed: true
}
})

wrapper.find('.oc-sidebar-button-close').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.emitted().close).toBeTruthy()
})
})
224 changes: 224 additions & 0 deletions src/elements/OcSidebar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
<template>
<div :class="$_ocSidebar_classes">
<div class="oc-sidebar-content-wrapper">
<oc-button
v-if="fixed"
class="oc-sidebar-button-close"
variation="raw"
@click.native="$_ocSidebar_buttonClose_click"
:aria-label="closeButtonLabel"
>
<oc-icon name="close" aria-hidden="true" />
</oc-button>
<router-link to="/" class="oc-sidebar-logo">
<oc-img
v-if="logoImg"
key="logo-image"
:src="logoImg"
:alt="productName"
class="oc-sidebar-logo-img"
/>
<span v-else key="product-name" class="oc-sidebar-logo-text" v-text="productName" />
</router-link>
<div v-if="$slots.upperContent" class="oc-sidebar-upper-content">
<!-- @slot Content above the navigation block -->
<slot name="upperContent" />
</div>
<nav :class="[{ 'uk-margin-bottom' : $slots.footer }, 'oc-sidebar-nav']">
<ul>
<oc-sidebar-nav-item
v-for="item in navItems"
:key="item.route.path"
:icon="item.icon || item.iconMaterial"
:active="item.active"
:target="item.route.path"
class="oc-sidebar-nav-item"
>
{{ item.name }}
</oc-sidebar-nav-item>
</ul>
</nav>
<div v-if="$slots.footer" class="oc-sidebar-footer">
<!-- @slot Footer of the sidebar -->
<slot name="footer" />
</div>
</div>
</div>
</template>

<script>
import OcSidebarNavItem from "./OcSidebarNavItem.vue"
import OcImg from "./OcImage.vue"
import OcButton from "./OcButton.vue"
import OcIcon from "./OcIcon.vue"

export default {
name: "oc-sidebar",
status: "review",
released: "unreleased",

components: {
OcSidebarNavItem,
OcImg,
OcButton,
OcIcon
},

props: {
/**
* Source of the logo image
*/
logoImg: {
type: String,
required: false
},
/**
* Name of the product where the sidebar is used
*/
productName: {
type: String,
required: true
},
/**
* Navigation items of the product
*/
navItems: {
type: Array,
required: true
},
/**
* Asserts whether the sidebar's position is fixed
*/
fixed: {
type: Boolean,
required: false,
default: false
},
/**
* Accessibility label of the close button
*/
closeButtonLabel: {
type: String,
required: false,
default: "Close navigation menu"
}
},

computed: {
$_ocSidebar_classes() {
const classes = ["oc-sidebar"]

if (this.fixed) {
classes.push("oc-sidebar-fixed")
}

return classes
}
},

methods: {
$_ocSidebar_buttonClose_click() {
/**
* The user clicked on the close button
*/
this.$emit("close")
}
}
}
</script>

<docs>
Sidebar component containing branding and navigation

## Examples
```jsx
<template>
<div>
<oc-sidebar
logoImg="https://owncloud.org/wp-content/themes/owncloud/img/owncloud-org-logo.svg"
productName="ownCloud"
:navItems="navItems"
class="uk-height-1-1"
/>
</div>
</template>
<script>
export default {
computed: {
navItems() {
return [
{ name: 'Home', route: { path: '/' }, icon: 'home', active: true },
{ name: 'All files', route: { path: '/files' }, icon: 'folder' },
{ name: 'Shared files', route: { path: '/shared' }, icon: 'share' }
]
}
}
}
</script>
```

If a source of the logo image is not provided, the product name is used instead.

```jsx
<template>
<div>
<oc-sidebar
productName="ownCloud"
:navItems="navItems"
class="uk-height-1-1"
/>
</div>
</template>
<script>
export default {
computed: {
navItems() {
return [
{ name: 'Home', route: { path: '/' }, icon: 'home' },
{ name: 'All files', route: { path: '/files' }, icon: 'folder', active: true },
{ name: 'Shared files', route: { path: '/shared' }, icon: 'share' }
]
}
}
}
</script>
```

To provide additional content above the navigation or in the footer, you can use the `upperContent` and `footer` slots.
The navigation block will automatically receive bottom margin in case the `footer` slot exists.

```jsx
<template>
<div>
<oc-sidebar
logoImg="https://owncloud.org/wp-content/themes/owncloud/img/owncloud-org-logo.svg"
productName="ownCloud"
:navItems="navItems"
LukasHirt marked this conversation as resolved.
Show resolved Hide resolved
class="uk-height-1-1"
>
<template v-slot:upperContent>
<strong class="oc-light">Files app</strong>
</template>
<template v-slot:footer>
<small class="oc-light">
Made by ownClouders
</small>
</template>
</oc-sidebar>
</div>
</template>
<script>
export default {
computed: {
navItems() {
return [
{ name: 'Home', route: { path: '/' }, icon: 'home' },
{ name: 'All files', route: { path: '/files' }, icon: 'folder' },
{ name: 'Shared files', route: { path: '/shared' }, icon: 'share', active: true }
]
}
}
}
</script>
```
</docs>
Loading