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", () => {
|
void test("parseBrandFromSearchParams returns correct brand per param value", () => {
|
||||||
assert.equal(parseBrandFromSearchParams(new URLSearchParams("isSxwz=true")), "sxwz");
|
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);
|
assert.equal(parseBrandFromSearchParams(new URLSearchParams("")), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
void test("resolveBrandSession falls back to default without url or storage", () => {
|
void test("resolveBrandSession falls back to xclaw without url or storage", () => {
|
||||||
assert.equal(resolveBrandSession({ urlBrand: null, storedBrand: null }), "default");
|
assert.equal(resolveBrandSession({ urlBrand: null, storedBrand: null }), "xclaw");
|
||||||
});
|
});
|
||||||
|
|
||||||
void test("resolveBrandSession keeps stored sxwz when later url omits the flag", () => {
|
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", () => {
|
void test("resolveBrandSession downgrades stored sxwz when url explicitly sets isSxwz=false", () => {
|
||||||
const urlBrand = parseBrandFromSearchParams(new URLSearchParams("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", () => {
|
void test("resolveBrandSession upgrades to sxwz when url flag is true", () => {
|
||||||
const urlBrand = parseBrandFromSearchParams(new URLSearchParams("isSxwz=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", () => {
|
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(getBrandRootClassName("sxwz"), "brand-sxwz");
|
||||||
assert.equal(BRAND_SESSION_STORAGE_KEY, "deerflow.brand-session");
|
assert.equal(BRAND_SESSION_STORAGE_KEY, "deerflow.brand-session");
|
||||||
});
|
});
|
||||||
|
|
||||||
void test("getBrandPrimaryColor returns the right global brand primary", () => {
|
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);
|
assert.equal(getBrandPrimaryColor("sxwz"), SXWZ_BRAND_PRIMARY);
|
||||||
});
|
});
|
||||||
|
|
||||||
void test("getBrandPrimaryColorWithAlpha preserves alpha values across brands", () => {
|
void test("getBrandPrimaryColorWithAlpha preserves alpha values across brands", () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
getBrandPrimaryColorWithAlpha("default", "1A"),
|
getBrandPrimaryColorWithAlpha("xclaw", "1A"),
|
||||||
`${DEFAULT_BRAND_PRIMARY}1A`,
|
`${DEFAULT_BRAND_PRIMARY}1A`,
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
@ -60,7 +60,7 @@ void test("getBrandPrimaryColorWithAlpha preserves alpha values across brands",
|
|||||||
`${SXWZ_BRAND_PRIMARY}1A`,
|
`${SXWZ_BRAND_PRIMARY}1A`,
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
getBrandPrimaryColorWithAlpha("default", "99"),
|
getBrandPrimaryColorWithAlpha("xclaw", "99"),
|
||||||
`${DEFAULT_BRAND_PRIMARY}99`,
|
`${DEFAULT_BRAND_PRIMARY}99`,
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
@ -71,25 +71,29 @@ void test("getBrandPrimaryColorWithAlpha preserves alpha values across brands",
|
|||||||
|
|
||||||
void test("getInitialBrandFromBrowser prioritizes url brand on first render", () => {
|
void test("getInitialBrandFromBrowser prioritizes url brand on first render", () => {
|
||||||
const searchParams = new URLSearchParams("isSxwz=true");
|
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(
|
assert.equal(
|
||||||
getInitialBrandFromBrowser({
|
getInitialBrandFromBrowser({
|
||||||
searchParams: new URLSearchParams("isSxwz=false"),
|
searchParams: new URLSearchParams("isSxwz=false"),
|
||||||
storedBrand: "sxwz",
|
storage,
|
||||||
}),
|
}),
|
||||||
"default",
|
"xclaw",
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
getInitialBrandFromBrowser({
|
getInitialBrandFromBrowser({
|
||||||
searchParams: new URLSearchParams(""),
|
searchParams: new URLSearchParams(""),
|
||||||
storedBrand: "sxwz",
|
storage,
|
||||||
}),
|
}),
|
||||||
"sxwz",
|
"xclaw",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
void test("syncBrandClassName rewrites brand classes on arbitrary targets", () => {
|
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 = {
|
const target = {
|
||||||
classList: {
|
classList: {
|
||||||
add: (value: string) => classSet.add(value),
|
add: (value: string) => classSet.add(value),
|
||||||
@ -100,6 +104,6 @@ void test("syncBrandClassName rewrites brand classes on arbitrary targets", () =
|
|||||||
syncBrandClassName(target, "sxwz");
|
syncBrandClassName(target, "sxwz");
|
||||||
|
|
||||||
assert.equal(classSet.has("foo"), true);
|
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);
|
assert.equal(classSet.has("brand-sxwz"), true);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
export const BRAND_SESSION_STORAGE_KEY = "deerflow.brand-session";
|
export const BRAND_SESSION_STORAGE_KEY = "deerflow.brand-session";
|
||||||
export const DEFAULT_BRAND = "default" as const;
|
export const XCLAW_BRAND = "xclaw" as const;
|
||||||
const SXWZ_BRAND = "sxwz" as const;
|
export const SXWZ_BRAND = "sxwz" as const;
|
||||||
const BRAND_PRIMARY_COLORS = {
|
const BRAND_PRIMARY_COLORS = {
|
||||||
default: `#${"150033"}`,
|
xclaw: `#${"150033"}`,
|
||||||
sxwz: `#${"000F33"}`,
|
sxwz: `#${"000F33"}`,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type Brand = typeof DEFAULT_BRAND | typeof SXWZ_BRAND;
|
export type Brand = typeof XCLAW_BRAND | typeof SXWZ_BRAND;
|
||||||
|
|
||||||
export type BrandCopy = {
|
export type BrandCopy = {
|
||||||
productLabel: string;
|
productLabel: string;
|
||||||
@ -16,7 +16,7 @@ export type BrandCopy = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const BRAND_COPY: Record<Brand, BrandCopy> = {
|
export const BRAND_COPY: Record<Brand, BrandCopy> = {
|
||||||
default: {
|
xclaw: {
|
||||||
productLabel: "轻办公",
|
productLabel: "轻办公",
|
||||||
appName: "coxworker",
|
appName: "coxworker",
|
||||||
appLogoSrc: "/coxwork.png",
|
appLogoSrc: "/coxwork.png",
|
||||||
@ -29,7 +29,7 @@ export const BRAND_COPY: Record<Brand, BrandCopy> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function isBrand(value: string | null): value is Brand {
|
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(
|
export function parseBrandFromSearchParams(
|
||||||
@ -37,7 +37,7 @@ export function parseBrandFromSearchParams(
|
|||||||
): Brand | null {
|
): Brand | null {
|
||||||
const value = searchParams.get("isSxwz");
|
const value = searchParams.get("isSxwz");
|
||||||
if (value === "true") return SXWZ_BRAND;
|
if (value === "true") return SXWZ_BRAND;
|
||||||
if (value === "false") return DEFAULT_BRAND;
|
if (value === "false") return XCLAW_BRAND;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,30 +52,47 @@ export function resolveBrandSession({
|
|||||||
return SXWZ_BRAND;
|
return SXWZ_BRAND;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlBrand === DEFAULT_BRAND) {
|
if (urlBrand === XCLAW_BRAND) {
|
||||||
return DEFAULT_BRAND;
|
return XCLAW_BRAND;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storedBrand === SXWZ_BRAND) {
|
if (storedBrand === SXWZ_BRAND) {
|
||||||
return SXWZ_BRAND;
|
return SXWZ_BRAND;
|
||||||
}
|
}
|
||||||
|
|
||||||
return DEFAULT_BRAND;
|
return XCLAW_BRAND;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInitialBrandFromBrowser({
|
export function getInitialBrandFromBrowser({
|
||||||
searchParams,
|
searchParams,
|
||||||
storedBrand,
|
storage,
|
||||||
}: {
|
}: {
|
||||||
searchParams: URLSearchParams;
|
searchParams: URLSearchParams;
|
||||||
storedBrand: Brand | null;
|
storage: Pick<Storage, "getItem" | "setItem">;
|
||||||
}): Brand {
|
}): Brand {
|
||||||
const urlBrand = parseBrandFromSearchParams(searchParams);
|
const isSxwz = searchParams.get("isSxwz");
|
||||||
return resolveBrandSession({ urlBrand, storedBrand });
|
|
||||||
|
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 {
|
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 = {
|
type BrandClassTarget = {
|
||||||
@ -89,7 +106,7 @@ export function syncBrandClassName(
|
|||||||
target: BrandClassTarget,
|
target: BrandClassTarget,
|
||||||
brand: Brand,
|
brand: Brand,
|
||||||
): void {
|
): void {
|
||||||
target.classList.remove("brand-default", "brand-sxwz");
|
target.classList.remove("brand-xclaw", "brand-sxwz");
|
||||||
target.classList.add(getBrandRootClassName(brand));
|
target.classList.add(getBrandRootClassName(brand));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { createContext, useContext, useState, type ReactNode } from "react";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
BRAND_COPY,
|
BRAND_COPY,
|
||||||
DEFAULT_BRAND,
|
XCLAW_BRAND,
|
||||||
getInitialBrandFromBrowser,
|
getInitialBrandFromBrowser,
|
||||||
getBrandRootClassName,
|
getBrandRootClassName,
|
||||||
type Brand,
|
type Brand,
|
||||||
@ -21,16 +21,12 @@ const BrandContext = createContext<BrandContextValue | null>(null);
|
|||||||
|
|
||||||
function getInitialBrand(): Brand {
|
function getInitialBrand(): Brand {
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return DEFAULT_BRAND;
|
return XCLAW_BRAND;
|
||||||
}
|
}
|
||||||
|
|
||||||
const storedBrand = window.sessionStorage.getItem("deerflow.brand-session");
|
|
||||||
return getInitialBrandFromBrowser({
|
return getInitialBrandFromBrowser({
|
||||||
searchParams: new URLSearchParams(window.location.search),
|
searchParams: new URLSearchParams(window.location.search),
|
||||||
storedBrand:
|
storage: window.sessionStorage,
|
||||||
storedBrand === "sxwz" || storedBrand === DEFAULT_BRAND
|
|
||||||
? storedBrand
|
|
||||||
: null,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,6 @@ const currentDir = path.dirname(url.fileURLToPath(import.meta.url));
|
|||||||
const globalsCss = readFileSync(path.join(currentDir, "globals.css"), "utf8");
|
const globalsCss = readFileSync(path.join(currentDir, "globals.css"), "utf8");
|
||||||
|
|
||||||
void test("brand selectors target :root to outrank default root variables", () => {
|
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*\{/);
|
assert.match(globalsCss, /:root\.brand-sxwz\s*\{/);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -68,7 +68,7 @@
|
|||||||
@source inline("bg-{background,muted,primary,secondary,accent}");
|
@source inline("bg-{background,muted,primary,secondary,accent}");
|
||||||
@source inline("border-{border,input}");
|
@source inline("border-{border,input}");
|
||||||
|
|
||||||
.brand-default {
|
.brand-xclaw {
|
||||||
--brand-color-primary: #150033;
|
--brand-color-primary: #150033;
|
||||||
--brand-color-primary-10: #1500331a;
|
--brand-color-primary-10: #1500331a;
|
||||||
--brand-color-primary-60: #15003399;
|
--brand-color-primary-60: #15003399;
|
||||||
|
|||||||
@ -14,17 +14,17 @@ export type WorkspaceColorToken = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const BRAND_PRIMARY_COLOR_TOKENS = {
|
export const BRAND_PRIMARY_COLOR_TOKENS = {
|
||||||
default: "#150033",
|
xclaw: "#150033",
|
||||||
sxwz: "#000F33",
|
sxwz: "#000F33",
|
||||||
defaultAlpha10: "#1500331A",
|
xclawAlpha10: "#1500331A",
|
||||||
sxwzAlpha10: "#000F331A",
|
sxwzAlpha10: "#000F331A",
|
||||||
defaultAlpha60: "#15003399",
|
xclawAlpha60: "#15003399",
|
||||||
sxwzAlpha60: "#000F3399",
|
sxwzAlpha60: "#000F3399",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Token 键保持语义化且稳定:`ws-<role>-<level>`(不要再使用原始 hex 命名)。
|
// Token 键保持语义化且稳定:`ws-<role>-<level>`(不要再使用原始 hex 命名)。
|
||||||
export const WORKSPACE_COLOR_TOKENS = {
|
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-fg-primary": { light: "#333333", dark: "#f5f5f5" },
|
||||||
"ws-surface-subtle": { light: "#f9f8fa", dark: "#1f1f1f" },
|
"ws-surface-subtle": { light: "#f9f8fa", dark: "#1f1f1f" },
|
||||||
"ws-surface-elevated": { light: "#fbfafc", dark: "#24222a" },
|
"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-text-subtle-strong": { light: "#000000c5", dark: "#ffffffcc" },
|
||||||
"ws-border-hairline": { light: "#00000015", dark: "#ffffff1f" },
|
"ws-border-hairline": { light: "#00000015", dark: "#ffffff1f" },
|
||||||
"ws-accent-tint-soft": {
|
"ws-accent-tint-soft": {
|
||||||
light: BRAND_PRIMARY_COLOR_TOKENS.defaultAlpha10,
|
light: BRAND_PRIMARY_COLOR_TOKENS.xclawAlpha10,
|
||||||
dark: "#f4ebff24",
|
dark: "#f4ebff24",
|
||||||
},
|
},
|
||||||
"ws-surface-app": { light: "#f8f9fb", dark: "#20242c" },
|
"ws-surface-app": { light: "#f8f9fb", dark: "#20242c" },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user