mirror of
https://github.com/directus/directus.git
synced 2026-01-27 11:57:58 -05:00
Add v-modal component (#274)
* Start on v-modal, fix layering in v-dialog
* Add shadow to v-dialog content
* Add optional group identifier to groupable compositions
This allows groupable parents to be nested more flexibly:
tabs group
item group
item
tab
In the case above, we only want the tab to trigger for tabs group, not item group.
* Add active prop support to v-list-item
Allows us to manually indicate that a list item is active, useful in v-menu
* Use v-list in vertical tabs
* Finish v-modal
* Update readme for groupable composition
This commit is contained in:
@@ -10,6 +10,7 @@ import VDialog from './v-dialog';
|
||||
import VDivider from './v-divider';
|
||||
import VForm from './v-form';
|
||||
import VHover from './v-hover/';
|
||||
import VModal from './v-modal/';
|
||||
import VIcon from './v-icon/';
|
||||
import VInput from './v-input/';
|
||||
import VItemGroup, { VItem } from './v-item-group';
|
||||
@@ -48,6 +49,7 @@ Vue.component('v-dialog', VDialog);
|
||||
Vue.component('v-divider', VDivider);
|
||||
Vue.component('v-form', VForm);
|
||||
Vue.component('v-hover', VHover);
|
||||
Vue.component('v-modal', VModal);
|
||||
Vue.component('v-icon', VIcon);
|
||||
Vue.component('v-input', VInput);
|
||||
Vue.component('v-item-group', VItemGroup);
|
||||
|
||||
@@ -28,13 +28,15 @@ export default defineComponent({
|
||||
.v-card {
|
||||
--v-card-min-width: none;
|
||||
--v-card-max-width: 400px;
|
||||
--v-card-height: auto;
|
||||
--v-card-min-height: none;
|
||||
--v-card-max-height: none;
|
||||
--v-card-max-height: min-content;
|
||||
--v-card-padding: 16px;
|
||||
--v-card-background-color: var(--highlight);
|
||||
|
||||
min-width: var(--v-card-min-width);
|
||||
max-width: var(--v-card-max-width);
|
||||
height: var(--v-card-height);
|
||||
min-height: var(--v-card-min-height);
|
||||
max-height: var(--v-card-max-height);
|
||||
background-color: var(--v-card-background-color);
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<slot name="activator" v-bind="{ on: () => $emit('toggle', true) }" />
|
||||
|
||||
<portal to="dialog-outlet">
|
||||
<div class="container" :class="[{ active }, className]">
|
||||
<v-overlay :active="active" absolute @click="emitToggle" />
|
||||
<div class="content">
|
||||
<transition name="dialog">
|
||||
<div v-if="active" class="container" :class="[className]">
|
||||
<v-overlay active absolute @click="emitToggle" />
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</portal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -56,6 +56,8 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/mixins/breakpoint';
|
||||
|
||||
.v-dialog {
|
||||
--v-dialog-z-index: 100;
|
||||
|
||||
@@ -72,9 +74,7 @@ export default defineComponent({
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity var(--medium) var(--transition);
|
||||
pointer-events: none;
|
||||
|
||||
.v-card {
|
||||
--v-card-min-width: 400px;
|
||||
@@ -90,29 +90,14 @@ export default defineComponent({
|
||||
--v-overlay-z-index: 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-height: 90%;
|
||||
transform: translateY(-50px);
|
||||
opacity: 0;
|
||||
transition: var(--medium) var(--transition-in);
|
||||
transition-property: opacity, transform;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
|
||||
.content {
|
||||
transform: translateY(-100px);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.nudge {
|
||||
animation: nudge 200ms;
|
||||
}
|
||||
|
||||
::v-deep > * {
|
||||
z-index: 2;
|
||||
box-shadow: 0px 4px 12px rgba(38, 50, 56, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes nudge {
|
||||
@@ -128,4 +113,24 @@ export default defineComponent({
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-enter-active,
|
||||
.dialog-leave-active {
|
||||
transition: opacity var(--slow) var(--transition);
|
||||
|
||||
::v-deep > *:not(.v-overlay) {
|
||||
transform: translateY(0px);
|
||||
transition: transform var(--slow) var(--transition-in);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-enter,
|
||||
.dialog-leave-to {
|
||||
opacity: 0;
|
||||
|
||||
::v-deep > *:not(.v-overlay) {
|
||||
transform: translateY(50px);
|
||||
transition: transform var(--slow) var(--transition-out);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -66,7 +66,8 @@ A wrapper for list items that formats children nicely. Can be used on its own or
|
||||
| `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` |
|
||||
| `disabled` | Disable the list item | `false` |
|
||||
| `disabled` | Disable the list item | `false` |
|
||||
| `active` | Enable the list item's active state | `false` |
|
||||
|
||||
## Events
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
class="v-list-item"
|
||||
:to="to"
|
||||
:class="{
|
||||
active,
|
||||
dense,
|
||||
link: isClickable,
|
||||
'three-line': lines === 3,
|
||||
@@ -40,6 +41,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { listeners }) {
|
||||
const component = computed<string>(() => (props.to ? 'router-link' : 'li'));
|
||||
|
||||
4
src/components/v-modal/index.ts
Normal file
4
src/components/v-modal/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import VModal from './v-modal.vue';
|
||||
|
||||
export { VModal };
|
||||
export default VModal;
|
||||
73
src/components/v-modal/readme.md
Normal file
73
src/components/v-modal/readme.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Modal
|
||||
|
||||
A modal is basically an elaborate pre-configured dialog. It supports an optional left sidebar that allows for easier tab usage.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<v-modal title="My Modal" v-modal="active">
|
||||
Hello, world!
|
||||
</v-modal>
|
||||
```
|
||||
|
||||
```html
|
||||
<v-modal title="My Modal">
|
||||
<template #activator="{ on }">
|
||||
<v-button @click="on">Open modal</v-button>
|
||||
</template>
|
||||
|
||||
Hello, world!
|
||||
</v-modal>
|
||||
```
|
||||
|
||||
```html
|
||||
<v-modal title="My Modal" v-model="active">
|
||||
<template #activator="{ on }">
|
||||
<v-button @click="on">Open modal</v-button>
|
||||
</template>
|
||||
|
||||
<template #sidebar>
|
||||
<v-tabs vertical>
|
||||
<v-tab>Hello</v-tab>
|
||||
<v-tab>Page 2</v-tab>
|
||||
<v-tab>Page 3</v-tab>
|
||||
</v-tabs>
|
||||
</template>
|
||||
|
||||
<v-tabs-items>
|
||||
<v-tab-item>Hello, world!</v-tab-item>
|
||||
<v-tab-item>I'm page 2!</v-tab-item>
|
||||
<v-tab-item>I'm page 3!</v-tab-item>
|
||||
</v-tabs-items>
|
||||
|
||||
<template #footer="{ close }">
|
||||
<v-button @click="close">Close modal</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Description | Default |
|
||||
|--------------|-----------------------------------------------------------------|---------|
|
||||
| `title`* | Title for the modal | |
|
||||
| `subtitle` | Optional subtitle for the modal | |
|
||||
| `active` | If the modal is active. Used in `v-model` | `false` |
|
||||
| `persistent` | Prevent the user from exiting the modal by clicking the overlay | `false` |
|
||||
|
||||
## Events
|
||||
|
||||
| Event | Description | Value |
|
||||
|----------|--------------------------|-----------|
|
||||
| `toggle` | Sync the `v-model` value | `boolean` |
|
||||
|
||||
## Slots
|
||||
| Slot | Description | Data |
|
||||
|-------------|--------------------------------------------------------|-------------------------|
|
||||
| _default_ | Modal content | |
|
||||
| `activator` | Element to enable the modal | `{ on: () => void }` |
|
||||
| `sidebar` | Sidebar content for the modal. Often used for `v-tabs` | |
|
||||
| `footer` | Footer content. Often used for action buttons | `{ close: () => void }` |
|
||||
|
||||
## CSS Variables
|
||||
n/a
|
||||
84
src/components/v-modal/v-modal.story.ts
Normal file
84
src/components/v-modal/v-modal.story.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import withPadding from '../../../.storybook/decorators/with-padding';
|
||||
import readme from './readme.md';
|
||||
import { defineComponent, ref } from '@vue/composition-api';
|
||||
|
||||
export default {
|
||||
title: 'Components / Modal',
|
||||
parameters: {
|
||||
notes: readme,
|
||||
},
|
||||
decorators: [withPadding],
|
||||
};
|
||||
|
||||
export const basic = () =>
|
||||
defineComponent({
|
||||
setup() {
|
||||
const active = ref(false);
|
||||
return { active };
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<v-modal
|
||||
v-model="active"
|
||||
title="Creating New Collection"
|
||||
subtitle="called Customers"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-button @click="on">Enable modal</v-button>
|
||||
</template>
|
||||
|
||||
<p>Hello world!</p>
|
||||
|
||||
<template #footer="{ close }">
|
||||
<v-button @click="close">Close modal</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
<portal-target name="dialog-outlet" />
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
export const withNav = () =>
|
||||
defineComponent({
|
||||
setup() {
|
||||
const active = ref(false);
|
||||
const current = ref(['hello']);
|
||||
return { active, current };
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<v-modal
|
||||
v-model="active"
|
||||
title="Creating New Collection"
|
||||
subtitle="called Customers"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-button @click="on">Enable modal</v-button>
|
||||
</template>
|
||||
|
||||
<template #sidebar>
|
||||
<v-tabs v-model="current" vertical>
|
||||
<v-tab value="hello">Hello</v-tab>
|
||||
<v-tab value="introduce">Modal</v-tab>
|
||||
</v-tabs>
|
||||
</template>
|
||||
|
||||
<v-tabs-items v-model="current">
|
||||
<v-tab-item value="hello">
|
||||
<p>Hello world!</p>
|
||||
</v-tab-item>
|
||||
|
||||
<v-tab-item value="introduce">
|
||||
<p>I'm a modal with tabs</p>
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
|
||||
|
||||
<template #footer="{ close }">
|
||||
<v-button @click="close">Close modal</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
<portal-target name="dialog-outlet" />
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
24
src/components/v-modal/v-modal.test.ts
Normal file
24
src/components/v-modal/v-modal.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
import VModal from './v-modal.vue';
|
||||
import VDialog from '@/components/v-dialog/';
|
||||
import VIcon from '@/components/v-icon/';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-dialog', VDialog);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Components / Modal', () => {
|
||||
it('Renders', () => {
|
||||
const component = shallowMount(VModal, {
|
||||
localVue,
|
||||
propsData: {
|
||||
title: 'My Modal',
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
});
|
||||
187
src/components/v-modal/v-modal.vue
Normal file
187
src/components/v-modal/v-modal.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<v-dialog :active="active" @toggle="$emit('toggle', $event)" :persistent="persistent">
|
||||
<template #activator="{ on }">
|
||||
<slot name="activator" v-bind="{ on }" />
|
||||
</template>
|
||||
|
||||
<article class="v-modal">
|
||||
<header class="header">
|
||||
<v-icon class="menu-toggle" name="menu" @click="sidebarActive = !sidebarActive" />
|
||||
<h2 class="title">{{ title }}</h2>
|
||||
<p v-if="subtitle" class="subtitle">{{ subtitle }}</p>
|
||||
<div class="spacer" />
|
||||
<v-icon name="" />
|
||||
</header>
|
||||
<div class="content">
|
||||
<v-overlay
|
||||
v-if="$slots.sidebar"
|
||||
absolute
|
||||
:active="sidebarActive"
|
||||
@click="sidebarActive = false"
|
||||
/>
|
||||
<nav
|
||||
v-if="$slots.sidebar"
|
||||
class="sidebar"
|
||||
:class="{ active: sidebarActive }"
|
||||
@click="sidebarActive = false"
|
||||
>
|
||||
<slot name="sidebar" />
|
||||
</nav>
|
||||
<main class="main">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
<footer class="footer" v-if="$slots.footer">
|
||||
<slot name="footer" v-bind="{ close: () => $emit('toggle', false) }" />
|
||||
</footer>
|
||||
</article>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from '@vue/composition-api';
|
||||
|
||||
export default defineComponent({
|
||||
model: {
|
||||
prop: 'active',
|
||||
event: 'toggle',
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
persistent: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const sidebarActive = ref(false);
|
||||
|
||||
return { sidebarActive };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/mixins/breakpoint';
|
||||
|
||||
.v-modal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(100% - 16px);
|
||||
max-width: 916px;
|
||||
height: calc(100% - 16px);
|
||||
max-height: 760px;
|
||||
background-color: var(--background-color);
|
||||
border-radius: 4px;
|
||||
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 2px solid var(--background-color-alt);
|
||||
|
||||
.title {
|
||||
margin-right: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--foreground-color-secondary);
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
margin-right: 8px;
|
||||
|
||||
@include breakpoint(medium) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(medium) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
flex-basis: 220px;
|
||||
flex-shrink: 0;
|
||||
width: 220px;
|
||||
height: 100%;
|
||||
background-color: var(--background-color-alt);
|
||||
transform: translateX(-100%);
|
||||
transition: transform var(--slow) var(--transition-out);
|
||||
|
||||
&.active {
|
||||
transform: translateX(0);
|
||||
transition-timing-function: var(--transition-in);
|
||||
}
|
||||
|
||||
@include breakpoint(medium) {
|
||||
position: relative;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.v-overlay {
|
||||
--v-overlay-z-index: none;
|
||||
|
||||
@include breakpoint(medium) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
flex-grow: 1;
|
||||
padding: 8px 16px;
|
||||
overflow: auto;
|
||||
|
||||
@include breakpoint(medium) {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
height: 60px;
|
||||
padding: 0 16px;
|
||||
border-top: 2px solid var(--background-color-alt);
|
||||
|
||||
@include breakpoint(medium) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(medium) {
|
||||
width: calc(100% - 64px);
|
||||
height: calc(100% - 64px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,20 @@
|
||||
<template>
|
||||
<div class="v-tab" :class="{ active, disabled }" @click="onClick">
|
||||
<v-list-item
|
||||
v-if="vertical"
|
||||
class="v-tab vertical"
|
||||
:active="active"
|
||||
:disabled="disabled"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot v-bind="{ active, toggle }" />
|
||||
</v-list-item>
|
||||
<div v-else class="v-tab horizontal" :class="{ active, disabled }" @click="onClick">
|
||||
<slot v-bind="{ active, toggle }" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import { defineComponent, inject, ref } from '@vue/composition-api';
|
||||
import { useGroupable } from '@/compositions/groupable';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -20,8 +29,10 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { active, toggle } = useGroupable(props.value);
|
||||
return { active, toggle, onClick };
|
||||
const { active, toggle } = useGroupable(props.value, 'v-tabs');
|
||||
const vertical = inject('v-tabs-vertical', ref(false));
|
||||
|
||||
return { active, toggle, onClick, vertical };
|
||||
|
||||
function onClick() {
|
||||
if (props.disabled === false) toggle();
|
||||
@@ -31,7 +42,7 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-tab {
|
||||
.v-tab.horizontal {
|
||||
--v-tab-color: var(--input-foreground-color);
|
||||
--v-tab-background-color: var(--input-background-color);
|
||||
--v-tab-color-active: var(--input-foreground-color);
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<template>
|
||||
<div class="v-tabs" :class="{ vertical }">
|
||||
<v-list class="v-tabs vertical alt-colors" v-if="vertical" nav>
|
||||
<slot />
|
||||
<div class="slider" :style="slideStyle"></div>
|
||||
</v-list>
|
||||
<div v-else class="v-tabs horizontal">
|
||||
<slot />
|
||||
<div class="slider" :style="slideStyle" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, toRefs, computed } from '@vue/composition-api';
|
||||
import { defineComponent, PropType, toRefs, computed, provide, ref } from '@vue/composition-api';
|
||||
import { useGroupableParent } from '@/compositions/groupable';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -21,20 +24,20 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { value: selection } = toRefs(props);
|
||||
const { value: selection, vertical } = toRefs(props);
|
||||
|
||||
const options = toRefs({
|
||||
multiple: false,
|
||||
max: -1,
|
||||
mandatory: true,
|
||||
});
|
||||
provide('v-tabs-vertical', vertical);
|
||||
|
||||
const { items } = useGroupableParent(
|
||||
{
|
||||
selection: selection,
|
||||
onSelectionChange: update,
|
||||
},
|
||||
options
|
||||
{
|
||||
multiple: ref(false),
|
||||
mandatory: ref(true),
|
||||
},
|
||||
'v-tabs'
|
||||
);
|
||||
|
||||
const slideStyle = computed(() => {
|
||||
@@ -50,12 +53,12 @@ export default defineComponent({
|
||||
emit('input', newSelection);
|
||||
}
|
||||
|
||||
return { update, slideStyle };
|
||||
return { update, slideStyle, items };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.v-tabs {
|
||||
.v-tabs.horizontal {
|
||||
--v-tabs-underline-color: var(--foreground-color);
|
||||
|
||||
position: relative;
|
||||
@@ -83,20 +86,5 @@ export default defineComponent({
|
||||
transition: var(--medium) cubic-bezier(0.66, 0, 0.33, 1);
|
||||
transition-property: left, top;
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
flex-direction: column;
|
||||
|
||||
::v-deep .v-tab {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.slider {
|
||||
top: calc(100% / var(--_v-tabs-items) * var(--_v-tabs-selected));
|
||||
left: 0;
|
||||
width: 2px;
|
||||
height: calc(100% / var(--_v-tabs-items));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,9 +11,9 @@ type GroupableInstance = {
|
||||
* Used to make child item part of the group context. Needs to be used in a component that is a child
|
||||
* of a component that has the `useGroupableParent` composition enabled
|
||||
*/
|
||||
export function useGroupable(value?: string | number) {
|
||||
export function useGroupable(value?: string | number, group = 'item-group') {
|
||||
// Injects the registration / toggle functions from the parent scope
|
||||
const parentFunctions = inject('item-group', null);
|
||||
const parentFunctions = inject(group, null);
|
||||
|
||||
if (isEmpty(parentFunctions)) {
|
||||
return {
|
||||
@@ -65,7 +65,8 @@ type GroupableParentOptions = {
|
||||
*/
|
||||
export function useGroupableParent(
|
||||
state: GroupableParentState = {},
|
||||
options: GroupableParentOptions = {}
|
||||
options: GroupableParentOptions = {},
|
||||
group = 'item-group'
|
||||
) {
|
||||
// References to the active state and value of the individual child items
|
||||
const items = ref<GroupableInstance[]>([]);
|
||||
@@ -95,7 +96,7 @@ export function useGroupableParent(
|
||||
|
||||
// Provide the needed functions to all children groupable components. Note: nested item groups
|
||||
// will override the item-group namespace, making nested item groups possible.
|
||||
provide('item-group', { register, unregister, toggle });
|
||||
provide(group, { register, unregister, toggle });
|
||||
|
||||
// Whenever the value of the selection changes, we have to update all the children's internal
|
||||
// states. If not, you can have an activated item that's not actually active.
|
||||
|
||||
@@ -8,7 +8,7 @@ the functionality of the `v-item-group` base component, and other groupable comp
|
||||
Use the `useGroupableParent` function in a parent component that will contain one or more components
|
||||
in it's slots (deeply nested or not) that use the `useGroupable` compositions.
|
||||
|
||||
### `useGroupableParent(state: GroupableParentState, options: GroupableParentOptions): void`
|
||||
### `useGroupableParent(state: GroupableParentState, options: GroupableParentOptions, group: string): void`
|
||||
|
||||
The `useGroupableParent` composition accepts two paremeters: state and options.
|
||||
State includes a `selection` key that can be used to pass an array of selected items, so you can
|
||||
@@ -42,7 +42,10 @@ export default defineComponent({
|
||||
});
|
||||
```
|
||||
|
||||
### `useGroupable(value: string | number): { active: Ref<boolean>; toggle: () => void; }`
|
||||
The optional group parameter allows you to control to what group this parent is registered. This
|
||||
can be useful when you have complexly nested groups.
|
||||
|
||||
### `useGroupable(value: string | number | undefined, group: string): { active: Ref<boolean>; toggle: () => void; }`
|
||||
Registers this component as a child of the first parent component that uses the `useGroupableParent`
|
||||
component.
|
||||
|
||||
@@ -61,3 +64,6 @@ export default defineComponent({
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The optional group parameter allows you to control to what group this child is registered. This
|
||||
can be useful when you have complexly nested groups.
|
||||
|
||||
Reference in New Issue
Block a user