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

Commit

Permalink
Sidebar component
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasHirt committed May 7, 2020
1 parent f125e4d commit 3f7494e
Show file tree
Hide file tree
Showing 16 changed files with 546 additions and 157 deletions.
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 application menu component in favor of the sidebar component.

https://github.com/owncloud/owncloud-design-system/pull/735
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().closed).toBeTruthy()
})
})
219 changes: 219 additions & 0 deletions src/elements/OcSidebar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
<template>
<div :class="$_ocSidebar_classes">
<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>
</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,
defult: 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("closed")
}
}
}
</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"
/>
</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"
/>
</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"
>
<template v-slot:upperContent>
<strong class="oc-light">Files app</strong>
</template>
<template v-slot:footer>
<small slot="footer" 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>
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<template>
<li :class="$_ocSidebarNavItem_class">
<component
:is="$_ocSidebarNavItem_componentType"
<router-link
:to="target"
v-on="!target ? { click: $_ocSidebarNavItem_onClick } : {}"
>
<oc-icon :name="icon" v-if="icon" />
<oc-icon :name="icon" aria-hidden="true" />
<slot name="default" />
</component>
</router-link>
<ul class="uk-nav-sub" v-if="$slots.subnav">
<slot name="subnav" />
</ul>
Expand All @@ -31,7 +29,7 @@ export default {
},
icon: {
type: String,
required: false,
required: true,
default: "",
},
isolate: {
Expand All @@ -41,9 +39,6 @@ export default {
},
},
computed: {
$_ocSidebarNavItem_componentType() {
return this.target ? "router-link" : "a"
},
$_ocSidebarNavItem_class() {
let classes = []
if (this.$slots.subnav) {
Expand All @@ -58,11 +53,6 @@ export default {
return classes
},
},
methods: {
$_ocSidebarNavItem_onClick() {
this.$emit("click")
},
},
}
</script>
<docs>
Expand Down
Loading

0 comments on commit 3f7494e

Please sign in to comment.