sdxc

[ @sdxc/ui ]

SharedElement

A plain host that carries a stable view-transition identity across separate page loads.

Pick a cover.
View code
let openSlug: string | null = null;

/** The same call viewTransition() makes for a Frame reload. */
function open(slug: string | null) {
	openSlug = slug;

	if (typeof document.startViewTransition !== "function") {
		void handle.update();
		return;
	}

	document.startViewTransition(() => handle.update());
}

openCover === undefined ? (
	<div mix={[vstack({ gap: 3, align: "stretch" })]}>
		<Text>Pick a cover.</Text>
		<div mix={[grid(), gridTemplate({ columns: "repeat(3, 1fr)" }), gap(3)]}>
			{covers.map((cover) => (
				<Button
					key={cover.slug}
					variant="ghost"
					mix={[p(0), bs("5rem"), on<HTMLButtonElement, "click">("click", () => open(cover.slug))]}
				>
					<SharedElement
						id={`preview-cover-${cover.slug}`}
						mix={[
							is("100%"),
							bs("100%"),
							rounded("lg"),
							bg(`${cover.tint}.tint`),
							fg(cover.tint),
							p(2),
							text("xs"),
							weight("medium"),
							css({ display: "grid", placeItems: "center", textAlign: "center" }),
						]}
					>
						{cover.title}
					</SharedElement>
				</Button>
			))}
		</div>
	</div>
) : (
	<div mix={[vstack({ gap: 3, align: "stretch" })]}>
		<SharedElement
			id={`preview-cover-${openCover.slug}`}
			mix={[
				is("100%"),
				bs("9rem"),
				rounded("lg"),
				bg(`${openCover.tint}.tint`),
				fg(openCover.tint),
				p(4),
				text("xl"),
				weight("semibold"),
				css({ display: "grid", placeItems: "center", textAlign: "center" }),
			]}
		>
			{openCover.title}
		</SharedElement>
		<Text>The same id on both views, so the browser morphs it instead of swapping it.</Text>
		<div mix={[hstack({ gap: 2, align: "center" })]}>
			<Button
				variant="outline"
				size="sm"
				mix={[on<HTMLButtonElement, "click">("click", () => open(null))]}
			>
				Back to the grid
			</Button>
		</div>
	</div>
)

Installation

An ordinary dependency: install it, import the theme once, and import the component where it is used.

npm add @sdxc/ui remix
import "@sdxc/ui/theme.css";

Usage

import { SharedElement } from "@sdxc/ui";

Renders a plain <div> host whose view-transition-name comes from its own id, so a same-origin, cross-document navigation matching that id on the other side morphs the element instead of replacing it outright.

Examples

On the book's own page

Built on Standards
Built on Standards, by Ana Torres
View code
let onBookPage = true;

function go(toBookPage: boolean) {
	onBookPage = toBookPage;
	if (typeof document.startViewTransition !== "function") return void handle.update();
	document.startViewTransition(() => handle.update());
}

onBookPage ? (
	<div mix={[vstack({ gap: 3, align: "stretch" }), is("22rem")]}>
		<SharedElement
			id={`cover-${book.slug}`}
			mix={[
				is("8rem"),
				bs("11rem"),
				rounded("lg"),
				bg("brand.tint"),
				fg("brand"),
				p(3),
				weight("semibold"),
				css({ display: "grid", placeItems: "center", textAlign: "center" }),
			]}
		>
			{book.title}
		</SharedElement>
		<Text>{book.title}, by {book.author}</Text>
		<div mix={[hstack({ gap: 2, align: "center" })]}>
			<Button variant="outline" size="sm" mix={[on<HTMLButtonElement, "click">("click", () => go(false))]}>
				Back to the shelf
			</Button>
		</div>
	</div>
) : (
	<div mix={[vstack({ gap: 3, align: "stretch" }), is("22rem")]}>
		<Button
			variant="ghost"
			mix={[hstack({ gap: 3, align: "center" }), on<HTMLButtonElement, "click">("click", () => go(true))]}
		>
			<SharedElement
				id={`cover-${book.slug}`}
				mix={[
					is("2rem"),
					bs("2.75rem"),
					rounded("sm"),
					bg("brand.tint"),
					fg("brand"),
					text("xs"),
					css({ display: "grid", placeItems: "center" }),
				]}
			>
				B
			</SharedElement>
			{book.title}
		</Button>
	</div>
)

Props

Read from the component's own types, so every prop, every default and every allowed value is listed.

Also accepts everything in TagProps<"div">.

Written by Sergio Xalambrí. Follow @sergiodxa for new packages, or sponsor the work.