feat: enhance Messages and ThreadChat components with improved textarea handling and styling

This commit is contained in:
Vishnu 2025-05-23 12:07:58 +05:30
parent 49d6b242d4
commit 3ad1cb3c58
7 changed files with 201 additions and 28 deletions

View File

@ -1,30 +1,71 @@
import React, { useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor"; import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
import { KebabIcon } from "../../icons/ExportCommonIcons"; import { KebabIcon } from "../../icons/ExportCommonIcons";
import { adjustHeight } from "./function/textAreaHeightAdjust";
interface MessageProps { interface MessageProps {
val: Reply; val: Reply | CommentSchema;
i: number; i: number;
} }
const Messages: React.FC<MessageProps> = ({ val, i }) => { const Messages: React.FC<MessageProps> = ({ val, i }) => {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [openOptions, setOpenOptions] = useState(false); const [openOptions, setOpenOptions] = useState(false);
// input
const [value, setValue] = useState<string>(
"reply" in val ? val.reply : val.comment
);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const currentUser = "1"; const currentUser = "1";
const UserName = "username"; const UserName = "username";
useEffect(() => {
if (textareaRef.current) adjustHeight(textareaRef.current);
}, [value]);
function handleCancelAction() {
setIsEditing(false);
}
function handleSaveAction() {
setIsEditing(false);
}
return ( return (
<> <>
{isEditing ? ( {isEditing ? (
<div className="edit-container"> <div className="edit-container">
<div className="input-container"> <div className="input-container">
<textarea /> <textarea
placeholder="type here"
ref={textareaRef}
autoFocus
value={value}
onChange={(e) => setValue(e.target.value)}
style={{ resize: "none" }}
/>
</div> </div>
<div className="actions-container"> <div className="actions-container">
<div className="options"></div>
<div className="actions"> <div className="actions">
<button className="cancel-button">Cancel</button> <button
<button className="save-button">Save</button> className="cancel-button"
onClick={() => {
handleCancelAction();
}}
>
Cancel
</button>
<button
className="save-button"
onClick={() => {
handleSaveAction();
}}
>
Save
</button>
</div> </div>
</div> </div>
</div> </div>
@ -66,7 +107,9 @@ const Messages: React.FC<MessageProps> = ({ val, i }) => {
)} )}
</div> </div>
)} )}
<div className="message">{val.reply}</div> <div className="message">
{"reply" in val ? val.reply : val.comment}
</div>
</div> </div>
</div> </div>
)} )}

View File

@ -1,37 +1,49 @@
import React, { useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { CloseIcon, KebabIcon } from "../../icons/ExportCommonIcons"; import { CloseIcon, KebabIcon } from "../../icons/ExportCommonIcons";
import Messages from "./Messages"; import Messages from "./Messages";
import { ExpandIcon } from "../../icons/SimulationIcons"; import { ExpandIcon } from "../../icons/SimulationIcons";
import { adjustHeight } from "./function/textAreaHeightAdjust";
const ThreadChat: React.FC = () => { const ThreadChat: React.FC = () => {
const [openThreadOptions, setOpenThreadOptions] = useState(false); const [openThreadOptions, setOpenThreadOptions] = useState(false);
const [inputActive, setInputActive] = useState(false);
const [value, setValue] = useState<string>("");
const textareaRef = useRef<HTMLTextAreaElement>(null);
const messages = [ const messages = [
{ {
replyId: "user 1", replyId: "user 1",
creatorId: "1", creatorId: "1",
createdAt: "hello, thread check", createdAt: "hello, thread check",
lastUpdatedAt: "2 hrs ago", lastUpdatedAt: "2 hrs ago",
reply: "true", reply: "reply 1",
}, },
{ {
replyId: "user 2", replyId: "user 2",
creatorId: "2", creatorId: "2",
createdAt: "hello, thread check", createdAt: "hello, thread check",
lastUpdatedAt: "2 hrs ago", lastUpdatedAt: "2 hrs ago",
reply: "true", reply: "reply 2",
}, },
]; ];
useEffect(() => {
if (textareaRef.current) adjustHeight(textareaRef.current);
}, [value]);
return ( return (
<div className="thread-char-wrapper"> <div className="thread-chat-wrapper">
<div className="thread-char-container"> <div className="thread-chat-container">
<div className="header-wrapper"> <div className="header-wrapper">
<div className="header">Comment</div> <div className="header">Comment</div>
<div className="header-options"> <div className="header-options">
<button <button
className="options-button" className="options-button"
onClick={() => { onClick={() => {
setOpenThreadOptions(true); setOpenThreadOptions(!openThreadOptions);
}} }}
> >
<KebabIcon /> <KebabIcon />
@ -40,7 +52,7 @@ const ThreadChat: React.FC = () => {
<div className="options-list"> <div className="options-list">
<div className="options">Mark as Unread</div> <div className="options">Mark as Unread</div>
<div className="options">Mark as Resolved</div> <div className="options">Mark as Resolved</div>
<div className="options">Delete Thread</div> <div className="options delete">Delete Thread</div>
</div> </div>
)} )}
<button className="close-button"> <button className="close-button">
@ -54,9 +66,21 @@ const ThreadChat: React.FC = () => {
))} ))}
</div> </div>
<div className="send-message-wrapper"> <div className="send-message-wrapper">
<div className="input-container"> <div className={`input-container ${inputActive ? "active" : ""}`}>
<input type="text" /> <textarea
<div className="sent-button"> placeholder="type something"
ref={textareaRef}
value={value}
onChange={(e) => setValue(e.target.value)}
onFocus={() => setInputActive(true)}
onBlur={() => setInputActive(false)}
style={{ resize: "none" }}
/>
<div
className={`sent-button ${
value === "" ? "disable-send-btn" : ""
}`}
>
<ExpandIcon /> <ExpandIcon />
</div> </div>
</div> </div>

View File

@ -0,0 +1,17 @@
export const adjustHeight = (textareaRef: HTMLTextAreaElement) => {
const el = textareaRef;
if (el) {
el.style.height = "auto";
el.style.height = "38px";
// Clamp to max height for 6 lines
const lineHeight = 18; // px, adjust if needed
const maxHeight = lineHeight * 6;
if (el.scrollHeight > maxHeight) {
el.style.overflowY = "auto";
el.style.height = `${maxHeight}px`;
} else {
el.style.overflowY = "hidden";
}
}
};

View File

@ -5,7 +5,6 @@ import Controls from '../controls/controls';
import { Environment } from '@react-three/drei' import { Environment } from '@react-three/drei'
import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr"; import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr";
import { MovingClouds } from '../clouds/clouds';
function Setup() { function Setup() {
return ( return (
@ -18,7 +17,7 @@ function Setup() {
<PostProcessing /> <PostProcessing />
<MovingClouds /> {/* <MovingClouds /> */}
<Environment files={background} environmentIntensity={1.5} /> <Environment files={background} environmentIntensity={1.5} />
</> </>

View File

@ -183,7 +183,7 @@ const Project: React.FC = () => {
</> </>
)} )}
<VersionSaved /> <VersionSaved />
{/* <ThreadChat /> */} <ThreadChat />
</div> </div>
); );
}; };

View File

@ -3,7 +3,8 @@
// global input style // global input style
input { input,
textarea {
width: 100%; width: 100%;
padding: 4px 8px; padding: 4px 8px;
border-radius: #{$border-radius-large}; border-radius: #{$border-radius-large};

View File

@ -93,36 +93,86 @@
} }
} }
.thread-char-wrapper { .thread-chat-wrapper {
position: absolute; position: absolute;
// remove later // remove later
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
// ---- // ----
z-index: 10000; z-index: #{$z-index-ui-highest};
.thread-char-container { .thread-chat-container {
background: var(--background-color);
backdrop-filter: blur(14px);
border-radius: #{$border-radius-extra-large};
width: 20rem;
.header-wrapper { .header-wrapper {
.header { padding: 12px;
} @include flex-space-between;
.header-options { .header-options {
.options-button { @include flex-center;
} position: relative;
.options-list { .options-list {
position: absolute;
bottom: 0;
right: 0;
transform: translate(-24px, 100%);
background: var(--background-color);
padding: 8px 4px;
border-radius: #{$border-radius-medium};
backdrop-filter: blur(10px);
.options { .options {
text-wrap: nowrap;
padding: 2px 4px;
border-radius: #{$border-radius-medium};
cursor: pointer;
&:hover {
background: var(--background-color-accent);
}
&.delete {
&:hover {
color: var(--log-error-text-color);
background: var(--log-error-background-color);
}
}
}
}
.options-button,
.close-button {
@include flex-center;
height: 24px;
width: 24px;
border-radius: #{$border-radius-medium};
&:hover {
background: var(--background-color-solid);
} }
} }
.close-button { .close-button {
svg {
scale: 1.4;
}
} }
} }
} }
.messages-wrapper { .messages-wrapper {
padding: 12px;
padding-top: 0;
.edit-container { .edit-container {
.input-container { .input-container {
} }
.actions-container { .actions-container {
@include flex-space-between;
width: 100%;
margin: 8px 0;
.actions { .actions {
.cancel-button { @include flex-center;
gap: 4px;
.cancel-button,
.save-button {
padding: 4px 6px;
border-radius: #{$border-radius-large};
background: var(--background-color-solid);
outline: 1px solid var(--border-color);
} }
.save-button { .save-button {
} }
@ -151,8 +201,47 @@
} }
} }
.send-message-wrapper { .send-message-wrapper {
padding: 12px;
padding-top: 8px;
.input-container { .input-container {
position: relative;
@include flex-space-between;
background: var(--background-color);
border-radius: #{$border-radius-extra-large};
outline: 1px solid var(--border-color);
textarea {
background: transparent;
outline: none;
width: calc(100% - 36px);
overflow: hidden;
line-height: 28px;
max-height: 108px;
}
.sent-button { .sent-button {
position: absolute;
right: 2px;
bottom: 2px;
@include flex-center;
padding: 2px;
svg {
rotate: 45deg;
}
}
.disable-send-btn {
filter: saturate(0);
}
&.active {
background: var(--background-color-solid);
padding-top: 4px;
flex-direction: column;
align-items: end;
textarea {
width: 100%;
line-height: 18px;
}
.sent-button {
position: relative;
}
} }
} }
} }