mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
fix(desktop-tooltips): use semantic secondary surfaces
Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
@@ -21,6 +21,7 @@ export default defineConfig({
|
||||
testMatch: [
|
||||
"**/smoke.spec.ts",
|
||||
"**/sidebar-offcanvas-rail.spec.ts",
|
||||
"**/tooltip-semantics.spec.ts",
|
||||
"**/search-scope-screenshots.spec.ts",
|
||||
"**/onboarding-docked-cta-screenshots.spec.ts",
|
||||
"**/identity-key-help.spec.ts",
|
||||
|
||||
@@ -88,8 +88,8 @@ function ChangeDescription({ change }: { change: RestartChange }) {
|
||||
const TOOLTIP_CAP = 6;
|
||||
|
||||
/**
|
||||
* `tooltip` — renders inside the dark `bg-primary` tooltip; uses
|
||||
* `text-primary-foreground` variants for contrast there.
|
||||
* `tooltip` — renders inside the semantic secondary tooltip surface; uses
|
||||
* `text-secondary-foreground` variants for contrast there.
|
||||
* `inline` — renders inside the amber Runtime banner or other light
|
||||
* surfaces; inherits foreground from the container instead.
|
||||
*/
|
||||
@@ -107,10 +107,10 @@ function DiffList({
|
||||
cap !== undefined && entries.length > cap ? entries.length - cap : 0;
|
||||
|
||||
const valueClass =
|
||||
variant === "tooltip" ? "text-primary-foreground/80" : "text-foreground";
|
||||
variant === "tooltip" ? "text-secondary-foreground/80" : "text-foreground";
|
||||
const overflowClass =
|
||||
variant === "tooltip"
|
||||
? "text-primary-foreground/60"
|
||||
? "text-secondary-foreground/60"
|
||||
: "text-muted-foreground";
|
||||
|
||||
return (
|
||||
@@ -180,7 +180,7 @@ export function RestartDiffBadge({
|
||||
<TooltipContent className="max-w-72 text-xs" side="bottom">
|
||||
<p className="mb-1.5 font-semibold">Config changed since last start:</p>
|
||||
<DiffList cap={TOOLTIP_CAP} entries={restartDiff} />
|
||||
<p className="mt-1.5 text-primary-foreground/70">
|
||||
<p className="mt-1.5 text-secondary-foreground/70">
|
||||
{autoRestartEnabled ? AUTO_RESTART_ON_BLURB : AUTO_RESTART_OFF_BLURB}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
|
||||
@@ -143,7 +143,7 @@ export function AddChannelBotTeamsSection({
|
||||
<div className="space-y-1.5">
|
||||
<p className="font-medium">{team.name}</p>
|
||||
{team.description ? (
|
||||
<p className="text-2xs text-primary-foreground/80">
|
||||
<p className="text-2xs text-secondary-foreground/80">
|
||||
{team.description}
|
||||
</p>
|
||||
) : null}
|
||||
@@ -153,15 +153,15 @@ export function AddChannelBotTeamsSection({
|
||||
inChannelPersonaIds?.has(persona.id) ?? false;
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-1 rounded-full bg-primary-foreground/10 px-1.5 py-0.5"
|
||||
className="flex items-center gap-1 rounded-full bg-secondary-foreground/10 px-1.5 py-0.5"
|
||||
key={persona.id}
|
||||
>
|
||||
<ProfileAvatar
|
||||
avatarUrl={persona.avatarUrl}
|
||||
className="h-4 w-4 text-3xs bg-primary-foreground/20 text-primary-foreground"
|
||||
className="h-4 w-4 text-3xs bg-secondary-foreground/20 text-secondary-foreground"
|
||||
label={persona.displayName}
|
||||
/>
|
||||
<span className="text-2xs text-primary-foreground">
|
||||
<span className="text-2xs text-secondary-foreground">
|
||||
{persona.displayName}
|
||||
</span>
|
||||
{personaInChannel ? (
|
||||
|
||||
@@ -60,7 +60,7 @@ export function ProjectAuthorIdentity({
|
||||
/>
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate font-medium">{label}</span>
|
||||
<span className="block text-primary-foreground/70">
|
||||
<span className="block text-secondary-foreground/70">
|
||||
{roleLabel}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -34,7 +34,7 @@ const TooltipContent = React.forwardRef<
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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-tooltip-content-transform-origin)",
|
||||
"z-50 overflow-hidden rounded-md bg-secondary px-3 py-1.5 text-xs text-secondary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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-tooltip-content-transform-origin)",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
88
desktop/tests/e2e/tooltip-semantics.spec.ts
Normal file
88
desktop/tests/e2e/tooltip-semantics.spec.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { expect, test, type Locator, type Page } from "@playwright/test";
|
||||
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
import { waitForAnimations } from "../helpers/animations";
|
||||
|
||||
const THEMES = ["buzz", "catppuccin-mocha"] as const;
|
||||
|
||||
async function seedTheme(page: Page, theme: (typeof THEMES)[number]) {
|
||||
await page.addInitScript((value) => {
|
||||
window.localStorage.setItem("buzz-theme", value);
|
||||
}, theme);
|
||||
}
|
||||
|
||||
async function expectSecondarySurface(tooltip: Locator) {
|
||||
const colors = await tooltip.evaluate((element) => {
|
||||
const sample = document.createElement("div");
|
||||
sample.style.cssText =
|
||||
"position:fixed;visibility:hidden;background:hsl(var(--secondary));color:hsl(var(--secondary-foreground))";
|
||||
document.body.append(sample);
|
||||
const tooltipStyle = getComputedStyle(element);
|
||||
const sampleStyle = getComputedStyle(sample);
|
||||
const result = {
|
||||
background: tooltipStyle.backgroundColor,
|
||||
color: tooltipStyle.color,
|
||||
expectedBackground: sampleStyle.backgroundColor,
|
||||
expectedColor: sampleStyle.color,
|
||||
};
|
||||
sample.remove();
|
||||
return result;
|
||||
});
|
||||
|
||||
expect(colors.background).toBe(colors.expectedBackground);
|
||||
expect(colors.color).toBe(colors.expectedColor);
|
||||
}
|
||||
|
||||
for (const theme of THEMES) {
|
||||
test(`simple and rich tooltips use the secondary surface — ${theme}`, async ({
|
||||
page,
|
||||
}) => {
|
||||
await seedTheme(page, theme);
|
||||
await installMockBridge(page, {
|
||||
personas: [
|
||||
{
|
||||
id: "tooltip-reviewer",
|
||||
displayName: "Tooltip Reviewer",
|
||||
systemPrompt: "Review tooltip contrast.",
|
||||
},
|
||||
],
|
||||
teams: [
|
||||
{
|
||||
id: "tooltip-team",
|
||||
name: "Contrast Crew",
|
||||
description: "Checks semantic surface contrast",
|
||||
personaIds: ["tooltip-reviewer"],
|
||||
},
|
||||
],
|
||||
});
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByTestId("channel-random").click();
|
||||
await page.getByTestId("channel-members-trigger").hover();
|
||||
const simpleTooltip = page.getByRole("tooltip");
|
||||
await expect(simpleTooltip).toHaveText("Channel members");
|
||||
await expectSecondarySurface(simpleTooltip);
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
await page.getByTestId("channel-intro-action-create-agent").click();
|
||||
const dialog = page.getByTestId("add-channel-bot-dialog");
|
||||
await expect(dialog).toBeVisible();
|
||||
await dialog.getByRole("button", { name: "Contrast Crew" }).hover();
|
||||
|
||||
const richTooltip = page.getByRole("tooltip");
|
||||
await expect(richTooltip).toContainText("Checks semantic surface contrast");
|
||||
await expect(richTooltip).toContainText("Tooltip Reviewer");
|
||||
await expectSecondarySurface(richTooltip);
|
||||
await expect(
|
||||
richTooltip.getByText("Checks semantic surface contrast"),
|
||||
).toHaveClass(/text-secondary-foreground\/80/);
|
||||
await expect(richTooltip.getByText("Tooltip Reviewer")).toHaveClass(
|
||||
/text-secondary-foreground/,
|
||||
);
|
||||
|
||||
await waitForAnimations(page);
|
||||
await dialog.screenshot({
|
||||
path: `test-results/tooltip-semantics/${theme}-team-tooltip.png`,
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user