authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-22 12:22:17-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-22 12:22:17-07:00
logda89e102f80b7ef6c8166d41cc202035f6ad75a4
treeb9d07d6197a07fd71fa61d153ca00c342e0f531c
parentd7736e06b6a4f8211f452908963fd23c4a04b2c4
signature Signed by PGP key B5690EEEBB952194

refactor(web): every textarea caps its growth; the diff comment box is a Textarea (#13036)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

6 files changed, 5 insertions(+), 10 deletions(-)

apps/web/src/components/diffs/DiffCommentAnnotation.test.tsx+2-4
...@@ -25,10 +25,8 @@ describe("DiffCommentAnnotation", () => {...@@ -25,10 +25,8 @@ describe("DiffCommentAnnotation", () => {
25 expect(markup).toContain("Add a comment…");25 expect(markup).toContain("Add a comment…");
26 expect(markup).toContain(">Comment</button>");26 expect(markup).toContain(">Comment</button>");
27 expect(markup).toContain("autofocus");27 expect(markup).toContain("autofocus");
28 const textareaControl = markup.match(/<span[^>]*data-slot="textarea-control"[^>]*>/)?.[0];28 // The comment box is the standard small Textarea, not a bespoke surface.
29 expect(textareaControl).toBeDefined();29 expect(markup).toMatch(/<span[^>]*data-size="sm"[^>]*data-slot="textarea-control"/);
30 expect(textareaControl).not.toContain("ring-ring");
31 expect(markup).toContain("cursor-text");
32 });30 });
3331
34 it("lets a pull-request diff configure actions without replacing the composer", () => {32 it("lets a pull-request diff configure actions without replacing the composer", () => {
apps/web/src/components/diffs/DiffCommentAnnotation.tsx-2
...@@ -91,8 +91,6 @@ export function DiffCommentAnnotation({...@@ -91,8 +91,6 @@ export function DiffCommentAnnotation({
91 <Textarea91 <Textarea
92 ref={textareaRef}92 ref={textareaRef}
93 autoFocus={focusOnMount}93 autoFocus={focusOnMount}
94 unstyled
95 className="relative inline-flex w-full rounded-md border border-border/50 bg-background/20 font-sans text-foreground transition-colors focus-within:border-border/70 [&_[data-slot=textarea]]:min-h-12 [&_[data-slot=textarea]]:cursor-text [&_[data-slot=textarea]]:caret-foreground [&_[data-slot=textarea]]:px-2.5 [&_[data-slot=textarea]]:py-1.5 [&_[data-slot=textarea]]:font-sans [&_[data-slot=textarea]]:text-xs [&_[data-slot=textarea]]:leading-5 max-sm:[&_[data-slot=textarea]]:min-h-12"
96 size="sm"94 size="sm"
97 value={displayedText}95 value={displayedText}
98 placeholder={placeholder}96 placeholder={placeholder}
apps/web/src/components/pullRequest/PullRequestCommentForm.tsx-1
...@@ -85,7 +85,6 @@ export function PullRequestCommentForm({...@@ -85,7 +85,6 @@ export function PullRequestCommentForm({
85 <div className="space-y-2">85 <div className="space-y-2">
86 <Textarea86 <Textarea
87 ref={textareaRef}87 ref={textareaRef}
88 className="[&_textarea]:max-h-64"
89 // Locked while posting: the body is cleared on success, which would otherwise throw88 // Locked while posting: the body is cleared on success, which would otherwise throw
90 // away a new draft typed while the request was still in flight.89 // away a new draft typed while the request was still in flight.
91 disabled={submitting !== null || actionPending}90 disabled={submitting !== null || actionPending}
apps/web/src/components/pullRequest/PullRequestReviewForm.tsx-1
...@@ -126,7 +126,6 @@ export function PullRequestReviewForm({...@@ -126,7 +126,6 @@ export function PullRequestReviewForm({
126 <Textarea126 <Textarea
127 ref={textareaRef}127 ref={textareaRef}
128 rows={3}128 rows={3}
129 className="[&_textarea]:max-h-64"
130 value={body}129 value={body}
131 placeholder={130 placeholder={
132 requestChangesSummaryRequired && verdicts.includes("request-changes")131 requestChangesSummaryRequired && verdicts.includes("request-changes")
apps/web/src/components/ui/textarea.tsx+2-1
...@@ -28,7 +28,8 @@ function Textarea({ className, size = "default", unstyled = false, ...props }: T...@@ -28,7 +28,8 @@ function Textarea({ className, size = "default", unstyled = false, ...props }: T
28 render={(defaultProps) => (28 render={(defaultProps) => (
29 <textarea29 <textarea
30 className={cn(30 className={cn(
31 "field-sizing-content min-h-17.5 w-full rounded-[inherit] px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)] outline-none max-sm:min-h-20.5",31 // Auto-grows with content up to one cap, then scrolls.
32 "field-sizing-content min-h-17.5 max-h-64 w-full rounded-[inherit] px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)] outline-none max-sm:min-h-20.5",
32 size === "sm" &&33 size === "sm" &&
33 "min-h-16.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)] max-sm:min-h-19.5",34 "min-h-16.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)] max-sm:min-h-19.5",
34 size === "lg" && "min-h-18.5 py-[calc(--spacing(2)-1px)] max-sm:min-h-21.5",35 size === "lg" && "min-h-18.5 py-[calc(--spacing(2)-1px)] max-sm:min-h-21.5",
scripts/lint-restyle-ceiling.ts+1-1
...@@ -8,7 +8,7 @@ import * as NodeURL from "node:url";...@@ -8,7 +8,7 @@ import * as NodeURL from "node:url";
8// components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the8// components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the
9// count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you9// count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you
10// migrate a file, and delete this script when the rule becomes an error.10// migrate a file, and delete this script when the rule becomes an error.
11export const RESTYLE_CEILING = 826;11export const RESTYLE_CEILING = 812;
1212
13const RULE = "shadcn(no-restyle)";13const RULE = "shadcn(no-restyle)";
1414