refactor(brand): 重构品牌系统,移除 SESSION_BRAND 并完善初始化逻辑
- 将 DEFAULT_BRAND 重命名为 XCLAW_BRAND,统一品牌命名规范 - 删除未使用的 SESSION_BRAND 类型和相关配置 - 优化 getInitialBrandFromBrowser 逻辑:根据 isSxwz 参数直接设置并返回对应品牌 - 更新品牌颜色 tokens 和 CSS 选择器测试 - 同步更新 provider.tsx 和相关测试用例
This commit is contained in:
parent
02692444a5
commit
d0afbf1897
@ -17,12 +17,12 @@ const SXWZ_BRAND_PRIMARY = `#${"000F33"}`;
|
||||
|
||||
void test("parseBrandFromSearchParams returns correct brand per param value", () => {
|
||||
assert.equal(parseBrandFromSearchParams(new URLSearchParams("isSxwz=true")), "sxwz");
|
||||
assert.equal(parseBrandFromSearchParams(new URLSearchParams("isSxwz=false")), "default");
|
||||
assert.equal(parseBrandFromSearchParams(new URLSearchParams("isSxwz=false")), "xclaw");
|
||||
assert.equal(parseBrandFromSearchParams(new URLSearchParams("")), null);
|
||||
});
|
||||
|
||||
void test("resolveBrandSession falls back to default without url or storage", () => {
|
||||
assert.equal(resolveBrandSession({ urlBrand: null, storedBrand: null }), "default");
|
||||
void test("resolveBrandSession falls back to xclaw without url or storage", () => {
|
||||
assert.equal(resolveBrandSession({ urlBrand: null, storedBrand: null }), "xclaw");
|
||||
});
|
||||
|
||||
void test("resolveBrandSession keeps stored sxwz when later url omits the flag", () => {
|
||||
@ -31,28 +31,28 @@ void test("resolveBrandSession keeps stored sxwz when later url omits the flag",
|
||||
|
||||
void test("resolveBrandSession downgrades stored sxwz when url explicitly sets isSxwz=false", () => {
|
||||
const urlBrand = parseBrandFromSearchParams(new URLSearchParams("isSxwz=false"));
|
||||
assert.equal(resolveBrandSession({ urlBrand, storedBrand: "sxwz" }), "default");
|
||||
assert.equal(resolveBrandSession({ urlBrand, storedBrand: "sxwz" }), "xclaw");
|
||||
});
|
||||
|
||||
void test("resolveBrandSession upgrades to sxwz when url flag is true", () => {
|
||||
const urlBrand = parseBrandFromSearchParams(new URLSearchParams("isSxwz=true"));
|
||||
assert.equal(resolveBrandSession({ urlBrand, storedBrand: "default" }), "sxwz");
|
||||
assert.equal(resolveBrandSession({ urlBrand, storedBrand: "xclaw" }), "sxwz");
|
||||
});
|
||||
|
||||
void test("getBrandRootClassName returns stable workspace hook classes", () => {
|
||||
assert.equal(getBrandRootClassName("default"), "brand-default");
|
||||
assert.equal(getBrandRootClassName("xclaw"), "brand-xclaw");
|
||||
assert.equal(getBrandRootClassName("sxwz"), "brand-sxwz");
|
||||
assert.equal(BRAND_SESSION_STORAGE_KEY, "deerflow.brand-session");
|
||||
});
|
||||
|
||||
void test("getBrandPrimaryColor returns the right global brand primary", () => {
|
||||
assert.equal(getBrandPrimaryColor("default"), DEFAULT_BRAND_PRIMARY);
|
||||
assert.equal(getBrandPrimaryColor("xclaw"), DEFAULT_BRAND_PRIMARY);
|
||||
assert.equal(getBrandPrimaryColor("sxwz"), SXWZ_BRAND_PRIMARY);
|
||||
});
|
||||
|
||||
void test("getBrandPrimaryColorWithAlpha preserves alpha values across brands", () => {
|
||||
assert.equal(
|
||||
getBrandPrimaryColorWithAlpha("default", "1A"),
|
||||
getBrandPrimaryColorWithAlpha("xclaw", "1A"),
|
||||
`${DEFAULT_BRAND_PRIMARY}1A`,
|
||||
);
|
||||
assert.equal(
|
||||
@ -60,7 +60,7 @@ void test("getBrandPrimaryColorWithAlpha preserves alpha values across brands",
|
||||
`${SXWZ_BRAND_PRIMARY}1A`,
|
||||
);
|
||||
assert.equal(
|
||||
getBrandPrimaryColorWithAlpha("default", "99"),
|
||||
getBrandPrimaryColorWithAlpha("xclaw", "99"),
|
||||
`${DEFAULT_BRAND_PRIMARY}99`,
|
||||
);
|
||||
assert.equal(
|
||||
@ -71,25 +71,29 @@ void test("getBrandPrimaryColorWithAlpha preserves alpha values across brands",
|
||||
|
||||
void test("getInitialBrandFromBrowser prioritizes url brand on first render", () => {
|
||||
const searchParams = new URLSearchParams("isSxwz=true");
|
||||
assert.equal(getInitialBrandFromBrowser({ searchParams, storedBrand: null }), "sxwz");
|
||||
const storage = {
|
||||
getItem: () => null,
|
||||
setItem: () => {},
|
||||
};
|
||||
assert.equal(getInitialBrandFromBrowser({ searchParams, storage }), "sxwz");
|
||||
assert.equal(
|
||||
getInitialBrandFromBrowser({
|
||||
searchParams: new URLSearchParams("isSxwz=false"),
|
||||
storedBrand: "sxwz",
|
||||
storage,
|
||||
}),
|
||||
"default",
|
||||
"xclaw",
|
||||
);
|
||||
assert.equal(
|
||||
getInitialBrandFromBrowser({
|
||||
searchParams: new URLSearchParams(""),
|
||||
storedBrand: "sxwz",
|
||||
storage,
|
||||
}),
|
||||
"sxwz",
|
||||
"xclaw",
|
||||
);
|
||||
});
|
||||
|
||||
void test("syncBrandClassName rewrites brand classes on arbitrary targets", () => {
|
||||
const classSet = new Set(["foo", "brand-default"]);
|
||||
const classSet = new Set(["foo", "brand-xclaw"]);
|
||||
const target = {
|
||||
classList: {
|
||||
add: (value: string) => classSet.add(value),
|
||||
@ -100,6 +104,6 @@ void test("syncBrandClassName rewrites brand classes on arbitrary targets", () =
|
||||
syncBrandClassName(target, "sxwz");
|
||||
|
||||
assert.equal(classSet.has("foo"), true);
|
||||
assert.equal(classSet.has("brand-default"), false);
|
||||
assert.equal(classSet.has("brand-xclaw"), false);
|
||||
assert.equal(classSet.has("brand-sxwz"), true);
|
||||
});
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
export const BRAND_SESSION_STORAGE_KEY = "deerflow.brand-session";
|
||||
export const DEFAULT_BRAND = "default" as const;
|
||||
const SXWZ_BRAND = "sxwz" as const;
|
||||
export const XCLAW_BRAND = "xclaw" as const;
|
||||
export const SXWZ_BRAND = "sxwz" as const;
|
||||
const BRAND_PRIMARY_COLORS = {
|
||||
default: `#${"150033"}`,
|
||||
xclaw: `#${"150033"}`,
|
||||
sxwz: `#${"000F33"}`,
|
||||
} as const;
|
||||
|
||||
export type Brand = typeof DEFAULT_BRAND | typeof SXWZ_BRAND;
|
||||
export type Brand = typeof XCLAW_BRAND | typeof SXWZ_BRAND;
|
||||
|
||||
export type BrandCopy = {
|
||||
productLabel: string;
|
||||
@ -16,7 +16,7 @@ export type BrandCopy = {
|
||||
};
|
||||
|
||||
export const BRAND_COPY: Record<Brand, BrandCopy> = {
|
||||
default: {
|
||||
xclaw: {
|
||||
productLabel: "轻办公",
|
||||
appName: "coxworker",
|
||||
appLogoSrc: "/coxwork.png",
|
||||
@ -29,7 +29,7 @@ export const BRAND_COPY: Record<Brand, BrandCopy> = {
|
||||
};
|
||||
|
||||
export function isBrand(value: string | null): value is Brand {
|
||||
return value === DEFAULT_BRAND || value === SXWZ_BRAND;
|
||||
return value === XCLAW_BRAND || value === SXWZ_BRAND;
|
||||
}
|
||||
|
||||
export function parseBrandFromSearchParams(
|
||||
@ -37,7 +37,7 @@ export function parseBrandFromSearchParams(
|
||||
): Brand | null {
|
||||
const value = searchParams.get("isSxwz");
|
||||
if (value === "true") return SXWZ_BRAND;
|
||||
if (value === "false") return DEFAULT_BRAND;
|
||||
if (value === "false") return XCLAW_BRAND;
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -52,30 +52,47 @@ export function resolveBrandSession({
|
||||
return SXWZ_BRAND;
|
||||
}
|
||||
|
||||
if (urlBrand === DEFAULT_BRAND) {
|
||||
return DEFAULT_BRAND;
|
||||
if (urlBrand === XCLAW_BRAND) {
|
||||
return XCLAW_BRAND;
|
||||
}
|
||||
|
||||
if (storedBrand === SXWZ_BRAND) {
|
||||
return SXWZ_BRAND;
|
||||
}
|
||||
|
||||
return DEFAULT_BRAND;
|
||||
return XCLAW_BRAND;
|
||||
}
|
||||
|
||||
export function getInitialBrandFromBrowser({
|
||||
searchParams,
|
||||
storedBrand,
|
||||
storage,
|
||||
}: {
|
||||
searchParams: URLSearchParams;
|
||||
storedBrand: Brand | null;
|
||||
storage: Pick<Storage, "getItem" | "setItem">;
|
||||
}): Brand {
|
||||
const urlBrand = parseBrandFromSearchParams(searchParams);
|
||||
return resolveBrandSession({ urlBrand, storedBrand });
|
||||
const isSxwz = searchParams.get("isSxwz");
|
||||
|
||||
if (isSxwz === "true") {
|
||||
storage.setItem(BRAND_SESSION_STORAGE_KEY, SXWZ_BRAND);
|
||||
return SXWZ_BRAND;
|
||||
}
|
||||
|
||||
if (isSxwz === "false") {
|
||||
storage.setItem(BRAND_SESSION_STORAGE_KEY, XCLAW_BRAND);
|
||||
return XCLAW_BRAND;
|
||||
}
|
||||
|
||||
const storedBrand = storage.getItem(BRAND_SESSION_STORAGE_KEY);
|
||||
if (storedBrand === SXWZ_BRAND || storedBrand === XCLAW_BRAND) {
|
||||
return storedBrand;
|
||||
}
|
||||
|
||||
return XCLAW_BRAND;
|
||||
}
|
||||
|
||||
export function getBrandRootClassName(brand: Brand): string {
|
||||
return brand === SXWZ_BRAND ? "brand-sxwz" : "brand-default";
|
||||
if (brand === SXWZ_BRAND) return "brand-sxwz";
|
||||
return "brand-xclaw";
|
||||
}
|
||||
|
||||
type BrandClassTarget = {
|
||||
@ -89,7 +106,7 @@ export function syncBrandClassName(
|
||||
target: BrandClassTarget,
|
||||
brand: Brand,
|
||||
): void {
|
||||
target.classList.remove("brand-default", "brand-sxwz");
|
||||
target.classList.remove("brand-xclaw", "brand-sxwz");
|
||||
target.classList.add(getBrandRootClassName(brand));
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { createContext, useContext, useState, type ReactNode } from "react";
|
||||
|
||||
import {
|
||||
BRAND_COPY,
|
||||
DEFAULT_BRAND,
|
||||
XCLAW_BRAND,
|
||||
getInitialBrandFromBrowser,
|
||||
getBrandRootClassName,
|
||||
type Brand,
|
||||
@ -21,16 +21,12 @@ const BrandContext = createContext<BrandContextValue | null>(null);
|
||||
|
||||
function getInitialBrand(): Brand {
|
||||
if (typeof window === "undefined") {
|
||||
return DEFAULT_BRAND;
|
||||
return XCLAW_BRAND;
|
||||
}
|
||||
|
||||
const storedBrand = window.sessionStorage.getItem("deerflow.brand-session");
|
||||
return getInitialBrandFromBrowser({
|
||||
searchParams: new URLSearchParams(window.location.search),
|
||||
storedBrand:
|
||||
storedBrand === "sxwz" || storedBrand === DEFAULT_BRAND
|
||||
? storedBrand
|
||||
: null,
|
||||
storage: window.sessionStorage,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,6 @@ const currentDir = path.dirname(url.fileURLToPath(import.meta.url));
|
||||
const globalsCss = readFileSync(path.join(currentDir, "globals.css"), "utf8");
|
||||
|
||||
void test("brand selectors target :root to outrank default root variables", () => {
|
||||
assert.match(globalsCss, /:root\.brand-default\s*\{/);
|
||||
assert.match(globalsCss, /:root\.brand-xclaw\s*\{/);
|
||||
assert.match(globalsCss, /:root\.brand-sxwz\s*\{/);
|
||||
});
|
||||
|
||||
@ -68,7 +68,7 @@
|
||||
@source inline("bg-{background,muted,primary,secondary,accent}");
|
||||
@source inline("border-{border,input}");
|
||||
|
||||
.brand-default {
|
||||
.brand-xclaw {
|
||||
--brand-color-primary: #150033;
|
||||
--brand-color-primary-10: #1500331a;
|
||||
--brand-color-primary-60: #15003399;
|
||||
|
||||
@ -14,17 +14,17 @@ export type WorkspaceColorToken = {
|
||||
};
|
||||
|
||||
export const BRAND_PRIMARY_COLOR_TOKENS = {
|
||||
default: "#150033",
|
||||
xclaw: "#150033",
|
||||
sxwz: "#000F33",
|
||||
defaultAlpha10: "#1500331A",
|
||||
xclawAlpha10: "#1500331A",
|
||||
sxwzAlpha10: "#000F331A",
|
||||
defaultAlpha60: "#15003399",
|
||||
xclawAlpha60: "#15003399",
|
||||
sxwzAlpha60: "#000F3399",
|
||||
} as const;
|
||||
|
||||
// Token 键保持语义化且稳定:`ws-<role>-<level>`(不要再使用原始 hex 命名)。
|
||||
export const WORKSPACE_COLOR_TOKENS = {
|
||||
"ws-base-1": { light: BRAND_PRIMARY_COLOR_TOKENS.default, dark: "#f4ebff" },
|
||||
"ws-base-1": { light: BRAND_PRIMARY_COLOR_TOKENS.xclaw, dark: "#f4ebff" },
|
||||
"ws-fg-primary": { light: "#333333", dark: "#f5f5f5" },
|
||||
"ws-surface-subtle": { light: "#f9f8fa", dark: "#1f1f1f" },
|
||||
"ws-surface-elevated": { light: "#fbfafc", dark: "#24222a" },
|
||||
@ -36,7 +36,7 @@ export const WORKSPACE_COLOR_TOKENS = {
|
||||
"ws-text-subtle-strong": { light: "#000000c5", dark: "#ffffffcc" },
|
||||
"ws-border-hairline": { light: "#00000015", dark: "#ffffff1f" },
|
||||
"ws-accent-tint-soft": {
|
||||
light: BRAND_PRIMARY_COLOR_TOKENS.defaultAlpha10,
|
||||
light: BRAND_PRIMARY_COLOR_TOKENS.xclawAlpha10,
|
||||
dark: "#f4ebff24",
|
||||
},
|
||||
"ws-surface-app": { light: "#f8f9fb", dark: "#20242c" },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user