refactor: finished tailwind
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, FC } from "react";
|
||||
import { Ping } from '@uiball/loaders';
|
||||
import { getDefaultSize } from "../../utils";
|
||||
// import { getDefaultSize } from "../../utils";
|
||||
|
||||
type Props = {
|
||||
uploading: boolean;
|
||||
@@ -20,7 +20,7 @@ const ImageMessage: FC<Props> = ({
|
||||
properties
|
||||
}) => {
|
||||
const [url, setUrl] = useState(thumbnail);
|
||||
const { width = 0, height = 0 } = getDefaultSize(properties);
|
||||
// const { width = 0, height = 0 } = getDefaultSize(properties);
|
||||
useEffect(() => {
|
||||
const newUrl = thumbnail;
|
||||
const img = new Image();
|
||||
@@ -36,21 +36,21 @@ const ImageMessage: FC<Props> = ({
|
||||
return (
|
||||
<div className="relative w-fit h-fit">
|
||||
{uploading && (
|
||||
<div className="absolute left-0 top-0 w-full h-full bg-white/40 flex flex-col justify-center items-center gap-1">
|
||||
<div className="absolute left-0 top-0 w-full h-full bg-white/50 flex flex-col justify-center items-center gap-1">
|
||||
<Ping
|
||||
size={45}
|
||||
speed={2}
|
||||
color="#666"
|
||||
color="#555"
|
||||
/>
|
||||
<span className="text-xs text-gray-600">{progress}%</span>
|
||||
<span className="text-xs text-gray-500">{progress}%</span>
|
||||
</div>
|
||||
)}
|
||||
<img
|
||||
className="max-w-[480px] cursor-zoom-in object-cover preview"
|
||||
style={{
|
||||
width: width ? `${width}px` : "",
|
||||
height: height ? `${height}px` : ""
|
||||
}}
|
||||
className="w-full h-auto max-w-[480px] cursor-zoom-in object-cover preview"
|
||||
// style={{
|
||||
// width: width ? `${width}px` : "",
|
||||
// height: height ? `${height}px` : ""
|
||||
// }}
|
||||
data-meta={JSON.stringify(properties)}
|
||||
data-origin={content}
|
||||
data-download={download}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
import { createGlobalStyle } from "styled-components";
|
||||
|
||||
const MarkdownOverrides = createGlobalStyle`
|
||||
[class^='toastui-editor-'] {
|
||||
.toastui-editor-md-container {
|
||||
border-bottom: none;
|
||||
.toastui-editor-md-preview {
|
||||
padding-right: 0;
|
||||
padding-left: 8px;
|
||||
}
|
||||
.toastui-editor-md-splitter {
|
||||
background-color: #D0D5DD;
|
||||
}
|
||||
.ProseMirror {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.ProseMirror, p, .toastui-editor.md-mode {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #475467;
|
||||
margin: 0 0 16px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
pre {
|
||||
width: 100%;
|
||||
width: -webkit-fill-available;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: none;
|
||||
display: flex;
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
padding: 16px;
|
||||
color: #98a2b3;
|
||||
opacity: 0.8;
|
||||
box-shadow: inset 2px 0px 0px #a8b0bd;
|
||||
align-items: center;
|
||||
|
||||
> p {
|
||||
padding-right: 10px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
> blockquote {
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
cursor: pointer;
|
||||
max-width: 300px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
ul{
|
||||
white-space: nowrap;
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
> li:before {
|
||||
margin-top: 8px;
|
||||
margin-left: -14px;
|
||||
background-color: #475467;
|
||||
}
|
||||
}
|
||||
ol li{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
li ol{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
ul,
|
||||
ol {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #475467;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3, [class*='heading'] {
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-bottom: none;
|
||||
font-weight: 700;
|
||||
color: #475467;
|
||||
}
|
||||
|
||||
h1, [class*='heading1'] {
|
||||
margin-top: 0 !important;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
h2, [class*='heading2'] {
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
h3, [class*='heading3'] {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default MarkdownOverrides;
|
||||
@@ -96,7 +96,7 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
|
||||
<>
|
||||
<ul
|
||||
ref={cmdsRef}
|
||||
className={clsx(`cmds bg-white dark:bg-gray-900 rounded-md z-[999] absolute right-2.5 top-0 -translate-y-1/2 flex items-center border border-solid border-black/10 invisible`, tippyVisible && '!visible')}>
|
||||
className={clsx(`bg-white dark:bg-gray-900 rounded-md z-[999] absolute right-2.5 top-0 -translate-y-1/2 flex items-center border border-solid border-black/10 invisible group-hover:visible`, tippyVisible && '!visible')}>
|
||||
<Tippy
|
||||
onShow={handleTippyVisible.bind(null, true)}
|
||||
onHide={handleTippyVisible.bind(null, false)}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { FC, ReactElement, useEffect, useState } from "react";
|
||||
import StyledMsg from "./styled";
|
||||
import renderContent from "./renderContent";
|
||||
import Avatar from "../Avatar";
|
||||
import useFavMessage from "../../hook/useFavMessage";
|
||||
@@ -18,22 +17,22 @@ const FavoredMessage: FC<Props> = ({ id = "" }) => {
|
||||
const favorite_mids = messages.map(({ from_mid }) => +from_mid) || [];
|
||||
|
||||
setMsgs(
|
||||
<div data-favorite-mids={favorite_mids.join(",")} className="favorite flex flex-col rounded-md bg-slate-200">
|
||||
<div data-favorite-mids={favorite_mids.join(",")} className="favorite flex flex-col rounded-md bg-slate-50 dark:bg-slate-800">
|
||||
<div className="list">
|
||||
{messages.map((msg, idx) => {
|
||||
const { user = {}, download, content, content_type, properties, thumbnail } = msg;
|
||||
return (
|
||||
<StyledMsg className="archive" key={idx}>
|
||||
<div className="w-full relative flex items-start gap-3 px-2 py-1 my-2 rounded-lg dark:hover:bg-gray-800" key={idx}>
|
||||
{user && (
|
||||
<div className="avatar">
|
||||
<div className="">
|
||||
<Avatar width={40} height={40} src={user.avatar} name={user.name} />
|
||||
</div>
|
||||
)}
|
||||
<div className="details">
|
||||
<div className="up">
|
||||
<span className="name">{user?.name || "Deleted User"}</span>
|
||||
<div className="w-full flex flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-semibold">
|
||||
<span className="text-gray-600 dark:text-gray-400">{user?.name || "Deleted User"}</span>
|
||||
</div>
|
||||
<div className="down">
|
||||
<div className="select-text text-gray-800 break-all whitespace-pre-wrap dark:text-white">
|
||||
{renderContent({
|
||||
download,
|
||||
content,
|
||||
@@ -43,7 +42,7 @@ const FavoredMessage: FC<Props> = ({ id = "" }) => {
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</StyledMsg>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@ const ForwardedMessage: FC<Props> = ({ context, to, from_uid, id }) => {
|
||||
const forward_mids = messages.map(({ from_mid }) => from_mid) || [];
|
||||
// console.log("fff", messages);
|
||||
setForwards(
|
||||
<div data-forwarded-mids={forward_mids.join(",")} className="flex flex-col rounded-lg bg-gray-100">
|
||||
<div data-forwarded-mids={forward_mids.join(",")} className="flex flex-col rounded-lg bg-gray-100 dark:bg-gray-900">
|
||||
<h4 className="p-2 pb-0 flex items-center gap-1 text-gray-500 text-xs">
|
||||
<IconForward className="w-4 h-4 fill-gray-500" />
|
||||
{t("action.forward")}
|
||||
@@ -35,17 +35,17 @@ const ForwardedMessage: FC<Props> = ({ context, to, from_uid, id }) => {
|
||||
{messages.map((msg, idx) => {
|
||||
const { user = {}, download, content, content_type, properties, thumbnail } = msg;
|
||||
return (
|
||||
<StyledMsg className="archive" key={idx}>
|
||||
<div className="w-full relative flex items-start gap-4 px-2 py-1 my-2 rounded-lg" key={idx}>
|
||||
{user && (
|
||||
<div className="avatar">
|
||||
<div >
|
||||
<Avatar width={24} height={24} src={user.avatar} name={user.name} />
|
||||
</div>
|
||||
)}
|
||||
<div className="details">
|
||||
<div className="up">
|
||||
<span className="name">{user?.name || "Deleted User"}</span>
|
||||
<div className="w-full flex flex-col">
|
||||
<div className="flex items-center gap-2 font-semibold">
|
||||
<span className="text-gray-500 text-sm">{user?.name || "Deleted User"}</span>
|
||||
</div>
|
||||
<div className="down">
|
||||
<div className="select-text text-gray-500 text-sm break-all whitespace-pre-wrap dark:text-white">
|
||||
{renderContent({
|
||||
download,
|
||||
context,
|
||||
@@ -58,7 +58,7 @@ const ForwardedMessage: FC<Props> = ({ context, to, from_uid, id }) => {
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</StyledMsg>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import clsx from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import renderContent from "./renderContent";
|
||||
import Avatar from "../Avatar";
|
||||
import StyledWrapper from "./styled";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
|
||||
interface Props {
|
||||
@@ -16,20 +14,19 @@ const PreviewMessage: FC<Props> = ({ mid = 0, context }) => {
|
||||
return { msg: store.message[mid], usersData: store.users.byId };
|
||||
});
|
||||
if (!msg) return null;
|
||||
const { from_uid = 0, created_at, content_type, content, thumbnail = "", properties } = msg;
|
||||
const { from_uid = 0, content_type, content, thumbnail = "", properties } = msg;
|
||||
const { name, avatar } = usersData[from_uid] ?? {};
|
||||
const pinMsg = context == "pin";
|
||||
return (
|
||||
<StyledWrapper className={clsx(`preview`, pinMsg && "max-h-64 !bg-transparent overflow-auto overflow-x-hidden border border-solid border-gray-200 dark:border-gray-400")}>
|
||||
<div className="avatar">
|
||||
<div className={clsx(`w-full relative flex items-start gap-3 p-2 my-2 rounded-lg`, pinMsg && "max-h-64 overflow-auto overflow-x-hidden border border-solid border-gray-200 dark:border-gray-400")}>
|
||||
<div>
|
||||
<Avatar width={40} height={40} src={avatar} name={name} />
|
||||
</div>
|
||||
<div className="details">
|
||||
<div className="up">
|
||||
<span className="name">{name}</span>
|
||||
<i className="time">{dayjs(created_at).format("YYYY-MM-DD h:mm:ss A")}</i>
|
||||
<div className="w-full flex flex-col items-start">
|
||||
<div className="flex items-center gap-2 font-semibold">
|
||||
<span className="text-gray-500 text-sm">{name}</span>
|
||||
</div>
|
||||
<div className={`down`}>
|
||||
<div className={`select-text text-gray-600 text-sm break-all whitespace-pre-wrap dark:text-white`}>
|
||||
{renderContent({
|
||||
content_type,
|
||||
content,
|
||||
@@ -39,7 +36,7 @@ const PreviewMessage: FC<Props> = ({ mid = 0, context }) => {
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,14 +14,14 @@ const renderContent = (data: MessagePayload) => {
|
||||
switch (content_type) {
|
||||
case ContentTypes.text:
|
||||
res = (
|
||||
<span className="text-ellipsis overflow-hidden break-words break-all text-gray-800">
|
||||
<span className="text-ellipsis overflow-hidden break-words break-all text-gray-800 dark:text-gray-100">
|
||||
<LinkifyText text={content} url={false} mentionTextOnly={true} mentionPopOver={false} />
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case ContentTypes.markdown:
|
||||
res = (
|
||||
<div className="max-h-[152px] overflow-hidden">
|
||||
<div className="max-h-[152px] overflow-hidden dark:text-gray-100">
|
||||
<MarkdownRender content={content} />
|
||||
</div>
|
||||
);
|
||||
@@ -36,7 +36,7 @@ const renderContent = (data: MessagePayload) => {
|
||||
res = (
|
||||
<>
|
||||
{icon}
|
||||
<span className="ml-1 text-[10px] text-gray-500">{name}</span>
|
||||
<span className="ml-1 text-[10px] text-gray-500 dark:text-gray-100">{name}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ const Reply: FC<ReplyProps> = ({ mid, interactive = true }) => {
|
||||
<div
|
||||
key={mid}
|
||||
data-mid={mid}
|
||||
className={`flex items-start p-2 bg-gray-100 rounded-lg gap-2 mb-1 ${interactive ? "cursor-pointer" : "!bg-transparent"}`}
|
||||
className={`flex items-start p-2 bg-gray-100 dark:bg-gray-900 rounded-lg gap-2 mb-1 ${interactive ? "cursor-pointer" : "!bg-transparent"}`}
|
||||
onClick={interactive ? handleClick : undefined}
|
||||
>
|
||||
<div className="flex items-center gap-1 whitespace-nowrap">
|
||||
@@ -90,7 +90,7 @@ const Reply: FC<ReplyProps> = ({ mid, interactive = true }) => {
|
||||
/>
|
||||
<span className="text-sm text-primary-500">{currUser.name}</span>
|
||||
</div>
|
||||
<div className="text-sm flex items-center text-gray-400 overflow-hidden">{renderContent(data)}</div>
|
||||
<div className="text-sm flex items-center overflow-hidden">{renderContent(data)}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useRef, useState, useEffect, FC } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import useInView from "./useInView";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import Reaction from "./Reaction";
|
||||
import Reply from "./Reply";
|
||||
import Profile from "../Profile";
|
||||
import Avatar from "../Avatar";
|
||||
import StyledWrapper from "./styled";
|
||||
import Commands from "./Commands";
|
||||
import EditMessage from "./EditMessage";
|
||||
import renderContent from "./renderContent";
|
||||
@@ -91,13 +91,16 @@ const Message: FC<IProps> = ({
|
||||
const _key = properties?.local_id || mid;
|
||||
const showExpire = (expires_in ?? 0) > 0;
|
||||
return (
|
||||
<StyledWrapper
|
||||
<div
|
||||
key={_key}
|
||||
onContextMenu={readOnly ? undefined : handleContextMenuEvent}
|
||||
data-msg-mid={mid}
|
||||
ref={inviewRef}
|
||||
className={`message dark:hover:!bg-gray-800 ${readOnly ? "readonly" : ""} ${showExpire ? "auto_delete" : ""} ${pinInfo ? "pinned" : ""} ${contextMenuVisible ? "contextVisible" : ""
|
||||
} `}
|
||||
className={clsx(`group w-full relative flex items-start gap-4 px-2 py-1 my-2 rounded-lg dark:hover:bg-gray-800 hover:bg-gray-50`,
|
||||
readOnly && "hover:bg-transparent",
|
||||
showExpire && "bg-red-200",
|
||||
pinInfo && "bg-cyan-50 dark:bg-cyan-800 pt-7"
|
||||
)}
|
||||
>
|
||||
<Tippy
|
||||
key={_key}
|
||||
@@ -108,7 +111,7 @@ const Message: FC<IProps> = ({
|
||||
trigger="click"
|
||||
content={<Profile uid={fromUid || 0} type="card" cid={context == "user" ? 0 : contextId} />}
|
||||
>
|
||||
<div className="avatar" data-uid={fromUid} ref={avatarRef}>
|
||||
<div className="cursor-pointer" data-uid={fromUid} ref={avatarRef}>
|
||||
<Avatar width={40} height={40} src={currUser?.avatar} name={currUser?.name} />
|
||||
</div>
|
||||
</Tippy>
|
||||
@@ -121,26 +124,29 @@ const Message: FC<IProps> = ({
|
||||
hide={hideContextMenu}
|
||||
>
|
||||
<div
|
||||
className="details"
|
||||
className={clsx("w-full flex flex-col gap-2", pinInfo && "relative")}
|
||||
data-pin-tip={`pinned by ${pinInfo?.created_by ? usersData[pinInfo.created_by]?.name : ""
|
||||
}`}
|
||||
>
|
||||
<div className="up">
|
||||
<span className="name">{currUser?.name || "Deleted User"}</span>
|
||||
{pinInfo && <span className="absolute left-0 -top-1 -translate-y-full text-xs text-gray-400">
|
||||
{`pinned by ${pinInfo.created_by ? usersData[pinInfo.created_by]?.name : ""}`}
|
||||
</span>}
|
||||
<div className="flex items-center gap-2 font-semibold">
|
||||
<span className="text-primary-500 text-sm">{currUser?.name || "Deleted User"}</span>
|
||||
<Tooltip
|
||||
delay={200}
|
||||
disabled={!timePrefix || readOnly}
|
||||
placement="top"
|
||||
tip={dayjsTime.format("YYYY-MM-DD h:mm:ss A")}
|
||||
>
|
||||
<time className="time">
|
||||
<time className="text-gray-400 text-xs">
|
||||
{timePrefix
|
||||
? `${timePrefix} ${dayjsTime.format("h:mm A")}`
|
||||
: dayjsTime.format("YYYY-MM-DD h:mm:ss A")}
|
||||
</time>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={`down dark:!text-white ${sending ? "sending" : ""}`}>
|
||||
<div className={`select-text text-gray-800 text-sm break-all whitespace-pre-wrap dark:!text-white ${sending ? "opacity-90" : ""}`}>
|
||||
{reply_mid && <Reply key={reply_mid} mid={reply_mid} />}
|
||||
{edit ? (
|
||||
<EditMessage mid={mid} cancelEdit={toggleEditMessage} />
|
||||
@@ -180,7 +186,7 @@ const Message: FC<IProps> = ({
|
||||
toggleEditMessage={toggleEditMessage}
|
||||
/>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default React.memo(Message, (prevs, nexts) => {
|
||||
|
||||
@@ -39,7 +39,7 @@ const renderContent = ({
|
||||
<>
|
||||
<LinkifyText text={content} cid={to} />
|
||||
{edited && (
|
||||
<span className="edited" title={dayjs(+edited).format("YYYY-MM-DD h:mm:ss A")}>
|
||||
<span className="ml-1 text-gray-500 text-[10px]" title={dayjs(+edited).format("YYYY-MM-DD h:mm:ss A")}>
|
||||
({i18n.t("edited", { ns: "chat" })})
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledMsg = styled.div`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 4px 8px;
|
||||
margin: 8px 0;
|
||||
border-radius: 8px;
|
||||
&[data-highlight="true"] {
|
||||
background: #f5f6f7;
|
||||
}
|
||||
&.auto_delete {
|
||||
background: #f1d1ca50;
|
||||
}
|
||||
&.pinned {
|
||||
background: #ecfdff;
|
||||
}
|
||||
&:hover,
|
||||
&.contextVisible,
|
||||
&.preview {
|
||||
background: #f5f6f7;
|
||||
.cmds {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
&.readonly:hover {
|
||||
background: none;
|
||||
}
|
||||
> .avatar {
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
img {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
> .details {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
.up {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
.name {
|
||||
color: #06b6d4;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.time {
|
||||
color: #bfbfbf;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
.down {
|
||||
user-select: text;
|
||||
color: #374151;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
.edited {
|
||||
margin-left: 5px;
|
||||
color: #999;
|
||||
font-size: 10px;
|
||||
}
|
||||
&.sending {
|
||||
opacity: 0.9;
|
||||
}
|
||||
> .link {
|
||||
text-decoration: none;
|
||||
border-radius: 2px;
|
||||
/* background-color: #f5feff; */
|
||||
padding: 2px;
|
||||
color: #1fe1f9;
|
||||
}
|
||||
/* 下载图标颜色 */
|
||||
.download_icon.gray{
|
||||
path{
|
||||
fill: #616161;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.archive {
|
||||
gap: 8px;
|
||||
> .details {
|
||||
gap: 0;
|
||||
.up .name {
|
||||
font-weight: 600;
|
||||
color: #475467;
|
||||
}
|
||||
.down {
|
||||
color: #475467;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.pinned {
|
||||
padding-top: 26px;
|
||||
> .details {
|
||||
position: relative;
|
||||
&:before {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: -4px;
|
||||
transform: translateY(-100%);
|
||||
content: attr(data-pin-tip);
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #98a2b3;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledMsg;
|
||||
@@ -1,7 +1,6 @@
|
||||
import dayjs from "dayjs";
|
||||
import renderContent from "./Message/renderContent";
|
||||
import Avatar from "./Avatar";
|
||||
import StyledWrapper from "./Message/styled";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
import { FC } from "react";
|
||||
import { PinnedMessage } from "../../types/channel";
|
||||
@@ -23,16 +22,16 @@ const PinnedMessageView: FC<Props> = ({ data }) => {
|
||||
const { created_at, content_type, content, properties, thumbnail = "" } = { ...data, ...normalized };
|
||||
const { name, avatar } = usersData[created_by] ?? {};
|
||||
return (
|
||||
<StyledWrapper className={`preview`}>
|
||||
<div className="avatar">
|
||||
<div className="w-full relative flex items-start gap-3 p-2 my-2 rounded-lg">
|
||||
<div>
|
||||
<Avatar width={40} height={40} src={avatar} name={name} />
|
||||
</div>
|
||||
<div className="details">
|
||||
<div className="up">
|
||||
<span className="name">{name}</span>
|
||||
<i className="time">{dayjs(created_at).format("YYYY-MM-DD h:mm:ss A")}</i>
|
||||
<div className="w-full flex flex-col items-start gap-1 text-sm">
|
||||
<div className="flex items-center gap-2 font-semibold">
|
||||
<span className="text-gray-500 dark:text-gray-400">{name}</span>
|
||||
<time className="text-xs text-gray-400">{dayjs(created_at).format("YYYY-MM-DD h:mm:ss A")}</time>
|
||||
</div>
|
||||
<div className={`down`}>
|
||||
<div className={`select-text text-gray-600 break-all whitespace-pre-wrap dark:text-white`}>
|
||||
{renderContent({
|
||||
content_type,
|
||||
content,
|
||||
@@ -42,7 +41,7 @@ const PinnedMessageView: FC<Props> = ({ data }) => {
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
const isCard = type == 'card';
|
||||
const canRemoveFromServer = !isCard && canRemove;
|
||||
const hasMore = email || canRemoveFromChannel || canRemoveFromServer;
|
||||
const iconClass = `cursor-pointer flex flex-col items-center gap-1 rounded-lg w-32 text-primary-400 bg-slate-100 dark:bg-gray-700 text-sm pt-3.5 pb-3`;
|
||||
const containerClass = clsx(`flex-center flex-col w-[432px] gap-1 z-[99] mt-20 select-none`, isCard && "p-4 w-[280px] bg-white dark:bg-gray-800 drop-shadow rounded-md");
|
||||
const iconClass = `cursor-pointer flex flex-col items-center gap-1 rounded-lg w-32 text-primary-400 bg-gray-100 dark:bg-gray-800 text-sm pt-3.5 pb-3`;
|
||||
const containerClass = clsx(`flex-center flex-col gap-1 z-[99] mt-20 select-none`, isCard ? "p-4 w-[280px] bg-white dark:bg-gray-800 drop-shadow rounded-md" : "w-[432px]");
|
||||
return (
|
||||
<div className={containerClass}>
|
||||
<Avatar
|
||||
@@ -65,8 +65,6 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
<span>{t("send_msg")}</span>
|
||||
</li>
|
||||
</NavLink>
|
||||
{/* <NavLink to={`#`}> */}
|
||||
{/* </NavLink> */}
|
||||
<Tippy
|
||||
disabled={!hasMore}
|
||||
interactive
|
||||
|
||||
@@ -72,9 +72,9 @@ export default function Replying({
|
||||
const user = usersData[from_uid];
|
||||
|
||||
return (
|
||||
<div className="reply bg-gray-100 z-[999] flex items-start justify-start gap-4 rounded-t-lg w-full px-4 py-3 text-sm">
|
||||
<div className="reply bg-gray-100 dark:bg-gray-700 z-[999] flex items-start justify-start gap-4 rounded-t-lg w-full px-4 py-3 text-sm">
|
||||
<div className="whitespace-nowrap text-gray-400 ">
|
||||
Replying to <span className="font-bold text-gray-600">{user?.name}</span>
|
||||
Replying to <span className="font-bold text-gray-600 dark:text-gray-400">{user?.name}</span>
|
||||
</div>
|
||||
<div className="text-gray-500 overflow-hidden pr-7 ">{renderContent(msg)}</div>
|
||||
<button className="absolute top-4 right-4 cursor-pointer" onClick={removeReply}>
|
||||
|
||||
@@ -145,7 +145,7 @@ const Send: FC<IProps> = ({
|
||||
{replying_mid && <Replying context={context} mid={replying_mid} id={id} />}
|
||||
{mode == Modes.text && <UploadFileList context={context} id={id} />}
|
||||
|
||||
<div className={clsx(`flex justify-between items-start gap-4 px-4 py-3.5`, isMarkdownMode && `grid grid-cols-[1fr_1fr] grid-rows-[auto_auto] gap-0`)}>
|
||||
<div className={clsx(`flex justify-between items-center gap-4 px-4 py-3.5`, isMarkdownMode && `grid grid-cols-[1fr_1fr] grid-rows-[auto_auto] gap-0`)}>
|
||||
<EmojiPicker selectEmoji={insertEmoji} />
|
||||
{mode == Modes.text && (
|
||||
<MixedInput
|
||||
|
||||
@@ -36,7 +36,7 @@ const StyledSettingContainer: FC<PropsWithChildren<Props>> = ({
|
||||
<ul key={title} data-title={title} className="flex flex-col gap-0.5 mb-9 before:pl-3 before:content-[attr(data-title)] before:font-bold before:text-xs before:text-gray-400">
|
||||
{items.map(({ name, title }) => {
|
||||
return (
|
||||
<li key={name} className={clsx(`text-sm text-gray-700 whitespace-nowrap dark:text-gray-200 rounded hover:bg-[#e7e5e4] dark:hover:bg-slate-500/20`, name == nav?.name && "bg-[#e7e5e4] dark:bg-slate-500/20")}>
|
||||
<li key={name} className={clsx(`text-sm font-semibold text-gray-600 whitespace-nowrap dark:text-gray-200 rounded hover:bg-[#e7e5e4] dark:hover:bg-slate-500/20`, name == nav?.name && "bg-[#e7e5e4] dark:bg-slate-500/20")}>
|
||||
<NavLink to={`${pathname}?nav=${name}`} className="block px-3 py-1">{title}</NavLink>
|
||||
</li>
|
||||
);
|
||||
@@ -50,7 +50,7 @@ const StyledSettingContainer: FC<PropsWithChildren<Props>> = ({
|
||||
if (typeof d === "boolean" || !d) return null;
|
||||
const { title, handler } = d;
|
||||
return (
|
||||
<li key={title} onClick={handler} className="text-sm text-white dark:text-gray-200 rounded cursor-pointer py-1.5 px-3 bg-[#ef4444] hover:bg-red-600">
|
||||
<li key={title} onClick={handler} className="text-sm text-red-500 dark:text-red-400 rounded cursor-pointer py-1.5 px-3">
|
||||
{title}
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -45,7 +45,7 @@ const User: FC<Props> = ({
|
||||
const online = curr.online || curr.uid == loginUid;
|
||||
const containerClass = clsx(`relative flex items-center justify-start gap-2 p-2 rounded-lg select-none `, interactive && "hover:bg-gray-500/10", compact && "p-0");
|
||||
const nameClass = clsx(`text-sm text-gray-500 max-w-[190px] overflow-hidden text-ellipsis font-semibold dark:text-white`);
|
||||
const statusClass = clsx(`absolute -bottom-0.5 -right-1.5 w-3 h-3 box-content rounded-full border-[2px] border-solid border-white`,
|
||||
const statusClass = clsx(`absolute -bottom-0.5 -right-1.5 w-3 h-3 box-content rounded-full border-[2px] border-solid border-white dark:border-gray-300`,
|
||||
online ? "bg-[#22c55e]" : "bg-[#a1a1aa]",
|
||||
compact && "w-3.5 h-3.5");
|
||||
if (!popover)
|
||||
|
||||
@@ -17,7 +17,7 @@ const StyledButton = ({ children, className = '', ...rest }: Props) => {
|
||||
isSmall && "!py-2",
|
||||
noBorder && "!shadow-none border-none",
|
||||
isMini && "!px-2.5 !py-1 !text-xs",
|
||||
isDanger && "bg-red-700 disabled:bg-gray-300 hover:bg-red-700/80",
|
||||
isDanger && "bg-red-500 disabled:bg-gray-300 hover:bg-red-500/80 active:bg-red-700",
|
||||
className
|
||||
)} {...rest}>
|
||||
{children}
|
||||
|
||||
@@ -1,80 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import { useState, FC, DetailedHTMLProps, InputHTMLAttributes } from "react";
|
||||
import { HiEye, HiEyeOff } from "react-icons/hi";
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0px 1px 2px rgba(31, 41, 55, 0.08);
|
||||
|
||||
.prefix {
|
||||
padding: 8px 16px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #9ca3af;
|
||||
background: #f3f4f6;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.view {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 10px;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
// todo: check ime problem
|
||||
const StyledInput = styled.input`
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #333;
|
||||
padding: 8px;
|
||||
outline: none;
|
||||
|
||||
&:not(.inner) {
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0px 1px 2px rgba(31, 41, 55, 0.08);
|
||||
}
|
||||
|
||||
&.large {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
padding: 11px 8px;
|
||||
}
|
||||
|
||||
&.none {
|
||||
outline: none;
|
||||
border: none;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: #78787c;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
&[type="password"] {
|
||||
padding-right: 30px;
|
||||
}
|
||||
`;
|
||||
|
||||
import IconEyeOpen from "../../../assets/icons/eye.open.svg";
|
||||
import IconEyeClose from "../../../assets/icons/eye.close.svg";
|
||||
interface Props
|
||||
extends DetailedHTMLProps<
|
||||
Pick<
|
||||
@@ -100,26 +27,36 @@ interface Props
|
||||
ref?: any;
|
||||
}
|
||||
|
||||
const Input: FC<Props> = ({ type = "text", prefix = "", className, ...rest }) => {
|
||||
const Input: FC<Props> = ({ type = "text", prefix = "", className = "", ...rest }) => {
|
||||
const [inputType, setInputType] = useState(type);
|
||||
const togglePasswordVisible = () => {
|
||||
setInputType((prev) => (prev == "password" ? "text" : "password"));
|
||||
};
|
||||
|
||||
const isLarge = className.includes("large");
|
||||
const isNone = className.includes("none");
|
||||
// const noInner=!className.includes("inner");
|
||||
const isPwd = type == "password";
|
||||
const inputClass = clsx(`w-full bg-white dark:bg-gray-800 text-sm text-gray-800 dark:text-gray-200 p-2 outline-none disabled:text-gray-500 disabled:bg-gray-100 dark:disabled:text-gray-500 dark:disabled:bg-gray-800/50 placeholder:text-gray-400`,
|
||||
// noInner && 'rounded border border-gray-200 shadow',
|
||||
isLarge && 'py-3',
|
||||
isNone && "border-none bg-transparent shadow-none",
|
||||
isPwd && "pr-[30px]"
|
||||
);
|
||||
return type == "password" ? (
|
||||
<StyledWrapper className={className}>
|
||||
<StyledInput type={inputType} autoComplete={inputType == "password" ? "current-password" : "on"} className={`inner ${className}`} {...rest} />
|
||||
<div className="view" onClick={togglePasswordVisible}>
|
||||
{inputType == "password" ? <HiEyeOff color="#78787c" /> : <HiEye color="#78787c" />}
|
||||
<div className={`w-full relative flex overflow-hidden rounded border border-gray-300 dark:border-gray-500 shadow ${className}`}>
|
||||
<input type={inputType} autoComplete={inputType == "password" ? "current-password" : "on"} className={`${inputClass} ${className}`} {...rest} />
|
||||
<div className="absolute top-1/2 right-2.5 -translate-y-1/2 cursor-pointer" onClick={togglePasswordVisible}>
|
||||
{inputType == "password" ? <IconEyeClose className="fill-gray-500" /> : <IconEyeOpen className="fill-gray-500" />}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
) : prefix ? (
|
||||
<StyledWrapper className={className}>
|
||||
<span className="prefix">{prefix}</span>
|
||||
<StyledInput className={`inner ${className}`} type={inputType} {...rest} />
|
||||
</StyledWrapper>
|
||||
<div className={`w-full relative flex overflow-hidden rounded border border-gray-300 dark:border-gray-500 shadow ${className}`}>
|
||||
<span className="px-4 py-2 text-sm text-gray-500 dark:text-gray-300 bg-gray-200 dark:bg-gray-800 border dark:border-gray-600">{prefix}</span>
|
||||
<input className={`${inputClass} ${className}`} type={inputType} {...rest} />
|
||||
</div>
|
||||
) : (
|
||||
<StyledInput type={inputType} className={className} {...rest} />
|
||||
<input type={inputType} className={`${inputClass} rounded border border-gray-200 dark:border-gray-400 shadow ${className}`} {...rest} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ type Props = {
|
||||
values: (string | number)[];
|
||||
defaultValue?: string | number;
|
||||
onChange?: (param: any) => void;
|
||||
value: object | number | string;
|
||||
value: number | string;
|
||||
};
|
||||
|
||||
const VALUE_NOT_SET = "";
|
||||
@@ -43,12 +43,12 @@ const Radio: FC<Props> = ({
|
||||
}}
|
||||
id={`${id}-${index}`}
|
||||
/>
|
||||
<div className="drop-shadow px-2 py-3 border border-solid border-[#d0d5dd] rounded-lg w-full h-full bg-white peer-checked:bg-primary-400 text-sm text-gray-500 peer-checked:text-white">
|
||||
<div className="drop-shadow px-2 py-3 border border-gray-300 dark:border-gray-400 rounded-lg w-full h-full bg-white dark:bg-gray-800 peer-checked:bg-primary-400 text-sm text-gray-500 dark:text-gray-300 peer-checked:text-white">
|
||||
<label className="ml-6" htmlFor={`${id}-${index}`}>{item}</label>
|
||||
</div>
|
||||
<div className="absolute top-1/2 left-3 -translate-y-1/2 w-3.5 h-3.5 rounded-full border border-gray-400 peer-checked:hidden"></div>
|
||||
<div className="absolute top-1/2 left-3 -translate-y-1/2 w-3.5 h-3.5 rounded-full border border-white invisible peer-checked:visible flex-center">
|
||||
<div className="w-2 h-2 bg-white rounded-full"></div>
|
||||
<div className="absolute top-1/2 left-3 -translate-y-1/2 w-3.5 h-3.5 rounded-full border border-gray-300 peer-checked:hidden"></div>
|
||||
<div className="absolute top-1/2 left-3 -translate-y-1/2 w-3.5 h-3.5 rounded-full border border-solid border-white invisible peer-checked:visible flex-center">
|
||||
<div className="w-1 h-1 bg-white rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { TextareaHTMLAttributes } from "react";
|
||||
|
||||
type Props = TextareaHTMLAttributes<HTMLTextAreaElement>
|
||||
const StyledTextarea = ({ className, ...rest }: Props) => {
|
||||
return <textarea className={`rounded text-sm p-2 bg-white text-gray-700 resize-none w-full shadow border border-solid border-gray-200 disabled:bg-[#f9fafb] disabled:text-gray-400 disabled:pointer-events-none placeholder:text-gray-400 ${className}`} {...rest}></textarea>;
|
||||
return <textarea className={`rounded text-sm p-2 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 resize-none w-full shadow border border-gray-200 dark:border-gray-400
|
||||
disabled:bg-gray-100 dark:disabled:bg-gray-800/50 disabled:text-gray-400 dark:disabled:text-gray-500 disabled:pointer-events-none placeholder:text-gray-400 ${className}`}
|
||||
{...rest}></textarea>;
|
||||
};
|
||||
export default StyledTextarea;
|
||||
|
||||
Reference in New Issue
Block a user