List group (#173)

* mvp list groups

* list group updates

* updated readme and list group to use css vars and icons

* added supgroups to story

* add list group test

* Expand Transition / Transitions Folder (#187)

* add expand transition to components folder

* expand readme

* test sorta

* test is dumb

* dummy component to test

* oops

* Add tests for capitalize first

* Rename v-transition-expand to expand-transition, inline util in test

* Update src/components/transitions/expand/expand-methods.ts

* Update src/components/transitions/expand/expand-methods.ts

* Update src/components/transitions/expand/expand-methods.ts

* Rename some more things, add storybook entry

* Use expand transition in detail drawer

* Improve readme

Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>

* Refactor out groupable top level component + indentLevel prop

* Fix tests

* Update readme

Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
This commit is contained in:
Jacob Rienstra
2020-03-16 14:51:59 -04:00
committed by GitHub
parent 7603323fc2
commit a0c421e4e9
24 changed files with 795 additions and 170 deletions

View File

@@ -14,7 +14,8 @@ import VList, {
VListItemContent,
VListItemIcon,
VListItemSubtitle,
VListItemTitle
VListItemTitle,
VListGroup
} from './v-list/';
import VOverlay from './v-overlay/';
import VProgressLinear from './v-progress/linear/';
@@ -40,6 +41,7 @@ Vue.component('v-list-item-content', VListItemContent);
Vue.component('v-list-item-icon', VListItemIcon);
Vue.component('v-list-item-subtitle', VListItemSubtitle);
Vue.component('v-list-item-title', VListItemTitle);
Vue.component('v-list-group', VListGroup);
Vue.component('v-overlay', VOverlay);
Vue.component('v-progress-linear', VProgressLinear);
Vue.component('v-progress-circular', VProgressCircular);
@@ -51,3 +53,7 @@ Vue.component('v-table', VTable);
import DrawerDetail from '@/views/private-view/drawer-detail/';
Vue.component('drawer-detail', DrawerDetail);
import TransitionExpand from './transition/expand';
Vue.component('transition-expand', TransitionExpand);

View File

@@ -0,0 +1,4 @@
import TransitionExpand from './transition-expand.vue';
export { TransitionExpand };
export default TransitionExpand;

View File

@@ -0,0 +1,50 @@
# `transition-expand`
Use around a `v-if` or `v-show` component to have it expand in and out of view.
## Usage
```html
<template>
<div>
<v-button @click="toggle">Click me!</v-button>
<transition-expand>
<div v-if="active">
More content
</div>
</transition-expand>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';
export default defineComponent({
setup(props) {
const active = ref<boolean>(false);
return { active, toggle };
function toggle() { active.value = !active.value; };
}
});
</script>
```
## Props
| Prop | Description | Default |
|-------------------------|----------------------------------------------------|---------|
| `x-axis` | Expand on the horizontal instead vertical axis | `false` |
| `expanded-parent-class` | Add a custom class to the element that is expanded | `''` |
## Slots
| Slot | Description | Data |
|-----------|-------------|------|
| _default_ | | |
## Events
n/a
## CSS Variables
n/a

View File

@@ -0,0 +1,92 @@
import capitalizeFirst from '@/utils/capitalize-first';
interface HTMLExpandElement extends HTMLElement {
_parent?: (Node & ParentNode & HTMLElement) | null;
_initialStyle: {
transition: string;
visibility: string;
overflow: string;
height?: string | null;
width?: string | null;
};
}
export default function(expandedParentClass = '', xAxis = false) {
const sizeProperty = xAxis ? 'width' : ('height' as 'width' | 'height');
const offsetProperty = `offset${capitalizeFirst(sizeProperty)}` as
| 'offsetHeight'
| 'offsetWidth';
return {
beforeEnter(el: HTMLExpandElement) {
el._parent = el.parentNode as (Node & ParentNode & HTMLElement) | null;
el._initialStyle = {
transition: el.style.transition,
visibility: el.style.visibility,
overflow: el.style.overflow,
[sizeProperty]: el.style[sizeProperty]
};
},
enter(el: HTMLExpandElement) {
const initialStyle = el._initialStyle;
const offset = `${el[offsetProperty]}px`;
el.style.setProperty('transition', 'none', 'important');
el.style.visibility = 'hidden';
el.style.visibility = initialStyle.visibility;
el.style.overflow = 'hidden';
el.style[sizeProperty] = '0';
void el.offsetHeight; // force reflow
el.style.transition =
initialStyle.transition !== ''
? initialStyle.transition
: `${sizeProperty} var(--medium) var(--transition)`;
if (expandedParentClass && el._parent) {
el._parent.classList.add(expandedParentClass);
}
requestAnimationFrame(() => {
el.style[sizeProperty] = offset;
});
},
afterEnter: resetStyles,
enterCancelled: resetStyles,
leave(el: HTMLExpandElement) {
el._initialStyle = {
transition: '',
visibility: '',
overflow: el.style.overflow,
[sizeProperty]: el.style[sizeProperty]
};
el.style.overflow = 'hidden';
el.style[sizeProperty] = `${el[offsetProperty]}px`;
void el.offsetHeight; // force reflow
requestAnimationFrame(() => (el.style[sizeProperty] = '0'));
},
afterLeave,
leaveCancelled: afterLeave
};
function afterLeave(el: HTMLExpandElement) {
if (expandedParentClass && el._parent) {
el._parent.classList.remove(expandedParentClass);
}
resetStyles(el);
}
function resetStyles(el: HTMLExpandElement) {
const size = el._initialStyle[sizeProperty];
el.style.overflow = el._initialStyle.overflow;
if (size != null) el.style[sizeProperty] = size;
delete el._initialStyle;
}
}

View File

@@ -0,0 +1,40 @@
import markdown from './readme.md';
import { defineComponent, ref } from '@vue/composition-api';
import TransitionExpand from './transition-expand.vue';
import withPadding from '../../../../.storybook/decorators/with-padding';
export default {
title: 'Transition / Expand',
parameters: {
notes: markdown
},
decorators: [withPadding]
};
export const basic = () =>
defineComponent({
components: { TransitionExpand },
props: {},
setup() {
const active = ref(false);
return { active, toggle };
function toggle() {
active.value = !active.value;
}
},
template: `
<div>
<v-button @click="toggle">Click me!</v-button>
<transition-expand>
<div v-if="active" style="margin-top: 50px">
<v-sheet style="--v-sheet-padding: 24px; --v-sheet-background-color: var(--red-100);">
<h1 class="type-title">Hello, world!</h1>
</v-sheet>
</div>
</transition-expand>
</div>
`
});

View File

@@ -0,0 +1,44 @@
import { mount, createLocalVue } from '@vue/test-utils';
import VueCompositionAPI, { defineComponent, ref } from '@vue/composition-api';
import TransitionExpand from './transition-expand.vue';
const localVue = createLocalVue();
localVue.use(VueCompositionAPI);
localVue.component('transition-expand', TransitionExpand);
const ExpandTestUtil = defineComponent({
setup() {
const active = ref<boolean>(false);
const toggle = () => (active.value = !active.value);
return { active, toggle };
},
template: `
<div>
<slot v-bind="{ active, toggle }" />
</div>
`
});
describe('Expand Transition', () => {
it('Renders the provided markup in the default slot', async () => {
const component = mount(ExpandTestUtil, {
localVue,
scopedSlots: {
default: `
<div slot-scope="foo">
<button @click="foo.toggle"/>
<transition-expand>
<div v-show="foo.active" class="test"> Content </div>
</transition-expand>
</div>
`
}
});
expect(component.find('.test').isVisible()).toBe(false);
component.find('button').trigger('click');
await component.vm.$nextTick();
expect(component.find('.test').isVisible()).toBe(true);
});
});

View File

@@ -0,0 +1,27 @@
<template>
<transition name="expand-transition" mode="in-out" v-on="methods">
<slot />
</transition>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import ExpandMethods from './transition-expand-methods';
export default defineComponent({
props: {
xAxis: {
type: Boolean,
default: false
},
expandedParentClass: {
type: String,
default: ''
}
},
setup(props) {
const methods = ExpandMethods(props.expandedParentClass, props.xAxis);
return { methods };
}
});
</script>

View File

@@ -0,0 +1,4 @@
import TransitionExpand from './expand';
export { TransitionExpand };
export default { TransitionExpand };

View File

@@ -0,0 +1,7 @@
# Transitions
Preset extensions of the Vue `transition` element.
## Table of Contents
- [`Expand`](./expand)

View File

@@ -8,7 +8,7 @@ localVue.use(VueCompositionAPI);
describe('Components / Form', () => {
it('Renders', () => {
const component = shallowMount(VForm, { localVue });
const component = shallowMount(VForm, { localVue, propsData: { collection: 'test' } });
expect(component.isVueInstance()).toBe(true);
});
});

View File

@@ -9,7 +9,7 @@ Vue.component('v-item-group', VItemGroup);
Vue.component('v-item', VItem);
export default {
title: 'Components / Groups / Item Group',
title: 'Components / Item Group',
component: VItemGroup,
decorators: [withKnobs, withPadding],
parameters: {

View File

@@ -4,6 +4,15 @@ import VListItemContent from './v-list-item-content.vue';
import VListItemTitle from './v-list-item-title.vue';
import VListItemSubtitle from './v-list-item-subtitle.vue';
import VListItemIcon from './v-list-item-icon.vue';
import VListGroup from './v-list-group.vue';
export { VList, VListItem, VListItemContent, VListItemTitle, VListItemSubtitle, VListItemIcon };
export {
VList,
VListItem,
VListItemContent,
VListItemTitle,
VListItemSubtitle,
VListItemIcon,
VListGroup
};
export default VList;

View File

@@ -10,129 +10,23 @@
</v-list>
```
## Subcomponents
### List Item
A wrapper for list items that formats children nicely. Can be used on its own or inside a list component. Best used with subcomponents (see below).
### List Item Content
A wrapper for the main text content of a list item. It adds some padding and helps control overflow. The parent of `v-list-title` and `v-list-subtitle` components, it's also the main controller of the `dense` option on lists.
### List Item Title
Wrapper that adds typographic styling and margin for the title of the list item. Responsive to `dense` styling.
### List Item Subtitle
Wrapper that adds typographic styling and margin for the subtitle/description of the list item. Responsive to `dense` and `threeLine` props.
```html
<v-list-item v-for="item in items">
<v-list-item-content>
<v-list-item-title>{{ item.title }}<v-list-item-title>
<v-list-item-subtitle>{{ item.description }}<v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
```
### List Item Icon
Wrapper for icon, action, or avatar type elements in a list item. Can be used on the left or right of an item.
```html
<v-list-item v-for="item in items">
<v-list-item-icon><v-icon name="info"></v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}<v-list-item-title>
</v-list-item-content>
</v-list-item>
```
## Colors
You can set the default, active, and hover colors and background colors with css variables. You can also set them on individual list items, which will override the list vars. Hover styles will only be set if the list item has a to link or an onClick handler.
```html
<v-list>
<v-list-item v-for="item in items">
<v-list-item-content>
{{ item.text }}
</v-list-item-content>
</v-list-item>
</v-list>
<style>
.v-list {
--v-list-color: var(--red);
--v-list-color-hover: var(--white);
--v-list-color-active: var(--white);
--v-list-background-color: var(--red-50);
--v-list-background-color-hover: var(--red-100);
--v-list-background-color-active: var(--red-800);
}
</style>
```
## Props
### List (`v-list`)
| Prop | Description | Default |
|-------------|-------------------------------------------------------------------------------------------------------------|---------|
| `dense` | Removes some padding to make the list items closer together | `false` |
| `threeLine` | Limits list items to three lines of text (1 of title, 2 of subtitle). Only works in webkit enabled browsers | `false` |
| `nav` | Adds a small margin and border-radius for nav menu styling | `false` |
### List Item (`v-list-item`)
| Prop | Description | Default |
|-------------|------------------------------------------------------------------------------------------------------------|---------|
| `dense` | Removes some padding to make the individual list item shorter | `false` |
| `threeLine` | Limits list item to three lines of text (1 of title, 2 of subtitle). Only works in webkit enabled browsers | `false` |
| `to` | Render as vue router-link with to link | `null` |
### List Item Content (`v-list-item-content`)
### List Item Title (`v-list-item-title`)
### List Item Subtitle (`v-list-item-subtitle`)
n/a
### List Item Icon (`v-list-item-icon`)
| Prop | Description | Default |
|----------|---------------------------------------------------------------------|---------|
| `center` | Whether to center the element (good for action elements or avatars) | `false` |
## Slots
### List (`v-list`)
### List Item (`v-list-item`)
### List Item Content (`v-list-item-content`)
### List Item Title (`v-list-item-title`)
### List Item Subtitle (`v-list-item-subtitle`)
### List Item Icon (`v-list-item-icon`)
| Slot | Description |
|-----------|---------------|
| _default_ | Content, etc. |
| `multiple` | Allows multiple child groups to be open at once | `true` |
## Events
n/a
## Slots
| Slot | Description | Data |
|-----------|--------------|------|
| _default_ | List content | |
## CSS Variables
### List (`v-list`)
| Variable | Default |
|------------------------------------|----------------------------------|
| `--v-list-padding` | `8px 0` |
@@ -147,34 +41,223 @@ n/a
| `--v-list-background-color-hover` | `var(--background-color-hover)` |
| `--v-list-background-color-active` | `var(--background-color-active)` |
### List Item (`v-list-item`)
---
| Variable | Default |
|-----------------------------------------|-----------------------------------------------------------------------|
| `--v-list-item-padding` | `0 16px` |
| `--v-list-item-min-width` | `none` |
| `--v-list-item-max-width` | `none` |
| `--v-list-item-min-height` | `48px` |
| `--v-list-item-max-height` | `auto` |
| `--v-list-item-border-radius` | `0` |
| `--v-list-item-margin-bottom` | `0` |
| `--v-list-item-color` | `var(--v-list-color, var(--foreground-color))` |
| `--v-list-item-color-hover` | `var(--v-list-color-hover, var(--foreground-color))` |
| `--v-list-item-color-active` | `var(--v-list-color-active, var(--foreground-color))` |
| `--v-list-item-background-color` | `var(--v-list-background-color, var(--background-color))` |
| `--v-list-item-background-color-hover` | `var(---list-background-color-hover, var(--background-color-hover))` |
| `--v-list-item-background-color-active` | `var(--vlist-background-color-active,var(--background-color-active))` |
# List Item
### List Item Content (`v-list-item-content`)
A wrapper for list items that formats children nicely. Can be used on its own or inside a list component. Best used with subcomponents (see below).
## Usage
```html
<v-list-item>
<v-list-item-title>Hello, world!</v-list-item-title>
</v-list-item>
```
## Props
| Prop | Description | Default |
|---------|----------------------------------------------------------------------|---------|
| `dense` | Removes some padding to make the individual list item shorter | `false` |
| `lines` | Sets if the list item will support `1`, `2`, or `3` lines of content | `null` |
| `to` | Render as vue router-link with to link | `null` |
## Events
n/a
## Slots
| Slot | Description | Data |
|-----------|-------------------|------|
| _default_ | List item content | |
## CSS Variables
| Variable | Default |
|---------------------------------------------|-------------------------------------------------------------------------|
| `--v-list-item-one-line-min-height` | `48px` |
| `--v-list-item-two-line-min-height` | `60px` |
| `--v-list-item-three-line-min-height` | `76px` |
| `--v-list-item-one-line-min-height-dense` | `40px` |
| `--v-list-item-two-line-min-height-dense` | `48px` |
| `--v-list-item-three-line-min-height-dense` | `64px` |
| `--v-list-item-padding` | `0 16px 0 calc(16px + var(--v-list-item-indent, 0px))` |
| `--v-list-item-min-width` | `none` |
| `--v-list-item-max-width` | `none` |
| `--v-list-item-min-height` | `var(--v-list-item-one-line-min-height)` |
| `--v-list-item-max-height` | `auto` |
| `--v-list-item-border-radius` | `0` |
| `--v-list-item-margin-bottom` | `0` |
| `--v-list-item-color` | `var(--v-list-color, var(--foreground-color))` |
| `--v-list-item-color-hover` | `var(--v-list-color-hover, var(--foreground-color))` |
| `--v-list-item-color-active` | `var(--v-list-color-active, var(--foreground-color))` |
| `--v-list-item-background-color` | `var(--v-list-background-color, var(--background-color))` |
| `--v-list-item-background-color-hover` | `var(--v-list-background-color-hover, var(--background-color-hover))` |
| `--v-list-item-background-color-active` | `var(--v-list-background-color-active, var(--background-color-active))` |
---
# List Item Content
A wrapper for the main text content of a list item. It adds some padding and helps control overflow. The parent of `v-list-title` and `v-list-subtitle` components, it's also the main controller of the `dense` option on lists.
## Usage
```html
<v-list-item-content>Hello, world!</v-list-item-content>
```
## Props
n/a
## Events
n/a
## Slots
| Slot | Description | Data |
|-----------|---------------------------|------|
| _default_ | List item content content | |
## CSS Variables
| Variable | Default |
|---------------------------------|----------|
| `--v-list-item-content-padding` | `12px 0` |
### List Item Title (`v-list-item-title`)
---
### List Item Subtitle (`v-list-item-subtitle`)
# List Item Title
### List Item Icon (`v-list-item-icon`)
Wrapper that adds typographic styling and margin for the subtitle/description of the list item. Responsive to `dense` and `threeLine` props.
## Usage
```html
<v-list-item-title>Hello, world</v-list-item-title>
```
## Props
n/a
## Events
n/a
## Slots
| Slot | Description | Data |
|-----------|-------------------------|------|
| _default_ | List item title content | |
## CSS Variables
n/a
---
# List Item Subtitle
Wrapper that adds typographic styling and margin for the subtitle/description of the list item. Responsive to `dense` and `threeLine` props.
## Usage
```html
<v-list-item-subtitle>This is the subtitle</v-list-item-subtitle>
```
## Props
n/a
## Events
n/a
## Slots
| Slot | Description | Data |
|-----------|----------------------------|------|
| _default_ | List item subtitle content | |
## CSS Variables
n/a
---
# List Item Icon
Wrapper for icon, action, or avatar type elements in a list item. Can be used on the left or right of an item.
## Usage
```html
<v-list-item-icon>
<v-icon name="person" />
</v-list-item-icon>
```
## Props
| Prop | Description | Default |
|----------|---------------------------------------------------------------------|---------|
| `center` | Whether to center the element (good for action elements or avatars) | `false` |
## Events
n/a
## Slots
| Slot | Description | Data |
|-----------|------------------------|------|
| _default_ | List item icon content | |
## CSS Variables
n/a
---
# List Group
Provides the ability to make a collapsable (sub)group of list items, within a list or independently. List groups can be nested to an arbitrary depth.
```html
<v-list>
<!-- Root level items -->
<v-list-item />
<v-list-item />
<v-list-group>
<template v-slot:activator>
... Click me to expand! ...
</template>
<v-list-item v-for="item in dropDownItems">
...item content etc.
</v-list-item>
<v-list-group>
<template v-slot:activator>
... Click me to expand this subgroup! ...
</template>
<v-list-item />
<v-list-item />
<v-list-group>
<template v-slot:activator>
... Click me to expand next subgroup! ...
</template>
<v-list-item v-for="item in subGroupDropdownItems">
</v-list-item>
</v-list-group>
</v-list-group>
</v-list-group>
</v-list>
```
## Props
| Prop | Description | Default |
|------|-------------|---------|
| `multiple` | Allow multiple subgroups to be open at the same time | `true` |
## Events
n/a
## Slots
| Slot | Description | Data |
|-----------|---------------|------|
| _default_ | Group content | |
## CSS Variables
n/a

View File

@@ -0,0 +1,58 @@
<template>
<div class="v-list-group">
<v-list-item class="activator" @click="toggle">
<slot name="activator" />
<v-list-item-icon class="activator-icon" :class="{ active }">
<v-icon name="chevron_left" />
</v-list-item-icon>
</v-list-item>
<transition-expand>
<div class="items" v-show="active">
<slot />
</div>
</transition-expand>
</div>
</template>
<script lang="ts">
import { defineComponent, toRefs } from '@vue/composition-api';
import { useGroupableParent, useGroupable } from '@/compositions/groupable';
export default defineComponent({
props: {
multiple: {
type: Boolean,
default: true
}
},
setup(props) {
const { active, toggle } = useGroupable();
useGroupableParent(
{},
{
multiple: toRefs(props).multiple
}
);
return { active, toggle };
}
});
</script>
<style lang="scss" scoped>
.v-list-group {
.activator-icon {
transform: rotate(0deg);
transition: transform var(--medium) var(--transition);
&.active {
transform: rotate(-90deg);
}
}
.items {
padding-left: 16px;
}
}
</style>

View File

@@ -31,32 +31,34 @@ export default defineComponent({
&:first-child {
margin-right: 12px;
}
&:last-child {
margin-left: 12px;
}
}
.v-list,
.v-list-item {
&.three-line,
&.two-line {
align-self: flex-start;
#{$this}.center {
align-self: center;
}
}
&.dense {
#{$this} {
margin-top: 8px;
margin-bottom: 8px;
&:not(:only-child) {
&:first-child {
margin-right: 16px;
@at-root {
.v-list,
.v-list-item {
&.three-line,
&.two-line {
#{$this} {
align-self: flex-start;
&.center {
align-self: center;
}
&:last-child {
margin-left: 8px;
}
}
&.dense {
#{$this} {
margin-top: 8px;
margin-bottom: 8px;
&:not(:only-child) {
&:first-child {
margin-right: 8px;
}
&:last-child {
margin-left: 8px;
}
}
}
}

View File

@@ -54,7 +54,7 @@ export default defineComponent({
--v-list-item-one-line-min-height-dense: 40px;
--v-list-item-two-line-min-height-dense: 48px;
--v-list-item-three-line-min-height-dense: 64px;
--v-list-item-padding: 0 16px;
--v-list-item-padding: 0 16px 0 calc(16px + var(--v-list-item-indent, 0px));
--v-list-item-min-width: none;
--v-list-item-max-width: none;
--v-list-item-min-height: var(--v-list-item-one-line-min-height);
@@ -66,11 +66,11 @@ export default defineComponent({
--v-list-item-color-active: var(--v-list-color-active, var(--foreground-color));
--v-list-item-background-color: var(--v-list-background-color, var(--background-color));
--v-list-item-background-color-hover: var(
---list-background-color-hover,
--v-list-background-color-hover,
var(--background-color-hover)
);
--v-list-item-background-color-active: var(
--vlist-background-color-active,
--v-list-background-color-active,
var(--background-color-active)
);
@@ -136,11 +136,13 @@ export default defineComponent({
}
}
}
.v-list.nav #{$this} {
--v-list-item-padding: 0 8px;
--v-list-item-border-radius: 4px;
#{$this}:not(:last-child):not(:only-child) {
--v-list-item-margin-bottom: 8px;
.v-list.nav {
& #{$this} {
--v-list-item-padding: 0 8px;
--v-list-item-border-radius: 4px;
&:not(:last-child):not(:only-child) {
--v-list-item-margin-bottom: 8px;
}
}
&.dense #{$this},
#{$this}.dense {

View File

@@ -8,6 +8,7 @@ import VListItemContent from './v-list-item-content.vue';
import VListItemTitle from './v-list-item-title.vue';
import VListItemSubTitle from './v-list-item-subtitle.vue';
import VListItemIcon from './v-list-item-icon.vue';
import VListGroup from './v-list-group.vue';
import VSheet from '../v-sheet';
import VCheckbox from '../v-checkbox';
import VueRouter from 'vue-router';
@@ -22,6 +23,7 @@ Vue.component('v-list-item-title', VListItemTitle);
Vue.component('v-list-item-subtitle', VListItemSubTitle);
Vue.component('v-list-item-icon', VListItemIcon);
Vue.component('v-checkbox', VCheckbox);
Vue.component('v-list-group', VListGroup);
Vue.component('v-sheet', VSheet);
@@ -76,6 +78,123 @@ export const basic = () =>
</v-sheet>`
});
export const listGroups = () =>
defineComponent({
router: router,
props: {
multiple: {
default: boolean('Multiple', true)
},
multipleGroup: {
default: boolean('Multiple (in the nested groups)', true)
}
},
setup() {
return {
items: [0, 1, 2, 3]
};
},
template: `
<v-sheet style="--v-sheet-max-width: 600px;">
<v-list :multiple="multiple">
<v-list-item>
<v-list-item-icon>
<v-icon name="home" />
</v-list-item-icon>
<v-list-item-title>Home</v-list-item-title>
</v-list-item>
<v-list-group :multiple="multipleGroup">
<template v-slot:activator>
<v-list-item-icon>
<v-icon name="box" />
</v-list-item-icon>
<v-list-item-title>Collections</v-list-item-title>
</template>
<v-list-item>
<v-list-item-icon><v-icon name="person" /></v-list-item-icon>
<v-list-item-title>Users</v-list-item-title>
</v-list-item>
</v-list-group>
<v-list-group :multiple="multipleGroup">
<template v-slot:activator>
<v-list-item-icon>
<v-icon name="folder" />
</v-list-item-icon>
<v-list-item-title>Files</v-list-item-title>
</template>
<v-list-group :multiple="multipleGroup">
<template v-slot:activator>
<v-list-item-icon>
<v-icon name="cloud_download" />
</v-list-item-icon>
<v-list-item-title>Download</v-list-item-title>
</template>
<v-list-item>
<v-list-item-icon><v-icon name="person" /></v-list-item-icon>
<v-list-item-title>Section 1</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon><v-icon name="person" /></v-list-item-icon>
<v-list-item-title>Section 2</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon><v-icon name="person" /></v-list-item-icon>
<v-list-item-title>Section 3</v-list-item-title>
</v-list-item>
</v-list-group>
<v-list-group :multiple="multipleGroup">
<template v-slot:activator>
<v-list-item-icon>
<v-icon name="cloud_upload" />
</v-list-item-icon>
<v-list-item-title>Upload</v-list-item-title>
</template>
<v-list-item>
<v-list-item-icon><v-icon name="person" /></v-list-item-icon>
<v-list-item-title>Section 1</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon><v-icon name="person" /></v-list-item-icon>
<v-list-item-title>Section 2</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon><v-icon name="person" /></v-list-item-icon>
<v-list-item-title>Section 3</v-list-item-title>
</v-list-item>
<v-list-group :multiple="multipleGroup">
<template v-slot:activator>
<v-list-item-icon>
<v-icon name="attach_file" />
</v-list-item-icon>
<v-list-item-title>Sub-sub-sub section</v-list-item-title>
</template>
<v-list-item>
<v-list-item-icon><v-icon name="person" /></v-list-item-icon>
<v-list-item-title>Section 1</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon><v-icon name="person" /></v-list-item-icon>
<v-list-item-title>Section 2</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon><v-icon name="person" /></v-list-item-icon>
<v-list-item-title>Section 3</v-list-item-title>
</v-list-item>
</v-list-group>
</v-list-group>
</v-list-group>
</v-list>
</v-sheet>
`
});
export const withSubtitle = () =>
defineComponent({
router: router,

View File

@@ -5,6 +5,9 @@ import router from '@/router';
import VList from './v-list.vue';
import VListItem from './v-list-item.vue';
import VListItemIcon from './v-list-item-icon.vue';
import VListGroup from './v-list-group.vue';
import VIcon from '@/components/v-icon';
import TransitionExpand from '@/components/transition/expand';
const localVue = createLocalVue();
localVue.use(VueCompositionAPI);
@@ -12,6 +15,9 @@ localVue.use(VueRouter);
localVue.component('v-list-item', VListItem);
localVue.component('v-list', VList);
localVue.component('v-list-item-icon', VListItemIcon);
localVue.component('v-list-group', VListGroup);
localVue.component('v-icon', VIcon);
localVue.component('transition-expand', TransitionExpand);
describe('List', () => {
it('Renders the provided markup in the default slot', () => {
@@ -191,4 +197,27 @@ describe('List', () => {
component.find('.v-list-item').trigger('click');
expect(onClick).toHaveBeenCalled();
});
it('Opens a list group when activator item is clicked', async () => {
const component = mount(VList, {
localVue,
slots: {
default: `
<v-list-group>
<template v-slot:activator>
Click me!
</template>
<v-list-item class="test"/>
<v-list-item/>
</v-list-group>
<v-list-item/>
<v-list-item/>
`
}
});
expect(component.find('.test').isVisible()).toBe(false);
component.find('.activator').trigger('click');
await component.vm.$nextTick();
expect(component.find('.test').isVisible()).toBe(true);
});
});

View File

@@ -14,7 +14,8 @@
</template>
<script lang="ts">
import { defineComponent, PropType } from '@vue/composition-api';
import { defineComponent, PropType, ref, toRefs } from '@vue/composition-api';
import { useGroupableParent } from '@/compositions/groupable';
export default defineComponent({
props: {
@@ -29,9 +30,21 @@ export default defineComponent({
lines: {
type: Number as PropType<1 | 2 | 3>,
default: null
},
multiple: {
type: Boolean,
default: true
}
},
setup() {
setup(props) {
useGroupableParent(
{},
{
mandatory: ref(false),
multiple: toRefs(props).multiple
}
);
return {};
}
});

View File

@@ -2,7 +2,13 @@ import CollectionsNavigation from './navigation.vue';
import VueCompositionAPI from '@vue/composition-api';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import useNavigation from '../../compositions/use-navigation';
import VList, { VListItem, VListItemContent } from '@/components/v-list';
import VList, {
VListItem,
VListItemContent,
VListItemIcon,
VListItemTitle
} from '@/components/v-list';
import VIcon from '@/components/v-icon';
jest.mock('../../compositions/use-navigation');
@@ -11,6 +17,9 @@ localVue.use(VueCompositionAPI);
localVue.component('v-list', VList);
localVue.component('v-list-item', VListItem);
localVue.component('v-list-item-content', VListItemContent);
localVue.component('v-list-item-title', VListItemTitle);
localVue.component('v-list-item-icon', VListItemIcon);
localVue.component('v-icon', VIcon);
describe('Modules / Collections / Components / CollectionsNavigation', () => {
beforeEach(() => {

View File

@@ -0,0 +1,16 @@
import capitalizeFirst from './capitalize-first';
describe('Utils / capitalizeFirst', () => {
it('Capitalizes the first letter', () => {
const testCases = [
['test', 'Test'],
['directus', 'Directus'],
['123', '123'],
['_abc', '_abc']
];
for (const testCase of testCases) {
expect(capitalizeFirst(testCase[0])).toBe(testCase[1]);
}
});
});

View File

@@ -0,0 +1,3 @@
export default function capitalizeFirst(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}

View File

@@ -0,0 +1,4 @@
import capitalizeFirst from './capitalize-first';
export { capitalizeFirst };
export default capitalizeFirst;

View File

@@ -8,9 +8,13 @@
{{ title }}
</div>
</button>
<div class="content" v-show="active">
<slot />
</div>
<transition-expand>
<div v-show="active">
<div class="content">
<slot />
</div>
</div>
</transition-expand>
</div>
</template>