@zmzai/theme 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -70,7 +70,18 @@ const paths: Record<string, { d: string; fill?: boolean }> = {
|
|
|
70
70
|
|
|
71
71
|
export type IconName = keyof typeof paths;
|
|
72
72
|
|
|
73
|
-
export function Icon({
|
|
73
|
+
export function Icon({
|
|
74
|
+
name,
|
|
75
|
+
size = 14,
|
|
76
|
+
className = "",
|
|
77
|
+
strokeWidth = 1.5,
|
|
78
|
+
}: {
|
|
79
|
+
name: IconName;
|
|
80
|
+
size?: number;
|
|
81
|
+
className?: string;
|
|
82
|
+
/** 描边宽度。viewBox 恒为 16,放大到 20px+ 时用 1.25 避免视觉过重(默认 1.5 不变) */
|
|
83
|
+
strokeWidth?: number;
|
|
84
|
+
}) {
|
|
74
85
|
const icon = paths[name];
|
|
75
86
|
if (!icon) return null;
|
|
76
87
|
return (
|
|
@@ -82,7 +93,7 @@ export function Icon({ name, size = 14, className = "" }: { name: IconName; size
|
|
|
82
93
|
aria-hidden="true"
|
|
83
94
|
fill={icon.fill ? "currentColor" : "none"}
|
|
84
95
|
stroke={icon.fill ? "none" : "currentColor"}
|
|
85
|
-
strokeWidth={
|
|
96
|
+
strokeWidth={strokeWidth}
|
|
86
97
|
strokeLinecap="round"
|
|
87
98
|
strokeLinejoin="round"
|
|
88
99
|
>
|
package/src/components/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// @zmzai/theme — Components barrel (
|
|
1
|
+
// @zmzai/theme — Components barrel (55 components)
|
|
2
2
|
|
|
3
3
|
// === Basic ===
|
|
4
4
|
export * from "./app-shell";
|
|
5
|
+
export * from "./page-header";
|
|
5
6
|
export * from "./button";
|
|
6
7
|
export * from "./input";
|
|
7
8
|
export * from "./textarea";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PageHeader — 页面标题统一组件
|
|
5
|
+
*
|
|
6
|
+
* v0.9.4 引入。此前 agent / relay 两站共 38 个页面各自手写 h1:
|
|
7
|
+
* 标题旁 0 图标、字号分 4~5 档(text-sm / text-lg / text-2xl / text-3xl /
|
|
8
|
+
* text-4xl / CSS clamp)、间距与颜色 token 各有偏差(text-muted-foreground
|
|
9
|
+
* 等旧变量混用)、部分页面缺 text-ink。
|
|
10
|
+
*
|
|
11
|
+
* 纪律:
|
|
12
|
+
* · 只有两个 variant —— hero(落地页)与 page(其余全部)
|
|
13
|
+
* · page 一律 text-2xl + 内联图标,不再区分公开页与控制台
|
|
14
|
+
* · 图标取 currentColor,不引入新色;hero 徽章用 accent 载体(墨底白图)
|
|
15
|
+
* · 描述统一 text-sm text-ink-2 —— ink-3 只有 3.69:1,副文案不用它
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ReactNode } from "react";
|
|
19
|
+
import { Icon, type IconName } from "../icon/Icon";
|
|
20
|
+
|
|
21
|
+
export interface PageHeaderProps {
|
|
22
|
+
/** 眉标:页面所属分区,如「控制台」「长期上下文」。hero 变体下作为统计/kicker 行。 */
|
|
23
|
+
eyebrow?: ReactNode;
|
|
24
|
+
/** 页面标题,渲染为 h1 */
|
|
25
|
+
title: ReactNode;
|
|
26
|
+
/** 标题左侧内联图标(page 变体)或墨底徽章内容(hero 变体) */
|
|
27
|
+
icon?: IconName;
|
|
28
|
+
/** 标题下方描述 */
|
|
29
|
+
description?: ReactNode;
|
|
30
|
+
/** 右侧操作区(按钮、筛选器等) */
|
|
31
|
+
actions?: ReactNode;
|
|
32
|
+
/** hero = 落地页(44px 墨底徽章 + text-4xl);page = 其余全部(内联图标 + text-2xl) */
|
|
33
|
+
variant?: "page" | "hero";
|
|
34
|
+
/** 追加到根元素 */
|
|
35
|
+
className?: string;
|
|
36
|
+
/** 追加到 h1(详情页标题过长时用 truncate) */
|
|
37
|
+
titleClassName?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function PageHeader({
|
|
41
|
+
eyebrow,
|
|
42
|
+
title,
|
|
43
|
+
icon,
|
|
44
|
+
description,
|
|
45
|
+
actions,
|
|
46
|
+
variant = "page",
|
|
47
|
+
className = "",
|
|
48
|
+
titleClassName = "",
|
|
49
|
+
}: PageHeaderProps) {
|
|
50
|
+
if (variant === "hero") {
|
|
51
|
+
return (
|
|
52
|
+
<header className={`flex flex-col ${className}`}>
|
|
53
|
+
{icon ? (
|
|
54
|
+
<span className="mb-4 grid size-11 shrink-0 place-items-center rounded-lg bg-accent text-accent-ink">
|
|
55
|
+
<Icon name={icon} size={22} strokeWidth={1.25} />
|
|
56
|
+
</span>
|
|
57
|
+
) : null}
|
|
58
|
+
{eyebrow ? (
|
|
59
|
+
<small className="mb-2 block font-mono text-xs tracking-wide text-ink-3">{eyebrow}</small>
|
|
60
|
+
) : null}
|
|
61
|
+
<h1
|
|
62
|
+
className={`font-serif text-4xl font-bold leading-tight tracking-tight text-ink ${titleClassName}`}
|
|
63
|
+
>
|
|
64
|
+
{title}
|
|
65
|
+
</h1>
|
|
66
|
+
{description ? (
|
|
67
|
+
<p className="mt-4 max-w-2xl text-lg leading-8 text-ink-2">{description}</p>
|
|
68
|
+
) : null}
|
|
69
|
+
{actions ? <div className="mt-7 flex flex-wrap items-center gap-4">{actions}</div> : null}
|
|
70
|
+
</header>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<header className={`flex flex-wrap items-end justify-between gap-3 ${className}`}>
|
|
76
|
+
<div className="min-w-0">
|
|
77
|
+
{eyebrow ? (
|
|
78
|
+
<small className="mb-1 block text-xs font-semibold uppercase tracking-wide text-ink-3">
|
|
79
|
+
{eyebrow}
|
|
80
|
+
</small>
|
|
81
|
+
) : null}
|
|
82
|
+
<div className="flex items-center gap-2">
|
|
83
|
+
{icon ? <Icon name={icon} size={20} className="shrink-0 text-ink" /> : null}
|
|
84
|
+
<h1
|
|
85
|
+
className={`font-serif text-2xl font-semibold tracking-tight text-ink ${titleClassName}`}
|
|
86
|
+
>
|
|
87
|
+
{title}
|
|
88
|
+
</h1>
|
|
89
|
+
</div>
|
|
90
|
+
{description ? <p className="mt-2 text-sm leading-6 text-ink-2">{description}</p> : null}
|
|
91
|
+
</div>
|
|
92
|
+
{actions ? <div className="flex flex-wrap items-center gap-2">{actions}</div> : null}
|
|
93
|
+
</header>
|
|
94
|
+
);
|
|
95
|
+
}
|