fix(ui): use the action color for the apps page visibility switches

The neutral switch tone drew the checked track in dark gray next to a
medium-gray unchecked track, so on and off were nearly indistinguishable
in light mode. Drop the neutral tone (its only caller was the apps page
"Show" column) and let those switches use the same action color as every
other switch.

Closes #8010
This commit is contained in:
Jason committed 2026-10-09 21:09:53 +08:00
1 parent 033017b0be
commit 8919d05cb3
3 files changed
+7 -23

No files matched your search

-1
View File
@@ -147,7 +147,6 @@ export function AppsPage() {
const visibilitySwitch = (app: AppId) => ( const visibilitySwitch = (app: AppId) => (
<Switch <Switch
size="sm" size="sm"
tone="neutral"
checked={visibleApps[app]} checked={visibleApps[app]}
disabled={!settings || (visibleApps[app] && visibleCount <= 1)} disabled={!settings || (visibleApps[app] && visibleCount <= 1)}
onCheckedChange={(checked) => setVisible(app, checked)} onCheckedChange={(checked) => setVisible(app, checked)}
+3 -15
View File
@@ -6,42 +6,30 @@ export interface SwitchProps
extends React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> { extends React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> {
/** default:30×18;sm:26×16,给表格里成排的开关用 */ /** default:30×18;sm:26×16,给表格里成排的开关用 */
size?: "default" | "sm"; size?: "default" | "sm";
/**
* action(默认):开 = 操作色;neutral:开 = 中性深灰(--text-2),
* 用在一列成排、不该比旁边按钮还抢眼的开关上(例如应用页的「显示」列)。
*/
tone?: "action" | "neutral";
} }
const Switch = React.forwardRef< const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>, React.ElementRef<typeof SwitchPrimitives.Root>,
SwitchProps SwitchProps
>(({ className, size = "default", tone = "action", ...props }, ref) => ( >(({ className, size = "default", ...props }, ref) => (
<SwitchPrimitives.Root <SwitchPrimitives.Root
ref={ref} ref={ref}
data-size={size} data-size={size}
data-tone={tone}
className={cn( className={cn(
// v7:轨道 30×18,开 = 操作色、圆点反色;关 = --control-off、白圆点。 // v7:轨道 30×18,开 = 操作色、圆点反色;关 = --control-off、白圆点。
// 用 before 把点击区域扩到 38×28。 // 用 before 把点击区域扩到 38×28。
"peer relative inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors duration-150 before:absolute before:-inset-x-1.5 before:-inset-y-1.5 before:content-[''] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-45 data-[state=unchecked]:bg-control-off", "peer relative inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors duration-150 before:absolute before:-inset-x-1.5 before:-inset-y-1.5 before:content-[''] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-45 data-[state=checked]:bg-action data-[state=unchecked]:bg-control-off",
size === "sm" ? "h-4 w-[26px]" : "h-[18px] w-[30px]", size === "sm" ? "h-4 w-[26px]" : "h-[18px] w-[30px]",
tone === "neutral"
? "data-[state=checked]:bg-fg-2"
: "data-[state=checked]:bg-action",
className, className,
)} )}
{...props} {...props}
> >
<SwitchPrimitives.Thumb <SwitchPrimitives.Thumb
className={cn( className={cn(
"pointer-events-none block rounded-full shadow-v7-sm ring-0 transition-[transform,background-color] duration-150 data-[state=unchecked]:translate-x-0 data-[state=unchecked]:bg-white", "pointer-events-none block rounded-full shadow-v7-sm ring-0 transition-[transform,background-color] duration-150 data-[state=checked]:bg-action-fg data-[state=unchecked]:translate-x-0 data-[state=unchecked]:bg-white",
size === "sm" size === "sm"
? "h-3 w-3 data-[state=checked]:translate-x-2.5" ? "h-3 w-3 data-[state=checked]:translate-x-2.5"
: "h-3.5 w-3.5 data-[state=checked]:translate-x-3", : "h-3.5 w-3.5 data-[state=checked]:translate-x-3",
tone === "neutral"
? "data-[state=checked]:bg-surface"
: "data-[state=checked]:bg-action-fg",
)} )}
/> />
</SwitchPrimitives.Root> </SwitchPrimitives.Root>
+4 -7
View File
@@ -3,22 +3,19 @@ import { describe, expect, it } from "vitest";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
describe("Switch", () => { describe("Switch", () => {
it("keeps the default size and action tone", () => { it("keeps the default size and action color", () => {
render(<Switch aria-label="default" defaultChecked />); render(<Switch aria-label="default" defaultChecked />);
const element = screen.getByRole("switch", { name: "default" }); const element = screen.getByRole("switch", { name: "default" });
expect(element).toHaveAttribute("data-size", "default"); expect(element).toHaveAttribute("data-size", "default");
expect(element).toHaveAttribute("data-tone", "action");
expect(element.className).toContain("w-[30px]"); expect(element.className).toContain("w-[30px]");
expect(element.className).toContain("data-[state=checked]:bg-action"); expect(element.className).toContain("data-[state=checked]:bg-action");
}); });
it("supports a small neutral variant", () => { it("uses the action color for the small variant too", () => {
render(<Switch aria-label="small" size="sm" tone="neutral" />); render(<Switch aria-label="small" size="sm" />);
const element = screen.getByRole("switch", { name: "small" }); const element = screen.getByRole("switch", { name: "small" });
expect(element).toHaveAttribute("data-size", "sm"); expect(element).toHaveAttribute("data-size", "sm");
expect(element).toHaveAttribute("data-tone", "neutral");
expect(element.className).toContain("w-[26px]"); expect(element.className).toContain("w-[26px]");
expect(element.className).toContain("data-[state=checked]:bg-fg-2"); expect(element.className).toContain("data-[state=checked]:bg-action");
expect(element.className).not.toContain("data-[state=checked]:bg-action");
}); });
}); });