fix(webui): soften form control focus rings

This commit is contained in:
chengyongru
2026-08-11 14:39:07 +08:00
committed by chengyongru
parent cac39477ba
commit 3778e7e628
10 changed files with 47 additions and 12 deletions
+1 -1
View File
@@ -362,7 +362,7 @@ function AuthForm({
disabled={submitting} disabled={submitting}
aria-invalid={validationError ? true : undefined} aria-invalid={validationError ? true : undefined}
aria-describedby={validationError ? "webui-auth-error" : undefined} aria-describedby={validationError ? "webui-auth-error" : undefined}
className="pr-10 focus-visible:ring-1 focus-visible:ring-ring/30 focus-visible:ring-offset-0" className="pr-10"
autoFocus autoFocus
/> />
<Button <Button
@@ -19,7 +19,6 @@ import { cn } from "@/lib/utils";
export const SETTINGS_SEARCH_INPUT_CLASS = cn( export const SETTINGS_SEARCH_INPUT_CLASS = cn(
"border-border/45 bg-settings-surface transition-colors hover:border-border/70", "border-border/45 bg-settings-surface transition-colors hover:border-border/70",
"focus-visible:border-border/70 focus-visible:bg-background", "focus-visible:border-border/70 focus-visible:bg-background",
"focus-visible:ring-0 focus-visible:ring-offset-0",
); );
export function CapabilityInstallNotice({ export function CapabilityInstallNotice({
@@ -35,6 +35,7 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { formControlFocusClassName } from "@/components/ui/form-control";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { SegmentedControl } from "@/components/ui/segmented-control"; import { SegmentedControl } from "@/components/ui/segmented-control";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
@@ -835,7 +836,10 @@ export function AutomationEditDialog({
everyUnit: event.target.value as AutomationEveryUnit, everyUnit: event.target.value as AutomationEveryUnit,
})) }))
} }
className="h-10 w-full rounded-[12px] border border-input bg-background px-3 text-[13px] text-foreground outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring" className={cn(
"h-10 w-full rounded-[12px] border border-input bg-background px-3 text-[13px] text-foreground transition-colors",
formControlFocusClassName,
)}
> >
{AUTOMATION_EVERY_UNITS.map((unit) => ( {AUTOMATION_EVERY_UNITS.map((unit) => (
<option key={unit.value} value={unit.value}> <option key={unit.value} value={unit.value}>
@@ -3,6 +3,7 @@ import { ListTree, Search } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { formControlFocusClassName } from "@/components/ui/form-control";
import { import {
Sheet, Sheet,
SheetContent, SheetContent,
@@ -83,7 +84,8 @@ export function PromptNavigator({
placeholder={t("thread.promptNavigator.search")} placeholder={t("thread.promptNavigator.search")}
className={cn( className={cn(
"h-10 w-full rounded-full border border-border bg-background pl-9 pr-3 text-sm", "h-10 w-full rounded-full border border-border bg-background pl-9 pr-3 text-sm",
"outline-none transition focus:border-ring focus:ring-2 focus:ring-ring/20", "transition-colors",
formControlFocusClassName,
)} )}
/> />
</div> </div>
@@ -256,10 +256,7 @@ export function WorkspaceProjectPicker({
aria-label={t("workspace.dialog.manual")} aria-label={t("workspace.dialog.manual")}
aria-invalid={pathError || error ? true : undefined} aria-invalid={pathError || error ? true : undefined}
aria-describedby={pathError || error ? pathErrorId : undefined} aria-describedby={pathError || error ? pathErrorId : undefined}
className={cn( className="h-9 rounded-full border-border/55 bg-background/80 px-3 text-[12.5px]"
"h-9 rounded-full border-border/55 bg-background/80 px-3 text-[12.5px]",
"focus-visible:ring-1 focus-visible:ring-foreground/10 focus-visible:ring-offset-0",
)}
/> />
<Button <Button
type="submit" type="submit"
+2
View File
@@ -0,0 +1,2 @@
export const formControlFocusClassName =
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/50";
+3 -1
View File
@@ -1,5 +1,6 @@
import * as React from "react"; import * as React from "react";
import { formControlFocusClassName } from "@/components/ui/form-control";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>; export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
@@ -10,7 +11,8 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input <input
type={type} type={type}
className={cn( className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
formControlFocusClassName,
className, className,
)} )}
ref={ref} ref={ref}
+3 -1
View File
@@ -1,5 +1,6 @@
import * as React from "react"; import * as React from "react";
import { formControlFocusClassName } from "@/components/ui/form-control";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>; export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
@@ -9,7 +10,8 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
return ( return (
<textarea <textarea
className={cn( className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
formControlFocusClassName,
className, className,
)} )}
ref={ref} ref={ref}
+26
View File
@@ -0,0 +1,26 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
describe("form control focus styles", () => {
it.each([
["input", <Input aria-label="input" />],
["textarea", <Textarea aria-label="textarea" />],
])("uses a subdued inset focus ring for the %s", (label, control) => {
render(control);
const element = screen.getByLabelText(label);
expect(element).toHaveClass(
"focus-visible:ring-2",
"focus-visible:ring-inset",
"focus-visible:ring-ring/50",
);
expect(element).not.toHaveClass(
"ring-offset-background",
"focus-visible:ring-ring",
"focus-visible:ring-offset-2",
);
});
});
+3 -2
View File
@@ -834,8 +834,9 @@ describe("Settings channels", () => {
const emailRow = await screen.findByRole("button", { name: "View Email settings" }); const emailRow = await screen.findByRole("button", { name: "View Email settings" });
expect(screen.getByPlaceholderText("Search channels")).toHaveClass( expect(screen.getByPlaceholderText("Search channels")).toHaveClass(
"focus-visible:ring-0", "focus-visible:ring-2",
"focus-visible:ring-offset-0", "focus-visible:ring-inset",
"focus-visible:ring-ring/50",
); );
expect(screen.queryByRole("switch", { name: "Email channel" })).not.toBeInTheDocument(); expect(screen.queryByRole("switch", { name: "Email channel" })).not.toBeInTheDocument();