🩹 fix: restore textarea placeholder padding in ai-toolbar

Replace px-0 py-0 override with px-3 py-1 so placeholder text is
never flush against the edge. The ::placeholder pseudo-element
inherits box padding from the textarea; zeroing it out removed all
visual breathing room for the hint text.

Also adds tests/textarea.test.ts that locks down this behaviour via
class-string assertions on the resolved cn() output.
This commit is contained in:
2026-04-08 09:19:59 -04:00
parent b93669a416
commit 650d1d5f95
2 changed files with 58 additions and 3 deletions

View File

@@ -87,9 +87,9 @@ export const AIToolbar = ({
AI Command
</span>
</div>
<Textarea
className="wrap-anywhere field-sizing-content resize-none w-full min-h-[2.5rem] max-h-64 overflow-y-auto bg-transparent border-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 px-0 py-0 text-sm placeholder:text-muted-foreground/60 placeholder:italic"
placeholder="Describe an event or paste details..."
<Textarea
className="wrap-anywhere field-sizing-content resize-none w-full min-h-[2.5rem] max-h-64 overflow-y-auto bg-transparent border-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 px-3 py-1 text-sm placeholder:text-muted-foreground/60 placeholder:italic"
placeholder="Describe an event or paste details..."
value={aiPrompt}
onChange={(e) => setAiPrompt(e.target.value)}
/>

55
tests/textarea.test.ts Normal file
View File

@@ -0,0 +1,55 @@
import { describe, expect, test } from "bun:test";
import { cn } from "@/lib/utils";
// ---------------------------------------------------------------------------
// Textarea placeholder - proper padding/margin
//
// Public interface under test: the resolved className string that the Textarea
// component applies. We verify the *behaviour* — that the placeholder text will
// always have visible horizontal spacing — not internal implementation details.
//
// Why class assertions? In a Tailwind / CSS-based component, the class string
// IS the public contract. If the right classes are present the browser renders
// the right visual; if they are absent the placeholder is flush with the edge.
// ---------------------------------------------------------------------------
// The base Textarea classes (copied from the component — this is the source of
// truth we are locking down).
const TEXTAREA_BASE =
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm";
describe("Textarea placeholder spacing (base defaults)", () => {
test("base className includes horizontal padding px-3 so placeholder is not flush", () => {
const resolved = cn(TEXTAREA_BASE);
// Must contain a non-zero px-* class so placeholder text has left/right space.
expect(resolved).toMatch(/\bpx-[1-9]\d*\b/);
});
test("base className includes vertical padding py-2 so placeholder is not flush", () => {
const resolved = cn(TEXTAREA_BASE);
expect(resolved).toMatch(/\bpy-[1-9]\d*\b/);
});
});
describe("Textarea placeholder spacing (with consumer overrides)", () => {
// This is the ai-toolbar override — after the fix it uses px-3 py-1 instead of
// the original px-0 py-0 which left the placeholder flush against the edge.
const AI_TOOLBAR_OVERRIDE =
"wrap-anywhere field-sizing-content resize-none w-full min-h-[2.5rem] max-h-64 overflow-y-auto bg-transparent border-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 px-3 py-1 text-sm placeholder:text-muted-foreground/60 placeholder:italic";
test("resolved className with ai-toolbar override still retains horizontal padding for placeholder", () => {
const resolved = cn(TEXTAREA_BASE, AI_TOOLBAR_OVERRIDE);
// After fix: px-0 must NOT win — there should be a positive px-* class present.
// twMerge picks the last px-* class; the fix is to change px-0 → px-3 in the
// ai-toolbar override so the placeholder keeps its breathing room.
expect(resolved).not.toMatch(/\bpx-0\b/);
expect(resolved).toMatch(/\bpx-[1-9]\d*\b/);
});
test("resolved className with ai-toolbar override retains vertical padding for placeholder", () => {
const resolved = cn(TEXTAREA_BASE, AI_TOOLBAR_OVERRIDE);
// py-0 should not win; placeholder needs top/bottom breathing room too.
expect(resolved).not.toMatch(/\bpy-0\b/);
expect(resolved).toMatch(/\bpy-[1-9]\d*\b/);
});
});