Modals
Dialogs can be displayed as modals with the Modal
component. Modal
renders a Backdrop and a dialog on top of all other content on the page in order to limit user interaction to the dialog.
Basic usage
To display a modal, you can use one of the methods exposed by the useModals()
composable.
<script setup lang="ts">
import useModals from '@leaflink/stash/useModals';
import BasicModal from '../components/Modal/BasicModal.vue';
const modals = useModals();
function openBasicModal() {
modals.open({ component: BasicModal });
}
</script>
<template>
<Button @click="openBasicModal">Open Basic Modal</Button>
</template>
Modal
component
Use this component to create custom modals in your app. See the Modal
docs for full usage.
<script setup lang="ts">
import useModals from '@leaflink/stash/useModals';
import MyModal from './MyModal.vue';
const modals = useModals();
function openModal() {
modals.open({ component: MyModal });
}
</script>
<script setup lang="ts">
import Modal from '@leaflink/stash/Modal.vue';
import Button from '@leaflink/stash/Button.vue';
const emit = defineEmits(['close', 'dismiss', 'cancel']);
</script>
<template>
<Modal open size="narrow" title="Test modal" @dismiss="emit('dismiss')">
<h1>Hello World!</h1>
<Button @click="emit('close')">Close</Button>
<Button @click="emit('cancel')">Cancel</Button>
<slot></slot>
</Modal>
</template>
Installation
You don't have to do anything to start using modals. Modals are actually enabled as a separate plugin within Stash named ModalsPlugin
. The plugin is installed automatically when using @leaflink/stash
.
You should be able to call useModals()
right away after installing and running app.use('@leaflink/stash')
.
If you instead want to set modal options:
import stash from '@leaflink/stash';
app.use(stash, {
modals: {
mountNodeId: 'some-id',
mountNodeClass: 'some-class',
},
});
If you don't need or want it to be loaded you can opt out:
import stash from '@leaflink/stash';
app.use(stash, { modals: false });
If you opt out from loading it automatically from Stash, you can also load it manually:
import { createApp } from 'vue';
import ModalsPlugin from '@leaflink/stash/ModalsPlugin';
const app = createApp(App);
app.use(ModalsPlugin);
The ModalsPlugin
can receive options as the second argument to app.use()
.
import { createApp } from 'vue';
import ModalsPlugin from '@leaflink/stash/ModalsPlugin';
const app = createApp(App);
app.use(ModalsPlugin, { mountNodeClass: 'my-class' });
Modals
component
The Modals
component is responsible for rendering all modals opened with the useModals()
composable. You won't typically need to use this component directly because it's injected by the ModalsPlugin
within Stash automatically, but it's available if you need to.
Modal sequence
Modals are opened in the order they're called. With two or more modals opened at a time, the modal called first takes priority, and any additional modals will be queued up in sequence.
const modals = useModals();
modals.open({ component: GlobalModal });
modals.open({ component: LocalModal });
Note
In most cases, the presentation of modals should be intentionally managed by developers. The modal management inside Modals.vue
was primarily designed to prevent collisions between unrelated modals on page load; for example, if a global modal were to be presented on a page that also loads a modal.
Default event listeners
The Modals
component sets 3 common event listeners that LeafLink uses.
dismiss
close
cancel
The listeners simply call the close
method from useModals
.
Devs can override these default listeners.
For example, if you need to open a confirm prompt when closing a modal, set the disableDefaultListeners
open to true inside the modal's options
.
const modals = useModals();
function onModalClose() {
confirm('Are you sure?')
modals.close();
}
modals.open({
component: MyModal,
attributes: {
onDismiss: onModalClose,
},
options: {
disableDefaultListeners: true,
},
});
Testing
In order to ensure that modals are being displayed during component tests, you need to use the stash
plugin.
import { render } from '@testing-library/vue';
import stash from '@leaflink/stash';
import userEvent from '@testing-library/user-event';
it('shows a modal when button is clicked', async () => {
render(Component, {
global: {
plugins: [stash],
},
});
const user = userEvent.setup();
await user.click(screen.getByRole('button'));
expect(await screen.findByText('Modal Title')).toBeInTheDocument();
});
Cleaning up
To ensure Modals aren't left open after every test, you can add a global afterEach
hook to ensure that modals are being closed after every test. If you're using @leaflink/dom-testing-utils
this is extremely easy.
In setup-env.ts
, just add the following line:
import '@leaflink/dom-testing-utils/setup-env';
If you're not using that library, you can also add this explicitly.
import { config } from '@vue/test-utils';
import stash from '@leaflink/stash';
import useModals from '@leaflink/stash/useModals';
config.global.plugins = [stash];
afterEach(() => {
useModals().closeAll();
});
If you only need to do this for a single test for some reason, you can also do it in the test itself:
import { render } from '@testing-library/vue';
import stash from '@leaflink/stash';
import useModals from '@leaflink/stash/useModals';
import userEvent from '@testing-library/user-event';
afterEach(() => {
useModals().closeAll();
});
it('shows a modal when button is clicked', async () => {
render(Component, {
global: {
plugins: [stash],
},
});
const user = userEvent.setup();
await user.click(screen.getByRole('button'));
expect(await screen.findByText('Modal Title')).toBeInTheDocument();
});
API
See the documentation below for a complete reference to all the props and classes available to the components mentioned here.