This commit is contained in:
1 parent
c1541b80df
commit
b4c7908029
97 files changed
+12403
-154
No files matched your search
@@ -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} />
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user