Skip to content

Instantly share code, notes, and snippets.

@EvanMarie
Created September 10, 2024 18:09
Show Gist options
  • Select an option

  • Save EvanMarie/648baca566cb16ab28f9989ab08e421b to your computer and use it in GitHub Desktop.

Select an option

Save EvanMarie/648baca566cb16ab28f9989ab08e421b to your computer and use it in GitHub Desktop.
interface TextAreaProps
extends Omit<
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
"value" | "onChange"
> {
className?: string;
value?: string;
onChange?: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
style?: React.CSSProperties;
textAreaHeight?: string;
textAreaWidth?: string;
autoFocus?: boolean;
isEditable?: boolean;
}
export function TextAreaNumberedLines({
className = "",
value: propValue,
onChange,
textAreaHeight = "h-full max-h-[56vh]",
style = {
resize: "none",
fontSize: "15px",
lineHeight: "23px",
minHeight: textAreaHeight.replace("h-", ""),
},
textAreaWidth = "w-full",
autoFocus = false,
...props
}: TextAreaProps) {
const [value, setValue] = useState(propValue || "");
const [lineCount, setLineCount] = useState<number>(1);
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
const lineNumberRef = useRef<HTMLDivElement | null>(null);
// Calculate the number of lines in the textarea, including wrapped lines
const updateLineCount = () => {
if (textAreaRef.current) {
const textareaLineHeight = 23; // Match this with your CSS line height
const textareaScrollHeight = textAreaRef.current.scrollHeight;
// If the textarea is empty, set line count to 1 (empty line)
if (textAreaRef.current.value.trim() === "") {
setLineCount(1);
} else {
const totalLines = Math.ceil(textareaScrollHeight / textareaLineHeight);
setLineCount(totalLines); // Update line count based on content height
}
}
};
const updateHeight = () => {
if (textAreaRef.current) {
textAreaRef.current.style.height = "auto";
textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`; // Dynamically adjust height based on content
}
};
// Recalculate lines and height after the component is mounted
useLayoutEffect(() => {
if (propValue !== undefined) {
setValue(propValue);
// Use setTimeout to delay recalculation to ensure content is fully loaded
setTimeout(() => {
updateLineCount();
updateHeight();
}, 0); // Small delay to allow layout to settle
}
}, [propValue]);
// Initial layout adjustment
useLayoutEffect(() => {
// Delay recalculation after the initial render
setTimeout(() => {
updateLineCount();
updateHeight();
}, 0);
}, []);
// Sync scrolling between textarea and line number
const handleScroll = () => {
if (textAreaRef.current && lineNumberRef.current) {
lineNumberRef.current.scrollTop = textAreaRef.current.scrollTop;
}
};
// Handle text input and trigger updates
const handleInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newValue = e.target.value;
setValue(newValue);
updateLineCount(); // Update line count on input
updateHeight(); // Adjust height on input
if (onChange) {
onChange(e); // Call the external onChange handler if provided
}
};
// Generate all line numbers based on the total line count
const renderedLines = Array.from({ length: lineCount }, (_, i) => (
<FlexFull key={i} className="h-[23px] items-center">
<Text className="text-xs text-col-950 text-stroke-5-950 text-right">
{i + 1}
</Text>
</FlexFull>
));
const scrollRef = useRef<HTMLDivElement>(null);
return (
// Main container with scroll progress and line numbers
<TransitionFull className="relative">
<ScrollProgressBar containerRef={scrollRef} height="h-[0.5vh]" />
<Flex
className={`${textAreaHeight} ${textAreaWidth} overflow-y-auto overflow-x-hidden textareaStyles p-0.5vh focus-border-[0.2vh] outline-col-300 insetGlow6Xl border-500-md`}
ref={scrollRef}
>
<HStack className={`h-fit w-full items-start relative `} tabIndex={1}>
{/* Line Numbers */}
<VStack
ref={lineNumberRef}
className="h-fit"
style={{ width: "3vh" }}
gap="gap-[0px]"
>
{renderedLines}
</VStack>
{/* Text Area */}
<textarea
ref={textAreaRef}
value={value}
onChange={handleInput}
onScroll={handleScroll}
autoFocus={autoFocus}
className="w-full h-fit bg-transparent focus-border-0 outline-none flex flex-1 overflow-hidden pb-[4vh]"
style={style}
{...props}
/>
</HStack>
</Flex>
</TransitionFull>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment