feat(frontend): M9 模态 UI(mode 系统+共享 store+WS 客户端 + chat(思维链/工具回显/情绪徽标)/code/Agent 监控/后台思考页 + Miuix 设计 token + PWA);tsc+vite build 通过
This commit is contained in:
1 parent
b3ffd51736
commit
4c48e94659
40 files changed
+5321
No files matched your search
@@ -0,0 +1,159 @@
|
||||
import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";
|
||||
|
||||
/** Miuix-style primitives: Button / Tag / Switch / Spinner / Surface / Empty.
|
||||
* Springs are applied only to interactive elements (cubic-bezier(0.34,1.56,0.64,1)). */
|
||||
|
||||
type ButtonVariant = "primary" | "soft" | "ghost" | "outline" | "danger";
|
||||
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: ButtonVariant;
|
||||
size?: "sm" | "md";
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const buttonVariant: Record<ButtonVariant, string> = {
|
||||
primary: "bg-[var(--mu-accent)] text-white hover:bg-[var(--mu-accent-hi)] shadow-[var(--mu-shadow-2)]",
|
||||
soft: "bg-[var(--mu-surface-4)] text-[var(--mu-text-hi)] hover:bg-[var(--mu-surface-5)]",
|
||||
ghost: "text-[var(--mu-text-mid)] hover:bg-[var(--mu-surface-4)]",
|
||||
outline:
|
||||
"border border-[var(--mu-glass-border)] text-[var(--mu-text-hi)] hover:bg-[var(--mu-surface-4)]",
|
||||
danger: "bg-[var(--mu-err)] text-white",
|
||||
};
|
||||
|
||||
export function Button({
|
||||
variant = "soft",
|
||||
size = "md",
|
||||
className = "",
|
||||
children,
|
||||
...rest
|
||||
}: ButtonProps): React.ReactNode {
|
||||
const pad = size === "sm" ? "px-2.5 py-1 text-xs" : "px-4 py-2 text-sm";
|
||||
return (
|
||||
<button
|
||||
className={`inline-flex items-center justify-center gap-1.5 font-medium transition-all duration-200 ease-[var(--mu-spring)] active:scale-[0.97] disabled:opacity-50 disabled:pointer-events-none ${pad} ${buttonVariant[variant]} ${className}`}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export type TagTone = "accent" | "ok" | "warn" | "err" | "neutral";
|
||||
|
||||
export interface TagProps {
|
||||
tone?: TagTone;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const tagTone: Record<TagTone, string> = {
|
||||
accent: "bg-[var(--mu-accent)]/15 text-[var(--mu-accent-hi)]",
|
||||
ok: "bg-[var(--mu-ok)]/15 text-[var(--mu-ok)]",
|
||||
warn: "bg-[var(--mu-warn)]/15 text-[var(--mu-warn)]",
|
||||
err: "bg-[var(--mu-err)]/15 text-[var(--mu-err)]",
|
||||
neutral: "bg-[var(--mu-surface-4)] text-[var(--mu-text-mid)]",
|
||||
};
|
||||
|
||||
export function Tag({ tone = "neutral", children, className = "" }: TagProps): React.ReactNode {
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center gap-1 rounded-[var(--mu-radius-sm)] px-2 py-0.5 text-xs font-medium ${tagTone[tone]} ${className}`}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export interface SwitchProps {
|
||||
checked: boolean;
|
||||
onChange: (next: boolean) => void;
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export function Switch({ checked, onChange, disabled, label }: SwitchProps): React.ReactNode {
|
||||
return (
|
||||
<label
|
||||
className={`flex items-center gap-2 select-none ${disabled ? "opacity-50 pointer-events-none" : ""}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
aria-label={label}
|
||||
onClick={() => onChange(!checked)}
|
||||
className={`relative h-6 w-11 shrink-0 rounded-[var(--mu-radius-pill)] transition-colors duration-200 ${
|
||||
checked ? "bg-[var(--mu-accent)]" : "bg-[var(--mu-surface-5)]"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-0.5 h-5 w-5 rounded-full bg-white shadow-md transition-transform duration-300 ease-[var(--mu-spring)] ${
|
||||
checked ? "translate-x-[22px]" : "translate-x-0.5"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
{label ? <span className="text-sm text-[var(--mu-text-mid)]">{label}</span> : null}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export interface SpinnerProps {
|
||||
size?: number;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export function Spinner({ size = 18, label }: SpinnerProps): React.ReactNode {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-2 text-[var(--mu-text-lo)]">
|
||||
<span
|
||||
className="inline-block animate-spin rounded-full border-2 border-[var(--mu-surface-5)] border-t-[var(--mu-accent)]"
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
{label ? <span className="text-xs">{label}</span> : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export interface SurfaceProps extends HTMLAttributes<HTMLDivElement> {
|
||||
glass?: boolean;
|
||||
liquid?: boolean;
|
||||
gap?: "none" | "sm" | "md";
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function Surface({
|
||||
glass = false,
|
||||
liquid = false,
|
||||
gap = "md",
|
||||
className = "",
|
||||
children,
|
||||
...rest
|
||||
}: SurfaceProps): React.ReactNode {
|
||||
const pad = gap === "none" ? "" : gap === "sm" ? "p-3" : "p-5";
|
||||
const base = glass
|
||||
? "glass"
|
||||
: liquid
|
||||
? "liquid-glass"
|
||||
: "bg-[var(--mu-surface-3)] border border-[var(--mu-glass-border)] shadow-[var(--mu-shadow-1)]";
|
||||
return (
|
||||
<div className={`rounded-[var(--mu-radius-lg)] ${base} ${pad} ${className}`} {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface EmptyStateProps {
|
||||
icon?: ReactNode;
|
||||
title: string;
|
||||
hint?: string;
|
||||
}
|
||||
|
||||
export function EmptyState({ icon, title, hint }: EmptyStateProps): React.ReactNode {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-2 py-16 text-center">
|
||||
{icon ? <div className="text-3xl opacity-60">{icon}</div> : null}
|
||||
<div className="text-sm font-medium text-[var(--mu-text-mid)]">{title}</div>
|
||||
{hint ? <div className="text-xs text-[var(--mu-text-lo)]">{hint}</div> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user