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 { KebabIcon } from "../../icons/ExportCommonIcons";
import { adjustHeight } from "./function/textAreaHeightAdjust";
interface MessageProps {
val: Reply;
val: Reply | CommentSchema;
i: number;
}
const Messages: React.FC<MessageProps> = ({ val, i }) => {
const [isEditing, setIsEditing] = 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 UserName = "username";
useEffect(() => {
if (textareaRef.current) adjustHeight(textareaRef.current);
}, [value]);
function handleCancelAction() {
setIsEditing(false);
}
function handleSaveAction() {
setIsEditing(false);
}
return (
<>
{isEditing ? (
<div className="edit-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 className="actions-container">
<div className="options"></div>
<div className="actions">
<button className="cancel-button">Cancel</button>
<button className="save-button">Save</button>
<button
className="cancel-button"
onClick={() => {
handleCancelAction();
}}
>
Cancel
</button>
<button
className="save-button"
onClick={() => {
handleSaveAction();
}}
>
Save
</button>
</div>
</div>
</div>
@ -66,7 +107,9 @@ const Messages: React.FC<MessageProps> = ({ val, i }) => {
)}
</div>
)}
<div className="message">{val.reply}</div>
<div className="message">
{"reply" in val ? val.reply : val.comment}
</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 Messages from "./Messages";
import { ExpandIcon } from "../../icons/SimulationIcons";
import { adjustHeight } from "./function/textAreaHeightAdjust";
const ThreadChat: React.FC = () => {
const [openThreadOptions, setOpenThreadOptions] = useState(false);
const [inputActive, setInputActive] = useState(false);
const [value, setValue] = useState<string>("");
const textareaRef = useRef<HTMLTextAreaElement>(null);
const messages = [
{
replyId: "user 1",
creatorId: "1",
createdAt: "hello, thread check",
lastUpdatedAt: "2 hrs ago",
reply: "true",
reply: "reply 1",
},
{
replyId: "user 2",
creatorId: "2",
createdAt: "hello, thread check",
lastUpdatedAt: "2 hrs ago",
reply: "true",
reply: "reply 2",
},
];
useEffect(() => {
if (textareaRef.current) adjustHeight(textareaRef.current);
}, [value]);
return (
<div className="thread-char-wrapper">
<div className="thread-char-container">
<div className="thread-chat-wrapper">
<div className="thread-chat-container">
<div className="header-wrapper">
<div className="header">Comment</div>
<div className="header-options">
<button
className="options-button"
onClick={() => {
setOpenThreadOptions(true);
setOpenThreadOptions(!openThreadOptions);
}}
>
<KebabIcon />
@ -40,7 +52,7 @@ const ThreadChat: React.FC = () => {
<div className="options-list">
<div className="options">Mark as Unread</div>
<div className="options">Mark as Resolved</div>
<div className="options">Delete Thread</div>
<div className="options delete">Delete Thread</div>
</div>
)}
<button className="close-button">
@ -54,9 +66,21 @@ const ThreadChat: React.FC = () => {
))}
</div>
<div className="send-message-wrapper">
<div className="input-container">
<input type="text" />
<div className="sent-button">
<div className={`input-container ${inputActive ? "active" : ""}`}>
<textarea
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 />
</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 background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr";
import { MovingClouds } from '../clouds/clouds';
function Setup() {
return (
@ -18,7 +17,7 @@ function Setup() {
<PostProcessing />
<MovingClouds />
{/* <MovingClouds /> */}
<Environment files={background} environmentIntensity={1.5} />
</>

View File

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

View File

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

View File

@ -93,36 +93,86 @@
}
}
.thread-char-wrapper {
.thread-chat-wrapper {
position: absolute;
// remove later
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
// ----
z-index: 10000;
.thread-char-container {
z-index: #{$z-index-ui-highest};
.thread-chat-container {
background: var(--background-color);
backdrop-filter: blur(14px);
border-radius: #{$border-radius-extra-large};
width: 20rem;
.header-wrapper {
.header {
}
padding: 12px;
@include flex-space-between;
.header-options {
.options-button {
}
@include flex-center;
position: relative;
.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 {
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 {
svg {
scale: 1.4;
}
}
}
}
.messages-wrapper {
padding: 12px;
padding-top: 0;
.edit-container {
.input-container {
}
.actions-container {
@include flex-space-between;
width: 100%;
margin: 8px 0;
.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 {
}
@ -151,8 +201,47 @@
}
}
.send-message-wrapper {
padding: 12px;
padding-top: 8px;
.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 {
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;
}
}
}
}