feat: goose2 compact settings modal headers (#8950)

This commit is contained in:
Kalvin C
2026-05-01 10:20:25 -07:00
committed by GitHub
parent 501c1edab5
commit 43ad9e6adb
15 changed files with 451 additions and 389 deletions
+14
View File
@@ -4,6 +4,14 @@ import { cn } from "@/shared/lib/cn";
import { Input } from "@/shared/ui/input";
const searchBarSizes = {
compact: {
wrapper:
"rounded-md border border-border-soft px-2 py-1 text-xs text-muted-foreground hover:bg-transparent hover:text-foreground",
icon: "left-2.5 size-3",
input:
"h-auto border-none bg-transparent px-0 pl-5 pr-0 text-[11px] font-normal shadow-none focus-visible:border-transparent focus-visible:ring-0 focus-visible:ring-offset-0",
inputVariant: "ghost" as const,
},
small: {
wrapper:
"rounded-md border border-border-soft px-2.5 py-1.5 text-xs text-muted-foreground hover:bg-transparent hover:text-foreground",
@@ -36,6 +44,8 @@ interface SearchBarProps {
size?: keyof typeof searchBarSizes;
/** Optional ref for the underlying input */
inputRef?: React.Ref<HTMLInputElement>;
/** Accessible label for the search input */
"aria-label"?: string;
}
export function SearchBar({
@@ -46,6 +56,7 @@ export function SearchBar({
className,
size = "default",
inputRef,
"aria-label": ariaLabel,
}: SearchBarProps) {
const styles = searchBarSizes[size];
@@ -62,11 +73,14 @@ export function SearchBar({
variant={styles.inputVariant}
type="search"
autoComplete="off"
autoCorrect="off"
autoCapitalize="none"
spellCheck={false}
value={value}
onChange={(e) => onChange(e.target.value)}
onKeyDown={onKeyDown}
placeholder={placeholder}
aria-label={ariaLabel}
className={cn("w-full placeholder:text-placeholder", styles.input)}
/>
</div>
@@ -0,0 +1,34 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { SettingsPage } from "./SettingsPage";
describe("SettingsPage", () => {
it("renders title-only headers", () => {
render(<SettingsPage title="General" />);
expect(
screen.getByRole("heading", { name: "General" }),
).toBeInTheDocument();
});
it("renders description, actions, controls, and children", () => {
render(
<SettingsPage
title="Extensions"
description="Manage extensions"
actions={<button type="button">Add</button>}
controls={<input aria-label="Search extensions" />}
contentClassName="custom-content"
>
<div>Extension list</div>
</SettingsPage>,
);
expect(screen.getByText("Manage extensions")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Add" })).toBeInTheDocument();
expect(screen.getByLabelText("Search extensions")).toBeInTheDocument();
expect(screen.getByText("Extension list").parentElement).toHaveClass(
"custom-content",
);
});
});
+50
View File
@@ -0,0 +1,50 @@
import type { ReactNode } from "react";
import { cn } from "@/shared/lib/cn";
interface SettingsPageProps {
title: ReactNode;
description?: ReactNode;
actions?: ReactNode;
controls?: ReactNode;
children?: ReactNode;
className?: string;
contentClassName?: string;
}
export function SettingsPage({
title,
description,
actions,
controls,
children,
className,
contentClassName,
}: SettingsPageProps) {
return (
<div className={cn("min-h-full", className)}>
<div className="sticky top-0 z-20 -mx-6 border-b bg-background px-6 py-4">
<div className="flex items-center justify-between gap-3 pr-12">
<div className="min-w-0 flex-1">
<h3 className="max-w-prose truncate font-display text-sm font-semibold leading-5 tracking-tight">
{title}
</h3>
{description ? (
<p className="mt-0.5 max-w-prose text-xs leading-4 text-muted-foreground">
{description}
</p>
) : null}
</div>
{actions ? (
<div className="flex flex-shrink-0 items-center gap-1.5">
{actions}
</div>
) : null}
</div>
{controls ? <div className="mt-3 pr-12">{controls}</div> : null}
</div>
{children ? (
<div className={cn("py-3", contentClassName)}>{children}</div>
) : null}
</div>
);
}
+2
View File
@@ -33,6 +33,7 @@ const buttonVariants = cva(
link: "text-brand underline-offset-4 hover:underline",
},
size: {
xxs: "h-6 gap-1.5 px-2 text-[11px]",
xs: "h-7 px-2.5 text-xs",
default: "h-9 px-4 py-2",
sm: "h-8 px-3 text-xs",
@@ -97,6 +98,7 @@ const buttonVariants = cva(
);
const buttonIconSizeClasses = {
xxs: "size-3",
xs: "size-3",
default: "size-3.5",
sm: "size-3",