A writing app. Focus opens a calm, dark domain in which the sidebar and toolbar, wrapped in `DomainSwitchAside`, dissolve, and the document in `DomainSwitchMain` comes forward. One `Editor` renders both worlds. Esc leaves.
registry/demos/domain-switch-focus-demo.tsx
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
import {
DomainSwitch,
DomainSwitchAside,
DomainSwitchMain,
DomainSwitchScope,
DomainSwitchToggle,
useDomainSwitch,
} from "@/components/ui/domain-switch";
const drafts = ["On slow software", "Field notes, March", "The case for less", "Interview: K. Ito"];
export function DomainSwitchFocusDemo() {
const [focus, setFocus] = React.useState(false);
React.useEffect(() => {
if (!focus) return;
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") setFocus(false);
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [focus]);
return (
<div className="w-full overflow-hidden rounded-xl border border-zinc-200 dark:border-zinc-800">
<DomainSwitch checked={focus} onCheckedChange={setFocus}>
<DomainSwitchScope className="h-[var(--demo-height,560px)]" seamClassName="text-stone-950">
<Editor />
</DomainSwitchScope>
</DomainSwitch>
</div>
);
}
// One editor for both worlds; it reads which one it's in.
function Editor() {
const { world } = useDomainSwitch();
const calm = world === "inside";
return (
<div
className={cn(
"flex h-full",
calm ? "bg-[#141311] text-stone-300" : "bg-white text-stone-800",
)}
>
<DomainSwitchAside
className={cn(
"hidden w-52 shrink-0 flex-col gap-1 border-r p-3 sm:flex",
calm ? "border-white/5" : "border-stone-200 bg-stone-50",
)}
>
<p className="px-2 pt-1 pb-2 text-xs font-medium text-stone-400">Drafts</p>
{drafts.map((draft, i) => (
<span
key={draft}
className={cn(
"truncate rounded-md px-2 py-1.5 text-sm",
i === 0 ? (calm ? "bg-white/5 text-stone-200" : "bg-white text-stone-900 shadow-xs") : "text-stone-500",
)}
>
{draft}
</span>
))}
</DomainSwitchAside>
<div className="flex min-w-0 flex-1 flex-col">
<div
className={cn(
"flex items-center gap-1 border-b px-3 py-2",
calm ? "border-transparent" : "border-stone-200",
)}
>
<DomainSwitchAside className="flex items-center gap-1">
{["B", "I", "U"].map((mark) => (
<span
key={mark}
className={cn(
"grid size-8 place-items-center rounded-md text-sm text-stone-500",
mark === "B" && "font-bold",
mark === "I" && "italic",
mark === "U" && "underline",
)}
>
{mark}
</span>
))}
<span className="mx-1 h-4 w-px bg-stone-200" />
<span className="grid h-8 place-items-center rounded-md px-2 text-sm text-stone-500">H1</span>
<span className="hidden h-8 place-items-center rounded-md px-2 text-sm text-stone-500 sm:grid">Quote</span>
</DomainSwitchAside>
{calm && <span className="ml-1 text-xs text-stone-500">Esc to leave</span>}
<DomainSwitchToggle
className={cn(
"ml-auto h-8 gap-1.5 rounded-full px-3 text-xs font-medium",
calm
? "bg-amber-200/10 text-amber-100 hover:bg-amber-200/15"
: "bg-stone-900 text-white hover:bg-stone-800",
)}
>
<FocusIcon />
{calm ? "Leave focus" : "Focus"}
</DomainSwitchToggle>
</div>
<div className="min-h-0 flex-1 overflow-hidden px-6 pt-8 sm:px-10 sm:pt-12">
<DomainSwitchMain className="mx-auto max-w-[34rem] origin-top">
<p className={cn("text-xs", calm ? "text-stone-500" : "text-stone-400")}>Essay · 412 words</p>
<h2
className={cn(
"mt-2 font-serif text-3xl tracking-tight sm:text-4xl",
calm ? "text-stone-100" : "text-stone-900",
)}
>
On slow software
</h2>
<div className="mt-5 space-y-4 font-serif text-[15px]/7 sm:text-base/7">
<p>
The best tools I use don't ask for my attention. They wait. A text editor that opens before
I've finished the thought, a calendar that doesn't blink.
</p>
<p>
Most software is built to be noticed. Badges, banners, a tour on every release. Each one is
small, and together they are the reason I can't finish a paragraph
<span
className={cn(
"ml-0.5 inline-block h-[1.1em] w-px translate-y-[3px] animate-pulse",
calm ? "bg-amber-200" : "bg-stone-900",
)}
/>
</p>
</div>
</DomainSwitchMain>
</div>
</div>
</div>
);
}
function FocusIcon() {
return (
<svg viewBox="0 0 16 16" className="size-3.5" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden>
<circle cx="8" cy="8" r="2.25" />
<path d="M2 5V3a1 1 0 0 1 1-1h2M11 2h2a1 1 0 0 1 1 1v2M14 11v2a1 1 0 0 1-1 1h-2M5 14H3a1 1 0 0 1-1-1v-2" strokeLinecap="round" />
</svg>
);
}
Live preview, examples and props: https://keyframeui.com/components/domain-switch#domain-switch-focus-demo