Modal

Базовый компонент модального окна. Basic modal component.

Import Permalink to "Import"

js
import ProximaModal from 'proxima-vue/modal';

Playground Permalink to "Playground"

Props
View
Footer align
ts
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();
html
<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:

  1. Не работает в Safari (в 2023 году точно) Doesn't work in Safari (in 2023 for sure)
  2. Если у модального окна много контента и есть свой скролл, то в 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:

js
// 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):

CSS Permalink to "CSS"

Слой компонента @layer proxima.modal, также вы можете гибко cтилизовать компонент через кастомные свойства: Component layer @layer proxima.modal, also you can style the component flexibly through custom properties:

css
--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

js
{
  modalCloseAriaLabel: "Close the modal",
}
Props Permalink to "Props"
ts
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"
html
<ProximaModal
  @open="onModalOpen"
  @close="onModalClose"
  @cancel="onModalCancel"
  @before:open="onBeforeModalOpen"
  @before:close="onBeforeModalClose"
/>
js
const onModalOpen = () => {};
const onModalClose = (event) => {};
const onModalCancel = (event) => {};
const onBeforeModalOpen = () => {};
const onBeforeModalClose = () => {};
ts
type Emits = {
  'open': () => void
  'close': (event: Event) => void
  'cancel': (event: Event) => void
  'before:open': () => void
  'before:close': () => void
}
Slots Permalink to "Slots"
html
<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>
ts
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"
html
<ProximaModal ref="modal">
  content ...
</ProximaModal>
ts
import { ref, unref, onMounted } from 'vue';
import ProximaModal from 'proxima-vue/modal';

const modal = ref({} as InstanceType<typeof ProximaModal>);

onMounted(() => {
  unref(modal).checkOpen(); // false
});
ts
type ProximaModalInstance = {
  showModal: () => void
  hideModal: () => void
  scrollTop: () => void
  checkOpen: () => boolean
}
View source on GitHub