feat: darkmode draft version
This commit is contained in:
@@ -49,8 +49,8 @@ const AutoDeleteMessages = ({ id, type = "channel" }: Props) => {
|
||||
return (
|
||||
<section className="max-w-[512px] h-full relative">
|
||||
<div className="text-sm">
|
||||
<h2 >{t("title")}</h2>
|
||||
<p className="text-gray-500">{t("desc")}</p>
|
||||
<h2 className='dark:text-white' >{t("title")}</h2>
|
||||
<p className="text-gray-500 dark:text-gray-400">{t("desc")}</p>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<StyledRadio
|
||||
|
||||
@@ -48,11 +48,11 @@ const AvatarUploader: FC<Props> = ({
|
||||
<Avatar width={size} height={size} type={type} src={url} name={name} className={`${className} object-cover w-full h-full`} />
|
||||
{!disabled && (
|
||||
<>
|
||||
<div className="flex-center flex-col whitespace-nowrap hidden group-hover:flex p-1 absolute top-0 left-0 right-0 bottom-0 bg-black/50 text-white font-bold text-xs">
|
||||
<div className="flex-center flex-col whitespace-nowrap hidden group-hover:flex p-1 absolute inset-0 bg-black/50 text-white font-bold text-xs">
|
||||
{uploading ? t("status.uploading") : t("action.change_avatar")}
|
||||
</div>
|
||||
<input
|
||||
className="opacity-0 absolute top-0 left-0 right-0 bottom-0 block cursor-pointer"
|
||||
className="opacity-0 absolute inset-0 block cursor-pointer"
|
||||
multiple={false}
|
||||
onChange={handleUpload}
|
||||
type="file"
|
||||
|
||||
@@ -14,9 +14,9 @@ interface Props {
|
||||
type?: "chat" | "user";
|
||||
}
|
||||
const classes = {
|
||||
box: "w-[150px] md:w-[200px] h-[150px] md:h-[200px] cursor-pointer bg-[#f9fafb] rounded-3xl flex-center flex-col gap-4",
|
||||
box: "w-[150px] md:w-[200px] h-[150px] md:h-[200px] cursor-pointer bg-[#f9fafb] dark:bg-gray-800 rounded-3xl flex-center flex-col gap-4",
|
||||
boxIcon: "w-10 h-10",
|
||||
boxTip: "px-5 text-sm text-[#475467] font-bold text-center"
|
||||
boxTip: "px-5 text-sm text-[#475467] dark:text-gray-100 font-bold text-center"
|
||||
};
|
||||
const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
const { t } = useTranslation("welcome");
|
||||
@@ -38,14 +38,14 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
const chatHandler = type == "chat" ? toggleChannelModalVisible : toggleUserListVisible;
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-8 -mt-[50px]">
|
||||
<div className="flex flex-col gap-8 -mt-[50px] dark:bg-[#384250]">
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<h2 className="text-3xl text-[#344054] font-bold">{t("title", { name: server.name })}</h2>
|
||||
<h2 className="text-3xl text-[#344054] dark:text-white font-bold">{t("title", { name: server.name })}</h2>
|
||||
<p className="text-sm text-[#98a2b3] max-w-[424px] text-center">
|
||||
{t("desc")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 grid-rows-2 gap-2 md:gap-6 mt-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 grid-rows-2 gap-2 md:gap-6 m-auto">
|
||||
<div className={classes.box} onClick={toggleInviteModalVisible}>
|
||||
<IconInvite className={classes.boxIcon} />
|
||||
<div className={classes.boxTip}>{t("invite")}</div>
|
||||
|
||||
@@ -5,7 +5,6 @@ import Modal from "../Modal";
|
||||
import Button from "../styled/Button";
|
||||
import ChannelIcon from "../ChannelIcon";
|
||||
import User from "../User";
|
||||
import StyledWrapper from "./styled";
|
||||
import StyledCheckbox from "../styled/Checkbox";
|
||||
import StyledToggle from "../../component/styled/Toggle";
|
||||
import useFilteredUsers from "../../hook/useFilteredUsers";
|
||||
@@ -13,6 +12,7 @@ import { useCreateChannelMutation } from "../../../app/services/channel";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { CreateChannelDTO } from "../../../types/channel";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface Props {
|
||||
personal?: boolean;
|
||||
@@ -94,18 +94,19 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
|
||||
|
||||
return (
|
||||
<Modal>
|
||||
<StyledWrapper>
|
||||
<div className="flex max-h-[402px] bg-white dark:bg-gray-800 drop-shadow rounded-lg">
|
||||
{!is_public && (
|
||||
<div className="left">
|
||||
<div className="search">
|
||||
<div className="w-[260px] shadow-[inset_-1px_0px_0px_rgba(0,_0,_0,_0.1)]">
|
||||
<div className="sticky top-0 z-[99] rounded-tl-lg shadow-[0px_1px_0px_rgba(0,_0,_0,_0.1)] p-2 w-[calc(100%_-_1px)]">
|
||||
<input
|
||||
className="outline-none p-2 text-sm w-full bg-transparent dark:text-white"
|
||||
value={input}
|
||||
onChange={handleInputChange}
|
||||
placeholder={t("search_user_placeholder")}
|
||||
/>
|
||||
</div>
|
||||
{users && (
|
||||
<ul className="users">
|
||||
<ul className="flex flex-col overflow-y-scroll overflow-x-hidden h-[calc(100%_-_52px_-_10px)]">
|
||||
{users.map((u) => {
|
||||
const { uid } = u;
|
||||
const checked = members ? members.includes(uid) : false;
|
||||
@@ -113,7 +114,7 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
|
||||
<li
|
||||
key={uid}
|
||||
data-uid={uid}
|
||||
className="user"
|
||||
className="cursor-pointer flex items-center px-4 rounded hover:bg-gray-500/10"
|
||||
onClick={loginUid == uid ? undefined : toggleCheckMember}
|
||||
>
|
||||
<StyledCheckbox
|
||||
@@ -131,38 +132,38 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={`right ${!is_public ? "private" : ""}`}>
|
||||
<h3 className="title">{t("create_channel")}</h3>
|
||||
<p className="desc normal">
|
||||
<div className={clsx(`min-w-[400px] flex flex-col items-center p-8 box-border`, !is_public && "w-[344px] justify-evenly")}>
|
||||
<h3 className="font-semibold text-xl text-gray-600 mb-4 dark:text-white">{t("create_channel")}</h3>
|
||||
<p className="text-gray-400 mb-12 text-sm font-normal">
|
||||
{!is_public
|
||||
? t("create_private_channel_desc")
|
||||
: t("create_channel_desc")}
|
||||
</p>
|
||||
<div className="name">
|
||||
<span className="label normal">{t("channel_name")}</span>
|
||||
<div className="input">
|
||||
<input onChange={handleNameInput} value={name} placeholder="new channel" />
|
||||
<ChannelIcon personal={!is_public} className="icon" />
|
||||
<div className="w-full flex flex-col justify-start gap-2 mb-8.5">
|
||||
<span className="text-gray-400 text-sm font-normal">{t("channel_name")}</span>
|
||||
<div className="relative">
|
||||
<input className="text-gray-600 rounded p-2 pl-9 border border-solid border-gray-300 w-full" onChange={handleNameInput} value={name} placeholder="new channel" />
|
||||
<ChannelIcon personal={!is_public} className="absolute left-2 top-1/2 -translate-y-1/2" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="private">
|
||||
<span className="txt normal">{t("private_channel")}</span>
|
||||
<div className="w-full flex items-center justify-between mb-12.5">
|
||||
<span className="text-gray-400 text-sm">{t("private_channel")}</span>
|
||||
<StyledToggle
|
||||
data-checked={!is_public}
|
||||
data-disabled={!loginUser?.is_admin}
|
||||
onClick={handleToggle}
|
||||
/>
|
||||
</div>
|
||||
<div className="btns">
|
||||
<Button onClick={closeModal} className="normal cancel">
|
||||
<div className="w-full flex gap-4 items-center justify-end">
|
||||
<Button onClick={closeModal} className="text-sm cancel">
|
||||
{t("action.cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<Button disabled={isLoading} onClick={handleCreate} className="normal">
|
||||
<Button disabled={isLoading} onClick={handleCreate} className="text-sm">
|
||||
{t("action.create", { ns: "common" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
max-height: 402px;
|
||||
background: #fff;
|
||||
box-shadow: 0px 25px 50px rgba(31, 41, 55, 0.25);
|
||||
border-radius: var(--br);
|
||||
transition: all 0.5s ease;
|
||||
.left {
|
||||
width: 260px;
|
||||
box-shadow: inset -1px 0px 0px rgba(0, 0, 0, 0.1);
|
||||
.search {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 99;
|
||||
background: #fff;
|
||||
border-top-left-radius: var(--br);
|
||||
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
|
||||
padding: 8px;
|
||||
width: calc(100% - 1px);
|
||||
input {
|
||||
outline: none;
|
||||
width: -webkit-fill-available;
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.users {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 52px - 10px);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
.user {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
width: -webkit-fill-available;
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
background: rgba(116, 127, 141, 0.1);
|
||||
}
|
||||
> div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
min-width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 32px;
|
||||
box-sizing: border-box;
|
||||
&.private {
|
||||
width: 344px;
|
||||
justify-content: space-evenly;
|
||||
.desc {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
> .title {
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
color: #374151;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.desc {
|
||||
font-weight: normal;
|
||||
color: #6b7280;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
.name {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 34px;
|
||||
.label {
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
}
|
||||
.input {
|
||||
position: relative;
|
||||
input {
|
||||
width: -webkit-fill-available;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0px 1px 2px rgba(31, 41, 55, 0.08);
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
color: #78787c;
|
||||
padding-left: 36px;
|
||||
}
|
||||
.icon {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.private {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 50px;
|
||||
.txt {
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
}
|
||||
input {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.btns {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 16px;
|
||||
}
|
||||
.normal {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
export default StyledWrapper;
|
||||
@@ -4,8 +4,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const Divider: FC<Props> = ({ content }) => {
|
||||
return <div className="relative border-none h-[1px] bg-[#e3e5e8] my-6 overflow-visible">
|
||||
<span className="p-1 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-xs text-gray-500 font-semibold bg-white">{content}</span>
|
||||
return <div className="relative border-none h-[1px] bg-[#e3e5e8] dark:bg-gray-500 my-6 overflow-visible">
|
||||
<span className="p-1 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-xs text-gray-500 dark:text-gray-300 font-semibold bg-white dark:bg-[#384250]">{content}</span>
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ const InviteLink: FC<Props> = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start pb-8">
|
||||
<span className="font-semibold text-sm mb-2 text-gray-500">{t("share_invite_link")}</span>
|
||||
<span className="font-semibold text-sm mb-2 text-gray-500 dark:text-gray-50">{t("share_invite_link")}</span>
|
||||
<div className="w-[512px] mb-3 relative">
|
||||
<Input readOnly className={"large !pr-16"} placeholder="Generating" value={link} />
|
||||
<Button onClick={copyLink} className="ghost small border_less absolute right-1 top-1/2 -translate-y-1/2">
|
||||
{linkCopied ? "Copied" : t("action.copy", { ns: "common" })}
|
||||
</Button>
|
||||
</div>
|
||||
<span className="text-xs text-gray-600">{t("invite_link_expire")}</span>
|
||||
<span className="text-xs text-gray-600 dark:text-gray-100">{t("invite_link_expire")}</span>
|
||||
<div className="w-44 h-44 my-2">
|
||||
<QRCode link={link} />
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@ const InviteByEmail: FC<Props> = ({ cid }) => {
|
||||
return (
|
||||
<div className="pt-4">
|
||||
<div className="flex flex-col gap-4 mb-6">
|
||||
<label className="text-sm text-gray-400" htmlFor="">{t("invite_by_email")}</label>
|
||||
<label className="text-sm text-gray-400 dark:text-gray-100" htmlFor="">{t("invite_by_email")}</label>
|
||||
<div className="relative flex items-center gap-2">
|
||||
<form ref={formRef} action="/" className="w-full">
|
||||
<Input
|
||||
@@ -69,7 +69,7 @@ const InviteByEmail: FC<Props> = ({ cid }) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 mb-3">
|
||||
<label className="text-sm text-gray-400" htmlFor="">{t("send_invite_link")}</label>
|
||||
<label className="text-sm text-gray-400 dark:text-gray-100" htmlFor="">{t("send_invite_link")}</label>
|
||||
<div className="relative flex items-center gap-2">
|
||||
<Input readOnly className="!pr-[50px]" placeholder="Generating" value={link} />
|
||||
<button className="absolute right-1 top-1/2 -translate-y-1/2 pr-2 text-sm text-primary-400 hover:text-primary-600" onClick={copyLink}>
|
||||
@@ -80,7 +80,7 @@ const InviteByEmail: FC<Props> = ({ cid }) => {
|
||||
<div className="w-44 h-44 my-2">
|
||||
{!generating && <QRCode link={link} />}
|
||||
</div>
|
||||
<div className="text-xs text-gray-600">
|
||||
<div className="text-xs text-gray-600 dark:text-gray-200">
|
||||
{t("invite_link_expire")}
|
||||
<button disabled={generating} className="text-primary-400 ml-1" onClick={() => generateNewLink()}>
|
||||
{t("generate_new_link")}
|
||||
|
||||
@@ -25,10 +25,10 @@ const InviteModal: FC<Props> = ({ type = "server", cid, title = "", closeModal }
|
||||
const finalTitle = type == "server" ? server.name : `#${title || channel?.name}`;
|
||||
return (
|
||||
<Modal>
|
||||
<div className="flex flex-col bg-white rounded-lg p-4 min-w-[408px]">
|
||||
<h2 className="flex items-center justify-between text-lg text-gray-700 ">
|
||||
<div className="flex flex-col bg-white dark:bg-gray-900 rounded-lg p-4 min-w-[408px]">
|
||||
<h2 className="flex items-center justify-between text-lg text-gray-700 dark:text-gray-50 ">
|
||||
{t("invite_title", { name: finalTitle })}
|
||||
<CloseIcon className="cursor-pointer" onClick={closeModal} />
|
||||
<CloseIcon className="cursor-pointer dark:fill-white" onClick={closeModal} />
|
||||
</h2>
|
||||
{!channel?.is_public && <AddMembers cid={cid} closeModal={closeModal} />}
|
||||
<InviteByEmail cid={channel?.is_public ? undefined : cid} />
|
||||
|
||||
@@ -30,7 +30,7 @@ const Loading: FC<Props> = ({ reload = false, fullscreen = false }) => {
|
||||
}, [reload]);
|
||||
|
||||
return (
|
||||
<div className={clsx("w-full h-full flex-center flex-col gap-4", fullscreen ? "w-screen h-screen" : "")}>
|
||||
<div className={clsx("w-full h-full flex-center flex-col gap-4 dark:bg-gray-800/80", fullscreen ? "w-screen h-screen" : "")}>
|
||||
<Ring size={40} lineWeight={5} speed={2} color="black" />
|
||||
<Button className={clsx(`danger`, reloadVisible ? "visible" : "invisible")} onClick={handleReload}>
|
||||
Reload
|
||||
|
||||
@@ -4,7 +4,6 @@ import { hideAll } from "tippy.js";
|
||||
import toast from "react-hot-toast";
|
||||
import { useUpdateUserMutation } from "../../../app/services/user";
|
||||
import User from "../User";
|
||||
import StyledWrapper from "./styled";
|
||||
import StyledMenu from "../styled/Menu";
|
||||
import InviteLink from "../InviteLink";
|
||||
import moreIcon from "../../../assets/icons/more.svg?url";
|
||||
@@ -54,16 +53,16 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
const channel = cid ? channels.byId[cid] : null;
|
||||
const uids = channel ? (channel.is_public ? users.ids : channel.members) : users.ids;
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<section className="flex flex-col w-full">
|
||||
{loginUser?.is_admin && <InviteLink />}
|
||||
<div className="intro">
|
||||
<h4 className="title">{t("manage_members")}</h4>
|
||||
<p className="desc">
|
||||
<div className="flex flex-col mb-10">
|
||||
<h4 className="font-bold text-gray-600 dark:text-white">{t("manage_members")}</h4>
|
||||
<p className="text-gray-500 dark:text-gray-100 text-xs">
|
||||
{t("manage_tip")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul className="members">
|
||||
<ul className="flex flex-col gap-1 w-[512px] mb-44">
|
||||
{uids.map((uid) => {
|
||||
const currUser = users.byId[uid];
|
||||
if (!currUser) return null;
|
||||
@@ -75,18 +74,18 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
const canRemoveFromChannel =
|
||||
channel && channel.owner == loginUser?.uid && loginUser?.uid != uid;
|
||||
return (
|
||||
<li key={uid} className="member">
|
||||
<div className="left">
|
||||
<li key={uid} className="w-full flex items-center justify-between px-3 py-2 rounded-md hover:bg-slate-50 dark:hover:bg-gray-800">
|
||||
<div className="flex gap-4">
|
||||
<User compact uid={uid} interactive={false} />
|
||||
<div className="info">
|
||||
<span className="name">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-bold text-sm text-gray-600 dark:text-white flex items-center gap-1">
|
||||
{name} {owner && <IconOwner />}
|
||||
</span>
|
||||
<span className="email">{email}</span>
|
||||
<span className="text-xs text-gray-500 dark:text-slate-50">{email}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="right">
|
||||
<span className="role">
|
||||
<div className="flex items-center gap-7">
|
||||
<span className="text-xs text-right text-gray-500 dark:text-slate-100 flex items-center gap-1">
|
||||
{is_admin ? t("admin") : t("user")}
|
||||
{switchRoleVisible && (
|
||||
<Tippy
|
||||
@@ -120,7 +119,7 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
</StyledMenu>
|
||||
}
|
||||
>
|
||||
<IconArrowDown className="icon" />
|
||||
<IconArrowDown className="cursor-pointer dark:fill-slate-50" />
|
||||
</Tippy>
|
||||
)}
|
||||
</span>
|
||||
@@ -130,7 +129,7 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
placement="right-start"
|
||||
trigger="click"
|
||||
content={
|
||||
<StyledMenu className="menu">
|
||||
<StyledMenu className="min-w-30">
|
||||
{email && (
|
||||
<li className="item" onClick={copyEmail.bind(null, email)}>
|
||||
{ct("action.copy_email")}
|
||||
@@ -149,8 +148,8 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
</StyledMenu>
|
||||
}
|
||||
>
|
||||
<div className="opts">
|
||||
<img className="dots" src={moreIcon} alt="dots icon" />
|
||||
<div className="relative w-6 h-6">
|
||||
<img className="cursor-pointer" src={moreIcon} alt="dots icon" />
|
||||
</div>
|
||||
</Tippy>
|
||||
)}
|
||||
@@ -159,7 +158,7 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</StyledWrapper>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
export default ManageMembers;
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
const StyledWrapper = styled.section`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
.intro {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 40px;
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #374151;
|
||||
}
|
||||
.desc {
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #616161;
|
||||
}
|
||||
}
|
||||
.members {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
width: 512px;
|
||||
margin-bottom: 176px;
|
||||
.member {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: var(--br);
|
||||
&:hover {
|
||||
background: #f9fafb;
|
||||
}
|
||||
.left {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.name {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #52525b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.email {
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #52525b;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 28px;
|
||||
.role {
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
text-align: right;
|
||||
color: #616161;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
> .icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
/* override */
|
||||
.menu {
|
||||
min-width: 120px;
|
||||
.item .icon {
|
||||
width: 16px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.opts {
|
||||
position: relative;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
.dots {
|
||||
cursor: pointer;
|
||||
}
|
||||
.menu {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export default StyledWrapper;
|
||||
@@ -5,10 +5,9 @@ import "prismjs/themes/prism.css";
|
||||
import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css";
|
||||
import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
|
||||
|
||||
import StyledWrapper from "./styled";
|
||||
import useUploadFile from "../../hook/useUploadFile";
|
||||
|
||||
import Button from "../styled/Button";
|
||||
|
||||
type Props = {
|
||||
updateDraft?: (draft: string) => void;
|
||||
initialValue: string;
|
||||
@@ -62,7 +61,7 @@ const MarkdownEditor: FC<Props> = ({
|
||||
}
|
||||
};
|
||||
return (
|
||||
<StyledWrapper className="input">
|
||||
<div className="input md-editor">
|
||||
<Editor
|
||||
initialValue={initialValue}
|
||||
plugins={[codeSyntaxHighlight]}
|
||||
@@ -78,7 +77,7 @@ const MarkdownEditor: FC<Props> = ({
|
||||
<Button className="send small" onClick={send}>
|
||||
Send
|
||||
</Button>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default MarkdownEditor;
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
width: -webkit-fill-available;
|
||||
margin-top: 16px;
|
||||
> div {
|
||||
transition: height 0.5s ease 0s;
|
||||
}
|
||||
.toastui-editor-defaultUI {
|
||||
border-bottom: none;
|
||||
border-radius: 0;
|
||||
border-top: 1px solid #d0d5dd;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
.toastui-editor {
|
||||
padding: 16px 0;
|
||||
[contenteditable="true"] {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.toastui-editor-md-preview {
|
||||
padding-top: 16px;
|
||||
.toastui-editor-contents {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.toastui-editor-toolbar {
|
||||
display: none;
|
||||
}
|
||||
.send {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState, useRef, FC } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { hideAll } from "tippy.js";
|
||||
import toast from "react-hot-toast";
|
||||
@@ -21,47 +20,7 @@ import IconDelete from "../../../assets/icons/delete.svg";
|
||||
import moreIcon from "../../../assets/icons/more.svg?url";
|
||||
import useMessageOperation from "./useMessageOperation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledCmds = styled.ul`
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 6px;
|
||||
background-color: #fff;
|
||||
visibility: hidden;
|
||||
&.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
.cmd {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
&:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
img,
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
&.fav {
|
||||
svg path {
|
||||
fill: #667085;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .picker {
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: 0;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
`;
|
||||
import clsx from "clsx";
|
||||
type Props = {
|
||||
context: "user" | "channel";
|
||||
contextId: number;
|
||||
@@ -124,9 +83,20 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// const StyledCmds = styled.ul`
|
||||
// > .picker {
|
||||
// position: absolute;
|
||||
// left: -10px;
|
||||
// top: 0;
|
||||
// transform: translateX(-100%);
|
||||
// }
|
||||
// `;
|
||||
return (
|
||||
<>
|
||||
<StyledCmds ref={cmdsRef} className={`cmds ${tippyVisible ? "visible" : ""}`}>
|
||||
<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')}>
|
||||
<Tippy
|
||||
onShow={handleTippyVisible.bind(null, true)}
|
||||
onHide={handleTippyVisible.bind(null, false)}
|
||||
@@ -135,29 +105,29 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
|
||||
trigger="click"
|
||||
content={<ReactionPicker mid={mid} hidePicker={hideAll} />}
|
||||
>
|
||||
<li className="cmd">
|
||||
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800">
|
||||
<Tooltip placement="top" tip={t("action.add_reaction")}>
|
||||
<img src={reactIcon} className="toggler" alt="icon emoji" />
|
||||
<img src={reactIcon} className="toggler w-6 h-6" alt="icon emoji" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
</Tippy>
|
||||
{canEdit && (
|
||||
<li className="cmd" onClick={toggleEditMessage}>
|
||||
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800" onClick={toggleEditMessage}>
|
||||
<Tooltip placement="top" tip={t("action.edit")}>
|
||||
<img src={editIcon} alt="icon edit" />
|
||||
<img src={editIcon} className="w-6 h-6" alt="icon edit" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
)}
|
||||
{canReply && (
|
||||
<li className="cmd" onClick={handleReply}>
|
||||
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800" onClick={handleReply}>
|
||||
<Tooltip placement="top" tip={t("action.reply")}>
|
||||
<img src={replyIcon} alt="icon reply" />
|
||||
<img src={replyIcon} className="w-6 h-6" alt="icon reply" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
)}
|
||||
<li className="cmd fav" onClick={handleAddFav}>
|
||||
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800" onClick={handleAddFav}>
|
||||
<Tooltip placement="top" tip={t("action.add_to_fav")}>
|
||||
<IconBookmark />
|
||||
<IconBookmark className="fill-[#667085] w-6 h-6" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
<Tippy
|
||||
@@ -196,13 +166,13 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
|
||||
/>
|
||||
}
|
||||
>
|
||||
<li className="cmd">
|
||||
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800">
|
||||
<Tooltip placement="top" tip={t("more")}>
|
||||
<img src={moreIcon} alt="icon more" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
</Tippy>
|
||||
</StyledCmds>
|
||||
</ul>
|
||||
{PinModal}
|
||||
{ForwardModal}
|
||||
{DeleteModal}
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
// import { FC, ReactNode } from "react";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import styled from "styled-components";
|
||||
import Profile from "../Profile";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
|
||||
const Styled = styled.span`
|
||||
padding: 0 2px;
|
||||
color: #1fe1f9;
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
uid: number;
|
||||
popover?: boolean;
|
||||
@@ -24,7 +15,7 @@ const Mention = ({ uid, popover = true, cid, textOnly = false }: Props) => {
|
||||
const user = usersData[uid];
|
||||
if (!user) return null;
|
||||
if (textOnly) return <>{`@${user.name}`}</>;
|
||||
if (!popover) return <Styled>{`@${user.name}`}</Styled>;
|
||||
if (!popover) return <span className="px-0.5 text-primary-400">{`@${user.name}`}</span>;
|
||||
return (
|
||||
<Tippy
|
||||
interactive
|
||||
@@ -32,7 +23,7 @@ const Mention = ({ uid, popover = true, cid, textOnly = false }: Props) => {
|
||||
trigger="click"
|
||||
content={<Profile uid={uid} type="card" cid={cid} />}
|
||||
>
|
||||
<Styled className="clickable">{`@${user.name}`}</Styled>
|
||||
<span className="px-0.5 text-primary-400 cursor-pointer">{`@${user.name}`}</span>
|
||||
</Tippy>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FC } from "react";
|
||||
import { useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import toast from "react-hot-toast";
|
||||
import usePinMessage from "../../hook/usePinMessage";
|
||||
import StyledModal from "../styled/Modal";
|
||||
@@ -9,20 +8,6 @@ import Modal from "../Modal";
|
||||
import PreviewMessage from "./PreviewMessage";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledPinModal = styled(StyledModal)`
|
||||
min-width: 406px;
|
||||
.title,
|
||||
.desc {
|
||||
text-align: left;
|
||||
}
|
||||
.preview {
|
||||
border: 1px solid #f2f4f7;
|
||||
max-height: 256px;
|
||||
overflow: auto;
|
||||
background: none;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
closeModal: () => void;
|
||||
@@ -40,7 +25,7 @@ const PinMessageModal: FC<Props> = ({ closeModal, mid = 0, gid = 0 }) => {
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
closeModal();
|
||||
toast.success("Pin Message Successfully");
|
||||
toast.success(t("tip.pin"));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isSuccess]);
|
||||
@@ -48,8 +33,8 @@ const PinMessageModal: FC<Props> = ({ closeModal, mid = 0, gid = 0 }) => {
|
||||
if (!mid) return null;
|
||||
return (
|
||||
<Modal>
|
||||
<StyledPinModal
|
||||
// className="animate__animated animate__fadeInDown animate__faster"
|
||||
<StyledModal
|
||||
className="min-w-[406px]"
|
||||
buttons={
|
||||
<>
|
||||
<Button onClick={closeModal} className="cancel">
|
||||
@@ -63,8 +48,8 @@ const PinMessageModal: FC<Props> = ({ closeModal, mid = 0, gid = 0 }) => {
|
||||
title={t("action.pin")}
|
||||
description={`Do you want to pin this message to #${channel?.name}`}
|
||||
>
|
||||
<PreviewMessage mid={mid} />
|
||||
</StyledPinModal>
|
||||
<PreviewMessage mid={mid} context="pin" />
|
||||
</StyledModal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
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";
|
||||
import { FC } from "react";
|
||||
|
||||
interface Props {
|
||||
mid?: number;
|
||||
context?: "forward" | "pin"
|
||||
}
|
||||
|
||||
const PreviewMessage: FC<Props> = ({ mid = 0 }) => {
|
||||
const PreviewMessage: FC<Props> = ({ mid = 0, context }) => {
|
||||
const { msg, usersData } = useAppSelector((store) => {
|
||||
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 { name, avatar } = usersData[from_uid] ?? {};
|
||||
const pinMsg = context == "pin";
|
||||
return (
|
||||
<StyledWrapper className={`preview`}>
|
||||
<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">
|
||||
<Avatar width={40} height={40} src={avatar} name={name} />
|
||||
</div>
|
||||
|
||||
@@ -96,7 +96,7 @@ const Message: FC<IProps> = ({
|
||||
onContextMenu={readOnly ? undefined : handleContextMenuEvent}
|
||||
data-msg-mid={mid}
|
||||
ref={inviewRef}
|
||||
className={`message ${readOnly ? "readonly" : ""} ${showExpire ? "auto_delete" : ""} ${pinInfo ? "pinned" : ""} ${contextMenuVisible ? "contextVisible" : ""
|
||||
className={`message dark:hover:!bg-gray-800 ${readOnly ? "readonly" : ""} ${showExpire ? "auto_delete" : ""} ${pinInfo ? "pinned" : ""} ${contextMenuVisible ? "contextVisible" : ""
|
||||
} `}
|
||||
>
|
||||
<Tippy
|
||||
@@ -133,14 +133,14 @@ const Message: FC<IProps> = ({
|
||||
placement="top"
|
||||
tip={dayjsTime.format("YYYY-MM-DD h:mm:ss A")}
|
||||
>
|
||||
<i className="time">
|
||||
<time className="time">
|
||||
{timePrefix
|
||||
? `${timePrefix} ${dayjsTime.format("h:mm A")}`
|
||||
: dayjsTime.format("YYYY-MM-DD h:mm:ss A")}
|
||||
</i>
|
||||
</time>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={`down ${sending ? "sending" : ""}`}>
|
||||
<div className={`down dark:!text-white ${sending ? "sending" : ""}`}>
|
||||
{reply_mid && <Reply key={reply_mid} mid={reply_mid} />}
|
||||
{edit ? (
|
||||
<EditMessage mid={mid} cancelEdit={toggleEditMessage} />
|
||||
|
||||
@@ -188,7 +188,7 @@ const Plugins: FC<Props> = ({
|
||||
|
||||
|
||||
return (
|
||||
<div className="input max-h-[50vh] overflow-auto text-sm text-gray-600" ref={editableRef}>
|
||||
<div className="input max-h-[50vh] overflow-auto text-sm text-gray-600 dark:text-white" ref={editableRef}>
|
||||
<Plate
|
||||
id={`${TEXT_EDITOR_PREFIX}_${id}`}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -45,8 +45,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 text-sm pt-3.5 pb-3`;
|
||||
const containerClass = clsx(`flex-center flex-col w-[432px] gap-1 z-[998] mt-20 select-none`, isCard && "p-4 w-[280px] bg-white drop-shadow rounded-md");
|
||||
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");
|
||||
return (
|
||||
<div className={containerClass}>
|
||||
<Avatar
|
||||
@@ -56,8 +56,8 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
src={avatar}
|
||||
name={name}
|
||||
/>
|
||||
<h2 className="text-lg select-text font-bold text-[#1c1c1e]">{name}</h2>
|
||||
<span className="text-sm text-[#98a2b3] select-text">{email}</span>
|
||||
<h2 className="text-lg select-text font-bold text-[#1c1c1e] dark:text-white">{name}</h2>
|
||||
<span className="text-sm text-[#98a2b3] dark:text-gray-200 select-text">{email}</span>
|
||||
{/* <p className="intro">{introduction}</p> */}
|
||||
<ul className={clsx("mt-6 flex items-center gap-2", isCard && "pb-0.5")}>
|
||||
<NavLink to={`/chat/dm/${uid}`}>
|
||||
|
||||
@@ -10,7 +10,7 @@ const QRCode = ({ link }: Props) => {
|
||||
const logo = useAppSelector(store => store.server.logo);
|
||||
return (
|
||||
<QRCodeCanvas value={link}
|
||||
className="rounded border border-solid border-gray-200 p-1 !w-full !h-full"
|
||||
className="rounded border border-solid border-gray-200 dark:border-gray-100 p-1 !w-full !h-full"
|
||||
size={512}
|
||||
bgColor={"#fff"}
|
||||
fgColor={"#22ccee"}
|
||||
|
||||
@@ -13,10 +13,10 @@ const SaveTip: FC<Props> = ({ saveHandler, resetHandler }) => {
|
||||
const { t } = useTranslation("setting");
|
||||
// const btnClass=clsx("")
|
||||
return (
|
||||
<div className="w-full p-2 absolute bottom-16 left-0 flex items-center justify-between text-gray-500 border border-solid border-gray-200 shadow-md rounded-full">
|
||||
<span className="p-2 text-sm">{t('save_tip')}</span>
|
||||
<div className="w-full p-2 absolute bottom-16 left-0 flex items-center justify-between text-gray-500 border border-solid border-gray-200 dark:border-gray-400 dark:bg-gray-600 shadow-md rounded-full">
|
||||
<span className="p-2 text-sm dark:text-gray-200">{t('save_tip')}</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<Button className="small ghost !border-none !text-gray-500 !shadow-none" onClick={resetHandler}>
|
||||
<Button className="small ghost !border-none !text-gray-500 !shadow-none dark:!text-gray-100" onClick={resetHandler}>
|
||||
{t('reset')}
|
||||
</Button>
|
||||
<Button className="small !rounded-full" onClick={saveHandler}>
|
||||
|
||||
@@ -1,52 +1,9 @@
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import Modal from "../../Modal";
|
||||
import Button from "../../styled/Button";
|
||||
import Input from "../../styled/Input";
|
||||
import CloseIcon from "../../../../assets/icons/close.svg";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
width: 406px;
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
color: #344054;
|
||||
width: 100%;
|
||||
.close {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.input {
|
||||
padding: 16px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
label {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #475467;
|
||||
}
|
||||
}
|
||||
.btns {
|
||||
margin-top: 32px;
|
||||
gap: 16px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function EditFileDetails({
|
||||
name,
|
||||
@@ -65,23 +22,24 @@ export default function EditFileDetails({
|
||||
updateName(fileName);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal>
|
||||
<StyledWrapper>
|
||||
<h4 className="title">
|
||||
File Details <CloseIcon className="close" onClick={closeModal} />
|
||||
<div className="bg-white dark:bg-gray-800 flex flex-col drop-shadow rounded-xl p-4 w-[406px]">
|
||||
<h4 className="flex items-center justify-between font-semibold text-lg text-gray-700 dark:text-gray-100 w-full">
|
||||
File Details <CloseIcon className="cursor-pointer dark:fill-white" onClick={closeModal} />
|
||||
</h4>
|
||||
<div className="input">
|
||||
<label htmlFor="name">Name</label>
|
||||
<div className="py-4 flex flex-col gap-2">
|
||||
<label className="font-semibold text-sm text-gray-600 dark:text-gray-200" htmlFor="name">Name</label>
|
||||
<Input id="name" value={fileName} onChange={handleNameChange} />
|
||||
</div>
|
||||
<div className="btns">
|
||||
<div className="flex justify-end gap-4 mt-8 w-full">
|
||||
<Button className="ghost cancel" onClick={closeModal}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleUpdate}>Save Changes</Button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import Styled from "./styled";
|
||||
import { useMixedEditor } from "../../MixedInput";
|
||||
import EditFileDetailsModal from "./EditFileDetails";
|
||||
import { getFileIcon, formatBytes } from "../../../utils";
|
||||
@@ -43,6 +42,7 @@ export default function UploadFileList({
|
||||
}, [stageFiles.length]);
|
||||
|
||||
if (!context || !id || !stageFiles || stageFiles.length == 0) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{editInfo && (
|
||||
@@ -53,21 +53,21 @@ export default function UploadFileList({
|
||||
/>
|
||||
)}
|
||||
|
||||
<Styled>
|
||||
<ul className="w-full overflow-auto flex justify-start p-4 pt-6 bg-gray-200 dark:bg-gray-800 rounded-t-lg">
|
||||
{stageFiles.map(({ name, url, size, type }, idx: number) => {
|
||||
return (
|
||||
<li className="file" key={url}>
|
||||
<div className="preview">
|
||||
{type.startsWith("image") ? <img src={url} alt="image" /> : getFileIcon(type, name)}
|
||||
<li className="group relative flex flex-col bg-gray-100 dark:bg-gray-700 rounded p-2" key={url}>
|
||||
<div className="flex-center w-40 h-40">
|
||||
{type.startsWith("image") ? <img className="w-full h-full object-cover" src={url} alt="image" /> : getFileIcon(type, name)}
|
||||
</div>
|
||||
<h4 className="name">{name}</h4>
|
||||
<span className="size">{formatBytes(size)}</span>
|
||||
<ul className="opts">
|
||||
<li className="opt edit" onClick={handleOpenEditModal.bind(null, idx)}>
|
||||
<h4 className="w-40 mt-4 mb-0.5 font-semibold text-sm text-gray-800 dark:text-gray-100 whitespace-nowrap overflow-hidden text-ellipsis">{name}</h4>
|
||||
<span className="text-xs text-gray-500">{formatBytes(size)}</span>
|
||||
<ul className="invisible group-hover:visible bg-inherit border border-solid border-black/10 box-border rounded-md flex items-center absolute -right-5 -top-2.5">
|
||||
<li className="p-1 cursor-pointer edit" onClick={handleOpenEditModal.bind(null, idx)}>
|
||||
<EditIcon />
|
||||
</li>
|
||||
<li
|
||||
className="opt delete"
|
||||
className="p-1 cursor-pointer delete"
|
||||
data-index={idx}
|
||||
onClick={removeStageFile.bind(null, idx)}
|
||||
>
|
||||
@@ -77,7 +77,7 @@ export default function UploadFileList({
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</Styled>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const Styled = styled.ul`
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 24px;
|
||||
padding: 24px 16px 16px 16px;
|
||||
background: #e5e7eb;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 8px 8px 0 0;
|
||||
.file {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
background: #fcfcfd;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
.preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
.name {
|
||||
width: 160px;
|
||||
margin: 16px 0 2px 0;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #1c1c1e;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.size {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #616161;
|
||||
}
|
||||
.opts {
|
||||
visibility: hidden;
|
||||
background: inherit;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
top: -10px;
|
||||
.opt {
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
&:hover .opts {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Styled;
|
||||
@@ -137,7 +137,7 @@ const Send: FC<IProps> = ({
|
||||
context == "channel" ? (channelsData[id]?.is_public ? uids : channelsData[id]?.members) : [];
|
||||
return (
|
||||
<StyledSend
|
||||
className={`send ${mode} ${markdownFullscreen ? "fullscreen" : ""} ${replying_mid ? "reply" : ""
|
||||
className={`send dark:bg-[#4D5761] ${mode} ${markdownFullscreen ? "fullscreen" : ""} ${replying_mid ? "reply" : ""
|
||||
} ${context}`}
|
||||
>
|
||||
{replying_mid && <Replying context={context} mid={replying_mid} id={id} />}
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function Server({ readonly = false }: Props) {
|
||||
<img alt={`${name} logo`} className="w-full h-full object-cover rounded-full" src={logo} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h3 className="text-sm text-gray-600" title={description}>
|
||||
<h3 className="text-sm text-gray-600 dark:text-gray-100" title={description}>
|
||||
{name}
|
||||
</h3>
|
||||
<span className="text-xs text-gray-500">{userCount} {t("members")}</span>
|
||||
@@ -45,7 +45,7 @@ export default function Server({ readonly = false }: Props) {
|
||||
<img alt={`${name} logo`} className="w-full h-full object-cover rounded-full" src={logo} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h3 className="text-sm text-gray-600 font-bold" title={description}>
|
||||
<h3 className="text-sm text-gray-600 font-bold dark:text-gray-100" title={description}>
|
||||
{name}
|
||||
</h3>
|
||||
<span className="text-xs text-gray-500">{userCount} {t("members")}</span>
|
||||
|
||||
@@ -1,96 +1,8 @@
|
||||
import { FC, PropsWithChildren, ReactNode } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useLocation, NavLink } from "react-router-dom";
|
||||
import backIcon from "../../assets/icons/arrow.left.svg?url";
|
||||
import IconBack from "../../assets/icons/arrow.left.svg";
|
||||
import { Nav } from "../../routes/settingChannel/navs";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
|
||||
> .left {
|
||||
max-height: 100vh;
|
||||
overflow: scroll;
|
||||
padding: 32px 16px;
|
||||
min-width: 212px;
|
||||
background-color: #f5f6f7;
|
||||
|
||||
> .title {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #1c1c1e;
|
||||
margin-bottom: 32px;
|
||||
padding-left: 24px;
|
||||
background-size: 16px;
|
||||
background: url(${backIcon}) no-repeat left;
|
||||
}
|
||||
|
||||
> .items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
margin-bottom: 36px;
|
||||
|
||||
&:before {
|
||||
padding-left: 12px;
|
||||
content: attr(data-title);
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.item {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #44494f;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover,
|
||||
&.curr {
|
||||
background: #e7e5e4;
|
||||
}
|
||||
|
||||
> a {
|
||||
display: block;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&.danger .item {
|
||||
cursor: pointer;
|
||||
padding: 4px 12px;
|
||||
color: #ef4444;
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .right {
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
padding: 32px;
|
||||
|
||||
> .title {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
color: #374151;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
import clsx from "clsx";
|
||||
export interface Danger {
|
||||
title: string;
|
||||
handler: () => void;
|
||||
@@ -114,18 +26,18 @@ const StyledSettingContainer: FC<PropsWithChildren<Props>> = ({
|
||||
}) => {
|
||||
const { pathname } = useLocation();
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="left">
|
||||
<h2 onClick={closeModal} className="title">
|
||||
{title}
|
||||
<div className="w-screen h-screen flex">
|
||||
<div className="max-h-screen min-w-[212px] overflow-scroll px-4 py-8 bg-[#f5f6f7] dark:bg-[#1F2A37]">
|
||||
<h2 onClick={closeModal} className="flex gap-2 items-center cursor-pointer mb-8 font-bold text-gray-800 dark:text-white">
|
||||
<IconBack className="dark:fill-gray-400" /> {title}
|
||||
</h2>
|
||||
{navs.map(({ title, items }) => {
|
||||
return (
|
||||
<ul key={title} data-title={title} className="items">
|
||||
<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={`item ${name == nav?.name ? "curr" : ""}`}>
|
||||
<NavLink to={`${pathname}?nav=${name}`}>{title}</NavLink>
|
||||
<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")}>
|
||||
<NavLink to={`${pathname}?nav=${name}`} className="block px-3 py-1">{title}</NavLink>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
@@ -133,12 +45,12 @@ const StyledSettingContainer: FC<PropsWithChildren<Props>> = ({
|
||||
);
|
||||
})}
|
||||
{dangers.length ? (
|
||||
<ul className="items danger">
|
||||
<ul className="flex flex-col gap-2 mb-9">
|
||||
{dangers.map((d) => {
|
||||
if (typeof d === "boolean" || !d) return null;
|
||||
const { title, handler } = d;
|
||||
return (
|
||||
<li key={title} onClick={handler} className="item">
|
||||
<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">
|
||||
{title}
|
||||
</li>
|
||||
);
|
||||
@@ -146,11 +58,11 @@ const StyledSettingContainer: FC<PropsWithChildren<Props>> = ({
|
||||
</ul>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="right">
|
||||
{nav && <h4 className="title">{nav.title}</h4>}
|
||||
<div className="bg-white w-full max-h-full overflow-auto p-8 dark:bg-[#384250]">
|
||||
{nav && <h4 className="font-bold text-xl text-gray-600 mb-8 dark:text-gray-100">{nav.title}</h4>}
|
||||
{children}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ const Tooltip: FC<Props> = ({ tip = "", placement = "right", delay = null, child
|
||||
duration={delay ? defaultDuration : 0}
|
||||
delay={delay ?? [150, 0]}
|
||||
placement={placement}
|
||||
content={<div className="relative bg-white px-3 py-2 text-xs rounded-lg drop-shadow text-gray-700">
|
||||
content={<div className="relative bg-white dark:bg-gray-800 px-3 py-2 text-xs rounded-lg drop-shadow text-gray-700 dark:text-gray-100">
|
||||
<Triangle placement={placement} />
|
||||
{tip}
|
||||
</div>}
|
||||
|
||||
@@ -44,7 +44,7 @@ const User: FC<Props> = ({
|
||||
if (!curr) return null;
|
||||
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`);
|
||||
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`,
|
||||
online ? "bg-[#22c55e]" : "bg-[#a1a1aa]",
|
||||
compact && "w-3.5 h-3.5");
|
||||
|
||||
@@ -6,7 +6,7 @@ const Version: FC<Props> = () => {
|
||||
const { t } = useTranslation("setting", { keyPrefix: "version" });
|
||||
const { data: serverVersion } = useGetServerVersionQuery();
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-3 dark:text-white">
|
||||
<div className="item">{t("client_version")}: {process.env.VERSION}</div>
|
||||
<div className="item">{t("server_version")}: {serverVersion}</div>
|
||||
<div className="item">{t("build_time")}: {process.env.REACT_APP_BUILD_TIME}</div>
|
||||
|
||||
@@ -12,9 +12,9 @@ interface Props {
|
||||
|
||||
const StyledModal: FC<Props> = ({ compact = false, title = "", description = "", buttons, children, className }) => {
|
||||
return (
|
||||
<div className={clsx("rounded-lg bg-white drop-shadow", compact ? "p-4 min-w-[406px] text-left" : "p-8 min-w-[440px] text-center", className)} >
|
||||
{title && <h3 className="text-xl text-gray-600 mb-4 font-semibold">{title}</h3>}
|
||||
{description && <p className="text-sm text-gray-400 mb-2">{description}</p>}
|
||||
<div className={clsx("rounded-lg bg-white dark:bg-gray-900 drop-shadow", compact ? "p-4 min-w-[406px] text-left" : "p-8 min-w-[440px] text-center", className)} >
|
||||
{title && <h3 className="text-xl text-gray-600 dark:text-white mb-4 font-semibold">{title}</h3>}
|
||||
{description && <p className="text-sm text-gray-400 dark:text-gray-100 mb-2">{description}</p>}
|
||||
{children}
|
||||
{buttons && <div className="pt-4 w-full flex justify-end gap-4 items-center">{buttons}</div>}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user