mirror of
https://github.com/directus/directus.git
synced 2026-01-27 05:28:06 -05:00
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:
@@ -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);
|
||||
|
||||
4
src/components/transition/expand/index.ts
Normal file
4
src/components/transition/expand/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import TransitionExpand from './transition-expand.vue';
|
||||
|
||||
export { TransitionExpand };
|
||||
export default TransitionExpand;
|
||||
50
src/components/transition/expand/readme.md
Normal file
50
src/components/transition/expand/readme.md
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
40
src/components/transition/expand/transition-expand.story.ts
Normal file
40
src/components/transition/expand/transition-expand.story.ts
Normal 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>
|
||||
`
|
||||
});
|
||||
44
src/components/transition/expand/transition-expand.test.ts
Normal file
44
src/components/transition/expand/transition-expand.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
27
src/components/transition/expand/transition-expand.vue
Normal file
27
src/components/transition/expand/transition-expand.vue
Normal 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>
|
||||
4
src/components/transition/index.ts
Normal file
4
src/components/transition/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import TransitionExpand from './expand';
|
||||
|
||||
export { TransitionExpand };
|
||||
export default { TransitionExpand };
|
||||
7
src/components/transition/readme.md
Normal file
7
src/components/transition/readme.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Transitions
|
||||
|
||||
Preset extensions of the Vue `transition` element.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [`Expand`](./expand)
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
16
src/utils/capitalize-first/capitalize-first.test.ts
Normal file
16
src/utils/capitalize-first/capitalize-first.test.ts
Normal 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]);
|
||||
}
|
||||
});
|
||||
});
|
||||
3
src/utils/capitalize-first/capitalize-first.ts
Normal file
3
src/utils/capitalize-first/capitalize-first.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function capitalizeFirst(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
4
src/utils/capitalize-first/index.ts
Normal file
4
src/utils/capitalize-first/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import capitalizeFirst from './capitalize-first';
|
||||
|
||||
export { capitalizeFirst };
|
||||
export default capitalizeFirst;
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user