refactor(workspace): 将颜色 token 重命名为语义化命名

This commit is contained in:
肖应宇 2026-04-23 17:52:42 +08:00 committed by MT-Fire
parent 81bd9b3d74
commit 6853ed71bc
20 changed files with 162 additions and 151 deletions

View File

@ -194,7 +194,7 @@ async function validateTokenRegistry() {
const darkSeen = new Map();
for (const [name, value] of entries) {
if (!/^ws-[0-9a-f]{6,8}$/.test(name)) {
if (!/^ws-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) {
errors.push(`invalid token name "${name}"`);
}
const light = String(value.light ?? "").toLowerCase();
@ -234,7 +234,7 @@ function collectWsVarsFromBlocks(css, selectorPattern) {
const selector = block[1]?.trim() ?? "";
const body = block[2] ?? "";
if (!selectorPattern.test(selector)) continue;
for (const match of body.matchAll(/--ws-color-([0-9a-z]+)\s*:/g)) {
for (const match of body.matchAll(/--ws-color-([0-9a-z-]+)\s*:/g)) {
vars.add(`ws-${match[1]}`);
}
}
@ -246,7 +246,7 @@ function validateGlobalsCoverage(tokenEntries) {
const rootVars = collectWsVarsFromBlocks(css, /(^|,)\s*:root(\s|,|$)/);
const darkVars = collectWsVarsFromBlocks(css, /(^|,)\s*\.dark(\s|,|$)/);
const inlineVars = new Set(
[...css.matchAll(/--color-ws-([0-9a-z]+)\s*:/g)].map((match) => `ws-${match[1]}`),
[...css.matchAll(/--color-ws-([0-9a-z-]+)\s*:/g)].map((match) => `ws-${match[1]}`),
);
const tokenNames = new Set(tokenEntries.map(([name]) => name));

View File

@ -96,7 +96,7 @@ export default function ChatPage() {
sloganIndex % motivationSlogans.length
] ?? {
text: t.chatPage.defaultSlogan,
color: "var(--color-ws-333333)",
color: "var(--color-ws-fg-primary)",
};
const tickerCharacterList = useMemo(() => {
const seen = new Set<string>();
@ -357,7 +357,7 @@ export default function ChatPage() {
<Button
size="sm"
variant="ghost"
className="px-[10px] py-[5px] text-sm font-medium text-ws-150033 hover:text-ws-150033/80"
className="px-[10px] py-[5px] text-sm font-medium text-ws-base-1 hover:text-ws-base-1/80"
disabled={isStreaming}
onClick={() => setShowExitDialog(true)}
>
@ -370,7 +370,7 @@ export default function ChatPage() {
>
<path
d="M3.5 10H13.25H15.6875H16.5M3.5 10L7.5625 6M3.5 10L7.5625 14"
className="text-ws-667085"
className="text-ws-text-muted"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
@ -380,7 +380,7 @@ export default function ChatPage() {
</Button>
</div>
<div
className="flex items-center justify-center overflow-hidden text-sm font-bold font-medium whitespace-nowrap text-ws-333333"
className="flex items-center justify-center overflow-hidden text-sm font-bold font-medium whitespace-nowrap text-ws-fg-primary"
style={{
color: currentSlogan.color,
}}
@ -400,7 +400,7 @@ export default function ChatPage() {
<div className="flex items-center justify-end gap-2 overflow-hidden">
{/* 取消TodoList */}
{/* <DevTodoList
className="bg-ws-ffffff"
className="bg-ws-surface-base"
todos={thread.values.todos ?? []}
hidden={
!thread.values.todos || thread.values.todos.length === 0
@ -409,7 +409,7 @@ export default function ChatPage() {
<Button
size="sm"
variant="ghost"
className="h-full px-[10px] py-[5px] text-sm font-medium text-ws-150033 hover:text-ws-150033"
className="h-full px-[10px] py-[5px] text-sm font-medium text-ws-base-1 hover:text-ws-base-1"
>
<ListTodoIcon className="size-4" /> To-dos
</Button>
@ -420,7 +420,7 @@ export default function ChatPage() {
<Tooltip content={t.chatPage.viewArtifactsTooltip}>
<Button
data-testid="artifacts-open-button"
className="text-ws-150033 hover:text-ws-150033/80"
className="text-ws-base-1 hover:text-ws-base-1/80"
variant="ghost"
onClick={() => {
setArtifactsOpen(true);
@ -438,7 +438,7 @@ export default function ChatPage() {
className={cn(
"flex min-h-0 max-w-full grow flex-col",
showWelcomeStyle && !hasSubmitted
? "bg-ws-ffffff"
? "bg-ws-surface-base"
: "bg-background",
)}
>
@ -501,7 +501,7 @@ export default function ChatPage() {
) : (
<div className="flex size-full max-w-(--container-width-sm) flex-col justify-center">
<header className="flex shrink-0 items-center justify-between border-b">
<h2 className="h-[58px] text-sm leading-[58px] font-bold text-ws-333333">
<h2 className="h-[58px] text-sm leading-[58px] font-bold text-ws-fg-primary">
<span>{t.common.artifacts}</span>
</h2>
<Button
@ -549,7 +549,7 @@ export default function ChatPage() {
{!(showWelcomeStyle && thread.isThreadLoading) ? (
<>
<InputBox
className={cn("w-full rounded-[20px] bg-ws-fbfafc")}
className={cn("w-full rounded-[20px] bg-ws-surface-elevated")}
threadId={threadId}
showWelcomeStyle={showWelcomeStyle}
hasSubmitted={hasSubmitted}
@ -609,14 +609,14 @@ export default function ChatPage() {
</p>
<DevDialogFooter>
<Button
className="w-full bg-ws-f9f8fa hover:bg-ws-8e47f0 hover:text-primary-foreground"
className="w-full bg-ws-surface-subtle hover:bg-ws-interactive-primary hover:text-primary-foreground"
variant="ghost"
onClick={() => setShowExitDialog(false)}
>
{t.common.cancel}
</Button>
<Button
className="w-full bg-ws-f9f8fa hover:bg-ws-8e47f0 hover:text-primary-foreground"
className="w-full bg-ws-surface-subtle hover:bg-ws-interactive-primary hover:text-primary-foreground"
variant="ghost"
onClick={async () => {
// 如果正在生成,先终止再退出
@ -665,7 +665,7 @@ export default function ChatPage() {
</p>
<DevDialogFooter singleColumn>
<Button
className="w-full bg-ws-f9f8fa hover:bg-ws-8e47f0 hover:text-primary-foreground"
className="w-full bg-ws-surface-subtle hover:bg-ws-interactive-primary hover:text-primary-foreground"
variant="ghost"
onClick={clearSelectedSkillError}
>

View File

@ -130,7 +130,7 @@ export default function WorkspaceLayout({
/* 灰色圆角矩形容器 */
"rounded-[20px] border-none",
/* 浅灰色背景 + 轻微透明 */
"bg-ws-999999! backdrop-blur-sm",
"bg-ws-overlay-neutral! backdrop-blur-sm",
/* 阴影极轻 */
"shadow-[0_2px_12px_0_rgba(0,0,0,0.18)]",
/* 内边距:宽松居中 */

View File

@ -36,7 +36,7 @@ export const Message = ({
"group flex w-full flex-col gap-2",
from === "user"
? cn("is-user ml-auto justify-end", !isFirstInSession && "mt-6")
: "is-assistant rounded-[10px] bg-ws-ffffff p-4",
: "is-assistant rounded-[10px] bg-ws-surface-base p-4",
className,
)}
{...props}

View File

@ -352,7 +352,7 @@ export function PromptInputAttachment({
{/* 删除按钮 - 右上角 */}
<button
aria-label={t.common.removeAttachment}
className="absolute top-1.5 right-1.5 z-10 flex size-4 cursor-pointer items-center justify-center rounded-sm transition-colors hover:bg-ws-ffffff/20"
className="absolute top-1.5 right-1.5 z-10 flex size-4 cursor-pointer items-center justify-center rounded-sm transition-colors hover:bg-ws-surface-base/20"
onClick={(e) => {
e.stopPropagation();
if (onRemove) {
@ -397,7 +397,7 @@ export function PromptInputAttachment({
{/* 关闭按钮 - 右上角 */}
<button
aria-label={t.common.removeAttachment}
className="absolute top-1 right-1 z-10 flex size-5 cursor-pointer items-center justify-center rounded bg-ws-ffffff/90 opacity-0 transition-opacity group-hover:opacity-100 hover:bg-ws-ffffff dark:bg-gray-800/90 dark:hover:bg-gray-800"
className="absolute top-1 right-1 z-10 flex size-5 cursor-pointer items-center justify-center rounded bg-ws-surface-base/90 opacity-0 transition-opacity group-hover:opacity-100 hover:bg-ws-surface-base dark:bg-gray-800/90 dark:hover:bg-gray-800"
onClick={(e) => {
e.stopPropagation();
if (onRemove) {

View File

@ -62,8 +62,8 @@ export const Suggestion = ({
<Button
className={cn(
"cursor-pointer rounded-full px-[20px] py-[15px] text-sm font-normal",
"border-none bg-ws-f9f8fa text-ws-667085",
"hover:bg-ws-fbfafc hover:text-ws-150033",
"border-none bg-ws-surface-subtle text-ws-text-muted",
"hover:bg-ws-surface-elevated hover:text-ws-base-1",
className,
)}
onClick={handleClick}

View File

@ -309,7 +309,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
<main
data-slot="sidebar-inset"
className={cn(
"relative flex w-full flex-1 flex-col",
"relative flex w-full flex-1 flex-col bg-ws-surface-base",
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
className,
)}

View File

@ -430,7 +430,7 @@ export function ArtifactFileDetail({
type="single"
variant={null}
size="default"
className="h-[28px] bg-ws-ffffff"
className="h-[28px] bg-ws-surface-base"
value={viewMode}
onValueChange={(value) => {
if (value) {
@ -721,7 +721,7 @@ export function ArtifactFileDetail({
</ArtifactHeader>
<ArtifactContent>
{/* 遮挡多余的滚动顶部 */}
{/* <div className="absolute w-[calc(100%-40px)] bg-ws-ffffff z-20 h-5 rounded-t-[10px] top-[57px]"></div> */}
{/* <div className="absolute w-[calc(100%-40px)] bg-ws-surface-base z-20 h-5 rounded-t-[10px] top-[57px]"></div> */}
{previewable &&
viewMode === "preview" &&
(language === "markdown" || language === "html") && (
@ -734,7 +734,7 @@ export function ArtifactFileDetail({
/>
)}
{isCodeFile && viewMode === "code" && (
<div className="mb-0 mb-[207px] min-h-full rounded-b-[10px] bg-ws-ffffff p-0">
<div className="mb-0 mb-[207px] min-h-full rounded-b-[10px] bg-ws-surface-base p-0">
<CodeEditor
className="size-full resize-none rounded-none border-none py-[20px]"
value={displayContent ?? ""}
@ -917,7 +917,7 @@ export function ArtifactFilePreview({
if (language === "markdown") {
return (
<div
className={cn("mb-[207px] w-full bg-ws-ffffff p-[20px]")}
className={cn("mb-[207px] w-full bg-ws-surface-base p-[20px]")}
style={{ "--zoom-scale": zoomScale } as CSSProperties}
>
<Streamdown
@ -974,7 +974,7 @@ function PreviewIframe({
{...props}
/>
{isLoading && (
<div className="absolute inset-0 z-10 flex items-center justify-center bg-ws-ffffff/85">
<div className="absolute inset-0 z-10 flex items-center justify-center bg-ws-surface-base/85">
<LoaderIcon className="text-muted-foreground size-5 animate-spin" />
</div>
)}
@ -1046,7 +1046,7 @@ function ArtifactPdfPreview({
const pageWrapper = document.createElement("div");
pageWrapper.className =
"mx-auto mb-4 w-fit rounded-md border border-ws-e4e7ec bg-ws-ffffff p-2 shadow-sm";
"mx-auto mb-4 w-fit rounded-md border border-ws-line-default bg-ws-surface-base p-2 shadow-sm";
const canvas = document.createElement("canvas");
canvas.style.width = `${viewport.width}px`;
@ -1089,8 +1089,8 @@ function ArtifactPdfPreview({
if (error) {
return (
<div className={cn("relative overflow-auto bg-ws-f9f8fa p-4", className)}>
<div className="mx-auto grid max-w-xl gap-3 rounded-md border border-ws-e4e7ec bg-ws-ffffff p-5 text-center">
<div className={cn("relative overflow-auto bg-ws-surface-subtle p-4", className)}>
<div className="mx-auto grid max-w-xl gap-3 rounded-md border border-ws-line-default bg-ws-surface-base p-5 text-center">
<p className="text-sm font-medium break-all">{fileName}</p>
<p className="text-muted-foreground text-sm">{error}</p>
<a
@ -1107,15 +1107,15 @@ function ArtifactPdfPreview({
}
return (
<div className={cn("relative overflow-auto bg-ws-f9f8fa p-4", className)}>
<div className="mb-3 text-center text-xs text-ws-667085">
<div className={cn("relative overflow-auto bg-ws-surface-subtle p-4", className)}>
<div className="mb-3 text-center text-xs text-ws-text-muted">
{pageCount > 0
? t.artifactPreview.pageCountLabel(fileName, pageCount)
: fileName}
</div>
<div ref={containerRef} />
{isLoading && (
<div className="absolute inset-0 z-10 flex items-center justify-center bg-ws-ffffff/70">
<div className="absolute inset-0 z-10 flex items-center justify-center bg-ws-surface-base/70">
<LoaderIcon className="text-muted-foreground size-5 animate-spin" />
</div>
)}
@ -1313,7 +1313,7 @@ function ArtifactOfficePreview({
}, [canRenderPptx, t.artifactPreview.pptxDownloadHint]);
return (
<div className={cn("relative h-full overflow-hidden bg-ws-ffffff", className)}>
<div className={cn("relative h-full overflow-hidden bg-ws-surface-base", className)}>
{canRenderXlsx && sheetNames.length > 0 && (
<div className="border-border flex items-center gap-1 overflow-x-auto border-b p-2">
{sheetNames.map((sheetName) => (
@ -1323,7 +1323,7 @@ function ArtifactOfficePreview({
className={cn(
"rounded px-4 py-3 text-xs whitespace-nowrap",
activeSheet === sheetName
? "bg-ws-1500331a text-foreground"
? "bg-ws-accent-tint-soft text-foreground"
: "text-muted-foreground hover:text-foreground",
)}
onClick={() => setActiveSheet(sheetName)}
@ -1357,7 +1357,7 @@ function ArtifactOfficePreview({
/>
)}
{isLoading && (
<div className="absolute inset-0 z-10 flex items-center justify-center bg-ws-ffffff/85">
<div className="absolute inset-0 z-10 flex items-center justify-center bg-ws-surface-base/85">
<LoaderIcon className="text-muted-foreground size-5 animate-spin" />
</div>
)}
@ -1376,7 +1376,7 @@ function ArtifactPreviewFallback({
}) {
const { t } = useI18n();
return (
<div className="absolute inset-0 z-20 grid place-content-center bg-ws-ffffff p-6 text-center">
<div className="absolute inset-0 z-20 grid place-content-center bg-ws-surface-base p-6 text-center">
<p className="text-foreground mb-2 text-sm font-medium">{fileName}</p>
<p className="text-muted-foreground mb-3 text-xs">{message}</p>
<a
@ -1559,34 +1559,34 @@ function buildArtifactViewerSrcDoc({
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
:root {
--ws-color-f8f9fb: rgb(248 249 251);
--ws-color-ffffff: rgb(255 255 255);
--ws-color-0f172a: rgb(15 23 42);
--ws-color-667085: rgb(102 112 133);
--ws-color-e4e7ec: rgb(228 231 236);
--ws-color-f4f4f5: rgb(244 244 245);
--ws-color-000000: rgb(0 0 0);
--ws-color-2563eb: rgb(37 99 235);
--bg: var(--ws-color-f8f9fb);
--panel: var(--ws-color-ffffff);
--text: var(--ws-color-0f172a);
--muted: var(--ws-color-667085);
--line: var(--ws-color-e4e7ec);
--checker: var(--ws-color-f4f4f5);
--media-bg: var(--ws-color-000000);
--link: var(--ws-color-2563eb);
--ws-color-surface-app: rgb(248 249 251);
--ws-color-surface-base: rgb(255 255 255);
--ws-color-text-primary-strong: rgb(15 23 42);
--ws-color-text-muted: rgb(102 112 133);
--ws-color-line-default: rgb(228 231 236);
--ws-color-surface-checker: rgb(244 244 245);
--ws-color-black-solid: rgb(0 0 0);
--ws-color-info-primary: rgb(37 99 235);
--bg: var(--ws-color-surface-app);
--panel: var(--ws-color-surface-base);
--text: var(--ws-color-text-primary-strong);
--muted: var(--ws-color-text-muted);
--line: var(--ws-color-line-default);
--checker: var(--ws-color-surface-checker);
--media-bg: var(--ws-color-black-solid);
--link: var(--ws-color-info-primary);
--radius: 12px;
}
@media (prefers-color-scheme: dark) {
:root {
--ws-color-f8f9fb: rgb(32 36 44);
--ws-color-ffffff: rgb(42 39 49);
--ws-color-0f172a: rgb(230 234 242);
--ws-color-667085: rgb(152 162 179);
--ws-color-e4e7ec: rgb(58 61 69);
--ws-color-f4f4f5: rgb(44 47 56);
--ws-color-000000: rgb(0 0 0);
--ws-color-2563eb: rgb(127 178 255);
--ws-color-surface-app: rgb(32 36 44);
--ws-color-surface-base: rgb(42 39 49);
--ws-color-text-primary-strong: rgb(230 234 242);
--ws-color-text-muted: rgb(152 162 179);
--ws-color-line-default: rgb(58 61 69);
--ws-color-surface-checker: rgb(44 47 56);
--ws-color-black-solid: rgb(0 0 0);
--ws-color-info-primary: rgb(127 178 255);
}
}
* { box-sizing: border-box; }

View File

@ -104,7 +104,7 @@ export function ArtifactFileList({
<div className="absolute top-5 left-4">
{getFileIcon(
file,
"size-9 stroke-1 text-ws-333333 stroke-current",
"size-9 stroke-1 text-ws-fg-primary stroke-current",
)}
</div>
<CardDescription className="pl-10 text-xs">
@ -137,7 +137,7 @@ export function ArtifactFileList({
>
<Button
variant="ghost"
className="text-muted-foreground h-full! hover:bg-transparent! hover:text-ws-333333!"
className="text-muted-foreground h-full! hover:bg-transparent! hover:text-ws-fg-primary!"
>
<DownloadIcon className="size-4" />
{t.common.download}

View File

@ -34,7 +34,7 @@ export function DevTodoList({
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
<DropdownMenuContent
className={cn(
"z-[100] rounded-[20px] bg-ws-ffffff p-5 shadow-[0_0_20px_0_rgba(0,0,0,0.20)]",
"z-[100] rounded-[20px] bg-ws-surface-base p-5 shadow-[0_0_20px_0_rgba(0,0,0,0.20)]",
className,
)}
align="start"

View File

@ -157,7 +157,7 @@ export function IframeTestPanel() {
<div
ref={panelRef}
className={cn(
"fixed z-[9999] w-72 rounded-xl border border-violet-200 bg-ws-ffffff/95 shadow-2xl backdrop-blur-sm",
"fixed z-[9999] w-72 rounded-xl border border-violet-200 bg-ws-surface-base/95 shadow-2xl backdrop-blur-sm",
position ? "top-0 left-0" : "bottom-24 left-3",
)}
style={position ? { left: position.x, top: position.y } : undefined}

View File

@ -149,7 +149,7 @@ function WorkspaceToolButton({
return (
<PromptInputButton
className={cn(
"group h-full rounded-[10px] p-[10px]! hover:bg-ws-f9f8fa hover:text-ws-8e47f0",
"group h-full rounded-[10px] p-[10px]! hover:bg-ws-surface-subtle hover:text-ws-interactive-primary",
className,
)}
{...props}
@ -889,7 +889,7 @@ export function InputBox({
textareaRef.current?.focus();
}}
>
<DropdownMenuLabel className="p-0 text-sm text-ws-333333">
<DropdownMenuLabel className="p-0 text-sm text-ws-fg-primary">
{t.inputBox.addReference}
</DropdownMenuLabel>
<DropdownMenuSeparator className="mx-0 mt-[20px] mb-0" />
@ -1234,7 +1234,7 @@ function AddAttachmentsButton({ className }: { className?: string }) {
return (
<Tooltip content={t.inputBox.addAttachments}>
<WorkspaceToolButton
className={cn("text-ws-150033 hover:text-ws-8e47f0", className)}
className={cn("text-ws-base-1 hover:text-ws-interactive-primary", className)}
onClick={() => attachments.openFileDialog()}
>
<svg
@ -1272,7 +1272,7 @@ function HistoryButton({
return (
<Tooltip content={t.inputBox.history}>
<WorkspaceToolButton
className={cn("text-ws-150033 hover:text-ws-8e47f0", className)}
className={cn("text-ws-base-1 hover:text-ws-interactive-primary", className)}
onClick={() =>
router.replace(`/workspace/chats/${threadId}?is_chatting=true`)
}
@ -1330,7 +1330,7 @@ function IframeSkillDialogButton({
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="size-4 text-ws-150033 transition-[color] duration-200 group-hover:text-ws-8e47f0"
className="size-4 text-ws-base-1 transition-[color] duration-200 group-hover:text-ws-interactive-primary"
viewBox="0 0 12 16"
fill="none"
>

View File

@ -114,7 +114,7 @@ export function MessageGroup({
);
return (
<ChainOfThought
className={cn("w-full gap-2 rounded-lg bg-ws-ffffff", className)}
className={cn("w-full gap-2 rounded-lg bg-ws-surface-base", className)}
open={true}
>
{aboveLastToolCallSteps.length > 0 && (

View File

@ -225,7 +225,7 @@ export function MessageList({
{showScrollToBottomButton && (
<ConversationScrollButton
className={cn(
"z-20 rounded-full border bg-ws-ffffff/90 shadow-sm backdrop-blur-sm",
"z-20 rounded-full border bg-ws-surface-base/90 shadow-sm backdrop-blur-sm",
scrollButtonClassName,
)}
title={t.chats.scrollToBottom}

View File

@ -157,7 +157,7 @@ function ThemePreviewCard({
"relative overflow-hidden rounded-md border text-xs transition-colors",
previewMode === "dark"
? "border-neutral-800 bg-neutral-900 text-neutral-200"
: "border-slate-200 bg-ws-ffffff text-slate-900",
: "border-slate-200 bg-ws-surface-base text-slate-900",
)}
>
<div className="border-border/50 flex items-center gap-2 border-b px-3 py-2">

View File

@ -14,19 +14,19 @@ export function StreamingIndicator({
<div
className={cn(
dotSize,
"animate-bouncing rounded-full bg-ws-a3a1a1 opacity-100",
"animate-bouncing rounded-full bg-ws-icon-muted opacity-100",
)}
/>
<div
className={cn(
dotSize,
"animate-bouncing rounded-full bg-ws-a3a1a1 opacity-100 [animation-delay:0.2s]",
"animate-bouncing rounded-full bg-ws-icon-muted opacity-100 [animation-delay:0.2s]",
)}
/>
<div
className={cn(
dotSize,
"animate-bouncing rounded-full bg-ws-a3a1a1 opacity-100 [animation-delay:0.4s]",
"animate-bouncing rounded-full bg-ws-icon-muted opacity-100 [animation-delay:0.4s]",
)}
/>
</div>

View File

@ -39,7 +39,7 @@ export function TodoList({
return (
<div
className={cn(
"flex h-fit w-full origin-bottom translate-y-4 flex-col overflow-hidden rounded-t-xl border border-b-0 bg-ws-ffffff backdrop-blur-sm transition-all duration-200 ease-out",
"flex h-fit w-full origin-bottom translate-y-4 flex-col overflow-hidden rounded-t-xl border border-b-0 bg-ws-surface-base backdrop-blur-sm transition-all duration-200 ease-out",
hidden ? "pointer-events-none translate-y-8 opacity-0" : "",
className,
)}

View File

@ -43,7 +43,7 @@ export function WorkspaceHeader({ className }: { className?: string }) {
) : (
<div className="text-primary ml-2 cursor-default font-serif">
{/* TODO: 测试标识 */}
XClaw <span className="text-sm text-ws-000000c5">v3.2.8</span>
XClaw <span className="text-sm text-ws-text-subtle-strong">v3.2.8</span>
</div>
)}
<SidebarTrigger />

View File

@ -201,24 +201,24 @@
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--color-tooltip-background: var(--tooltip-background);
--color-ws-150033: var(--ws-color-150033);
--color-ws-333333: var(--ws-color-333333);
--color-ws-f9f8fa: var(--ws-color-f9f8fa);
--color-ws-fbfafc: var(--ws-color-fbfafc);
--color-ws-8e47f0: var(--ws-color-8e47f0);
--color-ws-e4e7ec: var(--ws-color-e4e7ec);
--color-ws-667085: var(--ws-color-667085);
--color-ws-a3a1a1: var(--ws-color-a3a1a1);
--color-ws-999999: var(--ws-color-999999);
--color-ws-000000c5: var(--ws-color-000000c5);
--color-ws-00000015: var(--ws-color-00000015);
--color-ws-1500331a: var(--ws-color-1500331a);
--color-ws-f8f9fb: var(--ws-color-f8f9fb);
--color-ws-ffffff: var(--ws-color-ffffff);
--color-ws-0f172a: var(--ws-color-0f172a);
--color-ws-f4f4f5: var(--ws-color-f4f4f5);
--color-ws-000000: var(--ws-color-000000);
--color-ws-2563eb: var(--ws-color-2563eb);
--color-ws-base-1: var(--ws-color-base-1);
--color-ws-fg-primary: var(--ws-color-fg-primary);
--color-ws-surface-subtle: var(--ws-color-surface-subtle);
--color-ws-surface-elevated: var(--ws-color-surface-elevated);
--color-ws-interactive-primary: var(--ws-color-interactive-primary);
--color-ws-line-default: var(--ws-color-line-default);
--color-ws-text-muted: var(--ws-color-text-muted);
--color-ws-icon-muted: var(--ws-color-icon-muted);
--color-ws-overlay-neutral: var(--ws-color-overlay-neutral);
--color-ws-text-subtle-strong: var(--ws-color-text-subtle-strong);
--color-ws-border-hairline: var(--ws-color-border-hairline);
--color-ws-accent-tint-soft: var(--ws-color-accent-tint-soft);
--color-ws-surface-app: var(--ws-color-surface-app);
--color-ws-surface-base: var(--ws-color-surface-base);
--color-ws-text-primary-strong: var(--ws-color-text-primary-strong);
--color-ws-surface-checker: var(--ws-color-surface-checker);
--color-ws-black-solid: var(--ws-color-black-solid);
--color-ws-info-primary: var(--ws-color-info-primary);
--animate-aurora: aurora 8s ease-in-out infinite alternate;
@keyframes aurora {
@ -307,24 +307,24 @@
--sidebar-border: oklch(0.922 0.0098 87.47);
--sidebar-ring: oklch(0.708 0 0);
--tooltip-background: #00000066;
--ws-color-150033: #150033;
--ws-color-333333: #333333;
--ws-color-f9f8fa: #f9f8fa;
--ws-color-fbfafc: #fbfafc;
--ws-color-8e47f0: #8e47f0;
--ws-color-e4e7ec: #e4e7ec;
--ws-color-667085: #667085;
--ws-color-a3a1a1: #a3a1a1;
--ws-color-999999: #999999;
--ws-color-000000c5: #000000c5;
--ws-color-00000015: #00000015;
--ws-color-1500331a: #1500331a;
--ws-color-f8f9fb: #f8f9fb;
--ws-color-ffffff: #ffffff;
--ws-color-0f172a: #0f172a;
--ws-color-f4f4f5: #f4f4f5;
--ws-color-000000: #000000;
--ws-color-2563eb: #2563eb;
--ws-color-base-1: #150033;
--ws-color-fg-primary: #333333;
--ws-color-surface-subtle: #f9f8fa;
--ws-color-surface-elevated: #fbfafc;
--ws-color-interactive-primary: #8e47f0;
--ws-color-line-default: #e4e7ec;
--ws-color-text-muted: #667085;
--ws-color-icon-muted: #a3a1a1;
--ws-color-overlay-neutral: #999999;
--ws-color-text-subtle-strong: #000000c5;
--ws-color-border-hairline: #00000015;
--ws-color-accent-tint-soft: #1500331a;
--ws-color-surface-app: #f8f9fb;
--ws-color-surface-base: #ffffff;
--ws-color-text-primary-strong: #0f172a;
--ws-color-surface-checker: #f4f4f5;
--ws-color-black-solid: #000000;
--ws-color-info-primary: #2563eb;
}
.dark {
@ -360,24 +360,24 @@
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
--tooltip-background: oklch(0.85 0 0);
--ws-color-150033: #f4ebff;
--ws-color-333333: #f5f5f5;
--ws-color-f9f8fa: #1f1f1f;
--ws-color-fbfafc: #24222a;
--ws-color-8e47f0: #b987ff;
--ws-color-e4e7ec: #3b3f48;
--ws-color-667085: #98a2b3;
--ws-color-a3a1a1: #d0d0d0;
--ws-color-999999: #c2c2c2;
--ws-color-000000c5: #ffffffcc;
--ws-color-00000015: #ffffff1f;
--ws-color-1500331a: #f4ebff24;
--ws-color-f8f9fb: #20242c;
--ws-color-ffffff: #2a2731;
--ws-color-0f172a: #e6eaf2;
--ws-color-f4f4f5: #2c2f38;
--ws-color-000000: #000000;
--ws-color-2563eb: #7fb2ff;
--ws-color-base-1: #f4ebff;
--ws-color-fg-primary: #f5f5f5;
--ws-color-surface-subtle: #1f1f1f;
--ws-color-surface-elevated: #24222a;
--ws-color-interactive-primary: #b987ff;
--ws-color-line-default: #3b3f48;
--ws-color-text-muted: #98a2b3;
--ws-color-icon-muted: #d0d0d0;
--ws-color-overlay-neutral: #c2c2c2;
--ws-color-text-subtle-strong: #ffffffcc;
--ws-color-border-hairline: #ffffff1f;
--ws-color-accent-tint-soft: #f4ebff24;
--ws-color-surface-app: #20242c;
--ws-color-surface-base: #2a2731;
--ws-color-text-primary-strong: #e6eaf2;
--ws-color-surface-checker: #2c2f38;
--ws-color-black-solid: #000000;
--ws-color-info-primary: #7fb2ff;
font-weight: 300;
}

View File

@ -1,25 +1,36 @@
/**
* Workspace Token
*
*
* 1) Token UI 使 `bg-ws-surface-base`
* 2) `src/styles/globals.css` CSS
* - `:root` `.dark` `--ws-color-<token-suffix>`
* - `@theme inline` `--color-ws-<token-suffix>`
* 3) `scripts/color-guard.mjs`
*/
export type WorkspaceColorToken = {
light: `#${string}`;
dark: `#${string}`;
};
// Token 键保持语义化且稳定:`ws-<role>-<level>`(不要再使用原始 hex 命名)。
export const WORKSPACE_COLOR_TOKENS = {
"ws-150033": { light: "#150033", dark: "#f4ebff" },
"ws-333333": { light: "#333333", dark: "#f5f5f5" },
"ws-f9f8fa": { light: "#f9f8fa", dark: "#1f1f1f" },
"ws-fbfafc": { light: "#fbfafc", dark: "#24222a" },
"ws-8e47f0": { light: "#8e47f0", dark: "#b987ff" },
"ws-e4e7ec": { light: "#e4e7ec", dark: "#3b3f48" },
"ws-667085": { light: "#667085", dark: "#98a2b3" },
"ws-a3a1a1": { light: "#a3a1a1", dark: "#d0d0d0" },
"ws-999999": { light: "#999999", dark: "#c2c2c2" },
"ws-000000c5": { light: "#000000c5", dark: "#ffffffcc" },
"ws-00000015": { light: "#00000015", dark: "#ffffff1f" },
"ws-1500331a": { light: "#1500331a", dark: "#f4ebff24" },
"ws-f8f9fb": { light: "#f8f9fb", dark: "#20242c" },
"ws-ffffff": { light: "#ffffff", dark: "#2a2731" },
"ws-0f172a": { light: "#0f172a", dark: "#e6eaf2" },
"ws-f4f4f5": { light: "#f4f4f5", dark: "#2c2f38" },
"ws-000000": { light: "#000000", dark: "#000000" },
"ws-2563eb": { light: "#2563eb", dark: "#7fb2ff" },
"ws-base-1": { light: "#150033", dark: "#f4ebff" },
"ws-fg-primary": { light: "#333333", dark: "#f5f5f5" },
"ws-surface-subtle": { light: "#f9f8fa", dark: "#1f1f1f" },
"ws-surface-elevated": { light: "#fbfafc", dark: "#24222a" },
"ws-interactive-primary": { light: "#8e47f0", dark: "#b987ff" },
"ws-line-default": { light: "#e4e7ec", dark: "#3b3f48" },
"ws-text-muted": { light: "#667085", dark: "#98a2b3" },
"ws-icon-muted": { light: "#a3a1a1", dark: "#d0d0d0" },
"ws-overlay-neutral": { light: "#999999", dark: "#c2c2c2" },
"ws-text-subtle-strong": { light: "#000000c5", dark: "#ffffffcc" },
"ws-border-hairline": { light: "#00000015", dark: "#ffffff1f" },
"ws-accent-tint-soft": { light: "#1500331a", dark: "#f4ebff24" },
"ws-surface-app": { light: "#f8f9fb", dark: "#20242c" },
"ws-surface-base": { light: "#ffffff", dark: "#2a2731" },
"ws-text-primary-strong": { light: "#0f172a", dark: "#e6eaf2" },
"ws-surface-checker": { light: "#f4f4f5", dark: "#2c2f38" },
"ws-black-solid": { light: "#000000", dark: "#000000" },
"ws-info-primary": { light: "#2563eb", dark: "#7fb2ff" },
} as const satisfies Record<string, WorkspaceColorToken>;