mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-23 21:06:50 +00:00
refactor(design-tokens): V2 engine + verifier
Migrates component styling to the element/tone/emphasis/state token system and adds a standalone verifier (`npm run verify:theme`) plus vitest coverage that exercises every mode × theme × contrast-grid variant. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
9adaded472
commit
7edfbb11dc
94 changed files with 3820 additions and 2311 deletions
|
|
@ -413,13 +413,8 @@ protocol.registerSchemesAsPrivileged([
|
|||
process.env.APP_ROOT = MAIN_DIST;
|
||||
process.env.VITE_PUBLIC = VITE_PUBLIC;
|
||||
|
||||
// Respect system theme on Windows, keep light theme on macOS for consistency
|
||||
const isWindows = process.platform === 'win32';
|
||||
if (isWindows) {
|
||||
nativeTheme.themeSource = 'system'; // Respect Windows dark/light mode
|
||||
} else {
|
||||
nativeTheme.themeSource = 'light'; // Keep existing behavior for macOS
|
||||
}
|
||||
// Always follow OS appearance so renderer `prefers-color-scheme` stays accurate.
|
||||
nativeTheme.themeSource = 'system';
|
||||
|
||||
// Set log level
|
||||
log.transports.console.level = 'info';
|
||||
|
|
@ -2737,6 +2732,7 @@ let installationLock: Promise<PromiseReturnType> = Promise.resolve({
|
|||
// ==================== window create ====================
|
||||
async function createWindow() {
|
||||
const isMac = process.platform === 'darwin';
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
// Ensure .eigent directories exist before anything else
|
||||
ensureEigentDirectories();
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
"test:e2e": "vitest run --config vitest.config.ts",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"check:i18n": "node scripts/check-i18n-locale-parity.js",
|
||||
"verify:theme": "vite-node scripts/verify-theme-tokens.ts",
|
||||
"type-check": "tsc -p tsconfig.build.json --noEmit",
|
||||
"lint": "eslint . --no-warn-ignored",
|
||||
"lint:fix": "eslint . --fix --no-warn-ignored",
|
||||
|
|
|
|||
195
scripts/verify-theme-tokens.ts
Normal file
195
scripts/verify-theme-tokens.ts
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
// Standalone V2 design-token verification CLI.
|
||||
//
|
||||
// Runs the verifier over every registered theme/mode/contrast variant and
|
||||
// prints a human-readable report. Exits with a non-zero code if any `error`
|
||||
// findings are produced. Auxiliary contrast warnings do not fail the run
|
||||
// unless `--strict` is passed.
|
||||
//
|
||||
// Usage:
|
||||
// npm run verify:theme
|
||||
// npm run verify:theme -- --strict # auxiliary warnings fail too
|
||||
// npm run verify:theme -- --json # machine-readable output
|
||||
// npm run verify:theme -- --contrast 0,50,100
|
||||
|
||||
import {
|
||||
getDefaultContrastGrid,
|
||||
listRegisteredThemes,
|
||||
verifyThemeEngine,
|
||||
type VerifyFinding,
|
||||
} from '../src/lib/themeTokens/verifier';
|
||||
|
||||
type CliFlags = {
|
||||
strict: boolean;
|
||||
json: boolean;
|
||||
contrastGrid: number[];
|
||||
};
|
||||
|
||||
function parseArgs(argv: string[]): CliFlags {
|
||||
const flags: CliFlags = {
|
||||
strict: false,
|
||||
json: false,
|
||||
contrastGrid: getDefaultContrastGrid(),
|
||||
};
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const arg = argv[i];
|
||||
if (arg === '--strict') flags.strict = true;
|
||||
else if (arg === '--json') flags.json = true;
|
||||
else if (arg === '--contrast') {
|
||||
const raw = argv[++i];
|
||||
if (raw) {
|
||||
flags.contrastGrid = raw
|
||||
.split(',')
|
||||
.map((s) => Number(s.trim()))
|
||||
.filter((n) => Number.isFinite(n));
|
||||
}
|
||||
} else if (arg === '--help' || arg === '-h') {
|
||||
process.stdout.write(
|
||||
[
|
||||
'Usage: verify-theme-tokens [options]',
|
||||
'',
|
||||
'Options:',
|
||||
' --strict Treat auxiliary contrast warnings as errors',
|
||||
' --json Emit JSON report on stdout',
|
||||
' --contrast a,b,c Override contrast grid (default: 0,25,43,75,100)',
|
||||
' -h, --help Show this help',
|
||||
'',
|
||||
].join('\n')
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
const COLORS = {
|
||||
reset: '\x1b[0m',
|
||||
red: '\x1b[31m',
|
||||
yellow: '\x1b[33m',
|
||||
green: '\x1b[32m',
|
||||
dim: '\x1b[2m',
|
||||
bold: '\x1b[1m',
|
||||
cyan: '\x1b[36m',
|
||||
};
|
||||
|
||||
function colorize(text: string, code: string): string {
|
||||
if (!process.stdout.isTTY) return text;
|
||||
return `${code}${text}${COLORS.reset}`;
|
||||
}
|
||||
|
||||
function groupFindings(
|
||||
findings: VerifyFinding[]
|
||||
): Map<string, VerifyFinding[]> {
|
||||
const groups = new Map<string, VerifyFinding[]>();
|
||||
for (const f of findings) {
|
||||
const key = `${f.mode} / ${f.themeId} / contrast=${f.contrast}`;
|
||||
const bucket = groups.get(key);
|
||||
if (bucket) bucket.push(f);
|
||||
else groups.set(key, [f]);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
function printHumanReport(
|
||||
themes: Array<{ mode: string; id: string }>,
|
||||
flags: CliFlags,
|
||||
report: ReturnType<typeof verifyThemeEngine>
|
||||
): void {
|
||||
const { summary, findings } = report;
|
||||
|
||||
process.stdout.write(
|
||||
`\n${colorize('Design Token Engine Verification (V2)', COLORS.bold)}\n`
|
||||
);
|
||||
process.stdout.write(
|
||||
colorize(
|
||||
` Registered themes: ${themes.map((t) => `${t.mode}/${t.id}`).join(', ')}\n`,
|
||||
COLORS.dim
|
||||
)
|
||||
);
|
||||
process.stdout.write(
|
||||
colorize(
|
||||
` Contrast grid: ${flags.contrastGrid.join(', ')}\n`,
|
||||
COLORS.dim
|
||||
)
|
||||
);
|
||||
process.stdout.write(
|
||||
colorize(` Variants checked: ${summary.variantsChecked}\n\n`, COLORS.dim)
|
||||
);
|
||||
|
||||
if (findings.length === 0) {
|
||||
process.stdout.write(
|
||||
`${colorize('PASS', COLORS.green)} No findings — engine is clean.\n\n`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const groups = groupFindings(findings);
|
||||
for (const [variant, bucket] of groups) {
|
||||
process.stdout.write(`${colorize(variant, COLORS.cyan)}\n`);
|
||||
for (const f of bucket) {
|
||||
const badge =
|
||||
f.severity === 'error'
|
||||
? colorize('ERROR', COLORS.red)
|
||||
: colorize('WARN ', COLORS.yellow);
|
||||
const ratioSuffix =
|
||||
f.ratio !== undefined && f.threshold !== undefined
|
||||
? colorize(
|
||||
` (ratio ${f.ratio.toFixed(2)} / threshold ${f.threshold})`,
|
||||
COLORS.dim
|
||||
)
|
||||
: '';
|
||||
process.stdout.write(
|
||||
` ${badge} [${f.code}] ${f.message}${ratioSuffix}\n`
|
||||
);
|
||||
if (f.tokenKey && f.value) {
|
||||
process.stdout.write(
|
||||
colorize(` ↳ ${f.tokenKey} = ${f.value}\n`, COLORS.dim)
|
||||
);
|
||||
}
|
||||
}
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
|
||||
const errBadge =
|
||||
summary.errors === 0
|
||||
? colorize(`${summary.errors} errors`, COLORS.green)
|
||||
: colorize(`${summary.errors} errors`, COLORS.red);
|
||||
const warnBadge =
|
||||
summary.warnings === 0
|
||||
? colorize(`${summary.warnings} warnings`, COLORS.green)
|
||||
: colorize(`${summary.warnings} warnings`, COLORS.yellow);
|
||||
process.stdout.write(`Summary: ${errBadge}, ${warnBadge}\n\n`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const flags = parseArgs(process.argv.slice(2));
|
||||
const themes = listRegisteredThemes();
|
||||
const report = verifyThemeEngine({
|
||||
contrastGrid: flags.contrastGrid,
|
||||
strictAuxContrast: flags.strict,
|
||||
});
|
||||
|
||||
if (flags.json) {
|
||||
process.stdout.write(JSON.stringify({ themes, ...report }, null, 2) + '\n');
|
||||
} else {
|
||||
printHumanReport(themes, flags, report);
|
||||
}
|
||||
|
||||
const failed = report.summary.errors > 0;
|
||||
process.exit(failed ? 1 : 0);
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -758,12 +758,12 @@ const ToolSelect = forwardRef<
|
|||
{(initialSelectedTools || []).map((item: any) => (
|
||||
<Badge
|
||||
key={item.id + item.key + (item.isLocal + '')}
|
||||
className="h-5 gap-1 bg-button-tertiery-fill-default px-xs flex w-auto flex-shrink-0 items-center"
|
||||
className="h-5 gap-1 bg-ds-bg-neutral-subtle-default px-xs flex w-auto flex-shrink-0 items-center"
|
||||
>
|
||||
{item.name || item.mcp_name || item.key || `tool_${item.id}`}
|
||||
<div className="rounded-sm bg-button-secondary-fill-disabled flex items-center justify-center">
|
||||
<div className="rounded-sm bg-ds-bg-neutral-muted-disabled flex items-center justify-center">
|
||||
<X
|
||||
className="h-4 w-4 text-button-secondary-icon-disabled cursor-pointer"
|
||||
className="h-4 w-4 text-ds-text-neutral-muted-disabled hover:text-ds-text-neutral-default-default cursor-pointer"
|
||||
onClick={() => removeOption(item)}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -852,7 +852,7 @@ const ToolSelect = forwardRef<
|
|||
>
|
||||
<div className="gap-1 flex items-center">
|
||||
{/* {getCategoryIcon(item.category?.name)} */}
|
||||
<div className="text-sm font-bold leading-17 text-ds-text-brand-default-default line-clamp-1 overflow-hidden break-words text-ellipsis">
|
||||
<div className="text-body-md font-bold text-ds-text-brand-default-default line-clamp-1 overflow-hidden break-words text-ellipsis">
|
||||
{item.mcp_name}
|
||||
</div>
|
||||
<TooltipSimple content={item.mcp_desc}>
|
||||
|
|
@ -863,10 +863,7 @@ const ToolSelect = forwardRef<
|
|||
</TooltipSimple>
|
||||
</div>
|
||||
<div className="gap-1 flex items-center">
|
||||
<Button
|
||||
className="h-6 rounded-md bg-button-secondary-fill-default px-sm py-xs text-xs font-bold leading-17 text-button-secondary-text-default shadow-sm hover:bg-button-tertiery-text-default"
|
||||
disabled={true}
|
||||
>
|
||||
<Button variant="secondary" size="sm" textWeight="bold" disabled>
|
||||
{t('layout.installed')}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -889,7 +886,7 @@ const ToolSelect = forwardRef<
|
|||
inputRef.current?.focus();
|
||||
setIsOpen(true);
|
||||
}}
|
||||
className="gap-1 rounded-lg border-input-border-default bg-input-bg-default py-1 flex max-h-[120px] min-h-[60px] w-full flex-wrap justify-start overflow-y-auto border border-solid px-[6px]"
|
||||
className="gap-1 rounded-lg border-ds-border-neutral-default-default bg-ds-bg-neutral-default-default py-1 flex max-h-[120px] min-h-[60px] w-full flex-wrap justify-start overflow-y-auto border border-solid px-[6px]"
|
||||
>
|
||||
{renderSelectedItems()}
|
||||
<Textarea
|
||||
|
|
@ -905,7 +902,7 @@ const ToolSelect = forwardRef<
|
|||
|
||||
{/* floating dropdown */}
|
||||
{isOpen && (
|
||||
<div className="left-0 right-0 mt-1 rounded-lg border-input-border-default bg-dropdown-bg absolute top-full z-50 overflow-y-auto border border-solid">
|
||||
<div className="left-0 right-0 mt-1 rounded-lg border-ds-border-neutral-default-default bg-ds-bg-neutral-strong-default absolute top-full z-50 overflow-y-auto border border-solid">
|
||||
<div className="max-h-[192px] overflow-y-auto">
|
||||
<IntegrationList
|
||||
variant="select"
|
||||
|
|
|
|||
|
|
@ -522,12 +522,12 @@ export function AddWorker({
|
|||
secretVisible[key] ? (
|
||||
<EyeOff
|
||||
size={16}
|
||||
className="text-button-transparent-icon-disabled"
|
||||
className="text-ds-text-neutral-muted-disabled"
|
||||
/>
|
||||
) : (
|
||||
<Eye
|
||||
size={16}
|
||||
className="text-button-transparent-icon-disabled"
|
||||
className="text-ds-text-neutral-muted-disabled"
|
||||
/>
|
||||
)
|
||||
) : undefined
|
||||
|
|
@ -544,7 +544,7 @@ export function AddWorker({
|
|||
</div>
|
||||
</DialogContentSection>
|
||||
<DialogFooter
|
||||
className="!rounded-b-xl bg-white-100% p-md"
|
||||
className="!rounded-b-xl bg-ds-bg-neutral-inverse-default p-md"
|
||||
showCancelButton={true}
|
||||
showConfirmButton={true}
|
||||
cancelButtonText={t('workforce.cancel')}
|
||||
|
|
@ -569,7 +569,7 @@ export function AddWorker({
|
|||
) : (
|
||||
// default add interface
|
||||
<>
|
||||
<DialogContentSection className="gap-3 bg-white-100% p-md flex flex-col">
|
||||
<DialogContentSection className="gap-3 bg-ds-bg-neutral-inverse-default p-md flex flex-col">
|
||||
<div className="gap-4 flex flex-col">
|
||||
<div className="gap-sm flex items-center">
|
||||
<div className="h-16 w-16 flex items-center justify-center">
|
||||
|
|
@ -688,7 +688,7 @@ export function AddWorker({
|
|||
</div>
|
||||
</DialogContentSection>
|
||||
<DialogFooter
|
||||
className="!rounded-b-xl bg-white-100% p-md"
|
||||
className="!rounded-b-xl bg-ds-bg-neutral-inverse-default p-md"
|
||||
showCancelButton={true}
|
||||
showConfirmButton={true}
|
||||
cancelButtonText={t('workforce.cancel')}
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ export default function BrowserAgentWorkspace() {
|
|||
<CodeXml size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-emerald-700',
|
||||
bgColor: 'bg-bg-fill-coding-active',
|
||||
shapeColor: 'bg-bg-fill-coding-default',
|
||||
borderColor: 'border-bg-fill-coding-active',
|
||||
bgColor: 'bg-ds-bg-terminal-default-default',
|
||||
shapeColor: 'bg-ds-bg-terminal-subtle-default',
|
||||
borderColor: 'border-ds-border-terminal-default-default',
|
||||
bgColorLight: 'bg-emerald-200',
|
||||
},
|
||||
browser_agent: {
|
||||
|
|
@ -56,9 +56,9 @@ export default function BrowserAgentWorkspace() {
|
|||
<Globe size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-blue-700',
|
||||
bgColor: 'bg-bg-fill-browser-active',
|
||||
shapeColor: 'bg-bg-fill-browser-default',
|
||||
borderColor: 'border-bg-fill-browser-active',
|
||||
bgColor: 'bg-ds-bg-browser-default-default',
|
||||
shapeColor: 'bg-ds-bg-browser-subtle-default',
|
||||
borderColor: 'border-ds-border-browser-default-default',
|
||||
bgColorLight: 'bg-blue-200',
|
||||
},
|
||||
document_agent: {
|
||||
|
|
@ -67,9 +67,9 @@ export default function BrowserAgentWorkspace() {
|
|||
<FileText size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-yellow-700',
|
||||
bgColor: 'bg-bg-fill-writing-active',
|
||||
shapeColor: 'bg-bg-fill-writing-default',
|
||||
borderColor: 'border-bg-fill-writing-active',
|
||||
bgColor: 'bg-ds-bg-document-default-default',
|
||||
shapeColor: 'bg-ds-bg-document-subtle-default',
|
||||
borderColor: 'border-ds-border-document-default-default',
|
||||
bgColorLight: 'bg-yellow-200',
|
||||
},
|
||||
multi_modal_agent: {
|
||||
|
|
@ -78,9 +78,9 @@ export default function BrowserAgentWorkspace() {
|
|||
<Image size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-fuchsia-700',
|
||||
bgColor: 'bg-bg-fill-multimodal-active',
|
||||
shapeColor: 'bg-bg-fill-multimodal-default',
|
||||
borderColor: 'border-bg-fill-multimodal-active',
|
||||
bgColor: 'bg-ds-bg-neutral-default-default',
|
||||
shapeColor: 'bg-ds-bg-neutral-subtle-default',
|
||||
borderColor: 'border-ds-border-neutral-default-default',
|
||||
bgColorLight: 'bg-fuchsia-200',
|
||||
},
|
||||
social_media_agent: {
|
||||
|
|
@ -202,7 +202,7 @@ export default function BrowserAgentWorkspace() {
|
|||
<div
|
||||
className={`ease-in-out flex h-full w-full flex-1 items-center justify-center transition-all duration-300`}
|
||||
>
|
||||
<div className="blur-bg rounded-xl bg-ds-bg-neutral-default-default relative flex h-full w-full flex-col overflow-hidden">
|
||||
<div className="backdrop-blur-sm rounded-xl bg-ds-bg-neutral-default-default relative flex h-full w-full flex-col overflow-hidden">
|
||||
<div className="rounded-t-2xl px-2 pb-2 pt-3 flex flex-shrink-0 items-center justify-between">
|
||||
<div className="gap-sm flex items-center justify-start">
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export const FloatingAction = ({
|
|||
className
|
||||
)}
|
||||
>
|
||||
<div className="gap-2 p-1 backdrop-blur-md pointer-events-auto flex items-center rounded-full border border-[color:var(--ds-border-neutral-default-default)] bg-[var(--ds-bg-neutral-subtle-default)] shadow-[0px_4px_16px_rgba(0,0,0,0.12)]">
|
||||
<div className="gap-2 p-1 backdrop-blur-md pointer-events-auto flex items-center rounded-full border border-[color:var(--ds-border-neutral-default-default)] bg-[var(--ds-bg-neutral-subtle-default)] shadow-[0px_4px_16px_color-mix(in_srgb,var(--ds-bg-neutral-inverse-default)_14%,transparent)]">
|
||||
{/* Always show Stop Task button when running (removed pause/resume logic) */}
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -70,7 +70,7 @@ export const FloatingAction = ({
|
|||
{status === "running" ? (
|
||||
// State 1: Running - Show Pause button
|
||||
<Button
|
||||
variant="cuation"
|
||||
variant="caution"
|
||||
size="sm"
|
||||
onClick={onPause}
|
||||
disabled={loading}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ export function TaskItem({
|
|||
<Button
|
||||
onClick={() => onDelete()}
|
||||
className="rounded-full"
|
||||
variant="cuation"
|
||||
variant="caution"
|
||||
size="xs"
|
||||
buttonContent="icon-only"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ export default function ProjectGroup({
|
|||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<Tag variant="info" size="xs">
|
||||
<Tag variant="primary" tone="information" size="xs">
|
||||
<Activity className="w-3.5 h-3.5" />
|
||||
{t("layout.ongoing")}
|
||||
</Tag>
|
||||
|
|
@ -258,7 +258,7 @@ export default function ProjectGroup({
|
|||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<Tag variant="warning" size="xs">
|
||||
<Tag variant="primary" tone="warning" size="xs">
|
||||
{t("layout.issue") || "Issue"}
|
||||
</Tag>
|
||||
</motion.div>
|
||||
|
|
@ -292,7 +292,7 @@ export default function ProjectGroup({
|
|||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="border-ds-border-neutral-default-default bg-dropdown-bg z-50"
|
||||
className="border-ds-border-neutral-default-default bg-ds-bg-neutral-strong-default z-50"
|
||||
>
|
||||
{onProjectEdit && (
|
||||
<DropdownMenuItem
|
||||
|
|
@ -417,7 +417,7 @@ export default function ProjectGroup({
|
|||
|
||||
{/* Middle: Project, Trigger, Agent tags - Aligned to right */}
|
||||
<div className="gap-4 flex w-fit flex-1 items-center justify-end">
|
||||
<Tag variant="info" size="sm">
|
||||
<Tag variant="primary" tone="information" size="sm">
|
||||
<Hash />
|
||||
<span>
|
||||
{project.total_tokens
|
||||
|
|
@ -427,14 +427,24 @@ export default function ProjectGroup({
|
|||
</Tag>
|
||||
|
||||
<TooltipSimple content={t('layout.tasks')}>
|
||||
<Tag variant="default" size="sm" className="min-w-10">
|
||||
<Tag
|
||||
variant="primary"
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
className="min-w-10"
|
||||
>
|
||||
<Pin />
|
||||
<span>{project.task_count}</span>
|
||||
</Tag>
|
||||
</TooltipSimple>
|
||||
|
||||
<TooltipSimple content="Triggers">
|
||||
<Tag variant="warning" size="sm" className="min-w-10">
|
||||
<Tag
|
||||
variant="primary"
|
||||
tone="warning"
|
||||
size="sm"
|
||||
className="min-w-10"
|
||||
>
|
||||
<Zap />
|
||||
<span>{project.total_triggers || 0}</span>
|
||||
</Tag>
|
||||
|
|
@ -445,14 +455,14 @@ export default function ProjectGroup({
|
|||
<div className="ml-4 min-w-32 gap-2 border-ds-border-neutral-muted-disabled pl-4 flex w-fit items-center justify-end border border-y-0 border-r-0 border-solid">
|
||||
{/* Status tag */}
|
||||
{/* {isOngoing && (
|
||||
<Tag variant="info" size="sm">
|
||||
<Tag variant="primary" tone="information" size="sm">
|
||||
<Activity />
|
||||
{t("layout.ongoing")}
|
||||
</Tag>
|
||||
)} */}
|
||||
|
||||
{/* {!isOngoing && hasIssue && (
|
||||
<Tag variant="warning" size="sm">
|
||||
<Tag variant="primary" tone="warning" size="sm">
|
||||
{t("layout.issue") || "Issue"}
|
||||
</Tag>
|
||||
)} */}
|
||||
|
|
@ -471,7 +481,7 @@ export default function ProjectGroup({
|
|||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="border-ds-border-neutral-default-default bg-dropdown-bg z-50"
|
||||
className="border-ds-border-neutral-default-default bg-ds-bg-neutral-strong-default z-50"
|
||||
>
|
||||
{onProjectEdit && (
|
||||
<DropdownMenuItem
|
||||
|
|
|
|||
|
|
@ -72,21 +72,21 @@ export default function TaskItem({
|
|||
switch (status) {
|
||||
case 1: // ChatStatus.ongoing
|
||||
return (
|
||||
<Tag variant="info" size="sm">
|
||||
<Tag variant="primary" tone="information" size="sm">
|
||||
<Clock />
|
||||
<span>{t('layout.running')}</span>
|
||||
</Tag>
|
||||
);
|
||||
case 2: // ChatStatus.done
|
||||
return (
|
||||
<Tag variant="success" size="sm">
|
||||
<Tag variant="primary" tone="success" size="sm">
|
||||
<CheckCircle />
|
||||
<span>{t('layout.completed')}</span>
|
||||
</Tag>
|
||||
);
|
||||
default: // Unknown status
|
||||
return (
|
||||
<Tag variant="default" size="sm">
|
||||
<Tag variant="primary" tone="neutral" size="sm">
|
||||
<Clock />
|
||||
<span>{t('layout.unknown')}</span>
|
||||
</Tag>
|
||||
|
|
@ -102,7 +102,7 @@ export default function TaskItem({
|
|||
return (
|
||||
<div
|
||||
onClick={onSelect}
|
||||
className={` ${isActive ? '!bg-white-100%' : ''} h-14 gap-md rounded-xl border-ds-border-neutral-muted-disabled bg-white-30% p-3 shadow-history-item hover:bg-white-100% relative flex w-full cursor-pointer items-center justify-between border border-solid transition-all duration-300 ${!isLast ? 'mb-2' : ''} `}
|
||||
className={` ${isActive ? '!bg-ds-bg-neutral-inverse-default' : ''} h-14 gap-md rounded-xl border-ds-border-neutral-muted-disabled bg-ds-bg-neutral-inverse-default/30 p-3 shadow-history-item hover:bg-ds-bg-neutral-inverse-default relative flex w-full cursor-pointer items-center justify-between border border-solid transition-all duration-300 ${!isLast ? 'mb-2' : ''} `}
|
||||
>
|
||||
<div className="min-w-0 gap-2 flex flex-1 items-center">
|
||||
<TooltipSimple content={t('layout.tasks')}>
|
||||
|
|
@ -138,14 +138,15 @@ export default function TaskItem({
|
|||
<div className="gap-2 flex flex-shrink-0 items-center">
|
||||
{!isOngoing && getStatusTag(task.status)}
|
||||
|
||||
<Tag variant="info" size="sm">
|
||||
<Tag variant="primary" tone="information" size="sm">
|
||||
<Hash />
|
||||
<span>{task.tokens ? task.tokens.toLocaleString() : '0'}</span>
|
||||
</Tag>
|
||||
|
||||
{isOngoing && (onPause || onResume) && (
|
||||
<Tag
|
||||
variant={isPaused ? 'info' : 'success'}
|
||||
variant="primary"
|
||||
tone={isPaused ? 'information' : 'success'}
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -176,7 +177,7 @@ export default function TaskItem({
|
|||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
className="border-ds-border-neutral-default-default bg-dropdown-bg p-sm w-[98px] rounded-[12px] border border-solid"
|
||||
className="border-ds-border-neutral-default-default bg-ds-bg-neutral-strong-default p-sm w-[98px] rounded-[12px] border border-solid"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
{!isOngoing && (
|
||||
|
|
|
|||
|
|
@ -397,23 +397,20 @@ export default function GroupedHistoryView({
|
|||
{/* Summary */}
|
||||
<div className="pb-4 flex items-center justify-between">
|
||||
<div className="gap-2 flex items-center">
|
||||
<Tag variant="default" size="sm" className="gap-2">
|
||||
<Tag variant="secondary" tone="neutral" size="sm" className="gap-2">
|
||||
<Sparkle />
|
||||
<span className="text-body-sm"> {t('layout.projects')}</span>
|
||||
<span className="h-5 w-5 bg-tag-fill-default-foreground text-label-xs font-bold text-ds-text-neutral-default-default flex items-center justify-center rounded-full">
|
||||
{allProjects.length}
|
||||
</span>
|
||||
|
||||
{allProjects.length}
|
||||
</Tag>
|
||||
|
||||
<Tag variant="default" size="sm" className="gap-2">
|
||||
<Tag variant="secondary" tone="neutral" size="sm" className="gap-2">
|
||||
<Pin />
|
||||
<span className="text-body-sm"> {t('layout.total-tasks')}</span>
|
||||
<span className="h-5 w-5 bg-tag-fill-default-foreground text-label-xs font-bold text-ds-text-neutral-default-default flex items-center justify-center rounded-full">
|
||||
{allProjects.reduce(
|
||||
(total, project) => total + project.task_count,
|
||||
0
|
||||
)}
|
||||
</span>
|
||||
{t('layout.total-tasks')}
|
||||
{allProjects.reduce(
|
||||
(total, project) => total + project.task_count,
|
||||
0
|
||||
)}
|
||||
</Tag>
|
||||
</div>
|
||||
<div className="gap-md flex items-center">
|
||||
|
|
|
|||
|
|
@ -74,9 +74,9 @@ export function VerticalNavigation({
|
|||
className={cn(
|
||||
'gap-2 rounded-lg px-5 py-1.5 text-body-sm w-full justify-start',
|
||||
'bg-transparent data-[state=inactive]:bg-transparent',
|
||||
'data-[state=inactive]:text-menubutton-text-default data-[state=inactive]:opacity-70',
|
||||
'data-[state=inactive]:hover:bg-menubutton-fill-hover data-[state=inactive]:hover:opacity-100',
|
||||
'data-[state=active]:bg-menubutton-fill-active data-[state=active]:text-menutabs-text-active',
|
||||
'data-[state=inactive]:text-ds-text-neutral-muted-default data-[state=inactive]:opacity-70',
|
||||
'data-[state=inactive]:hover:bg-ds-bg-neutral-default-hover data-[state=inactive]:hover:opacity-100',
|
||||
'data-[state=active]:bg-ds-bg-neutral-default-default data-[state=active]:text-ds-text-neutral-default-default',
|
||||
triggerClassName
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export default function SearchInput({
|
|||
<motion.div
|
||||
className={cn(
|
||||
'rounded-lg py-0.5 flex items-center justify-center overflow-hidden border border-solid border-transparent bg-transparent',
|
||||
'focus-within:border-input-border-focus focus-within:bg-input-bg-input',
|
||||
'focus-within:border-ds-border-brand-default-focus focus-within:bg-ds-bg-neutral-strong-default',
|
||||
'hover:bg-ds-bg-neutral-strong-hover hover:border-transparent'
|
||||
)}
|
||||
initial={false}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export default function CloseNoticeDialog({
|
|||
<div className="gap-md bg-ds-bg-neutral-strong-default p-md flex flex-col">
|
||||
{t('layout.a-task-is-currently-running')}
|
||||
</div>
|
||||
<DialogFooter className="!rounded-b-xl bg-white-100% p-md">
|
||||
<DialogFooter className="!rounded-b-xl bg-ds-bg-neutral-inverse-default p-md">
|
||||
<DialogClose asChild>
|
||||
<Button variant="ghost" size="md">
|
||||
{t('layout.cancel')}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export default function EndNoticeDialog({
|
|||
<div className="gap-md bg-ds-bg-neutral-strong-default p-md flex flex-col">
|
||||
{t('layout.ending-this-project-will-stop')}
|
||||
</div>
|
||||
<DialogFooter className="!rounded-b-xl bg-white-100% p-md">
|
||||
<DialogFooter className="!rounded-b-xl bg-ds-bg-neutral-inverse-default p-md">
|
||||
<DialogClose asChild>
|
||||
<Button variant="ghost" size="md" disabled={loading}>
|
||||
{t('layout.cancel')}
|
||||
|
|
@ -64,7 +64,7 @@ export default function EndNoticeDialog({
|
|||
<Button
|
||||
size="md"
|
||||
onClick={onSubmit}
|
||||
variant="cuation"
|
||||
variant="caution"
|
||||
disabled={loading}
|
||||
>
|
||||
{t('layout.yes-end-project')}
|
||||
|
|
|
|||
|
|
@ -96,11 +96,11 @@ export function SearchHistoryDialog() {
|
|||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="border-menutabs-border-default bg-ds-bg-neutral-strong-default h-[32px] border border-solid"
|
||||
className="border-ds-border-neutral-default-default bg-ds-bg-neutral-strong-default h-[32px] border border-solid"
|
||||
size="sm"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<Search className="text-menutabs-icon-active" size={16} />
|
||||
<Search className="text-ds-icon-neutral-default-default" size={16} />
|
||||
<span>{t('dashboard.search')}</span>
|
||||
</Button>
|
||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export const ZoomControls = ({
|
|||
variant="ghost"
|
||||
onClick={onZoomOut}
|
||||
title="Zoom Out"
|
||||
className="h-7 w-7 text-ds-text-neutral-muted-default hover:bg-fill-fill-transparent-hover hover:text-ds-text-neutral-default-default"
|
||||
className="h-7 w-7 text-ds-text-neutral-muted-default hover:bg-ds-bg-neutral-subtle-hover hover:text-ds-text-neutral-default-default"
|
||||
>
|
||||
<ZoomOut className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
|
|
@ -51,18 +51,18 @@ export const ZoomControls = ({
|
|||
variant="ghost"
|
||||
onClick={onZoomIn}
|
||||
title="Zoom In"
|
||||
className="h-7 w-7 text-ds-text-neutral-muted-default hover:bg-fill-fill-transparent-hover hover:text-ds-text-neutral-default-default"
|
||||
className="h-7 w-7 text-ds-text-neutral-muted-default hover:bg-ds-bg-neutral-subtle-hover hover:text-ds-text-neutral-default-default"
|
||||
>
|
||||
<ZoomIn className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<div className="mx-0.5 h-4 bg-border-secondary w-px" />
|
||||
<div className="mx-0.5 h-4 bg-ds-border-neutral-default-default w-px" />
|
||||
<Button
|
||||
size="xs"
|
||||
buttonContent="icon-only"
|
||||
variant="ghost"
|
||||
onClick={onZoomReset}
|
||||
title="Reset Zoom"
|
||||
className="h-7 w-7 text-ds-text-neutral-muted-default hover:bg-fill-fill-transparent-hover hover:text-ds-text-neutral-default-default"
|
||||
className="h-7 w-7 text-ds-text-neutral-muted-default hover:bg-ds-bg-neutral-subtle-hover hover:text-ds-text-neutral-default-default"
|
||||
>
|
||||
<RotateCcw className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -957,7 +957,7 @@ export default function Folder({ data: _data }: { data?: Agent }) {
|
|||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="border-ds-border-neutral-default-default bg-dropdown-bg z-50"
|
||||
className="border-ds-border-neutral-default-default bg-ds-bg-neutral-strong-default z-50"
|
||||
>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleOpenInIDE('system')}
|
||||
|
|
@ -1018,7 +1018,7 @@ export default function Folder({ data: _data }: { data?: Agent }) {
|
|||
<DropdownMenuContent
|
||||
side="bottom"
|
||||
align="start"
|
||||
className="border-ds-border-neutral-default-default bg-dropdown-bg z-50 min-w-[10rem]"
|
||||
className="border-ds-border-neutral-default-default bg-ds-bg-neutral-strong-default z-50 min-w-[10rem]"
|
||||
>
|
||||
<DropdownMenuRadioGroup
|
||||
value={fileTreeScope}
|
||||
|
|
|
|||
|
|
@ -443,7 +443,7 @@ export default function HistorySidebar() {
|
|||
|
||||
<div className="gap-2 flex flex-shrink-0 items-center">
|
||||
<TooltipSimple content={t('chat.token')}>
|
||||
<Tag variant="info" size="sm">
|
||||
<Tag variant="primary" tone="information" size="sm">
|
||||
<Hash className="h-3.5 w-3.5" />
|
||||
<span className="text-xs">
|
||||
{(project.total_tokens || 0).toLocaleString()}
|
||||
|
|
@ -452,7 +452,7 @@ export default function HistorySidebar() {
|
|||
</TooltipSimple>
|
||||
|
||||
<TooltipSimple content="Tasks">
|
||||
<Tag variant="default" size="sm">
|
||||
<Tag variant="primary" tone="neutral" size="sm">
|
||||
<Pin className="h-3.5 w-3.5" />
|
||||
<span className="text-xs">
|
||||
{project.task_count}
|
||||
|
|
@ -565,7 +565,7 @@ export default function HistorySidebar() {
|
|||
|
||||
<div className="gap-2 flex flex-shrink-0 items-center">
|
||||
<TooltipSimple content={t('chat.token')}>
|
||||
<Tag variant="info" size="sm">
|
||||
<Tag variant="primary" tone="information" size="sm">
|
||||
<Hash className="h-3.5 w-3.5" />
|
||||
<span className="text-xs">
|
||||
{(project.total_tokens || 0).toLocaleString()}
|
||||
|
|
@ -574,7 +574,7 @@ export default function HistorySidebar() {
|
|||
</TooltipSimple>
|
||||
|
||||
<TooltipSimple content="Tasks">
|
||||
<Tag variant="default" size="sm">
|
||||
<Tag variant="primary" tone="neutral" size="sm">
|
||||
<Pin className="h-3.5 w-3.5" />
|
||||
<span className="text-xs">
|
||||
{project.task_count}
|
||||
|
|
|
|||
|
|
@ -220,8 +220,8 @@ export const CarouselStep: React.FC = () => {
|
|||
onMouseEnter={() => handleIndicatorHover(index)}
|
||||
className={`h-1 w-32 cursor-pointer rounded-full transition-all duration-300 ${
|
||||
index === currentSlide
|
||||
? 'bg-fill-fill-secondary'
|
||||
: 'bg-fill-fill-tertiary hover:bg-fill-fill-secondary'
|
||||
? 'bg-ds-bg-neutral-default-default'
|
||||
: 'bg-ds-bg-neutral-subtle-default hover:bg-ds-bg-neutral-default-default'
|
||||
}`}
|
||||
></div>
|
||||
))}
|
||||
|
|
|
|||
101
src/components/Layout/ThemeProvider.test.tsx
Normal file
101
src/components/Layout/ThemeProvider.test.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { act, cleanup, render, waitFor } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
|
||||
import { ThemeProvider } from './ThemeProvider';
|
||||
|
||||
describe('ThemeProvider', () => {
|
||||
let mediaQuery: {
|
||||
matches: boolean;
|
||||
media: string;
|
||||
onchange: null;
|
||||
addListener: ReturnType<typeof vi.fn>;
|
||||
removeListener: ReturnType<typeof vi.fn>;
|
||||
dispatchEvent: ReturnType<typeof vi.fn>;
|
||||
addEventListener?: undefined;
|
||||
removeEventListener?: undefined;
|
||||
};
|
||||
let changeListener: (() => void) | null;
|
||||
|
||||
beforeEach(() => {
|
||||
changeListener = null;
|
||||
|
||||
mediaQuery = {
|
||||
matches: true,
|
||||
media: '(prefers-color-scheme: dark)',
|
||||
onchange: null,
|
||||
addListener: vi.fn((listener: () => void) => {
|
||||
changeListener = listener;
|
||||
}),
|
||||
removeListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
addEventListener: undefined,
|
||||
removeEventListener: undefined,
|
||||
};
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation(() => mediaQuery),
|
||||
});
|
||||
|
||||
useAuthStore.setState({
|
||||
appearance: 'light',
|
||||
appearanceMode: 'system',
|
||||
lightColorThemeId: 'eigent',
|
||||
darkColorThemeId: 'eigent',
|
||||
customThemeCatalog: { light: {}, dark: {} },
|
||||
themeContrast: 43,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
document.documentElement.removeAttribute('data-theme-mode');
|
||||
document.documentElement.removeAttribute('data-color-theme');
|
||||
document.documentElement.style.removeProperty('color-scheme');
|
||||
document.documentElement.style.removeProperty('--ds-theme-contrast');
|
||||
});
|
||||
|
||||
it('uses addListener fallback and follows system preference changes', async () => {
|
||||
const { unmount } = render(
|
||||
<ThemeProvider>
|
||||
<div data-testid="child" />
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
|
||||
});
|
||||
expect(mediaQuery.addListener).toHaveBeenCalledTimes(1);
|
||||
expect(changeListener).toBeTruthy();
|
||||
|
||||
mediaQuery.matches = false;
|
||||
act(() => {
|
||||
changeListener?.();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
|
||||
});
|
||||
expect(useAuthStore.getState().appearance).toBe('light');
|
||||
|
||||
unmount();
|
||||
expect(mediaQuery.removeListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -13,8 +13,8 @@
|
|||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import {
|
||||
applyThemeContractV1,
|
||||
createDefaultThemeContract,
|
||||
applyThemeContractV2,
|
||||
createDefaultThemeContractV2,
|
||||
} from '@/lib/themeTokens';
|
||||
import { DEFAULT_THEME_CATALOG } from '@/lib/themeTokens/catalog';
|
||||
import type { Mode } from '@/lib/themeTokens/types';
|
||||
|
|
@ -46,9 +46,17 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|||
};
|
||||
update();
|
||||
|
||||
media.addEventListener('change', update);
|
||||
if (typeof media.addEventListener === 'function') {
|
||||
media.addEventListener('change', update);
|
||||
} else {
|
||||
media.addListener(update);
|
||||
}
|
||||
return () => {
|
||||
media.removeEventListener('change', update);
|
||||
if (typeof media.removeEventListener === 'function') {
|
||||
media.removeEventListener('change', update);
|
||||
} else {
|
||||
media.removeListener(update);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -82,13 +90,12 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|||
root.setAttribute('data-theme', resolvedMode);
|
||||
root.setAttribute('data-theme-mode', appearanceMode);
|
||||
root.setAttribute('data-color-theme', colorThemeId);
|
||||
root.style.setProperty('color-scheme', resolvedMode);
|
||||
root.style.setProperty('--ds-theme-contrast', String(themeContrast));
|
||||
|
||||
// V2 semantic tokens are generated in parallel to legacy tokens.
|
||||
// Existing components continue to use legacy variables until migration.
|
||||
applyThemeContractV1(
|
||||
createDefaultThemeContract(resolvedMode, {
|
||||
colorThemeId,
|
||||
applyThemeContractV2(
|
||||
createDefaultThemeContractV2(resolvedMode, {
|
||||
themeId: colorThemeId,
|
||||
contrast: themeContrast,
|
||||
}),
|
||||
root,
|
||||
|
|
@ -136,9 +143,9 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|||
|
||||
(
|
||||
window as Window & {
|
||||
__eigentThemeV1?: typeof api;
|
||||
__eigentThemeV2?: typeof api;
|
||||
}
|
||||
).__eigentThemeV1 = api;
|
||||
).__eigentThemeV2 = api;
|
||||
}, [mergedCatalog]);
|
||||
|
||||
return <>{children}</>;
|
||||
|
|
|
|||
|
|
@ -29,12 +29,6 @@ export function mergeLayoutAliasStyles(
|
|||
|
||||
// Shared layout-level aliases for TopBar, HistorySidebar, and ProjectPageSidebar.
|
||||
export const productLayoutTokenAliases = asCssVarMap({
|
||||
'--surface-primary': 'var(--ds-bg-neutral-subtle-default)',
|
||||
'--surface-secondary': 'var(--ds-bg-neutral-default-default)',
|
||||
'--surface-tertiary': 'var(--ds-bg-neutral-strong-default)',
|
||||
'--surface-information': 'var(--ds-bg-status-splitting-subtle-default)',
|
||||
'--surface-hover-subtle': 'var(--ds-bg-neutral-default-hover)',
|
||||
|
||||
'--border-secondary': 'var(--ds-border-neutral-default-default)',
|
||||
'--border-disabled': 'var(--ds-border-neutral-subtle-default)',
|
||||
|
||||
|
|
@ -51,7 +45,7 @@ export const productLayoutTokenAliases = asCssVarMap({
|
|||
'--icon-information': 'var(--ds-icon-status-splitting-default-default)',
|
||||
'--icon-success': 'var(--ds-icon-status-completed-default-default)',
|
||||
'--icon-warning': 'var(--ds-icon-status-pending-default-default)',
|
||||
'--icon-cuation': 'var(--ds-icon-status-error-default-default)',
|
||||
'--icon-caution': 'var(--ds-icon-status-error-default-default)',
|
||||
|
||||
'--project-surface': 'var(--ds-bg-neutral-default-default)',
|
||||
'--project-surface-hover': 'var(--ds-bg-neutral-default-hover)',
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ export function HeaderAction() {
|
|||
type="button"
|
||||
className={cn(
|
||||
'no-drag h-8 px-3 rounded-lg text-ds-icon-neutral-muted-default ease-in-out flex shrink-0 items-center justify-center transition-colors duration-200',
|
||||
'hover:bg-ds-bg-neutral-subtle-hover focus-visible:ring-ds-border-neutral-subtle-default hover:text-ds-icon-neutral-muted-hover focus-visible:ring-2 focus-visible:outline-none'
|
||||
'hover:bg-ds-bg-neutral-subtle-hover focus-visible:ring-ds-ring-neutral-subtle-default hover:text-ds-icon-neutral-muted-hover focus-visible:ring-2 focus-visible:outline-none'
|
||||
)}
|
||||
aria-label={foldTooltip}
|
||||
onClick={() => toggleProjectSidebarFolded()}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ export function NavList({
|
|||
onClick={() => setExpanded((v) => !v)}
|
||||
className={cn(
|
||||
'no-drag gap-1 rounded-xl py-1 px-2 text-body-xs font-bold text-ds-text-neutral-muted-default hover:bg-ds-bg-neutral-subtle-default inline-flex w-fit max-w-[calc(100%-5rem)] shrink items-center text-left outline-none',
|
||||
'focus-visible:ring-ds-border-neutral-subtle-default focus-visible:ring-2 focus-visible:outline-none'
|
||||
'focus-visible:ring-ds-ring-neutral-subtle-default focus-visible:ring-2 focus-visible:outline-none'
|
||||
)}
|
||||
aria-expanded={expanded}
|
||||
aria-label={expanded ? collapseAria : expandAria}
|
||||
|
|
@ -149,7 +149,7 @@ export function NavList({
|
|||
className={cn(
|
||||
'no-drag gap-1 rounded-xl py-1 px-2 text-body-xs font-semibold !text-ds-text-neutral-muted-default inline-flex w-fit shrink-0 items-center text-left outline-none',
|
||||
showAllActive ? 'underline underline-offset-2' : 'hover:underline',
|
||||
'focus-visible:ring-ds-border-neutral-subtle-default focus-visible:ring-2 focus-visible:outline-none'
|
||||
'focus-visible:ring-ds-ring-neutral-subtle-default focus-visible:ring-2 focus-visible:outline-none'
|
||||
)}
|
||||
>
|
||||
{showAllLabel}
|
||||
|
|
@ -183,7 +183,7 @@ export function NavList({
|
|||
onClick={() => onSessionClick?.(session.id)}
|
||||
className={cn(
|
||||
'no-drag min-h-0 min-w-0 gap-3 py-1 relative z-0 flex flex-1 items-center overflow-hidden text-left outline-none',
|
||||
'focus-visible:ring-ds-border-neutral-subtle-default focus-visible:ring-2 focus-visible:outline-none'
|
||||
'focus-visible:ring-ds-ring-neutral-subtle-default focus-visible:ring-2 focus-visible:outline-none'
|
||||
)}
|
||||
>
|
||||
<MessageCircle
|
||||
|
|
@ -226,7 +226,7 @@ export function NavList({
|
|||
'md:group-focus-within/session-item:opacity-100',
|
||||
'data-[state=open]:opacity-100',
|
||||
],
|
||||
'focus-visible:ring-ds-border-neutral-subtle-default focus-visible:ring-2 focus-visible:outline-none'
|
||||
'focus-visible:ring-ds-ring-neutral-subtle-default focus-visible:ring-2 focus-visible:outline-none'
|
||||
)}
|
||||
aria-label={sessionMenuAria}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export function workspaceTabButtonClass(active: boolean): string {
|
|||
return cn(
|
||||
'no-drag h-8 min-h-8 w-full min-w-0 shrink-0 rounded-xl cursor-pointer ease-in-out flex items-center justify-start gap-3 px-3 text-left outline-none overflow-hidden transition-colors duration-200',
|
||||
'text-ds-text-neutral-muted-default',
|
||||
'hover:bg-ds-bg-neutral-subtle-hover focus-visible:ring-ds-border-neutral-subtle-default focus-visible:ring-2 focus-visible:outline-none',
|
||||
'hover:bg-ds-bg-neutral-subtle-hover focus-visible:ring-ds-ring-neutral-subtle-default focus-visible:ring-2 focus-visible:outline-none',
|
||||
active && 'bg-ds-bg-neutral-subtle-default'
|
||||
);
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ export const WORKSPACE_TAB_LABEL_CLASS =
|
|||
'min-w-0 flex-1 truncate text-ds-text-neutral-muted-default text-body-sm font-medium';
|
||||
|
||||
const SPLIT_MAIN_BUTTON_CLASS =
|
||||
'no-drag min-h-8 min-w-0 gap-3 rounded-xl py-0 px-3 relative flex flex-1 items-center text-left outline-none text-ds-text-neutral-muted-default focus-visible:ring-ds-border-neutral-subtle-default hover:bg-transparent focus-visible:z-10 focus-visible:ring-2 focus-visible:outline-none';
|
||||
'no-drag min-h-8 min-w-0 gap-3 rounded-xl py-0 px-3 relative flex flex-1 items-center text-left outline-none text-ds-text-neutral-muted-default focus-visible:ring-ds-ring-neutral-subtle-default hover:bg-transparent focus-visible:z-10 focus-visible:ring-2 focus-visible:outline-none';
|
||||
|
||||
const SPLIT_OUTER_EXTRA_CLASS =
|
||||
'min-w-0 gap-0 !p-0 relative flex items-stretch overflow-visible';
|
||||
|
|
@ -83,7 +83,7 @@ export function NavTabReconnectSuffix({
|
|||
type="button"
|
||||
className={cn(
|
||||
'no-drag h-8 w-8 rounded-xl text-ds-icon-neutral-muted-default hover:bg-ds-bg-neutral-subtle-default flex shrink-0 items-center justify-center transition-colors outline-none',
|
||||
'focus-visible:ring-ds-border-neutral-subtle-default focus-visible:z-10 focus-visible:ring-2 focus-visible:outline-none'
|
||||
'focus-visible:ring-ds-ring-neutral-subtle-default focus-visible:z-10 focus-visible:ring-2 focus-visible:outline-none'
|
||||
)}
|
||||
aria-label={reconnectHint}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -354,11 +354,11 @@ export default function TerminalComponent({
|
|||
return (
|
||||
<div
|
||||
ref={terminalContainerRef}
|
||||
className="rounded-2xl border-ds-border-neutral-strong-default relative flex h-full w-full flex-col overflow-hidden border border-solid"
|
||||
className="rounded-2xl relative flex h-full w-full flex-col overflow-hidden"
|
||||
style={{ fontFamily: '"Courier New", Courier, monospace' }}
|
||||
>
|
||||
{/* background blur effect */}
|
||||
<div className="blur-bg inset-0 rounded-xl bg-black-100% pointer-events-none absolute"></div>
|
||||
<div className="blur-bg inset-0 rounded-xl bg-terminal-viewport-surface pointer-events-none absolute"></div>
|
||||
|
||||
{/* terminal container */}
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -64,9 +64,9 @@ export default function TerminalAgentWorkspace() {
|
|||
<CodeXml size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-emerald-700',
|
||||
bgColor: 'bg-bg-fill-coding-active',
|
||||
shapeColor: 'bg-bg-fill-coding-default',
|
||||
borderColor: 'border-bg-fill-coding-active',
|
||||
bgColor: 'bg-ds-bg-terminal-default-default',
|
||||
shapeColor: 'bg-ds-bg-terminal-subtle-default',
|
||||
borderColor: 'border-ds-border-terminal-default-default',
|
||||
bgColorLight: 'bg-emerald-200',
|
||||
},
|
||||
browser_agent: {
|
||||
|
|
@ -75,9 +75,9 @@ export default function TerminalAgentWorkspace() {
|
|||
<Globe size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-blue-700',
|
||||
bgColor: 'bg-bg-fill-browser-active',
|
||||
shapeColor: 'bg-bg-fill-browser-default',
|
||||
borderColor: 'border-bg-fill-browser-active',
|
||||
bgColor: 'bg-ds-bg-browser-default-default',
|
||||
shapeColor: 'bg-ds-bg-browser-subtle-default',
|
||||
borderColor: 'border-ds-border-browser-default-default',
|
||||
bgColorLight: 'bg-blue-200',
|
||||
},
|
||||
document_agent: {
|
||||
|
|
@ -86,9 +86,9 @@ export default function TerminalAgentWorkspace() {
|
|||
<FileText size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-yellow-700',
|
||||
bgColor: 'bg-bg-fill-writing-active',
|
||||
shapeColor: 'bg-bg-fill-writing-default',
|
||||
borderColor: 'border-bg-fill-writing-active',
|
||||
bgColor: 'bg-ds-bg-document-default-default',
|
||||
shapeColor: 'bg-ds-bg-document-subtle-default',
|
||||
borderColor: 'border-ds-border-document-default-default',
|
||||
bgColorLight: 'bg-yellow-200',
|
||||
},
|
||||
multi_modal_agent: {
|
||||
|
|
@ -97,9 +97,9 @@ export default function TerminalAgentWorkspace() {
|
|||
<Image size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-fuchsia-700',
|
||||
bgColor: 'bg-bg-fill-multimodal-active',
|
||||
shapeColor: 'bg-bg-fill-multimodal-default',
|
||||
borderColor: 'border-bg-fill-multimodal-active',
|
||||
bgColor: 'bg-ds-bg-neutral-default-default',
|
||||
shapeColor: 'bg-ds-bg-neutral-subtle-default',
|
||||
borderColor: 'border-ds-border-neutral-default-default',
|
||||
bgColorLight: 'bg-fuchsia-200',
|
||||
},
|
||||
social_media_agent: {
|
||||
|
|
@ -152,7 +152,7 @@ export default function TerminalAgentWorkspace() {
|
|||
<div
|
||||
className={`ease-in-out flex h-full w-full flex-1 items-center justify-center transition-all duration-300`}
|
||||
>
|
||||
<div className="blur-bg rounded-xl bg-ds-bg-neutral-default-default relative flex h-full w-full flex-col overflow-hidden">
|
||||
<div className="backdrop-blur-sm rounded-xl bg-ds-bg-neutral-default-default relative flex h-full w-full flex-col overflow-hidden">
|
||||
<div className="rounded-t-2xl px-2 pb-2 pt-3 flex flex-shrink-0 items-center justify-between">
|
||||
<div className="gap-sm flex items-center justify-start">
|
||||
<Button
|
||||
|
|
@ -222,7 +222,7 @@ export default function TerminalAgentWorkspace() {
|
|||
}
|
||||
/>
|
||||
{/* <div className=" flex justify-center items-center opacity-0 transition-all group-hover:opacity-100 rounded-b-lg absolute inset-0 w-full h-full bg-black/20 pointer-events-none">
|
||||
<Button className="cursor-pointer px-md py-sm h-auto flex gap-sm rounded-full bg-bg-fill-primary">
|
||||
<Button className="cursor-pointer px-md py-sm h-auto flex gap-sm rounded-full bg-ds-bg-brand-default-default">
|
||||
<Hand size={24} className="text-ds-icon-neutral-inverse-default" />
|
||||
<span className="text-base leading-9 font-medium text-ds-text-neutral-inverse-default">
|
||||
Take Control
|
||||
|
|
@ -259,7 +259,7 @@ export default function TerminalAgentWorkspace() {
|
|||
onClick={() => handleTakeControl(task.id)}
|
||||
className="flex justify-center items-center opacity-0 transition-all group-hover:opacity-100 rounded-lg absolute inset-0 w-full h-full bg-black/20 pointer-events-none"
|
||||
>
|
||||
<Button className="cursor-pointer px-md py-sm h-auto flex gap-sm rounded-full bg-bg-fill-primary">
|
||||
<Button className="cursor-pointer px-md py-sm h-auto flex gap-sm rounded-full bg-ds-bg-brand-default-default">
|
||||
<Hand
|
||||
size={24}
|
||||
className="text-ds-icon-neutral-inverse-default"
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ function HeaderWin() {
|
|||
<button
|
||||
id="active-task-title-btn"
|
||||
type="button"
|
||||
className="no-drag min-w-0 px-2 text-label-sm font-bold focus-visible:ring-ring/50 !text-ds-text-neutral-default-default hover:bg-ds-bg-neutral-default-hover active:bg-ds-bg-neutral-default-active flex min-h-[28px] max-w-[300px] flex-1 items-center text-left outline-none focus-visible:ring-[3px]"
|
||||
className="no-drag min-w-0 px-2 text-label-sm font-bold focus-visible:ring-ds-ring-brand-default-focus/50 !text-ds-text-neutral-default-default hover:bg-ds-bg-neutral-default-hover active:bg-ds-bg-neutral-default-active flex min-h-[28px] max-w-[300px] flex-1 items-center text-left outline-none focus-visible:ring-[3px]"
|
||||
onClick={toggleHistorySidebar}
|
||||
aria-expanded={historySidebarOpen}
|
||||
aria-haspopup="dialog"
|
||||
|
|
@ -342,7 +342,7 @@ function HeaderWin() {
|
|||
>
|
||||
<button
|
||||
type="button"
|
||||
className="no-drag w-8 focus-visible:ring-ring/50 !text-ds-text-neutral-default-default hover:bg-ds-bg-neutral-default-hover active:bg-ds-bg-neutral-default-active box-border flex min-h-[28px] shrink-0 items-center justify-center outline-none focus-visible:ring-[3px]"
|
||||
className="no-drag w-8 focus-visible:ring-ds-ring-brand-default-focus/50 !text-ds-text-neutral-default-default hover:bg-ds-bg-neutral-default-hover active:bg-ds-bg-neutral-default-active box-border flex min-h-[28px] shrink-0 items-center justify-center outline-none focus-visible:ring-[3px]"
|
||||
onClick={createNewProject}
|
||||
aria-label={t('layout.new-project')}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -472,7 +472,7 @@ const MultiSelectField: React.FC<FieldProps> = ({
|
|||
<Badge
|
||||
key={val}
|
||||
variant="secondary"
|
||||
className="hover:bg-destructive/20 text-xs cursor-pointer"
|
||||
className="text-xs hover:bg-ds-bg-status-error-subtle-default/50 cursor-pointer"
|
||||
onClick={() => handleToggle(val)}
|
||||
>
|
||||
{opt?.label || val}
|
||||
|
|
@ -602,7 +602,7 @@ const MultiTextInputField: React.FC<FieldProps> = ({
|
|||
<Badge
|
||||
key={val}
|
||||
variant="secondary"
|
||||
className="hover:bg-destructive/20 text-xs cursor-pointer"
|
||||
className="text-xs hover:bg-ds-bg-status-error-subtle-default/50 cursor-pointer"
|
||||
onClick={() => handleRemove(val)}
|
||||
>
|
||||
{val}
|
||||
|
|
|
|||
|
|
@ -402,7 +402,7 @@ export default function Overview({
|
|||
<Button
|
||||
size="md"
|
||||
onClick={handleConfirmDelete}
|
||||
variant="cuation"
|
||||
variant="caution"
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ export const MarkDown = ({
|
|||
className="mb-4 min-w-0 !table w-full"
|
||||
style={{
|
||||
borderCollapse: 'collapse',
|
||||
border: '1px solid #d1d5db',
|
||||
border: '1px solid var(--ds-border-neutral-default-default)',
|
||||
borderSpacing: 0,
|
||||
}}
|
||||
>
|
||||
|
|
@ -186,7 +186,7 @@ export const MarkDown = ({
|
|||
<th
|
||||
className="text-ds-text-neutral-default-default font-semibold !table-cell text-left text-[10px]"
|
||||
style={{
|
||||
border: '1px solid #d1d5db',
|
||||
border: '1px solid var(--ds-border-neutral-default-default)',
|
||||
padding: '2px 5px',
|
||||
borderCollapse: 'collapse',
|
||||
}}
|
||||
|
|
@ -198,7 +198,7 @@ export const MarkDown = ({
|
|||
<td
|
||||
className="text-ds-text-neutral-default-default !table-cell text-[10px]"
|
||||
style={{
|
||||
border: '1px solid #d1d5db',
|
||||
border: '1px solid var(--ds-border-neutral-default-default)',
|
||||
padding: '2px 5px',
|
||||
borderCollapse: 'collapse',
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -39,18 +39,18 @@ export const agentMap: Record<WorkflowAgentType, AgentDisplayInfo> = {
|
|||
<CodeXml size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-ds-text-terminal-default-default',
|
||||
bgColor: 'bg-bg-fill-coding-active',
|
||||
shapeColor: 'bg-bg-fill-coding-default',
|
||||
borderColor: 'border-bg-fill-coding-active',
|
||||
bgColor: 'bg-ds-bg-terminal-default-default',
|
||||
shapeColor: 'bg-ds-bg-terminal-subtle-default',
|
||||
borderColor: 'border-ds-border-terminal-default-default',
|
||||
bgColorLight: 'bg-emerald-200',
|
||||
},
|
||||
browser_agent: {
|
||||
name: 'Browser Agent',
|
||||
icon: <Globe size={16} className="text-ds-text-neutral-default-default" />,
|
||||
textColor: 'text-blue-700',
|
||||
bgColor: 'bg-bg-fill-browser-active',
|
||||
shapeColor: 'bg-bg-fill-browser-default',
|
||||
borderColor: 'border-bg-fill-browser-active',
|
||||
bgColor: 'bg-ds-bg-browser-default-default',
|
||||
shapeColor: 'bg-ds-bg-browser-subtle-default',
|
||||
borderColor: 'border-ds-border-browser-default-default',
|
||||
bgColorLight: 'bg-blue-200',
|
||||
},
|
||||
document_agent: {
|
||||
|
|
@ -59,18 +59,18 @@ export const agentMap: Record<WorkflowAgentType, AgentDisplayInfo> = {
|
|||
<FileText size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-yellow-700',
|
||||
bgColor: 'bg-bg-fill-writing-active',
|
||||
shapeColor: 'bg-bg-fill-writing-default',
|
||||
borderColor: 'border-bg-fill-writing-active',
|
||||
bgColor: 'bg-ds-bg-document-default-default',
|
||||
shapeColor: 'bg-ds-bg-document-subtle-default',
|
||||
borderColor: 'border-ds-border-document-default-default',
|
||||
bgColorLight: 'bg-yellow-200',
|
||||
},
|
||||
multi_modal_agent: {
|
||||
name: 'Multi Modal Agent',
|
||||
icon: <Image size={16} className="text-ds-text-neutral-default-default" />,
|
||||
textColor: 'text-fuchsia-700',
|
||||
bgColor: 'bg-bg-fill-multimodal-active',
|
||||
shapeColor: 'bg-bg-fill-multimodal-default',
|
||||
borderColor: 'border-bg-fill-multimodal-active',
|
||||
bgColor: 'bg-ds-bg-neutral-default-default',
|
||||
shapeColor: 'bg-ds-bg-neutral-subtle-default',
|
||||
borderColor: 'border-ds-border-neutral-default-default',
|
||||
bgColorLight: 'bg-fuchsia-200',
|
||||
},
|
||||
social_media_agent: {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ function WorkforceOverlayCanvas() {
|
|||
{activeWorkSpace === 'documentWorkSpace' && (
|
||||
<div className="flex h-full w-full flex-1 items-center justify-center">
|
||||
<div className="relative flex h-full w-full flex-col">
|
||||
<div className="blur-bg inset-0 rounded-xl bg-ds-bg-neutral-default-default pointer-events-none absolute"></div>
|
||||
<div className="backdrop-blur-sm inset-0 rounded-xl bg-ds-bg-neutral-default-default pointer-events-none absolute"></div>
|
||||
<div className="relative z-10 h-full w-full">
|
||||
<Folder />
|
||||
</div>
|
||||
|
|
@ -97,7 +97,7 @@ function WorkforceOverlayCanvas() {
|
|||
)?.type === 'document_agent' && (
|
||||
<div className="flex h-full w-full flex-1 items-center justify-center">
|
||||
<div className="relative flex h-full w-full flex-col">
|
||||
<div className="blur-bg inset-0 rounded-xl bg-ds-bg-neutral-default-default pointer-events-none absolute"></div>
|
||||
<div className="backdrop-blur-sm inset-0 rounded-xl bg-ds-bg-neutral-default-default pointer-events-none absolute"></div>
|
||||
<div className="relative z-10 h-full w-full">
|
||||
<Folder
|
||||
data={activeTask.taskAssigning?.find(
|
||||
|
|
@ -111,7 +111,7 @@ function WorkforceOverlayCanvas() {
|
|||
{activeWorkSpace === 'inbox' && (
|
||||
<div className="flex h-full w-full flex-1 items-center justify-center">
|
||||
<div className="relative flex h-full w-full flex-col">
|
||||
<div className="blur-bg inset-0 rounded-xl bg-ds-bg-neutral-default-default pointer-events-none absolute"></div>
|
||||
<div className="backdrop-blur-sm inset-0 rounded-xl bg-ds-bg-neutral-default-default pointer-events-none absolute"></div>
|
||||
<div className="relative z-10 h-full w-full">
|
||||
<Folder />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -255,9 +255,9 @@ export default function WorkforceMenu({
|
|||
<CodeXml size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-ds-text-terminal-default-default',
|
||||
bgColor: 'bg-bg-fill-coding-active',
|
||||
shapeColor: 'bg-bg-fill-coding-default',
|
||||
borderColor: 'border-bg-fill-coding-active',
|
||||
bgColor: 'bg-ds-bg-terminal-default-default',
|
||||
shapeColor: 'bg-ds-bg-terminal-subtle-default',
|
||||
borderColor: 'border-ds-border-terminal-default-default',
|
||||
bgColorLight: 'bg-emerald-200',
|
||||
},
|
||||
browser_agent: {
|
||||
|
|
@ -266,9 +266,9 @@ export default function WorkforceMenu({
|
|||
<Globe size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-blue-700',
|
||||
bgColor: 'bg-bg-fill-browser-active',
|
||||
shapeColor: 'bg-bg-fill-browser-default',
|
||||
borderColor: 'border-bg-fill-browser-active',
|
||||
bgColor: 'bg-ds-bg-browser-default-default',
|
||||
shapeColor: 'bg-ds-bg-browser-subtle-default',
|
||||
borderColor: 'border-ds-border-browser-default-default',
|
||||
bgColorLight: 'bg-blue-200',
|
||||
},
|
||||
document_agent: {
|
||||
|
|
@ -277,9 +277,9 @@ export default function WorkforceMenu({
|
|||
<FileText size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-yellow-700',
|
||||
bgColor: 'bg-bg-fill-writing-active',
|
||||
shapeColor: 'bg-bg-fill-writing-default',
|
||||
borderColor: 'border-bg-fill-writing-active',
|
||||
bgColor: 'bg-ds-bg-document-default-default',
|
||||
shapeColor: 'bg-ds-bg-document-subtle-default',
|
||||
borderColor: 'border-ds-border-document-default-default',
|
||||
bgColorLight: 'bg-yellow-200',
|
||||
},
|
||||
multi_modal_agent: {
|
||||
|
|
@ -288,9 +288,9 @@ export default function WorkforceMenu({
|
|||
<Image size={16} className="text-ds-text-neutral-default-default" />
|
||||
),
|
||||
textColor: 'text-fuchsia-700',
|
||||
bgColor: 'bg-bg-fill-multimodal-active',
|
||||
shapeColor: 'bg-bg-fill-multimodal-default',
|
||||
borderColor: 'border-bg-fill-multimodal-active',
|
||||
bgColor: 'bg-ds-bg-neutral-default-default',
|
||||
shapeColor: 'bg-ds-bg-neutral-subtle-default',
|
||||
borderColor: 'border-ds-border-neutral-default-default',
|
||||
bgColorLight: 'bg-fuchsia-200',
|
||||
},
|
||||
social_media_agent: {
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export function WorkforceAgentList({
|
|||
'p-2 inline-flex items-center justify-center',
|
||||
'text-ds-text-neutral-muted-default transition-all duration-200',
|
||||
'hover:text-ds-text-neutral-default-default opacity-80 hover:opacity-100',
|
||||
'focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none'
|
||||
'focus-visible:ring-ds-ring-brand-default-focus focus-visible:ring-2 focus-visible:outline-none'
|
||||
)}
|
||||
onClick={onAddWorker}
|
||||
aria-label={t('triggers.add')}
|
||||
|
|
|
|||
|
|
@ -40,13 +40,13 @@ const AccordionTrigger = React.forwardRef<
|
|||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex flex-1 items-center justify-between py-4 text-left text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
|
||||
'py-4 text-sm font-medium flex flex-1 items-center justify-between text-left transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="text-muted-foreground h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||
<ChevronDown className="h-4 w-4 text-ds-text-neutral-muted-default shrink-0 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
));
|
||||
|
|
@ -58,7 +58,7 @@ const AccordionContent = React.forwardRef<
|
|||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
||||
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down text-sm overflow-hidden"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn('p-0', className)}>{children}</div>
|
||||
|
|
|
|||
|
|
@ -18,13 +18,14 @@ import * as React from 'react';
|
|||
import { cn } from '@/lib/utils';
|
||||
|
||||
const alertVariants = cva(
|
||||
'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
|
||||
'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-ds-text-neutral-default-default [&>svg~*]:pl-7',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-background text-foreground',
|
||||
default:
|
||||
'border-ds-border-neutral-default-default bg-ds-bg-neutral-default-default text-ds-text-neutral-default-default',
|
||||
destructive:
|
||||
'border-destructive/50 text-ds-text-status-error-strong-default dark:border-destructive [&>svg]:text-destructive',
|
||||
'border-ds-border-status-error-default-default/50 text-ds-text-status-error-strong-default [&>svg]:text-ds-text-status-error-strong-default',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
|
|
|||
|
|
@ -12,18 +12,14 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Button,
|
||||
type ButtonLegacyVariant,
|
||||
type ButtonVariant,
|
||||
} from '@/components/ui/button';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
type ButtonVariant =
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'outline'
|
||||
| 'ghost'
|
||||
| 'success'
|
||||
| 'cuation'
|
||||
| 'information'
|
||||
| 'warning';
|
||||
type ConfirmVariant = ButtonVariant | ButtonLegacyVariant;
|
||||
|
||||
interface ConfirmModalProps {
|
||||
isOpen: boolean;
|
||||
|
|
@ -33,7 +29,7 @@ interface ConfirmModalProps {
|
|||
message?: string;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
confirmVariant?: ButtonVariant;
|
||||
confirmVariant?: ConfirmVariant;
|
||||
}
|
||||
|
||||
export default function ConfirmModal({
|
||||
|
|
@ -44,7 +40,7 @@ export default function ConfirmModal({
|
|||
message = 'Confirm content?',
|
||||
confirmText = 'Confirm',
|
||||
cancelText = 'Cancel',
|
||||
confirmVariant = 'cuation',
|
||||
confirmVariant = 'caution',
|
||||
}: ConfirmModalProps) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import {
|
|||
} from './semanticProps';
|
||||
|
||||
const badgeBase = cva(
|
||||
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2'
|
||||
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ds-ring-brand-default-focus focus:ring-offset-2 focus:ring-offset-ds-bg-neutral-subtle-default'
|
||||
);
|
||||
|
||||
type BadgeLegacyVariant = 'default' | 'secondary' | 'destructive' | 'outline';
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ type ButtonStyleVariant = UiVariant | 'inverse';
|
|||
* @deprecated Map to `variant` + `tone` (+ optional `emphasis`) instead:
|
||||
* - success → variant="primary" tone="success"
|
||||
* - warning → variant="primary" tone="warning"
|
||||
* - cuation → variant="primary" tone="error"
|
||||
* - caution → variant="primary" tone="error"
|
||||
* - information → variant="primary" tone="information"
|
||||
* - inverse → variant="primary" emphasis="inverse"
|
||||
*/
|
||||
|
|
@ -53,7 +53,7 @@ export type ButtonLegacyVariant =
|
|||
| 'inverse'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'cuation'
|
||||
| 'caution'
|
||||
| 'information';
|
||||
|
||||
const LEGACY_VARIANT_TO_TONE: Record<
|
||||
|
|
@ -62,7 +62,7 @@ const LEGACY_VARIANT_TO_TONE: Record<
|
|||
> = {
|
||||
success: { variant: 'primary', tone: 'success' },
|
||||
warning: { variant: 'primary', tone: 'warning' },
|
||||
cuation: { variant: 'primary', tone: 'error' },
|
||||
caution: { variant: 'primary', tone: 'error' },
|
||||
information: { variant: 'primary', tone: 'information' },
|
||||
};
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ function resolveVariantToneAndEmphasis(
|
|||
if (
|
||||
v === 'success' ||
|
||||
v === 'warning' ||
|
||||
v === 'cuation' ||
|
||||
v === 'caution' ||
|
||||
v === 'information'
|
||||
) {
|
||||
const mapped = LEGACY_VARIANT_TO_TONE[v];
|
||||
|
|
@ -194,28 +194,28 @@ const TONE_PRIMARY: Record<ButtonToneForStyles, string> = {
|
|||
`focus:bg-ds-bg-brand-default-hover focus:border-ds-bg-brand-default-hover ${FOCUS_RING}`,
|
||||
].join(' '),
|
||||
success: [
|
||||
'bg-ds-bg-success-default-default border-ds-bg-success-default-default !text-ds-text-success-strong-default',
|
||||
'bg-ds-bg-success-default-default border-ds-bg-success-default-default !text-ds-text-success-inverse-default',
|
||||
'shadow-button-shadow',
|
||||
'hover:bg-ds-bg-success-default-hover hover:border-ds-bg-success-default-hover',
|
||||
'active:bg-ds-bg-success-default-active active:border-ds-bg-success-default-active',
|
||||
`focus:bg-ds-bg-success-default-hover focus:border-ds-bg-success-default-hover ${FOCUS_RING}`,
|
||||
].join(' '),
|
||||
error: [
|
||||
'bg-ds-bg-error-default-default border-ds-bg-error-default-default !text-ds-text-error-strong-default',
|
||||
'bg-ds-bg-error-default-default border-ds-bg-error-default-default !text-ds-text-error-inverse-default',
|
||||
'shadow-button-shadow',
|
||||
'hover:bg-ds-bg-error-default-hover hover:border-ds-bg-error-default-hover',
|
||||
'active:bg-ds-bg-error-default-active active:border-ds-bg-error-default-active',
|
||||
`focus:bg-ds-bg-error-default-hover focus:border-ds-bg-error-default-hover ${FOCUS_RING}`,
|
||||
].join(' '),
|
||||
information: [
|
||||
'bg-ds-bg-information-default-default border-ds-bg-information-default-default !text-ds-text-information-strong-default',
|
||||
'bg-ds-bg-information-default-default border-ds-bg-information-default-default !text-ds-text-information-inverse-default',
|
||||
'shadow-button-shadow',
|
||||
'hover:bg-ds-bg-information-default-hover hover:border-ds-bg-information-default-hover',
|
||||
'active:bg-ds-bg-information-default-active active:border-ds-bg-information-default-active',
|
||||
`focus:bg-ds-bg-information-default-hover focus:border-ds-bg-information-default-hover ${FOCUS_RING}`,
|
||||
].join(' '),
|
||||
warning: [
|
||||
'bg-ds-bg-warning-default-default border-ds-bg-warning-default-default !text-ds-text-warning-strong-default',
|
||||
'bg-ds-bg-warning-default-default border-ds-bg-warning-default-default !text-ds-text-warning-inverse-default',
|
||||
'shadow-button-shadow',
|
||||
'hover:bg-ds-bg-warning-default-hover hover:border-ds-bg-warning-default-hover',
|
||||
'active:bg-ds-bg-warning-default-active active:border-ds-bg-warning-default-active',
|
||||
|
|
@ -233,7 +233,7 @@ const TONE_SECONDARY: Record<ButtonToneForStyles, string> = {
|
|||
`focus:bg-ds-bg-neutral-subtle-hover focus:border-ds-bg-neutral-subtle-hover ${FOCUS_RING}`,
|
||||
].join(' '),
|
||||
success: [
|
||||
'bg-ds-bg-status-completed-subtle-default border-ds-bg-status-completed-subtle-default !text-ds-text-status-completed-strong-default',
|
||||
'bg-ds-bg-success-subtle-default border-ds-bg-success-subtle-default !text-ds-text-neutral-inverse-default',
|
||||
'shadow-button-shadow',
|
||||
'hover:bg-ds-bg-status-completed-subtle-hover hover:border-ds-bg-status-completed-subtle-hover',
|
||||
'active:bg-ds-bg-status-completed-subtle-active active:border-ds-bg-status-completed-subtle-active',
|
||||
|
|
@ -343,7 +343,7 @@ const INVERSE = [
|
|||
].join(' ');
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center whitespace-nowrap border border-solid transition-all duration-200 ease-in-out disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:border-destructive shrink-0 cursor-pointer',
|
||||
'inline-flex items-center whitespace-nowrap border border-solid transition-all duration-200 ease-in-out disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 outline-none focus-visible:border-ds-border-brand-default-focus focus-visible:ring-ds-ring-brand-default-focus/50 focus-visible:ring-[3px] aria-invalid:ring-ds-ring-error-default-default/20 aria-invalid:border-ds-border-status-error-default-default shrink-0 cursor-pointer',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const CardHeader = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
||||
className={cn('space-y-1.5 p-6 flex flex-col', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
|
@ -46,7 +46,7 @@ const CardTitle = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('font-semibold leading-none tracking-tight', className)}
|
||||
className={cn('font-semibold tracking-tight leading-none', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
|
@ -58,7 +58,7 @@ const CardDescription = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('text-muted-foreground text-sm', className)}
|
||||
className={cn('text-ds-text-neutral-muted-default text-sm', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
|
@ -78,7 +78,7 @@ const CardFooter = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex items-center p-6 pt-0', className)}
|
||||
className={cn('p-6 pt-0 flex items-center', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const Checkbox = React.forwardRef<
|
|||
<CheckboxPrimitives.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'focus-visible:ring-ring peer h-4 w-4 rounded border-input-border-default bg-input-bg-default hover:border-input-border-hover data-[state=checked]:border-switch-on-fill-track-fill data-[state=checked]:bg-switch-on-fill-track-fill data-[state=checked]:text-switch-on-fill-thumb-fill shrink-0 border border-solid transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'focus-visible:ring-ds-ring-brand-default-focus peer h-4 w-4 rounded border-ds-border-neutral-default-default bg-ds-bg-neutral-default-default hover:border-ds-border-neutral-strong-default data-[state=checked]:border-ds-border-status-completed-default-default data-[state=checked]:bg-ds-bg-status-completed-default-default data-[state=checked]:text-ds-text-brand-inverse-default shrink-0 border border-solid transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
style={mergeAliasStyles(checkboxTokenAliases, style)}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const Command = React.forwardRef<
|
|||
<CommandPrimitive
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-popover-foreground rounded-md bg-white-100% flex h-full w-full flex-col overflow-hidden',
|
||||
'text-ds-text-neutral-default-default rounded-md bg-ds-bg-neutral-inverse-default flex h-full w-full flex-col overflow-hidden',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -51,12 +51,15 @@ const CommandDialog = ({
|
|||
return (
|
||||
<Dialog {...props}>
|
||||
<DialogContent
|
||||
className={cn('bg-white-100% p-0 overflow-hidden', contentClassName)}
|
||||
className={cn(
|
||||
'bg-ds-bg-neutral-inverse-default p-0 overflow-hidden',
|
||||
contentClassName
|
||||
)}
|
||||
overlayClassName={overlayClassName}
|
||||
>
|
||||
<Command
|
||||
className={cn(
|
||||
'[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5',
|
||||
'[&_[cmdk-group-heading]]:text-ds-text-neutral-muted-default [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5',
|
||||
commandClassName
|
||||
)}
|
||||
>
|
||||
|
|
@ -72,14 +75,14 @@ const CommandInput = React.forwardRef<
|
|||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
className="px-3 flex items-center border-b"
|
||||
className="border-ds-border-neutral-default-default px-3 flex items-center border-b"
|
||||
{...({ 'cmdk-input-wrapper': '' } as any)}
|
||||
>
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'placeholder:text-muted-foreground h-10 rounded-md py-3 text-sm flex w-full bg-transparent outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'placeholder:text-ds-text-neutral-muted-default h-10 rounded-md py-3 text-sm flex w-full bg-transparent outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -122,7 +125,7 @@ const CommandGroup = React.forwardRef<
|
|||
<CommandPrimitive.Group
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-foreground [&_[cmdk-group-heading]]:text-muted-foreground p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium overflow-hidden',
|
||||
'text-ds-text-neutral-default-default [&_[cmdk-group-heading]]:text-ds-text-neutral-muted-default p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium overflow-hidden',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -137,7 +140,7 @@ const CommandSeparator = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('bg-border -mx-1 h-px', className)}
|
||||
className={cn('-mx-1 bg-ds-border-neutral-default-default h-px', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
|
@ -150,7 +153,7 @@ const CommandItem = React.forwardRef<
|
|||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground gap-2 rounded-sm px-2 py-1.5 text-sm hover:border-green-600 [&_svg]:size-4 relative flex cursor-default items-center border border-solid border-transparent transition-all duration-300 outline-none select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
||||
'data-[selected=true]:bg-ds-bg-neutral-default-hover data-[selected=true]:text-ds-text-neutral-default-default gap-2 rounded-sm px-2 py-1.5 text-sm hover:border-ds-border-status-completed-default-focus [&_svg]:size-4 relative flex cursor-default items-center border border-solid border-transparent transition-all duration-300 outline-none select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -166,7 +169,7 @@ const CommandShortcut = ({
|
|||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'text-muted-foreground text-xs tracking-widest ml-auto',
|
||||
'text-ds-text-neutral-muted-default text-xs tracking-widest ml-auto',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ interface DialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||
| 'outline'
|
||||
| 'ghost'
|
||||
| 'success'
|
||||
| 'cuation'
|
||||
| 'caution'
|
||||
| 'information'
|
||||
| 'warning';
|
||||
cancelButtonVariant?:
|
||||
|
|
@ -251,7 +251,7 @@ interface DialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||
| 'outline'
|
||||
| 'ghost'
|
||||
| 'success'
|
||||
| 'cuation'
|
||||
| 'caution'
|
||||
| 'information'
|
||||
| 'warning';
|
||||
confirmButtonDisabled?: boolean;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ const DropdownMenuSubContent = React.forwardRef<
|
|||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-popover-foreground rounded-xl border-ds-border-neutral-subtle-default bg-ds-bg-neutral-subtle-default p-1 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-[--radix-dropdown-menu-content-transform-origin] overflow-hidden border border-solid',
|
||||
'text-ds-text-neutral-default-default rounded-xl border-ds-border-neutral-subtle-default bg-ds-bg-neutral-subtle-default p-1 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-[--radix-dropdown-menu-content-transform-origin] overflow-hidden border border-solid',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -77,7 +77,7 @@ const DropdownMenuContent = React.forwardRef<
|
|||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'text-popover-foreground rounded-xl border-ds-border-neutral-subtle-default bg-ds-bg-neutral-subtle-default p-xs shadow-md z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-x-hidden overflow-y-auto border border-solid',
|
||||
'text-ds-text-neutral-default-default rounded-xl border-ds-border-neutral-subtle-default bg-ds-bg-neutral-subtle-default p-xs shadow-md z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-x-hidden overflow-y-auto border border-solid',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]',
|
||||
className
|
||||
)}
|
||||
|
|
@ -97,7 +97,7 @@ const DropdownMenuItem = React.forwardRef<
|
|||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'focus:bg-accent focus:text-accent-foreground gap-2 rounded-xl px-2 py-1.5 text-sm hover:bg-menutabs-fill-hover [&>svg]:size-4 relative flex cursor-pointer items-center transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:shrink-0',
|
||||
'focus:bg-ds-bg-neutral-default-hover focus:text-ds-text-neutral-default-default gap-2 rounded-xl px-2 py-1.5 text-sm hover:bg-ds-bg-neutral-default-hover [&>svg]:size-4 relative flex cursor-pointer items-center transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:shrink-0',
|
||||
inset && 'pl-8',
|
||||
className
|
||||
)}
|
||||
|
|
@ -113,7 +113,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'focus:bg-accent focus:text-accent-foreground rounded-sm py-1.5 pl-8 pr-2 text-sm relative flex cursor-default items-center transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'focus:bg-ds-bg-neutral-default-hover focus:text-ds-text-neutral-default-default rounded-sm py-1.5 pl-8 pr-2 text-sm relative flex cursor-default items-center transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
|
|
@ -137,7 +137,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'focus:bg-accent focus:text-accent-foreground rounded-sm py-1.5 pl-8 pr-2 text-sm relative flex cursor-default items-center transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'focus:bg-ds-bg-neutral-default-hover focus:text-ds-text-neutral-default-default rounded-sm py-1.5 pl-8 pr-2 text-sm relative flex cursor-default items-center transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -176,7 +176,7 @@ const DropdownMenuSeparator = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('bg-muted -mx-1 my-1 h-px', className)}
|
||||
className={cn('bg-ds-bg-neutral-muted-default -mx-1 my-1 h-px', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
|
|
|||
|
|
@ -73,27 +73,29 @@ function resolveStateClasses(
|
|||
if (disabled) {
|
||||
return {
|
||||
wrapper: 'opacity-50 cursor-not-allowed',
|
||||
container: 'border-transparent bg-input-bg-default',
|
||||
container: 'border-transparent bg-ds-bg-neutral-default-default',
|
||||
note: 'text-ds-text-neutral-muted-default',
|
||||
};
|
||||
}
|
||||
if (state === 'error') {
|
||||
return {
|
||||
wrapper: '',
|
||||
container: 'border-input-border-cuation bg-input-bg-default',
|
||||
container:
|
||||
'border-ds-border-status-error-default-default bg-ds-bg-neutral-default-default',
|
||||
note: 'text-ds-text-status-error-strong-default',
|
||||
};
|
||||
}
|
||||
if (state === 'success') {
|
||||
return {
|
||||
wrapper: '',
|
||||
container: 'border-input-border-success bg-input-bg-confirm',
|
||||
container:
|
||||
'border-ds-border-status-completed-default-default bg-ds-bg-status-completed-subtle-default',
|
||||
note: 'text-ds-text-status-completed-strong-default',
|
||||
};
|
||||
}
|
||||
return {
|
||||
wrapper: '',
|
||||
container: 'border-transparent bg-input-bg-default',
|
||||
container: 'border-transparent bg-ds-bg-neutral-default-default',
|
||||
note: 'text-ds-text-neutral-muted-default',
|
||||
};
|
||||
}
|
||||
|
|
@ -358,9 +360,9 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||
!disabled &&
|
||||
state !== 'error' &&
|
||||
state !== 'success' && [
|
||||
'hover:bg-input-bg-hover hover:ring-input-border-hover hover:ring-1 hover:ring-offset-0',
|
||||
'hover:bg-ds-bg-neutral-default-hover hover:ring-ds-ring-neutral-strong-default hover:ring-1 hover:ring-offset-0',
|
||||
isOpen &&
|
||||
'bg-input-bg-input ring-input-border-focus ring-1 ring-offset-0',
|
||||
'bg-ds-bg-neutral-strong-default ring-ds-ring-brand-default-focus ring-1 ring-offset-0',
|
||||
]
|
||||
)}
|
||||
>
|
||||
|
|
@ -393,7 +395,7 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||
{isOpen && (
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
className="left-0 right-0 mt-1 bg-input-bg-default rounded-lg shadow-md absolute top-full z-50 overflow-hidden border border-solid border-transparent"
|
||||
className="left-0 right-0 mt-1 bg-ds-bg-neutral-default-default rounded-lg shadow-md absolute top-full z-50 overflow-hidden border border-solid border-transparent"
|
||||
>
|
||||
<div
|
||||
className="p-1 overflow-x-hidden overflow-y-auto overscroll-contain"
|
||||
|
|
@ -405,9 +407,9 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||
key={option.value}
|
||||
onClick={() => handleOptionClick(option)}
|
||||
className={cn(
|
||||
'rounded-lg py-1.5 pl-2 pr-8 text-sm hover:bg-menutabs-fill-hover relative flex w-full cursor-pointer items-center transition-colors outline-none select-none',
|
||||
'rounded-lg py-1.5 pl-2 pr-8 text-sm hover:bg-ds-bg-neutral-default-hover relative flex w-full cursor-pointer items-center transition-colors outline-none select-none',
|
||||
selectedOption?.value === option.value &&
|
||||
'bg-menutabs-fill-hover'
|
||||
'bg-ds-bg-neutral-default-hover'
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ function resolveStateClasses(state: InputState | undefined) {
|
|||
if (state === 'disabled') {
|
||||
return {
|
||||
container: 'opacity-50 cursor-not-allowed',
|
||||
field: 'border-input-border-default bg-input-bg-default',
|
||||
field:
|
||||
'border-ds-border-neutral-default-default bg-ds-bg-neutral-default-default',
|
||||
input: 'text-ds-text-neutral-default-default',
|
||||
placeholder: 'placeholder-input-label-default',
|
||||
};
|
||||
|
|
@ -61,7 +62,8 @@ function resolveStateClasses(state: InputState | undefined) {
|
|||
if (state === 'hover') {
|
||||
return {
|
||||
container: '',
|
||||
field: 'border-input-border-hover bg-input-bg-default',
|
||||
field:
|
||||
'border-ds-border-neutral-strong-default bg-ds-bg-neutral-default-default',
|
||||
input: 'text-ds-text-neutral-default-default',
|
||||
placeholder: 'placeholder-input-label-default',
|
||||
};
|
||||
|
|
@ -69,7 +71,8 @@ function resolveStateClasses(state: InputState | undefined) {
|
|||
if (state === 'input') {
|
||||
return {
|
||||
container: '',
|
||||
field: 'border-input-border-focus bg-input-bg-input',
|
||||
field:
|
||||
'border-ds-border-brand-default-focus bg-ds-bg-neutral-strong-default',
|
||||
input: 'text-ds-text-neutral-default-default',
|
||||
placeholder: 'placeholder-input-label-default',
|
||||
};
|
||||
|
|
@ -77,7 +80,8 @@ function resolveStateClasses(state: InputState | undefined) {
|
|||
if (state === 'error') {
|
||||
return {
|
||||
container: '',
|
||||
field: 'border-input-border-cuation bg-input-bg-default',
|
||||
field:
|
||||
'border-ds-border-status-error-default-default bg-ds-bg-neutral-default-default',
|
||||
input: 'text-ds-text-neutral-default-default',
|
||||
placeholder: 'placeholder-input-label-default',
|
||||
};
|
||||
|
|
@ -85,14 +89,16 @@ function resolveStateClasses(state: InputState | undefined) {
|
|||
if (state === 'success') {
|
||||
return {
|
||||
container: '',
|
||||
field: 'border-input-border-success bg-input-bg-confirm',
|
||||
field:
|
||||
'border-ds-border-status-completed-default-default bg-ds-bg-status-completed-subtle-default',
|
||||
input: 'text-ds-text-neutral-default-default',
|
||||
placeholder: 'placeholder-input-label-default',
|
||||
};
|
||||
}
|
||||
return {
|
||||
container: '',
|
||||
field: 'border-input-border-default bg-input-bg-default',
|
||||
field:
|
||||
'border-ds-border-neutral-default-default bg-ds-bg-neutral-default-default',
|
||||
input: 'text-ds-text-neutral-default-default',
|
||||
placeholder: 'placeholder-input-label-default/10',
|
||||
};
|
||||
|
|
@ -159,7 +165,7 @@ const Input = React.forwardRef<HTMLInputElement, BaseInputProps>(
|
|||
// Only apply hover/focus visuals when not in error state
|
||||
state !== 'error' &&
|
||||
state !== 'success' &&
|
||||
'focus-within:bg-input-bg-input focus-within:ring-input-border-focus hover:bg-input-bg-hover hover:ring-input-border-hover focus-within:ring-1 focus-within:ring-offset-0 hover:ring-1 hover:ring-offset-0',
|
||||
'focus-within:bg-ds-bg-neutral-strong-default focus-within:ring-ds-ring-brand-default-focus hover:bg-ds-bg-neutral-default-hover hover:ring-ds-ring-neutral-strong-default focus-within:ring-1 focus-within:ring-offset-0 hover:ring-1 hover:ring-offset-0',
|
||||
stateCls.field,
|
||||
sizeClasses[size]
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -19,15 +19,15 @@ import { cva, type VariantProps } from 'class-variance-authority';
|
|||
import * as React from 'react';
|
||||
|
||||
const menuButtonVariants = cva(
|
||||
'relative inline-flex items-center justify-center select-none transition-colors duration-200 ease-in-out outline-none disabled:opacity-30 disabled:pointer-events-none bg-menubutton-fill-default hover:bg-menubutton-fill-hover hover:text-ds-text-neutral-default-default focus:text-ds-text-neutral-default-default data-[state=on]:bg-menubutton-fill-active data-[state=on]:text-ds-text-neutral-default-default text-ds-text-neutral-muted-default disabled:text-ds-text-neutral-muted-disabled cursor-pointer data-[state=on]:shadow-button-shadow rounded-lg',
|
||||
'relative inline-flex items-center justify-center select-none transition-colors duration-200 ease-in-out outline-none disabled:opacity-30 disabled:pointer-events-none bg-ds-bg-neutral-subtle-default hover:bg-ds-bg-neutral-default-hover hover:text-ds-text-neutral-default-default focus:text-ds-text-neutral-default-default data-[state=on]:bg-ds-bg-neutral-default-default data-[state=on]:text-ds-text-neutral-default-default text-ds-text-neutral-muted-default disabled:text-ds-text-neutral-muted-disabled cursor-pointer data-[state=on]:shadow-button-shadow rounded-lg',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'border border-solid text-ds-text-neutral-default-default border-menubutton-border-default hover:border-menubutton-border-hover focus:bg-menubutton-fill-active focus:border-menubutton-border-active data-[state=on]:border-menubutton-border-active data-[state=on]:shadow-button-shadow',
|
||||
'border border-solid text-ds-text-neutral-default-default border-ds-border-neutral-default-default hover:border-ds-border-neutral-strong-default focus:bg-ds-bg-neutral-default-default focus:border-ds-border-brand-default-focus data-[state=on]:border-ds-border-brand-default-focus data-[state=on]:shadow-button-shadow',
|
||||
clear:
|
||||
'border border-solid text-ds-text-neutral-default-default border-menubutton-border-default hover:border-menubutton-border-hover focus:bg-menubutton-fill-active focus:border-menubutton-border-default data-[state=on]:shadow-button-shadow',
|
||||
info: 'text-ds-text-neutral-default-default !font-medium hover:bg-menubutton-fill-active focus:bg-menubutton-fill-active data-[state=on]:text-ds-text-neutral-default-default data-[state=on]:!font-bold',
|
||||
'border border-solid text-ds-text-neutral-default-default border-ds-border-neutral-default-default hover:border-ds-border-neutral-strong-default focus:bg-ds-bg-neutral-default-default focus:border-ds-border-neutral-default-default data-[state=on]:shadow-button-shadow',
|
||||
info: 'text-ds-text-neutral-default-default !font-medium hover:bg-ds-bg-neutral-default-default focus:bg-ds-bg-neutral-default-default data-[state=on]:text-ds-text-neutral-default-default data-[state=on]:!font-bold',
|
||||
},
|
||||
size: {
|
||||
xs: 'px-2 py-1 text-label-sm font-bold [&_svg]:size-[16px] rounded-lg',
|
||||
|
|
|
|||
|
|
@ -41,14 +41,16 @@ function resolveStateClasses(
|
|||
if (state === 'error') {
|
||||
return {
|
||||
wrapper: '',
|
||||
trigger: 'border-input-border-cuation bg-input-bg-default',
|
||||
trigger:
|
||||
'border-ds-border-status-error-default-default bg-ds-bg-neutral-default-default',
|
||||
note: 'text-ds-text-status-error-strong-default',
|
||||
};
|
||||
}
|
||||
if (state === 'success') {
|
||||
return {
|
||||
wrapper: '',
|
||||
trigger: 'border-input-border-success bg-input-bg-confirm',
|
||||
trigger:
|
||||
'border-ds-border-status-completed-default-default bg-ds-bg-status-completed-subtle-default',
|
||||
note: 'text-ds-text-status-completed-strong-default',
|
||||
};
|
||||
}
|
||||
|
|
@ -152,17 +154,17 @@ const PopoverTrigger = React.forwardRef<
|
|||
sizeClasses[size],
|
||||
'whitespace-nowrap [&>span]:line-clamp-1',
|
||||
// Default state (when no error/success)
|
||||
!state && 'bg-input-bg-default',
|
||||
!state && 'bg-ds-bg-neutral-default-default',
|
||||
// Interactive states (only when no error/success state)
|
||||
state !== 'error' &&
|
||||
state !== 'success' && [
|
||||
'hover:bg-input-bg-hover hover:ring-input-border-hover hover:ring-1 hover:ring-offset-0',
|
||||
'focus-visible:ring-input-border-focus data-[state=open]:bg-input-bg-input data-[state=open]:ring-input-border-focus focus-visible:ring-1 focus-visible:ring-offset-0 data-[state=open]:ring-1 data-[state=open]:ring-offset-0',
|
||||
'hover:bg-ds-bg-neutral-default-hover hover:ring-ds-ring-neutral-strong-default hover:ring-1 hover:ring-offset-0',
|
||||
'focus-visible:ring-ds-ring-brand-default-focus data-[state=open]:bg-ds-bg-neutral-strong-default data-[state=open]:ring-ds-ring-brand-default-focus focus-visible:ring-1 focus-visible:ring-offset-0 data-[state=open]:ring-1 data-[state=open]:ring-offset-0',
|
||||
],
|
||||
// Validation states (override defaults)
|
||||
stateCls.trigger,
|
||||
// Placeholder styling
|
||||
'data-[placeholder]:text-input-label-default/50',
|
||||
'data-[placeholder]:text-ds-text-neutral-muted-default/50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -272,7 +274,7 @@ const PopoverContent = React.forwardRef<
|
|||
onOpenAutoFocus={handleOpenAutoFocus}
|
||||
onInteractOutside={handleInteractOutside}
|
||||
className={cn(
|
||||
'text-popover-foreground rounded-lg bg-input-bg-default shadow-md relative z-50 min-w-[8rem] overflow-hidden border border-solid border-transparent',
|
||||
'text-ds-text-neutral-default-default rounded-lg bg-ds-bg-neutral-default-default shadow-md relative z-50 min-w-[8rem] overflow-hidden border border-solid border-transparent',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]',
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
'w-[var(--radix-popover-trigger-width)]',
|
||||
|
|
@ -296,9 +298,9 @@ const PopoverItem = React.forwardRef<HTMLDivElement, PopoverItemProps>(
|
|||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'rounded-lg py-1.5 pl-2 pr-8 text-sm hover:bg-menutabs-fill-hover relative flex w-full cursor-pointer items-center outline-none select-none',
|
||||
'rounded-lg py-1.5 pl-2 pr-8 text-sm hover:bg-ds-bg-neutral-default-hover relative flex w-full cursor-pointer items-center outline-none select-none',
|
||||
disabled && 'pointer-events-none opacity-50',
|
||||
selected && 'bg-menutabs-fill-hover',
|
||||
selected && 'bg-ds-bg-neutral-default-hover',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -62,13 +62,13 @@ function ResizableHandle({
|
|||
<ResizablePrimitive.PanelResizeHandle
|
||||
data-slot="resizable-handle"
|
||||
className={cn(
|
||||
'bg-border focus-visible:ring-ring after:inset-y-0 after:w-1 data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:translate-x-0 relative flex w-px items-center justify-center after:absolute after:left-1/2 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90',
|
||||
'bg-ds-border-neutral-default-default after:inset-y-0 after:w-1 focus-visible:ring-ds-ring-brand-default-focus focus-visible:ring-offset-ds-bg-neutral-subtle-default data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:translate-x-0 relative flex w-px items-center justify-center after:absolute after:left-1/2 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{withHandle && (
|
||||
<div className="bg-border rounded-xs h-4 w-3 z-10 flex items-center justify-center border">
|
||||
<div className="h-4 w-3 rounded-xs border-ds-border-neutral-default-default bg-ds-bg-neutral-default-default z-10 flex items-center justify-center border">
|
||||
<GripVerticalIcon className="size-2.5" />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -51,14 +51,16 @@ function resolveStateClasses(
|
|||
if (state === 'error') {
|
||||
return {
|
||||
wrapper: '',
|
||||
trigger: 'border-input-border-cuation bg-input-bg-default',
|
||||
trigger:
|
||||
'border-ds-border-status-error-default-default bg-ds-bg-neutral-default-default',
|
||||
note: 'text-ds-text-status-error-strong-default',
|
||||
};
|
||||
}
|
||||
if (state === 'success') {
|
||||
return {
|
||||
wrapper: '',
|
||||
trigger: 'border-input-border-success bg-input-bg-confirm',
|
||||
trigger:
|
||||
'border-ds-border-status-completed-default-default bg-ds-bg-status-completed-subtle-default',
|
||||
note: 'text-ds-text-status-completed-strong-default',
|
||||
};
|
||||
}
|
||||
|
|
@ -127,17 +129,17 @@ const SelectTrigger = React.forwardRef<
|
|||
sizeClasses[size],
|
||||
'whitespace-nowrap [&>span]:line-clamp-1',
|
||||
// Default state (when no error/success)
|
||||
!state && 'bg-input-bg-default',
|
||||
!state && 'bg-ds-bg-neutral-default-default',
|
||||
// Interactive states (only when no error/success state)
|
||||
state !== 'error' &&
|
||||
state !== 'success' && [
|
||||
'hover:bg-input-bg-hover hover:ring-input-border-hover hover:ring-1 hover:ring-offset-0',
|
||||
'focus-visible:ring-input-border-focus data-[state=open]:bg-input-bg-input data-[state=open]:ring-input-border-focus focus-visible:ring-1 focus-visible:ring-offset-0 data-[state=open]:ring-1 data-[state=open]:ring-offset-0',
|
||||
'hover:bg-ds-bg-neutral-default-hover hover:ring-ds-ring-neutral-strong-default hover:ring-1 hover:ring-offset-0',
|
||||
'focus-visible:ring-ds-ring-brand-default-focus data-[state=open]:bg-ds-bg-neutral-strong-default data-[state=open]:ring-ds-ring-brand-default-focus focus-visible:ring-1 focus-visible:ring-offset-0 data-[state=open]:ring-1 data-[state=open]:ring-offset-0',
|
||||
],
|
||||
// Validation states (override defaults)
|
||||
stateCls.trigger,
|
||||
// Placeholder styling
|
||||
'data-[placeholder]:text-input-label-default/50',
|
||||
'data-[placeholder]:text-ds-text-neutral-muted-default/50',
|
||||
className
|
||||
)}
|
||||
style={mergeAliasStyles(formControlTokenAliases, style)}
|
||||
|
|
@ -200,7 +202,7 @@ const SelectContent = React.forwardRef<
|
|||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-popover-foreground rounded-xl bg-input-bg-default shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] origin-[--radix-select-content-transform-origin] overflow-x-hidden overflow-y-auto border border-solid border-transparent',
|
||||
'text-ds-text-neutral-default-default rounded-xl bg-ds-bg-neutral-default-default shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] origin-[--radix-select-content-transform-origin] overflow-x-hidden overflow-y-auto border border-solid border-transparent',
|
||||
position === 'popper' &&
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
className
|
||||
|
|
@ -244,7 +246,7 @@ const SelectItem = React.forwardRef<
|
|||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'focus:bg-accent focus:text-accent-foreground rounded-lg py-1.5 pl-2 pr-8 text-sm hover:bg-menutabs-fill-hover relative flex w-full cursor-pointer items-center outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'focus:bg-ds-bg-neutral-default-hover focus:text-ds-text-neutral-default-default rounded-lg py-1.5 pl-2 pr-8 text-sm hover:bg-ds-bg-neutral-default-hover relative flex w-full cursor-pointer items-center outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -265,7 +267,7 @@ const SelectSeparator = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('bg-muted -mx-1 my-1 h-px', className)}
|
||||
className={cn('bg-ds-bg-neutral-muted-default -mx-1 my-1 h-px', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
|
@ -301,7 +303,7 @@ const SelectItemWithButton = React.forwardRef<
|
|||
value={value}
|
||||
disabled={!enabled}
|
||||
className={cn(
|
||||
'focus:bg-accent focus:text-accent-foreground group rounded-lg py-1.5 pl-2 pr-8 text-sm hover:bg-menutabs-fill-hover relative flex w-full cursor-pointer items-center outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'focus:bg-ds-bg-neutral-default-hover focus:text-ds-text-neutral-default-default group rounded-lg py-1.5 pl-2 pr-8 text-sm hover:bg-ds-bg-neutral-default-hover relative flex w-full cursor-pointer items-center outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const Separator = React.forwardRef<
|
|||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
'bg-border shrink-0',
|
||||
'bg-ds-border-neutral-default-default shrink-0',
|
||||
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
||||
className
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const SheetOverlay = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
'bg-black/80 fixed inset-0 z-50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
'inset-0 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed z-50 bg-[color:var(--ds-bg-neutral-inverse-default)]/80',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -45,7 +45,7 @@ const SheetOverlay = React.forwardRef<
|
|||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||
|
||||
const sheetVariants = cva(
|
||||
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out',
|
||||
'fixed z-50 gap-4 bg-ds-bg-neutral-subtle-default p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out',
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
|
|
@ -79,7 +79,7 @@ const SheetContent = React.forwardRef<
|
|||
className={cn(sheetVariants({ side }), className)}
|
||||
{...props}
|
||||
>
|
||||
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none">
|
||||
<SheetPrimitive.Close className="right-4 top-4 rounded-sm ring-offset-ds-bg-neutral-subtle-default focus:ring-ds-ring-brand-default-focus data-[state=open]:bg-ds-bg-neutral-strong-default absolute opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
|
|
@ -95,7 +95,7 @@ const SheetHeader = ({
|
|||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col space-y-2 text-center sm:text-left',
|
||||
'space-y-2 sm:text-left flex flex-col text-center',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -109,7 +109,7 @@ const SheetFooter = ({
|
|||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||
'sm:flex-row sm:justify-end sm:space-x-2 flex flex-col-reverse',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -123,7 +123,10 @@ const SheetTitle = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn('text-foreground text-lg font-semibold', className)}
|
||||
className={cn(
|
||||
'text-lg font-semibold text-ds-text-neutral-default-default',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
|
@ -135,7 +138,7 @@ const SheetDescription = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn('text-muted-foreground text-sm', className)}
|
||||
className={cn('text-sm text-ds-text-neutral-muted-default', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ const SidebarProvider = React.forwardRef<
|
|||
} as React.CSSProperties
|
||||
}
|
||||
className={cn(
|
||||
'group/sidebar-wrapper has-[[data-variant=inset]]:bg-sidebar flex min-h-svh w-full',
|
||||
'group/sidebar-wrapper has-[[data-variant=inset]]:bg-ds-bg-neutral-strong-default flex min-h-svh w-full',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
|
@ -199,7 +199,7 @@ const Sidebar = React.forwardRef<
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'bg-sidebar text-sidebar-foreground flex h-full w-[--sidebar-width] flex-col',
|
||||
'bg-ds-bg-neutral-strong-default text-ds-text-neutral-default-default flex h-full w-[--sidebar-width] flex-col',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
|
@ -216,7 +216,7 @@ const Sidebar = React.forwardRef<
|
|||
<SheetContent
|
||||
data-sidebar="sidebar"
|
||||
data-mobile="true"
|
||||
className="bg-sidebar text-sidebar-foreground p-0 w-[--sidebar-width] [&>button]:hidden"
|
||||
className="bg-ds-bg-neutral-strong-default text-ds-text-neutral-default-default p-0 w-[--sidebar-width] [&>button]:hidden"
|
||||
style={
|
||||
{
|
||||
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
|
||||
|
|
@ -237,7 +237,7 @@ const Sidebar = React.forwardRef<
|
|||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="text-sidebar-foreground group peer md:block hidden"
|
||||
className="text-ds-text-neutral-default-default group peer md:block hidden"
|
||||
data-state={state}
|
||||
data-collapsible={state === 'collapsed' ? collapsible : ''}
|
||||
data-variant={variant}
|
||||
|
|
@ -270,7 +270,7 @@ const Sidebar = React.forwardRef<
|
|||
>
|
||||
<div
|
||||
data-sidebar="sidebar"
|
||||
className="bg-sidebar group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow flex h-full w-full flex-col group-data-[variant=floating]:border"
|
||||
className="bg-ds-bg-neutral-strong-default group-data-[variant=floating]:border-ds-border-neutral-default-default group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow flex h-full w-full flex-col group-data-[variant=floating]:border"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
|
@ -323,10 +323,10 @@ const SidebarRail = React.forwardRef<
|
|||
onClick={toggleSidebar}
|
||||
title="Toggle Sidebar"
|
||||
className={cn(
|
||||
'hover:after:bg-sidebar-border inset-y-0 w-4 after:inset-y-0 group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex absolute z-20 hidden -translate-x-1/2 transition-all ease-linear after:absolute after:left-1/2 after:w-[2px]',
|
||||
'hover:after:bg-ds-border-neutral-default-default inset-y-0 w-4 after:inset-y-0 group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex absolute z-20 hidden -translate-x-1/2 transition-all ease-linear after:absolute after:left-1/2 after:w-[2px]',
|
||||
'[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
|
||||
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
|
||||
'group-data-[collapsible=offcanvas]:hover:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
|
||||
'group-data-[collapsible=offcanvas]:hover:bg-ds-bg-neutral-strong-default group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
|
||||
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
|
||||
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
|
||||
className
|
||||
|
|
@ -345,7 +345,7 @@ const SidebarInset = React.forwardRef<
|
|||
<main
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'bg-background relative flex w-full flex-1 flex-col',
|
||||
'bg-ds-bg-neutral-subtle-default relative flex w-full flex-1 flex-col',
|
||||
'md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
|
||||
className
|
||||
)}
|
||||
|
|
@ -364,7 +364,7 @@ const SidebarInput = React.forwardRef<
|
|||
ref={ref}
|
||||
data-sidebar="input"
|
||||
className={cn(
|
||||
'bg-background focus-visible:ring-sidebar-ring h-8 w-full shadow-none focus-visible:ring-2',
|
||||
'bg-ds-bg-neutral-subtle-default focus-visible:ring-ds-ring-brand-default-focus h-8 w-full shadow-none focus-visible:ring-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -411,7 +411,10 @@ const SidebarSeparator = React.forwardRef<
|
|||
<Separator
|
||||
ref={ref}
|
||||
data-sidebar="separator"
|
||||
className={cn('bg-sidebar-border mx-2 w-auto', className)}
|
||||
className={cn(
|
||||
'bg-ds-border-neutral-default-default mx-2 w-auto',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
@ -462,7 +465,7 @@ const SidebarGroupLabel = React.forwardRef<
|
|||
ref={ref}
|
||||
data-sidebar="group-label"
|
||||
className={cn(
|
||||
'text-sidebar-foreground/70 ring-sidebar-ring h-8 rounded-md px-2 text-xs font-medium [&>svg]:size-4 flex shrink-0 items-center transition-[margin,opacity] duration-200 ease-linear outline-none focus-visible:ring-2 [&>svg]:shrink-0',
|
||||
'text-ds-text-neutral-muted-default ring-ds-ring-brand-default-focus h-8 rounded-md px-2 text-xs font-medium [&>svg]:size-4 flex shrink-0 items-center transition-[margin,opacity] duration-200 ease-linear outline-none focus-visible:ring-2 [&>svg]:shrink-0',
|
||||
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
|
||||
className
|
||||
)}
|
||||
|
|
@ -483,7 +486,7 @@ const SidebarGroupAction = React.forwardRef<
|
|||
ref={ref}
|
||||
data-sidebar="group-action"
|
||||
className={cn(
|
||||
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground right-3 top-3.5 w-5 rounded-md p-0 [&>svg]:size-4 absolute flex aspect-square items-center justify-center transition-transform outline-none focus-visible:ring-2 [&>svg]:shrink-0',
|
||||
'text-ds-text-neutral-default-default ring-ds-ring-brand-default-focus hover:bg-ds-bg-neutral-default-hover hover:text-ds-text-neutral-default-default right-3 top-3.5 w-5 rounded-md p-0 [&>svg]:size-4 absolute flex aspect-square items-center justify-center transition-transform outline-none focus-visible:ring-2 [&>svg]:shrink-0',
|
||||
// Increases the hit area of the button on mobile.
|
||||
'after:-inset-2 after:md:hidden after:absolute',
|
||||
'group-data-[collapsible=icon]:hidden',
|
||||
|
|
@ -535,13 +538,14 @@ const SidebarMenuItem = React.forwardRef<
|
|||
SidebarMenuItem.displayName = 'SidebarMenuItem';
|
||||
|
||||
const sidebarMenuButtonVariants = cva(
|
||||
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-ds-ring-brand-default-focus transition-[width,height,padding] hover:bg-ds-bg-neutral-default-hover hover:text-ds-text-neutral-default-default focus-visible:ring-2 active:bg-ds-bg-neutral-default-hover active:text-ds-text-neutral-default-default disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-ds-bg-neutral-default-hover data-[active=true]:font-medium data-[active=true]:text-ds-text-neutral-default-default data-[state=open]:hover:bg-ds-bg-neutral-default-hover data-[state=open]:hover:text-ds-text-neutral-default-default group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
|
||||
default:
|
||||
'hover:bg-ds-bg-neutral-default-hover hover:text-ds-text-neutral-default-default',
|
||||
outline:
|
||||
'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
|
||||
'bg-ds-bg-neutral-subtle-default shadow-[0_0_0_1px_var(--ds-border-neutral-default-default)] hover:bg-ds-bg-neutral-default-hover hover:text-ds-text-neutral-default-default hover:shadow-[0_0_0_1px_var(--ds-bg-neutral-default-hover)]',
|
||||
},
|
||||
size: {
|
||||
default: 'h-8 text-sm',
|
||||
|
|
@ -629,7 +633,7 @@ const SidebarMenuAction = React.forwardRef<
|
|||
ref={ref}
|
||||
data-sidebar="menu-action"
|
||||
className={cn(
|
||||
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground right-1 top-1.5 w-5 rounded-md p-0 [&>svg]:size-4 absolute flex aspect-square items-center justify-center transition-transform outline-none focus-visible:ring-2 [&>svg]:shrink-0',
|
||||
'text-ds-text-neutral-default-default ring-ds-ring-brand-default-focus hover:bg-ds-bg-neutral-default-hover hover:text-ds-text-neutral-default-default peer-hover/menu-button:text-ds-text-neutral-default-default right-1 top-1.5 w-5 rounded-md p-0 [&>svg]:size-4 absolute flex aspect-square items-center justify-center transition-transform outline-none focus-visible:ring-2 [&>svg]:shrink-0',
|
||||
// Increases the hit area of the button on mobile.
|
||||
'after:-inset-2 after:md:hidden after:absolute',
|
||||
'peer-data-[size=sm]/menu-button:top-1',
|
||||
|
|
@ -637,7 +641,7 @@ const SidebarMenuAction = React.forwardRef<
|
|||
'peer-data-[size=lg]/menu-button:top-2.5',
|
||||
'group-data-[collapsible=icon]:hidden',
|
||||
showOnHover &&
|
||||
'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0 group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100',
|
||||
'peer-data-[active=true]/menu-button:text-ds-text-neutral-default-default md:opacity-0 group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -654,8 +658,8 @@ const SidebarMenuBadge = React.forwardRef<
|
|||
ref={ref}
|
||||
data-sidebar="menu-badge"
|
||||
className={cn(
|
||||
'text-sidebar-foreground right-1 h-5 min-w-5 rounded-md px-1 text-xs font-medium pointer-events-none absolute flex items-center justify-center tabular-nums select-none',
|
||||
'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
|
||||
'text-ds-text-neutral-default-default right-1 h-5 min-w-5 rounded-md px-1 text-xs font-medium pointer-events-none absolute flex items-center justify-center tabular-nums select-none',
|
||||
'peer-hover/menu-button:text-ds-text-neutral-default-default peer-data-[active=true]/menu-button:text-ds-text-neutral-default-default',
|
||||
'peer-data-[size=sm]/menu-button:top-1',
|
||||
'peer-data-[size=default]/menu-button:top-1.5',
|
||||
'peer-data-[size=lg]/menu-button:top-2.5',
|
||||
|
|
@ -713,7 +717,7 @@ const SidebarMenuSub = React.forwardRef<
|
|||
ref={ref}
|
||||
data-sidebar="menu-sub"
|
||||
className={cn(
|
||||
'border-sidebar-border mx-3.5 min-w-0 gap-1 px-2.5 py-0.5 flex translate-x-px flex-col border-l',
|
||||
'border-ds-border-neutral-default-default mx-3.5 min-w-0 gap-1 px-2.5 py-0.5 flex translate-x-px flex-col border-l',
|
||||
'group-data-[collapsible=icon]:hidden',
|
||||
className
|
||||
)}
|
||||
|
|
@ -745,8 +749,8 @@ const SidebarMenuSubButton = React.forwardRef<
|
|||
data-size={size}
|
||||
data-active={isActive}
|
||||
className={cn(
|
||||
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground h-7 min-w-0 gap-2 rounded-md px-2 [&>svg]:size-4 flex -translate-x-px items-center overflow-hidden outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0',
|
||||
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
|
||||
'text-ds-text-neutral-default-default ring-ds-ring-brand-default-focus hover:bg-ds-bg-neutral-default-hover hover:text-ds-text-neutral-default-default active:bg-ds-bg-neutral-default-hover active:text-ds-text-neutral-default-default [&>svg]:text-ds-text-neutral-default-default h-7 min-w-0 gap-2 rounded-md px-2 [&>svg]:size-4 flex -translate-x-px items-center overflow-hidden outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0',
|
||||
'data-[active=true]:bg-ds-bg-neutral-default-hover data-[active=true]:text-ds-text-neutral-default-default',
|
||||
size === 'sm' && 'text-xs',
|
||||
size === 'md' && 'text-sm',
|
||||
'group-data-[collapsible=icon]:hidden',
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
|||
toastOptions={{
|
||||
classNames: {
|
||||
toast:
|
||||
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||
description: 'group-[.toast]:text-muted-foreground',
|
||||
'group toast group-[.toaster]:bg-ds-bg-neutral-subtle-default group-[.toaster]:text-ds-text-neutral-default-default group-[.toaster]:border-ds-border-neutral-default-default group-[.toaster]:shadow-lg',
|
||||
description: 'group-[.toast]:text-ds-text-neutral-muted-default',
|
||||
actionButton:
|
||||
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
||||
'group-[.toast]:bg-ds-bg-brand-default-default group-[.toast]:text-ds-text-brand-inverse-default',
|
||||
cancelButton:
|
||||
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
||||
'group-[.toast]:bg-ds-bg-neutral-muted-default group-[.toast]:text-ds-text-neutral-muted-default',
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ const Switch = React.forwardRef<
|
|||
>(({ className, size = 'default', style, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
'focus-visible:ring-ring focus-visible:ring-offset-background peer data-[state=checked]:bg-switch-on-fill-track-fill data-[state=unchecked]:bg-switch-off-fill-track-fill inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'peer focus-visible:ring-ds-ring-brand-default-focus focus-visible:ring-offset-ds-bg-neutral-subtle-default data-[state=checked]:bg-ds-bg-status-completed-default-default data-[state=unchecked]:bg-ds-bg-neutral-default-default inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
sizeClasses[size].root,
|
||||
className
|
||||
)}
|
||||
|
|
@ -53,7 +53,7 @@ const Switch = React.forwardRef<
|
|||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
'bg-switch-on-fill-thumb-fill data-[state=unchecked]:translate-x-0 pointer-events-none block rounded-full shadow-none ring-0 transition-transform',
|
||||
'bg-ds-text-brand-inverse-default data-[state=unchecked]:translate-x-0 pointer-events-none block rounded-full shadow-none ring-0 transition-transform',
|
||||
sizeClasses[size].thumb
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
import * as React from "react"
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
|
|
@ -9,20 +23,20 @@ const Table = React.forwardRef<
|
|||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
className={cn('text-sm w-full caption-bottom', className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
Table.displayName = "Table"
|
||||
));
|
||||
Table.displayName = 'Table';
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
))
|
||||
TableHeader.displayName = "TableHeader"
|
||||
<thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
|
||||
));
|
||||
TableHeader.displayName = 'TableHeader';
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
|
|
@ -30,11 +44,11 @@ const TableBody = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
className={cn('[&_tr:last-child]:border-0', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableBody.displayName = "TableBody"
|
||||
));
|
||||
TableBody.displayName = 'TableBody';
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
|
|
@ -43,13 +57,13 @@ const TableFooter = React.forwardRef<
|
|||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||
'bg-ds-bg-neutral-muted-default/50 font-medium border-t [&>tr]:last:border-b-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableFooter.displayName = "TableFooter"
|
||||
));
|
||||
TableFooter.displayName = 'TableFooter';
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
|
|
@ -58,13 +72,13 @@ const TableRow = React.forwardRef<
|
|||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
'hover:bg-ds-bg-neutral-muted-default/50 data-[state=selected]:bg-ds-bg-neutral-muted-default border-b transition-colors',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableRow.displayName = "TableRow"
|
||||
));
|
||||
TableRow.displayName = 'TableRow';
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
|
|
@ -73,13 +87,13 @@ const TableHead = React.forwardRef<
|
|||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
'h-10 px-2 font-medium text-ds-text-neutral-muted-default [&:has([role=checkbox])]:pr-0 text-left align-middle [&>[role=checkbox]]:translate-y-[2px]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableHead.displayName = "TableHead"
|
||||
));
|
||||
TableHead.displayName = 'TableHead';
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
|
|
@ -88,13 +102,13 @@ const TableCell = React.forwardRef<
|
|||
<td
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
'p-2 [&:has([role=checkbox])]:pr-0 align-middle [&>[role=checkbox]]:translate-y-[2px]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCell.displayName = "TableCell"
|
||||
));
|
||||
TableCell.displayName = 'TableCell';
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
|
|
@ -102,19 +116,19 @@ const TableCaption = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
className={cn('mt-4 text-sm text-ds-text-neutral-muted-default', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCaption.displayName = "TableCaption"
|
||||
));
|
||||
TableCaption.displayName = 'TableCaption';
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ const TabsList = React.forwardRef<
|
|||
className={cn(
|
||||
variant === 'outline'
|
||||
? 'gap-0 bg-ds-bg-neutral-muted-disabled p-0 relative inline-flex items-center justify-center'
|
||||
: 'rounded-xl border-menutabs-border-default bg-ds-bg-neutral-strong-default p-0.5 inline-flex items-center justify-center border border-solid',
|
||||
: 'rounded-xl border-ds-border-neutral-default-default bg-ds-bg-neutral-strong-default p-0.5 inline-flex items-center justify-center border border-solid',
|
||||
'data-[orientation=vertical]:flex data-[orientation=vertical]:h-full data-[orientation=vertical]:w-full data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch data-[orientation=vertical]:justify-start',
|
||||
className
|
||||
)}
|
||||
|
|
@ -158,7 +158,7 @@ const TabsTrigger = React.forwardRef<
|
|||
className={cn(
|
||||
variant === 'outline'
|
||||
? 'gap-2 px-4 py-3 !text-body-sm !font-semibold text-ds-text-neutral-default-default data-[state=active]:bg-ds-bg-neutral-muted-disabled data-[state=active]:!font-bold [&_svg]:text-ds-icon-neutral-default-default relative flex cursor-pointer flex-row items-center justify-center bg-transparent transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50'
|
||||
: 'ring-offset-background focus-visible:ring-ring gap-1 rounded-xl bg-menutabs-fill-default px-2 py-1 text-body-sm font-semibold data-[state=active]:bg-menutabs-fill-active data-[state=active]:text-menutabs-text-active data-[state=active]:shadow-sm inline-flex items-center justify-center whitespace-nowrap transition-all focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',
|
||||
: 'ring-offset-ds-bg-neutral-subtle-default focus-visible:ring-ds-ring-brand-default-focus gap-1 rounded-xl bg-ds-bg-neutral-strong-default px-2 py-1 text-body-sm font-semibold data-[state=active]:bg-ds-bg-neutral-default-default data-[state=active]:text-ds-text-neutral-default-default data-[state=active]:shadow-sm inline-flex items-center justify-center whitespace-nowrap transition-all focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
data-variant={variant}
|
||||
|
|
@ -177,7 +177,7 @@ const TabsContent = React.forwardRef<
|
|||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'ring-offset-background focus-visible:ring-ring mt-2 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none',
|
||||
'ring-offset-ds-bg-neutral-subtle-default focus-visible:ring-ds-ring-brand-default-focus mt-2 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import * as React from 'react';
|
|||
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
normalizeUiTone,
|
||||
DEFAULT_EMPHASIS_BY_VARIANT,
|
||||
type UiEmphasis,
|
||||
type UiTone,
|
||||
type UiToneInput,
|
||||
|
|
@ -26,120 +26,316 @@ import {
|
|||
} from './semanticProps';
|
||||
import { mergeAliasStyles, tagTokenAliases } from './tokenAliases';
|
||||
|
||||
/** User-friendly tone aliases map to {@link UiTone}. */
|
||||
export type TagToneInput = UiToneInput | 'info' | 'caution';
|
||||
|
||||
export type TagVariant = UiVariant;
|
||||
export type TagTone = UiTone;
|
||||
export type TagEmphasis = UiEmphasis;
|
||||
|
||||
type TagStyleTone = 'default' | 'success' | 'error' | 'information' | 'warning';
|
||||
type TagEmphasisMatrix = 'subtle' | 'muted' | 'default' | 'strong';
|
||||
|
||||
const TAG_INVERSE =
|
||||
'border-transparent bg-ds-bg-neutral-subtle-default !text-ds-text-neutral-default-default';
|
||||
|
||||
export function normalizeTagTone(tone?: TagToneInput): UiTone {
|
||||
if (!tone || tone === 'default') return 'neutral';
|
||||
if (tone === 'info') return 'information';
|
||||
if (tone === 'caution') return 'error';
|
||||
return tone;
|
||||
}
|
||||
|
||||
function toStyleTone(tone: UiTone): TagStyleTone {
|
||||
return tone === 'neutral' ? 'default' : tone;
|
||||
}
|
||||
|
||||
function resolveTagAxes(
|
||||
variant: TagVariant | undefined,
|
||||
tone: TagToneInput | undefined,
|
||||
emphasis: TagEmphasis | undefined
|
||||
): {
|
||||
variant: UiVariant;
|
||||
tone: UiTone;
|
||||
emphasis: TagEmphasis;
|
||||
} {
|
||||
const base = variant ?? 'primary';
|
||||
return {
|
||||
variant: base,
|
||||
tone: normalizeTagTone(tone),
|
||||
emphasis: emphasis ?? DEFAULT_EMPHASIS_BY_VARIANT[base],
|
||||
};
|
||||
}
|
||||
|
||||
/** Filled chips — emphasis ramps from quiet surface to solid semantic fill. */
|
||||
const TAG_PRIMARY: Record<TagStyleTone, Record<TagEmphasisMatrix, string>> = {
|
||||
default: {
|
||||
subtle:
|
||||
'border-transparent bg-ds-bg-neutral-subtle-default !text-ds-text-neutral-default-default',
|
||||
muted:
|
||||
'border-transparent bg-ds-bg-neutral-subtle-default !text-ds-text-neutral-muted-default',
|
||||
default:
|
||||
'border-transparent bg-ds-bg-neutral-default-default !text-ds-text-neutral-default-default',
|
||||
strong:
|
||||
'border-transparent bg-ds-bg-brand-default-default !text-ds-text-brand-inverse-default',
|
||||
},
|
||||
success: {
|
||||
subtle:
|
||||
'border-transparent bg-ds-bg-status-completed-subtle-default !text-ds-text-status-completed-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-ds-bg-neutral-subtle-default !text-ds-text-success-strong-default',
|
||||
default:
|
||||
'border-transparent bg-ds-bg-success-subtle-default !text-ds-text-success-strong-default',
|
||||
strong:
|
||||
'border-transparent bg-ds-bg-success-default-default !text-ds-text-success-inverse-default',
|
||||
},
|
||||
error: {
|
||||
subtle:
|
||||
'border-transparent bg-ds-bg-status-error-subtle-default !text-ds-text-status-error-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-ds-bg-neutral-subtle-default !text-ds-text-error-strong-default',
|
||||
default:
|
||||
'border-transparent bg-ds-bg-error-subtle-default !text-ds-text-error-strong-default',
|
||||
strong:
|
||||
'border-transparent bg-ds-bg-error-default-default !text-ds-text-error-inverse-default',
|
||||
},
|
||||
information: {
|
||||
subtle:
|
||||
'border-transparent bg-ds-bg-status-splitting-subtle-default !text-ds-text-status-splitting-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-ds-bg-neutral-subtle-default !text-ds-text-information-strong-default',
|
||||
default:
|
||||
'border-transparent bg-ds-bg-information-subtle-default !text-ds-text-information-strong-default',
|
||||
strong:
|
||||
'border-transparent bg-ds-bg-information-default-default !text-ds-text-information-inverse-default',
|
||||
},
|
||||
warning: {
|
||||
subtle:
|
||||
'border-transparent bg-ds-bg-status-pending-subtle-default !text-ds-text-status-pending-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-ds-bg-neutral-subtle-default !text-ds-text-warning-strong-default',
|
||||
default:
|
||||
'border-transparent bg-ds-bg-warning-subtle-default !text-ds-text-warning-strong-default',
|
||||
strong:
|
||||
'border-transparent bg-ds-bg-warning-default-default !text-ds-text-warning-inverse-default',
|
||||
},
|
||||
};
|
||||
|
||||
/** Softer bordered / filled secondary surface. */
|
||||
const TAG_SECONDARY: Record<TagStyleTone, Record<TagEmphasisMatrix, string>> = {
|
||||
default: {
|
||||
subtle:
|
||||
'border-ds-border-neutral-muted-default bg-ds-bg-neutral-subtle-default !text-ds-text-neutral-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-ds-bg-neutral-default-default !text-ds-text-neutral-muted-default',
|
||||
default:
|
||||
'border-ds-border-neutral-default-default bg-ds-bg-neutral-subtle-default !text-ds-text-neutral-default-default',
|
||||
strong:
|
||||
'border-ds-border-neutral-strong-default bg-ds-bg-neutral-default-default !text-ds-text-neutral-default-default',
|
||||
},
|
||||
success: {
|
||||
subtle:
|
||||
'border-ds-border-success-muted-default bg-ds-bg-success-subtle-default !text-ds-text-status-completed-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-ds-bg-neutral-subtle-default !text-ds-text-success-strong-default',
|
||||
default:
|
||||
'border-ds-border-success-default-default bg-ds-bg-success-subtle-default !text-ds-text-success-strong-default',
|
||||
strong:
|
||||
'border-ds-border-success-default-default bg-ds-bg-success-default-default !text-ds-text-success-inverse-default',
|
||||
},
|
||||
error: {
|
||||
subtle:
|
||||
'border-ds-border-error-muted-default bg-ds-bg-error-subtle-default !text-ds-text-status-error-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-ds-bg-neutral-subtle-default !text-ds-text-error-strong-default',
|
||||
default:
|
||||
'border-ds-border-error-default-default bg-ds-bg-error-subtle-default !text-ds-text-error-strong-default',
|
||||
strong:
|
||||
'border-ds-border-error-default-default bg-ds-bg-error-default-default !text-ds-text-error-inverse-default',
|
||||
},
|
||||
information: {
|
||||
subtle:
|
||||
'border-ds-border-information-muted-default bg-ds-bg-information-subtle-default !text-ds-text-status-splitting-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-ds-bg-neutral-subtle-default !text-ds-text-information-strong-default',
|
||||
default:
|
||||
'border-ds-border-information-default-default bg-ds-bg-information-subtle-default !text-ds-text-information-strong-default',
|
||||
strong:
|
||||
'border-ds-border-information-default-default bg-ds-bg-information-default-default !text-ds-text-information-inverse-default',
|
||||
},
|
||||
warning: {
|
||||
subtle:
|
||||
'border-ds-border-warning-muted-default bg-ds-bg-warning-subtle-default !text-ds-text-status-pending-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-ds-bg-neutral-subtle-default !text-ds-text-warning-strong-default',
|
||||
default:
|
||||
'border-ds-border-warning-default-default bg-ds-bg-warning-subtle-default !text-ds-text-warning-strong-default',
|
||||
strong:
|
||||
'border-ds-border-warning-default-default bg-ds-bg-warning-default-default !text-ds-text-warning-inverse-default',
|
||||
},
|
||||
};
|
||||
|
||||
/** Transparent fill; semantic border. */
|
||||
const TAG_OUTLINE: Record<TagStyleTone, Record<TagEmphasisMatrix, string>> = {
|
||||
default: {
|
||||
subtle:
|
||||
'border-ds-border-neutral-muted-default bg-transparent !text-ds-text-neutral-muted-default',
|
||||
muted:
|
||||
'border-ds-border-neutral-muted-default bg-transparent !text-ds-text-neutral-muted-default',
|
||||
default:
|
||||
'border-ds-border-neutral-strong-default bg-transparent !text-ds-text-neutral-default-default',
|
||||
strong:
|
||||
'border-ds-border-neutral-strong-default bg-transparent !text-ds-text-neutral-default-default font-semibold',
|
||||
},
|
||||
success: {
|
||||
subtle:
|
||||
'border-ds-border-success-muted-default bg-transparent !text-ds-text-status-completed-muted-default',
|
||||
muted:
|
||||
'border-ds-border-neutral-default-default bg-transparent !text-ds-text-success-strong-default',
|
||||
default:
|
||||
'border-ds-border-success-default-default bg-transparent !text-ds-text-success-strong-default',
|
||||
strong:
|
||||
'border-ds-border-success-strong-default bg-transparent !text-ds-text-success-strong-default font-semibold',
|
||||
},
|
||||
error: {
|
||||
subtle:
|
||||
'border-ds-border-error-muted-default bg-transparent !text-ds-text-status-error-muted-default',
|
||||
muted:
|
||||
'border-ds-border-neutral-default-default bg-transparent !text-ds-text-error-strong-default',
|
||||
default:
|
||||
'border-ds-border-error-default-default bg-transparent !text-ds-text-error-strong-default',
|
||||
strong:
|
||||
'border-ds-border-error-strong-default bg-transparent !text-ds-text-error-strong-default font-semibold',
|
||||
},
|
||||
information: {
|
||||
subtle:
|
||||
'border-ds-border-information-muted-default bg-transparent !text-ds-text-status-splitting-muted-default',
|
||||
muted:
|
||||
'border-ds-border-neutral-default-default bg-transparent !text-ds-text-information-strong-default',
|
||||
default:
|
||||
'border-ds-border-information-default-default bg-transparent !text-ds-text-information-strong-default',
|
||||
strong:
|
||||
'border-ds-border-information-strong-default bg-transparent !text-ds-text-information-strong-default font-semibold',
|
||||
},
|
||||
warning: {
|
||||
subtle:
|
||||
'border-ds-border-warning-muted-default bg-transparent !text-ds-text-status-pending-muted-default',
|
||||
muted:
|
||||
'border-ds-border-neutral-default-default bg-transparent !text-ds-text-warning-strong-default',
|
||||
default:
|
||||
'border-ds-border-warning-default-default bg-transparent !text-ds-text-warning-strong-default',
|
||||
strong:
|
||||
'border-ds-border-warning-strong-default bg-transparent !text-ds-text-warning-strong-default font-semibold',
|
||||
},
|
||||
};
|
||||
|
||||
/** No border; text-first. */
|
||||
const TAG_GHOST: Record<TagStyleTone, Record<TagEmphasisMatrix, string>> = {
|
||||
default: {
|
||||
subtle:
|
||||
'border-transparent bg-transparent !text-ds-text-neutral-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-transparent !text-ds-text-neutral-muted-default',
|
||||
default:
|
||||
'border-transparent bg-transparent !text-ds-text-neutral-default-default',
|
||||
strong:
|
||||
'border-transparent bg-transparent !text-ds-text-neutral-default-default font-semibold',
|
||||
},
|
||||
success: {
|
||||
subtle:
|
||||
'border-transparent bg-transparent !text-ds-text-status-completed-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-transparent opacity-80 !text-ds-text-success-strong-default',
|
||||
default:
|
||||
'border-transparent bg-transparent !text-ds-text-success-strong-default',
|
||||
strong:
|
||||
'border-transparent bg-transparent !text-ds-text-success-strong-default font-semibold',
|
||||
},
|
||||
error: {
|
||||
subtle:
|
||||
'border-transparent bg-transparent !text-ds-text-status-error-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-transparent opacity-80 !text-ds-text-error-strong-default',
|
||||
default:
|
||||
'border-transparent bg-transparent !text-ds-text-error-strong-default',
|
||||
strong:
|
||||
'border-transparent bg-transparent !text-ds-text-error-strong-default font-semibold',
|
||||
},
|
||||
information: {
|
||||
subtle:
|
||||
'border-transparent bg-transparent !text-ds-text-status-splitting-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-transparent opacity-80 !text-ds-text-information-strong-default',
|
||||
default:
|
||||
'border-transparent bg-transparent !text-ds-text-information-strong-default',
|
||||
strong:
|
||||
'border-transparent bg-transparent !text-ds-text-information-strong-default font-semibold',
|
||||
},
|
||||
warning: {
|
||||
subtle:
|
||||
'border-transparent bg-transparent !text-ds-text-status-pending-muted-default',
|
||||
muted:
|
||||
'border-transparent bg-transparent opacity-80 !text-ds-text-warning-strong-default',
|
||||
default:
|
||||
'border-transparent bg-transparent !text-ds-text-warning-strong-default',
|
||||
strong:
|
||||
'border-transparent bg-transparent !text-ds-text-warning-strong-default font-semibold',
|
||||
},
|
||||
};
|
||||
|
||||
const TAG_BY_VARIANT: Record<
|
||||
UiVariant,
|
||||
Record<TagStyleTone, Record<TagEmphasisMatrix, string>>
|
||||
> = {
|
||||
primary: TAG_PRIMARY,
|
||||
secondary: TAG_SECONDARY,
|
||||
outline: TAG_OUTLINE,
|
||||
ghost: TAG_GHOST,
|
||||
};
|
||||
|
||||
function tagChromeClasses(
|
||||
variant: UiVariant,
|
||||
styleTone: TagStyleTone,
|
||||
emphasis: TagEmphasis
|
||||
): string {
|
||||
if (emphasis === 'inverse') {
|
||||
return TAG_INVERSE;
|
||||
}
|
||||
const em = emphasis as TagEmphasisMatrix;
|
||||
return TAG_BY_VARIANT[variant][styleTone][em];
|
||||
}
|
||||
|
||||
const tagVariants = cva(
|
||||
'inline-flex justify-start items-center leading-relaxed',
|
||||
'inline-flex justify-start items-center border border-solid leading-relaxed transition-colors duration-150',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary: 'bg-tag-fill-info text-[var(--tag-foreground-info)]',
|
||||
info: 'bg-tag-fill-info !text-[var(--tag-foreground-info)]',
|
||||
success: 'bg-tag-fill-success !text-[var(--tag-foreground-success)]',
|
||||
cuation: 'bg-tag-fill-cuation !text-[var(--tag-foreground-cuation)]',
|
||||
warning: 'bg-tag-fill-warning !text-[var(--tag-foreground-warning)]',
|
||||
default: 'bg-tag-fill-default !text-[var(--tag-foreground-default)]',
|
||||
ghost: 'bg-transparent !text-[var(--tag-foreground-default)]',
|
||||
},
|
||||
size: {
|
||||
xs: 'px-2 py-0.5 gap-1 text-body-xs font-bold leading-tight [&_svg]:size-[10px] rounded-full',
|
||||
sm: 'px-2 py-1.5 gap-1 text-body-xs font-bold leading-tight [&_svg]:size-[16px] rounded-full',
|
||||
md: 'px-3 py-1.5 gap-2 text-body-md font-semibold leading-relaxed [&_svg]:size-[20px] rounded-xl',
|
||||
xxs: 'gap-0.5 rounded-full px-1.5 py-px text-label-xs font-medium [&_svg]:size-[12px]',
|
||||
xs: 'gap-1 rounded-full px-2 py-0.5 text-label-xs font-medium [&_svg]:size-[14px]',
|
||||
sm: 'gap-1 rounded-full px-2 py-1 text-label-sm font-medium [&_svg]:size-[16px]',
|
||||
md: 'gap-1.5 rounded-full px-2.5 py-1 text-label-md font-medium [&_svg]:size-[18px]',
|
||||
lg: 'gap-2 rounded-full px-3 py-1.5 text-label-md font-semibold [&_svg]:size-[20px]',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'primary',
|
||||
size: 'sm',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type TagVisualVariant = NonNullable<
|
||||
VariantProps<typeof tagVariants>['variant']
|
||||
>;
|
||||
type TagSize = NonNullable<VariantProps<typeof tagVariants>['size']>;
|
||||
|
||||
export type TagVariant = UiVariant;
|
||||
export type TagEmphasis = UiEmphasis;
|
||||
export type TagTone = UiTone;
|
||||
export type TagLegacyVariant = TagVisualVariant;
|
||||
|
||||
const TONE_TO_TAG_VARIANT: Record<TagTone, TagVisualVariant> = {
|
||||
neutral: 'default',
|
||||
success: 'success',
|
||||
error: 'cuation',
|
||||
information: 'info',
|
||||
warning: 'warning',
|
||||
};
|
||||
|
||||
const OUTLINE_BORDER_BY_TONE: Record<TagTone, string> = {
|
||||
neutral: 'border-ds-border-neutral-default-default',
|
||||
success: 'border-ds-border-success-default-default',
|
||||
error: 'border-ds-border-error-default-default',
|
||||
information: 'border-ds-border-information-default-default',
|
||||
warning: 'border-ds-border-warning-default-default',
|
||||
};
|
||||
|
||||
function resolveTagVisual(
|
||||
variant: TagVariant | TagLegacyVariant | undefined,
|
||||
tone: UiToneInput | undefined,
|
||||
emphasis: TagEmphasis | undefined
|
||||
): {
|
||||
visualVariant: TagVisualVariant;
|
||||
tone: TagTone;
|
||||
emphasis: TagEmphasis;
|
||||
outlineBorderClass: string | null;
|
||||
} {
|
||||
const normalizedTone = normalizeUiTone(tone);
|
||||
const resolvedEmphasis = emphasis ?? 'default';
|
||||
const v = variant ?? 'primary';
|
||||
|
||||
// Legacy dedicated semantic variants remain valid.
|
||||
if (
|
||||
v === 'info' ||
|
||||
v === 'success' ||
|
||||
v === 'cuation' ||
|
||||
v === 'warning' ||
|
||||
v === 'default'
|
||||
) {
|
||||
return {
|
||||
visualVariant: v,
|
||||
tone: normalizedTone,
|
||||
emphasis: resolvedEmphasis,
|
||||
outlineBorderClass: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (resolvedEmphasis === 'inverse' || v === 'ghost') {
|
||||
return {
|
||||
visualVariant: 'ghost',
|
||||
tone: normalizedTone,
|
||||
emphasis: resolvedEmphasis,
|
||||
outlineBorderClass: null,
|
||||
};
|
||||
}
|
||||
|
||||
const visualVariant = TONE_TO_TAG_VARIANT[normalizedTone];
|
||||
if (v === 'outline') {
|
||||
return {
|
||||
visualVariant,
|
||||
tone: normalizedTone,
|
||||
emphasis: resolvedEmphasis,
|
||||
outlineBorderClass: OUTLINE_BORDER_BY_TONE[normalizedTone],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
visualVariant,
|
||||
tone: normalizedTone,
|
||||
emphasis: resolvedEmphasis,
|
||||
outlineBorderClass: null,
|
||||
};
|
||||
}
|
||||
|
||||
interface TagProps extends React.ComponentProps<'div'> {
|
||||
asChild?: boolean;
|
||||
variant?: TagVariant | TagLegacyVariant;
|
||||
/** Chrome pattern: filled, softer fill, outline, or text-only. */
|
||||
variant?: TagVariant;
|
||||
/** Visual weight within the chosen `variant`. */
|
||||
emphasis?: TagEmphasis;
|
||||
tone?: UiToneInput;
|
||||
/**
|
||||
* Semantic palette. Shorthands: `info` → information, `caution` → error; `default` → neutral.
|
||||
* Omitted tone reads as neutral.
|
||||
*/
|
||||
tone?: TagToneInput;
|
||||
size?: TagSize;
|
||||
text?: string;
|
||||
icon?: React.ReactNode;
|
||||
|
|
@ -149,9 +345,9 @@ const Tag = React.forwardRef<HTMLDivElement, TagProps>(
|
|||
(
|
||||
{
|
||||
className,
|
||||
variant,
|
||||
emphasis,
|
||||
tone,
|
||||
variant: variantProp,
|
||||
emphasis: emphasisProp,
|
||||
tone: toneProp,
|
||||
size,
|
||||
asChild = false,
|
||||
text,
|
||||
|
|
@ -163,27 +359,22 @@ const Tag = React.forwardRef<HTMLDivElement, TagProps>(
|
|||
ref
|
||||
) => {
|
||||
const Comp = asChild ? Slot : 'div';
|
||||
const {
|
||||
visualVariant,
|
||||
tone: resolvedTone,
|
||||
emphasis: resolvedEmphasis,
|
||||
outlineBorderClass,
|
||||
} = resolveTagVisual(variant, tone, emphasis);
|
||||
const { variant, tone, emphasis } = resolveTagAxes(
|
||||
variantProp,
|
||||
toneProp,
|
||||
emphasisProp
|
||||
);
|
||||
const styleTone = toStyleTone(tone);
|
||||
const chrome = tagChromeClasses(variant, styleTone, emphasis);
|
||||
|
||||
// When asChild is true, just pass through the child without wrapping
|
||||
if (asChild) {
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
data-variant={variant ?? 'primary'}
|
||||
data-tone={resolvedTone}
|
||||
data-emphasis={resolvedEmphasis}
|
||||
className={cn(
|
||||
tagVariants({ variant: visualVariant, size, className }),
|
||||
outlineBorderClass
|
||||
? ['border', 'border-solid', outlineBorderClass]
|
||||
: null
|
||||
)}
|
||||
data-variant={variant}
|
||||
data-tone={tone}
|
||||
data-emphasis={emphasis}
|
||||
className={cn(tagVariants({ size }), chrome, className)}
|
||||
style={mergeAliasStyles(tagTokenAliases, style)}
|
||||
{...props}
|
||||
>
|
||||
|
|
@ -192,19 +383,13 @@ const Tag = React.forwardRef<HTMLDivElement, TagProps>(
|
|||
);
|
||||
}
|
||||
|
||||
// Normal rendering when asChild is false
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
data-variant={variant ?? 'primary'}
|
||||
data-tone={resolvedTone}
|
||||
data-emphasis={resolvedEmphasis}
|
||||
className={cn(
|
||||
tagVariants({ variant: visualVariant, size, className }),
|
||||
outlineBorderClass
|
||||
? ['border', 'border-solid', outlineBorderClass]
|
||||
: null
|
||||
)}
|
||||
data-variant={variant}
|
||||
data-tone={tone}
|
||||
data-emphasis={emphasis}
|
||||
className={cn(tagVariants({ size }), chrome, className)}
|
||||
style={mergeAliasStyles(tagTokenAliases, style)}
|
||||
{...props}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -54,44 +54,48 @@ function resolveStateClasses(state: TextareaState | undefined) {
|
|||
if (state === 'disabled') {
|
||||
return {
|
||||
container: 'opacity-50 cursor-not-allowed',
|
||||
field: 'border-transparent bg-input-bg-default text-input-text-default',
|
||||
placeholder: 'text-input-label-default',
|
||||
field:
|
||||
'border-transparent bg-ds-bg-neutral-default-default text-ds-text-neutral-default-default',
|
||||
placeholder: 'text-ds-text-neutral-muted-default',
|
||||
};
|
||||
}
|
||||
if (state === 'hover') {
|
||||
return {
|
||||
container: '',
|
||||
field: 'border-transparent bg-input-bg-default text-input-text-default',
|
||||
placeholder: 'text-input-label-default',
|
||||
field:
|
||||
'border-transparent bg-ds-bg-neutral-default-default text-ds-text-neutral-default-default',
|
||||
placeholder: 'text-ds-text-neutral-muted-default',
|
||||
};
|
||||
}
|
||||
if (state === 'input') {
|
||||
return {
|
||||
container: '',
|
||||
field: 'border-transparent bg-input-bg-input text-input-text-focus',
|
||||
placeholder: 'text-input-label-default',
|
||||
field:
|
||||
'border-transparent bg-ds-bg-neutral-strong-default text-ds-text-neutral-default-default',
|
||||
placeholder: 'text-ds-text-neutral-muted-default',
|
||||
};
|
||||
}
|
||||
if (state === 'error') {
|
||||
return {
|
||||
container: '',
|
||||
field:
|
||||
'border-input-border-cuation bg-input-bg-default text-ds-text-neutral-default-default',
|
||||
placeholder: 'text-input-label-default',
|
||||
'border-ds-border-status-error-default-default bg-ds-bg-neutral-default-default text-ds-text-neutral-default-default',
|
||||
placeholder: 'text-ds-text-neutral-muted-default',
|
||||
};
|
||||
}
|
||||
if (state === 'success') {
|
||||
return {
|
||||
container: '',
|
||||
field:
|
||||
'border-input-border-success bg-input-bg-confirm text-ds-text-neutral-default-default',
|
||||
placeholder: 'text-input-label-default',
|
||||
'border-ds-border-status-completed-default-default bg-ds-bg-status-completed-subtle-default text-ds-text-neutral-default-default',
|
||||
placeholder: 'text-ds-text-neutral-muted-default',
|
||||
};
|
||||
}
|
||||
return {
|
||||
container: '',
|
||||
field: 'border-transparent bg-input-bg-default text-input-text-default',
|
||||
placeholder: 'text-input-label-default/10',
|
||||
field:
|
||||
'border-transparent bg-ds-bg-neutral-default-default text-ds-text-neutral-default-default',
|
||||
placeholder: 'text-ds-text-neutral-muted-default/10',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +130,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, BaseTextareaProps>(
|
|||
<textarea
|
||||
data-scrollbar="ui-textarea"
|
||||
className={cn(
|
||||
'border-input placeholder:text-ds-text-neutral-muted-default/20 focus-visible:ring-ring rounded-lg py-2 pl-3 pr-3 text-body-sm shadow-sm flex min-h-[60px] w-full border bg-transparent [scrollbar-gutter:stable] focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'border-ds-border-neutral-default-default placeholder:text-ds-text-neutral-muted-default/20 focus-visible:ring-ds-ring-brand-default-focus rounded-lg py-2 pl-3 pr-3 text-body-sm shadow-sm flex min-h-[60px] w-full border bg-transparent [scrollbar-gutter:stable] focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
style={mergeAliasStyles(formControlTokenAliases, {
|
||||
|
|
@ -195,7 +199,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, BaseTextareaProps>(
|
|||
// Only apply hover/focus visuals when not in error or success state
|
||||
state !== 'error' &&
|
||||
state !== 'success' &&
|
||||
'focus-within:bg-input-bg-input focus-within:ring-input-border-focus hover:bg-input-bg-hover hover:ring-input-border-hover focus-within:ring-1 focus-within:ring-offset-0 hover:ring-1 hover:ring-offset-0',
|
||||
'focus-within:bg-ds-bg-neutral-strong-default focus-within:ring-ds-ring-brand-default-focus hover:bg-ds-bg-neutral-default-hover hover:ring-ds-ring-neutral-strong-default focus-within:ring-1 focus-within:ring-offset-0 hover:ring-1 hover:ring-offset-0',
|
||||
stateCls.field,
|
||||
sizeClasses[size]
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ import * as React from 'react';
|
|||
import { cn } from '@/lib/utils';
|
||||
|
||||
const toggleVariants = cva(
|
||||
'inline-flex items-center justify-center gap-2 rounded-lg text-sm font-medium transition-colors border border-transparent border-solid hover:bg-menubutton-fill-active focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:border-menubutton-border-active data-[state=on]:bg-menubutton-fill-active data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
'inline-flex items-center justify-center gap-2 rounded-lg text-sm font-medium transition-colors border border-transparent border-solid hover:bg-ds-bg-neutral-default-default focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ds-ring-brand-default-focus disabled:pointer-events-none disabled:opacity-50 data-[state=on]:border-ds-border-brand-default-focus data-[state=on]:bg-ds-bg-neutral-default-default data-[state=on]:text-ds-text-neutral-default-default [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-transparent',
|
||||
outline:
|
||||
'border border-input bg-transparent shadow-sm hover:bg-white-100% hover:text-accent-foreground',
|
||||
'border border-ds-border-neutral-default-default bg-transparent shadow-sm hover:bg-ds-bg-neutral-inverse-default hover:text-ds-text-neutral-default-default',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-2 min-w-9',
|
||||
|
|
|
|||
|
|
@ -36,16 +36,15 @@ export const formControlTokenAliases = asCssVarMap({
|
|||
'--input-border-hover': 'var(--ds-border-neutral-strong-default)',
|
||||
'--input-border-focus': 'var(--ds-border-brand-default-focus)',
|
||||
'--input-border-success': 'var(--ds-border-status-completed-default-default)',
|
||||
'--input-border-cuation': 'var(--ds-border-status-error-default-default)',
|
||||
'--input-border-caution': 'var(--ds-border-status-error-default-default)',
|
||||
'--input-text-default': 'var(--ds-text-neutral-default-default)',
|
||||
'--input-text-focus': 'var(--ds-text-neutral-default-default)',
|
||||
'--input-label-default': 'var(--ds-text-neutral-muted-default)',
|
||||
'--surface-disabled': 'var(--ds-bg-neutral-muted-disabled)',
|
||||
'--text-heading': 'var(--ds-text-neutral-default-default)',
|
||||
'--text-body': 'var(--ds-text-neutral-default-default)',
|
||||
'--text-label': 'var(--ds-text-neutral-muted-default)',
|
||||
'--text-success': 'var(--ds-text-status-completed-strong-default)',
|
||||
'--text-cuation': 'var(--ds-text-status-error-strong-default)',
|
||||
'--text-caution': 'var(--ds-text-status-error-strong-default)',
|
||||
'--text-information': 'var(--ds-text-status-splitting-strong-default)',
|
||||
'--icon-primary': 'var(--ds-icon-neutral-default-default)',
|
||||
'--menutabs-fill-hover': 'var(--ds-bg-neutral-default-hover)',
|
||||
|
|
@ -70,14 +69,14 @@ export const buttonTokenAliases = asCssVarMap({
|
|||
'--button-secondary-text-active': 'var(--ds-text-neutral-default-default)',
|
||||
'--button-secondary-text-disabled': 'var(--ds-text-neutral-muted-disabled)',
|
||||
|
||||
'--button-tertiery-fill-default': 'var(--ds-bg-neutral-subtle-default)',
|
||||
'--button-tertiery-fill-hover': 'var(--ds-bg-neutral-default-hover)',
|
||||
'--button-tertiery-fill-active': 'var(--ds-bg-neutral-default-active)',
|
||||
'--button-tertiery-fill-disabled': 'var(--ds-bg-neutral-muted-disabled)',
|
||||
'--button-tertiery-text-default': 'var(--ds-text-neutral-default-default)',
|
||||
'--button-tertiery-text-hover': 'var(--ds-text-neutral-default-default)',
|
||||
'--button-tertiery-text-active': 'var(--ds-text-neutral-default-default)',
|
||||
'--button-tertiery-text-disabled': 'var(--ds-text-neutral-muted-disabled)',
|
||||
'--button-tertiary-fill-default': 'var(--ds-bg-neutral-subtle-default)',
|
||||
'--button-tertiary-fill-hover': 'var(--ds-bg-neutral-default-hover)',
|
||||
'--button-tertiary-fill-active': 'var(--ds-bg-neutral-default-active)',
|
||||
'--button-tertiary-fill-disabled': 'var(--ds-bg-neutral-muted-disabled)',
|
||||
'--button-tertiary-text-default': 'var(--ds-text-neutral-default-default)',
|
||||
'--button-tertiary-text-hover': 'var(--ds-text-neutral-default-default)',
|
||||
'--button-tertiary-text-active': 'var(--ds-text-neutral-default-default)',
|
||||
'--button-tertiary-text-disabled': 'var(--ds-text-neutral-muted-disabled)',
|
||||
|
||||
'--button-transparent-fill-default': 'transparent',
|
||||
'--button-transparent-fill-hover': 'var(--ds-bg-neutral-default-hover)',
|
||||
|
|
@ -94,8 +93,8 @@ export const buttonTokenAliases = asCssVarMap({
|
|||
'--fill-fill-success-hover': 'var(--ds-bg-status-completed-subtle-hover)',
|
||||
'--fill-fill-success-active': 'var(--ds-bg-status-completed-default-default)',
|
||||
|
||||
'--button-fill-cuation': 'var(--ds-bg-status-error-default-default)',
|
||||
'--button-fill-cuation-foreground':
|
||||
'--button-fill-caution': 'var(--ds-bg-status-error-default-default)',
|
||||
'--button-fill-caution-foreground':
|
||||
'var(--ds-text-status-error-strong-default)',
|
||||
|
||||
'--button-fill-information': 'var(--ds-bg-status-splitting-default-default)',
|
||||
|
|
@ -114,8 +113,8 @@ export const tagTokenAliases = asCssVarMap({
|
|||
'--tag-foreground-success': 'var(--ds-text-status-completed-strong-default)',
|
||||
'--tag-fill-warning': 'var(--ds-bg-status-pending-subtle-default)',
|
||||
'--tag-foreground-warning': 'var(--ds-text-status-pending-strong-default)',
|
||||
'--tag-fill-cuation': 'var(--ds-bg-status-error-subtle-default)',
|
||||
'--tag-foreground-cuation': 'var(--ds-text-status-error-strong-default)',
|
||||
'--tag-fill-caution': 'var(--ds-bg-status-error-subtle-default)',
|
||||
'--tag-foreground-caution': 'var(--ds-text-status-error-strong-default)',
|
||||
'--tag-fill-default': 'var(--ds-bg-neutral-default-default)',
|
||||
'--tag-foreground-default': 'var(--ds-text-neutral-default-default)',
|
||||
});
|
||||
|
|
@ -138,6 +137,5 @@ export const switchTokenAliases = asCssVarMap({
|
|||
|
||||
export const tooltipTokenAliases = asCssVarMap({
|
||||
'--border-secondary': 'var(--ds-border-neutral-default-default)',
|
||||
'--surface-tertiary': 'var(--ds-bg-neutral-strong-default)',
|
||||
'--text-primary': 'var(--ds-text-neutral-default-default)',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ const Update = () => {
|
|||
if (isDownloading) {
|
||||
toast.custom(
|
||||
(_toastId) => (
|
||||
<div className="rounded-lg bg-white-100% p-4 shadow-lg w-[300px]">
|
||||
<div className="rounded-lg bg-ds-bg-neutral-inverse-default p-4 shadow-lg w-[300px]">
|
||||
<div className="mb-2 text-sm font-medium">
|
||||
{t('update.downloading-update')}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
# Legacy To V2 Token Mapping
|
||||
|
||||
This document maps existing CSS variables to the new semantic token model:
|
||||
|
||||
`element.tone.emphasis.state`
|
||||
|
||||
## Task Lifecycle Mapping
|
||||
|
||||
| Legacy Token | New Token | Notes |
|
||||
| --- | --- | --- |
|
||||
| `--badge-running-surface` | `bg.status-running.subtle.default` | Running badge/card background |
|
||||
| `--badge-running-surface-foreground` | `text.status-running.strong.default` | Running text/icon foreground |
|
||||
| `--badge-splitting-surface` | `bg.status-splitting.subtle.default` | Splitting status |
|
||||
| `--badge-splitting-surface-foreground` | `text.status-splitting.strong.default` | Splitting text/icon |
|
||||
| `--badge-paused-surface` | `bg.status-paused.subtle.default` | Pause status |
|
||||
| `--badge-paused-surface-foreground` | `text.status-paused.strong.default` | Pause text/icon |
|
||||
| `--badge-error-surface` | `bg.status-error.subtle.default` | Error status |
|
||||
| `--badge-error-surface-foreground` | `text.status-error.strong.default` | Error text/icon |
|
||||
| `--badge-complete-surface` | `bg.status-completed.subtle.default` | Completed status |
|
||||
| `--badge-complete-surface-foreground` | `text.status-completed.strong.default` | Completed text/icon |
|
||||
| `--task-fill-running` | `bg.status-running.subtle.default` | Task row/card running |
|
||||
| `--task-fill-success` | `bg.status-completed.subtle.default` | Task row/card completed |
|
||||
| `--task-fill-warning` | `bg.status-blocked.subtle.default` | Used by blocked/reassigning flows |
|
||||
| `--task-fill-error` | `bg.status-error.subtle.default` | Task row/card failed |
|
||||
| `--task-border-focus-success` | `border.status-completed.default.focus` | Focus ring/border for success |
|
||||
| `--task-border-focus-warning` | `border.status-blocked.default.focus` | Focus ring/border for warning |
|
||||
| `--task-border-focus-error` | `border.status-error.default.focus` | Focus ring/border for error |
|
||||
|
||||
## Generic Semantic Mapping
|
||||
|
||||
| Legacy Token | New Token | Notes |
|
||||
| --- | --- | --- |
|
||||
| `--surface-success` | `bg.status-completed.subtle.default` | |
|
||||
| `--surface-information` | `bg.status-splitting.subtle.default` | Info is used for splitting state in chat/task UIs |
|
||||
| `--surface-warning` | `bg.status-pending.subtle.default` | |
|
||||
| `--surface-cuation` | `bg.status-error.subtle.default` | Legacy spelling retained; migrate naming |
|
||||
| `--text-success` | `text.status-completed.strong.default` | |
|
||||
| `--text-information` | `text.status-splitting.strong.default` | |
|
||||
| `--text-warning` | `text.status-pending.strong.default` | |
|
||||
| `--text-cuation` | `text.status-error.strong.default` | |
|
||||
| `--border-success` | `border.status-completed.default.default` | |
|
||||
| `--border-information` | `border.status-splitting.default.default` | |
|
||||
| `--border-warning` | `border.status-pending.default.default` | |
|
||||
| `--border-cuation` | `border.status-error.default.default` | |
|
||||
| `--icon-success` | `icon.status-completed.default.default` | |
|
||||
| `--icon-information` | `icon.status-splitting.default.default` | |
|
||||
| `--icon-warning` | `icon.status-pending.default.default` | |
|
||||
| `--icon-cuation` | `icon.status-error.default.default` | |
|
||||
|
||||
## Recommended Status Selection
|
||||
|
||||
When mapping runtime status to tokens:
|
||||
|
||||
- `running` -> `status-running`
|
||||
- `splitting` -> `status-splitting`
|
||||
- `pending`/`waiting` -> `status-pending`
|
||||
- `reassigning` -> `status-reassigning`
|
||||
- `failed`/`error` -> `status-error`
|
||||
- `completed` -> `status-completed`
|
||||
- `blocked` -> `status-blocked`
|
||||
- `paused` -> `status-paused`
|
||||
- `skipped` -> `status-skipped`
|
||||
- `cancelled` -> `status-cancelled`
|
||||
|
||||
## Migration Order
|
||||
|
||||
1. Introduce `--ds-*` semantic tokens.
|
||||
2. Add/maintain aliases for legacy tokens.
|
||||
3. Migrate component aliases to `--ds-*`.
|
||||
4. Replace legacy token usage in components.
|
||||
5. Remove legacy aliases after migration completion.
|
||||
|
|
@ -1,190 +1,68 @@
|
|||
# Theme Tokens V2
|
||||
|
||||
This module introduces a new semantic token system based on:
|
||||
Theme Tokens V2 is a full cutover to a DTCG-driven, OKLCH-based token engine.
|
||||
|
||||
`element.tone.emphasis.state`
|
||||
## Architecture
|
||||
|
||||
Example: `bg.status-running.subtle.default`
|
||||
1. **Canonical token sources** in `/tokens`:
|
||||
- `base.color.json` (theme seeds + fixed role anchors)
|
||||
- `semantic.color.json` (axes + transforms + contrast policy)
|
||||
- `component.color.json` (component/global alias vars)
|
||||
- `contracts/*.json` (contract presets with `$extends`)
|
||||
2. **Compiler pipeline** in `engine.ts`:
|
||||
- DTCG parse
|
||||
- `$extends` resolution
|
||||
- Semantic generation (`tone × emphasis × state × element`)
|
||||
- WCAG contrast enforcement
|
||||
- APCA diagnostics emission
|
||||
- CSS variable emission (`--ds-*` + component aliases)
|
||||
3. **Runtime application** in `ThemeProvider` via `applyThemeContractV2`.
|
||||
|
||||
## Goals
|
||||
## Contract
|
||||
|
||||
- Keep user-facing controls small: mode + theme id + single contrast scalar.
|
||||
- Derive most UI color decisions from seeds (`accent`, `background`, `ink`) and `contrast`.
|
||||
- Make task lifecycle states first-class semantics (running, splitting, pending, error, reassigning).
|
||||
- Keep components token-driven (no hardcoded color values).
|
||||
|
||||
## Token Layers
|
||||
|
||||
1. Theme contract (`ThemeContractV1`)
|
||||
2. Theme seed (`accent`, `background`, `ink`)
|
||||
3. Derived tokens (`buildThemeV1`)
|
||||
4. Semantic tokens (`element.tone.emphasis.state`)
|
||||
5. Component aliases (component-local CSS variables referencing semantic tokens)
|
||||
|
||||
## Naming Contract
|
||||
|
||||
- `element`: `bg`, `text`, `border`, `icon`, `ring`
|
||||
- `tone`:
|
||||
- global: `neutral`, `brand`
|
||||
- task states: `status-running`, `status-splitting`, `status-pending`, `status-error`, `status-reassigning`, `status-completed`, `status-blocked`, `status-paused`, `status-skipped`, `status-cancelled`
|
||||
- fixed tones: `single-agent`, `workforce`, `browser`, `terminal`, `document`, `success`, `caution`, `warning`, `information`
|
||||
- `emphasis`: `subtle`, `muted`, `default`, `strong`, `inverse`
|
||||
- `state`: `default`, `hover`, `active`, `selected`, `focus`, `disabled`
|
||||
|
||||
## Surface Level Mapping
|
||||
|
||||
Legacy surface levels should map to V2 semantic naming:
|
||||
|
||||
- `surface-primary` -> `bg.neutral.subtle.default`
|
||||
- `surface-secondary` -> `bg.neutral.default.default`
|
||||
- `surface-tertiary` -> `bg.neutral.strong.default`
|
||||
|
||||
All surface tokens are generated as solid fills (not opacity overlays).
|
||||
|
||||
## Fixed Tone Tokens
|
||||
|
||||
Fixed tones are generated in the same `--ds-*` graph but are not derived from user seed colors.
|
||||
They currently use the original light/dark values from `token.css` as a migration baseline.
|
||||
|
||||
Fixed-tone palette source (developer-owned, not user-editable):
|
||||
|
||||
- `src/lib/themeTokens/fixedToneSchema.ts`
|
||||
- Edit `DEFAULT_FIXED_TONE_SCHEMA` to set the base tone colors.
|
||||
- The engine derives hover/active/focus/selected state tokens from these base colors.
|
||||
|
||||
Examples:
|
||||
|
||||
- `--ds-text-success-default-default`
|
||||
- `--ds-border-warning-default-default`
|
||||
- `--ds-icon-browser-default-default`
|
||||
- `--ds-bg-single-agent-subtle-selected`
|
||||
|
||||
## Direct Tailwind Usage
|
||||
|
||||
`tailwind.config.js` registers semantic `--ds-*` color entries so you can use token classes directly:
|
||||
|
||||
- `text-ds-text-success-default-default`
|
||||
- `hover:text-ds-text-success-default-hover`
|
||||
- `bg-ds-bg-warning-subtle-selected`
|
||||
- `border-ds-border-browser-default-focus`
|
||||
|
||||
## How To Search Token Usage
|
||||
|
||||
Use ripgrep to track usage in code and styles:
|
||||
|
||||
```bash
|
||||
rg "var\\(--ds-" src
|
||||
rg "status-running|status-splitting|status-pending|status-error" src
|
||||
rg "--task-fill-|--badge-|--surface-(information|success|warning|cuation)" src/style tailwind.config.js
|
||||
```ts
|
||||
type Adjustment = { dL?: number; dC?: number; dH?: number; alpha?: number };
|
||||
type ThemeContractV2 = {
|
||||
version: 2;
|
||||
mode: "light" | "dark";
|
||||
themeId: string;
|
||||
contrast: number; // 0..100
|
||||
overrides?: {
|
||||
tone?: Record<Tone, Adjustment>;
|
||||
emphasis?: Record<Emphasis, Adjustment>;
|
||||
state?: Record<State, Adjustment>;
|
||||
cell?: Record<`${Tone}.${Emphasis}.${State}`, Adjustment>;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Search strategy:
|
||||
Override precedence is deterministic:
|
||||
|
||||
1. Search semantic tokens first (`--ds-*`).
|
||||
2. Search legacy tokens from `legacyMapping.ts`.
|
||||
3. Migrate component aliases before touching component internals.
|
||||
1. tone defaults
|
||||
2. emphasis transform
|
||||
3. state transform
|
||||
4. axis overrides (`tone`, `emphasis`, `state`)
|
||||
5. cell override (`tone.emphasis.state`)
|
||||
|
||||
## How To Select Tokens For Components
|
||||
## Developer API
|
||||
|
||||
For each component area, pick tokens in this order:
|
||||
|
||||
1. Base layer:
|
||||
- background: `bg.neutral.*`
|
||||
- text/icon: `text.neutral.*`, `icon.neutral.*`
|
||||
- border/ring: `border.neutral.*`, `ring.*`
|
||||
2. Interactive state:
|
||||
- hover/active/focus from same tone/emphasis family.
|
||||
3. Domain state (task lifecycle):
|
||||
- use `status-*` tones for task chips, cards, rows, progress markers.
|
||||
|
||||
Rules:
|
||||
|
||||
- Do not mix unrelated tones in one control unless intentional.
|
||||
- Prefer `subtle` backgrounds and `strong` text for status badges.
|
||||
- Use `default` borders and `focus` rings for keyboard state.
|
||||
- Avoid direct palette tokens (`--colors-*`) in components.
|
||||
|
||||
## Semantic Token vs Component Alias
|
||||
|
||||
Create semantic tokens when the token expresses stable product meaning:
|
||||
|
||||
1. The meaning is reused across multiple components/features.
|
||||
2. The value should respond consistently to theme mode/contrast.
|
||||
3. The name is domain-driven, not component-driven (`status-running`, `status-error`).
|
||||
4. It is part of design language, not local layout implementation.
|
||||
|
||||
Create component aliases when the token is local implementation detail:
|
||||
|
||||
1. The value is specific to one component’s structure.
|
||||
2. You need to map several semantic tokens into a simpler component API.
|
||||
3. You are preserving backwards compatibility during migration.
|
||||
4. You are tuning only one component without changing global semantics.
|
||||
|
||||
Promotion rule:
|
||||
|
||||
1. Start with a component alias if unsure.
|
||||
2. If the same alias pattern appears in 2 or more components, promote to semantic token.
|
||||
|
||||
Anti-patterns:
|
||||
|
||||
1. Semantic tokens named after a single component (`button-primary-bg`).
|
||||
2. Component aliases pointing to raw hex values.
|
||||
3. Creating semantic tokens for one-off visual exceptions.
|
||||
|
||||
## How To Create New Component Tokens
|
||||
|
||||
Create component-scoped aliases that reference semantic tokens. Example:
|
||||
|
||||
```css
|
||||
.task-row {
|
||||
--task-row-bg-default: var(--ds-bg-neutral-default-default);
|
||||
--task-row-bg-hover: var(--ds-bg-neutral-default-hover);
|
||||
--task-row-border-focus: var(--ds-border-neutral-default-focus);
|
||||
}
|
||||
|
||||
.task-row[data-status='running'] {
|
||||
--task-row-bg-default: var(--ds-bg-status-running-subtle-default);
|
||||
--task-row-border-focus: var(--ds-border-status-running-default-focus);
|
||||
}
|
||||
```
|
||||
|
||||
Guidelines:
|
||||
|
||||
1. Always define at least `default`, `hover`, and `focus` aliases.
|
||||
2. Keep aliases local to the component namespace (`--task-row-*`, `--badge-*`).
|
||||
3. Point aliases to semantic tokens, not to raw color constants.
|
||||
4. If a status is new, add a new semantic tone in `types.ts` first.
|
||||
|
||||
## Runtime Usage
|
||||
|
||||
- Build tokens: `buildThemeV1(contract)`
|
||||
- Apply to root: `applyThemeContractV1(contract, document.documentElement)`
|
||||
|
||||
Current integration applies `--ds-*` variables at runtime without replacing old tokens yet.
|
||||
|
||||
In development builds, `ThemeProvider` exposes `window.__eigentThemeV1`:
|
||||
In development, `ThemeProvider` exposes:
|
||||
|
||||
```js
|
||||
window.__eigentThemeV1.listThemes(); // { light: [...], dark: [...] }
|
||||
window.__eigentThemeV1.setTheme('light', 'vivid');
|
||||
window.__eigentThemeV1.setContrast(65);
|
||||
window.__eigentThemeV1.getState();
|
||||
window.__eigentThemeV2.listThemes(); // { light: [...], dark: [...] }
|
||||
window.__eigentThemeV2.setTheme("light", "codex");
|
||||
window.__eigentThemeV2.setContrast(65);
|
||||
window.__eigentThemeV2.getState();
|
||||
```
|
||||
|
||||
## Refactor Workflow (Required)
|
||||
## Validation
|
||||
|
||||
1. Define/adjust base theme seeds first in `catalog.ts` (`accent`, `background`, `ink`).
|
||||
2. Apply themes and validate semantics across multiple color themes (same component states, different seeds).
|
||||
3. Fix semantic token mapping issues discovered during theme switching.
|
||||
4. Only then migrate feature/component token usage.
|
||||
|
||||
Why:
|
||||
|
||||
- Seed-first validation exposes wrong token selection early (for example neutral vs status tone misuse).
|
||||
- Switching theme IDs should change appearance without requiring component code changes.
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- See `MAPPING.md` for old-to-new token migration.
|
||||
- Keep old token names only as aliases during migration.
|
||||
- Correct naming drift as part of migration (`spliting` -> `splitting`, `cuation` -> `error/caution`, `tertiery` -> `tertiary`).
|
||||
- WCAG AA is enforced by default on required semantic pairs.
|
||||
- APCA scores are emitted as diagnostics (non-blocking).
|
||||
- Engine tests live in `engine.v2.test.ts` and cover:
|
||||
- determinism
|
||||
- override precedence
|
||||
- gamut-safe outputs
|
||||
- contrast enforcement
|
||||
- randomized seed stability
|
||||
- runtime reapplication behavior
|
||||
|
|
|
|||
|
|
@ -12,128 +12,99 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import baseColorTokens from '../../../tokens/base.color.json';
|
||||
import contractBase from '../../../tokens/contracts/default.base.json';
|
||||
import darkContractRaw from '../../../tokens/contracts/default.dark.json';
|
||||
import lightContractRaw from '../../../tokens/contracts/default.light.json';
|
||||
import { clamp } from './colorMath';
|
||||
import { resolveExtends } from './dtcg';
|
||||
import {
|
||||
THEME_CONTRACT_VERSION,
|
||||
type ColorThemeDefinitionV1,
|
||||
type ColorThemeDefinitionV2,
|
||||
type Mode,
|
||||
type ThemeCatalog,
|
||||
type ThemeContractV1,
|
||||
type ThemeCatalogV2,
|
||||
type ThemeContractV2,
|
||||
type ThemeSeedV2,
|
||||
} from './types';
|
||||
|
||||
export const DEFAULT_CONTRAST = 43;
|
||||
export const DEFAULT_COLOR_THEME_ID = 'eigent';
|
||||
export const DEFAULT_THEME_ID = 'eigent';
|
||||
export const DEFAULT_COLOR_THEME_ID = DEFAULT_THEME_ID;
|
||||
|
||||
export const DEFAULT_THEME_CATALOG: ThemeCatalog = {
|
||||
light: {
|
||||
eigent: {
|
||||
id: 'eigent',
|
||||
mode: 'light',
|
||||
seed: {
|
||||
accent: '#000000',
|
||||
background: '#faf7f6',
|
||||
ink: '#1d1d1d',
|
||||
},
|
||||
},
|
||||
claude: {
|
||||
id: 'claude',
|
||||
mode: 'light',
|
||||
seed: {
|
||||
accent: '#cc7d5e',
|
||||
background: '#f9f9f7',
|
||||
ink: '#2d2d2b',
|
||||
},
|
||||
},
|
||||
codex: {
|
||||
id: 'codex',
|
||||
mode: 'light',
|
||||
seed: {
|
||||
accent: '#0169cc',
|
||||
background: '#ffffff',
|
||||
ink: '#0d0d0d',
|
||||
},
|
||||
},
|
||||
camel: {
|
||||
id: 'camel',
|
||||
mode: 'light',
|
||||
seed: {
|
||||
accent: '#4c19e8',
|
||||
background: '#ffffff',
|
||||
ink: '#1d1d1d',
|
||||
},
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
eigent: {
|
||||
id: 'eigent',
|
||||
mode: 'dark',
|
||||
seed: {
|
||||
accent: '#ede1db',
|
||||
background: '#1f1f1f',
|
||||
ink: '#ffffff',
|
||||
},
|
||||
},
|
||||
claude: {
|
||||
id: 'claude',
|
||||
mode: 'dark',
|
||||
seed: {
|
||||
accent: '#cc7d5e',
|
||||
background: '#2d2d2b',
|
||||
ink: '#f9f9f7',
|
||||
},
|
||||
},
|
||||
codex: {
|
||||
id: 'codex',
|
||||
mode: 'dark',
|
||||
seed: {
|
||||
accent: '#0169cc',
|
||||
background: '#111111',
|
||||
ink: '#fcfcfc',
|
||||
},
|
||||
},
|
||||
camel: {
|
||||
id: 'camel',
|
||||
mode: 'dark',
|
||||
seed: {
|
||||
accent: '#b5afff',
|
||||
background: '#1f1f1f',
|
||||
ink: '#fafafa',
|
||||
},
|
||||
},
|
||||
},
|
||||
type BaseColorTokenShape = {
|
||||
themes: Record<Mode, Record<string, ThemeSeedV2>>;
|
||||
};
|
||||
|
||||
export function getColorThemeDefinition(
|
||||
mode: Mode,
|
||||
colorThemeId: string,
|
||||
catalog: ThemeCatalog = DEFAULT_THEME_CATALOG
|
||||
): ColorThemeDefinitionV1 {
|
||||
const modeThemes = catalog[mode] ?? {};
|
||||
const selected = modeThemes[colorThemeId];
|
||||
if (selected) {
|
||||
return selected;
|
||||
}
|
||||
const BASE = baseColorTokens as BaseColorTokenShape;
|
||||
|
||||
const fallback = modeThemes[DEFAULT_COLOR_THEME_ID];
|
||||
if (fallback) {
|
||||
return fallback;
|
||||
}
|
||||
function toCatalog(themes: BaseColorTokenShape['themes']): ThemeCatalogV2 {
|
||||
return {
|
||||
light: Object.fromEntries(
|
||||
Object.entries(themes.light).map(([id, seed]) => [
|
||||
id,
|
||||
{ id, mode: 'light', seed },
|
||||
])
|
||||
),
|
||||
dark: Object.fromEntries(
|
||||
Object.entries(themes.dark).map(([id, seed]) => [
|
||||
id,
|
||||
{ id, mode: 'dark', seed },
|
||||
])
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export const DEFAULT_THEME_CATALOG: ThemeCatalogV2 = toCatalog(BASE.themes);
|
||||
|
||||
export function getColorThemeDefinitionV2(
|
||||
mode: Mode,
|
||||
themeId: string,
|
||||
catalog: ThemeCatalogV2 = DEFAULT_THEME_CATALOG
|
||||
): ColorThemeDefinitionV2 {
|
||||
const modeThemes = catalog[mode] ?? {};
|
||||
const selected = modeThemes[themeId];
|
||||
if (selected) return selected;
|
||||
|
||||
const fallback = modeThemes[DEFAULT_THEME_ID];
|
||||
if (fallback) return fallback;
|
||||
|
||||
const firstTheme = Object.values(modeThemes)[0];
|
||||
if (firstTheme) {
|
||||
return firstTheme;
|
||||
}
|
||||
if (firstTheme) return firstTheme;
|
||||
|
||||
throw new Error(`No color themes configured for mode "${mode}"`);
|
||||
}
|
||||
|
||||
export function createDefaultThemeContract(
|
||||
mode: Mode,
|
||||
overrides?: Partial<Omit<ThemeContractV1, 'version' | 'mode'>>
|
||||
): ThemeContractV1 {
|
||||
function resolveContractPreset(raw: unknown): ThemeContractV2 {
|
||||
const tree = resolveExtends({
|
||||
...(contractBase as Record<string, unknown>),
|
||||
contract: raw as Record<string, unknown>,
|
||||
});
|
||||
const contract = (tree as { contract: ThemeContractV2 }).contract;
|
||||
return {
|
||||
...contract,
|
||||
version: THEME_CONTRACT_VERSION,
|
||||
mode,
|
||||
colorThemeId: overrides?.colorThemeId ?? DEFAULT_COLOR_THEME_ID,
|
||||
contrast: overrides?.contrast ?? DEFAULT_CONTRAST,
|
||||
contrast: clamp(Math.round(contract.contrast), 0, 100),
|
||||
};
|
||||
}
|
||||
|
||||
const LIGHT_CONTRACT_PRESET = resolveContractPreset(lightContractRaw);
|
||||
const DARK_CONTRACT_PRESET = resolveContractPreset(darkContractRaw);
|
||||
|
||||
export function createDefaultThemeContractV2(
|
||||
mode: Mode,
|
||||
overrides?: Partial<Omit<ThemeContractV2, 'version' | 'mode'>>
|
||||
): ThemeContractV2 {
|
||||
const preset = mode === 'dark' ? DARK_CONTRACT_PRESET : LIGHT_CONTRACT_PRESET;
|
||||
return {
|
||||
...preset,
|
||||
version: THEME_CONTRACT_VERSION,
|
||||
mode,
|
||||
themeId: overrides?.themeId ?? preset.themeId ?? DEFAULT_THEME_ID,
|
||||
contrast: clamp(
|
||||
Math.round(overrides?.contrast ?? preset.contrast ?? DEFAULT_CONTRAST),
|
||||
0,
|
||||
100
|
||||
),
|
||||
overrides: overrides?.overrides ?? preset.overrides,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ export function clamp(value: number, min: number, max: number): number {
|
|||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
export function normalizeHue(degrees: number): number {
|
||||
let h = degrees % 360;
|
||||
if (h < 0) h += 360;
|
||||
return h;
|
||||
}
|
||||
|
||||
export function hexToRgb(hex: string): { r: number; g: number; b: number } {
|
||||
const normalized = hex.replace('#', '').trim();
|
||||
const full =
|
||||
|
|
@ -58,13 +64,21 @@ export function alpha(hex: string, opacity: number): string {
|
|||
return `rgba(${r}, ${g}, ${b}, ${clamp(opacity, 0, 1).toFixed(3)})`;
|
||||
}
|
||||
|
||||
function srgbToLinear(channel: number): number {
|
||||
const value = channel / 255;
|
||||
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
||||
}
|
||||
|
||||
function linearToSrgb(linear: number): number {
|
||||
const clamped = clamp(linear, 0, 1);
|
||||
return clamped <= 0.0031308
|
||||
? clamped * 12.92
|
||||
: 1.055 * clamped ** (1 / 2.4) - 0.055;
|
||||
}
|
||||
|
||||
function luminance(hex: string): number {
|
||||
const { r, g, b } = hexToRgb(hex);
|
||||
const toLinear = (channel: number) => {
|
||||
const value = channel / 255;
|
||||
return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
||||
};
|
||||
const [lr, lg, lb] = [toLinear(r), toLinear(g), toLinear(b)];
|
||||
const [lr, lg, lb] = [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)];
|
||||
return 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;
|
||||
}
|
||||
|
||||
|
|
@ -89,3 +103,164 @@ export function chooseReadableText(
|
|||
? black
|
||||
: white;
|
||||
}
|
||||
|
||||
export type Oklch = {
|
||||
l: number; // 0..1
|
||||
c: number; // >= 0
|
||||
h: number; // 0..360
|
||||
};
|
||||
|
||||
export type Oklab = {
|
||||
l: number;
|
||||
a: number;
|
||||
b: number;
|
||||
};
|
||||
|
||||
function srgbHexToLinearRgb(hex: string): { r: number; g: number; b: number } {
|
||||
const { r, g, b } = hexToRgb(hex);
|
||||
return {
|
||||
r: srgbToLinear(r),
|
||||
g: srgbToLinear(g),
|
||||
b: srgbToLinear(b),
|
||||
};
|
||||
}
|
||||
|
||||
function linearRgbToHex(rgb: {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
}): `#${string}` {
|
||||
return rgbToHex(
|
||||
linearToSrgb(rgb.r) * 255,
|
||||
linearToSrgb(rgb.g) * 255,
|
||||
linearToSrgb(rgb.b) * 255
|
||||
);
|
||||
}
|
||||
|
||||
function linearRgbToOklab(rgb: { r: number; g: number; b: number }): Oklab {
|
||||
const l = 0.4122214708 * rgb.r + 0.5363325363 * rgb.g + 0.0514459929 * rgb.b;
|
||||
const m = 0.2119034982 * rgb.r + 0.6806995451 * rgb.g + 0.1073969566 * rgb.b;
|
||||
const s = 0.0883024619 * rgb.r + 0.2817188376 * rgb.g + 0.6299787005 * rgb.b;
|
||||
|
||||
const l_ = Math.cbrt(l);
|
||||
const m_ = Math.cbrt(m);
|
||||
const s_ = Math.cbrt(s);
|
||||
|
||||
return {
|
||||
l: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
|
||||
a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
|
||||
b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_,
|
||||
};
|
||||
}
|
||||
|
||||
function oklabToLinearRgb(oklab: Oklab): { r: number; g: number; b: number } {
|
||||
const l_ = oklab.l + 0.3963377774 * oklab.a + 0.2158037573 * oklab.b;
|
||||
const m_ = oklab.l - 0.1055613458 * oklab.a - 0.0638541728 * oklab.b;
|
||||
const s_ = oklab.l - 0.0894841775 * oklab.a - 1.291485548 * oklab.b;
|
||||
|
||||
const l = l_ * l_ * l_;
|
||||
const m = m_ * m_ * m_;
|
||||
const s = s_ * s_ * s_;
|
||||
|
||||
return {
|
||||
r: 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
||||
g: -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
||||
b: -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,
|
||||
};
|
||||
}
|
||||
|
||||
export function hexToOklch(hex: string): Oklch {
|
||||
const oklab = linearRgbToOklab(srgbHexToLinearRgb(hex));
|
||||
const c = Math.sqrt(oklab.a * oklab.a + oklab.b * oklab.b);
|
||||
const h = normalizeHue((Math.atan2(oklab.b, oklab.a) * 180) / Math.PI);
|
||||
return {
|
||||
l: clamp(oklab.l, 0, 1),
|
||||
c: Math.max(0, c),
|
||||
h,
|
||||
};
|
||||
}
|
||||
|
||||
function oklchToOklab(color: Oklch): Oklab {
|
||||
const hRad = (normalizeHue(color.h) * Math.PI) / 180;
|
||||
return {
|
||||
l: clamp(color.l, 0, 1),
|
||||
a: Math.max(0, color.c) * Math.cos(hRad),
|
||||
b: Math.max(0, color.c) * Math.sin(hRad),
|
||||
};
|
||||
}
|
||||
|
||||
function isLinearRgbInSrgbGamut(rgb: {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
}): boolean {
|
||||
return (
|
||||
rgb.r >= 0 &&
|
||||
rgb.r <= 1 &&
|
||||
rgb.g >= 0 &&
|
||||
rgb.g <= 1 &&
|
||||
rgb.b >= 0 &&
|
||||
rgb.b <= 1
|
||||
);
|
||||
}
|
||||
|
||||
export function deltaEOK(a: Oklch, b: Oklch): number {
|
||||
const la = oklchToOklab(a);
|
||||
const lb = oklchToOklab(b);
|
||||
const dl = la.l - lb.l;
|
||||
const da = la.a - lb.a;
|
||||
const db = la.b - lb.b;
|
||||
return Math.sqrt(dl * dl + da * da + db * db);
|
||||
}
|
||||
|
||||
export function oklchToHex(input: Oklch): `#${string}` {
|
||||
const normalized: Oklch = {
|
||||
l: clamp(input.l, 0, 1),
|
||||
c: Math.max(0, input.c),
|
||||
h: normalizeHue(input.h),
|
||||
};
|
||||
|
||||
const candidateRgb = oklabToLinearRgb(oklchToOklab(normalized));
|
||||
if (isLinearRgbInSrgbGamut(candidateRgb)) {
|
||||
return linearRgbToHex(candidateRgb);
|
||||
}
|
||||
|
||||
// Gamut mapping by chroma reduction (binary search, local minimum deltaEOK).
|
||||
const start = normalized;
|
||||
let low = 0;
|
||||
let high = normalized.c;
|
||||
let best = { ...normalized, c: 0 };
|
||||
for (let i = 0; i < 24; i += 1) {
|
||||
const mid = (low + high) / 2;
|
||||
const probe = { ...normalized, c: mid };
|
||||
const probeRgb = oklabToLinearRgb(oklchToOklab(probe));
|
||||
if (isLinearRgbInSrgbGamut(probeRgb)) {
|
||||
best = probe;
|
||||
low = mid;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the in-gamut candidate with smallest deltaEOK to requested color.
|
||||
const edgeA = { ...normalized, c: low };
|
||||
const edgeB = { ...normalized, c: Math.max(0, low - 0.0005) };
|
||||
const bestFinal =
|
||||
deltaEOK(start, edgeA) <= deltaEOK(start, edgeB) ? edgeA : edgeB;
|
||||
return linearRgbToHex(oklabToLinearRgb(oklchToOklab(bestFinal)));
|
||||
}
|
||||
|
||||
export function wcagMinimumContrast(largeText?: boolean): number {
|
||||
return largeText ? 3 : 4.5;
|
||||
}
|
||||
|
||||
export function apcaContrastApprox(textHex: string, bgHex: string): number {
|
||||
const yText = luminance(textHex);
|
||||
const yBg = luminance(bgHex);
|
||||
const polarity = yBg >= yText ? 1 : -1;
|
||||
// Lightweight APCA-like diagnostic curve (non-gating for this release).
|
||||
const bgExp = yBg >= yText ? 0.56 : 0.65;
|
||||
const textExp = yBg >= yText ? 0.57 : 0.62;
|
||||
const lc = (yBg ** bgExp - yText ** textExp) * 1.14 * 100;
|
||||
return polarity * lc;
|
||||
}
|
||||
|
|
|
|||
150
src/lib/themeTokens/dtcg.ts
Normal file
150
src/lib/themeTokens/dtcg.ts
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
type JsonObject = Record<string, unknown>;
|
||||
|
||||
export type DtcgLeafToken = {
|
||||
path: string;
|
||||
value: unknown;
|
||||
type?: string;
|
||||
extensions?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
function isObject(value: unknown): value is JsonObject {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function deepClone<T>(value: T): T {
|
||||
return JSON.parse(JSON.stringify(value)) as T;
|
||||
}
|
||||
|
||||
function deepMerge(base: unknown, override: unknown): unknown {
|
||||
if (!isObject(base) || !isObject(override)) {
|
||||
return deepClone(override);
|
||||
}
|
||||
const out: JsonObject = deepClone(base);
|
||||
for (const [key, value] of Object.entries(override)) {
|
||||
if (key === '$extends') continue;
|
||||
const existing = out[key];
|
||||
out[key] =
|
||||
isObject(existing) && isObject(value)
|
||||
? (deepMerge(existing, value) as JsonObject)
|
||||
: deepClone(value);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function getByPath(root: JsonObject, path: string): unknown {
|
||||
const parts = path.split('.').filter(Boolean);
|
||||
let current: unknown = root;
|
||||
for (const part of parts) {
|
||||
if (!isObject(current) || !(part in current)) {
|
||||
return undefined;
|
||||
}
|
||||
current = current[part];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
function resolveExtendsRec(
|
||||
root: JsonObject,
|
||||
node: unknown,
|
||||
path: string,
|
||||
stack: Set<string>
|
||||
): unknown {
|
||||
if (!isObject(node)) return node;
|
||||
|
||||
const extendsPath =
|
||||
typeof node.$extends === 'string'
|
||||
? node.$extends.trim().replace(/^\{|\}$/g, '')
|
||||
: null;
|
||||
|
||||
if (extendsPath) {
|
||||
const stackKey = `${path} -> ${extendsPath}`;
|
||||
if (stack.has(stackKey)) {
|
||||
throw new Error(`Circular $extends detected at "${path}"`);
|
||||
}
|
||||
stack.add(stackKey);
|
||||
const base = getByPath(root, extendsPath);
|
||||
if (!isObject(base)) {
|
||||
throw new Error(
|
||||
`$extends target "${extendsPath}" not found for "${path}"`
|
||||
);
|
||||
}
|
||||
const resolvedBase = resolveExtendsRec(root, base, extendsPath, stack);
|
||||
const merged = deepMerge(resolvedBase, node);
|
||||
stack.delete(stackKey);
|
||||
return resolveExtendsRec(root, merged, path, stack);
|
||||
}
|
||||
|
||||
const out: JsonObject = {};
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
out[key] = resolveExtendsRec(
|
||||
root,
|
||||
value,
|
||||
path ? `${path}.${key}` : key,
|
||||
stack
|
||||
);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function resolveExtends<T extends JsonObject>(root: T): T {
|
||||
const cloned = deepClone(root);
|
||||
return resolveExtendsRec(cloned, cloned, '', new Set<string>()) as T;
|
||||
}
|
||||
|
||||
function flattenRec(
|
||||
node: unknown,
|
||||
path: string[],
|
||||
output: DtcgLeafToken[]
|
||||
): void {
|
||||
if (!isObject(node)) return;
|
||||
|
||||
if ('$value' in node) {
|
||||
output.push({
|
||||
path: path.join('.'),
|
||||
value: node.$value,
|
||||
type: typeof node.$type === 'string' ? node.$type : undefined,
|
||||
extensions: isObject(node.$extensions)
|
||||
? (node.$extensions as Record<string, unknown>)
|
||||
: undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
if (key.startsWith('$')) continue;
|
||||
flattenRec(value, [...path, key], output);
|
||||
}
|
||||
}
|
||||
|
||||
export function flattenDtcgTokens(tree: JsonObject): DtcgLeafToken[] {
|
||||
const output: DtcgLeafToken[] = [];
|
||||
flattenRec(tree, [], output);
|
||||
return output;
|
||||
}
|
||||
|
||||
export function resolveAliasReferences<T>(
|
||||
value: T,
|
||||
lookup: (path: string) => unknown
|
||||
): T {
|
||||
if (typeof value === 'string') {
|
||||
return value.replace(/\{([^}]+)\}/g, (_m, tokenPath: string) => {
|
||||
const resolved = lookup(tokenPath.trim());
|
||||
return resolved == null ? '' : String(resolved);
|
||||
}) as T;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
|
@ -12,60 +12,122 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { DEFAULT_THEME_CATALOG, getColorThemeDefinition } from './catalog';
|
||||
import { alpha, chooseReadableText, clamp, mix } from './colorMath';
|
||||
import baseColorTokens from '../../../tokens/base.color.json';
|
||||
import componentTokens from '../../../tokens/component.color.json';
|
||||
import semanticTokens from '../../../tokens/semantic.color.json';
|
||||
import { DEFAULT_THEME_CATALOG, getColorThemeDefinitionV2 } from './catalog';
|
||||
import {
|
||||
DEFAULT_FIXED_TONE_SCHEMA,
|
||||
type FixedToneSchema,
|
||||
type FixedToneSeed,
|
||||
} from './fixedToneSchema';
|
||||
alpha,
|
||||
apcaContrastApprox,
|
||||
chooseReadableText,
|
||||
clamp,
|
||||
contrastRatio,
|
||||
hexToOklch,
|
||||
mix,
|
||||
normalizeHue,
|
||||
oklchToHex,
|
||||
rgbToHex,
|
||||
wcagMinimumContrast,
|
||||
type Oklch,
|
||||
} from './colorMath';
|
||||
import {
|
||||
flattenDtcgTokens,
|
||||
resolveAliasReferences,
|
||||
resolveExtends,
|
||||
} from './dtcg';
|
||||
import { tokenKeyToCssVarName } from './naming';
|
||||
import type {
|
||||
FixedTone,
|
||||
Adjustment,
|
||||
ContrastDiagnostic,
|
||||
Element,
|
||||
Emphasis,
|
||||
Mode,
|
||||
ResolvedThemeV1,
|
||||
StatusTone,
|
||||
ThemeCatalog,
|
||||
ThemeContractV1,
|
||||
ResolvedThemeV2,
|
||||
State,
|
||||
ThemeCatalogV2,
|
||||
ThemeContractV2,
|
||||
ThemeTokens,
|
||||
TokenKey,
|
||||
Tone,
|
||||
} from './types';
|
||||
|
||||
const STATUS_TONES: StatusTone[] = [
|
||||
'status-running',
|
||||
'status-splitting',
|
||||
'status-pending',
|
||||
'status-error',
|
||||
'status-reassigning',
|
||||
'status-completed',
|
||||
'status-blocked',
|
||||
'status-paused',
|
||||
'status-skipped',
|
||||
'status-cancelled',
|
||||
];
|
||||
type SemanticShape = {
|
||||
axes: {
|
||||
elements: Element[];
|
||||
tones: Tone[];
|
||||
emphasis: Emphasis[];
|
||||
states: State[];
|
||||
};
|
||||
transforms: {
|
||||
emphasis: Record<Emphasis, Adjustment>;
|
||||
state: Record<State, Adjustment>;
|
||||
element: Record<Element, Adjustment>;
|
||||
};
|
||||
toneSource: Record<
|
||||
Tone,
|
||||
{
|
||||
source: 'accent' | 'background' | 'ink' | 'fixed';
|
||||
sourceByElement?: Partial<
|
||||
Record<Element, 'accent' | 'background' | 'ink' | 'fixed'>
|
||||
>;
|
||||
dL?: number;
|
||||
dC?: number;
|
||||
dH?: number;
|
||||
dLLight?: number;
|
||||
dLDark?: number;
|
||||
}
|
||||
>;
|
||||
contrastPairs: Array<{
|
||||
fg: TokenKey;
|
||||
bg: TokenKey;
|
||||
minContrast: number;
|
||||
largeText?: boolean;
|
||||
}>;
|
||||
};
|
||||
|
||||
type RuntimeStatusTone = Record<StatusTone, `#${string}`>;
|
||||
type NeutralTokenElement = 'bg' | 'text' | 'border' | 'icon' | 'ring';
|
||||
type BaseShape = {
|
||||
fixedAnchors: Record<Mode, Partial<Record<Tone, `#${string}`>>>;
|
||||
};
|
||||
|
||||
const NEUTRAL_EMPHASIS = [
|
||||
const BASE = baseColorTokens as BaseShape;
|
||||
const SEMANTIC = semanticTokens as SemanticShape;
|
||||
const LEGACY_NEUTRAL_EMPHASIS: Emphasis[] = [
|
||||
'subtle',
|
||||
'muted',
|
||||
'default',
|
||||
'strong',
|
||||
'inverse',
|
||||
] as const;
|
||||
const UI_STATES = [
|
||||
];
|
||||
const LEGACY_UI_STATES: State[] = [
|
||||
'default',
|
||||
'hover',
|
||||
'active',
|
||||
'selected',
|
||||
'focus',
|
||||
'disabled',
|
||||
] as const;
|
||||
];
|
||||
|
||||
type NeutralEmphasis = (typeof NEUTRAL_EMPHASIS)[number];
|
||||
type UiState = (typeof UI_STATES)[number];
|
||||
type NeutralStateMatrix = Record<NeutralEmphasis, Record<UiState, string>>;
|
||||
type NeutralStateMatrix = Record<Emphasis, Record<State, string>>;
|
||||
|
||||
function mergeAdjustment(...values: Array<Adjustment | undefined>): Adjustment {
|
||||
const out: Adjustment = {};
|
||||
for (const value of values) {
|
||||
if (!value) continue;
|
||||
if (typeof value.dL === 'number') out.dL = (out.dL ?? 0) + value.dL;
|
||||
if (typeof value.dC === 'number') out.dC = (out.dC ?? 0) + value.dC;
|
||||
if (typeof value.dH === 'number') out.dH = (out.dH ?? 0) + value.dH;
|
||||
if (typeof value.alpha === 'number') out.alpha = value.alpha;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function applyAdjustment(base: Oklch, adjustment: Adjustment): Oklch {
|
||||
return {
|
||||
l: clamp(base.l + (adjustment.dL ?? 0), 0, 1),
|
||||
c: Math.max(0, base.c + (adjustment.dC ?? 0)),
|
||||
h: normalizeHue(base.h + (adjustment.dH ?? 0)),
|
||||
};
|
||||
}
|
||||
|
||||
function setTokenIfMissing(
|
||||
tokens: ThemeTokens,
|
||||
|
|
@ -79,11 +141,11 @@ function setTokenIfMissing(
|
|||
|
||||
function ensureNeutralMatrix(
|
||||
tokens: ThemeTokens,
|
||||
element: NeutralTokenElement,
|
||||
element: Element,
|
||||
matrix: NeutralStateMatrix
|
||||
): void {
|
||||
for (const emphasis of NEUTRAL_EMPHASIS) {
|
||||
for (const state of UI_STATES) {
|
||||
for (const emphasis of LEGACY_NEUTRAL_EMPHASIS) {
|
||||
for (const state of LEGACY_UI_STATES) {
|
||||
setTokenIfMissing(
|
||||
tokens,
|
||||
`${element}.neutral.${emphasis}.${state}` as TokenKey,
|
||||
|
|
@ -93,40 +155,18 @@ function ensureNeutralMatrix(
|
|||
}
|
||||
}
|
||||
|
||||
function resolveStatusToneBase(
|
||||
accent: `#${string}`,
|
||||
ink: `#${string}`,
|
||||
background: `#${string}`
|
||||
): RuntimeStatusTone {
|
||||
return {
|
||||
'status-running': accent,
|
||||
'status-splitting': mix(accent, '#2563eb', 0.46),
|
||||
'status-pending': mix(accent, '#d97706', 0.65),
|
||||
'status-error': mix(accent, '#dc2626', 0.76),
|
||||
'status-reassigning': mix(accent, '#a16207', 0.62),
|
||||
'status-completed': mix(accent, '#16a34a', 0.62),
|
||||
'status-blocked': mix(accent, '#ca8a04', 0.62),
|
||||
'status-paused': mix(accent, '#a16207', 0.48),
|
||||
'status-skipped': mix(ink, background, 0.46),
|
||||
'status-cancelled': mix(ink, background, 0.56),
|
||||
};
|
||||
}
|
||||
|
||||
function buildCoreTokens(
|
||||
accent: `#${string}`,
|
||||
ink: `#${string}`,
|
||||
background: `#${string}`,
|
||||
contrastT: number
|
||||
function buildLegacyNeutralContrastTokens(
|
||||
seed: { accent: `#${string}`; background: `#${string}`; ink: `#${string}` },
|
||||
contrast: number
|
||||
): ThemeTokens {
|
||||
const tokens: ThemeTokens = {};
|
||||
const contrastT = clamp(contrast, 0, 100) / 100;
|
||||
const { background, ink } = seed;
|
||||
|
||||
// Required core formulas (contract-controlled by contrast)
|
||||
const panel = mix(background, ink, 0.02 + contrastT * 0.06);
|
||||
const border = alpha(ink, 0.08 + contrastT * 0.16);
|
||||
const textSecondary = mix(ink, background, 0.28 - contrastT * 0.08);
|
||||
const hover = mix(background, ink, 0.03 + contrastT * 0.05);
|
||||
|
||||
// Extended derived values for consistency across interactive states.
|
||||
const active = mix(background, ink, 0.05 + contrastT * 0.08);
|
||||
const panelStrong = mix(background, ink, 0.04 + contrastT * 0.08);
|
||||
const panelSelected = mix(background, ink, 0.1 + contrastT * 0.1);
|
||||
|
|
@ -159,7 +199,6 @@ function buildCoreTokens(
|
|||
const textInverse = chooseReadableText(inverseBgDefault, ink);
|
||||
|
||||
tokens['bg.neutral.subtle.default'] = background;
|
||||
// Hover between canvas and selected (used by nav tabs, outline buttons, etc.)
|
||||
tokens['bg.neutral.subtle.hover'] = subtleHover;
|
||||
tokens['bg.neutral.subtle.selected'] = subtleSelected;
|
||||
tokens['bg.neutral.default.default'] = panel;
|
||||
|
|
@ -168,7 +207,6 @@ function buildCoreTokens(
|
|||
tokens['bg.neutral.default.selected'] = panelSelected;
|
||||
tokens['bg.neutral.strong.default'] = panelStrong;
|
||||
tokens['bg.neutral.strong.selected'] = panelSelectedStrong;
|
||||
// Muted surfaces remain solid fills (no alpha overlays).
|
||||
tokens['bg.neutral.muted.default'] = mutedDefault;
|
||||
tokens['bg.neutral.muted.hover'] = mutedHover;
|
||||
tokens['bg.neutral.muted.active'] = mutedActive;
|
||||
|
|
@ -180,28 +218,13 @@ function buildCoreTokens(
|
|||
tokens['text.neutral.subtle.default'] = textMuted;
|
||||
tokens['text.neutral.muted.disabled'] = textDisabled;
|
||||
|
||||
const accentHover = mix(accent, ink, 0.08 + contrastT * 0.08);
|
||||
const accentActive = mix(accent, ink, 0.14 + contrastT * 0.1);
|
||||
// Brand buttons should prefer white text when contrast is acceptable for
|
||||
// UI button label sizing; fall back to darker text for very light accents.
|
||||
const accentForeground = chooseReadableText(accent, '#ffffff', 3);
|
||||
tokens['bg.brand.default.default'] = accent;
|
||||
tokens['bg.brand.default.hover'] = accentHover;
|
||||
tokens['bg.brand.default.active'] = accentActive;
|
||||
tokens['text.brand.inverse.default'] = accentForeground;
|
||||
tokens['icon.brand.default.default'] = accent;
|
||||
|
||||
tokens['border.neutral.subtle.default'] = alpha(ink, 0.05 + contrastT * 0.1);
|
||||
tokens['border.neutral.muted.default'] = alpha(ink, 0.07 + contrastT * 0.12);
|
||||
tokens['border.neutral.muted.hover'] = alpha(ink, 0.1 + contrastT * 0.14);
|
||||
tokens['border.neutral.muted.disabled'] = alpha(ink, 0.05 + contrastT * 0.08);
|
||||
tokens['border.neutral.default.default'] = border;
|
||||
tokens['border.neutral.strong.default'] = alpha(ink, 0.14 + contrastT * 0.2);
|
||||
tokens['border.brand.default.focus'] = alpha(accent, 0.55 + contrastT * 0.25);
|
||||
|
||||
tokens['ring.neutral.subtle.focus'] = alpha(ink, 0.2 + contrastT * 0.2);
|
||||
tokens['ring.brand.default.focus'] = alpha(accent, 0.45 + contrastT * 0.3);
|
||||
|
||||
tokens['icon.neutral.default.default'] = textSecondary;
|
||||
tokens['icon.neutral.muted.default'] = textMuted;
|
||||
|
||||
|
|
@ -423,110 +446,253 @@ function buildCoreTokens(
|
|||
return tokens;
|
||||
}
|
||||
|
||||
function buildStatusTokens(
|
||||
accent: `#${string}`,
|
||||
ink: `#${string}`,
|
||||
background: `#${string}`,
|
||||
contrastT: number
|
||||
): ThemeTokens {
|
||||
const tokens: ThemeTokens = {};
|
||||
const statusBase = resolveStatusToneBase(accent, ink, background);
|
||||
function parseCssColor(
|
||||
color: string | undefined
|
||||
): { oklch: Oklch; alpha?: number } | null {
|
||||
const hex = parseHexOnly(color);
|
||||
if (hex) return { oklch: hexToOklch(hex) };
|
||||
|
||||
for (const tone of STATUS_TONES) {
|
||||
const base = statusBase[tone];
|
||||
const bgSubtleDefault = mix(background, base, 0.1 + contrastT * 0.1);
|
||||
const bgSubtleHover = mix(background, base, 0.14 + contrastT * 0.12);
|
||||
const bgDefault = mix(background, base, 0.2 + contrastT * 0.14);
|
||||
|
||||
tokens[`bg.${tone}.subtle.default`] = bgSubtleDefault;
|
||||
tokens[`bg.${tone}.subtle.hover`] = bgSubtleHover;
|
||||
tokens[`bg.${tone}.default.default`] = bgDefault;
|
||||
|
||||
tokens[`border.${tone}.default.default`] = alpha(
|
||||
base,
|
||||
0.32 + contrastT * 0.28
|
||||
if (!color) return null;
|
||||
const rgbaMatch = color
|
||||
.trim()
|
||||
.match(
|
||||
/^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|0?\.\d+|1(?:\.0+)?)\s*\)$/i
|
||||
);
|
||||
tokens[`border.${tone}.default.focus`] = alpha(
|
||||
base,
|
||||
0.45 + contrastT * 0.3
|
||||
);
|
||||
tokens[`ring.${tone}.default.focus`] = alpha(base, 0.4 + contrastT * 0.35);
|
||||
if (!rgbaMatch) return null;
|
||||
|
||||
tokens[`icon.${tone}.default.default`] = base;
|
||||
tokens[`text.${tone}.strong.default`] = chooseReadableText(
|
||||
bgSubtleDefault,
|
||||
mix(base, ink, 0.2)
|
||||
);
|
||||
tokens[`text.${tone}.muted.default`] = mix(base, ink, 0.35);
|
||||
const r = clamp(Number(rgbaMatch[1]), 0, 255);
|
||||
const g = clamp(Number(rgbaMatch[2]), 0, 255);
|
||||
const b = clamp(Number(rgbaMatch[3]), 0, 255);
|
||||
const a = clamp(Number(rgbaMatch[4]), 0, 1);
|
||||
const parsedHex = rgbToHex(r, g, b);
|
||||
return { oklch: hexToOklch(parsedHex), alpha: a };
|
||||
}
|
||||
|
||||
function contrastBias(
|
||||
element: Element,
|
||||
mode: Mode,
|
||||
contrast: number
|
||||
): Adjustment {
|
||||
const t = clamp(contrast, 0, 100) / 100;
|
||||
const direction = mode === 'dark' ? 1 : -1;
|
||||
if (element === 'bg') {
|
||||
return { dL: direction * (0.01 + t * 0.07) };
|
||||
}
|
||||
if (element === 'text' || element === 'icon') {
|
||||
return { dL: direction * (0.02 + t * 0.08) };
|
||||
}
|
||||
if (element === 'border' || element === 'ring') {
|
||||
return { dL: direction * (0.01 + t * 0.05) };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function toneBaseColor(
|
||||
tone: Tone,
|
||||
mode: Mode,
|
||||
seed: { accent: `#${string}`; background: `#${string}`; ink: `#${string}` },
|
||||
element: Element
|
||||
): Oklch {
|
||||
const spec = SEMANTIC.toneSource[tone];
|
||||
const source = spec.sourceByElement?.[element] ?? spec.source;
|
||||
const sourceHex =
|
||||
source === 'fixed'
|
||||
? (BASE.fixedAnchors[mode][tone] ?? seed.accent)
|
||||
: seed[source as 'accent' | 'background' | 'ink'];
|
||||
const base = hexToOklch(sourceHex);
|
||||
return applyAdjustment(base, {
|
||||
dL:
|
||||
(spec.dL ?? 0) +
|
||||
(mode === 'light' ? (spec.dLLight ?? 0) : (spec.dLDark ?? 0)),
|
||||
dC: spec.dC ?? 0,
|
||||
dH: spec.dH ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
function parseHexOnly(color: string | undefined): `#${string}` | null {
|
||||
if (!color) return null;
|
||||
const trimmed = color.trim().toLowerCase();
|
||||
if (!/^#[0-9a-f]{6}$/.test(trimmed)) return null;
|
||||
return trimmed as `#${string}`;
|
||||
}
|
||||
|
||||
function solveForegroundContrast(
|
||||
fgHex: `#${string}`,
|
||||
bgHex: `#${string}`,
|
||||
minContrast: number
|
||||
): `#${string}` {
|
||||
const current = contrastRatio(fgHex, bgHex);
|
||||
if (current >= minContrast) return fgHex;
|
||||
|
||||
const base = hexToOklch(fgHex);
|
||||
let best: { hex: `#${string}`; delta: number } | null = null;
|
||||
for (let i = 0; i <= 100; i += 1) {
|
||||
const targetL = i / 100;
|
||||
const probeHex = oklchToHex({ l: targetL, c: base.c, h: base.h });
|
||||
const ratio = contrastRatio(probeHex, bgHex);
|
||||
if (ratio >= minContrast) {
|
||||
const delta = Math.abs(targetL - base.l);
|
||||
if (!best || delta < best.delta) best = { hex: probeHex, delta };
|
||||
}
|
||||
}
|
||||
if (best) return best.hex;
|
||||
|
||||
// Chroma reduction pass if pure lightness search was insufficient.
|
||||
for (let i = 0; i <= 100; i += 1) {
|
||||
const targetC = (base.c * (100 - i)) / 100;
|
||||
const probeHex = oklchToHex({ l: base.l, c: targetC, h: base.h });
|
||||
if (contrastRatio(probeHex, bgHex) >= minContrast) return probeHex;
|
||||
}
|
||||
|
||||
return chooseReadableText(bgHex, fgHex, minContrast);
|
||||
}
|
||||
|
||||
function enforceContrastPairs(tokens: ThemeTokens): {
|
||||
tokens: ThemeTokens;
|
||||
diagnostics: ContrastDiagnostic[];
|
||||
} {
|
||||
const out: ThemeTokens = { ...tokens };
|
||||
const diagnostics: ContrastDiagnostic[] = [];
|
||||
for (const pair of SEMANTIC.contrastPairs) {
|
||||
const fgToken = pair.fg;
|
||||
const bgToken = pair.bg;
|
||||
const fgValue = out[fgToken];
|
||||
const bgValue = out[bgToken];
|
||||
const fgHex = parseHexOnly(fgValue);
|
||||
const bgHex = parseHexOnly(bgValue);
|
||||
if (!fgHex || !bgHex) continue;
|
||||
|
||||
const minRequired = Math.max(
|
||||
pair.minContrast ?? 0,
|
||||
wcagMinimumContrast(pair.largeText)
|
||||
);
|
||||
const solvedFg = solveForegroundContrast(fgHex, bgHex, minRequired);
|
||||
out[fgToken] = solvedFg;
|
||||
|
||||
const ratio = contrastRatio(solvedFg, bgHex);
|
||||
diagnostics.push({
|
||||
fg: fgToken,
|
||||
bg: bgToken,
|
||||
ratio,
|
||||
minRequired,
|
||||
passes: ratio >= minRequired,
|
||||
apcaLc: apcaContrastApprox(solvedFg, bgHex),
|
||||
});
|
||||
}
|
||||
return { tokens: out, diagnostics };
|
||||
}
|
||||
|
||||
function buildSemanticTokens(
|
||||
contract: ThemeContractV2,
|
||||
seed: ResolvedThemeV2['seed']
|
||||
) {
|
||||
const tokens: ThemeTokens = {};
|
||||
const { elements, tones, emphasis, states } = SEMANTIC.axes;
|
||||
const legacyNeutralTokens = buildLegacyNeutralContrastTokens(
|
||||
seed,
|
||||
contract.contrast
|
||||
);
|
||||
|
||||
for (const tone of tones) {
|
||||
for (const emph of emphasis) {
|
||||
for (const state of states) {
|
||||
const tokenSuffix = `${tone}.${emph}.${state}` as const;
|
||||
const baseAdjustment = mergeAdjustment(
|
||||
SEMANTIC.transforms.emphasis[emph],
|
||||
SEMANTIC.transforms.state[state]
|
||||
);
|
||||
const axisOverride = mergeAdjustment(
|
||||
contract.overrides?.tone?.[tone],
|
||||
contract.overrides?.emphasis?.[emph],
|
||||
contract.overrides?.state?.[state],
|
||||
contract.overrides?.cell?.[tokenSuffix]
|
||||
);
|
||||
|
||||
for (const element of elements) {
|
||||
const tokenKey = `${element}.${tokenSuffix}` as TokenKey;
|
||||
if (tone === 'neutral') {
|
||||
const legacy = parseCssColor(legacyNeutralTokens[tokenKey]);
|
||||
if (legacy) {
|
||||
const adjusted = applyAdjustment(legacy.oklch, axisOverride);
|
||||
const legacyHex = oklchToHex(adjusted);
|
||||
const resolvedAlpha =
|
||||
typeof axisOverride.alpha === 'number'
|
||||
? axisOverride.alpha
|
||||
: legacy.alpha;
|
||||
tokens[tokenKey] =
|
||||
typeof resolvedAlpha === 'number' && resolvedAlpha < 1
|
||||
? alpha(legacyHex, resolvedAlpha)
|
||||
: legacyHex;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const toneBase = toneBaseColor(tone, contract.mode, seed, element);
|
||||
const adjustment = mergeAdjustment(
|
||||
baseAdjustment,
|
||||
SEMANTIC.transforms.element[element],
|
||||
contrastBias(element, contract.mode, contract.contrast),
|
||||
axisOverride
|
||||
);
|
||||
|
||||
const colorHex = oklchToHex(applyAdjustment(toneBase, adjustment));
|
||||
const value =
|
||||
typeof adjustment.alpha === 'number' && adjustment.alpha < 1
|
||||
? alpha(colorHex, adjustment.alpha)
|
||||
: colorHex;
|
||||
tokens[tokenKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function buildFixedToneTokens(
|
||||
mode: Mode,
|
||||
background: `#${string}`,
|
||||
ink: `#${string}`,
|
||||
contrastT: number,
|
||||
schema: FixedToneSchema = DEFAULT_FIXED_TONE_SCHEMA
|
||||
/**
|
||||
* Tones used for filled primary-style controls (`button` `TONE_PRIMARY`): same rule as
|
||||
* brand — prefer near-white on saturated fills (WCAG large-text ~3:1), else best black/white.
|
||||
*/
|
||||
const FILLED_ACCENT_INVERSE_TONES: Tone[] = [
|
||||
'brand',
|
||||
'success',
|
||||
'error',
|
||||
'warning',
|
||||
'information',
|
||||
];
|
||||
|
||||
function applyFilledAccentInverseTextHeuristic(
|
||||
tokens: ThemeTokens
|
||||
): ThemeTokens {
|
||||
const tokens: ThemeTokens = {};
|
||||
const fixed = schema[mode];
|
||||
|
||||
for (const [tone, values] of Object.entries(fixed) as Array<
|
||||
[FixedTone, FixedToneSeed]
|
||||
>) {
|
||||
const base = values.color;
|
||||
const bgSubtleDefault = mix(background, base, 0.1 + contrastT * 0.1);
|
||||
const bgSubtleHover = mix(background, base, 0.14 + contrastT * 0.12);
|
||||
const bgSubtleActive = mix(background, base, 0.18 + contrastT * 0.14);
|
||||
const bgDefault = mix(background, base, 0.2 + contrastT * 0.14);
|
||||
const textHover = mix(base, ink, 0.12 + contrastT * 0.06);
|
||||
const textActive = mix(base, ink, 0.2 + contrastT * 0.08);
|
||||
const borderDefault = alpha(base, 0.32 + contrastT * 0.28);
|
||||
const borderHover = alpha(base, 0.38 + contrastT * 0.3);
|
||||
const borderFocus = alpha(base, 0.45 + contrastT * 0.3);
|
||||
|
||||
// Text: full emphasis ramp (UI state `default`). Regular semantic color is
|
||||
// `text.<tone>.default.default` (e.g. `text.error.default.default`).
|
||||
tokens[`text.${tone}.subtle.default`] = mix(
|
||||
base,
|
||||
background,
|
||||
0.48 + contrastT * 0.1
|
||||
);
|
||||
tokens[`text.${tone}.muted.default`] = mix(
|
||||
base,
|
||||
ink,
|
||||
0.18 + contrastT * 0.06
|
||||
);
|
||||
tokens[`text.${tone}.default.default`] = base;
|
||||
tokens[`text.${tone}.default.hover`] = textHover;
|
||||
tokens[`text.${tone}.default.active`] = textActive;
|
||||
tokens[`text.${tone}.strong.default`] = mix(
|
||||
base,
|
||||
ink,
|
||||
0.4 + contrastT * 0.12
|
||||
);
|
||||
tokens[`text.${tone}.inverse.default`] = chooseReadableText(bgDefault, ink);
|
||||
|
||||
tokens[`icon.${tone}.default.default`] = base;
|
||||
tokens[`icon.${tone}.default.hover`] = textHover;
|
||||
tokens[`icon.${tone}.default.active`] = textActive;
|
||||
|
||||
tokens[`border.${tone}.default.default`] = borderDefault;
|
||||
tokens[`border.${tone}.default.hover`] = borderHover;
|
||||
tokens[`border.${tone}.default.focus`] = borderFocus;
|
||||
tokens[`ring.${tone}.default.focus`] = alpha(base, 0.4 + contrastT * 0.35);
|
||||
|
||||
tokens[`bg.${tone}.subtle.default`] = bgSubtleDefault;
|
||||
tokens[`bg.${tone}.subtle.hover`] = bgSubtleHover;
|
||||
tokens[`bg.${tone}.subtle.active`] = bgSubtleActive;
|
||||
tokens[`bg.${tone}.subtle.selected`] =
|
||||
values.selectedBg ?? mix(background, base, 0.16 + contrastT * 0.12);
|
||||
tokens[`bg.${tone}.default.default`] = bgDefault;
|
||||
const out: ThemeTokens = { ...tokens };
|
||||
for (const tone of FILLED_ACCENT_INVERSE_TONES) {
|
||||
for (const state of SEMANTIC.axes.states) {
|
||||
const bgKey = `bg.${tone}.default.${state}` as TokenKey;
|
||||
const textKey = `text.${tone}.inverse.${state}` as TokenKey;
|
||||
const bgHex = parseHexOnly(out[bgKey]);
|
||||
if (!bgHex) continue;
|
||||
out[textKey] = chooseReadableText(bgHex, '#ffffff', 3);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
function buildComponentAliasVariables(
|
||||
tokens: ThemeTokens
|
||||
): Record<string, string> {
|
||||
const resolved = resolveExtends(componentTokens as Record<string, unknown>);
|
||||
const leaves = flattenDtcgTokens(resolved);
|
||||
const out: Record<string, string> = {};
|
||||
for (const leaf of leaves) {
|
||||
if (typeof leaf.value !== 'string') continue;
|
||||
const resolvedValue = resolveAliasReferences(leaf.value, (path) => {
|
||||
const key = path as TokenKey;
|
||||
return tokens[key];
|
||||
});
|
||||
const cssVar = (leaf.extensions?.cssVar as string | undefined) ?? null;
|
||||
if (!cssVar || !resolvedValue) continue;
|
||||
out[cssVar] = resolvedValue;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function toCssVariables(tokens: ThemeTokens): Record<string, string> {
|
||||
|
|
@ -538,76 +704,59 @@ function toCssVariables(tokens: ThemeTokens): Record<string, string> {
|
|||
return variables;
|
||||
}
|
||||
|
||||
function resolveContract(contract: ThemeContractV1): ThemeContractV1 {
|
||||
function normalizeContract(contract: ThemeContractV2): ThemeContractV2 {
|
||||
return {
|
||||
...contract,
|
||||
contrast: clamp(Math.round(contract.contrast), 0, 100),
|
||||
};
|
||||
}
|
||||
|
||||
function getThemeSeed(
|
||||
contract: ThemeContractV1,
|
||||
catalog: ThemeCatalog
|
||||
): {
|
||||
colorThemeId: string;
|
||||
seed: ResolvedThemeV1['seed'];
|
||||
} {
|
||||
const definition = getColorThemeDefinition(
|
||||
function getThemeSeed(contract: ThemeContractV2, catalog: ThemeCatalogV2) {
|
||||
const definition = getColorThemeDefinitionV2(
|
||||
contract.mode,
|
||||
contract.colorThemeId,
|
||||
contract.themeId,
|
||||
catalog
|
||||
);
|
||||
return {
|
||||
colorThemeId: definition.id,
|
||||
themeId: definition.id,
|
||||
seed: definition.seed,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildThemeV1(
|
||||
contract: ThemeContractV1,
|
||||
catalog: ThemeCatalog = DEFAULT_THEME_CATALOG
|
||||
): ResolvedThemeV1 {
|
||||
const resolvedContract = resolveContract(contract);
|
||||
const { colorThemeId, seed } = getThemeSeed(resolvedContract, catalog);
|
||||
const contrastT = resolvedContract.contrast / 100;
|
||||
export function buildThemeV2(
|
||||
contract: ThemeContractV2,
|
||||
catalog: ThemeCatalogV2 = DEFAULT_THEME_CATALOG
|
||||
): ResolvedThemeV2 {
|
||||
const normalized = normalizeContract(contract);
|
||||
const { seed, themeId } = getThemeSeed(normalized, catalog);
|
||||
|
||||
const core = buildCoreTokens(
|
||||
seed.accent,
|
||||
seed.ink,
|
||||
seed.background,
|
||||
contrastT
|
||||
);
|
||||
const status = buildStatusTokens(
|
||||
seed.accent,
|
||||
seed.ink,
|
||||
seed.background,
|
||||
contrastT
|
||||
);
|
||||
const fixedTone = buildFixedToneTokens(
|
||||
resolvedContract.mode,
|
||||
seed.background,
|
||||
seed.ink,
|
||||
contrastT
|
||||
);
|
||||
const tokens: ThemeTokens = {
|
||||
...core,
|
||||
...status,
|
||||
...fixedTone,
|
||||
const semantic = buildSemanticTokens(normalized, seed);
|
||||
const accentInverseAdjusted = applyFilledAccentInverseTextHeuristic(semantic);
|
||||
const enforced = enforceContrastPairs(accentInverseAdjusted);
|
||||
const semanticCssVars = toCssVariables(enforced.tokens);
|
||||
const componentVars = buildComponentAliasVariables(enforced.tokens);
|
||||
const cssVariables = {
|
||||
...semanticCssVars,
|
||||
...componentVars,
|
||||
'--ds-theme-contrast': String(normalized.contrast),
|
||||
};
|
||||
|
||||
return {
|
||||
contract: {
|
||||
...resolvedContract,
|
||||
colorThemeId,
|
||||
...normalized,
|
||||
themeId,
|
||||
},
|
||||
seed,
|
||||
tokens,
|
||||
cssVariables: toCssVariables(tokens),
|
||||
tokens: enforced.tokens,
|
||||
cssVariables,
|
||||
diagnostics: {
|
||||
contrast: enforced.diagnostics,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function applyResolvedThemeToElement(
|
||||
resolvedTheme: ResolvedThemeV1,
|
||||
resolvedTheme: ResolvedThemeV2,
|
||||
element: HTMLElement
|
||||
): void {
|
||||
for (const [name, value] of Object.entries(resolvedTheme.cssVariables)) {
|
||||
|
|
@ -615,12 +764,25 @@ export function applyResolvedThemeToElement(
|
|||
}
|
||||
}
|
||||
|
||||
export function applyThemeContractV1(
|
||||
contract: ThemeContractV1,
|
||||
export function applyThemeContractV2(
|
||||
contract: ThemeContractV2,
|
||||
element: HTMLElement,
|
||||
catalog: ThemeCatalog = DEFAULT_THEME_CATALOG
|
||||
): ResolvedThemeV1 {
|
||||
const resolved = buildThemeV1(contract, catalog);
|
||||
catalog: ThemeCatalogV2 = DEFAULT_THEME_CATALOG
|
||||
): ResolvedThemeV2 {
|
||||
const resolved = buildThemeV2(contract, catalog);
|
||||
applyResolvedThemeToElement(resolved, element);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
export function createApcaDiagnosticsReport(
|
||||
resolvedTheme: ResolvedThemeV2
|
||||
): string {
|
||||
return JSON.stringify(
|
||||
{
|
||||
contract: resolvedTheme.contract,
|
||||
diagnostics: resolvedTheme.diagnostics,
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
|
|
|||
306
src/lib/themeTokens/engine.v2.test.ts
Normal file
306
src/lib/themeTokens/engine.v2.test.ts
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { DEFAULT_THEME_CATALOG, createDefaultThemeContractV2 } from './catalog';
|
||||
import { contrastRatio } from './colorMath';
|
||||
import {
|
||||
applyThemeContractV2,
|
||||
buildThemeV2,
|
||||
createApcaDiagnosticsReport,
|
||||
} from './engine';
|
||||
import {
|
||||
TOKEN_ELEMENTS,
|
||||
TOKEN_EMPHASIS,
|
||||
TOKEN_TONES,
|
||||
TOKEN_UI_STATES,
|
||||
type Mode,
|
||||
type ThemeCatalogV2,
|
||||
} from './types';
|
||||
|
||||
function isHex(value: string): boolean {
|
||||
return /^#[0-9a-f]{6}$/i.test(value);
|
||||
}
|
||||
|
||||
function isRgba(value: string): boolean {
|
||||
return /^rgba\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*(0|0?\.\d+|1(?:\.0+)?)\s*\)$/i.test(
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
function relativeLuminance(hex: string): number {
|
||||
const clean = hex.replace('#', '');
|
||||
const r = parseInt(clean.slice(0, 2), 16) / 255;
|
||||
const g = parseInt(clean.slice(2, 4), 16) / 255;
|
||||
const b = parseInt(clean.slice(4, 6), 16) / 255;
|
||||
const linear = (channel: number) =>
|
||||
channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
|
||||
return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
|
||||
}
|
||||
|
||||
function randomHex(): `#${string}` {
|
||||
const num = Math.floor(Math.random() * 0xffffff);
|
||||
return `#${num.toString(16).padStart(6, '0')}` as `#${string}`;
|
||||
}
|
||||
|
||||
describe('themeTokens v2 engine', () => {
|
||||
it('is deterministic for a fixed contract and catalog', () => {
|
||||
const contract = createDefaultThemeContractV2('light', {
|
||||
themeId: 'eigent',
|
||||
contrast: 52,
|
||||
});
|
||||
const first = buildThemeV2(contract, DEFAULT_THEME_CATALOG);
|
||||
const second = buildThemeV2(contract, DEFAULT_THEME_CATALOG);
|
||||
expect(first.tokens).toStrictEqual(second.tokens);
|
||||
expect(first.cssVariables).toStrictEqual(second.cssVariables);
|
||||
});
|
||||
|
||||
it('enforces override precedence with cell override as final authority', () => {
|
||||
const baseContract = createDefaultThemeContractV2('light', {
|
||||
themeId: 'codex',
|
||||
contrast: 50,
|
||||
});
|
||||
const base = buildThemeV2(baseContract, DEFAULT_THEME_CATALOG);
|
||||
|
||||
const overridden = buildThemeV2(
|
||||
{
|
||||
...baseContract,
|
||||
overrides: {
|
||||
tone: { brand: { dL: 0.2, dC: 0.05 } },
|
||||
emphasis: { default: { dL: -0.1 } },
|
||||
state: { default: { dL: -0.08 } },
|
||||
cell: { 'brand.default.default': { dL: 0.01, dC: 0.001, dH: 3 } },
|
||||
},
|
||||
},
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
|
||||
const key = 'bg.brand.default.default';
|
||||
expect(overridden.tokens[key]).not.toBe(base.tokens[key]);
|
||||
});
|
||||
|
||||
it('produces a full tone × emphasis × state × element matrix', () => {
|
||||
const theme = buildThemeV2(
|
||||
createDefaultThemeContractV2('dark', { themeId: 'camel', contrast: 63 }),
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
const expected =
|
||||
TOKEN_ELEMENTS.length *
|
||||
TOKEN_TONES.length *
|
||||
TOKEN_EMPHASIS.length *
|
||||
TOKEN_UI_STATES.length;
|
||||
expect(Object.keys(theme.tokens)).toHaveLength(expected);
|
||||
});
|
||||
|
||||
it('ensures all generated tokens are valid CSS colors', () => {
|
||||
const theme = buildThemeV2(
|
||||
createDefaultThemeContractV2('light', {
|
||||
themeId: 'claude',
|
||||
contrast: 40,
|
||||
}),
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
for (const value of Object.values(theme.tokens)) {
|
||||
if (!value) continue;
|
||||
expect(isHex(value) || isRgba(value)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('enforces required WCAG pairs and emits APCA diagnostics', () => {
|
||||
const theme = buildThemeV2(
|
||||
createDefaultThemeContractV2('dark', { themeId: 'eigent', contrast: 80 }),
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
expect(theme.diagnostics.contrast.length).toBeGreaterThan(0);
|
||||
for (const item of theme.diagnostics.contrast) {
|
||||
expect(item.ratio).toBeGreaterThanOrEqual(item.minRequired);
|
||||
expect(Number.isFinite(item.apcaLc)).toBe(true);
|
||||
}
|
||||
const report = createApcaDiagnosticsReport(theme);
|
||||
const parsed = JSON.parse(report) as {
|
||||
diagnostics: { contrast: Array<{ apcaLc: number }> };
|
||||
};
|
||||
expect(parsed.diagnostics.contrast.length).toBe(
|
||||
theme.diagnostics.contrast.length
|
||||
);
|
||||
});
|
||||
|
||||
it('applies and re-applies themes without stale root values', () => {
|
||||
const root = document.createElement('div');
|
||||
const light = applyThemeContractV2(
|
||||
createDefaultThemeContractV2('light', {
|
||||
themeId: 'eigent',
|
||||
contrast: 40,
|
||||
}),
|
||||
root
|
||||
);
|
||||
const firstBg = root.style.getPropertyValue(
|
||||
'--ds-bg-neutral-subtle-default'
|
||||
);
|
||||
expect(firstBg).toBe(light.cssVariables['--ds-bg-neutral-subtle-default']);
|
||||
|
||||
const dark = applyThemeContractV2(
|
||||
createDefaultThemeContractV2('dark', { themeId: 'eigent', contrast: 70 }),
|
||||
root
|
||||
);
|
||||
const secondBg = root.style.getPropertyValue(
|
||||
'--ds-bg-neutral-subtle-default'
|
||||
);
|
||||
expect(secondBg).toBe(dark.cssVariables['--ds-bg-neutral-subtle-default']);
|
||||
expect(secondBg).not.toBe(firstBg);
|
||||
});
|
||||
|
||||
it('keeps neutral surface polarity aligned with mode', () => {
|
||||
const light = buildThemeV2(
|
||||
createDefaultThemeContractV2('light', {
|
||||
themeId: 'eigent',
|
||||
contrast: 50,
|
||||
}),
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
const dark = buildThemeV2(
|
||||
createDefaultThemeContractV2('dark', { themeId: 'eigent', contrast: 50 }),
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
|
||||
const lightBg = light.tokens['bg.neutral.subtle.default'] as string;
|
||||
const darkBg = dark.tokens['bg.neutral.subtle.default'] as string;
|
||||
const lightText = light.tokens['text.neutral.default.default'] as string;
|
||||
const darkText = dark.tokens['text.neutral.default.default'] as string;
|
||||
|
||||
expect(relativeLuminance(lightBg)).toBeGreaterThan(
|
||||
relativeLuminance(darkBg)
|
||||
);
|
||||
expect(relativeLuminance(darkText)).toBeGreaterThan(
|
||||
relativeLuminance(lightText)
|
||||
);
|
||||
});
|
||||
|
||||
it('uses legacy-style monotonic contrast response for neutral tokens', () => {
|
||||
const lightLow = buildThemeV2(
|
||||
createDefaultThemeContractV2('light', { themeId: 'eigent', contrast: 0 }),
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
const lightHigh = buildThemeV2(
|
||||
createDefaultThemeContractV2('light', {
|
||||
themeId: 'eigent',
|
||||
contrast: 100,
|
||||
}),
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
const darkLow = buildThemeV2(
|
||||
createDefaultThemeContractV2('dark', { themeId: 'eigent', contrast: 0 }),
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
const darkHigh = buildThemeV2(
|
||||
createDefaultThemeContractV2('dark', {
|
||||
themeId: 'eigent',
|
||||
contrast: 100,
|
||||
}),
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
|
||||
const lightBgLow = lightLow.tokens['bg.neutral.default.default'] as string;
|
||||
const lightBgHigh = lightHigh.tokens[
|
||||
'bg.neutral.default.default'
|
||||
] as string;
|
||||
const darkBgLow = darkLow.tokens['bg.neutral.default.default'] as string;
|
||||
const darkBgHigh = darkHigh.tokens['bg.neutral.default.default'] as string;
|
||||
const lightTextLow = lightLow.tokens[
|
||||
'text.neutral.muted.default'
|
||||
] as string;
|
||||
const lightTextHigh = lightHigh.tokens[
|
||||
'text.neutral.muted.default'
|
||||
] as string;
|
||||
const darkTextLow = darkLow.tokens['text.neutral.muted.default'] as string;
|
||||
const darkTextHigh = darkHigh.tokens[
|
||||
'text.neutral.muted.default'
|
||||
] as string;
|
||||
|
||||
expect(relativeLuminance(lightBgHigh)).toBeLessThan(
|
||||
relativeLuminance(lightBgLow)
|
||||
);
|
||||
expect(relativeLuminance(darkBgHigh)).toBeGreaterThan(
|
||||
relativeLuminance(darkBgLow)
|
||||
);
|
||||
expect(relativeLuminance(lightTextHigh)).toBeLessThan(
|
||||
relativeLuminance(lightTextLow)
|
||||
);
|
||||
expect(relativeLuminance(darkTextHigh)).toBeGreaterThan(
|
||||
relativeLuminance(darkTextLow)
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps brand inverse text white for black brand fills', () => {
|
||||
const theme = buildThemeV2(
|
||||
createDefaultThemeContractV2('light', {
|
||||
themeId: 'eigent',
|
||||
contrast: 50,
|
||||
}),
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
expect(theme.tokens['bg.brand.default.default']).toBe('#000000');
|
||||
expect(theme.tokens['text.brand.inverse.default']).toBe('#ffffff');
|
||||
});
|
||||
|
||||
it('keeps success inverse text as light as brand inverse on filled success (light)', () => {
|
||||
const theme = buildThemeV2(
|
||||
createDefaultThemeContractV2('light', {
|
||||
themeId: 'eigent',
|
||||
contrast: 50,
|
||||
}),
|
||||
DEFAULT_THEME_CATALOG
|
||||
);
|
||||
const successBg = theme.tokens['bg.success.default.default'] as string;
|
||||
const successInverse = theme.tokens[
|
||||
'text.success.inverse.default'
|
||||
] as string;
|
||||
expect(contrastRatio(successInverse, successBg)).toBeGreaterThanOrEqual(3);
|
||||
expect(successInverse).toBe('#ffffff');
|
||||
});
|
||||
|
||||
it('remains stable under randomized theme seeds', () => {
|
||||
for (let i = 0; i < 20; i += 1) {
|
||||
const mode: Mode = i % 2 === 0 ? 'light' : 'dark';
|
||||
const themeId = `random-${i}`;
|
||||
const catalog: ThemeCatalogV2 = {
|
||||
...DEFAULT_THEME_CATALOG,
|
||||
[mode]: {
|
||||
...DEFAULT_THEME_CATALOG[mode],
|
||||
[themeId]: {
|
||||
id: themeId,
|
||||
mode,
|
||||
seed: {
|
||||
accent: randomHex(),
|
||||
background: randomHex(),
|
||||
ink: randomHex(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const theme = buildThemeV2(
|
||||
createDefaultThemeContractV2(mode, {
|
||||
themeId,
|
||||
contrast: Math.floor(Math.random() * 101),
|
||||
}),
|
||||
catalog
|
||||
);
|
||||
for (const value of Object.values(theme.tokens)) {
|
||||
if (!value) continue;
|
||||
expect(isHex(value) || isRgba(value)).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import type { FixedTone, Mode } from './types';
|
||||
|
||||
export type FixedToneSeed = {
|
||||
// Base tone color for text/icon and derived border/bg states.
|
||||
color: `#${string}`;
|
||||
// Optional explicit selected background. Falls back to derived value when omitted.
|
||||
selectedBg?: string;
|
||||
};
|
||||
|
||||
export type FixedToneSchema = Record<Mode, Record<FixedTone, FixedToneSeed>>;
|
||||
|
||||
// Developer-owned fixed tones. Not user-editable through theme import/customization UI.
|
||||
export const DEFAULT_FIXED_TONE_SCHEMA: FixedToneSchema = {
|
||||
light: {
|
||||
'single-agent': {
|
||||
color: '#7e22ce',
|
||||
selectedBg: '#f3e8ff',
|
||||
},
|
||||
workforce: {
|
||||
color: '#007a55',
|
||||
selectedBg: '#d0fae5',
|
||||
},
|
||||
browser: {
|
||||
color: '#0084d1',
|
||||
},
|
||||
terminal: {
|
||||
color: '#009966',
|
||||
},
|
||||
document: {
|
||||
color: '#e17100',
|
||||
},
|
||||
success: {
|
||||
color: '#00a63e',
|
||||
},
|
||||
caution: {
|
||||
color: '#e7000b',
|
||||
},
|
||||
error: {
|
||||
color: '#e7000b',
|
||||
},
|
||||
warning: {
|
||||
color: '#d08700',
|
||||
},
|
||||
information: {
|
||||
color: '#155dfc',
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
'single-agent': {
|
||||
color: '#e9d5ff',
|
||||
selectedBg: 'rgba(168, 85, 247, 0.22)',
|
||||
},
|
||||
workforce: {
|
||||
color: '#6ee7b7',
|
||||
selectedBg: 'rgba(52, 211, 153, 0.2)',
|
||||
},
|
||||
browser: {
|
||||
color: '#7dd3fc',
|
||||
},
|
||||
terminal: {
|
||||
color: '#6ee7b7',
|
||||
},
|
||||
document: {
|
||||
color: '#ffd479',
|
||||
},
|
||||
success: {
|
||||
color: '#4ade80',
|
||||
},
|
||||
caution: {
|
||||
color: '#f87171',
|
||||
},
|
||||
error: {
|
||||
color: '#f87171',
|
||||
},
|
||||
warning: {
|
||||
color: '#facc15',
|
||||
},
|
||||
information: {
|
||||
color: '#7ab3ff',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -13,8 +13,7 @@
|
|||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
export * from './catalog';
|
||||
export * from './dtcg';
|
||||
export * from './engine';
|
||||
export * from './fixedToneSchema';
|
||||
export * from './legacyMapping';
|
||||
export * from './naming';
|
||||
export * from './types';
|
||||
|
|
|
|||
|
|
@ -1,172 +0,0 @@
|
|||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { tokenKeyToCssVarValue } from './naming';
|
||||
import type { TokenKey } from './types';
|
||||
|
||||
export type LegacyTokenMappingEntry = {
|
||||
legacyVariable: string;
|
||||
token: TokenKey;
|
||||
notes?: string;
|
||||
};
|
||||
|
||||
export const LEGACY_TOKEN_MAPPING: LegacyTokenMappingEntry[] = [
|
||||
// Task/badge lifecycle semantics
|
||||
{
|
||||
legacyVariable: '--badge-running-surface',
|
||||
token: 'bg.status-running.subtle.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--badge-running-surface-foreground',
|
||||
token: 'text.status-running.strong.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--badge-splitting-surface',
|
||||
token: 'bg.status-splitting.subtle.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--badge-splitting-surface-foreground',
|
||||
token: 'text.status-splitting.strong.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--badge-paused-surface',
|
||||
token: 'bg.status-paused.subtle.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--badge-paused-surface-foreground',
|
||||
token: 'text.status-paused.strong.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--badge-error-surface',
|
||||
token: 'bg.status-error.subtle.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--badge-error-surface-foreground',
|
||||
token: 'text.status-error.strong.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--badge-complete-surface',
|
||||
token: 'bg.status-completed.subtle.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--badge-complete-surface-foreground',
|
||||
token: 'text.status-completed.strong.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--task-fill-running',
|
||||
token: 'bg.status-running.subtle.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--task-fill-success',
|
||||
token: 'bg.status-completed.subtle.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--task-fill-warning',
|
||||
token: 'bg.status-blocked.subtle.default',
|
||||
notes:
|
||||
'In task cards this often also represents reassigning, which should migrate to status-reassigning.',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--task-fill-error',
|
||||
token: 'bg.status-error.subtle.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--task-border-focus-success',
|
||||
token: 'border.status-completed.default.focus',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--task-border-focus-warning',
|
||||
token: 'border.status-blocked.default.focus',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--task-border-focus-error',
|
||||
token: 'border.status-error.default.focus',
|
||||
},
|
||||
|
||||
// Generic semantic colors currently used in status-like UI contexts
|
||||
{
|
||||
legacyVariable: '--surface-success',
|
||||
token: 'bg.status-completed.subtle.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--surface-information',
|
||||
token: 'bg.status-splitting.subtle.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--surface-warning',
|
||||
token: 'bg.status-pending.subtle.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--surface-cuation',
|
||||
token: 'bg.status-error.subtle.default',
|
||||
notes:
|
||||
'Spelling kept for legacy compatibility. New naming should use error.',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--text-success',
|
||||
token: 'text.status-completed.strong.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--text-information',
|
||||
token: 'text.status-splitting.strong.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--text-warning',
|
||||
token: 'text.status-pending.strong.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--text-cuation',
|
||||
token: 'text.status-error.strong.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--border-success',
|
||||
token: 'border.status-completed.default.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--border-information',
|
||||
token: 'border.status-splitting.default.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--border-warning',
|
||||
token: 'border.status-pending.default.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--border-cuation',
|
||||
token: 'border.status-error.default.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--icon-success',
|
||||
token: 'icon.status-completed.default.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--icon-information',
|
||||
token: 'icon.status-splitting.default.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--icon-warning',
|
||||
token: 'icon.status-pending.default.default',
|
||||
},
|
||||
{
|
||||
legacyVariable: '--icon-cuation',
|
||||
token: 'icon.status-error.default.default',
|
||||
},
|
||||
];
|
||||
|
||||
export function buildLegacyAliasVariableValues(): Record<string, string> {
|
||||
const out: Record<string, string> = {};
|
||||
for (const item of LEGACY_TOKEN_MAPPING) {
|
||||
out[item.legacyVariable] = tokenKeyToCssVarValue(item.token);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
export const THEME_CONTRACT_VERSION = 1 as const;
|
||||
export const THEME_CONTRACT_VERSION = 2 as const;
|
||||
|
||||
export const TOKEN_ELEMENTS = ['bg', 'text', 'border', 'icon', 'ring'] as const;
|
||||
export const TOKEN_EMPHASIS = [
|
||||
|
|
@ -30,21 +30,19 @@ export const TOKEN_UI_STATES = [
|
|||
'focus',
|
||||
'disabled',
|
||||
] as const;
|
||||
|
||||
export const TASK_SEMANTIC_STATES = [
|
||||
'running',
|
||||
'splitting',
|
||||
'pending',
|
||||
'error',
|
||||
'reassigning',
|
||||
'completed',
|
||||
'blocked',
|
||||
'paused',
|
||||
'skipped',
|
||||
'cancelled',
|
||||
] as const;
|
||||
|
||||
export const FIXED_TONES = [
|
||||
export const TOKEN_TONES = [
|
||||
'neutral',
|
||||
'brand',
|
||||
'status-running',
|
||||
'status-splitting',
|
||||
'status-pending',
|
||||
'status-error',
|
||||
'status-reassigning',
|
||||
'status-completed',
|
||||
'status-blocked',
|
||||
'status-paused',
|
||||
'status-skipped',
|
||||
'status-cancelled',
|
||||
'single-agent',
|
||||
'workforce',
|
||||
'browser',
|
||||
|
|
@ -59,41 +57,85 @@ export const FIXED_TONES = [
|
|||
|
||||
export type Mode = 'light' | 'dark';
|
||||
|
||||
export type ThemeContractV1 = {
|
||||
version: typeof THEME_CONTRACT_VERSION;
|
||||
mode: Mode;
|
||||
colorThemeId: string;
|
||||
contrast: number; // 0..100
|
||||
export type Element = (typeof TOKEN_ELEMENTS)[number];
|
||||
export type Emphasis = (typeof TOKEN_EMPHASIS)[number];
|
||||
export type State = (typeof TOKEN_UI_STATES)[number];
|
||||
export type Tone = (typeof TOKEN_TONES)[number];
|
||||
export type TokenKey = `${Element}.${Tone}.${Emphasis}.${State}`;
|
||||
|
||||
export type Adjustment = {
|
||||
dL?: number;
|
||||
dC?: number;
|
||||
dH?: number;
|
||||
alpha?: number;
|
||||
};
|
||||
|
||||
export type ThemeSeed = {
|
||||
export type ThemeContractV2 = {
|
||||
version: typeof THEME_CONTRACT_VERSION;
|
||||
mode: Mode;
|
||||
themeId: string;
|
||||
contrast: number; // 0..100
|
||||
overrides?: {
|
||||
tone?: Partial<Record<Tone, Adjustment>>;
|
||||
emphasis?: Partial<Record<Emphasis, Adjustment>>;
|
||||
state?: Partial<Record<State, Adjustment>>;
|
||||
cell?: Partial<Record<`${Tone}.${Emphasis}.${State}`, Adjustment>>;
|
||||
};
|
||||
};
|
||||
|
||||
export type TokenGenerationContractV2 = {
|
||||
baseToneAdjustments?: Partial<Record<Tone, Adjustment>>;
|
||||
emphasisAdjustments?: Partial<Record<Emphasis, Adjustment>>;
|
||||
stateAdjustments?: Partial<Record<State, Adjustment>>;
|
||||
requiredContrastPairs?: Array<{
|
||||
fg: TokenKey;
|
||||
bg: TokenKey;
|
||||
minContrast: number;
|
||||
largeText?: boolean;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type ThemeSeedV2 = {
|
||||
accent: `#${string}`;
|
||||
background: `#${string}`;
|
||||
ink: `#${string}`;
|
||||
};
|
||||
|
||||
export type ColorThemeDefinitionV1 = {
|
||||
export type ColorThemeDefinitionV2 = {
|
||||
id: string;
|
||||
mode: Mode;
|
||||
seed: ThemeSeed;
|
||||
seed: ThemeSeedV2;
|
||||
};
|
||||
|
||||
export type ThemeCatalog = Record<Mode, Record<string, ColorThemeDefinitionV1>>;
|
||||
|
||||
export type Element = (typeof TOKEN_ELEMENTS)[number];
|
||||
export type Emphasis = (typeof TOKEN_EMPHASIS)[number];
|
||||
export type UiState = (typeof TOKEN_UI_STATES)[number];
|
||||
export type TaskSemanticState = (typeof TASK_SEMANTIC_STATES)[number];
|
||||
export type StatusTone = `status-${TaskSemanticState}`;
|
||||
export type FixedTone = (typeof FIXED_TONES)[number];
|
||||
export type Tone = 'neutral' | 'brand' | StatusTone | FixedTone;
|
||||
export type TokenKey = `${Element}.${Tone}.${Emphasis}.${UiState}`;
|
||||
export type ThemeCatalogV2 = Record<
|
||||
Mode,
|
||||
Record<string, ColorThemeDefinitionV2>
|
||||
>;
|
||||
|
||||
export type ThemeTokens = Partial<Record<TokenKey, string>>;
|
||||
|
||||
export type ResolvedThemeV1 = {
|
||||
contract: ThemeContractV1;
|
||||
seed: ThemeSeed;
|
||||
export type ContrastDiagnostic = {
|
||||
fg: TokenKey;
|
||||
bg: TokenKey;
|
||||
ratio: number;
|
||||
minRequired: number;
|
||||
passes: boolean;
|
||||
apcaLc: number;
|
||||
};
|
||||
|
||||
export type ThemeDiagnostics = {
|
||||
contrast: ContrastDiagnostic[];
|
||||
};
|
||||
|
||||
export type ResolvedThemeV2 = {
|
||||
contract: ThemeContractV2;
|
||||
seed: ThemeSeedV2;
|
||||
tokens: ThemeTokens;
|
||||
cssVariables: Record<string, string>;
|
||||
diagnostics: ThemeDiagnostics;
|
||||
};
|
||||
|
||||
// Backward-compatible type aliases during V2 cutover.
|
||||
export type ThemeSeed = ThemeSeedV2;
|
||||
export type ThemeCatalog = ThemeCatalogV2;
|
||||
export type UiState = State;
|
||||
|
|
|
|||
65
src/lib/themeTokens/verifier.test.ts
Normal file
65
src/lib/themeTokens/verifier.test.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { createDefaultThemeContractV2 } from './catalog';
|
||||
import { buildThemeV2 } from './engine';
|
||||
import { verifyThemeEngine } from './verifier';
|
||||
|
||||
describe('themeTokens v2 engine verifier', () => {
|
||||
it('produces zero errors across every registered theme/mode/contrast', () => {
|
||||
const report = verifyThemeEngine();
|
||||
const errors = report.findings.filter((f) => f.severity === 'error');
|
||||
if (errors.length > 0) {
|
||||
const preview = errors
|
||||
.slice(0, 5)
|
||||
.map(
|
||||
(e) =>
|
||||
`[${e.code}] ${e.mode}/${e.themeId}@${e.contrast}: ${e.message}`
|
||||
)
|
||||
.join('\n');
|
||||
throw new Error(
|
||||
`Theme engine emitted ${errors.length} errors. First few:\n${preview}`
|
||||
);
|
||||
}
|
||||
expect(errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('applies contrast clamping at bounds', () => {
|
||||
const low = buildThemeV2(
|
||||
createDefaultThemeContractV2('light', {
|
||||
themeId: 'eigent',
|
||||
contrast: -50,
|
||||
})
|
||||
);
|
||||
const high = buildThemeV2(
|
||||
createDefaultThemeContractV2('light', {
|
||||
themeId: 'eigent',
|
||||
contrast: 500,
|
||||
})
|
||||
);
|
||||
expect(low.contract.contrast).toBe(0);
|
||||
expect(high.contract.contrast).toBe(100);
|
||||
});
|
||||
|
||||
it('falls back to a registered theme for unknown ids', () => {
|
||||
const resolved = buildThemeV2(
|
||||
createDefaultThemeContractV2('light', {
|
||||
themeId: 'this-theme-does-not-exist',
|
||||
contrast: 43,
|
||||
})
|
||||
);
|
||||
expect(resolved.tokens['bg.neutral.subtle.default']).toBeDefined();
|
||||
});
|
||||
});
|
||||
407
src/lib/themeTokens/verifier.ts
Normal file
407
src/lib/themeTokens/verifier.ts
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
// V2 design-token engine verifier.
|
||||
//
|
||||
// The engine declares a short list of `contrastPairs` in semantic.color.json and
|
||||
// actively solves the foreground color to meet each pair's threshold. This
|
||||
// verifier:
|
||||
// 1. Exercises every (mode x theme x contrast-grid) variant.
|
||||
// 2. Forwards the engine's own declared-pair diagnostics.
|
||||
// 3. Checks **additional, undeclared** pairings that the engine does NOT solve
|
||||
// for — every status badge and every fixed tone's `strong` text on its
|
||||
// `subtle` surface. Those are what typically drift as transforms are tuned,
|
||||
// because the engine won't auto-correct them.
|
||||
// 4. Validates token coverage and CSS-value syntax.
|
||||
//
|
||||
// Structured output lets both the CLI script and vitest consume the same result.
|
||||
|
||||
import {
|
||||
DEFAULT_THEME_CATALOG,
|
||||
createDefaultThemeContractV2,
|
||||
getColorThemeDefinitionV2,
|
||||
} from './catalog';
|
||||
import { contrastRatio } from './colorMath';
|
||||
import { buildThemeV2 } from './engine';
|
||||
import {
|
||||
TOKEN_ELEMENTS,
|
||||
TOKEN_EMPHASIS,
|
||||
TOKEN_TONES,
|
||||
TOKEN_UI_STATES,
|
||||
type Mode,
|
||||
type ResolvedThemeV2,
|
||||
type ThemeCatalogV2,
|
||||
type TokenKey,
|
||||
} from './types';
|
||||
|
||||
export type VerifySeverity = 'error' | 'warn';
|
||||
|
||||
export type VerifyFinding = {
|
||||
severity: VerifySeverity;
|
||||
mode: Mode;
|
||||
themeId: string;
|
||||
contrast: number;
|
||||
code: string;
|
||||
message: string;
|
||||
tokenKey?: string;
|
||||
value?: string;
|
||||
ratio?: number;
|
||||
threshold?: number;
|
||||
};
|
||||
|
||||
export type VerifySummary = {
|
||||
variantsChecked: number;
|
||||
errors: number;
|
||||
warnings: number;
|
||||
};
|
||||
|
||||
export type VerifyReport = {
|
||||
summary: VerifySummary;
|
||||
findings: VerifyFinding[];
|
||||
};
|
||||
|
||||
// WCAG 2.1 AA minimums. "Large text" applies to >=18pt or bold >=14pt.
|
||||
const MIN_CONTRAST_NORMAL_AA = 4.5;
|
||||
const MIN_CONTRAST_LARGE_AA = 3.0;
|
||||
|
||||
// Contrast grid covers the engine's full input range. 43 is the app default.
|
||||
const DEFAULT_CONTRAST_GRID = [0, 25, 43, 75, 100];
|
||||
|
||||
// Tokens the engine is required to emit for every variant. Missing any of these
|
||||
// is an error, not a warning, since downstream components hard-reference them.
|
||||
const REQUIRED_CORE_TOKENS: TokenKey[] = [
|
||||
'bg.neutral.subtle.default',
|
||||
'bg.neutral.default.default',
|
||||
'bg.neutral.muted.default',
|
||||
'bg.neutral.strong.default',
|
||||
'bg.brand.default.default',
|
||||
'text.neutral.default.default',
|
||||
'text.neutral.muted.default',
|
||||
'text.neutral.subtle.default',
|
||||
'text.brand.inverse.default',
|
||||
'border.neutral.default.default',
|
||||
'border.neutral.strong.default',
|
||||
'ring.brand.default.focus',
|
||||
'icon.neutral.default.default',
|
||||
];
|
||||
|
||||
type Pairing = {
|
||||
bg: TokenKey;
|
||||
text: TokenKey;
|
||||
threshold: number;
|
||||
label: string;
|
||||
};
|
||||
|
||||
// Status + fixed-tone badges typically render `text.<tone>.strong.default` on
|
||||
// `bg.<tone>.subtle.default`. None of these are in semantic.color.json's
|
||||
// `contrastPairs` so the engine doesn't auto-solve them — perfect place for the
|
||||
// verifier to catch regressions.
|
||||
const AUXILIARY_BADGE_TONES: Array<{
|
||||
tone: string;
|
||||
threshold: number;
|
||||
label?: string;
|
||||
}> = [
|
||||
// Task lifecycle (10 tones)
|
||||
{ tone: 'status-running', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'status-splitting', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'status-pending', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'status-error', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'status-reassigning', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'status-completed', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'status-blocked', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'status-paused', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'status-skipped', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'status-cancelled', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
// Fixed tones (10)
|
||||
{ tone: 'single-agent', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'workforce', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'browser', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'terminal', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'document', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'success', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'caution', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'error', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'warning', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
{ tone: 'information', threshold: MIN_CONTRAST_NORMAL_AA },
|
||||
];
|
||||
|
||||
function buildAuxiliaryPairings(): Pairing[] {
|
||||
const pairs: Pairing[] = [];
|
||||
for (const { tone, threshold, label } of AUXILIARY_BADGE_TONES) {
|
||||
pairs.push({
|
||||
bg: `bg.${tone}.subtle.default` as TokenKey,
|
||||
text: `text.${tone}.strong.default` as TokenKey,
|
||||
threshold,
|
||||
label: label ?? `${tone} badge text on subtle surface`,
|
||||
});
|
||||
}
|
||||
// Body text on muted surface — not declared but ubiquitous.
|
||||
pairs.push({
|
||||
bg: 'bg.neutral.muted.default' as TokenKey,
|
||||
text: 'text.neutral.default.default' as TokenKey,
|
||||
threshold: MIN_CONTRAST_NORMAL_AA,
|
||||
label: 'body text on muted surface',
|
||||
});
|
||||
// Brand inverse text on brand hover/active (engine handles `default` via heuristic).
|
||||
pairs.push({
|
||||
bg: 'bg.brand.default.hover' as TokenKey,
|
||||
text: 'text.brand.inverse.hover' as TokenKey,
|
||||
threshold: MIN_CONTRAST_LARGE_AA,
|
||||
label: 'brand button label (hover)',
|
||||
});
|
||||
pairs.push({
|
||||
bg: 'bg.brand.default.active' as TokenKey,
|
||||
text: 'text.brand.inverse.active' as TokenKey,
|
||||
threshold: MIN_CONTRAST_LARGE_AA,
|
||||
label: 'brand button label (active)',
|
||||
});
|
||||
return pairs;
|
||||
}
|
||||
|
||||
const VALID_COLOR_RE =
|
||||
/^(#[0-9a-fA-F]{3,8}|rgba?\(\s*[0-9.\s,%-]+\)|transparent)$/;
|
||||
|
||||
function validateTokenValue(value: string | undefined): boolean {
|
||||
if (!value) return false;
|
||||
if (value.includes('NaN')) return false;
|
||||
return VALID_COLOR_RE.test(value.trim());
|
||||
}
|
||||
|
||||
function extractHex(value: string | undefined): `#${string}` | null {
|
||||
if (!value) return null;
|
||||
const trimmed = value.trim().toLowerCase();
|
||||
if (/^#[0-9a-f]{6}$/.test(trimmed)) return trimmed as `#${string}`;
|
||||
// rgba(r,g,b,a) — only safe to compare when alpha is 1; otherwise the visual
|
||||
// contrast depends on what's behind the translucent layer.
|
||||
const m = trimmed.match(
|
||||
/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(0|0?\.\d+|1(?:\.0+)?)\s*)?\)$/
|
||||
);
|
||||
if (!m) return null;
|
||||
const alpha = m[4] !== undefined ? Number(m[4]) : 1;
|
||||
if (alpha < 1) return null;
|
||||
const toHex = (n: string) => Number(n).toString(16).padStart(2, '0');
|
||||
return `#${toHex(m[1])}${toHex(m[2])}${toHex(m[3])}` as `#${string}`;
|
||||
}
|
||||
|
||||
function pushFinding(
|
||||
findings: VerifyFinding[],
|
||||
base: Omit<VerifyFinding, 'severity' | 'code' | 'message'>,
|
||||
severity: VerifySeverity,
|
||||
code: string,
|
||||
message: string,
|
||||
extra: Partial<VerifyFinding> = {}
|
||||
) {
|
||||
findings.push({ ...base, severity, code, message, ...extra });
|
||||
}
|
||||
|
||||
export type VerifyOptions = {
|
||||
catalog?: ThemeCatalogV2;
|
||||
contrastGrid?: number[];
|
||||
modes?: Mode[];
|
||||
themeIds?: string[];
|
||||
extraPairings?: Pairing[];
|
||||
// When true, elevate auxiliary contrast failures to 'error' severity so CI
|
||||
// blocks on them. Engine-declared pair failures are always errors.
|
||||
strictAuxContrast?: boolean;
|
||||
};
|
||||
|
||||
export function verifyThemeEngine(options: VerifyOptions = {}): VerifyReport {
|
||||
const catalog = options.catalog ?? DEFAULT_THEME_CATALOG;
|
||||
const contrastGrid = options.contrastGrid ?? DEFAULT_CONTRAST_GRID;
|
||||
const modes: Mode[] = options.modes ?? ['light', 'dark'];
|
||||
const auxSeverity: VerifySeverity = options.strictAuxContrast
|
||||
? 'error'
|
||||
: 'warn';
|
||||
const auxPairings = [
|
||||
...buildAuxiliaryPairings(),
|
||||
...(options.extraPairings ?? []),
|
||||
];
|
||||
|
||||
const findings: VerifyFinding[] = [];
|
||||
let variantsChecked = 0;
|
||||
|
||||
for (const mode of modes) {
|
||||
const modeCatalog = catalog[mode] ?? {};
|
||||
const themeIds = options.themeIds ?? Object.keys(modeCatalog);
|
||||
|
||||
for (const themeId of themeIds) {
|
||||
if (!modeCatalog[themeId]) {
|
||||
findings.push({
|
||||
severity: 'error',
|
||||
mode,
|
||||
themeId,
|
||||
contrast: -1,
|
||||
code: 'unknown-theme',
|
||||
message: `Theme "${themeId}" not registered for mode "${mode}".`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const contrast of contrastGrid) {
|
||||
variantsChecked += 1;
|
||||
const base = { mode, themeId, contrast };
|
||||
|
||||
let resolved: ResolvedThemeV2;
|
||||
try {
|
||||
resolved = buildThemeV2(
|
||||
createDefaultThemeContractV2(mode, { themeId, contrast }),
|
||||
catalog
|
||||
);
|
||||
} catch (err) {
|
||||
pushFinding(
|
||||
findings,
|
||||
base,
|
||||
'error',
|
||||
'engine-throw',
|
||||
`buildThemeV2 threw: ${(err as Error).message}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Contract sanity.
|
||||
if (resolved.contract.mode !== mode) {
|
||||
pushFinding(
|
||||
findings,
|
||||
base,
|
||||
'error',
|
||||
'contract-mode-drift',
|
||||
`Resolved contract mode "${resolved.contract.mode}" !== requested "${mode}".`
|
||||
);
|
||||
}
|
||||
if (resolved.contract.themeId !== themeId) {
|
||||
pushFinding(
|
||||
findings,
|
||||
base,
|
||||
'error',
|
||||
'contract-theme-drift',
|
||||
`Resolved contract themeId "${resolved.contract.themeId}" !== requested "${themeId}".`
|
||||
);
|
||||
}
|
||||
|
||||
// Required core tokens.
|
||||
for (const key of REQUIRED_CORE_TOKENS) {
|
||||
const value = resolved.tokens[key];
|
||||
if (!validateTokenValue(value)) {
|
||||
pushFinding(
|
||||
findings,
|
||||
base,
|
||||
'error',
|
||||
'missing-or-invalid-token',
|
||||
`Required token "${key}" is missing or not a valid color.`,
|
||||
{ tokenKey: key, value }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// All emitted tokens must be valid CSS colors.
|
||||
for (const [key, value] of Object.entries(resolved.tokens)) {
|
||||
if (!validateTokenValue(value)) {
|
||||
pushFinding(
|
||||
findings,
|
||||
base,
|
||||
'error',
|
||||
'invalid-token-value',
|
||||
`Token "${key}" resolved to invalid value "${value}".`,
|
||||
{ tokenKey: key, value }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Engine-declared pairs (the engine actively solves for these; if any
|
||||
// fail, the solver couldn't converge — that's always an error).
|
||||
for (const diag of resolved.diagnostics.contrast) {
|
||||
if (!diag.passes) {
|
||||
pushFinding(
|
||||
findings,
|
||||
base,
|
||||
'error',
|
||||
'engine-contrast-unsolved',
|
||||
`Declared pair ${diag.fg} on ${diag.bg}: ratio ${diag.ratio.toFixed(2)} < ${diag.minRequired} (solver failed to converge).`,
|
||||
{
|
||||
tokenKey: diag.fg,
|
||||
ratio: diag.ratio,
|
||||
threshold: diag.minRequired,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Auxiliary (undeclared) pairings — drift detector.
|
||||
for (const pairing of auxPairings) {
|
||||
const bg = resolved.tokens[pairing.bg];
|
||||
const text = resolved.tokens[pairing.text];
|
||||
if (!bg || !text) continue;
|
||||
|
||||
const bgHex = extractHex(bg);
|
||||
const textHex = extractHex(text);
|
||||
if (!bgHex || !textHex) continue;
|
||||
|
||||
const ratio = contrastRatio(bgHex, textHex);
|
||||
if (ratio < pairing.threshold) {
|
||||
pushFinding(
|
||||
findings,
|
||||
base,
|
||||
auxSeverity,
|
||||
'aux-contrast-below-threshold',
|
||||
`${pairing.label}: contrast ${ratio.toFixed(2)} < ${pairing.threshold} (bg=${pairing.bg}, text=${pairing.text}).`,
|
||||
{
|
||||
tokenKey: pairing.text,
|
||||
value: `bg=${bg} / text=${text}`,
|
||||
ratio,
|
||||
threshold: pairing.threshold,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const errors = findings.filter((f) => f.severity === 'error').length;
|
||||
const warnings = findings.filter((f) => f.severity === 'warn').length;
|
||||
return {
|
||||
summary: { variantsChecked, errors, warnings },
|
||||
findings,
|
||||
};
|
||||
}
|
||||
|
||||
export function getDefaultContrastGrid(): number[] {
|
||||
return [...DEFAULT_CONTRAST_GRID];
|
||||
}
|
||||
|
||||
export function listRegisteredThemes(
|
||||
catalog: ThemeCatalogV2 = DEFAULT_THEME_CATALOG
|
||||
): Array<{ mode: Mode; id: string }> {
|
||||
const out: Array<{ mode: Mode; id: string }> = [];
|
||||
for (const mode of ['light', 'dark'] as Mode[]) {
|
||||
for (const id of Object.keys(catalog[mode] ?? {})) {
|
||||
const def = getColorThemeDefinitionV2(mode, id, catalog);
|
||||
if (def.id !== id) {
|
||||
throw new Error(
|
||||
`Catalog getter drift: asked for "${id}" in mode "${mode}", got "${def.id}".`
|
||||
);
|
||||
}
|
||||
out.push({ mode, id });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Expose axis constants for scripts that want to reason about token coverage.
|
||||
export const TOKEN_AXES = {
|
||||
elements: TOKEN_ELEMENTS,
|
||||
emphasis: TOKEN_EMPHASIS,
|
||||
states: TOKEN_UI_STATES,
|
||||
tones: TOKEN_TONES,
|
||||
} as const;
|
||||
|
|
@ -1262,24 +1262,38 @@ export default function SettingModels() {
|
|||
</div>
|
||||
<div className="gap-2 flex items-center">
|
||||
{form[idx].prefer ? (
|
||||
<span className="px-2 py-1 text-label-xs font-bold text-ds-text-status-completed-strong-default inline-flex items-center rounded-full">
|
||||
<Button
|
||||
variant="primary"
|
||||
tone="success"
|
||||
size="xs"
|
||||
textWeight="bold"
|
||||
disabled
|
||||
buttonRadius="full"
|
||||
>
|
||||
{t('setting.default')}
|
||||
</span>
|
||||
) : (
|
||||
</Button>
|
||||
) : canSwitch ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
tone="neutral"
|
||||
size="xs"
|
||||
disabled={!canSwitch || loading === idx}
|
||||
textWeight="bold"
|
||||
disabled={loading === idx}
|
||||
buttonRadius="full"
|
||||
onClick={() => handleSwitch(idx, true)}
|
||||
className={
|
||||
canSwitch
|
||||
? 'bg-button-transparent-fill-hover !text-ds-text-neutral-muted-default hover:bg-button-transparent-fill-active inline-flex items-center rounded-full shadow-none'
|
||||
: 'gap-1.5 inline-flex items-center'
|
||||
}
|
||||
>
|
||||
{!canSwitch
|
||||
? t('setting.not-configured')
|
||||
: t('setting.set-as-default')}
|
||||
{t('setting.set-as-default')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
tone="neutral"
|
||||
size="xs"
|
||||
textWeight="bold"
|
||||
disabled
|
||||
buttonRadius="full"
|
||||
>
|
||||
{t('setting.not-configured')}
|
||||
</Button>
|
||||
)}
|
||||
{form[idx].provider_id ? (
|
||||
|
|
@ -1506,7 +1520,7 @@ export default function SettingModels() {
|
|||
onClick={() => handleLocalSwitch(true)}
|
||||
className={
|
||||
isConfigured
|
||||
? 'bg-button-transparent-fill-hover !text-ds-text-neutral-muted-default rounded-full shadow-none'
|
||||
? 'bg-ds-bg-neutral-default-hover !text-ds-text-neutral-muted-default hover:bg-ds-bg-neutral-default-active rounded-full shadow-none'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export default function SkillDeleteDialog({
|
|||
})}
|
||||
confirmText={t('layout.delete')}
|
||||
cancelText={t('layout.cancel')}
|
||||
confirmVariant="cuation"
|
||||
confirmVariant="caution"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -665,7 +665,7 @@ export default function SkillUploadDialog({
|
|||
message="There's an existing skill with the same name. Uploading this skill will replace the existing one, which can't be restored."
|
||||
confirmText="Update and Replace"
|
||||
cancelText="Cancel"
|
||||
confirmVariant="cuation"
|
||||
confirmVariant="caution"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ export default function CDP() {
|
|||
})}
|
||||
confirmText={t('layout.remove')}
|
||||
cancelText={t('layout.cancel')}
|
||||
confirmVariant="cuation"
|
||||
confirmVariant="caution"
|
||||
/>
|
||||
|
||||
<div className="px-6 pb-6 pt-8 flex w-full items-center justify-between">
|
||||
|
|
@ -212,10 +212,14 @@ export default function CDP() {
|
|||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
textWeight="semibold"
|
||||
buttonContent="text"
|
||||
buttonRadius="lg"
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
onClick={handleConnectExistingBrowser}
|
||||
>
|
||||
<Link2 className="h-4 w-4 text-button-tertiery-text-default" />
|
||||
<Link2 />
|
||||
{t('layout.connect-existing-browser')}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -28,31 +28,29 @@ import {
|
|||
DEFAULT_THEME_CATALOG,
|
||||
} from '@/lib/themeTokens/catalog';
|
||||
import type {
|
||||
ColorThemeDefinitionV1,
|
||||
ColorThemeDefinitionV2,
|
||||
Mode,
|
||||
ThemeCatalog,
|
||||
ThemeSeed,
|
||||
} from '@/lib/themeTokens/types';
|
||||
import { useAuthStore, type WorkspaceMainBackground } from '@/store/authStore';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type SaveStatus = {
|
||||
type: 'success' | 'error';
|
||||
message: string;
|
||||
} | null;
|
||||
|
||||
const HEX_COLOR_RE = /^#[0-9a-fA-F]{6}$/;
|
||||
const NEW_TEMPLATE_TAB_ID = '__new_template__';
|
||||
const DEFAULT_EDITABLE_THEME_IDS = [
|
||||
'eigent',
|
||||
'claude',
|
||||
'codex',
|
||||
'camel',
|
||||
] as const;
|
||||
const CUSTOM_THEME_IDS = ['custom-1', 'custom-2'] as const;
|
||||
|
||||
function normalizeThemeId(input: string): string {
|
||||
return input
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9-_]+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '');
|
||||
}
|
||||
type ThemeOption = {
|
||||
id: string;
|
||||
label: string;
|
||||
isDefault: boolean;
|
||||
};
|
||||
|
||||
function normalizeHexColor(input: string): `#${string}` | null {
|
||||
const trimmed = input.trim();
|
||||
|
|
@ -74,21 +72,10 @@ function buildMergedCatalog(customThemeCatalog: ThemeCatalog): ThemeCatalog {
|
|||
};
|
||||
}
|
||||
|
||||
function extractThemeList(
|
||||
mode: Mode,
|
||||
catalog: ThemeCatalog
|
||||
): ColorThemeDefinitionV1[] {
|
||||
return Object.values(catalog[mode] ?? {}).sort((a, b) =>
|
||||
a.id.localeCompare(b.id)
|
||||
);
|
||||
}
|
||||
|
||||
function nextAutoThemeId(mode: Mode, catalog: ThemeCatalog): string {
|
||||
let index = 1;
|
||||
while (catalog[mode][`custom-${index}`]) {
|
||||
index += 1;
|
||||
}
|
||||
return `custom-${index}`;
|
||||
function formatThemeLabel(id: string): string {
|
||||
if (id === 'custom-1') return 'Custom 1';
|
||||
if (id === 'custom-2') return 'Custom 2';
|
||||
return id;
|
||||
}
|
||||
|
||||
function ColorSeedEditor({
|
||||
|
|
@ -101,12 +88,17 @@ function ColorSeedEditor({
|
|||
onChange: (value: string) => void;
|
||||
}) {
|
||||
const normalizedPreview = normalizeHexColor(value) ?? '#000000';
|
||||
|
||||
return (
|
||||
<div className="gap-2 grid grid-cols-[96px_minmax(0,1fr)_40px] items-center">
|
||||
<div className="text-body-sm font-semibold text-ds-text-neutral-default-default">
|
||||
{label}
|
||||
</div>
|
||||
<Input value={value} onChange={(e) => onChange(e.target.value)} />
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
note={normalizeHexColor(value) ? '' : 'Hex format: #1a2b3c'}
|
||||
/>
|
||||
<div
|
||||
className="h-9 w-9 rounded-md border-ds-border-neutral-default-default border"
|
||||
style={{ backgroundColor: normalizedPreview }}
|
||||
|
|
@ -115,6 +107,38 @@ function ColorSeedEditor({
|
|||
);
|
||||
}
|
||||
|
||||
function ModePanel({
|
||||
title,
|
||||
description,
|
||||
active,
|
||||
onClick,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={[
|
||||
'rounded-xl px-4 py-4 border text-left transition-colors',
|
||||
active
|
||||
? 'border-ds-border-brand-default-focus bg-ds-bg-brand-subtle-default'
|
||||
: 'border-ds-border-neutral-subtle-default bg-ds-bg-neutral-subtle-default hover:bg-ds-bg-neutral-subtle-hover',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="text-body-sm font-semibold text-ds-text-neutral-default-default">
|
||||
{title}
|
||||
</div>
|
||||
<div className="mt-1 text-label-sm text-ds-text-neutral-muted-default">
|
||||
{description}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AppearanceSettings() {
|
||||
const { t } = useTranslation();
|
||||
const appearanceMode = useAuthStore((s) => s.appearanceMode);
|
||||
|
|
@ -130,6 +154,8 @@ export default function AppearanceSettings() {
|
|||
const removeCustomThemeTemplate = useAuthStore(
|
||||
(s) => s.removeCustomThemeTemplate
|
||||
);
|
||||
const themeContrast = useAuthStore((s) => s.themeContrast);
|
||||
const setThemeContrast = useAuthStore((s) => s.setThemeContrast);
|
||||
const workspaceMainBackground = useAuthStore(
|
||||
(s) => s.workspaceMainBackground
|
||||
);
|
||||
|
|
@ -139,142 +165,163 @@ export default function AppearanceSettings() {
|
|||
|
||||
const activeMode: Mode =
|
||||
appearanceMode === 'system' ? appearance : appearanceMode;
|
||||
|
||||
const mergedCatalog = useMemo(
|
||||
() => buildMergedCatalog(customThemeCatalog),
|
||||
[customThemeCatalog]
|
||||
);
|
||||
const themeList = useMemo(
|
||||
() => extractThemeList(activeMode, mergedCatalog),
|
||||
[activeMode, mergedCatalog]
|
||||
);
|
||||
const selectedThemeId =
|
||||
activeMode === 'dark' ? darkColorThemeId : lightColorThemeId;
|
||||
const selectedTheme =
|
||||
themeList.find((theme) => theme.id === selectedThemeId) ??
|
||||
themeList[0] ??
|
||||
null;
|
||||
|
||||
const [activeThemeTab, setActiveThemeTab] = useState<string>(
|
||||
selectedThemeId || NEW_TEMPLATE_TAB_ID
|
||||
const themeOptions = useMemo<ThemeOption[]>(
|
||||
() => [
|
||||
...DEFAULT_EDITABLE_THEME_IDS.map((id) => ({
|
||||
id,
|
||||
label: formatThemeLabel(id),
|
||||
isDefault: true,
|
||||
})),
|
||||
...CUSTOM_THEME_IDS.map((id) => ({
|
||||
id,
|
||||
label: formatThemeLabel(id),
|
||||
isDefault: false,
|
||||
})),
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const allowedThemeIds = useMemo(
|
||||
() => themeOptions.map((option) => option.id),
|
||||
[themeOptions]
|
||||
);
|
||||
|
||||
const modeThemeId =
|
||||
activeMode === 'dark' ? darkColorThemeId : lightColorThemeId;
|
||||
|
||||
const [activeThemeId, setActiveThemeId] = useState<string>(
|
||||
allowedThemeIds.includes(modeThemeId) ? modeThemeId : DEFAULT_COLOR_THEME_ID
|
||||
);
|
||||
const [templateName, setTemplateName] = useState('');
|
||||
const [accent, setAccent] = useState('');
|
||||
const [background, setBackground] = useState('');
|
||||
const [ink, setInk] = useState('');
|
||||
const [saveStatus, setSaveStatus] = useState<SaveStatus>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedTheme && themeList.length > 0) {
|
||||
setColorThemeForMode(activeMode, themeList[0].id);
|
||||
const nextThemeId = allowedThemeIds.includes(modeThemeId)
|
||||
? modeThemeId
|
||||
: DEFAULT_COLOR_THEME_ID;
|
||||
setActiveThemeId(nextThemeId);
|
||||
|
||||
if (modeThemeId !== nextThemeId) {
|
||||
setColorThemeForMode(activeMode, nextThemeId);
|
||||
}
|
||||
}, [activeMode, selectedTheme, setColorThemeForMode, themeList]);
|
||||
}, [activeMode, allowedThemeIds, modeThemeId, setColorThemeForMode]);
|
||||
|
||||
const fallbackSeed =
|
||||
DEFAULT_THEME_CATALOG[activeMode][DEFAULT_COLOR_THEME_ID]?.seed ??
|
||||
Object.values(DEFAULT_THEME_CATALOG[activeMode])[0]?.seed;
|
||||
|
||||
const activeTheme = useMemo<ColorThemeDefinitionV2 | null>(() => {
|
||||
const fromMerged = mergedCatalog[activeMode]?.[activeThemeId];
|
||||
if (fromMerged) return fromMerged;
|
||||
|
||||
const fromDefault = DEFAULT_THEME_CATALOG[activeMode]?.[activeThemeId];
|
||||
if (fromDefault) {
|
||||
return {
|
||||
id: activeThemeId,
|
||||
mode: activeMode,
|
||||
seed: fromDefault.seed,
|
||||
};
|
||||
}
|
||||
|
||||
if (!fallbackSeed) return null;
|
||||
return {
|
||||
id: activeThemeId,
|
||||
mode: activeMode,
|
||||
seed: fallbackSeed,
|
||||
};
|
||||
}, [activeMode, activeThemeId, fallbackSeed, mergedCatalog]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeThemeTab === NEW_TEMPLATE_TAB_ID) return;
|
||||
const fallbackTab = selectedTheme?.id ?? themeList[0]?.id ?? '';
|
||||
if (!fallbackTab) return;
|
||||
if (!themeList.some((theme) => theme.id === activeThemeTab)) {
|
||||
setActiveThemeTab(fallbackTab);
|
||||
}
|
||||
}, [activeThemeTab, selectedTheme?.id, themeList]);
|
||||
if (!activeTheme) return;
|
||||
setAccent(activeTheme.seed.accent);
|
||||
setBackground(activeTheme.seed.background);
|
||||
setInk(activeTheme.seed.ink);
|
||||
}, [
|
||||
activeTheme?.id,
|
||||
activeTheme?.seed.accent,
|
||||
activeTheme?.seed.background,
|
||||
activeTheme?.seed.ink,
|
||||
activeMode,
|
||||
]);
|
||||
|
||||
const currentTheme =
|
||||
activeThemeTab === NEW_TEMPLATE_TAB_ID
|
||||
? null
|
||||
: (themeList.find((theme) => theme.id === activeThemeTab) ??
|
||||
selectedTheme ??
|
||||
null);
|
||||
const isCurrentCustom = Boolean(
|
||||
currentTheme && customThemeCatalog[activeMode][currentTheme.id]
|
||||
);
|
||||
const commitThemeSeed = (
|
||||
nextAccent: string,
|
||||
nextBackground: string,
|
||||
nextInk: string
|
||||
) => {
|
||||
const accentHex = normalizeHexColor(nextAccent);
|
||||
const backgroundHex = normalizeHexColor(nextBackground);
|
||||
const inkHex = normalizeHexColor(nextInk);
|
||||
if (!accentHex || !backgroundHex || !inkHex || !activeTheme) return;
|
||||
|
||||
const resetDraftTemplate = useCallback(() => {
|
||||
setTemplateName('');
|
||||
setAccent('');
|
||||
setBackground('');
|
||||
setInk('');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeThemeTab === NEW_TEMPLATE_TAB_ID) {
|
||||
resetDraftTemplate();
|
||||
setSaveStatus(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentTheme) return;
|
||||
setAccent(currentTheme.seed.accent);
|
||||
setBackground(currentTheme.seed.background);
|
||||
setInk(currentTheme.seed.ink);
|
||||
setSaveStatus(null);
|
||||
}, [activeThemeTab, currentTheme, resetDraftTemplate]);
|
||||
|
||||
const createTemplate = () => {
|
||||
const accentHex = normalizeHexColor(accent);
|
||||
const backgroundHex = normalizeHexColor(background);
|
||||
const inkHex = normalizeHexColor(ink);
|
||||
const explicitId = normalizeThemeId(templateName);
|
||||
const themeId = explicitId || nextAutoThemeId(activeMode, mergedCatalog);
|
||||
|
||||
if (!accentHex || !backgroundHex || !inkHex) {
|
||||
setSaveStatus({
|
||||
type: 'error',
|
||||
message: 'Colors must be valid hex values (example: #1a2b3c).',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (mergedCatalog[activeMode][themeId]) {
|
||||
setSaveStatus({
|
||||
type: 'error',
|
||||
message: 'Theme id already exists. Choose another id.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const seed: ThemeSeed = {
|
||||
const nextSeed: ThemeSeed = {
|
||||
accent: accentHex,
|
||||
background: backgroundHex,
|
||||
ink: inkHex,
|
||||
};
|
||||
|
||||
upsertCustomThemeTemplate(activeMode, themeId, seed);
|
||||
resetDraftTemplate();
|
||||
setSaveStatus({
|
||||
type: 'success',
|
||||
message: `Created "${themeId}" in ${activeMode} themes.`,
|
||||
});
|
||||
};
|
||||
|
||||
const deleteTemplate = () => {
|
||||
if (!currentTheme) return;
|
||||
if (!isCurrentCustom) {
|
||||
setSaveStatus({
|
||||
type: 'error',
|
||||
message: 'Built-in themes cannot be deleted.',
|
||||
});
|
||||
const current = activeTheme.seed;
|
||||
if (
|
||||
current.accent === nextSeed.accent &&
|
||||
current.background === nextSeed.background &&
|
||||
current.ink === nextSeed.ink
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fallbackTheme =
|
||||
themeList.find(
|
||||
(theme) =>
|
||||
theme.id !== currentTheme.id && theme.id === DEFAULT_COLOR_THEME_ID
|
||||
) ?? themeList.find((theme) => theme.id !== currentTheme.id);
|
||||
|
||||
if (fallbackTheme) {
|
||||
setColorThemeForMode(activeMode, fallbackTheme.id);
|
||||
setActiveThemeTab(fallbackTheme.id);
|
||||
}
|
||||
removeCustomThemeTemplate(activeMode, currentTheme.id);
|
||||
setSaveStatus({
|
||||
type: 'success',
|
||||
message: `Deleted "${currentTheme.id}".`,
|
||||
});
|
||||
upsertCustomThemeTemplate(activeMode, activeThemeId, nextSeed);
|
||||
};
|
||||
|
||||
const openDraftTemplate = () => {
|
||||
setActiveThemeTab(NEW_TEMPLATE_TAB_ID);
|
||||
const handleAccentChange = (value: string) => {
|
||||
setAccent(value);
|
||||
commitThemeSeed(value, background, ink);
|
||||
};
|
||||
|
||||
const handleBackgroundChange = (value: string) => {
|
||||
setBackground(value);
|
||||
commitThemeSeed(accent, value, ink);
|
||||
};
|
||||
|
||||
const handleInkChange = (value: string) => {
|
||||
setInk(value);
|
||||
commitThemeSeed(accent, background, value);
|
||||
};
|
||||
|
||||
const handleThemeChange = (themeId: string) => {
|
||||
setActiveThemeId(themeId);
|
||||
setColorThemeForMode(activeMode, themeId);
|
||||
};
|
||||
|
||||
const resetActiveTheme = () => {
|
||||
if (!activeTheme || !fallbackSeed) return;
|
||||
|
||||
const isDefaultTheme = DEFAULT_EDITABLE_THEME_IDS.includes(
|
||||
activeThemeId as (typeof DEFAULT_EDITABLE_THEME_IDS)[number]
|
||||
);
|
||||
|
||||
if (isDefaultTheme) {
|
||||
const defaultSeed =
|
||||
DEFAULT_THEME_CATALOG[activeMode][activeThemeId]?.seed;
|
||||
if (defaultSeed) {
|
||||
removeCustomThemeTemplate(activeMode, activeThemeId);
|
||||
setAccent(defaultSeed.accent);
|
||||
setBackground(defaultSeed.background);
|
||||
setInk(defaultSeed.ink);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
upsertCustomThemeTemplate(activeMode, activeThemeId, fallbackSeed);
|
||||
setAccent(fallbackSeed.accent);
|
||||
setBackground(fallbackSeed.background);
|
||||
setInk(fallbackSeed.ink);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -294,30 +341,28 @@ export default function AppearanceSettings() {
|
|||
<div className="text-body-base font-bold text-ds-text-neutral-default-default">
|
||||
Mode
|
||||
</div>
|
||||
<Select
|
||||
value={appearanceMode}
|
||||
onValueChange={(value) =>
|
||||
setAppearanceMode(value as Mode | 'system')
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-64">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-input-bg-default border">
|
||||
<SelectGroup>
|
||||
<SelectItem value="light">{t('setting.light')}</SelectItem>
|
||||
<SelectItem value="dark">{t('setting.dark')}</SelectItem>
|
||||
<SelectItem value="system">
|
||||
{t('setting.system-default')}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-body-sm text-ds-text-neutral-muted-default">
|
||||
{appearanceMode === 'system'
|
||||
? `Following system. Current system mode: ${appearance}.`
|
||||
: `Using ${appearanceMode} mode.`}
|
||||
|
||||
<div className="gap-3 grid grid-cols-2">
|
||||
<ModePanel
|
||||
title={t('setting.light')}
|
||||
description="Use light mode as the active UI mode."
|
||||
active={appearanceMode === 'light'}
|
||||
onClick={() => setAppearanceMode('light')}
|
||||
/>
|
||||
<ModePanel
|
||||
title={t('setting.dark')}
|
||||
description="Use dark mode as the active UI mode."
|
||||
active={appearanceMode === 'dark'}
|
||||
onClick={() => setAppearanceMode('dark')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ModePanel
|
||||
title={t('setting.system-default')}
|
||||
description={`Follow system. Current system mode: ${appearance}.`}
|
||||
active={appearanceMode === 'system'}
|
||||
onClick={() => setAppearanceMode('system')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="item-center gap-4 rounded-2xl bg-ds-bg-neutral-default-default px-6 py-4 flex flex-col">
|
||||
|
|
@ -326,120 +371,67 @@ export default function AppearanceSettings() {
|
|||
Schema Customization
|
||||
</div>
|
||||
<div className="text-body-sm text-ds-text-neutral-muted-default">
|
||||
Theme tabs are mode-specific. Select a current theme for
|
||||
Add/Delete. Use New Template for Create.
|
||||
4 default themes + 2 custom slots. Changes are auto-saved and
|
||||
applied live.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="gap-3 flex w-full flex-col">
|
||||
<div className="w-full overflow-x-auto">
|
||||
<Tabs
|
||||
value={activeThemeTab}
|
||||
onValueChange={(value) => {
|
||||
setSaveStatus(null);
|
||||
setActiveThemeTab(value);
|
||||
if (value !== NEW_TEMPLATE_TAB_ID) {
|
||||
setColorThemeForMode(activeMode, value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="gap-3 flex w-full items-center justify-between">
|
||||
<div className="min-w-0 flex-1 overflow-x-auto">
|
||||
<Tabs value={activeThemeId} onValueChange={handleThemeChange}>
|
||||
<TabsList className="min-w-max">
|
||||
{themeList.map((theme) => {
|
||||
const isCustom = Boolean(
|
||||
customThemeCatalog[activeMode][theme.id]
|
||||
);
|
||||
return (
|
||||
<TabsTrigger key={theme.id} value={theme.id}>
|
||||
{isCustom ? `${theme.id} *` : theme.id}
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
<TabsTrigger value={NEW_TEMPLATE_TAB_ID}>
|
||||
+ New Template
|
||||
</TabsTrigger>
|
||||
{themeOptions.map((option) => (
|
||||
<TabsTrigger key={option.id} value={option.id}>
|
||||
{option.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<div className="gap-2 flex">
|
||||
{activeThemeTab === NEW_TEMPLATE_TAB_ID ? (
|
||||
<Button variant="secondary" onClick={createTemplate}>
|
||||
Create
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button variant="secondary" onClick={openDraftTemplate}>
|
||||
Add
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={deleteTemplate}
|
||||
disabled={!isCurrentCustom}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="secondary" onClick={resetActiveTheme}>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
title="New Template ID"
|
||||
placeholder="my-theme-template (optional, auto-generated when empty)"
|
||||
value={templateName}
|
||||
onChange={(e) => setTemplateName(e.target.value)}
|
||||
note="Use lowercase letters, numbers, hyphen, or underscore."
|
||||
/>
|
||||
|
||||
<div className="gap-3 flex flex-col">
|
||||
<ColorSeedEditor
|
||||
label="Accent"
|
||||
value={accent}
|
||||
onChange={setAccent}
|
||||
onChange={handleAccentChange}
|
||||
/>
|
||||
<ColorSeedEditor
|
||||
label="Background"
|
||||
value={background}
|
||||
onChange={setBackground}
|
||||
onChange={handleBackgroundChange}
|
||||
/>
|
||||
<ColorSeedEditor
|
||||
label="Ink"
|
||||
value={ink}
|
||||
onChange={handleInkChange}
|
||||
/>
|
||||
<ColorSeedEditor label="Ink" value={ink} onChange={setInk} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="h-16 rounded-lg border-ds-border-neutral-default-default w-full overflow-hidden border"
|
||||
style={{
|
||||
backgroundColor: normalizeHexColor(background) ?? '#ffffff',
|
||||
}}
|
||||
>
|
||||
<div className="flex h-full">
|
||||
<div
|
||||
className="w-4 shrink-0 self-stretch"
|
||||
style={{
|
||||
backgroundColor: normalizeHexColor(accent) ?? '#000000',
|
||||
}}
|
||||
/>
|
||||
<div className="min-w-0 p-2 flex flex-1 flex-col justify-end">
|
||||
<span
|
||||
className="text-label-sm font-semibold truncate text-left capitalize"
|
||||
style={{ color: normalizeHexColor(ink) ?? '#1d1d1d' }}
|
||||
>
|
||||
Preview
|
||||
</span>
|
||||
<div className="gap-2 flex flex-col">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-body-sm font-semibold text-ds-text-neutral-default-default">
|
||||
Contract (Contrast)
|
||||
</div>
|
||||
<div className="text-body-sm font-semibold text-ds-text-neutral-muted-default">
|
||||
{themeContrast}
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
value={themeContrast}
|
||||
onChange={(e) => setThemeContrast(Number(e.target.value))}
|
||||
className="h-2 bg-ds-bg-neutral-strong-default w-full cursor-pointer appearance-none rounded-full accent-[var(--ds-bg-brand-default-default)]"
|
||||
aria-label="Theme contrast"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{saveStatus ? (
|
||||
<div
|
||||
className={
|
||||
saveStatus.type === 'error'
|
||||
? 'text-body-sm text-ds-text-status-error-strong-default'
|
||||
: 'text-body-sm text-ds-text-status-completed-strong-default'
|
||||
}
|
||||
>
|
||||
{saveStatus.message}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="item-center rounded-2xl bg-ds-bg-neutral-default-default px-6 py-4 flex flex-row justify-between">
|
||||
|
|
|
|||
|
|
@ -218,13 +218,21 @@ export default function SettingGeneral() {
|
|||
window.location.href = `https://www.eigent.ai/dashboard?email=${authStore.email}`;
|
||||
}}
|
||||
variant="primary"
|
||||
textWeight="semibold"
|
||||
buttonContent="text"
|
||||
buttonRadius="lg"
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
>
|
||||
<Settings className="h-4 w-4 text-button-primary-icon-default" />
|
||||
<Settings />
|
||||
{t('setting.manage')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
textWeight="semibold"
|
||||
buttonContent="text"
|
||||
buttonRadius="lg"
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
chatStore.clearTasks();
|
||||
|
|
@ -236,7 +244,7 @@ export default function SettingGeneral() {
|
|||
navigate('/login');
|
||||
}}
|
||||
>
|
||||
<LogOut className="h-4 w-4 text-button-tertiery-text-default" />
|
||||
<LogOut />
|
||||
{t('setting.log-out')}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ body {
|
|||
|
||||
@layer base {
|
||||
body {
|
||||
color: var(--text-body);
|
||||
color: var(--ds-text-neutral-default-default);
|
||||
}
|
||||
|
||||
p,
|
||||
|
|
@ -53,20 +53,20 @@ body {
|
|||
input,
|
||||
textarea,
|
||||
select {
|
||||
color: var(--text-primary);
|
||||
background-color: var(--surface-primary);
|
||||
border: 1px solid var(--border-secondary);
|
||||
color: var(--ds-text-neutral-default-default);
|
||||
background-color: var(--ds-bg-neutral-subtle-default);
|
||||
border: 1px solid var(--ds-border-neutral-default-default);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
color: var(--text-tertiary);
|
||||
color: var(--ds-text-neutral-subtle-default);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.lucide {
|
||||
color: var(--icon-secondary);
|
||||
color: var(--ds-icon-neutral-muted-default);
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ body {
|
|||
button .lucide,
|
||||
a .lucide,
|
||||
.lucide[data-state='active'] {
|
||||
color: var(--icon-primary);
|
||||
color: var(--ds-icon-neutral-default-default);
|
||||
}
|
||||
|
||||
:is(
|
||||
|
|
@ -84,13 +84,13 @@ body {
|
|||
[class*='bg-white-100%'],
|
||||
[class*='bg-white-50']
|
||||
) {
|
||||
color: var(--text-primary);
|
||||
background-color: var(--surface-card) !important;
|
||||
color: var(--ds-text-neutral-default-default);
|
||||
background-color: var(--ds-bg-neutral-default-default) !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[class*='bg-white-50'] {
|
||||
background-color: var(--surface-tertiary) !important;
|
||||
background-color: var(--ds-bg-neutral-strong-default) !important;
|
||||
}
|
||||
|
||||
.theme-image-invert-dark {
|
||||
|
|
@ -114,10 +114,10 @@ body {
|
|||
box-shadow:
|
||||
-2px -2px 100px rgba(255, 255, 255, 0.1) inset,
|
||||
2px 2px 100px rgba(29, 29, 29, 0.1) inset;
|
||||
background-color: var(--bg-page) !important;
|
||||
background-color: var(--ds-bg-neutral-subtle-default) !important;
|
||||
backdrop-filter: blur(75px);
|
||||
z-index: 0;
|
||||
color: var(--text-body);
|
||||
color: var(--ds-text-neutral-default-default);
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
|
@ -177,7 +177,7 @@ body {
|
|||
}
|
||||
|
||||
.custom-resizable-handle:hover {
|
||||
background: var(--border-information);
|
||||
background: var(--ds-border-brand-default-focus);
|
||||
width: 2px;
|
||||
height: 40px;
|
||||
transform: none;
|
||||
|
|
@ -528,7 +528,7 @@ code {
|
|||
}
|
||||
|
||||
.hover-style-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: var(--text-label, rgba(0, 0, 0, 0.4));
|
||||
background-color: var(--ds-text-neutral-muted-default, rgba(0, 0, 0, 0.4));
|
||||
opacity: 0;
|
||||
border-radius: 9999px;
|
||||
transition: opacity 0.2s ease;
|
||||
|
|
@ -546,7 +546,7 @@ code {
|
|||
|
||||
/* Dark mode overrides for FolderComponent (CSV/table content) */
|
||||
[data-theme='dark'] .folder-component-content {
|
||||
color: var(--text-primary);
|
||||
color: var(--ds-text-neutral-default-default);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .folder-component-content * {
|
||||
|
|
@ -560,7 +560,7 @@ code {
|
|||
[data-theme='dark'] .folder-component-content table th,
|
||||
[data-theme='dark'] .folder-component-content table td {
|
||||
border-color: #30363d !important;
|
||||
color: var(--text-primary) !important;
|
||||
color: var(--ds-text-neutral-default-default) !important;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .folder-component-content table th {
|
||||
|
|
@ -611,11 +611,11 @@ code {
|
|||
color: transparent;
|
||||
background: linear-gradient(
|
||||
110deg,
|
||||
var(--text-body) 0%,
|
||||
var(--text-body) 35%,
|
||||
var(--ds-text-neutral-default-default) 0%,
|
||||
var(--ds-text-neutral-default-default) 35%,
|
||||
var(--colors-primary-4) 50%,
|
||||
var(--text-body) 65%,
|
||||
var(--text-body) 100%
|
||||
var(--ds-text-neutral-default-default) 65%,
|
||||
var(--ds-text-neutral-default-default) 100%
|
||||
);
|
||||
background-size: 250% 100%;
|
||||
background-position: 100% 0;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
@layer base {
|
||||
:root {
|
||||
/* Red */
|
||||
--colors-red-50: #fef2f2;
|
||||
--colors-red-100: #ffe2e2;
|
||||
--colors-red-200: #ffc9c9;
|
||||
|
|
@ -12,7 +11,7 @@
|
|||
--colors-red-900: #82181a;
|
||||
--colors-red-950: #460809;
|
||||
--colors-red-default: #e7000b;
|
||||
/* Yellow */
|
||||
|
||||
--colors-yellow-50: #fefce8;
|
||||
--colors-yellow-100: #fef9c2;
|
||||
--colors-yellow-200: #fff085;
|
||||
|
|
@ -24,7 +23,7 @@
|
|||
--colors-yellow-900: #733e0a;
|
||||
--colors-yellow-950: #432004;
|
||||
--colors-yellow-default: #d08700;
|
||||
/* Green */
|
||||
|
||||
--colors-green-50: #f0fdf4;
|
||||
--colors-green-100: #dcfce7;
|
||||
--colors-green-200: #b9f8cf;
|
||||
|
|
@ -36,7 +35,7 @@
|
|||
--colors-green-900: #0d542b;
|
||||
--colors-green-950: #032e15;
|
||||
--colors-green-default: #00a63e;
|
||||
/* Indigo */
|
||||
|
||||
--colors-indigo-50: #eef2ff;
|
||||
--colors-indigo-100: #e0e7ff;
|
||||
--colors-indigo-200: #c6d2ff;
|
||||
|
|
@ -48,7 +47,7 @@
|
|||
--colors-indigo-900: #312c85;
|
||||
--colors-indigo-950: #1e1a4d;
|
||||
--colors-indigo-default: #4f39f6;
|
||||
/* Blue */
|
||||
|
||||
--colors-blue-50: #eff6ff;
|
||||
--colors-blue-100: #dbeafe;
|
||||
--colors-blue-200: #bedbff;
|
||||
|
|
@ -60,7 +59,7 @@
|
|||
--colors-blue-900: #1c398e;
|
||||
--colors-blue-950: #162456;
|
||||
--colors-blue-default: #155dfc;
|
||||
/* Amber */
|
||||
|
||||
--colors-amber-50: #fffbeb;
|
||||
--colors-amber-100: #fef3c6;
|
||||
--colors-amber-200: #fee685;
|
||||
|
|
@ -72,7 +71,7 @@
|
|||
--colors-amber-900: #7b3306;
|
||||
--colors-amber-950: #461901;
|
||||
--colors-amber-default: #e17100;
|
||||
/* Emerald */
|
||||
|
||||
--colors-emerald-50: #ecfdf5;
|
||||
--colors-emerald-100: #d0fae5;
|
||||
--colors-emerald-200: #a4f4cf;
|
||||
|
|
@ -84,7 +83,7 @@
|
|||
--colors-emerald-900: #004f3b;
|
||||
--colors-emerald-950: #002c22;
|
||||
--colors-emerald-default: #009966;
|
||||
/* Purple */
|
||||
|
||||
--colors-purple-50: #faf5ff;
|
||||
--colors-purple-100: #f3e8ff;
|
||||
--colors-purple-200: #e9d5ff;
|
||||
|
|
@ -95,7 +94,7 @@
|
|||
--colors-purple-800: #6b21a8;
|
||||
--colors-purple-900: #581c87;
|
||||
--colors-purple-default: #9333ea;
|
||||
/* Orange */
|
||||
|
||||
--colors-orange-50: #fff7ed;
|
||||
--colors-orange-100: #ffedd4;
|
||||
--colors-orange-200: #ffd6a8;
|
||||
|
|
@ -107,7 +106,7 @@
|
|||
--colors-orange-900: #7e2a0c;
|
||||
--colors-orange-950: #441306;
|
||||
--colors-orange-default: #f54900;
|
||||
/* Sky */
|
||||
|
||||
--colors-sky-50: #f0f9ff;
|
||||
--colors-sky-100: #dff2fe;
|
||||
--colors-sky-200: #b8e6fe;
|
||||
|
|
@ -119,7 +118,7 @@
|
|||
--colors-sky-900: #024a70;
|
||||
--colors-sky-950: #052f4a;
|
||||
--colors-sky-default: #0084d1;
|
||||
/* Fuchsia */
|
||||
|
||||
--colors-fuchsia-50: #fdf4ff;
|
||||
--colors-fuchsia-100: #fae8ff;
|
||||
--colors-fuchsia-200: #f6cfff;
|
||||
|
|
@ -131,14 +130,14 @@
|
|||
--colors-fuchsia-900: #721378;
|
||||
--colors-fuchsia-950: #4b004f;
|
||||
--colors-fuchsia-default: #c800de;
|
||||
/* Black */
|
||||
|
||||
--colors-black-0: #00000000;
|
||||
--colors-black-10: #0000001a;
|
||||
--colors-black-30: #0000004d;
|
||||
--colors-black-50: #00000080;
|
||||
--colors-black-80: #000000cc;
|
||||
--colors-black-100: #000000;
|
||||
/* Primary */
|
||||
|
||||
--colors-primary-1: #f5f5f5;
|
||||
--colors-primary-2: #eeeeee;
|
||||
--colors-primary-3: #cccccc;
|
||||
|
|
@ -150,31 +149,30 @@
|
|||
--colors-primary-10: #111111;
|
||||
--colors-primary-11: #000000;
|
||||
--colors-primary-default: #222222;
|
||||
/* Off-White */
|
||||
|
||||
--colors-off-white-0: #f5f5f500;
|
||||
--colors-off-white-10: #f5f5f51a;
|
||||
--colors-off-white-30: #f5f5f54d;
|
||||
--colors-off-white-50: #f5f5f580;
|
||||
--colors-off-white-80: #f5f5f5cc;
|
||||
--colors-off-white-100: #f5f5f5;
|
||||
/* White */
|
||||
|
||||
--colors-white-0: #ffffff00;
|
||||
--colors-white-10: #ffffff1a;
|
||||
--colors-white-30: #ffffff4d;
|
||||
--colors-white-50: #ffffff80;
|
||||
--colors-white-80: #ffffffcc;
|
||||
--colors-white-100: #ffffff;
|
||||
/* Off-Black */
|
||||
|
||||
--colors-off-black-0: #1d1c1b00;
|
||||
--colors-off-black-10: #1d1c1b1a;
|
||||
--colors-off-black-30: #1d1c1b4d;
|
||||
--colors-off-black-50: #1d1c1b80;
|
||||
--colors-off-black-80: #1d1c1bcc;
|
||||
--colors-off-black-100: #1d1c1b;
|
||||
/* Gradient */
|
||||
|
||||
--colors-gradient: #ffffff;
|
||||
|
||||
/* Neon (Workforce / camel brand) */
|
||||
--colors-neon-50: #f2f2ff;
|
||||
--colors-neon-100: #e9e7ff;
|
||||
--colors-neon-200: #d5d3ff;
|
||||
|
|
@ -188,556 +186,17 @@
|
|||
--colors-neon-950: #1e096c;
|
||||
--colors-neon-default: #4c19e8;
|
||||
|
||||
/* Alias: same as --colors-neon-default */
|
||||
--camel-color: var(--colors-neon-default);
|
||||
|
||||
/* Component Tokens */
|
||||
/* Input Component */
|
||||
--input-bg-input: var(--surface-tertiary);
|
||||
--input-bg-spliting: var(--surface-information);
|
||||
--input-bg-confirm: var(--surface-success);
|
||||
--input-bg-default: var(--surface-primary);
|
||||
--input-bg-hover: var(--surface-secondary);
|
||||
--input-border-default: var(--border-secondary);
|
||||
--input-border-hover: var(--border-action-hover);
|
||||
--input-border-focus: var(--border-information);
|
||||
--input-text-default: var(--text-secondary);
|
||||
--input-text-focus: var(--text-primary);
|
||||
--input-label-default: var(--text-secondary);
|
||||
--input-border-success: var(--border-success);
|
||||
--input-border-cuation: var(--border-cuation);
|
||||
--input-border-warning: var(--border-warning);
|
||||
|
||||
/* Popup Component */
|
||||
--popup-surface: var(--surface-primary);
|
||||
--popup-bg: var(--fill-default);
|
||||
--popup-border: var(--border-secondary);
|
||||
|
||||
/* Menu Tabs Component */
|
||||
--menutabs-fill-default: var(--fill-fill-transparent);
|
||||
--menutabs-fill-hover: var(--fill-fill-tertiary-hover);
|
||||
--menutabs-fill-active: var(--fill-default);
|
||||
--menutabs-fill-disabled: var(--fill-fill-tertiary-disabled);
|
||||
--menutabs-border-disabled: var(--fill-fill-tertiary-disabled);
|
||||
--menutabs-border-active: var(--fill-fill-tertiary-active);
|
||||
--menutabs-border-hover: var(--fill-fill-tertiary-hover);
|
||||
--menutabs-border-default: var(--fill-fill-tertiary);
|
||||
--menutabs-text-active: var(--text-action);
|
||||
--menutabs-text-disabled: var(--text-disabled);
|
||||
--menutabs-text-hover: var(--text-action-hover);
|
||||
--menutabs-text-default: var(--text-disabled);
|
||||
--menutabs-icon-hover: var(--icon-action-hover);
|
||||
--menutabs-icon-default: var(--icon-disabled);
|
||||
--menutabs-icon-disabled: var(--icon-disabled);
|
||||
--menutabs-icon-active: var(--icon-action);
|
||||
--menutabs-bg-default: var(--surface-primary);
|
||||
|
||||
/* Progress Component */
|
||||
--progress-fill-default: var(--fill-fill-success);
|
||||
--progress-bg: var(--fill-default);
|
||||
--progress-fill-complete: var(--fill-fill-success-active);
|
||||
--progress-fill-past: var(--fill-fill-primary);
|
||||
--progress-fill-new: var(--fill-fill-warning);
|
||||
|
||||
/* Button Primary Component */
|
||||
--button-primary-fill-default: var(--fill-fill-primary);
|
||||
--button-primary-fill-hover: var(--fill-fill-primary-hover);
|
||||
--button-primary-fill-active: var(--fill-fill-primary-active);
|
||||
--button-primary-fill-disabled: var(--fill-fill-primary-disabled);
|
||||
--button-primary-icon-hover: var(--text-on-hover);
|
||||
--button-primary-icon-default: var(--text-on-action);
|
||||
--button-primary-text-disabled: var(--text-on-disabled);
|
||||
--button-primary-text-active: var(--text-on-action);
|
||||
--button-primary-text-hover: var(--text-on-hover);
|
||||
--button-primary-text-default: var(--text-on-action);
|
||||
--button-primary-icon-disabled: var(--text-on-disabled);
|
||||
--button-primary-icon-active: var(--text-on-action);
|
||||
|
||||
/* Button Secondary Component */
|
||||
--button-secondary-fill-disabled: var(--fill-fill-secondary-disabled);
|
||||
--button-secondary-fill-active: var(--fill-fill-secondary-active);
|
||||
--button-secondary-fill-hover: var(--fill-fill-secondary-hover);
|
||||
--button-secondary-fill-default: var(--fill-fill-secondary);
|
||||
--button-secondary-text-default: var(--text-on-action);
|
||||
--button-secondary-text-hover: var(--text-on-hover);
|
||||
--button-secondary-text-active: var(--text-on-action);
|
||||
--button-secondary-text-disabled: var(--text-on-disabled);
|
||||
--button-secondary-icon-disabled: var(--icon-on-disabled);
|
||||
--button-secondary-icon-active: var(--icon-on-action);
|
||||
--button-secondary-icon-hover: var(--icon-on-hover);
|
||||
--button-secondary-icon-default: var(--icon-on-action);
|
||||
|
||||
/* Button Transparent Component */
|
||||
--button-transparent-fill-disabled: var(--fill-fill-transparent-disabled);
|
||||
--button-transparent-fill-active: var(--fill-fill-transparent-active);
|
||||
--button-transparent-fill-hover: var(--fill-fill-transparent-hover);
|
||||
--button-transparent-fill-default: var(--fill-fill-transparent);
|
||||
--button-transparent-icon-default: var(--icon-action);
|
||||
--button-transparent-text-disabled: var(--text-disabled);
|
||||
--button-transparent-text-default: var(--text-action);
|
||||
--button-transparent-text-active: var(--text-action);
|
||||
--button-transparent-icon-hover: var(--icon-action-hover);
|
||||
--button-transparent-text-hover: var(--text-action-hover);
|
||||
--button-transparent-icon-disabled: var(--icon-disabled);
|
||||
--button-transparent-icon-active: var(--icon-action);
|
||||
|
||||
/* Button Tertiary Component */
|
||||
--button-tertiery-fill-hover: var(--fill-fill-tertiary-hover);
|
||||
--button-tertiery-fill-default: var(--fill-fill-tertiary);
|
||||
--button-tertiery-fill-disabled: var(--fill-fill-tertiary-disabled);
|
||||
--button-tertiery-fill-active: var(--fill-fill-tertiary-active);
|
||||
--button-tertiery-icon-hover: var(--icon-action-hover);
|
||||
--button-tertiery-icon-default: var(--icon-action);
|
||||
--button-tertiery-text-disabled: var(--text-disabled);
|
||||
--button-tertiery-text-active: var(--text-action);
|
||||
--button-tertiery-text-hover: var(--text-action-hover);
|
||||
--button-tertiery-text-default: var(--text-action);
|
||||
--button-tertiery-icon-disabled: var(--icon-disabled);
|
||||
--button-tertiery-icon-active: var(--icon-action);
|
||||
--button-tertiery-icon-hover-2: var(--icon-on-hover);
|
||||
--button-tertiery-icon-default-2: var(--icon-on-action);
|
||||
--button-tertiery-text-disabled-2: var(--text-on-disabled);
|
||||
--button-tertiery-text-active-2: var(--text-on-action);
|
||||
--button-tertiery-text-hover-2: var(--text-on-hover);
|
||||
--button-tertiery-text-default-2: var(--text-on-action);
|
||||
--button-tertiery-icon-disabled-2: var(--icon-on-disabled);
|
||||
--button-tertiery-icon-active-2: var(--icon-on-action);
|
||||
|
||||
/* Button State Colors */
|
||||
--button-fill-success: var(--fill-fill-success);
|
||||
--button-fill-cuation: var(--fill-fill-cuation);
|
||||
--button-fill-warning: var(--fill-fill-warning);
|
||||
--button-fill-success-foreground: var(--text-on-action);
|
||||
--button-fill-cuation-foreground: var(--text-on-action);
|
||||
--button-fill-warning-foreground: var(--text-on-action);
|
||||
--button-fill-information: var(--fill-fill-information);
|
||||
--button-fill-information-foreground: var(--text-on-action);
|
||||
|
||||
/* Badge Component */
|
||||
--badge-running-surface: var(--surface-success);
|
||||
--badge-running-surface-foreground: var(--text-success);
|
||||
--badge-paused-surface-foreground: var(--text-warning);
|
||||
--badge-paused-surface: var(--surface-warning);
|
||||
--badge-error-surface-foreground: var(--text-cuation);
|
||||
--badge-error-surface: var(--surface-cuation);
|
||||
--badge-complete-surface-foreground: var(--text-body);
|
||||
--badge-complete-surface: var(--surface-primary);
|
||||
--badge-splitting-surface-foreground: var(--text-information);
|
||||
--badge-splitting-surface: var(--surface-information);
|
||||
|
||||
/* Switch Component */
|
||||
--switch-off-fill-track-fill: var(--fill-fill-secondary);
|
||||
--switch-off-fill-track-border: var(--border-primary);
|
||||
--switch-off-fill-thumb-border: var(--border-primary);
|
||||
--switch-off-fill-thumb-fill: var(--fill-default);
|
||||
--switch-on-fill-thumb-border: var(--border-success);
|
||||
--switch-on-fill-thumb-fill: var(--fill-default);
|
||||
--switch-on-fill-track-border: var(--border-success);
|
||||
--switch-on-fill-track-fill: var(--fill-fill-success);
|
||||
--switch-disabled-fill-thumb-border: var(--border-disabled);
|
||||
--switch-disabled-fill-track-border: var(--border-disabled);
|
||||
--switch-disabled-fill-thumb-fill: var(--fill-default);
|
||||
--switch-disabled-fill-track-fill: var(--fill-fill-primary-disabled);
|
||||
/* Pill Component */
|
||||
--pill-bg: var(--fill-default);
|
||||
--pill-surface: var(--fill-fill-primary);
|
||||
--pill-border: var(--border-primary);
|
||||
|
||||
/* Menu Button Component */
|
||||
--menubutton-fill-default: var(--fill-fill-transparent);
|
||||
--menubutton-fill-hover: var(--fill-fill-transparent-hover);
|
||||
--menubutton-fill-active: var(--fill-default);
|
||||
--menubutton-border-active: var(--border-primary);
|
||||
--menubutton-border-default: var(--border-transparent);
|
||||
--menubutton-border-hover: var(--border-disabled);
|
||||
--menubutton-disabled: 1.25rem;
|
||||
|
||||
/* Dropdown Component */
|
||||
--dropdown-bg: var(--fill-default);
|
||||
--dropdown-border: var(--border-secondary);
|
||||
--dropdown-item-bg-default: var(--fill-fill-transparent);
|
||||
--dropdown-item-bg-hover: var(--fill-fill-tertiary-hover);
|
||||
--dropdown-item-bg-active: var(--fill-fill-tertiary);
|
||||
|
||||
/* Search Component */
|
||||
--search-bg: var(--fill-default);
|
||||
--search-border-hover: var(--border-secondary);
|
||||
--search-border-default: var(--border-disabled);
|
||||
--search-default: 3.125rem;
|
||||
|
||||
/* Tag Component */
|
||||
--tag-surface: var(--button-tertiery-fill-default);
|
||||
--tag-fill-browser: var(--fill-browser);
|
||||
--tag-fill-camel: var(--fill-camel);
|
||||
--tag-fill-developer: var(--fill-developer);
|
||||
--tag-fill-document: var(--fill-document);
|
||||
--tag-fill-multimodal: var(--fill-multimodal);
|
||||
--tag-fill-socialmedia: var(--fill-socialmedia);
|
||||
--tag-fill-info: var(--surface-information);
|
||||
--tag-foreground-info: var(--text-information);
|
||||
--tag-surface-hover: var(--button-tertiery-fill-hover);
|
||||
--tag-fill-success: var(--surface-success);
|
||||
--tag-foreground-success: var(--text-success);
|
||||
--tag-fill-warning: var(--surface-warning);
|
||||
--tag-foreground-warning: var(--text-warning);
|
||||
--tag-fill-cuation: var(--surface-cuation);
|
||||
--tag-foreground-cuation: var(--text-cuation);
|
||||
--tag-foreground-default: var(--text-body);
|
||||
--tag-fill-default: var(--surface-tertiary);
|
||||
--tag-fill-default-foreground: var(--surface-information);
|
||||
|
||||
/* Project Component */
|
||||
--project-surface: var(--surface-tertiary);
|
||||
--project-surface-hover: var(--surface-tertiary-hover);
|
||||
--project-border-default: var(--border-tertiary);
|
||||
--project-border-hover: var(--border-primary);
|
||||
|
||||
/* Message Component */
|
||||
--message-fill-default: var(--surface-tertiary);
|
||||
--message-fill-hover: var(--surface-secondary);
|
||||
--message-fill-active: var(--surface-primary);
|
||||
--message-border-default: var(--border-disabled);
|
||||
--message-border-focus: var(--border-focus);
|
||||
|
||||
/* Task Component */
|
||||
--task-surface: var(--surface-tertiary);
|
||||
--task-border-default: var(--border-disabled);
|
||||
--task-border-focus: var(--border-focus);
|
||||
--task-fill-default: var(--fill-fill-tertiary);
|
||||
--task-fill-hover: var(--fill-fill-tertiary-hover);
|
||||
--task-fill-success: var(--surface-success);
|
||||
--task-fill-warning: var(--surface-warning);
|
||||
--task-fill-error: var(--surface-cuation);
|
||||
--task-border-focus-success: var(--border-success);
|
||||
--task-border-focus-warning: var(--border-warning);
|
||||
--task-border-focus-error: var(--border-cuation);
|
||||
--task-fill-running: var(--surface-primary);
|
||||
|
||||
/* Log Component */
|
||||
--log-default: #f5f5f5;
|
||||
|
||||
/* Worker Component */
|
||||
--worker-surface-primary: var(--surface-tertiary);
|
||||
--worker-border-default: var(--border-disabled);
|
||||
--worker-border-focus: var(--border-focus);
|
||||
--worker-surface-secondary: var(--surface-disabled);
|
||||
|
||||
/* Mask Component */
|
||||
--mask-default: var(--bg-secondary);
|
||||
--mask-dark: var(--bg-dark-secondary);
|
||||
|
||||
/* Code Component */
|
||||
--code-bg: var(--bg-dark-default);
|
||||
--code-foreground: var(--text-on-action);
|
||||
--code-surface: #f4f4f5;
|
||||
/* zinc-100 equivalent */
|
||||
|
||||
/* Surface Variants */
|
||||
--surface-error-subtle: #fee2e2;
|
||||
/* red-100 equivalent */
|
||||
--surface-hover-subtle: #f3f4f6;
|
||||
/* gray-100 equivalent */
|
||||
--surface-success-subtle: #d1fae5;
|
||||
/* emerald-100 equivalent */
|
||||
--surface-tertiary-subtle: #f9fafb;
|
||||
/* gray-50 equivalent */
|
||||
|
||||
/* Text Variants */
|
||||
--text-muted: var(--colors-primary-5);
|
||||
--text-muted-strong: var(--colors-primary-7);
|
||||
--text-link: var(--colors-blue-500);
|
||||
--text-link-hover: var(--colors-blue-700);
|
||||
--text-error: var(--colors-red-500);
|
||||
|
||||
--border-subtle: var(--colors-primary-2);
|
||||
--border-subtle-strong: var(--colors-primary-3);
|
||||
|
||||
/* Shadow Tokens */
|
||||
|
||||
/* Perfect Shadow */
|
||||
--shadow-perfect:
|
||||
0 8px 20px -2px #1d21291a, 0 32px 48px -12px #1d21291f,
|
||||
0 96px 120px -12px #414a5c0f, 0 108px 72px -16px #414a5c14,
|
||||
0 32px 64px -8px #7199bd1f, 0 8px 10px 0 #7199bd1f;
|
||||
|
||||
/* Button Shadow */
|
||||
--shadow-button:
|
||||
inset 0 1px 0 0 #ffffff54, 0 3px 4px -1px #00000040, 0 0 0 1px #d4d4d440;
|
||||
}
|
||||
|
||||
.root,
|
||||
[data-theme='light'] {
|
||||
--text-heading: var(--colors-primary-10);
|
||||
--text-body: var(--colors-primary-default);
|
||||
--text-label: var(--colors-primary-6);
|
||||
--text-action: var(--colors-primary-default);
|
||||
--text-action-hover: var(--colors-primary-10);
|
||||
--text-disabled: var(--colors-primary-3);
|
||||
--text-information: var(--colors-blue-default);
|
||||
--text-success: var(--colors-green-default);
|
||||
--text-warning: var(--colors-yellow-default);
|
||||
--text-cuation: var(--colors-red-default);
|
||||
--text-on-action: var(--colors-primary-1);
|
||||
--text-on-disabled: var(--colors-primary-1);
|
||||
--text-document: var(--colors-amber-default);
|
||||
--text-socialmedia: var(--colors-purple-default);
|
||||
--text-browser: var(--colors-sky-default);
|
||||
--text-developer: var(--colors-emerald-default);
|
||||
--text-multimodal: var(--colors-fuchsia-default);
|
||||
--text-session-single-agent: var(--colors-purple-700);
|
||||
--text-session-workforce: var(--colors-emerald-700);
|
||||
--surface-session-single-agent-selected: var(--colors-purple-100);
|
||||
--surface-session-workforce-selected: var(--colors-emerald-100);
|
||||
--text-on-hover: var(--colors-primary-2);
|
||||
--surface-primary: var(--colors-off-white-100);
|
||||
--surface-secondary: var(--colors-primary-2);
|
||||
--surface-success: var(--colors-green-50);
|
||||
--surface-information: var(--colors-blue-50);
|
||||
--surface-warning: var(--colors-yellow-50);
|
||||
--surface-cuation: var(--colors-red-50);
|
||||
--surface-action: var(--colors-primary-2);
|
||||
--surface-action-hover: var(--colors-primary-1);
|
||||
--surface-disabled: var(--colors-off-white-30);
|
||||
--surface-tertiary: var(--colors-white-100);
|
||||
--surface-tertiary-hover: var(--colors-white-50);
|
||||
--surface-card: var(--colors-off-white-30);
|
||||
--surface-card-hover: var(--colors-off-white-80);
|
||||
--surface-card-focus: var(--colors-white-100);
|
||||
--surface-card-default: 1.25rem;
|
||||
--border-primary: var(--colors-primary-4);
|
||||
--border-secondary: var(--colors-primary-3);
|
||||
--border-tertiary: var(--colors-primary-1);
|
||||
--border-information: var(--colors-blue-default);
|
||||
--border-success: var(--colors-green-default);
|
||||
--border-warning: var(--colors-yellow-default);
|
||||
--border-cuation: var(--colors-red-default);
|
||||
--border-focus: var(--colors-primary-4);
|
||||
--border-action: var(--colors-primary-3);
|
||||
--border-action-hover: var(--colors-primary-4);
|
||||
--border-disabled: var(--colors-primary-2);
|
||||
--border-developer: var(--colors-emerald-default);
|
||||
--border-browser: var(--colors-sky-default);
|
||||
--border-socialmedia: var(--colors-purple-default);
|
||||
--border-multimodal: var(--colors-fuchsia-default);
|
||||
--border-document: var(--colors-amber-default);
|
||||
--border-camel: var(--colors-neon-500);
|
||||
--border-transparent: var(--colors-white-0);
|
||||
--text-camel: var(--colors-neon-default);
|
||||
--fill-camel: var(--colors-neon-100);
|
||||
--icon-primary: var(--colors-primary-default);
|
||||
--icon-action: var(--colors-primary-default);
|
||||
--icon-disabled: var(--colors-primary-3);
|
||||
--icon-information: var(--colors-blue-default);
|
||||
--icon-success: var(--colors-green-default);
|
||||
--icon-warning: var(--colors-yellow-default);
|
||||
--icon-cuation: var(--colors-red-default);
|
||||
--icon-action-hover: var(--colors-primary-10);
|
||||
--icon-multimodal: var(--colors-fuchsia-default);
|
||||
--icon-socialmedia: var(--colors-purple-default);
|
||||
--icon-document: var(--colors-amber-default);
|
||||
--icon-browser: var(--colors-sky-default);
|
||||
--icon-developer: var(--colors-emerald-default);
|
||||
--icon-on-disabled: var(--colors-off-white-50);
|
||||
--icon-on-hover: var(--colors-primary-2);
|
||||
--icon-on-action: var(--colors-primary-1);
|
||||
--icon-secondary: var(--colors-primary-5);
|
||||
--developer: var(--colors-emerald-default);
|
||||
--browser: var(--colors-sky-default);
|
||||
--document: var(--colors-amber-default);
|
||||
--multimodal: var(--colors-fuchsia-default);
|
||||
--socialmedia: var(--colors-purple-default);
|
||||
--fill-default: var(--colors-white-100);
|
||||
--fill-fill-primary: var(--colors-primary-default);
|
||||
--fill-fill-primary-hover: var(--colors-primary-10);
|
||||
--fill-fill-primary-active: var(--colors-primary-11);
|
||||
--fill-fill-primary-disabled: var(--colors-primary-5);
|
||||
--fill-fill-tertiary: var(--colors-primary-1);
|
||||
--fill-fill-transparent: var(--colors-white-0);
|
||||
--fill-fill-transparent-hover: var(--colors-off-white-100);
|
||||
--fill-fill-tertiary-hover: var(--colors-primary-2);
|
||||
--fill-fill-tertiary-active: var(--colors-primary-3);
|
||||
--fill-fill-tertiary-disabled: var(--colors-primary-2);
|
||||
--fill-fill-transparent-active: var(--colors-white-100);
|
||||
--fill-fill-transparent-disabled: var(--colors-white-0);
|
||||
--fill-fill-secondary-disabled: var(--colors-primary-4);
|
||||
--fill-fill-secondary-active: var(--colors-primary-7);
|
||||
--fill-fill-secondary-hover: var(--colors-primary-6);
|
||||
--fill-fill-secondary: var(--colors-primary-5);
|
||||
--fill-fill-success: var(--colors-green-default);
|
||||
--fill-fill-success-hover: var(--colors-green-700);
|
||||
--fill-fill-success-active: var(--colors-green-800);
|
||||
--fill-fill-success-disable: var(--colors-green-400);
|
||||
--fill-fill-warning: var(--colors-yellow-default);
|
||||
--fill-fill-cuation: var(--colors-red-default);
|
||||
--fill-socialmedia: var(--colors-purple-100);
|
||||
--fill-document: var(--colors-amber-100);
|
||||
--fill-browser: var(--colors-sky-100);
|
||||
--fill-multimodal: var(--colors-fuchsia-100);
|
||||
--fill-developer: var(--colors-emerald-100);
|
||||
--fill-scrollbar-dark: var(--colors-primary-3);
|
||||
--fill-scrollbar-light: var(--colors-primary-1);
|
||||
--fill-skeloten-default: var(--colors-primary-2);
|
||||
--fill-fill-information: var(--colors-blue-default);
|
||||
--bg-page: var(--colors-primary-1);
|
||||
--bg-primary: var(--colors-primary-1);
|
||||
--bg-secondary: var(--colors-primary-2);
|
||||
--bg-tertiary: var(--colors-primary-3);
|
||||
--bg-dark: var(--colors-off-black-80);
|
||||
--bg-dark-primary: var(--colors-off-black-50);
|
||||
--bg-dark-secondary: var(--colors-off-black-30);
|
||||
--bg-dark-tertiary: var(--colors-off-black-10);
|
||||
--bg-dark-default: var(--colors-off-black-100);
|
||||
--bg-page-default: var(--colors-off-white-100);
|
||||
--background: var(--bg-page);
|
||||
}
|
||||
|
||||
.root,
|
||||
[data-theme='dark'] {
|
||||
--text-heading: #e8ecff;
|
||||
--text-body: #f4f6ff;
|
||||
--text-label: #b0bdd8;
|
||||
--text-action: #f8fafc;
|
||||
--text-action-hover: #d9e2ff;
|
||||
--text-disabled: rgba(148, 163, 184, 0.35);
|
||||
--text-information: #7ab3ff;
|
||||
--text-success: #4ade80;
|
||||
--text-warning: #facc15;
|
||||
--text-cuation: #f87171;
|
||||
--text-on-action: #0b1020;
|
||||
--text-on-disabled: rgba(15, 23, 42, 0.7);
|
||||
--text-document: #ffd479;
|
||||
--text-socialmedia: #d8b4fe;
|
||||
--text-browser: #7dd3fc;
|
||||
--text-developer: #6ee7b7;
|
||||
--text-multimodal: #f5a8ff;
|
||||
--text-session-single-agent: #e9d5ff;
|
||||
--text-session-workforce: var(--text-developer);
|
||||
--surface-session-single-agent-selected: rgba(168, 85, 247, 0.22);
|
||||
--surface-session-workforce-selected: rgba(52, 211, 153, 0.2);
|
||||
--text-on-hover: #ffffff;
|
||||
--text-primary: #f4f6ff;
|
||||
--text-secondary: var(--text-label);
|
||||
--text-tertiary: var(--text-disabled);
|
||||
--text-inverse-primary: var(--text-on-action);
|
||||
--text-success-primary: var(--text-success);
|
||||
--text-success-default: var(--text-success);
|
||||
--text-caution: var(--text-cuation);
|
||||
--text-cuation-default: var(--text-cuation);
|
||||
--surface-primary: #131b2b;
|
||||
--surface-secondary: #1b2435;
|
||||
--surface-success: rgba(15, 118, 110, 0.25);
|
||||
--surface-information: rgba(30, 64, 175, 0.22);
|
||||
--surface-warning: rgba(161, 98, 7, 0.26);
|
||||
--surface-cuation: rgba(153, 27, 27, 0.26);
|
||||
--surface-action: #2c3a55;
|
||||
--surface-action-hover: #35476a;
|
||||
--surface-disabled: rgba(148, 163, 184, 0.14);
|
||||
--surface-tertiary: #222d41;
|
||||
--surface-tertiary-hover: #2c3950;
|
||||
--surface-card: #161f30;
|
||||
--surface-card-hover: #1f2a40;
|
||||
--surface-card-focus: #2a3a55;
|
||||
--surface-card-default: 1.25rem;
|
||||
--border-primary: rgba(148, 163, 184, 0.24);
|
||||
--border-secondary: rgba(148, 163, 184, 0.12);
|
||||
--border-tertiary: rgba(148, 163, 184, 0.08);
|
||||
--border-information: rgba(125, 179, 255, 0.65);
|
||||
--border-success: rgba(74, 222, 128, 0.6);
|
||||
--border-cuation: rgba(248, 113, 113, 0.6);
|
||||
--border-warning: rgba(250, 204, 21, 0.6);
|
||||
--border-focus: rgba(226, 232, 240, 0.2);
|
||||
--border-action: rgba(148, 163, 184, 0.24);
|
||||
--border-action-hover: rgba(226, 232, 240, 0.38);
|
||||
--border-disabled: rgba(148, 163, 184, 0.08);
|
||||
--border-developer: rgba(110, 231, 183, 0.6);
|
||||
--border-browser: rgba(125, 211, 252, 0.6);
|
||||
--border-socialmedia: rgba(216, 180, 254, 0.6);
|
||||
--border-multimodal: rgba(245, 168, 255, 0.6);
|
||||
--border-document: rgba(255, 212, 121, 0.6);
|
||||
--border-camel: var(--colors-neon-500);
|
||||
--border-transparent: var(--colors-black-0);
|
||||
/* Dark: lighter default for readability on dark surfaces */
|
||||
--camel-color: var(--colors-neon-400);
|
||||
--text-camel: var(--camel-color);
|
||||
--fill-camel: var(--colors-neon-100);
|
||||
--icon-primary: #d0dcff;
|
||||
--icon-action: #f1f5ff;
|
||||
--icon-disabled: rgba(148, 163, 184, 0.4);
|
||||
--icon-information: #7ab3ff;
|
||||
--icon-success: #4ade80;
|
||||
--icon-warning: #facc15;
|
||||
--icon-cuation: #f87171;
|
||||
--icon-action-hover: #ffffff;
|
||||
--icon-multimodal: #f5a8ff;
|
||||
--icon-socialmedia: #d8b4fe;
|
||||
--icon-document: #ffd479;
|
||||
--icon-browser: #7dd3fc;
|
||||
--icon-developer: #6ee7b7;
|
||||
--icon-on-disabled: rgba(15, 23, 42, 0.7);
|
||||
--icon-on-hover: #ffffff;
|
||||
--icon-on-action: #0b1020;
|
||||
--icon-secondary: rgba(226, 232, 240, 0.55);
|
||||
--developer: #34d399;
|
||||
--browser: #38bdf8;
|
||||
--document: #fbbf24;
|
||||
--multimodal: #f472b6;
|
||||
--socialmedia: #c084fc;
|
||||
--fill-default: #131b2b;
|
||||
--fill-fill-primary: #e2e8ff;
|
||||
--fill-fill-primary-hover: #ffffff;
|
||||
--fill-fill-primary-active: #ffffff;
|
||||
--fill-fill-primary-disabled: rgba(209, 213, 219, 0.4);
|
||||
--fill-fill-tertiary: #1f2937;
|
||||
--fill-fill-transparent: rgba(15, 23, 42, 0.4);
|
||||
--fill-fill-transparent-hover: rgba(59, 74, 99, 0.6);
|
||||
--fill-fill-tertiary-hover: #2c3a55;
|
||||
--fill-fill-tertiary-active: #35476a;
|
||||
--fill-fill-tertiary-disabled: rgba(31, 41, 55, 0.6);
|
||||
--fill-fill-transparent-active: rgba(59, 74, 99, 0.75);
|
||||
--fill-fill-transparent-disabled: rgba(15, 23, 42, 0.2);
|
||||
--fill-fill-secondary-disabled: rgba(148, 163, 184, 0.25);
|
||||
--fill-fill-secondary-active: #f1f5ff;
|
||||
--fill-fill-secondary-hover: rgba(226, 232, 240, 0.85);
|
||||
--fill-fill-secondary: rgba(226, 232, 240, 0.65);
|
||||
--fill-fill-success: #22c55e;
|
||||
--fill-fill-success-hover: #4ade80;
|
||||
--fill-fill-success-active: #a7f3d0;
|
||||
--fill-fill-success-disable: rgba(15, 118, 110, 0.45);
|
||||
--fill-fill-warning: #facc15;
|
||||
--fill-fill-cuation: #f87171;
|
||||
--fill-socialmedia: rgba(134, 94, 189, 0.45);
|
||||
--fill-document: rgba(180, 128, 41, 0.45);
|
||||
--fill-browser: rgba(40, 94, 151, 0.45);
|
||||
--fill-multimodal: rgba(161, 60, 190, 0.45);
|
||||
--fill-developer: rgba(23, 121, 96, 0.45);
|
||||
--fill-scrollbar-dark: rgba(148, 163, 184, 0.2);
|
||||
--fill-scrollbar-light: rgba(209, 213, 219, 0.35);
|
||||
--fill-skeloten-default: rgba(148, 163, 184, 0.18);
|
||||
--fill-fill-information: #7ab3ff;
|
||||
--bg-page: #0d1424;
|
||||
--bg-primary: #121a2a;
|
||||
--bg-secondary: #161f30;
|
||||
--bg-tertiary: #1c2640;
|
||||
--bg-dark: #131b2b;
|
||||
--bg-dark-primary: #1b2435;
|
||||
--bg-dark-secondary: #222d41;
|
||||
--bg-dark-tertiary: #2c3950;
|
||||
--bg-dark-default: #0d1424;
|
||||
--bg-page-default: #0d1424;
|
||||
--background: var(--bg-page);
|
||||
--task-fill-running: #1f2937;
|
||||
--log-default: #1f2937;
|
||||
--code-surface: #27272a;
|
||||
--surface-error-subtle: rgba(153, 27, 27, 0.3);
|
||||
--surface-hover-subtle: rgba(55, 65, 81, 0.5);
|
||||
--surface-success-subtle: rgba(6, 95, 70, 0.35);
|
||||
--surface-tertiary-subtle: rgba(31, 41, 55, 0.5);
|
||||
--text-muted: var(--colors-primary-4);
|
||||
--text-muted-strong: var(--colors-primary-3);
|
||||
--text-link: var(--colors-blue-400);
|
||||
--text-link-hover: var(--colors-blue-300);
|
||||
--text-error: var(--colors-red-400);
|
||||
--border-subtle: var(--colors-primary-7);
|
||||
--border-subtle-strong: var(--colors-primary-6);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,61 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
const DS_TOKEN_ELEMENTS = ['bg', 'text', 'border', 'icon', 'ring'];
|
||||
const DS_TOKEN_EMPHASIS = ['subtle', 'muted', 'default', 'strong', 'inverse'];
|
||||
const DS_TOKEN_STATES = [
|
||||
'default',
|
||||
'hover',
|
||||
'active',
|
||||
'selected',
|
||||
'focus',
|
||||
'disabled',
|
||||
];
|
||||
const DS_TOKEN_TONES = [
|
||||
'neutral',
|
||||
'brand',
|
||||
'status-running',
|
||||
'status-splitting',
|
||||
'status-pending',
|
||||
'status-error',
|
||||
'status-reassigning',
|
||||
'status-completed',
|
||||
'status-blocked',
|
||||
'status-paused',
|
||||
'status-skipped',
|
||||
'status-cancelled',
|
||||
'single-agent',
|
||||
'workforce',
|
||||
'browser',
|
||||
'terminal',
|
||||
'document',
|
||||
'success',
|
||||
'caution',
|
||||
'error',
|
||||
'warning',
|
||||
'information',
|
||||
];
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
function loadTokenManifest() {
|
||||
const fallback = {
|
||||
elements: ['bg', 'text', 'border', 'icon', 'ring'],
|
||||
emphasis: ['subtle', 'muted', 'default', 'strong', 'inverse'],
|
||||
states: ['default', 'hover', 'active', 'selected', 'focus', 'disabled'],
|
||||
tones: [
|
||||
'neutral',
|
||||
'brand',
|
||||
'status-running',
|
||||
'status-splitting',
|
||||
'status-pending',
|
||||
'status-error',
|
||||
'status-reassigning',
|
||||
'status-completed',
|
||||
'status-blocked',
|
||||
'status-paused',
|
||||
'status-skipped',
|
||||
'status-cancelled',
|
||||
'single-agent',
|
||||
'workforce',
|
||||
'browser',
|
||||
'terminal',
|
||||
'document',
|
||||
'success',
|
||||
'caution',
|
||||
'error',
|
||||
'warning',
|
||||
'information',
|
||||
],
|
||||
};
|
||||
|
||||
const manifestPath = path.join(__dirname, 'tokens', 'manifest.json');
|
||||
try {
|
||||
const raw = fs.readFileSync(manifestPath, 'utf8');
|
||||
const parsed = JSON.parse(raw);
|
||||
if (
|
||||
Array.isArray(parsed.elements) &&
|
||||
Array.isArray(parsed.emphasis) &&
|
||||
Array.isArray(parsed.states) &&
|
||||
Array.isArray(parsed.tones)
|
||||
) {
|
||||
return parsed;
|
||||
}
|
||||
return fallback;
|
||||
} catch (_error) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
const TOKEN_MANIFEST = loadTokenManifest();
|
||||
const DS_TOKEN_ELEMENTS = TOKEN_MANIFEST.elements;
|
||||
const DS_TOKEN_EMPHASIS = TOKEN_MANIFEST.emphasis;
|
||||
const DS_TOKEN_STATES = TOKEN_MANIFEST.states;
|
||||
const DS_TOKEN_TONES = TOKEN_MANIFEST.tones;
|
||||
|
||||
function buildDsTokenColorMap() {
|
||||
const map = {};
|
||||
|
|
@ -316,19 +339,19 @@ module.exports = {
|
|||
input: {
|
||||
'bg-default': 'var(--input-bg-default)',
|
||||
'bg-hover': 'var(--input-bg-hover)',
|
||||
'bg-spliting': 'var(--input-bg-spliting)',
|
||||
'bg-splitting': 'var(--input-bg-splitting)',
|
||||
'bg-confirm': 'var(--input-bg-confirm)',
|
||||
'bg-input': 'var(--input-bg-input)',
|
||||
'border-default': 'var(--input-border-default)',
|
||||
'border-hover': 'var(--input-border-hover)',
|
||||
'border-focus': 'var(--input-border-focus)',
|
||||
'border-success': 'var(--input-border-success)',
|
||||
'border-cuation': 'var(--input-border-cuation)',
|
||||
'border-caution': 'var(--input-border-caution)',
|
||||
'border-warning': 'var(--input-border-warning)',
|
||||
'text-default': 'var(--input-text-default)',
|
||||
'text-focus': 'var(--input-text-focus)',
|
||||
'text-success': 'var(--text-success)',
|
||||
'text-cuation': 'var(--text-cuation)',
|
||||
'text-caution': 'var(--text-caution)',
|
||||
'text-warning': 'var(--text-warning)',
|
||||
'label-default': 'var(--input-label-default)',
|
||||
},
|
||||
|
|
@ -406,33 +429,33 @@ module.exports = {
|
|||
'icon-disabled': 'var(--button-transparent-icon-disabled)',
|
||||
'icon-active': 'var(--button-transparent-icon-active)',
|
||||
},
|
||||
tertiery: {
|
||||
'fill-hover': 'var(--button-tertiery-fill-hover)',
|
||||
'fill-default': 'var(--button-tertiery-fill-default)',
|
||||
'fill-disabled': 'var(--button-tertiery-fill-disabled)',
|
||||
'fill-active': 'var(--button-tertiery-fill-active)',
|
||||
'icon-hover': 'var(--button-tertiery-icon-hover)',
|
||||
'icon-default': 'var(--button-tertiery-icon-default)',
|
||||
'text-disabled': 'var(--button-tertiery-text-disabled)',
|
||||
'text-active': 'var(--button-tertiery-text-active)',
|
||||
'text-hover': 'var(--button-tertiery-text-hover)',
|
||||
'text-default': 'var(--button-tertiery-text-default)',
|
||||
'icon-disabled': 'var(--button-tertiery-icon-disabled)',
|
||||
'icon-active': 'var(--button-tertiery-icon-active)',
|
||||
'icon-hover 2': 'var(--button-tertiery-icon-hover-2)',
|
||||
'icon-default 2': 'var(--button-tertiery-icon-default-2)',
|
||||
'text-disabled 2': 'var(--button-tertiery-text-disabled-2)',
|
||||
'text-active 2': 'var(--button-tertiery-text-active-2)',
|
||||
'text-hover 2': 'var(--button-tertiery-text-hover-2)',
|
||||
'text-default 2': 'var(--button-tertiery-text-default-2)',
|
||||
'icon-disabled 2': 'var(--button-tertiery-icon-disabled-2)',
|
||||
'icon-active 2': 'var(--button-tertiery-icon-active-2)',
|
||||
tertiary: {
|
||||
'fill-hover': 'var(--button-tertiary-fill-hover)',
|
||||
'fill-default': 'var(--button-tertiary-fill-default)',
|
||||
'fill-disabled': 'var(--button-tertiary-fill-disabled)',
|
||||
'fill-active': 'var(--button-tertiary-fill-active)',
|
||||
'icon-hover': 'var(--button-tertiary-icon-hover)',
|
||||
'icon-default': 'var(--button-tertiary-icon-default)',
|
||||
'text-disabled': 'var(--button-tertiary-text-disabled)',
|
||||
'text-active': 'var(--button-tertiary-text-active)',
|
||||
'text-hover': 'var(--button-tertiary-text-hover)',
|
||||
'text-default': 'var(--button-tertiary-text-default)',
|
||||
'icon-disabled': 'var(--button-tertiary-icon-disabled)',
|
||||
'icon-active': 'var(--button-tertiary-icon-active)',
|
||||
'icon-hover 2': 'var(--button-tertiary-icon-hover-2)',
|
||||
'icon-default 2': 'var(--button-tertiary-icon-default-2)',
|
||||
'text-disabled 2': 'var(--button-tertiary-text-disabled-2)',
|
||||
'text-active 2': 'var(--button-tertiary-text-active-2)',
|
||||
'text-hover 2': 'var(--button-tertiary-text-hover-2)',
|
||||
'text-default 2': 'var(--button-tertiary-text-default-2)',
|
||||
'icon-disabled 2': 'var(--button-tertiary-icon-disabled-2)',
|
||||
'icon-active 2': 'var(--button-tertiary-icon-active-2)',
|
||||
},
|
||||
'fill-success': 'var(--button-fill-success)',
|
||||
'fill-cuation': 'var(--button-fill-cuation)',
|
||||
'fill-caution': 'var(--button-fill-caution)',
|
||||
'fill-warning': 'var(--button-fill-warning)',
|
||||
'fill-success-foreground': 'var(--button-fill-success-foreground)',
|
||||
'fill-cuation-foreground': 'var(--button-fill-cuation-foreground)',
|
||||
'fill-caution-foreground': 'var(--button-fill-caution-foreground)',
|
||||
'fill-warning-foreground': 'var(--button-fill-warning-foreground)',
|
||||
'fill-information': 'var(--button-fill-information)',
|
||||
'fill-information-foreground':
|
||||
|
|
@ -515,8 +538,8 @@ module.exports = {
|
|||
'text-success': 'var(--tag-text-success)',
|
||||
'fill-warning': 'var(--tag-fill-warning)',
|
||||
'foreground-warning': 'var(--tag-foreground-warning)',
|
||||
'fill-cuation': 'var(--tag-fill-cuation)',
|
||||
'foreground-cuation': 'var(--tag-foreground-cuation)',
|
||||
'fill-caution': 'var(--tag-fill-caution)',
|
||||
'foreground-caution': 'var(--tag-foreground-caution)',
|
||||
'fill-default': 'var(--tag-fill-default)',
|
||||
'foreground-default': 'var(--tag-foreground-default)',
|
||||
'fill-default-foreground': 'var(--tag-fill-default-foreground)',
|
||||
|
|
@ -564,10 +587,10 @@ module.exports = {
|
|||
surface: 'var(--code-surface)',
|
||||
},
|
||||
surface: {
|
||||
'error-subtle': 'var(--surface-error-subtle)',
|
||||
'hover-subtle': 'var(--surface-hover-subtle)',
|
||||
'success-subtle': 'var(--surface-success-subtle)',
|
||||
'tertiary-subtle': 'var(--surface-tertiary-subtle)',
|
||||
'error-subtle': 'var(--ds-bg-status-error-subtle-default)',
|
||||
'hover-subtle': 'var(--ds-bg-neutral-subtle-hover)',
|
||||
'success-subtle': 'var(--ds-bg-status-completed-subtle-default)',
|
||||
'tertiary-subtle': 'var(--ds-bg-neutral-strong-default)',
|
||||
},
|
||||
'text-muted': 'var(--text-muted)',
|
||||
'text-muted-strong': 'var(--text-muted-strong)',
|
||||
|
|
@ -592,7 +615,7 @@ module.exports = {
|
|||
'text-information': 'var(--text-information)',
|
||||
'text-success': 'var(--text-success)',
|
||||
'text-warning': 'var(--text-warning)',
|
||||
'text-cuation': 'var(--text-cuation)',
|
||||
'text-caution': 'var(--text-caution)',
|
||||
'text-on-action': 'var(--text-on-action)',
|
||||
'text-on-disabled': 'var(--text-on-disabled)',
|
||||
'text-document': 'var(--text-document)',
|
||||
|
|
@ -605,25 +628,24 @@ module.exports = {
|
|||
'session-workforce': 'var(--text-session-workforce)',
|
||||
'text-on-hover': 'var(--text-on-hover)',
|
||||
|
||||
'surface-primary': 'var(--surface-primary)',
|
||||
'surface-secondary': 'var(--surface-secondary)',
|
||||
'surface-success': 'var(--surface-success)',
|
||||
'surface-information': 'var(--surface-information)',
|
||||
'surface-warning': 'var(--surface-warning)',
|
||||
'surface-cuation': 'var(--surface-cuation)',
|
||||
'surface-caution': 'var(--surface-cuation)',
|
||||
'surface-action': 'var(--surface-action)',
|
||||
'surface-action-hover': 'var(--surface-action-hover)',
|
||||
'surface-disabled': 'var(--surface-disabled)',
|
||||
'surface-tertiary': 'var(--surface-tertiary)',
|
||||
'surface-card': 'var(--surface-card)',
|
||||
'surface-card-hover': 'var(--surface-card-hover)',
|
||||
'surface-card-focus': 'var(--surface-card-focus)',
|
||||
'surface-card-default': 'var(--surface-card-default)',
|
||||
'surface-primary': 'var(--ds-bg-neutral-subtle-default)',
|
||||
'surface-secondary': 'var(--ds-bg-neutral-default-default)',
|
||||
'surface-success': 'var(--ds-bg-status-completed-subtle-default)',
|
||||
'surface-information': 'var(--ds-bg-status-splitting-subtle-default)',
|
||||
'surface-warning': 'var(--ds-bg-status-pending-subtle-default)',
|
||||
'surface-caution': 'var(--ds-bg-status-error-subtle-default)',
|
||||
'surface-action': 'var(--ds-bg-neutral-default-default)',
|
||||
'surface-action-hover': 'var(--ds-bg-neutral-default-hover)',
|
||||
'surface-disabled': 'var(--ds-bg-neutral-muted-disabled)',
|
||||
'surface-tertiary': 'var(--ds-bg-neutral-strong-default)',
|
||||
'surface-card': 'var(--ds-bg-neutral-default-default)',
|
||||
'surface-card-hover': 'var(--ds-bg-neutral-default-hover)',
|
||||
'surface-card-focus': 'var(--ds-bg-neutral-default-focus)',
|
||||
'surface-card-default': '1.25rem',
|
||||
'surface-session-single-agent-selected':
|
||||
'var(--surface-session-single-agent-selected)',
|
||||
'var(--ds-bg-single-agent-subtle-selected)',
|
||||
'surface-session-workforce-selected':
|
||||
'var(--surface-session-workforce-selected)',
|
||||
'var(--ds-bg-workforce-subtle-selected)',
|
||||
|
||||
'border-primary': 'var(--border-primary)',
|
||||
'border-secondary': 'var(--border-secondary)',
|
||||
|
|
@ -631,7 +653,7 @@ module.exports = {
|
|||
'border-information': 'var(--border-information)',
|
||||
'border-success': 'var(--border-success)',
|
||||
'border-warning': 'var(--border-warning)',
|
||||
'border-cuation': 'var(--border-cuation)',
|
||||
'border-caution': 'var(--border-caution)',
|
||||
'border-focus': 'var(--border-focus)',
|
||||
'border-action': 'var(--border-action)',
|
||||
'border-action-hover': 'var(--border-action-hover)',
|
||||
|
|
@ -650,8 +672,7 @@ module.exports = {
|
|||
'icon-information': 'var(--icon-information)',
|
||||
'icon-success': 'var(--icon-success)',
|
||||
'icon-warning': 'var(--icon-warning)',
|
||||
'icon-caution': 'var(--icon-cuation)',
|
||||
'icon-cuation': 'var(--icon-cuation)',
|
||||
'icon-caution': 'var(--icon-caution)',
|
||||
'icon-action-hover': 'var(--icon-action-hover)',
|
||||
'icon-multimodal': 'var(--icon-multimodal)',
|
||||
'icon-socialmedia': 'var(--icon-socialmedia)',
|
||||
|
|
@ -688,7 +709,7 @@ module.exports = {
|
|||
'fill-fill-success-active': 'var(--fill-fill-success-active)',
|
||||
'fill-fill-success-disable': 'var(--fill-fill-success-disable)',
|
||||
'fill-fill-warning': 'var(--fill-fill-warning)',
|
||||
'fill-fill-cuation': 'var(--fill-fill-cuation)',
|
||||
'fill-fill-caution': 'var(--fill-fill-caution)',
|
||||
'fill-socialmedia': 'var(--fill-socialmedia)',
|
||||
'fill-document': 'var(--fill-document)',
|
||||
'fill-browser': 'var(--fill-browser)',
|
||||
|
|
@ -700,6 +721,9 @@ module.exports = {
|
|||
'fill-skeloten-default': 'var(--fill-skeloten-default)',
|
||||
'fill-fill-information': 'var(--fill-fill-information)',
|
||||
|
||||
/** Embedded xterm viewport backdrop; fixed black (see tokens/component.color.json). */
|
||||
'terminal-viewport-surface': 'var(--terminal-viewport-surface)',
|
||||
|
||||
'bg-page': 'var(--bg-page)',
|
||||
'bg-primary': 'var(--bg-primary)',
|
||||
'bg-secondary': 'var(--bg-secondary)',
|
||||
|
|
|
|||
75
tokens/base.color.json
Normal file
75
tokens/base.color.json
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"$description": "Base color seeds and fixed role anchors for Design Tokens V2.",
|
||||
"themes": {
|
||||
"light": {
|
||||
"eigent": {
|
||||
"accent": "#000000",
|
||||
"background": "#faf7f6",
|
||||
"ink": "#1d1d1d"
|
||||
},
|
||||
"claude": {
|
||||
"accent": "#cc7d5e",
|
||||
"background": "#f9f9f7",
|
||||
"ink": "#2d2d2b"
|
||||
},
|
||||
"codex": {
|
||||
"accent": "#0169cc",
|
||||
"background": "#ffffff",
|
||||
"ink": "#0d0d0d"
|
||||
},
|
||||
"camel": {
|
||||
"accent": "#4c19e8",
|
||||
"background": "#ffffff",
|
||||
"ink": "#1d1d1d"
|
||||
}
|
||||
},
|
||||
"dark": {
|
||||
"eigent": {
|
||||
"accent": "#ede1db",
|
||||
"background": "#1f1f1f",
|
||||
"ink": "#ffffff"
|
||||
},
|
||||
"claude": {
|
||||
"accent": "#cc7d5e",
|
||||
"background": "#2d2d2b",
|
||||
"ink": "#f9f9f7"
|
||||
},
|
||||
"codex": {
|
||||
"accent": "#0169cc",
|
||||
"background": "#111111",
|
||||
"ink": "#fcfcfc"
|
||||
},
|
||||
"camel": {
|
||||
"accent": "#b5afff",
|
||||
"background": "#1f1f1f",
|
||||
"ink": "#fafafa"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fixedAnchors": {
|
||||
"light": {
|
||||
"single-agent": "#7e22ce",
|
||||
"workforce": "#007a55",
|
||||
"browser": "#0084d1",
|
||||
"terminal": "#009966",
|
||||
"document": "#e17100",
|
||||
"success": "#00a63e",
|
||||
"caution": "#e7000b",
|
||||
"error": "#e7000b",
|
||||
"warning": "#d08700",
|
||||
"information": "#155dfc"
|
||||
},
|
||||
"dark": {
|
||||
"single-agent": "#e9d5ff",
|
||||
"workforce": "#6ee7b7",
|
||||
"browser": "#7dd3fc",
|
||||
"terminal": "#6ee7b7",
|
||||
"document": "#ffd479",
|
||||
"success": "#4ade80",
|
||||
"caution": "#f87171",
|
||||
"error": "#f87171",
|
||||
"warning": "#facc15",
|
||||
"information": "#7ab3ff"
|
||||
}
|
||||
}
|
||||
}
|
||||
403
tokens/component.color.json
Normal file
403
tokens/component.color.json
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
{
|
||||
"$description": "Component aliases resolved from semantic tokens.",
|
||||
"component": {
|
||||
"text": {
|
||||
"heading": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.default.default}",
|
||||
"$extensions": { "cssVar": "--text-heading" }
|
||||
},
|
||||
"body": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.default.default}",
|
||||
"$extensions": { "cssVar": "--text-body" }
|
||||
},
|
||||
"label": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.muted.default}",
|
||||
"$extensions": { "cssVar": "--text-label" }
|
||||
},
|
||||
"primary": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.default.default}",
|
||||
"$extensions": { "cssVar": "--text-primary" }
|
||||
},
|
||||
"secondary": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.muted.default}",
|
||||
"$extensions": { "cssVar": "--text-secondary" }
|
||||
},
|
||||
"tertiary": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.subtle.default}",
|
||||
"$extensions": { "cssVar": "--text-tertiary" }
|
||||
},
|
||||
"action": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.default.default}",
|
||||
"$extensions": { "cssVar": "--text-action" }
|
||||
},
|
||||
"action-hover": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.default.hover}",
|
||||
"$extensions": { "cssVar": "--text-action-hover" }
|
||||
},
|
||||
"disabled": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.muted.disabled}",
|
||||
"$extensions": { "cssVar": "--text-disabled" }
|
||||
},
|
||||
"information": {
|
||||
"$type": "color",
|
||||
"$value": "{text.status-splitting.strong.default}",
|
||||
"$extensions": { "cssVar": "--text-information" }
|
||||
},
|
||||
"success": {
|
||||
"$type": "color",
|
||||
"$value": "{text.status-completed.strong.default}",
|
||||
"$extensions": { "cssVar": "--text-success" }
|
||||
},
|
||||
"warning": {
|
||||
"$type": "color",
|
||||
"$value": "{text.status-pending.strong.default}",
|
||||
"$extensions": { "cssVar": "--text-warning" }
|
||||
},
|
||||
"caution": {
|
||||
"$type": "color",
|
||||
"$value": "{text.status-error.strong.default}",
|
||||
"$extensions": { "cssVar": "--text-caution" }
|
||||
},
|
||||
"on-action": {
|
||||
"$type": "color",
|
||||
"$value": "{text.brand.inverse.default}",
|
||||
"$extensions": { "cssVar": "--text-on-action" }
|
||||
},
|
||||
"on-hover": {
|
||||
"$type": "color",
|
||||
"$value": "{text.brand.inverse.default}",
|
||||
"$extensions": { "cssVar": "--text-on-hover" }
|
||||
},
|
||||
"on-disabled": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.muted.disabled}",
|
||||
"$extensions": { "cssVar": "--text-on-disabled" }
|
||||
}
|
||||
},
|
||||
"border": {
|
||||
"primary": {
|
||||
"$type": "color",
|
||||
"$value": "{border.neutral.strong.default}",
|
||||
"$extensions": { "cssVar": "--border-primary" }
|
||||
},
|
||||
"secondary": {
|
||||
"$type": "color",
|
||||
"$value": "{border.neutral.default.default}",
|
||||
"$extensions": { "cssVar": "--border-secondary" }
|
||||
},
|
||||
"tertiary": {
|
||||
"$type": "color",
|
||||
"$value": "{border.neutral.subtle.default}",
|
||||
"$extensions": { "cssVar": "--border-tertiary" }
|
||||
},
|
||||
"focus": {
|
||||
"$type": "color",
|
||||
"$value": "{border.brand.default.focus}",
|
||||
"$extensions": { "cssVar": "--border-focus" }
|
||||
},
|
||||
"information": {
|
||||
"$type": "color",
|
||||
"$value": "{border.status-splitting.default.default}",
|
||||
"$extensions": { "cssVar": "--border-information" }
|
||||
},
|
||||
"success": {
|
||||
"$type": "color",
|
||||
"$value": "{border.status-completed.default.default}",
|
||||
"$extensions": { "cssVar": "--border-success" }
|
||||
},
|
||||
"warning": {
|
||||
"$type": "color",
|
||||
"$value": "{border.status-pending.default.default}",
|
||||
"$extensions": { "cssVar": "--border-warning" }
|
||||
},
|
||||
"caution": {
|
||||
"$type": "color",
|
||||
"$value": "{border.status-error.default.default}",
|
||||
"$extensions": { "cssVar": "--border-caution" }
|
||||
},
|
||||
"disabled": {
|
||||
"$type": "color",
|
||||
"$value": "{border.neutral.subtle.disabled}",
|
||||
"$extensions": { "cssVar": "--border-disabled" }
|
||||
}
|
||||
},
|
||||
"icon": {
|
||||
"primary": {
|
||||
"$type": "color",
|
||||
"$value": "{icon.neutral.default.default}",
|
||||
"$extensions": { "cssVar": "--icon-primary" }
|
||||
},
|
||||
"secondary": {
|
||||
"$type": "color",
|
||||
"$value": "{icon.neutral.muted.default}",
|
||||
"$extensions": { "cssVar": "--icon-secondary" }
|
||||
},
|
||||
"action": {
|
||||
"$type": "color",
|
||||
"$value": "{icon.neutral.default.default}",
|
||||
"$extensions": { "cssVar": "--icon-action" }
|
||||
},
|
||||
"action-hover": {
|
||||
"$type": "color",
|
||||
"$value": "{icon.neutral.default.hover}",
|
||||
"$extensions": { "cssVar": "--icon-action-hover" }
|
||||
},
|
||||
"disabled": {
|
||||
"$type": "color",
|
||||
"$value": "{icon.neutral.muted.disabled}",
|
||||
"$extensions": { "cssVar": "--icon-disabled" }
|
||||
},
|
||||
"information": {
|
||||
"$type": "color",
|
||||
"$value": "{icon.status-splitting.default.default}",
|
||||
"$extensions": { "cssVar": "--icon-information" }
|
||||
},
|
||||
"success": {
|
||||
"$type": "color",
|
||||
"$value": "{icon.status-completed.default.default}",
|
||||
"$extensions": { "cssVar": "--icon-success" }
|
||||
},
|
||||
"warning": {
|
||||
"$type": "color",
|
||||
"$value": "{icon.status-pending.default.default}",
|
||||
"$extensions": { "cssVar": "--icon-warning" }
|
||||
},
|
||||
"caution": {
|
||||
"$type": "color",
|
||||
"$value": "{icon.status-error.default.default}",
|
||||
"$extensions": { "cssVar": "--icon-caution" }
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"background": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.subtle.default}",
|
||||
"$extensions": { "cssVar": "--bg-page" }
|
||||
},
|
||||
"fill-default": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.strong.default}",
|
||||
"$extensions": { "cssVar": "--fill-default" }
|
||||
},
|
||||
"code-bg": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.inverse.default}",
|
||||
"$extensions": { "cssVar": "--code-bg" }
|
||||
},
|
||||
"code-foreground": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.inverse.default}",
|
||||
"$extensions": { "cssVar": "--code-foreground" }
|
||||
},
|
||||
"code-surface": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.inverse.hover}",
|
||||
"$extensions": { "cssVar": "--code-surface" }
|
||||
},
|
||||
"mask-default": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.subtle.default}",
|
||||
"$extensions": { "cssVar": "--mask-default" }
|
||||
},
|
||||
"mask-dark": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.inverse.default}",
|
||||
"$extensions": { "cssVar": "--mask-dark" }
|
||||
}
|
||||
},
|
||||
"terminal": {
|
||||
"viewport-surface": {
|
||||
"$type": "color",
|
||||
"$description": "Fixed black backdrop for the embedded terminal viewport (xterm); not theme-tinted.",
|
||||
"$value": "#000000",
|
||||
"$extensions": { "cssVar": "--terminal-viewport-surface" }
|
||||
}
|
||||
},
|
||||
"compat": {
|
||||
"text-session-single-agent": {
|
||||
"$type": "color",
|
||||
"$value": "{text.single-agent.default.default}",
|
||||
"$extensions": { "cssVar": "--text-session-single-agent" }
|
||||
},
|
||||
"text-session-workforce": {
|
||||
"$type": "color",
|
||||
"$value": "{text.workforce.default.default}",
|
||||
"$extensions": { "cssVar": "--text-session-workforce" }
|
||||
},
|
||||
"fill-fill-primary": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.brand.default.default}",
|
||||
"$extensions": { "cssVar": "--fill-fill-primary" }
|
||||
},
|
||||
"fill-fill-primary-hover": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.brand.default.hover}",
|
||||
"$extensions": { "cssVar": "--fill-fill-primary-hover" }
|
||||
},
|
||||
"fill-fill-primary-active": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.brand.default.active}",
|
||||
"$extensions": { "cssVar": "--fill-fill-primary-active" }
|
||||
},
|
||||
"fill-fill-primary-disabled": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.muted.disabled}",
|
||||
"$extensions": { "cssVar": "--fill-fill-primary-disabled" }
|
||||
},
|
||||
"fill-fill-tertiary": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.subtle.default}",
|
||||
"$extensions": { "cssVar": "--fill-fill-tertiary" }
|
||||
},
|
||||
"fill-fill-tertiary-hover": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.subtle.hover}",
|
||||
"$extensions": { "cssVar": "--fill-fill-tertiary-hover" }
|
||||
},
|
||||
"fill-fill-tertiary-active": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.subtle.active}",
|
||||
"$extensions": { "cssVar": "--fill-fill-tertiary-active" }
|
||||
},
|
||||
"fill-fill-tertiary-disabled": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.muted.disabled}",
|
||||
"$extensions": { "cssVar": "--fill-fill-tertiary-disabled" }
|
||||
},
|
||||
"fill-fill-transparent": {
|
||||
"$type": "color",
|
||||
"$value": "transparent",
|
||||
"$extensions": { "cssVar": "--fill-fill-transparent" }
|
||||
},
|
||||
"fill-fill-transparent-hover": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.subtle.hover}",
|
||||
"$extensions": { "cssVar": "--fill-fill-transparent-hover" }
|
||||
},
|
||||
"fill-fill-transparent-active": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.subtle.active}",
|
||||
"$extensions": { "cssVar": "--fill-fill-transparent-active" }
|
||||
},
|
||||
"fill-fill-transparent-disabled": {
|
||||
"$type": "color",
|
||||
"$value": "transparent",
|
||||
"$extensions": { "cssVar": "--fill-fill-transparent-disabled" }
|
||||
},
|
||||
"fill-fill-secondary": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.default.default}",
|
||||
"$extensions": { "cssVar": "--fill-fill-secondary" }
|
||||
},
|
||||
"fill-fill-secondary-hover": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.default.hover}",
|
||||
"$extensions": { "cssVar": "--fill-fill-secondary-hover" }
|
||||
},
|
||||
"fill-fill-secondary-active": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.default.active}",
|
||||
"$extensions": { "cssVar": "--fill-fill-secondary-active" }
|
||||
},
|
||||
"fill-fill-secondary-disabled": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.muted.disabled}",
|
||||
"$extensions": { "cssVar": "--fill-fill-secondary-disabled" }
|
||||
},
|
||||
"fill-fill-success": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.status-completed.default.default}",
|
||||
"$extensions": { "cssVar": "--fill-fill-success" }
|
||||
},
|
||||
"fill-fill-success-hover": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.status-completed.subtle.hover}",
|
||||
"$extensions": { "cssVar": "--fill-fill-success-hover" }
|
||||
},
|
||||
"fill-fill-success-active": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.status-completed.subtle.active}",
|
||||
"$extensions": { "cssVar": "--fill-fill-success-active" }
|
||||
},
|
||||
"fill-fill-success-disable": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.status-completed.subtle.default}",
|
||||
"$extensions": { "cssVar": "--fill-fill-success-disable" }
|
||||
},
|
||||
"fill-fill-warning": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.status-pending.default.default}",
|
||||
"$extensions": { "cssVar": "--fill-fill-warning" }
|
||||
},
|
||||
"fill-fill-caution": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.status-error.default.default}",
|
||||
"$extensions": { "cssVar": "--fill-fill-caution" }
|
||||
},
|
||||
"fill-fill-information": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.status-splitting.default.default}",
|
||||
"$extensions": { "cssVar": "--fill-fill-information" }
|
||||
},
|
||||
"fill-browser": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.browser.subtle.default}",
|
||||
"$extensions": { "cssVar": "--fill-browser" }
|
||||
},
|
||||
"fill-document": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.document.subtle.default}",
|
||||
"$extensions": { "cssVar": "--fill-document" }
|
||||
},
|
||||
"fill-socialmedia": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.single-agent.subtle.default}",
|
||||
"$extensions": { "cssVar": "--fill-socialmedia" }
|
||||
},
|
||||
"fill-multimodal": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.information.subtle.default}",
|
||||
"$extensions": { "cssVar": "--fill-multimodal" }
|
||||
},
|
||||
"fill-developer": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.workforce.subtle.default}",
|
||||
"$extensions": { "cssVar": "--fill-developer" }
|
||||
},
|
||||
"icon-on-action": {
|
||||
"$type": "color",
|
||||
"$value": "{text.brand.inverse.default}",
|
||||
"$extensions": { "cssVar": "--icon-on-action" }
|
||||
},
|
||||
"icon-on-hover": {
|
||||
"$type": "color",
|
||||
"$value": "{text.brand.inverse.default}",
|
||||
"$extensions": { "cssVar": "--icon-on-hover" }
|
||||
},
|
||||
"icon-on-disabled": {
|
||||
"$type": "color",
|
||||
"$value": "{text.neutral.muted.disabled}",
|
||||
"$extensions": { "cssVar": "--icon-on-disabled" }
|
||||
},
|
||||
"bg-page-default": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.subtle.default}",
|
||||
"$extensions": { "cssVar": "--bg-page-default" }
|
||||
},
|
||||
"background": {
|
||||
"$type": "color",
|
||||
"$value": "{bg.neutral.subtle.default}",
|
||||
"$extensions": { "cssVar": "--background" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
tokens/contracts/default.base.json
Normal file
15
tokens/contracts/default.base.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"contracts": {
|
||||
"base": {
|
||||
"version": 2,
|
||||
"themeId": "eigent",
|
||||
"contrast": 43,
|
||||
"overrides": {
|
||||
"tone": {},
|
||||
"emphasis": {},
|
||||
"state": {},
|
||||
"cell": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
tokens/contracts/default.dark.json
Normal file
4
tokens/contracts/default.dark.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$extends": "contracts.base",
|
||||
"mode": "dark"
|
||||
}
|
||||
4
tokens/contracts/default.light.json
Normal file
4
tokens/contracts/default.light.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$extends": "contracts.base",
|
||||
"mode": "light"
|
||||
}
|
||||
29
tokens/manifest.json
Normal file
29
tokens/manifest.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"elements": ["bg", "text", "border", "icon", "ring"],
|
||||
"emphasis": ["subtle", "muted", "default", "strong", "inverse"],
|
||||
"states": ["default", "hover", "active", "selected", "focus", "disabled"],
|
||||
"tones": [
|
||||
"neutral",
|
||||
"brand",
|
||||
"status-running",
|
||||
"status-splitting",
|
||||
"status-pending",
|
||||
"status-error",
|
||||
"status-reassigning",
|
||||
"status-completed",
|
||||
"status-blocked",
|
||||
"status-paused",
|
||||
"status-skipped",
|
||||
"status-cancelled",
|
||||
"single-agent",
|
||||
"workforce",
|
||||
"browser",
|
||||
"terminal",
|
||||
"document",
|
||||
"success",
|
||||
"caution",
|
||||
"error",
|
||||
"warning",
|
||||
"information"
|
||||
]
|
||||
}
|
||||
143
tokens/semantic.color.json
Normal file
143
tokens/semantic.color.json
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
{
|
||||
"$description": "Semantic token axes and generation transforms for V2.",
|
||||
"axes": {
|
||||
"elements": ["bg", "text", "border", "icon", "ring"],
|
||||
"tones": [
|
||||
"neutral",
|
||||
"brand",
|
||||
"status-running",
|
||||
"status-splitting",
|
||||
"status-pending",
|
||||
"status-error",
|
||||
"status-reassigning",
|
||||
"status-completed",
|
||||
"status-blocked",
|
||||
"status-paused",
|
||||
"status-skipped",
|
||||
"status-cancelled",
|
||||
"single-agent",
|
||||
"workforce",
|
||||
"browser",
|
||||
"terminal",
|
||||
"document",
|
||||
"success",
|
||||
"caution",
|
||||
"error",
|
||||
"warning",
|
||||
"information"
|
||||
],
|
||||
"emphasis": ["subtle", "muted", "default", "strong", "inverse"],
|
||||
"states": ["default", "hover", "active", "selected", "focus", "disabled"]
|
||||
},
|
||||
"transforms": {
|
||||
"emphasis": {
|
||||
"subtle": { "dL": 0.18, "dC": -0.02 },
|
||||
"muted": { "dL": 0.1, "dC": -0.01 },
|
||||
"default": { "dL": 0, "dC": 0 },
|
||||
"strong": { "dL": -0.08, "dC": 0.02 },
|
||||
"inverse": { "dL": -0.22, "dC": 0.01 }
|
||||
},
|
||||
"state": {
|
||||
"default": { "dL": 0, "dC": 0 },
|
||||
"hover": { "dL": -0.03, "dC": 0.01 },
|
||||
"active": { "dL": -0.06, "dC": 0.015 },
|
||||
"selected": { "dL": -0.08, "dC": 0.015 },
|
||||
"focus": { "dL": -0.05, "dC": 0.03 },
|
||||
"disabled": { "dL": 0.08, "dC": -0.03, "alpha": 0.5 }
|
||||
},
|
||||
"element": {
|
||||
"bg": { "dL": 0, "dC": 0 },
|
||||
"text": { "dL": -0.12, "dC": -0.005 },
|
||||
"border": { "dL": -0.06, "dC": -0.01, "alpha": 0.75 },
|
||||
"icon": { "dL": -0.1, "dC": -0.005, "alpha": 0.92 },
|
||||
"ring": { "dL": -0.04, "dC": 0.01, "alpha": 0.62 }
|
||||
}
|
||||
},
|
||||
"toneSource": {
|
||||
"neutral": {
|
||||
"source": "background",
|
||||
"sourceByElement": {
|
||||
"text": "ink",
|
||||
"icon": "ink"
|
||||
},
|
||||
"dC": -0.08,
|
||||
"dLLight": 0.08,
|
||||
"dLDark": -0.08
|
||||
},
|
||||
"brand": { "source": "accent", "dC": 0.03 },
|
||||
"status-running": { "source": "accent", "dH": 0, "dC": 0.02 },
|
||||
"status-splitting": { "source": "accent", "dH": 25, "dC": 0.015 },
|
||||
"status-pending": { "source": "accent", "dH": 72, "dC": 0.02 },
|
||||
"status-error": {
|
||||
"source": "accent",
|
||||
"dH": 18,
|
||||
"dC": 0.03,
|
||||
"dLLight": -0.03,
|
||||
"dLDark": 0.03
|
||||
},
|
||||
"status-reassigning": { "source": "accent", "dH": 58, "dC": 0.015 },
|
||||
"status-completed": { "source": "accent", "dH": 140, "dC": 0.02 },
|
||||
"status-blocked": { "source": "accent", "dH": 62, "dC": 0.015 },
|
||||
"status-paused": { "source": "accent", "dH": 50, "dC": 0.01 },
|
||||
"status-skipped": {
|
||||
"source": "ink",
|
||||
"dC": -0.06,
|
||||
"dLLight": 0.12,
|
||||
"dLDark": -0.12
|
||||
},
|
||||
"status-cancelled": {
|
||||
"source": "ink",
|
||||
"dC": -0.05,
|
||||
"dLLight": 0.09,
|
||||
"dLDark": -0.09
|
||||
},
|
||||
"single-agent": { "source": "fixed" },
|
||||
"workforce": { "source": "fixed" },
|
||||
"browser": { "source": "fixed" },
|
||||
"terminal": { "source": "fixed" },
|
||||
"document": { "source": "fixed" },
|
||||
"success": { "source": "fixed" },
|
||||
"caution": { "source": "fixed" },
|
||||
"error": { "source": "fixed" },
|
||||
"warning": { "source": "fixed" },
|
||||
"information": { "source": "fixed" }
|
||||
},
|
||||
"contrastPairs": [
|
||||
{
|
||||
"fg": "text.neutral.default.default",
|
||||
"bg": "bg.neutral.subtle.default",
|
||||
"minContrast": 4.5
|
||||
},
|
||||
{
|
||||
"fg": "text.neutral.muted.default",
|
||||
"bg": "bg.neutral.subtle.default",
|
||||
"minContrast": 4.5
|
||||
},
|
||||
{
|
||||
"fg": "text.brand.inverse.default",
|
||||
"bg": "bg.brand.default.default",
|
||||
"minContrast": 3,
|
||||
"largeText": true
|
||||
},
|
||||
{
|
||||
"fg": "text.status-completed.strong.default",
|
||||
"bg": "bg.status-completed.subtle.default",
|
||||
"minContrast": 4.5
|
||||
},
|
||||
{
|
||||
"fg": "text.status-error.strong.default",
|
||||
"bg": "bg.status-error.subtle.default",
|
||||
"minContrast": 4.5
|
||||
},
|
||||
{
|
||||
"fg": "text.status-splitting.strong.default",
|
||||
"bg": "bg.status-splitting.subtle.default",
|
||||
"minContrast": 4.5
|
||||
},
|
||||
{
|
||||
"fg": "text.status-pending.strong.default",
|
||||
"bg": "bg.status-pending.subtle.default",
|
||||
"minContrast": 4.5
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -6,5 +6,5 @@
|
|||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts", "package.json"]
|
||||
"include": ["vite.config.ts", "package.json", "scripts/**/*.ts"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue