Перейти до вмісту
tatetUI

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

НазваТипТиповоОпис
parallaxnumber28На скільки (% ширини) нижній екран відступає під верхній.
baseClassstring"bg-card"Класи нижнього екрана — тло й розкладка вмісту.
pageClassstring"bg-card"Класи верхнього екрана — тло й розкладка вмісту.
openbooleanfalseВерхній екран відкрито: він в'їжджає справа поверх нижнього.
swipebooleantrueСвайп праворуч з будь-якого місця верхнього екрана повертає назад. Вимкніть для екрана, що сам обробляє горизонтальні жести (полотно, карта) — з таких елементів жест і так не починається.

Події

НазваPayloadОпис
back[]Свайп довів верхній екран за край — закрийте його (зазвичай крок назад у роутері).

Слоти

НазваОпис
baseНижній екран — корінь стеку (список). Лишається змонтованим під верхнім: повернення миттєве, прокрутка й пошук на місці.
pageВерхній екран (деталі). Рендериться, поки `open`.

Доступно через ref

НазваТипОпис
reset() => voidСкинути стан жесту — наприклад, коли батько закрив екран сам.

Коли використовувати

Для телефонної розкладки «список → деталі»: чати й переписка, пошта й лист, налаштування й підрозділ. На широкому екрані ті самі екрани зазвичай стоять поруч (UiResizablePanels), і стек не потрібен.

Коли НЕ використовувати

Не для глибокої навігації: стек має рівно два рівні. Третій рівень (деталі деталей) — ще один UiSwipeStack чи useSwipeStack усередині верхнього екрана. Не для модальних дій — це UiModal чи UiDrawer: вони мають затемнений фон, пастку фокуса й Escape.

Доступність

Нижній екран, поки на ньому стоїть верхній, позначено inert: до нього не доходять ні Tab, ні скрінрідер, а після завершення переходу він ще й невидимий. Свайп — додатковий шлях: у верхньому екрані має бути видима кнопка «Назад».