feat: implement AutoTextarea component for dynamic height adjustment in text areas

This commit is contained in:
Christopher
2026-01-22 10:47:44 -08:00
parent 64fbca7fbb
commit f589635d03
5 changed files with 66 additions and 5 deletions
+2 -1
View File
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
import { ArrowLeftIcon, CalendarIcon, HeartIcon, RepeatIcon, MessageIcon, FlagIcon } from '@/components/Icons'; import { ArrowLeftIcon, CalendarIcon, HeartIcon, RepeatIcon, MessageIcon, FlagIcon } from '@/components/Icons';
import AutoTextarea from '@/components/AutoTextarea';
interface User { interface User {
id: string; id: string;
@@ -481,7 +482,7 @@ export default function ProfilePage() {
</div> </div>
<div> <div>
<label style={{ fontSize: '12px', color: 'var(--foreground-tertiary)' }}>Bio</label> <label style={{ fontSize: '12px', color: 'var(--foreground-tertiary)' }}>Bio</label>
<textarea <AutoTextarea
className="input" className="input"
value={profileForm.bio} value={profileForm.bio}
onChange={(e) => setProfileForm({ ...profileForm, bio: e.target.value })} onChange={(e) => setProfileForm({ ...profileForm, bio: e.target.value })}
+4 -3
View File
@@ -2,6 +2,7 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import AutoTextarea from '@/components/AutoTextarea';
type AdminUser = { type AdminUser = {
id: string; id: string;
@@ -494,7 +495,7 @@ export default function AdminPage() {
<div> <div>
<label style={{ fontSize: '13px', fontWeight: 500, marginBottom: '4px', display: 'block' }}>Short Description</label> <label style={{ fontSize: '13px', fontWeight: 500, marginBottom: '4px', display: 'block' }}>Short Description</label>
<textarea <AutoTextarea
className="input" className="input"
value={nodeSettings.description} value={nodeSettings.description}
onChange={e => setNodeSettings({ ...nodeSettings, description: e.target.value })} onChange={e => setNodeSettings({ ...nodeSettings, description: e.target.value })}
@@ -554,7 +555,7 @@ export default function AdminPage() {
<div> <div>
<label style={{ fontSize: '13px', fontWeight: 500, marginBottom: '4px', display: 'block' }}>Long Description (About)</label> <label style={{ fontSize: '13px', fontWeight: 500, marginBottom: '4px', display: 'block' }}>Long Description (About)</label>
<textarea <AutoTextarea
className="input" className="input"
value={nodeSettings.longDescription} value={nodeSettings.longDescription}
onChange={e => setNodeSettings({ ...nodeSettings, longDescription: e.target.value })} onChange={e => setNodeSettings({ ...nodeSettings, longDescription: e.target.value })}
@@ -565,7 +566,7 @@ export default function AdminPage() {
<div> <div>
<label style={{ fontSize: '13px', fontWeight: 500, marginBottom: '4px', display: 'block' }}>Rules</label> <label style={{ fontSize: '13px', fontWeight: 500, marginBottom: '4px', display: 'block' }}>Rules</label>
<textarea <AutoTextarea
className="input" className="input"
value={nodeSettings.rules} value={nodeSettings.rules}
onChange={e => setNodeSettings({ ...nodeSettings, rules: e.target.value })} onChange={e => setNodeSettings({ ...nodeSettings, rules: e.target.value })}
+20
View File
@@ -1311,4 +1311,24 @@ a.btn-primary:visited {
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: var(--background);
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: var(--radius-full);
border: 2px solid var(--background); /* Creates a padding effect */
}
::-webkit-scrollbar-thumb:hover {
background: var(--border-hover);
} }
+2 -1
View File
@@ -3,6 +3,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { useAuth } from '@/lib/contexts/AuthContext'; import { useAuth } from '@/lib/contexts/AuthContext';
import AutoTextarea from '@/components/AutoTextarea';
interface User { interface User {
id: string; id: string;
@@ -260,7 +261,7 @@ function Compose({ onPost }: { onPost: (content: string, mediaIds: string[]) =>
return ( return (
<div className="compose"> <div className="compose">
<textarea <AutoTextarea
className="compose-input" className="compose-input"
placeholder="What's happening?" placeholder="What's happening?"
value={content} value={content}
+38
View File
@@ -0,0 +1,38 @@
'use client';
import { useEffect, useRef, TextareaHTMLAttributes } from 'react';
export default function AutoTextarea(props: TextareaHTMLAttributes<HTMLTextAreaElement>) {
const textareaRef = useRef<HTMLTextAreaElement>(null);
const adjustHeight = () => {
const textarea = textareaRef.current;
if (textarea) {
textarea.style.height = 'auto';
textarea.style.height = `${textarea.scrollHeight}px`;
}
};
useEffect(() => {
adjustHeight();
}, [props.value]);
return (
<textarea
{...props}
ref={textareaRef}
// Ensure 1 row minimum and hide scrollbars to prevent jitter
rows={props.rows || 1}
onInput={(e) => {
adjustHeight();
props.onInput?.(e);
}}
style={{
...props.style,
resize: 'none',
overflow: 'hidden',
minHeight: 'auto', // Override specific min-heights if they conflict
}}
/>
);
}