This commit is contained in:
z.ai committed 2026-10-08 11:09:37 +08:00
1 parent c1541b80df
commit b4c7908029
97 files changed
+12403 -154

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
import type { HTMLAttributes } from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
/**
* 徽章变体,对应 DESIGN.md 的 badge-* 组件:
* - tag → badge-tag(文章标签,蓝色 + 半透明底)
* - type → badge-type(代码风格的类型标记)
* - discount → badge-discount(品牌绿强调)
* - required → badge-required(错误红,大写微标签)
* - neutral → 中性灰,用于日期 / 阅读时长等元信息
*/
export const badgeVariants = cva(
"inline-flex items-center gap-[4px] whitespace-nowrap rounded-sm leading-none",
{
variants: {
variant: {
tag: "bg-brand-tag/15 text-brand-tag text-caption font-semibold px-xs py-[3px]",
type: "bg-surface text-steel font-mono text-code-sm px-xs py-[3px]",
discount: "bg-brand-green text-primary text-caption font-semibold rounded-full px-xs py-[3px]",
required:
"bg-brand-error text-on-dark text-micro-uppercase font-semibold px-[6px] py-[3px]",
neutral: "bg-surface text-steel text-caption px-xs py-[3px]",
},
},
defaultVariants: {
variant: "neutral",
},
},
);
export type BadgeProps = HTMLAttributes<HTMLSpanElement> &
VariantProps<typeof badgeVariants>;
/** 徽章 / 标签 */
export function Badge({ className, variant, ...props }: BadgeProps) {
return (
<span className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
+30
View File
@@ -0,0 +1,30 @@
import type { AnchorHTMLAttributes } from "react";
import {
buttonClassName,
type ButtonStyleOptions,
} from "./button-variants";
export type ButtonLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> &
ButtonStyleOptions;
/**
* 外链按钮:用于邮件、社交链接等外部地址。
* 站内跳转请使用 next-intl 的 <Link> 并传入 buttonClassName()。
*/
export function ButtonLink({
className,
variant,
size,
rel = "noopener noreferrer",
target = "_blank",
...props
}: ButtonLinkProps) {
return (
<a
rel={rel}
target={target}
className={buttonClassName({ className, variant, size })}
{...props}
/>
);
}
+57
View File
@@ -0,0 +1,57 @@
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
/**
* 按钮样式变体。
*
* 对应 DESIGN.md 的组件定义:
* - primary → button-primary(黑底胶囊,主 CTA)
* - secondary → button-secondary(描边胶囊)
* - accent → button-accent-green(品牌薄荷绿,谨慎使用)
* - ghost → button-ghost(无背景矩形)
* - link → button-link(内联文字链接)
*
* 规范要求:所有按钮一律 `{rounded.full}` 胶囊形,不软化圆角。
*/
export const buttonVariants = cva(
"inline-flex items-center justify-center gap-xs font-medium whitespace-nowrap rounded-full transition-colors duration-150 ease-out disabled:pointer-events-none disabled:bg-hairline disabled:text-muted",
{
variants: {
variant: {
primary: "bg-primary text-on-primary active:bg-charcoal",
secondary:
"bg-transparent text-ink border border-hairline active:bg-surface",
accent: "bg-brand-green text-primary active:bg-brand-green-deep",
onDark: "bg-on-dark text-primary active:bg-on-dark-muted",
ghost: "rounded-md bg-transparent text-ink active:bg-surface",
link: "rounded-none bg-transparent text-ink underline-offset-4 active:underline p-0 h-auto",
},
size: {
sm: "h-9 px-md text-body-sm",
// DESIGN.md: padding 10px 20px,字号 14px / 行高 1.3
md: "h-10 px-lg text-button-md",
// 移动端触控目标 ≥ 44px
lg: "h-11 px-xl text-body-md",
},
},
defaultVariants: {
variant: "primary",
size: "md",
},
},
);
export type ButtonVariantProps = VariantProps<typeof buttonVariants>;
export interface ButtonStyleOptions extends ButtonVariantProps {
className?: string;
}
/** 生成按钮 className,供 <button> 与 <Link> 复用同一套样式 */
export function buttonClassName({
className,
variant,
size,
}: ButtonStyleOptions = {}): string {
return cn(buttonVariants({ variant, size }), className);
}
+25
View File
@@ -0,0 +1,25 @@
import type { ButtonHTMLAttributes } from "react";
import {
buttonClassName,
type ButtonStyleOptions,
} from "./button-variants";
export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> &
ButtonStyleOptions;
/** 按钮组件:渲染原生 <button>,样式由 buttonVariants 统一管理 */
export function Button({
className,
variant,
size,
type = "button",
...props
}: ButtonProps) {
return (
<button
type={type}
className={buttonClassName({ className, variant, size })}
{...props}
/>
);
}
+42
View File
@@ -0,0 +1,42 @@
import type { HTMLAttributes } from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
/**
* 卡片变体,对应 DESIGN.md:
* - base → card-base(白底 + hairline 描边)
* - feature → card-feature(surface 底、无描边)
* - mockup → hero-product-mockup(含大扩散阴影,仅 Hero 使用)
*/
export const cardVariants = cva("", {
variants: {
variant: {
base: "bg-canvas border border-hairline rounded-lg",
feature: "bg-surface rounded-lg",
mockup: "bg-canvas border border-hairline-soft rounded-lg shadow-mockup",
},
padding: {
none: "p-0",
sm: "p-md",
md: "p-xl",
lg: "p-xxl",
},
},
defaultVariants: {
variant: "base",
padding: "md",
},
});
export type CardProps = HTMLAttributes<HTMLDivElement> &
VariantProps<typeof cardVariants>;
/** 通用卡片容器 */
export function Card({ className, variant, padding, ...props }: CardProps) {
return (
<div
className={cn(cardVariants({ variant, padding }), className)}
{...props}
/>
);
}
+84
View File
@@ -0,0 +1,84 @@
import type { HTMLAttributes, ElementType } from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
/**
* 容器:统一站点内容宽度与左右留白。
* DESIGN.md:营销页面 1280px 最大宽度 + 32px 沟槽。
*/
const containerVariants = cva("mx-auto w-full", {
variants: {
size: {
// 文章正文最舒适的阅读宽度
prose: "max-w-[720px]",
default: "max-w-[1280px]",
narrow: "max-w-[960px]",
},
gutter: {
default: "px-lg sm:px-xxl",
none: "px-0",
},
},
defaultVariants: {
size: "default",
gutter: "default",
},
});
export type ContainerProps = HTMLAttributes<HTMLElement> &
VariantProps<typeof containerVariants> & {
/** 渲染的语义化标签,默认 div */
as?: ElementType;
};
export function Container({
className,
size,
gutter,
as: Component = "div",
...props
}: ContainerProps) {
return (
<Component
className={cn(containerVariants({ size, gutter }), className)}
{...props}
/>
);
}
/**
* 区块:负责垂直节奏。
* DESIGN.md:营销页区块之间 96px,文档面 32–64px。
*/
const sectionVariants = cva("w-full", {
variants: {
spacing: {
none: "py-0",
sm: "py-section-sm",
default: "py-section",
lg: "py-section-lg",
},
},
defaultVariants: {
spacing: "default",
},
});
export type SectionProps = HTMLAttributes<HTMLElement> &
VariantProps<typeof sectionVariants> & {
as?: ElementType;
};
export function Section({
className,
spacing,
as: Component = "section",
...props
}: SectionProps) {
return (
<Component
className={cn(sectionVariants({ spacing }), className)}
{...props}
/>
);
}
+52
View File
@@ -0,0 +1,52 @@
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
/**
* 区块标题:DESIGN.md 中反复出现的「大写微标签 + 标题 + 描述」结构。
* 微标签使用 {typography.micro-uppercase}(11px / 600 / +0.5px 字距)。
*/
export interface SectionHeadingProps {
/** 标签上方的大写微标签 */
eyebrow?: string;
title: ReactNode;
description?: ReactNode;
className?: string;
/** 对齐方式,默认居中(营销区块);文档面可用 left */
align?: "center" | "left";
as?: "h1" | "h2" | "h3";
}
export function SectionHeading({
eyebrow,
title,
description,
className,
align = "center",
as: Heading = "h2",
}: SectionHeadingProps) {
return (
<div
className={cn(
"flex flex-col gap-sm",
align === "center" ? "items-center text-center" : "items-start text-left",
className,
)}
>
{eyebrow ? (
<p className="text-micro-uppercase font-semibold uppercase tracking-[0.5px] text-steel">
{eyebrow}
</p>
) : null}
<Heading className="text-heading-2 font-semibold text-ink text-balance-pretty">
{title}
</Heading>
{description ? (
<p className="max-w-[640px] text-subtitle text-steel text-balance-pretty">
{description}
</p>
) : null}
</div>
);
}