mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-27 15:16:53 +09:00
Closes #6301 ## Summary - Restore Dashboard project tags to the pre-color-refactor treatment: fixed white text over a 30% black scrim in both application themes. - Use one high-contrast text-selection treatment across native inputs, textareas, contenteditable fields, and every shared CodeMirror editor. - Preserve the existing subtle selection treatment for Navigator, Project Settings, command menus, Dashboard navigation, and other selected controls. - Add direct Storybook coverage for the CSS fragment editor. - Simplify chromatic color derivation from 571 to 329 lines without changing computed colors. ## Technical choices - Keep project tags independent from application-theme polarity because they overlay arbitrary thumbnail artwork. - Keep the existing `selectionBackground` control-state export unchanged at its original 12% light / 20% dark accent blend. - Define separate `--background-text-selection` and `--foreground-on-text-selection` tokens exclusively for selected text. - Use the bounded accent in light mode and a luminance-lifted accent in dark mode so selected text remains distinct even with extreme custom theme colors. - Style CodeMirror through its existing selection layer and `::selection` CSS hooks; no selection tracking or editor-state logic is added. - Leave canvas rich-text selection independent: authored canvas documents do not load the Builder UI theme stylesheet. - Replace five duplicated XYZ clamp formulas with the exact upper-luminance cap they reduce to. The removed lower-bound and white-mixing branches were unreachable because the minimum was fixed at zero. ## Todo - [x] Restore the original project-tag scrim and foreground in both themes. - [x] Inventory native, CodeMirror, and canvas rich-text selection paths. - [x] Unify native and shared CodeMirror text-selection colors. - [x] Keep text selection independent from selected control and navigation states. - [x] Add the missing CSS fragment editor story. - [x] Add light- and dark-mode browser regressions and confirm they fail before the fix. - [x] Enforce 4.5:1 selected-text contrast and 3:1 highlight-to-surface contrast across supported theme inputs. - [x] Remove duplicated and unreachable chromatic color derivation while preserving outputs. - [x] Review documentation impact; the design-system color-token reference is updated, with no user-facing product documentation change required. - [x] Verify fixture impact; no fixture inputs or generated outputs are affected. ## Verification - `pnpm --filter @webstudio-is/design-system test` — 227 tests passed, including the control/text separation regression, Navigator tree interaction, default-palette comparisons, and the full supported-theme contrast sweep - `pnpm --filter @webstudio-is/design-system typecheck` - `pnpm --filter @webstudio-is/builder exec vitest run app/shared/code-editor-base.browser.test.tsx --project app-browser-shared` - `pnpm --filter @webstudio-is/builder exec vitest run app/dashboard/projects/project-card.browser.test.tsx --project app-browser-canvas-dashboard` - `pnpm --filter @webstudio-is/builder typecheck` - Targeted `oxlint`, formatting, and `git diff --check` - Live dark-mode Dashboard verification of the restored subtle navigation selection - Storybook visual checks for the full color system and input/CodeMirror selection in light and dark modes
65 lines
1.9 KiB
TypeScript
65 lines
1.9 KiB
TypeScript
import { act } from "react-dom/test-utils";
|
|
import { createRoot, type Root } from "react-dom/client";
|
|
import { afterEach, beforeEach, expect, test } from "vitest";
|
|
import { ErrorMessage } from "./error-message.client";
|
|
|
|
(
|
|
globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
|
).IS_REACT_ACT_ENVIRONMENT = true;
|
|
|
|
let container: HTMLDivElement;
|
|
let root: Root;
|
|
|
|
beforeEach(() => {
|
|
container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
root = createRoot(container);
|
|
});
|
|
|
|
afterEach(() => {
|
|
act(() => root.unmount());
|
|
container.remove();
|
|
});
|
|
|
|
test("renders without design-system global color variables", () => {
|
|
act(() => {
|
|
root.render(
|
|
<ErrorMessage
|
|
error={{
|
|
status: 500,
|
|
message: "Canvas failed to render",
|
|
}}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const page = container.firstElementChild;
|
|
const link = container.querySelector("a");
|
|
const panel = link?.previousElementSibling;
|
|
if (
|
|
page instanceof HTMLElement === false ||
|
|
panel instanceof HTMLElement === false ||
|
|
link instanceof HTMLElement === false
|
|
) {
|
|
throw new Error("Expected the error page, panel, and link");
|
|
}
|
|
|
|
expect(getComputedStyle(page).color).toBe("rgb(17, 24, 28)");
|
|
expect(getComputedStyle(panel).backgroundColor).toBe("rgb(255, 255, 255)");
|
|
expect(getComputedStyle(link).backgroundColor).toBe("rgb(9, 108, 255)");
|
|
expect(getComputedStyle(link).color).toBe("rgb(255, 255, 255)");
|
|
|
|
for (const state of ["hover", "pressed"]) {
|
|
link.dataset.state = state;
|
|
const style = getComputedStyle(link);
|
|
expect(style.backgroundImage, state).not.toBe("none");
|
|
expect(style.color, state).toBe("rgb(255, 255, 255)");
|
|
}
|
|
|
|
link.dataset.state = "focus";
|
|
const focusStyle = getComputedStyle(link);
|
|
expect(focusStyle.outlineColor).toBe("rgb(41, 122, 255)");
|
|
expect(focusStyle.outlineStyle).not.toBe("none");
|
|
expect(focusStyle.color).toBe("rgb(255, 255, 255)");
|
|
});
|