包含前端源码、预构建产物(dist/)、零依赖 Node.js 服务器(server.mjs)、 Cloudflare Worker 版本(worker/)、部署文档(DEPLOY.md)和交接文档。 技术总监可直接 clone 后运行 node server.mjs 启动,无需安装 React 或 build。 DEEPSEEK_API_KEY 通过环境变量注入,未纳入仓库。
153 lines
4.1 KiB
TypeScript
153 lines
4.1 KiB
TypeScript
import { StrictMode, useState } from "react";
|
||
import { createRoot } from "react-dom/client";
|
||
import {
|
||
BottomSheet,
|
||
Carousel,
|
||
FlowStack,
|
||
KeyboardInput,
|
||
MobileRuntime,
|
||
MobileScroll,
|
||
type FlowScreen,
|
||
} from "../src/mobile";
|
||
import "../src/styles.css";
|
||
import "./runtime-fixture.css";
|
||
|
||
function CarouselFixture() {
|
||
const [tapCount, setTapCount] = useState(0);
|
||
const [sheetOpen, setSheetOpen] = useState(false);
|
||
|
||
return (
|
||
<MobileRuntime>
|
||
<MobileScroll className="fixture-screen">
|
||
<main className="fixture-content">
|
||
<h1>Runtime fixture</h1>
|
||
<button className="sheet-trigger" type="button" onClick={() => setSheetOpen(true)}>
|
||
Open sheet
|
||
</button>
|
||
<Carousel
|
||
ariaLabel="Featured cards"
|
||
className="fixture-carousel"
|
||
contentClassName="fixture-carousel-track"
|
||
>
|
||
{Array.from({ length: 7 }, (_, index) => (
|
||
<button
|
||
className="carousel-card"
|
||
type="button"
|
||
onClick={() => setTapCount((count) => count + 1)}
|
||
key={index}
|
||
>
|
||
Card {index + 1}
|
||
</button>
|
||
))}
|
||
</Carousel>
|
||
<output data-testid="tap-count">{tapCount}</output>
|
||
<div className="fixture-tall-content">Scrollable parent content</div>
|
||
</main>
|
||
</MobileScroll>
|
||
<BottomSheet
|
||
open={sheetOpen}
|
||
onOpenChange={setSheetOpen}
|
||
title="Animated sheet"
|
||
description="Exit motion regression fixture."
|
||
>
|
||
<p>Sheet content</p>
|
||
</BottomSheet>
|
||
</MobileRuntime>
|
||
);
|
||
}
|
||
|
||
function KeyboardFixture() {
|
||
const screen: FlowScreen = {
|
||
id: "keyboard",
|
||
footerHeight: 84,
|
||
footer: () => (
|
||
<div className="keyboard-footer">
|
||
<KeyboardInput aria-label="Message" placeholder="Message" />
|
||
</div>
|
||
),
|
||
render: () => (
|
||
<MobileScroll className="fixture-screen">
|
||
<main className="fixture-content fixture-tall-content">Keyboard fixture</main>
|
||
</MobileScroll>
|
||
),
|
||
};
|
||
|
||
return (
|
||
<MobileRuntime>
|
||
<FlowStack initial={screen} />
|
||
</MobileRuntime>
|
||
);
|
||
}
|
||
|
||
function stackedScreen(level: number): FlowScreen {
|
||
return {
|
||
id: `flow-level-${level}`,
|
||
headerHeight: 56,
|
||
header: (flow) => (
|
||
<div className="flow-fixture-header">
|
||
<button type="button" onClick={flow.pop}>‹ Back</button>
|
||
<strong>Level {level}</strong>
|
||
<span />
|
||
</div>
|
||
),
|
||
footerHeight: 64,
|
||
footer: (flow) => (
|
||
<div className="flow-fixture-footer">
|
||
<button type="button" onClick={flow.pop}>Done</button>
|
||
</div>
|
||
),
|
||
render: (flow) => (
|
||
<MobileScroll className="fixture-screen">
|
||
<main className="fixture-content">
|
||
<h1>{level === 2 ? "Screen stacking works" : `Nested view level ${level}`}</h1>
|
||
{level < 4 ? (
|
||
<button type="button" onClick={() => flow.push(stackedScreen(level + 1))}>
|
||
Push level {level + 1}
|
||
</button>
|
||
) : null}
|
||
</main>
|
||
</MobileScroll>
|
||
),
|
||
};
|
||
}
|
||
|
||
function FlowFixture() {
|
||
const screen: FlowScreen = {
|
||
id: "flow-root",
|
||
footerHeight: 84,
|
||
footer: () => (
|
||
<div className="keyboard-footer">
|
||
<KeyboardInput aria-label="Flow message" placeholder="Message" />
|
||
</div>
|
||
),
|
||
render: (flow) => (
|
||
<MobileScroll className="fixture-screen">
|
||
<main className="fixture-content">
|
||
<h1>Flow root</h1>
|
||
<button type="button" onClick={() => flow.push(stackedScreen(2))}>
|
||
Push level 2
|
||
</button>
|
||
</main>
|
||
</MobileScroll>
|
||
),
|
||
};
|
||
|
||
return (
|
||
<MobileRuntime>
|
||
<FlowStack initial={screen} />
|
||
</MobileRuntime>
|
||
);
|
||
}
|
||
|
||
const fixture = new URLSearchParams(window.location.search).get("fixture");
|
||
const fixtureElement =
|
||
fixture === "keyboard"
|
||
? <KeyboardFixture />
|
||
: fixture === "flow"
|
||
? <FlowFixture />
|
||
: <CarouselFixture />;
|
||
|
||
createRoot(document.getElementById("root")!).render(
|
||
<StrictMode>{fixtureElement}</StrictMode>,
|
||
);
|