Increase complexity of test demo
This commit is contained in:
@@ -153,6 +153,57 @@ export const fall = defineOriginChoreography({
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* A connected presentation rather than a full-screen route replacement.
|
||||
*
|
||||
* At rest the page has moved right by two thirds, leaving its left third
|
||||
* visible in the exposed rail. `persistAtRest` keeps these progress-1 effects
|
||||
* connected after commit instead of parking and hiding the page.
|
||||
*/
|
||||
export const partialDrawerOpen = defineOriginChoreography({
|
||||
name: "partial-drawer-open",
|
||||
persistAtRest: true,
|
||||
commitThreshold: 0.28,
|
||||
commitVelocity: 0.68,
|
||||
effects: ({ progress }) => ({
|
||||
source: {
|
||||
transform: `translate3d(${percent(progress * (2 / 3))}, 0, 0)`,
|
||||
style: {
|
||||
filter: `brightness(${1 - progress * 0.22})`,
|
||||
},
|
||||
},
|
||||
target: {
|
||||
transform: `translate3d(${percent((progress - 1) * (2 / 3))}, 0, 0)`,
|
||||
opacity: 0.35 + progress * 0.65,
|
||||
style: {
|
||||
boxShadow: `24px 0 70px rgb(0 0 0 / ${progress * 0.36})`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
/** Reciprocal close motion for the connected two-thirds drawer. */
|
||||
export const partialDrawerClose = defineOriginChoreography({
|
||||
name: "partial-drawer-close",
|
||||
commitThreshold: 0.28,
|
||||
commitVelocity: 0.68,
|
||||
effects: ({ progress }) => ({
|
||||
source: {
|
||||
transform: `translate3d(${percent(progress * (-2 / 3))}, 0, 0)`,
|
||||
opacity: 1 - progress * 0.65,
|
||||
style: {
|
||||
boxShadow: `24px 0 70px rgb(0 0 0 / ${(1 - progress) * 0.36})`,
|
||||
},
|
||||
},
|
||||
target: {
|
||||
transform: `translate3d(${percent((1 - progress) * (2 / 3))}, 0, 0)`,
|
||||
style: {
|
||||
filter: `brightness(${0.78 + progress * 0.22})`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Reveal a full-screen overlay from above while its actual dialog remains
|
||||
* anchored to the left. The gesture's custom start predicate lives in the
|
||||
|
||||
@@ -343,6 +343,14 @@ summary:focus-visible {
|
||||
--lab-glow: #438cff;
|
||||
}
|
||||
|
||||
.lab-card--teal {
|
||||
--lab-glow: #4fffd0;
|
||||
}
|
||||
|
||||
.lab-card--orange {
|
||||
--lab-glow: #ff7b42;
|
||||
}
|
||||
|
||||
.lab-number {
|
||||
color: rgb(255 255 255 / 42%);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
@@ -401,6 +409,444 @@ summary:focus-visible {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
/* Replace checkout */
|
||||
|
||||
.checkout-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: max(1.25rem, env(safe-area-inset-top))
|
||||
max(1.25rem, env(safe-area-inset-right))
|
||||
max(1.25rem, env(safe-area-inset-bottom))
|
||||
max(1.25rem, env(safe-area-inset-left));
|
||||
overflow: auto;
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 15% 20%,
|
||||
rgb(34 101 100 / 34%),
|
||||
transparent 32rem
|
||||
),
|
||||
linear-gradient(145deg, #07100f, #0d1518 55%, #07100f);
|
||||
}
|
||||
|
||||
.checkout-toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
width: min(100%, 68rem);
|
||||
margin: 0 auto;
|
||||
color: rgb(255 255 255 / 62%);
|
||||
font-size: 0.76rem;
|
||||
font-weight: 720;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.checkout-toolbar button {
|
||||
justify-self: start;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border: 1px solid rgb(255 255 255 / 16%);
|
||||
border-radius: 50%;
|
||||
background: rgb(255 255 255 / 7%);
|
||||
}
|
||||
|
||||
.checkout-step {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.checkout-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(20rem, 27rem);
|
||||
align-items: center;
|
||||
gap: clamp(2rem, 7vw, 7rem);
|
||||
width: min(100%, 68rem);
|
||||
min-height: calc(100% - 3rem);
|
||||
margin: 0 auto;
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.checkout-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.checkout-copy h1,
|
||||
.confirmation-card h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(3.4rem, 8vw, 7rem);
|
||||
line-height: 0.9;
|
||||
letter-spacing: -0.065em;
|
||||
}
|
||||
|
||||
.checkout-copy p,
|
||||
.confirmation-card p {
|
||||
max-width: 34rem;
|
||||
margin: 0 0 0.5rem;
|
||||
color: rgb(255 255 255 / 61%);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.payment-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
border: 1px solid rgb(255 255 255 / 14%);
|
||||
border-radius: 1.8rem;
|
||||
padding: 1.4rem;
|
||||
background: rgb(255 255 255 / 7%);
|
||||
box-shadow: 0 2rem 6rem rgb(0 0 0 / 32%);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.payment-card label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 0.42rem;
|
||||
color: rgb(255 255 255 / 48%);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 720;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.payment-card input {
|
||||
width: 100%;
|
||||
border: 1px solid rgb(255 255 255 / 13%);
|
||||
border-radius: 0.8rem;
|
||||
padding: 0.8rem;
|
||||
color: white;
|
||||
background: rgb(0 0 0 / 18%);
|
||||
}
|
||||
|
||||
.order-line,
|
||||
.payment-row,
|
||||
.card-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.order-line {
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid rgb(255 255 255 / 10%);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.order-line strong {
|
||||
color: #7bffd4;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.card-number {
|
||||
border: 1px solid rgb(255 255 255 / 13%);
|
||||
border-radius: 0.8rem;
|
||||
padding-left: 0.8rem;
|
||||
color: rgb(255 255 255 / 38%);
|
||||
background: rgb(0 0 0 / 18%);
|
||||
}
|
||||
|
||||
.card-number input {
|
||||
min-width: 4rem;
|
||||
border: 0;
|
||||
padding-left: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.pay-button,
|
||||
.confirmation-action {
|
||||
border: 0;
|
||||
border-radius: 0.9rem;
|
||||
padding: 0.9rem 1.1rem;
|
||||
color: #06110d;
|
||||
background: #70ffd0;
|
||||
font-weight: 820;
|
||||
}
|
||||
|
||||
.pay-button:disabled {
|
||||
cursor: wait;
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
.payment-card > small,
|
||||
.confirmation-card > small {
|
||||
align-self: center;
|
||||
color: rgb(255 255 255 / 40%);
|
||||
}
|
||||
|
||||
.confirmation-view {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.confirmation-card {
|
||||
display: flex;
|
||||
width: min(100%, 44rem);
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.confirmation-mark {
|
||||
display: grid;
|
||||
width: 4.5rem;
|
||||
height: 4.5rem;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
color: #06110d;
|
||||
background: #70ffd0;
|
||||
box-shadow: 0 0 0 0.8rem rgb(112 255 208 / 9%);
|
||||
font-size: 2rem;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.confirmation-card code {
|
||||
color: #8edfff;
|
||||
}
|
||||
|
||||
.confirmation-card dl {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
width: 100%;
|
||||
margin: 0.5rem 0;
|
||||
border: 1px solid rgb(255 255 255 / 13%);
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.confirmation-card dl > div {
|
||||
padding: 0.9rem;
|
||||
background: rgb(255 255 255 / 5%);
|
||||
}
|
||||
|
||||
.confirmation-card dl > div + div {
|
||||
border-left: 1px solid rgb(255 255 255 / 10%);
|
||||
}
|
||||
|
||||
.confirmation-card dt {
|
||||
color: rgb(255 255 255 / 42%);
|
||||
font-size: 0.64rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.confirmation-card dd {
|
||||
margin: 0.3rem 0 0;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
|
||||
/* Connected partial drawer */
|
||||
|
||||
.partial-origin-view {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #10131a;
|
||||
}
|
||||
|
||||
.partial-origin-rail {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
padding: max(1.5rem, env(safe-area-inset-top)) clamp(0.8rem, 3vw, 2rem)
|
||||
max(1.5rem, env(safe-area-inset-bottom));
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 20% 15%,
|
||||
rgb(255 132 74 / 30%),
|
||||
transparent 18rem
|
||||
),
|
||||
#19151a;
|
||||
}
|
||||
|
||||
.partial-brand {
|
||||
display: grid;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
place-items: center;
|
||||
border-radius: 0.9rem;
|
||||
color: #261006;
|
||||
background: #ff8c57;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.partial-origin-rail nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.partial-origin-rail nav button {
|
||||
border: 0;
|
||||
border-radius: 0.7rem;
|
||||
padding: 0.75rem;
|
||||
color: rgb(255 255 255 / 62%);
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.partial-origin-rail nav button:first-child {
|
||||
color: white;
|
||||
background: rgb(255 255 255 / 9%);
|
||||
}
|
||||
|
||||
.partial-origin-rail .instance-card {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.partial-origin-rail .instance-card strong {
|
||||
grid-row: auto;
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.partial-origin-content {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
padding: max(1.5rem, env(safe-area-inset-top))
|
||||
max(1.5rem, env(safe-area-inset-right))
|
||||
max(1.5rem, env(safe-area-inset-bottom)) clamp(1.5rem, 5vw, 5rem);
|
||||
background:
|
||||
linear-gradient(rgb(255 255 255 / 3%) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgb(255 255 255 / 3%) 1px, transparent 1px), #0b0e14;
|
||||
background-size: 48px 48px;
|
||||
}
|
||||
|
||||
.partial-origin-content > header,
|
||||
.partial-drawer-panel > header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.partial-origin-content > header button,
|
||||
.partial-drawer-panel > header button {
|
||||
border: 1px solid rgb(255 255 255 / 14%);
|
||||
border-radius: 999px;
|
||||
padding: 0.55rem 0.8rem;
|
||||
color: white;
|
||||
background: rgb(255 255 255 / 7%);
|
||||
}
|
||||
|
||||
.partial-origin-content > header span {
|
||||
color: rgb(255 255 255 / 38%);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.partial-origin-copy {
|
||||
display: flex;
|
||||
max-width: 48rem;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.partial-origin-copy h1,
|
||||
.partial-drawer-panel h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(3.5rem, 8vw, 7rem);
|
||||
line-height: 0.9;
|
||||
letter-spacing: -0.065em;
|
||||
}
|
||||
|
||||
.partial-origin-copy p,
|
||||
.partial-drawer-panel > p {
|
||||
max-width: 38rem;
|
||||
margin: 0;
|
||||
color: rgb(255 255 255 / 62%);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.partial-drawer-layer {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.partial-drawer-panel {
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 1.4rem;
|
||||
padding: max(1.5rem, env(safe-area-inset-top)) clamp(1.2rem, 5vw, 4rem)
|
||||
max(1.5rem, env(safe-area-inset-bottom))
|
||||
max(1.2rem, env(safe-area-inset-left));
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 12% 12%,
|
||||
rgb(255 141 88 / 28%),
|
||||
transparent 25rem
|
||||
),
|
||||
#18151b;
|
||||
box-shadow: 24px 0 80px rgb(0 0 0 / 35%);
|
||||
}
|
||||
|
||||
.partial-drawer-panel > header > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.partial-drawer-panel > header button {
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
padding: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.partial-drawer-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.partial-drawer-list button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
border: 1px solid rgb(255 255 255 / 10%);
|
||||
border-radius: 1rem;
|
||||
padding: 1rem;
|
||||
color: white;
|
||||
text-align: left;
|
||||
background: rgb(255 255 255 / 5%);
|
||||
}
|
||||
|
||||
.partial-drawer-list button span {
|
||||
color: #ff9b6c;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.partial-drawer-panel > small {
|
||||
color: rgb(255 255 255 / 38%);
|
||||
}
|
||||
|
||||
.partial-drawer-backdrop {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: rgb(0 0 0 / 18%);
|
||||
cursor: w-resize;
|
||||
}
|
||||
|
||||
/* Scene inspector */
|
||||
|
||||
.scene-debug {
|
||||
@@ -501,6 +947,10 @@ summary:focus-visible {
|
||||
color: #8edfff;
|
||||
}
|
||||
|
||||
.node-state--exposed {
|
||||
color: #ffad7f;
|
||||
}
|
||||
|
||||
.debug-content code {
|
||||
color: #8edfff;
|
||||
}
|
||||
@@ -1949,6 +2399,78 @@ summary:focus-visible {
|
||||
.nested-page-gesture {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkout-layout {
|
||||
grid-template-columns: 1fr;
|
||||
align-items: start;
|
||||
padding-top: 4rem;
|
||||
}
|
||||
|
||||
.checkout-copy h1,
|
||||
.confirmation-card h1 {
|
||||
font-size: clamp(3.2rem, 16vw, 5.4rem);
|
||||
}
|
||||
|
||||
.checkout-step {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkout-toolbar {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.confirmation-card dl {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.confirmation-card dl > div + div {
|
||||
border-top: 1px solid rgb(255 255 255 / 10%);
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.partial-origin-rail {
|
||||
gap: 1rem;
|
||||
padding-right: 0.6rem;
|
||||
padding-left: 0.6rem;
|
||||
}
|
||||
|
||||
.partial-origin-rail nav button {
|
||||
padding: 0.55rem 0.35rem;
|
||||
font-size: 0.66rem;
|
||||
}
|
||||
|
||||
.partial-origin-rail .instance-card {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.partial-origin-content {
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
.partial-origin-copy h1,
|
||||
.partial-drawer-panel h1 {
|
||||
font-size: clamp(2.8rem, 13vw, 4.8rem);
|
||||
}
|
||||
|
||||
.partial-drawer-panel {
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.partial-drawer-panel > header {
|
||||
position: relative;
|
||||
padding-right: 2.8rem;
|
||||
}
|
||||
|
||||
.partial-drawer-panel > header button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.partial-drawer-panel h1 {
|
||||
font-size: clamp(2.15rem, 10vw, 3.25rem);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 700px) {
|
||||
|
||||
@@ -31,13 +31,11 @@ const createSecond = () => above(secondView(), slideLeft);
|
||||
const goBack = (context: OriginContext) =>
|
||||
context.canGoBack ? back(slideRight) : null;
|
||||
|
||||
const forwardGesture = gesture.to
|
||||
.left()
|
||||
const forwardGesture = gesture
|
||||
.to.left()
|
||||
.navigate(() => above(secondView()))
|
||||
.animate(slideLeft);
|
||||
const backGesture = gesture
|
||||
// .from.left("clamp(24px, 8%, 64px)")
|
||||
// .from.left("20%")
|
||||
.to.right()
|
||||
.navigate((context) => (context.canGoBack ? back() : null))
|
||||
.animate(slideRight);
|
||||
|
||||
@@ -17,6 +17,8 @@ import EdgePredicateView from "./EdgePredicateView.vue";
|
||||
import FirstView from "./FirstView.vue";
|
||||
import GalleryView from "./GalleryView.vue";
|
||||
import NestedScenesView from "./NestedScenesView.vue";
|
||||
import PaymentDetailsView from "./PaymentDetailsView.vue";
|
||||
import PartialDrawerPageView from "./PartialDrawerPageView.vue";
|
||||
import PlayerView from "./PlayerView.vue";
|
||||
|
||||
const origin = useOrigin();
|
||||
@@ -60,6 +62,10 @@ const openPredicate = () =>
|
||||
actionFor(EdgePredicateView, "Predicate Edge", focusPortal);
|
||||
const openNestedScenes = () =>
|
||||
actionFor(NestedScenesView, "Nested Scenes", focusPortal);
|
||||
const openCheckout = () =>
|
||||
actionFor(PaymentDetailsView, "Payment Details", slideLeft);
|
||||
const openPartialDrawer = () =>
|
||||
actionFor(PartialDrawerPageView, "Partial Drawer", focusPortal);
|
||||
|
||||
const labs = [
|
||||
{
|
||||
@@ -123,6 +129,24 @@ const labs = [
|
||||
action: openNestedScenes,
|
||||
accent: "blue",
|
||||
},
|
||||
{
|
||||
number: "08",
|
||||
title: "Replace checkout",
|
||||
description:
|
||||
"Confirm a mock payment, replace its mounted entry, then test where back returns.",
|
||||
tags: ["atomic replace", "history rewrite"],
|
||||
action: openCheckout,
|
||||
accent: "teal",
|
||||
},
|
||||
{
|
||||
number: "09",
|
||||
title: "Partial drawer",
|
||||
description:
|
||||
"Push a connected drawer two-thirds across while the live source occupies the final third.",
|
||||
tags: ["persistent effects", "partial view"],
|
||||
action: openPartialDrawer,
|
||||
accent: "orange",
|
||||
},
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
|
||||
74
apps/origins-demo/src/views/OrderConfirmationView.vue
Normal file
74
apps/origins-demo/src/views/OrderConfirmationView.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
OriginGesture,
|
||||
back,
|
||||
gesture,
|
||||
slideRight,
|
||||
useOrigin,
|
||||
} from "@native-vue-router/core-v2";
|
||||
import InstanceCard from "../components/InstanceCard.vue";
|
||||
import { useDemoInstance } from "../lab-state";
|
||||
|
||||
defineProps<{
|
||||
orderReference: string;
|
||||
replacedInstance: string;
|
||||
}>();
|
||||
|
||||
const origin = useOrigin();
|
||||
const identity = useDemoInstance("Confirmation");
|
||||
|
||||
const returnGesture = gesture.from
|
||||
.left("clamp(24px, 8%, 64px)")
|
||||
.to.right()
|
||||
.navigate((context) => (context.canGoBack ? back() : null))
|
||||
.animate(slideRight);
|
||||
|
||||
function returnToLabs() {
|
||||
if (origin.context.value.canGoBack) void origin.perform(back(slideRight));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OriginGesture
|
||||
class="checkout-view confirmation-view"
|
||||
:gesture="returnGesture"
|
||||
>
|
||||
<section class="confirmation-card">
|
||||
<div class="confirmation-mark" aria-hidden="true">✓</div>
|
||||
<div class="eyebrow">Payment complete</div>
|
||||
<h1>Order confirmed.</h1>
|
||||
<p>
|
||||
The Payment Details node <code>{{ replacedInstance }}</code> has been
|
||||
removed. Back now resolves the retained Hub instance directly.
|
||||
</p>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>Order</dt>
|
||||
<dd>{{ orderReference }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>History depth</dt>
|
||||
<dd>{{ origin.context.value.history.length }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Previous view</dt>
|
||||
<dd>{{ origin.context.value.previous?.name ?? "None" }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<InstanceCard
|
||||
label="Confirmation instance"
|
||||
:instance="identity.instance"
|
||||
:status="identity.status.value"
|
||||
/>
|
||||
<button
|
||||
class="confirmation-action"
|
||||
type="button"
|
||||
data-origin-gesture="ignore"
|
||||
@click="returnToLabs"
|
||||
>
|
||||
Back to labs
|
||||
</button>
|
||||
<small>Try the left-edge back gesture too.</small>
|
||||
</section>
|
||||
</OriginGesture>
|
||||
</template>
|
||||
99
apps/origins-demo/src/views/PartialDrawerPageView.vue
Normal file
99
apps/origins-demo/src/views/PartialDrawerPageView.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
OriginGestureSurface,
|
||||
back,
|
||||
forward,
|
||||
gesture,
|
||||
originView,
|
||||
slideRight,
|
||||
useOrigin,
|
||||
} from "@native-vue-router/core-v2";
|
||||
import InstanceCard from "../components/InstanceCard.vue";
|
||||
import { useDemoInstance } from "../lab-state";
|
||||
import { partialDrawerOpen } from "../motions";
|
||||
import PartialDrawerView from "./PartialDrawerView.vue";
|
||||
|
||||
const origin = useOrigin();
|
||||
const identity = useDemoInstance("Drawer page");
|
||||
|
||||
const drawerView = () =>
|
||||
originView(
|
||||
PartialDrawerView,
|
||||
{ openedFrom: identity.instance },
|
||||
{ key: "partial-drawer", name: "Two-thirds Drawer" },
|
||||
);
|
||||
|
||||
const openDrawerGesture = gesture.from
|
||||
.left("clamp(32px, 10%, 88px)")
|
||||
.to.right()
|
||||
.navigate(() => forward(drawerView()))
|
||||
.animate(partialDrawerOpen);
|
||||
|
||||
const gestures = [openDrawerGesture] as const;
|
||||
|
||||
function openDrawer() {
|
||||
void origin.perform(forward(drawerView(), partialDrawerOpen));
|
||||
}
|
||||
|
||||
function returnToLabs() {
|
||||
if (origin.context.value.canGoBack) void origin.perform(back(slideRight));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OriginGestureSurface
|
||||
as="main"
|
||||
class="partial-origin-view"
|
||||
:gestures="gestures"
|
||||
>
|
||||
<!--
|
||||
This rail is the left third of the full page. Once the whole page moves
|
||||
right by 2/3, this exact live DOM remains visible in the final third.
|
||||
-->
|
||||
<aside class="partial-origin-rail">
|
||||
<div class="partial-brand">OV</div>
|
||||
<nav aria-label="Example navigation">
|
||||
<button type="button" data-origin-gesture="ignore">Inbox</button>
|
||||
<button type="button" data-origin-gesture="ignore">Projects</button>
|
||||
<button type="button" data-origin-gesture="ignore">Archive</button>
|
||||
</nav>
|
||||
<InstanceCard
|
||||
label="Page instance"
|
||||
:instance="identity.instance"
|
||||
:status="identity.status.value"
|
||||
/>
|
||||
</aside>
|
||||
|
||||
<section class="partial-origin-content">
|
||||
<header>
|
||||
<button
|
||||
type="button"
|
||||
data-origin-gesture="ignore"
|
||||
aria-label="Return to labs"
|
||||
@click="returnToLabs"
|
||||
>
|
||||
← Labs
|
||||
</button>
|
||||
<span>Connected presentation</span>
|
||||
</header>
|
||||
|
||||
<div class="partial-origin-copy">
|
||||
<div class="eyebrow">Partial route lab</div>
|
||||
<h1>Keep one third of this page on screen.</h1>
|
||||
<p>
|
||||
Drag right from the left edge. The drawer becomes a real history
|
||||
entry, while this same mounted page remains translated and visible.
|
||||
</p>
|
||||
<button
|
||||
class="primary-action"
|
||||
type="button"
|
||||
data-origin-gesture="ignore"
|
||||
@click="openDrawer"
|
||||
>
|
||||
Open two-thirds drawer
|
||||
</button>
|
||||
<div class="gesture-hint"><span>→</span> Pull from the left edge</div>
|
||||
</div>
|
||||
</section>
|
||||
</OriginGestureSurface>
|
||||
</template>
|
||||
84
apps/origins-demo/src/views/PartialDrawerView.vue
Normal file
84
apps/origins-demo/src/views/PartialDrawerView.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
OriginGesture,
|
||||
back,
|
||||
gesture,
|
||||
useOrigin,
|
||||
} from "@native-vue-router/core-v2";
|
||||
import InstanceCard from "../components/InstanceCard.vue";
|
||||
import { useDemoInstance } from "../lab-state";
|
||||
import { partialDrawerClose } from "../motions";
|
||||
|
||||
defineProps<{ openedFrom: string }>();
|
||||
|
||||
const origin = useOrigin();
|
||||
const identity = useDemoInstance("Drawer");
|
||||
|
||||
const closeGesture = gesture.to
|
||||
.left()
|
||||
.navigate((context) => (context.canGoBack ? back() : null))
|
||||
.animate(partialDrawerClose);
|
||||
|
||||
function closeDrawer() {
|
||||
if (origin.context.value.canGoBack)
|
||||
void origin.perform(back(partialDrawerClose));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OriginGesture class="partial-drawer-layer" :gesture="closeGesture">
|
||||
<aside class="partial-drawer-panel">
|
||||
<header>
|
||||
<div>
|
||||
<div class="eyebrow">Two-thirds destination</div>
|
||||
<h1>Workspace</h1>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-origin-gesture="ignore"
|
||||
aria-label="Close drawer"
|
||||
@click="closeDrawer"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<p>
|
||||
This drawer is its own Vue view and history entry. It did not copy,
|
||||
teleport, or remount the page visible beside it.
|
||||
</p>
|
||||
|
||||
<div class="partial-drawer-list">
|
||||
<button type="button" data-origin-gesture="ignore">
|
||||
<span>01</span>
|
||||
Recent activity
|
||||
</button>
|
||||
<button type="button" data-origin-gesture="ignore">
|
||||
<span>02</span>
|
||||
Shared with me
|
||||
</button>
|
||||
<button type="button" data-origin-gesture="ignore">
|
||||
<span>03</span>
|
||||
Offline files
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<InstanceCard
|
||||
label="Drawer instance"
|
||||
:instance="identity.instance"
|
||||
:status="identity.status.value"
|
||||
/>
|
||||
<small>
|
||||
Opened over {{ openedFrom }} · swipe left or tap the exposed third to
|
||||
close.
|
||||
</small>
|
||||
</aside>
|
||||
|
||||
<button
|
||||
class="partial-drawer-backdrop"
|
||||
type="button"
|
||||
aria-label="Close drawer from exposed page"
|
||||
@click="closeDrawer"
|
||||
/>
|
||||
</OriginGesture>
|
||||
</template>
|
||||
156
apps/origins-demo/src/views/PaymentDetailsView.vue
Normal file
156
apps/origins-demo/src/views/PaymentDetailsView.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import {
|
||||
OriginGestureSurface,
|
||||
back,
|
||||
gesture,
|
||||
originView,
|
||||
replace,
|
||||
slideLeft,
|
||||
slideRight,
|
||||
useOrigin,
|
||||
} from "@native-vue-router/core-v2";
|
||||
import InstanceCard from "../components/InstanceCard.vue";
|
||||
import { useDemoInstance } from "../lab-state";
|
||||
import OrderConfirmationView from "./OrderConfirmationView.vue";
|
||||
|
||||
const origin = useOrigin();
|
||||
const identity = useDemoInstance("Payment");
|
||||
const cardholder = ref("Ada Lovelace");
|
||||
const lastFour = ref("4242");
|
||||
const processing = ref(false);
|
||||
|
||||
function confirmationView() {
|
||||
return originView(
|
||||
OrderConfirmationView,
|
||||
{
|
||||
orderReference: `NVO-${Math.floor(Date.now() / 1000)
|
||||
.toString()
|
||||
.slice(-6)}`,
|
||||
replacedInstance: identity.instance,
|
||||
},
|
||||
{ key: "order-confirmation", name: "Order Confirmation" },
|
||||
);
|
||||
}
|
||||
|
||||
const confirmGesture = gesture.to
|
||||
.left()
|
||||
.navigate(() => replace(confirmationView()))
|
||||
.animate(slideLeft);
|
||||
|
||||
const backGesture = gesture.from
|
||||
.left("clamp(24px, 8%, 64px)")
|
||||
.to.right()
|
||||
.navigate((context) => (context.canGoBack ? back() : null))
|
||||
.animate(slideRight);
|
||||
|
||||
const gestures = [confirmGesture, backGesture] as const;
|
||||
|
||||
function returnToLabs() {
|
||||
if (origin.context.value.canGoBack) void origin.perform(back(slideRight));
|
||||
}
|
||||
|
||||
async function pay() {
|
||||
if (processing.value) return;
|
||||
processing.value = true;
|
||||
|
||||
// `replace` is one atomic scene operation. Payment Details stays mounted
|
||||
// throughout the animation and is removed only after the commit completes.
|
||||
await origin.perform(replace(confirmationView(), slideLeft));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OriginGestureSurface
|
||||
as="main"
|
||||
class="checkout-view payment-view"
|
||||
:gestures="gestures"
|
||||
>
|
||||
<header class="checkout-toolbar">
|
||||
<button
|
||||
type="button"
|
||||
data-origin-gesture="ignore"
|
||||
aria-label="Return to labs"
|
||||
@click="returnToLabs"
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<span>Secure checkout</span>
|
||||
<span class="checkout-step">Step 2 of 2</span>
|
||||
</header>
|
||||
|
||||
<section class="checkout-layout">
|
||||
<div class="checkout-copy">
|
||||
<div class="eyebrow">Replace-history lab</div>
|
||||
<h1>Payment details</h1>
|
||||
<p>
|
||||
Completing this mock payment replaces this mounted entry. The
|
||||
confirmation page will point straight back to the Hub.
|
||||
</p>
|
||||
<InstanceCard
|
||||
label="Payment instance"
|
||||
:instance="identity.instance"
|
||||
:status="identity.status.value"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<form class="payment-card" @submit.prevent="pay">
|
||||
<div class="order-line">
|
||||
<span>Origin Pro</span>
|
||||
<strong>$48.00</strong>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
Cardholder
|
||||
<input
|
||||
v-model="cardholder"
|
||||
data-origin-gesture="ignore"
|
||||
autocomplete="cc-name"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Card number
|
||||
<span class="card-number">
|
||||
<b>•••• •••• ••••</b>
|
||||
<input
|
||||
v-model="lastFour"
|
||||
data-origin-gesture="ignore"
|
||||
inputmode="numeric"
|
||||
maxlength="4"
|
||||
autocomplete="cc-number"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
<div class="payment-row">
|
||||
<label>
|
||||
Expiry
|
||||
<input
|
||||
data-origin-gesture="ignore"
|
||||
value="08 / 29"
|
||||
autocomplete="cc-exp"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
CVC
|
||||
<input
|
||||
data-origin-gesture="ignore"
|
||||
value="123"
|
||||
maxlength="4"
|
||||
autocomplete="cc-csc"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="pay-button"
|
||||
type="submit"
|
||||
data-origin-gesture="ignore"
|
||||
:disabled="processing"
|
||||
>
|
||||
{{ processing ? "Confirming…" : "Pay $48.00" }}
|
||||
</button>
|
||||
<small>Or swipe left anywhere outside the form controls.</small>
|
||||
</form>
|
||||
</section>
|
||||
</OriginGestureSurface>
|
||||
</template>
|
||||
Reference in New Issue
Block a user