Modal
Базовый компонент модального окна. Basic modal component.
Import Permalink to "Import"
import ProximaModal from 'proxima-vue/modal';
Playground Permalink to "Playground"
import { ref, unref } from 'vue';
import ProximaModal from 'proxima-vue/modal';
import ProximaButton from 'proxima-vue/button';
const modal = ref({} as InstanceType<typeof ProximaModal>);
const showModal = () => unref(modal).showModal();
const hideModal = () => unref(modal).hideModal();
<ProximaModal
ref="modal"
title="Proxima Centauri"
>
Proxima Centauri is a small...
<template #footer>
<ProximaButton text="Close" @click="hideModal" />
</template>
</ProximaModal>
<ProximaButton
text="Show modal"
@click="showModal"
/>
Top layer Permalink to "Top layer"
Нативный dialog
находится поверх всех других слоев и не реагирует на z‑index
. Native dialog
element is placed on the top layer and does not respond to z‑index
.
Scrollbar Permalink to "Scrollbar"
При открытии модального окна нужно блокировать скролл страницы, при этом контент будет прыгать и это смотрится не очень красиво, для решения проблемы можно использовать свойство scrollbar-gutter, но и у него есть недостатки: When opening a modal window, you need to block the scrolling of the page, while the content may jump, and this doesn’t look very nice. To solve the problem, you can use the scrollbar-gutter property, but it also has disadvantages:
- Не работает в Safari (в 2023 году точно) Doesn't work in Safari (in 2023 for sure)
- Если у модального окна много контента и есть свой скролл, то в Firefox будет два скроллбара, а в Chrome его не будет видно вовсе If a modal window has a lot of content and its own scroll, then in Firefox there will be two scrollbars, in Chrome it will not be visible at all
Конечно Safari рано или поздно поддержит это свойство, а вот второй пункт довольно критичный, поэтому есть специальная функция: Of course, Safari will support this property sooner or later, but the second point is quite critical, so you can use a special function:
// App.vue
import { onBeforeMount } from 'vue';
import { setScrollbarLockStyles } from 'proxima-vue/utils/scrollbar';
onBeforeMount(setScrollbarLockStyles);
Accessibility Permalink to "Accessibility"
Компонент использует нативный dialog
, поэтому role="dialog"
, aria‑modal="true"
, aria‑live="assertive"
включены в этот тег по умолчанию. The component uses the native dialog
tag, so role="dialog"
, aria‑modal="true"
, aria‑live="assertive"
are enabled by default.
BEM Permalink to "BEM"
Блок компонента modal
, модификаторы (создаются автоматически на основе пропсов): Component block modal
, modifiers (automatically created based on props):
modal_opened
modal_not_native
modal_inactive_backdrop
modal_ignore_esc
modal_has_header
modal_has_footer
modal_has_aside
modal_sticky_header
modal_sticky_footer
modal_sticky_actions
modal_view_plain
modal_view_fullscreen
modal_theme_[name]
CSS Permalink to "CSS"
Слой компонента @layer proxima.modal
, также вы можете гибко cтилизовать компонент через кастомные свойства: Component layer @layer proxima.modal
, also you can style the component flexibly through custom properties:
--modal-backdrop: rgba(0, 0, 0, 0.4);
--modal-inner-padding-x: 0.5rem;
--modal-inner-padding-y: 0.5rem;
--modal-title-font-size: 1.25em;
/* Dialog */
--modal-dialog-width: 30rem;
--modal-dialog-background: #fff;
--modal-dialog-color: inherit;
--modal-dialog-border: none;
--modal-dialog-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
--modal-dialog-border-radius: 0.5em;
--modal-dialog-margin-x: auto;
--modal-dialog-margin-y: auto;
--modal-dialog-margin-y-start: auto;
--modal-dialog-padding-y: 1.375rem;
--modal-dialog-padding-x: 1.625rem;
--modal-dialog-animation-translate-x: 0;
--modal-dialog-animation-translate-y: 1rem;
--modal-dialog-animation: ProximaModalAnimation 300ms ease;
--modal-dialog-padding-from-aside: calc(
var(--modal-dialog-padding-x) + var(--modal-aside-width, 3em)
);
/* Header */
--modal-header-position: static;
--modal-header-inset-block-start: auto;
--modal-header-display: flex;
--modal-header-align-items: center;
--modal-header-justify-content: start;
--modal-header-gap: 1em;
--modal-header-background: inherit;
--modal-header-reduced-transparency-background: inherit;
--modal-header-border-bottom: none;
--modal-header-margin-y-end: 0em;
--modal-header-padding-x: var(--modal-dialog-padding-x);
--modal-header-padding-y: var(--modal-dialog-padding-y);
--modal-header-border-top-right-radius: inherit;
--modal-header-border-top-left-radius: inherit;
--modal-header-backdrop-filter: none;
--modal-header-z-index: 10;
/* Content */
--modal-content-background: none;
--modal-content-padding-x: var(--modal-dialog-padding-x);
--modal-content-padding-y: var(--modal-dialog-padding-x);
/* Footer */
--modal-footer-position: static;
--modal-footer-inset-block-end: auto;
--modal-footer-display: flex;
--modal-footer-align-items: center;
--modal-footer-justify-content: normal;
--modal-footer-gap: 0.5em;
--modal-footer-background: inherit;
--modal-footer-reduced-transparency-background: inherit;
--modal-footer-border-top: none;
--modal-footer-margin-y-start: 0em;
--modal-footer-padding-x: var(--modal-dialog-padding-x);
--modal-footer-padding-y: calc(
var(--modal-dialog-padding-y) * 0.75
);
--modal-footer-border-bottom-right-radius: inherit;
--modal-footer-border-bottom-left-radius: inherit;
--modal-footer-backdrop-filter: none;
--modal-footer-z-index: 10;
/* Aside */
--modal-aside-position: absolute;
--modal-aside-inset: 0;
--modal-aside-display: flex;
--modal-aside-align-items: flex-start;
--modal-aside-justify-content: flex-end;
--modal-aside-width: auto;
--modal-aside-height: auto;
--modal-aside-padding-x: 1rem;
--modal-aside-padding-y: 1rem;
--modal-aside-margin-x: 0;
--modal-aside-margin-y: 0;
--modal-aside-pointer-events: none;
--modal-aside-z-index: 11;
/* Actions */
--modal-actions-position: static;
--modal-actions-inset-block-start: auto;
/* Spinner */
--modal-spinner-background: var(--modal-dialog-background);
--modal-spinner-z-index: 15;
Locale Permalink to "Locale"
Компонент использует токены локализации, вы можете передать другие токены или обычный текст через пропсы: The component uses localization tokens, and you can pass other tokens or plain text via props:
closeAriaLabel
{
modalCloseAriaLabel: "Close the modal",
}
Props Permalink to "Props"
interface ProximaModalProps {
title?: string
shouldCloseOnEsc?: boolean
shouldCloseOnBackdrop?: boolean
shouldLockScrollbar?: boolean
stickyHeader?: boolean
stickyFooter?: boolean
stickyActions?: boolean
hasCloseButton?: boolean
closeAriaLabel?: string
closeButtonProps?: ProximaDynamicProps
footerAlign?: ProximaAlign
view?: 'plain' | 'fullscreen'
theme?: string
}
Events Permalink to "Events"
<ProximaModal
@open="onModalOpen"
@close="onModalClose"
@cancel="onModalCancel"
@before:open="onBeforeModalOpen"
@before:close="onBeforeModalClose"
/>
const onModalOpen = () => {};
const onModalClose = (event) => {};
const onModalCancel = (event) => {};
const onBeforeModalOpen = () => {};
const onBeforeModalClose = () => {};
type Emits = {
'open': () => void
'close': (event: Event) => void
'cancel': (event: Event) => void
'before:open': () => void
'before:close': () => void
}
Slots Permalink to "Slots"
<ProximaModal>
<template #header="slotProps">
you code (append to header)
</template>
<template #footer="slotProps">
you code (append to footer)
</template>
<template #aside="slotProps">
you code (append to aside)
</template>
<template #actions="slotProps">
you code (append to actions)
</template>
<template #dialog="slotProps">
you code (append to dialog)
</template>
<template #inner="slotProps">
you code (append to inner)
</template>
</ProximaModal>
type SlotProps = {
title: string
closeAriaLabel: string
isOpen: boolean
isNativeDialog: boolean
hasHeader: boolean
hasContent: boolean
hasFooter: boolean
hasActions: boolean
hasAside: boolean
showModal: () => void
hideModal: () => void
scrollTop: () => void
}
Expose Permalink to "Expose"
<ProximaModal ref="modal">
content ...
</ProximaModal>
import { ref, unref, onMounted } from 'vue';
import ProximaModal from 'proxima-vue/modal';
const modal = ref({} as InstanceType<typeof ProximaModal>);
onMounted(() => {
unref(modal).checkOpen(); // false
});
type ProximaModalInstance = {
showModal: () => void
hideModal: () => void
scrollTop: () => void
checkOpen: () => boolean
}