Polish static UI flows
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
export default function GridBg() {
|
||||
return (
|
||||
<>
|
||||
<div className="grid-bg" aria-hidden />
|
||||
<pre className="scatter" style={{ top: 96, left: 280 }} aria-hidden>
|
||||
{` · · +
|
||||
· +XX+
|
||||
+XXXX·
|
||||
+X· `}
|
||||
</pre>
|
||||
<pre className="scatter" style={{ top: 340, right: 96 }} aria-hidden>
|
||||
{`+ · ·
|
||||
XX· ·
|
||||
·XXXX·+
|
||||
·++· `}
|
||||
</pre>
|
||||
<pre className="scatter" style={{ bottom: 160, left: "42%" }} aria-hidden>
|
||||
{` · +
|
||||
+·XX·
|
||||
·X+ ·
|
||||
· `}
|
||||
</pre>
|
||||
<pre className="scatter" style={{ top: 580, left: 60 }} aria-hidden>
|
||||
{` +X·
|
||||
·XX·
|
||||
+·X·+`}
|
||||
</pre>
|
||||
<span className="sq" style={{ top: 238, left: 478 }} aria-hidden />
|
||||
<span className="sq" style={{ top: 478, left: 1198 }} aria-hidden />
|
||||
<span className="sq" style={{ bottom: 300, left: 238 }} aria-hidden />
|
||||
<span className="sq" style={{ top: 718, right: 240 }} aria-hidden />
|
||||
<span className="corner-tag" style={{ top: 158, left: 34 }} aria-hidden>[ 200 OK ]</span>
|
||||
<span className="corner-tag" style={{ top: 158, right: 34 }} aria-hidden>[ /v2 ]</span>
|
||||
<span className="corner-tag" style={{ bottom: 36, left: 34 }} aria-hidden>[ .MP4 · 9:16 ]</span>
|
||||
<span className="corner-tag" style={{ bottom: 36, right: 34 }} aria-hidden>[ STUDIO ]</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
import type { SVGProps } from "react";
|
||||
|
||||
type IconName =
|
||||
| "home"
|
||||
| "play"
|
||||
| "folder"
|
||||
| "package"
|
||||
| "boxes"
|
||||
| "images"
|
||||
| "credit-card"
|
||||
| "clapperboard"
|
||||
| "film"
|
||||
| "video"
|
||||
| "tile"
|
||||
| "bars"
|
||||
| "bars2"
|
||||
| "key"
|
||||
| "cog"
|
||||
| "chev-down"
|
||||
| "chev-right"
|
||||
| "search"
|
||||
| "bell"
|
||||
| "help"
|
||||
| "doc"
|
||||
| "up"
|
||||
| "plus"
|
||||
| "product-plus"
|
||||
| "airshelf"
|
||||
| "flame"
|
||||
| "check"
|
||||
| "x"
|
||||
| "play-tri"
|
||||
| "rotate"
|
||||
| "more"
|
||||
| "wallet"
|
||||
| "coin"
|
||||
| "download"
|
||||
| "team"
|
||||
| "lightbulb"
|
||||
| "sparkles"
|
||||
| "info"
|
||||
| "arrow-right";
|
||||
|
||||
const PATHS: Record<IconName, React.ReactNode> = {
|
||||
home: (
|
||||
<>
|
||||
<path d="M3 12 12 3l9 9" />
|
||||
<path d="M5 10v10h14V10" />
|
||||
</>
|
||||
),
|
||||
play: <path d="m6 4 14 8-14 8Z" />,
|
||||
package: (
|
||||
<>
|
||||
<path d="M11 21.7a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.7l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.7Z" />
|
||||
<path d="m3.3 7 8.7 5 8.7-5" />
|
||||
<path d="M12 22V12" />
|
||||
<path d="m7.5 4.3 9 5.1" />
|
||||
</>
|
||||
),
|
||||
boxes: (
|
||||
<>
|
||||
<path d="M11 21.7a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.7l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.7Z" />
|
||||
<path d="m3.3 7 8.7 5 8.7-5" />
|
||||
<path d="M12 22V12" />
|
||||
<path d="m7.5 4.3 9 5.1" />
|
||||
</>
|
||||
),
|
||||
images: (
|
||||
<>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||
<circle cx="9" cy="9" r="2" />
|
||||
<path d="m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21" />
|
||||
</>
|
||||
),
|
||||
clapperboard: (
|
||||
<>
|
||||
<path d="m12.3 3.5 3 4" />
|
||||
<path d="M20.2 6 3 11l-.9-2.4a2 2 0 0 1 1.3-2.5l13.5-4a2 2 0 0 1 2.5 1.3Z" />
|
||||
<path d="m6.2 5.3 3.1 3.9" />
|
||||
<path d="M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z" />
|
||||
</>
|
||||
),
|
||||
film: (
|
||||
<>
|
||||
<path d="m12.3 3.5 3 4" />
|
||||
<path d="M20.2 6 3 11l-.9-2.4a2 2 0 0 1 1.3-2.5l13.5-4a2 2 0 0 1 2.5 1.3Z" />
|
||||
<path d="m6.2 5.3 3.1 3.9" />
|
||||
<path d="M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z" />
|
||||
</>
|
||||
),
|
||||
video: (
|
||||
<>
|
||||
<path d="m16 13 5.2 3.1a.5.5 0 0 0 .8-.4V8.3a.5.5 0 0 0-.8-.4L16 11" />
|
||||
<rect x="2" y="6" width="14" height="12" rx="2" />
|
||||
</>
|
||||
),
|
||||
folder: (
|
||||
<>
|
||||
<rect x="3" y="4" width="18" height="16" rx="2" />
|
||||
<path d="M3 10h18" />
|
||||
</>
|
||||
),
|
||||
tile: <path d="M3 6h18M3 12h18M3 18h18" />,
|
||||
bars: (
|
||||
<>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||
<circle cx="9" cy="9" r="2" />
|
||||
<path d="m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21" />
|
||||
</>
|
||||
),
|
||||
bars2: <path d="M3 21V9M9 21V5M15 21v-8M21 21V11" />,
|
||||
key: (
|
||||
<>
|
||||
<circle cx="9" cy="12" r="6" />
|
||||
<path d="m15 12 6 0M19 9v6" />
|
||||
</>
|
||||
),
|
||||
cog: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8 2 2 0 0 1-2.8 2.8 1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5 2 2 0 0 1-4 0 1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3 2 2 0 0 1-2.8-2.8 1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1 2 2 0 0 1 0-4 1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8 2 2 0 0 1 2.8-2.8 1.7 1.7 0 0 0 1.8.3 1.7 1.7 0 0 0 1-1.5 2 2 0 0 1 4 0 1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3 2 2 0 0 1 2.8 2.8 1.7 1.7 0 0 0-.3 1.8 1.7 1.7 0 0 0 1.5 1 2 2 0 0 1 0 4 1.7 1.7 0 0 0-1.5 1.1Z" />
|
||||
</>
|
||||
),
|
||||
"chev-down": <path d="m6 9 6 6 6-6" />,
|
||||
"chev-right": <path d="m9 6 6 6-6 6" />,
|
||||
search: (
|
||||
<>
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<path d="m21 21-4.3-4.3" />
|
||||
</>
|
||||
),
|
||||
bell: <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 0 0 4 0" />,
|
||||
help: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M9.5 9a2.5 2.5 0 1 1 4 2.2c-.7.5-1.5 1-1.5 2v.3M12 17h.01" />
|
||||
</>
|
||||
),
|
||||
doc: (
|
||||
<>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<path d="M14 2v6h6M9 13h6M9 17h6" />
|
||||
</>
|
||||
),
|
||||
up: <path d="M12 19V5M5 12l7-7 7 7" />,
|
||||
plus: <path d="M12 5v14M5 12h14" />,
|
||||
"product-plus": (
|
||||
<>
|
||||
<path d="M12 22V12" />
|
||||
<path d="M16 17h6" />
|
||||
<path d="M19 14v6" />
|
||||
<path d="M21 10.5V8a2 2 0 0 0-1-1.7l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.7l7 4a2 2 0 0 0 2 0l1.7-1" />
|
||||
<path d="m3.3 7 8.7 5 8.7-5" />
|
||||
<path d="m7.5 4.3 9 5.1" />
|
||||
</>
|
||||
),
|
||||
airshelf: (
|
||||
<>
|
||||
<path d="M4.5 19 12 4.5 19.5 19" />
|
||||
<path d="M8 14h8" />
|
||||
<path d="M7 17h10" />
|
||||
<path d="M9.5 10.3c1.5-1.2 3.5-1.2 5 0" />
|
||||
</>
|
||||
),
|
||||
flame: (
|
||||
<>
|
||||
<path d="M8.5 14.5c0-2 1.5-3.4 3.5-5.5.4 2.2 2.8 3.1 2.8 5.6a3.3 3.3 0 0 1-6.3-.1Z" />
|
||||
<path d="M12 22c4 0 7-2.8 7-7 0-4.7-3.3-7.3-5-12-2 3.3-7 6-7 12a5 5 0 0 0 5 7Z" />
|
||||
</>
|
||||
),
|
||||
check: <path d="M4 12l5 5L20 6" />,
|
||||
x: <path d="M5 5l14 14M19 5L5 19" />,
|
||||
"play-tri": <path d="m5 4 14 8-14 8Z" />,
|
||||
rotate: (
|
||||
<>
|
||||
<path d="M4 12a8 8 0 0 1 14-5.5L21 9" />
|
||||
<path d="M21 4v5h-5" />
|
||||
<path d="M20 12a8 8 0 0 1-14 5.5L3 15" />
|
||||
<path d="M3 20v-5h5" />
|
||||
</>
|
||||
),
|
||||
more: (
|
||||
<>
|
||||
<circle cx="5" cy="12" r="1.6" fill="currentColor" stroke="none" />
|
||||
<circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none" />
|
||||
<circle cx="19" cy="12" r="1.6" fill="currentColor" stroke="none" />
|
||||
</>
|
||||
),
|
||||
wallet: (
|
||||
<>
|
||||
<rect x="3" y="6" width="18" height="13" rx="2" />
|
||||
<path d="M3 10h18M16 14h2" />
|
||||
</>
|
||||
),
|
||||
"credit-card": (
|
||||
<>
|
||||
<rect x="2" y="5" width="20" height="14" rx="2" />
|
||||
<path d="M2 10h20" />
|
||||
</>
|
||||
),
|
||||
coin: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M12 7v10M9 10h4a1.5 1.5 0 0 1 0 3h-3a1.5 1.5 0 0 0 0 3h4" />
|
||||
</>
|
||||
),
|
||||
download: <path d="M12 4v12m0 0l-5-5m5 5l5-5M4 20h16" />,
|
||||
team: (
|
||||
<>
|
||||
<circle cx="9" cy="9" r="3" />
|
||||
<path d="M3 20c0-3 3-5 6-5s6 2 6 5" />
|
||||
<circle cx="17" cy="10" r="2.4" />
|
||||
<path d="M21 19c0-2-1.6-4-4-4-.6 0-1.2.2-1.7.4" />
|
||||
</>
|
||||
),
|
||||
lightbulb: (
|
||||
<>
|
||||
<path d="M9 18h6" />
|
||||
<path d="M10 22h4" />
|
||||
<path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14" />
|
||||
</>
|
||||
),
|
||||
sparkles: (
|
||||
<>
|
||||
<path d="M12 3l1.7 4.6L18 9l-4.3 1.4L12 15l-1.7-4.6L6 9l4.3-1.4L12 3z" />
|
||||
<path d="M19 16l.85 2.3L22 19l-2.15.7L19 22l-.85-2.3L16 19l2.15-.7L19 16z" />
|
||||
</>
|
||||
),
|
||||
info: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M12 16v-4M12 8h.01" />
|
||||
</>
|
||||
),
|
||||
"arrow-right": <path d="M5 12h14M13 6l6 6-6 6" />,
|
||||
};
|
||||
|
||||
interface Props extends Omit<SVGProps<SVGSVGElement>, "name"> {
|
||||
name: IconName;
|
||||
size?: number;
|
||||
strokeWidth?: number;
|
||||
}
|
||||
|
||||
export default function Icon({ name, size = 16, strokeWidth = 1.5, ...rest }: Props) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width={size}
|
||||
height={size}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={strokeWidth}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...rest}
|
||||
>
|
||||
{PATHS[name]}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import Icon from "./Icon";
|
||||
|
||||
const NAV = [
|
||||
{ id: "workspace", label: "工作台", icon: "home" as const, href: "/" },
|
||||
{ id: "play", label: "试拍台", icon: "play" as const, href: "/play" },
|
||||
{
|
||||
id: "projects",
|
||||
label: "项目",
|
||||
icon: "clapperboard" as const,
|
||||
href: "/projects",
|
||||
chev: true,
|
||||
},
|
||||
{ id: "products", label: "商品库", icon: "package" as const, href: "/products" },
|
||||
{ id: "library", label: "资产库", icon: "images" as const, href: "/library" },
|
||||
{ id: "usage", label: "用量", icon: "bars2" as const, href: "/usage" },
|
||||
{ id: "api", label: "API Keys", icon: "key" as const, href: "/api-keys" },
|
||||
{ id: "settings", label: "设置", icon: "cog" as const, href: "/settings" },
|
||||
];
|
||||
|
||||
function isActive(pathname: string, href: string): boolean {
|
||||
if (href === "/") return pathname === "/";
|
||||
return pathname === href || pathname.startsWith(href + "/");
|
||||
}
|
||||
|
||||
export default function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
// /account is reached via the user pill in the topbar — not in nav.
|
||||
return (
|
||||
<aside className="sidebar">
|
||||
<div className="brand">
|
||||
<div className="brand-mark">
|
||||
<Icon name="airshelf" size={22} />
|
||||
</div>
|
||||
<div className="brand-name">Airshelf</div>
|
||||
<div className="brand-ver">v1</div>
|
||||
</div>
|
||||
|
||||
<div className="search-box">
|
||||
<Icon name="search" />
|
||||
<span>搜索</span>
|
||||
<span className="kbd">⌘K</span>
|
||||
</div>
|
||||
|
||||
<nav className="sidebar-nav">
|
||||
{NAV.map((n) => {
|
||||
const active = isActive(pathname, n.href);
|
||||
return (
|
||||
<Link key={n.id} href={n.href} className={active ? "active" : ""}>
|
||||
<Icon name={n.icon} />
|
||||
<span className="label">{n.label}</span>
|
||||
{n.chev && <Icon name="chev-down" className="chev" />}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
<div className="nav-section">协作</div>
|
||||
<div className="nav-item disabled" title="V1.5 上线 · 敬请期待">
|
||||
<Icon name="team" />
|
||||
<span className="label">团队</span>
|
||||
<span className="badge-mini">V1.5</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div className="aside-foot">
|
||||
<Link href="/account" className="aside-user">
|
||||
<div className="av">李</div>
|
||||
<div className="em">li.dao@studio.cn</div>
|
||||
</Link>
|
||||
<div className="aside-collapse">
|
||||
<Icon name="chev-right" size={12} style={{ transform: "rotate(180deg)" }} />
|
||||
收起
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import Link from "next/link";
|
||||
import Icon from "./Icon";
|
||||
|
||||
export interface Crumb {
|
||||
label: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
/** Pass crumbs to show breadcrumbs (e.g. inner pages). Omit to show the team switcher (workspace). */
|
||||
crumbs?: Crumb[];
|
||||
balance?: string;
|
||||
}
|
||||
|
||||
export default function Topbar({ crumbs, balance = "¥327.40" }: Props) {
|
||||
return (
|
||||
<header className="topbar">
|
||||
{crumbs && crumbs.length > 0 ? (
|
||||
<nav className="crumbs">
|
||||
{crumbs.map((c, i) => {
|
||||
const last = i === crumbs.length - 1;
|
||||
const sep = i > 0 ? <span key={`s-${i}`} className="sep">/</span> : null;
|
||||
return last ? (
|
||||
<span key={c.label}>{sep}<span className="here">{c.label}</span></span>
|
||||
) : (
|
||||
<span key={c.label}>
|
||||
{sep}
|
||||
{c.href ? <Link href={c.href}>{c.label}</Link> : <span>{c.label}</span>}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
) : (
|
||||
<div className="team-switcher" role="button">
|
||||
<div className="p">个</div>
|
||||
个人工作室
|
||||
<Icon name="chev-down" size={12} className="chev" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="top-r">
|
||||
<span className="balance-chip">
|
||||
<Icon name="credit-card" size={13} />
|
||||
余额 <strong>{balance}</strong>
|
||||
</span>
|
||||
<button className="icon-btn" aria-label="通知">
|
||||
<Icon name="bell" />
|
||||
<span className="dot" />
|
||||
</button>
|
||||
<button className="pill-btn">
|
||||
<Icon name="help" />
|
||||
帮助
|
||||
</button>
|
||||
<button className="pill-btn">
|
||||
<Icon name="doc" />
|
||||
文档
|
||||
</button>
|
||||
<button className="pill-btn upgrade">
|
||||
<Icon name="up" />
|
||||
升级
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user