mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-28 07:37:36 +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
129 lines
3.5 KiB
TypeScript
129 lines
3.5 KiB
TypeScript
import {
|
|
AccessibleIcon,
|
|
css,
|
|
cssVar,
|
|
Grid,
|
|
LinkButton,
|
|
Text,
|
|
theme,
|
|
webstudioBrand,
|
|
} from "@webstudio-is/design-system";
|
|
import { WebstudioIcon } from "@webstudio-is/icons";
|
|
|
|
const pageStyle = css({
|
|
position: "fixed",
|
|
justifyItems: "center",
|
|
alignContent: "start",
|
|
inset: 0,
|
|
background: webstudioBrand.backgroundGradient,
|
|
paddingTop: "10vh",
|
|
// prevent global root styles override error color
|
|
color: `light-dark(
|
|
${cssVar("--foreground-primary", "#11181c")},
|
|
${cssVar("--foreground-on-inverse", "#fff")}
|
|
)`,
|
|
});
|
|
|
|
const standaloneButtonBackground = cssVar("--background-accent", "#096cff");
|
|
const standaloneButtonForeground = cssVar("--foreground-on-accent", "#fff");
|
|
const standaloneButtonHoverOverlay = `oklch(from ${standaloneButtonForeground} 0 0 h / 6.2745%)`;
|
|
const standaloneButtonPressedOverlay = `oklch(from ${standaloneButtonForeground} 0 0 h / 10.9804%)`;
|
|
|
|
const standaloneButtonStyle = {
|
|
background: standaloneButtonBackground,
|
|
color: standaloneButtonForeground,
|
|
"&&[data-state=auto]:hover, &&[data-state=hover]": {
|
|
background: `linear-gradient(${standaloneButtonHoverOverlay}, ${standaloneButtonHoverOverlay}), ${standaloneButtonBackground}`,
|
|
color: standaloneButtonForeground,
|
|
},
|
|
"&&[data-state=auto]:focus-visible, &&[data-state=focus]": {
|
|
color: standaloneButtonForeground,
|
|
outline: `1px solid ${cssVar("--border-focus", "#297aff")}`,
|
|
outlineOffset: "1px",
|
|
},
|
|
"&&[data-state=auto]:active, &&[data-state=pressed]": {
|
|
background: `linear-gradient(${standaloneButtonPressedOverlay}, ${standaloneButtonPressedOverlay}), ${standaloneButtonBackground}`,
|
|
color: standaloneButtonForeground,
|
|
},
|
|
};
|
|
|
|
export const ErrorMessage = ({
|
|
error,
|
|
}: {
|
|
error: {
|
|
status: number;
|
|
statusText?: string;
|
|
message: string;
|
|
description?: string;
|
|
};
|
|
}) => {
|
|
return (
|
|
<Grid className={pageStyle()} justify={"center"} gap={6}>
|
|
<AccessibleIcon label="Logo">
|
|
<WebstudioIcon size="60" />
|
|
</AccessibleIcon>
|
|
<div />
|
|
<div />
|
|
<Text
|
|
css={{
|
|
fontSize: theme.spacing[21],
|
|
lineHeight: 1,
|
|
}}
|
|
variant={"bigTitle"}
|
|
>
|
|
{error.status}
|
|
</Text>
|
|
|
|
<Grid
|
|
css={{
|
|
justifyItems: "center",
|
|
marginInline: theme.spacing[10],
|
|
maxWidth: 600,
|
|
}}
|
|
gap={5}
|
|
>
|
|
<Grid
|
|
css={{
|
|
background: `light-dark(
|
|
${cssVar("--background-primary", "#fff")},
|
|
${cssVar("--background-inverse", "#11181c")}
|
|
)`,
|
|
padding: theme.spacing[7],
|
|
borderRadius: theme.spacing[5],
|
|
minWidth: theme.spacing[34],
|
|
}}
|
|
gap="3"
|
|
>
|
|
<Text
|
|
css={{
|
|
display: "-webkit-box",
|
|
"-webkit-line-clamp": 4,
|
|
"-webkit-box-orient": "vertical",
|
|
overflow: "hidden",
|
|
textOverflow: "ellipsis",
|
|
wordBreak: "break-word",
|
|
}}
|
|
variant="brandSectionTitle"
|
|
userSelect="text"
|
|
>
|
|
{error.message ?? error.statusText}
|
|
</Text>
|
|
|
|
{error.description && (
|
|
<Text
|
|
css={{ wordBreak: "break-word", whiteSpace: "pre-line" }}
|
|
userSelect="text"
|
|
variant="brandRegular"
|
|
>
|
|
{error.description}
|
|
</Text>
|
|
)}
|
|
</Grid>
|
|
<LinkButton color="primary" href="/" css={standaloneButtonStyle}>
|
|
Go home
|
|
</LinkButton>
|
|
</Grid>
|
|
</Grid>
|
|
);
|
|
};
|