SwipeStack
Стек двох екранів на телефоні — деталі в'їжджають поверх списку, свайп праворуч повертає назад.
Навігація телефона як у нативному застосунку: верхній екран (деталі)
в'їжджає справа, нижній (список) відступає ліворуч у тінь і лишається
змонтованим — повернення миттєве, прокрутка й пошук на місці. Свайп
праворуч з будь-якого місця верхнього екрана тягне його за пальцем, і під
ним видно нижній. Відпустили далі ~40% ширини чи кидком — компонент
доводить екран за край і шле back; кидок назад чи коротший рух повертає
екран на місце.
Приклад
Стан тримає батько: open — чи відкритий верхній екран, а на back він
його закриває. Зазвичай це роутер: open — «маршрут не корінь», back —
router.back(). Тоді і системне «назад» браузера, і кнопка «Назад» у шапці
дають ту саму анімацію.
<UiSwipeStack :open="route.name !== 'home'" class="h-full" @back="router.back()">
<template #base><ChatList /></template>
<template #page><RouterView /></template>
</UiSwipeStack>
Жест
Свайп не починається з полів, повзунків, полотна й елементів із
touch-action: none — вони самі обробляють палець; для решти є
data-no-swipe і проп swipe. Області вертикальної прокрутки всередині
верхнього екрана (overflow-y-auto) компонент сам переводить у
touch-action: pan-y pinch-zoom: без цього кожна з них забирала б
горизонтальний рух собі. Горизонтальні області прокрутки (код, вкладки)
лишаються як є — там палець прокручує вбік.
Власний свайп «назад» браузера (Safari на iPhone, жест Android) уже показує
свою анімацію: компонент це бачить (hasUAVisualTransition) і другої
поверх неї не програє. Зі зменшеним рухом (prefers-reduced-motion) екрани
змінюються без зсуву, а жест лишається — його рух веде палець.
Своя розмітка
Коли нижній і верхній екрани не вкладаються в слоти (у розмітки є власні
обгортки й обробники), логіку дає композабл useSwipeStack: стилі обох
екранів, затемнення, тінь і хуки для <Transition> — без глобального CSS.
const stack = useSwipeStack({ open, pageEl, back: () => router.back() })
// <div :style="stack.underStyle.value" :class="stack.underHidden.value ? 'invisible' : ''">…</div>
// <Transition v-bind="stack.transition"><div v-if="open" ref="pageEl" :style="stack.pageStyle.value">…</div></Transition>
API
Props
| Назва | Тип | Типово | Опис |
|---|---|---|---|
parallax | number | 28 | На скільки (% ширини) нижній екран відступає під верхній. |
baseClass | string | "bg-card" | Класи нижнього екрана — тло й розкладка вмісту. |
pageClass | string | "bg-card" | Класи верхнього екрана — тло й розкладка вмісту. |
open | boolean | false | Верхній екран відкрито: він в'їжджає справа поверх нижнього. |
swipe | boolean | true | Свайп праворуч з будь-якого місця верхнього екрана повертає назад. Вимкніть для екрана, що сам обробляє горизонтальні жести (полотно, карта) — з таких елементів жест і так не починається. |
Події
| Назва | Payload | Опис |
|---|---|---|
back | [] | Свайп довів верхній екран за край — закрийте його (зазвичай крок назад у роутері). |
Слоти
| Назва | Опис |
|---|---|
base | Нижній екран — корінь стеку (список). Лишається змонтованим під верхнім: повернення миттєве, прокрутка й пошук на місці. |
page | Верхній екран (деталі). Рендериться, поки `open`. |
Доступно через ref
| Назва | Тип | Опис |
|---|---|---|
reset | () => void | Скинути стан жесту — наприклад, коли батько закрив екран сам. |
Коли використовувати
Для телефонної розкладки «список → деталі»: чати й переписка, пошта й лист,
налаштування й підрозділ. На широкому екрані ті самі екрани зазвичай стоять
поруч (UiResizablePanels), і стек не потрібен.
Коли НЕ використовувати
Не для глибокої навігації: стек має рівно два рівні. Третій рівень (деталі
деталей) — ще один UiSwipeStack чи useSwipeStack усередині верхнього
екрана. Не для модальних дій — це UiModal чи UiDrawer: вони мають
затемнений фон, пастку фокуса й Escape.
Доступність
Нижній екран, поки на ньому стоїть верхній, позначено inert: до нього не
доходять ні Tab, ні скрінрідер, а після завершення переходу він ще й
невидимий. Свайп — додатковий шлях: у верхньому екрані має бути видима
кнопка «Назад».