V2: Origin based animations, Gesture Builder, New Demo, non-url-based-routing. Massive improvements.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import {
|
||||
back,
|
||||
forward,
|
||||
gesture,
|
||||
OriginGestureSurface,
|
||||
useOrigin,
|
||||
type OriginChoreography,
|
||||
type OriginGestureDirectedBuilder,
|
||||
type OriginView,
|
||||
} from "@native-vue-router/core-v2";
|
||||
import InstanceCard from "../components/InstanceCard.vue";
|
||||
import { useDemoInstance } from "../lab-state";
|
||||
import {
|
||||
nestedCarouselBack,
|
||||
nestedCarouselForward,
|
||||
nestedCubeBack,
|
||||
nestedCubeForward,
|
||||
nestedDeckBack,
|
||||
nestedDeckForward,
|
||||
} from "../motions";
|
||||
|
||||
type NestedSceneKind = "cooperative" | "vertical" | "conflict";
|
||||
|
||||
const props = defineProps<{
|
||||
kind: NestedSceneKind;
|
||||
index: number;
|
||||
total: number;
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
accent: string;
|
||||
next?: () => OriginView;
|
||||
}>();
|
||||
|
||||
const origin = useOrigin();
|
||||
const identity = useDemoInstance(`Nested ${props.kind} ${props.index + 1}`);
|
||||
const localLikes = ref(0);
|
||||
|
||||
const vertical = props.kind === "vertical";
|
||||
const forwardMotion: OriginChoreography = vertical
|
||||
? nestedDeckForward
|
||||
: props.kind === "conflict"
|
||||
? nestedCubeForward
|
||||
: nestedCarouselForward;
|
||||
const backMotion: OriginChoreography = vertical
|
||||
? nestedDeckBack
|
||||
: props.kind === "conflict"
|
||||
? nestedCubeBack
|
||||
: nestedCarouselBack;
|
||||
|
||||
/*
|
||||
* Cooperative cases deliberately leave the outer page's first 44 pixels
|
||||
* untouched. The conflict case starts anywhere, so its pointer-down handler
|
||||
* stops propagation before it knows whether `back()` is available.
|
||||
*/
|
||||
function movementBuilder(
|
||||
direction: "forward" | "back",
|
||||
): OriginGestureDirectedBuilder {
|
||||
const to = vertical
|
||||
? direction === "forward"
|
||||
? "up"
|
||||
: "down"
|
||||
: direction === "forward"
|
||||
? "left"
|
||||
: "right";
|
||||
const start =
|
||||
props.kind === "conflict"
|
||||
? gesture.to
|
||||
: gesture.from.when(({ point, bounds }) => {
|
||||
const parentRail = Math.min(52, Math.max(22, bounds.width * 0.12));
|
||||
return point.localX > parentRail;
|
||||
}).to;
|
||||
return start[to]();
|
||||
}
|
||||
|
||||
const forwardGesture = movementBuilder("forward")
|
||||
.navigate(() => {
|
||||
const target = props.next?.();
|
||||
return target ? forward(target) : null;
|
||||
})
|
||||
.animate(forwardMotion);
|
||||
|
||||
const backGesture = movementBuilder("back")
|
||||
.navigate((context) => (context.canGoBack ? back() : null))
|
||||
.animate(backMotion);
|
||||
|
||||
const gestures = [forwardGesture, backGesture] as const;
|
||||
|
||||
function nextWithButton() {
|
||||
const target = props.next?.();
|
||||
if (target) void origin.perform(forward(target, forwardMotion));
|
||||
}
|
||||
|
||||
function backWithButton() {
|
||||
if (origin.context.value.canGoBack) void origin.perform(back(backMotion));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OriginGestureSurface
|
||||
as="article"
|
||||
class="nested-slide"
|
||||
:class="`nested-slide--${kind}`"
|
||||
:data-nested-kind="kind"
|
||||
:data-nested-index="index"
|
||||
:style="{ '--nested-accent': accent }"
|
||||
:gestures="gestures"
|
||||
>
|
||||
<div class="nested-slide__orb" aria-hidden="true" />
|
||||
|
||||
<header>
|
||||
<span>{{ eyebrow }}</span>
|
||||
<strong>{{ index + 1 }} / {{ total }}</strong>
|
||||
</header>
|
||||
|
||||
<div class="nested-slide__copy">
|
||||
<h3>{{ title }}</h3>
|
||||
<p>{{ description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="nested-slide__state">
|
||||
<InstanceCard
|
||||
label="Nested instance"
|
||||
:instance="identity.instance"
|
||||
:status="identity.status.value"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
data-origin-gesture="ignore"
|
||||
@click="localLikes += 1"
|
||||
>
|
||||
Local state · {{ localLikes }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<button
|
||||
type="button"
|
||||
data-origin-gesture="ignore"
|
||||
:disabled="!origin.canGoBack.value"
|
||||
@click="backWithButton"
|
||||
>
|
||||
{{ vertical ? "↓ Previous" : "← Previous" }}
|
||||
</button>
|
||||
|
||||
<div class="nested-slide__dots" aria-label="Slide position">
|
||||
<span
|
||||
v-for="dot in total"
|
||||
:key="dot"
|
||||
:class="{ active: dot === index + 1 }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-origin-gesture="ignore"
|
||||
:disabled="!next"
|
||||
@click="nextWithButton"
|
||||
>
|
||||
{{ vertical ? "Next ↑" : "Next →" }}
|
||||
</button>
|
||||
</footer>
|
||||
</OriginGestureSurface>
|
||||
</template>
|
||||
Reference in New Issue
Block a user