refactor: more tailwind components
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import { hideAll } from "tippy.js";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
import IconInvite from "../../assets/icons/add.person.svg";
|
||||
@@ -10,38 +9,6 @@ import UsersModal from "./UsersModal";
|
||||
import InviteModal from "./InviteModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Styled = styled.ul`
|
||||
z-index: 999;
|
||||
user-select: none;
|
||||
box-shadow: 0 24px 48px -12px rgba(16, 24, 40, 0.18);
|
||||
border-radius: 12px;
|
||||
color: #616161;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 4px;
|
||||
.item {
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
padding: 10px 8px;
|
||||
&:hover {
|
||||
background: rgba(116, 127, 141, 0.2);
|
||||
}
|
||||
.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
path {
|
||||
fill: #475467;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export default function AddEntriesMenu() {
|
||||
const { t } = useTranslation();
|
||||
const currentUser = useAppSelector((store) => store.authData.user);
|
||||
@@ -74,29 +41,32 @@ export default function AddEntriesMenu() {
|
||||
const handleCloseModal = () => {
|
||||
setChannelModalVisible(false);
|
||||
};
|
||||
|
||||
const itemClass = "rounded flex items-center gap-2 text-sm font-semibold cursor-pointer px-2 py-2.5 hover:bg-gray-800/20";
|
||||
const iconClass = "w-5 h-5";
|
||||
return (
|
||||
<>
|
||||
<Styled>
|
||||
<ul className="flex flex-col rounded-xl drop-shadow p-1 select-none text-gray-500 bg-white">
|
||||
{/* temp remove public channel */}
|
||||
{currentUser?.is_admin && (
|
||||
<li className="item" onClick={handleOpenChannelModal.bind(null, false)}>
|
||||
<ChannelIcon className="icon" />
|
||||
<li className={itemClass} onClick={handleOpenChannelModal.bind(null, false)}>
|
||||
<ChannelIcon className={iconClass} />
|
||||
{t("action.new_channel")}
|
||||
</li>
|
||||
)}
|
||||
<li className="item" onClick={handleOpenChannelModal.bind(null, true)}>
|
||||
<ChannelIcon personal={true} className="icon" />
|
||||
<li className={itemClass} onClick={handleOpenChannelModal.bind(null, true)}>
|
||||
<ChannelIcon personal={true} className={iconClass} />
|
||||
{t("action.new_private_channel")}
|
||||
</li>
|
||||
<li className="item" onClick={toggleUsersModalVisible}>
|
||||
<IconMention className="icon" />
|
||||
<li className={itemClass} onClick={toggleUsersModalVisible}>
|
||||
<IconMention className={iconClass} />
|
||||
{t("action.new_msg")}
|
||||
</li>
|
||||
<li className="item" onClick={toggleInviteModalVisible}>
|
||||
<IconInvite className="icon" />
|
||||
<li className={itemClass} onClick={toggleInviteModalVisible}>
|
||||
<IconInvite className={iconClass} />
|
||||
{t("action.invite_people")}
|
||||
</li>
|
||||
</Styled>
|
||||
</ul>
|
||||
{channelModalVisible && <ChannelModal personal={isPrivate} closeModal={handleCloseModal} />}
|
||||
{usersModalVisible && <UsersModal closeModal={toggleUsersModalVisible} />}
|
||||
{inviteModalVisible && <InviteModal closeModal={toggleInviteModalVisible} />}
|
||||
|
||||
@@ -34,7 +34,7 @@ const Avatar: FC<Props> = ({
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className="rounded-full flex items-center justify-center"
|
||||
className="rounded-full flex-center"
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
|
||||
@@ -14,7 +14,7 @@ interface Props {
|
||||
type?: "chat" | "user";
|
||||
}
|
||||
const classes = {
|
||||
box: "w-[200px] h-[200px] cursor-pointer bg-[#f9fafb] rounded-3xl flex flex-col justify-center items-center gap-4",
|
||||
box: "w-[200px] h-[200px] cursor-pointer bg-[#f9fafb] rounded-3xl flex-center flex-col gap-4",
|
||||
boxIcon: "w-10 h-10",
|
||||
boxTip: "px-5 text-sm text-[#475467] font-bold text-center"
|
||||
};
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import HashIcon from "../../assets/icons/channel.svg";
|
||||
import LockHashIcon from "../../assets/icons/channel.private.svg";
|
||||
|
||||
const Styled = styled.div`
|
||||
display: flex;
|
||||
&.muted path {
|
||||
fill: #d0d5dd;
|
||||
}
|
||||
`;
|
||||
interface Props {
|
||||
personal?: boolean;
|
||||
muted?: boolean;
|
||||
@@ -17,9 +10,9 @@ interface Props {
|
||||
|
||||
const ChannelIcon: FC<Props> = ({ personal = false, muted = false, className = "" }) => {
|
||||
return (
|
||||
<Styled className={`${muted ? "muted" : ""} ${className}`}>
|
||||
<div className={`flex${muted ? "!text-gray-400 !fill-slate-400" : ""} ${className}`}>
|
||||
{personal ? <LockHashIcon /> : <HashIcon />}
|
||||
</Styled>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ const Audio: FC<Props> = ({ url }) => {
|
||||
|
||||
if (!url) return null;
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="flex-center h-full">
|
||||
{err ? (
|
||||
<div className="p-[18px] text-base text-gray-500">Unable to play this audio</div>
|
||||
) : (
|
||||
|
||||
@@ -34,8 +34,6 @@ const StyledSocialButton = styled(Button)`
|
||||
}
|
||||
}
|
||||
> .hide {
|
||||
/* z-index: 1; */
|
||||
/* opacity: 0; */
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
|
||||
@@ -1,56 +1,8 @@
|
||||
import { useRef, useState, useEffect, FC } from "react";
|
||||
import styled, { keyframes } from "styled-components";
|
||||
import { useOutsideClick, useKey } from "rooks";
|
||||
import { Ring } from "@uiball/loaders";
|
||||
import Modal from "./Modal";
|
||||
|
||||
const AniFadeIn = keyframes`
|
||||
from {
|
||||
background: transparent;
|
||||
}
|
||||
to {
|
||||
background: rgba(1, 1, 1, 0.9);
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
transition: all 0.5s ease;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
animation: ${AniFadeIn} 0.3s ease-in-out forwards;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.box {
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
> .loading {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
> .image {
|
||||
overflow: auto;
|
||||
|
||||
img {
|
||||
max-width: 70vw;
|
||||
max-height: 80vh;
|
||||
}
|
||||
}
|
||||
|
||||
&.loading .image img {
|
||||
filter: blur(2px);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface PreviewImageData {
|
||||
originUrl: string;
|
||||
@@ -100,10 +52,11 @@ const ImagePreviewModal: FC<Props> = ({ download = true, data, closeModal }) =>
|
||||
const { originUrl, downloadLink, name, type } = data;
|
||||
return (
|
||||
<Modal>
|
||||
<StyledWrapper>
|
||||
<div className={`box ${loading ? "loading" : ""}`} ref={wrapperRef}>
|
||||
<div className="image">
|
||||
<div className="w-screen h-screen flex-center bg-black/90">
|
||||
<div className={`relative flex flex-col justify-start gap-3 ${loading ? "absolute top-1/3 left-1/2 -translate-x-1/2" : ""}`} ref={wrapperRef}>
|
||||
<div className="overflow-auto">
|
||||
<img
|
||||
className={`max-w-[70vw] max-h-[80vh] ${loading ? "blur-sm" : ""}`}
|
||||
src={url}
|
||||
alt="preview"
|
||||
/>
|
||||
@@ -126,7 +79,7 @@ const ImagePreviewModal: FC<Props> = ({ download = true, data, closeModal }) =>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ const InactiveScreen: FC<Props> = () => {
|
||||
location.reload();
|
||||
};
|
||||
return (
|
||||
<div className="w-screen h-screen flex justify-center items-center text-4xl font-bold">
|
||||
<div className="w-screen h-screen flex-center text-4xl font-bold">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<h1 className="text-4xl font-bold">{t("inactive.title")}</h1>
|
||||
<p className="text-gray-400 text-base font-semibold" >{t("inactive.desc")}</p>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useState, FC } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import styled from "styled-components";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Modal from "../Modal";
|
||||
import useLeaveChannel from "../../hook/useLeaveChannel";
|
||||
@@ -8,29 +7,7 @@ import StyledModal from "../styled/Modal";
|
||||
import Button from "../styled/Button";
|
||||
import User from "../User";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const UserList = styled.ul`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 260px;
|
||||
padding: 16px 0;
|
||||
overflow-y: scroll;
|
||||
.user {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
width: -webkit-fill-available;
|
||||
&:hover,
|
||||
&.selected {
|
||||
background: rgba(116, 127, 141, 0.1);
|
||||
}
|
||||
> a {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
import clsx from "clsx";
|
||||
interface Props {
|
||||
id: number;
|
||||
closeModal: () => void;
|
||||
@@ -90,19 +67,19 @@ const TransferOwnerModal: FC<Props> = ({ id, closeModal, withLeave = true }) =>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<UserList>
|
||||
<ul className="flex flex-col max-h-[260px] py-4 overflow-y-scroll">
|
||||
{otherMembers.map((id) => {
|
||||
return (
|
||||
<li
|
||||
key={id}
|
||||
className={`user ${uid == id ? "selected" : ""}`}
|
||||
className={clsx(`cursor-pointer flex items-center px-2 hover:bg-gray-500/10`, uid == id ? "bg-gray-500/10" : "")}
|
||||
onClick={handleSelectUser.bind(null, id)}
|
||||
>
|
||||
<User uid={id} interactive={false} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</UserList>
|
||||
</ul>
|
||||
</StyledModal>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ const Loading: FC<Props> = ({ reload = false, fullscreen = false }) => {
|
||||
}, [reload]);
|
||||
|
||||
return (
|
||||
<div className={clsx("w-full h-full flex flex-col items-center justify-center gap-4", fullscreen ? "w-screen h-screen" : "")}>
|
||||
<div className={clsx("w-full h-full flex-center flex-col gap-4", fullscreen ? "w-screen h-screen" : "")}>
|
||||
<Ring size={40} lineWeight={5} speed={2} color="black" />
|
||||
<Button className={clsx(`danger`, reloadVisible ? "visible" : "invisible")} onClick={handleReload}>
|
||||
Reload
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import { FC, ReactElement, useEffect, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import StyledMsg from "./styled";
|
||||
import renderContent from "./renderContent";
|
||||
import Avatar from "../Avatar";
|
||||
import useFavMessage from "../../hook/useFavMessage";
|
||||
|
||||
const StyledFav = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: var(--br);
|
||||
background-color: #f4f4f5;
|
||||
`;
|
||||
type Props = {
|
||||
id?: string;
|
||||
};
|
||||
@@ -25,7 +18,7 @@ const FavoredMessage: FC<Props> = ({ id = "" }) => {
|
||||
const favorite_mids = messages.map(({ from_mid }) => +from_mid) || [];
|
||||
|
||||
setMsgs(
|
||||
<StyledFav data-favorite-mids={favorite_mids.join(",")} className="favorite">
|
||||
<div data-favorite-mids={favorite_mids.join(",")} className="favorite flex flex-col rounded-md bg-slate-200">
|
||||
<div className="list">
|
||||
{messages.map((msg, idx) => {
|
||||
const { user = {}, download, content, content_type, properties, thumbnail } = msg;
|
||||
@@ -54,7 +47,7 @@ const FavoredMessage: FC<Props> = ({ id = "" }) => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</StyledFav>
|
||||
</div>
|
||||
);
|
||||
}, [favorites, id]);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import LinkifyText from '../LinkifyText';
|
||||
|
||||
import Avatar from "../Avatar";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { MessagePayload } from "../../../app/slices/message";
|
||||
const Styled = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -96,7 +97,7 @@ const Styled = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const renderContent = (data) => {
|
||||
const renderContent = (data: MessagePayload) => {
|
||||
const { content_type, content, thumbnail, properties } = data;
|
||||
let res = null;
|
||||
switch (content_type) {
|
||||
@@ -116,7 +117,7 @@ const renderContent = (data) => {
|
||||
break;
|
||||
case ContentTypes.file:
|
||||
{
|
||||
const { content_type, name, size } = properties;
|
||||
const { content_type = "", name, size } = properties || {};
|
||||
const icon = getFileIcon(content_type, name);
|
||||
if (isImage(content_type, size)) {
|
||||
res = <img className="pic" src={thumbnail} />;
|
||||
|
||||
@@ -1,95 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useLazyGetOGInfoQuery } from "../../../app/services/message";
|
||||
|
||||
const StyledCompact = styled.a`
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #d4d4d4;
|
||||
box-sizing: border-box;
|
||||
border-radius: 6px;
|
||||
padding: 12px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 380px;
|
||||
.favicon {
|
||||
display: flex;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 4px;
|
||||
img {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.title {
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #1c1c1e;
|
||||
}
|
||||
.desc {
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #616161;
|
||||
}
|
||||
.link {
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
line-height: 18px;
|
||||
color: #616161;
|
||||
}
|
||||
.dots {
|
||||
width: 288px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
`;
|
||||
const StyledDetails = styled.a`
|
||||
width: 380px;
|
||||
padding: 12px;
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #d4d4d4;
|
||||
box-sizing: border-box;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #06aed4;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.desc {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #616161;
|
||||
margin-bottom: 8px;
|
||||
width: 356px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function URLPreview({ url = "" }) {
|
||||
const [favicon, setFavicon] = useState("");
|
||||
@@ -122,26 +33,31 @@ export default function URLPreview({ url = "" }) {
|
||||
};
|
||||
if (!url || !data || !data.title) return null;
|
||||
const { title, description, ogImage } = data;
|
||||
|
||||
const containerClass = `flex items-center border border-solid border-gray-300 box-border rounded-md w-[380px]`;
|
||||
const dotsClass = `overflow-hidden whitespace-nowrap text-ellipsis`;
|
||||
return ogImage ? (
|
||||
<StyledDetails href={url} target="_blank">
|
||||
<h3 className="title">{title}</h3>
|
||||
<p className="desc dots">{description}</p>
|
||||
<div className="image">
|
||||
<img src={ogImage} alt="og image" />
|
||||
<a className={`${containerClass} flex-col !items-start p-3`} href={url} target="_blank" rel="noreferrer">
|
||||
<h3 className={`text-base text-primary-500 w-full ${dotsClass}`}>{title}</h3>
|
||||
<p className={`text-xs text-gray-400 mb-2 w-full ${dotsClass}`}>{description}</p>
|
||||
<div className="w-full h-[180px]">
|
||||
<img className="w-full h-full object-cover" src={ogImage} alt="og image" />
|
||||
</div>
|
||||
</StyledDetails>
|
||||
</a>
|
||||
) : (
|
||||
<StyledCompact href={url} target="_blank">
|
||||
<a
|
||||
className={`${containerClass} gap-2 px-2 py-3`}
|
||||
href={url} target="_blank" rel="noreferrer">
|
||||
{favicon && (
|
||||
<div className="favicon">
|
||||
<img onError={handleFavError} src={favicon} alt="favicon" />
|
||||
<div className="flex w-12 h-12 rounded">
|
||||
<img onError={handleFavError} className="object-contain" src={favicon} alt="favicon" />
|
||||
</div>
|
||||
)}
|
||||
<div className="info">
|
||||
<h3 className="title">{title}</h3>
|
||||
<p className="desc dots">{description}</p>
|
||||
<span className="link dots">{url}</span>
|
||||
<div className="flex flex-col">
|
||||
<h3 className="text-sm text-gray-900">{title}</h3>
|
||||
<p className={`text-xs text-gray-500 w-[288px] ${dotsClass}`}>{description}</p>
|
||||
<span className={`text-[10px] text-gray-500 w-[288px] ${dotsClass}`}>{url}</span>
|
||||
</div>
|
||||
</StyledCompact>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import Tippy from "@tippyjs/react";
|
||||
import IconMessage from "../../../assets/icons/message.svg";
|
||||
import IconMore from "../../../assets/icons/more.svg";
|
||||
import Avatar from "../Avatar";
|
||||
import StyledWrapper from "./styled";
|
||||
import StyledMenu from "../styled/Menu";
|
||||
import useUserOperation from "../../hook/useUserOperation";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface Props {
|
||||
uid: number;
|
||||
@@ -42,11 +42,13 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
avatar
|
||||
// introduction = "This guy has nothing to introduce",
|
||||
} = data;
|
||||
const canRemoveFromServer = type == "embed" && canRemove;
|
||||
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");
|
||||
return (
|
||||
<StyledWrapper className={type}>
|
||||
<div className={containerClass}>
|
||||
<Avatar
|
||||
width={80}
|
||||
height={80}
|
||||
@@ -57,11 +59,11 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
<h2 className="text-lg select-text font-bold text-[#1c1c1e]">{name}</h2>
|
||||
<span className="text-sm text-[#98a2b3] select-text">{email}</span>
|
||||
{/* <p className="intro">{introduction}</p> */}
|
||||
<ul className="icons">
|
||||
<ul className={clsx("mt-6 flex items-center gap-2", isCard && "pb-0.5")}>
|
||||
<NavLink to={`/chat/dm/${uid}`}>
|
||||
<li className="icon chat">
|
||||
<li className={`${iconClass} icon chat`}>
|
||||
<IconMessage />
|
||||
<span className="txt">{t("send_msg")}</span>
|
||||
<span>{t("send_msg")}</span>
|
||||
</li>
|
||||
</NavLink>
|
||||
{/* <NavLink to={`#`}> */}
|
||||
@@ -93,13 +95,13 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
</StyledMenu>
|
||||
}
|
||||
>
|
||||
<li className={`icon more ${hasMore ? "" : "disabled"}`}>
|
||||
<IconMore />
|
||||
<span className="txt">{ct("more")}</span>
|
||||
<li className={`${iconClass} icon ${hasMore ? "" : "text-gray-500"}`}>
|
||||
<IconMore className={hasMore ? "fill-primary-500" : ""} />
|
||||
<span >{ct("more")}</span>
|
||||
</li>
|
||||
</Tippy>
|
||||
</ul>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
z-index: 998;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 80px;
|
||||
width: 432px;
|
||||
gap: 4px;
|
||||
.icons {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #22ccee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
background: #f9fafb;
|
||||
border-radius: 8px;
|
||||
width: 128px;
|
||||
padding: 14px 0 12px 0;
|
||||
|
||||
&:not(.disabled):hover {
|
||||
background: #f2f4f7;
|
||||
}
|
||||
&.call,
|
||||
&.more {
|
||||
svg path {
|
||||
fill: #22ccee;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
color: #667085;
|
||||
svg path {
|
||||
fill: #667085;
|
||||
}
|
||||
}
|
||||
.txt {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.card {
|
||||
padding: 16px;
|
||||
width: 280px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0px 20px 25px 20px rgba(31, 41, 55, 0.1), 0px 10px 10px rgba(31, 41, 55, 0.04);
|
||||
border-radius: 6px;
|
||||
.icons {
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -1,53 +1,8 @@
|
||||
// import clsx from "clsx";
|
||||
import { FC, MouseEvent } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import Button from '../component/styled/Button';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 64px;
|
||||
left: 0;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
|
||||
border-radius: 25px;
|
||||
.txt {
|
||||
padding: 8px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
.btn {
|
||||
color: #fff;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
padding: 8px 14px;
|
||||
background: #1fe1f9;
|
||||
border: 1px solid #1fe1f9;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
|
||||
border-radius: 25px;
|
||||
&.reset {
|
||||
background: none;
|
||||
color: #667085;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
saveHandler: (e: MouseEvent) => void;
|
||||
@@ -56,18 +11,19 @@ interface Props {
|
||||
|
||||
const SaveTip: FC<Props> = ({ saveHandler, resetHandler }) => {
|
||||
const { t } = useTranslation("setting");
|
||||
// const btnClass=clsx("")
|
||||
return (
|
||||
<StyledWrapper className="animate__animated animate__flipInX animate__faster">
|
||||
<span className="txt">{t('save_tip')}</span>
|
||||
<div className="btns">
|
||||
<button className="btn reset" onClick={resetHandler}>
|
||||
<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="flex items-center gap-3">
|
||||
<Button className="small ghost !border-none !text-gray-500 !shadow-none" onClick={resetHandler}>
|
||||
{t('reset')}
|
||||
</button>
|
||||
<button className="btn" onClick={saveHandler}>
|
||||
</Button>
|
||||
<Button className="small !rounded-full" onClick={saveHandler}>
|
||||
{t('save_change')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,34 +1,10 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useOutsideClick } from "rooks";
|
||||
import Tooltip from "../Tooltip";
|
||||
import { Picker } from 'emoji-mart';
|
||||
import SmileIcon from "../../../assets/icons/emoji.smile.svg";
|
||||
import clsx from "clsx";
|
||||
|
||||
const Styled = styled.div`
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> .emoji {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
> svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
> .picker {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: -20px;
|
||||
transform: translateY(-100%);
|
||||
&.visible {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function EmojiPicker({ selectEmoji }: { selectEmoji: (e: string) => void }) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
@@ -75,12 +51,12 @@ export default function EmojiPicker({ selectEmoji }: { selectEmoji: (e: string)
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" tip="Emojis" disabled={visible}>
|
||||
<Styled>
|
||||
<div ref={ref} className={`picker ${visible ? "visible" : ""}`}>
|
||||
<div className="relative w-fit flex items-center">
|
||||
<div ref={ref} className={clsx(`absolute -top-5 -left-5 -translate-y-full`, visible ? 'block' : 'hidden')}>
|
||||
{/* emoji picker */}
|
||||
</div>
|
||||
<SmileIcon data-emoji="toggler" className="emoji" onClick={togglePickerVisible} />
|
||||
</Styled>
|
||||
<SmileIcon data-emoji="toggler" className="cursor-pointer select-none !w-[22px] !h-[22px]" onClick={togglePickerVisible} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ const renderContent = (data: MessagePayload) => {
|
||||
break;
|
||||
case ContentTypes.file:
|
||||
{
|
||||
const { content_type, name, size } = properties;
|
||||
const { content_type = "", name, size } = properties || {};
|
||||
const image = isImage(content_type, size);
|
||||
// console.log("replying data", content_type, size, image);
|
||||
if (image) {
|
||||
@@ -136,7 +136,7 @@ export default function Replying({
|
||||
removeReplying();
|
||||
};
|
||||
if (!msg) return null;
|
||||
const { from_uid } = msg;
|
||||
const { from_uid = 0 } = msg;
|
||||
const user = usersData[from_uid];
|
||||
return (
|
||||
<Styled className="reply">
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { useState } from "react";
|
||||
|
||||
export default function useFiles(initialFiles = []) {
|
||||
const [files, setFiles] = useState(initialFiles);
|
||||
const resetFiles = () => {
|
||||
setFiles([]);
|
||||
};
|
||||
return {
|
||||
files,
|
||||
setFiles,
|
||||
resetFiles
|
||||
};
|
||||
}
|
||||
@@ -1,56 +1,11 @@
|
||||
import { ChangeEvent, useRef, FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { useOutsideClick } from "rooks";
|
||||
import useFilteredUsers from "../hook/useFilteredUsers";
|
||||
|
||||
import User from "./User";
|
||||
import Modal from "./Modal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 440px;
|
||||
max-height: 402px;
|
||||
background: #fff;
|
||||
box-shadow: 0 25px 50px rgba(31, 41, 55, 0.25);
|
||||
border-radius: 8px;
|
||||
transition: all 0.5s ease;
|
||||
.search {
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 8px;
|
||||
width: -webkit-fill-available;
|
||||
input {
|
||||
outline: none;
|
||||
width: -webkit-fill-available;
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.users {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 260px;
|
||||
padding: 16px 0;
|
||||
overflow-y: scroll;
|
||||
.user {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
width: -webkit-fill-available;
|
||||
&:hover {
|
||||
background: rgba(116, 127, 141, 0.1);
|
||||
}
|
||||
> a {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
closeModal: () => void;
|
||||
@@ -66,19 +21,20 @@ const UsersModal: FC<Props> = ({ closeModal }) => {
|
||||
updateInput(evt.target.value);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Modal>
|
||||
<StyledWrapper ref={wrapperRef}>
|
||||
<div className="search">
|
||||
<input value={input} onChange={handleSearch} placeholder={t("search_user_placeholder")} />
|
||||
<div className="flex flex-col w-[440px] max-h-[402px] bg-white drop-shadow rounded-lg" ref={wrapperRef}>
|
||||
<div className="shadow-md p-2">
|
||||
<input className=" p-2 text-sm" value={input} onChange={handleSearch} placeholder={t("search_user_placeholder")} />
|
||||
</div>
|
||||
{users && (
|
||||
<ul className="users">
|
||||
<ul className="flex flex-col overflow-y-scroll h-[260px] py-4">
|
||||
{users.map((u) => {
|
||||
const { uid = 0 } = u || {};
|
||||
return (
|
||||
<li key={uid} className="user">
|
||||
<NavLink onClick={closeModal} to={`/chat/dm/${uid}`}>
|
||||
<li key={uid} className="cursor-pointer px-2 hover:bg-gray-600/10">
|
||||
<NavLink className={'w-full'} onClick={closeModal} to={`/chat/dm/${uid}`}>
|
||||
<User uid={uid} interactive={false} />
|
||||
</NavLink>
|
||||
</li>
|
||||
@@ -86,7 +42,7 @@ const UsersModal: FC<Props> = ({ closeModal }) => {
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,40 +1,11 @@
|
||||
import { InputHTMLAttributes } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Styled = styled.input`
|
||||
-webkit-appearance: none;
|
||||
/* Remove most all native input styles */
|
||||
appearance: none;
|
||||
/* Not removed via appearance */
|
||||
margin: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 1px solid #d0d5dd;
|
||||
border-radius: 6px;
|
||||
place-content: center;
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: 4px;
|
||||
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
||||
transform: scale(0);
|
||||
transform-origin: bottom left;
|
||||
transition: 120ms transform ease-in-out;
|
||||
box-shadow: inset 10px 10px #1fe1f9;
|
||||
}
|
||||
&:checked {
|
||||
border-color: #1fe1f9;
|
||||
&:before {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function StyledCheckbox(props: InputHTMLAttributes<HTMLInputElement>) {
|
||||
return <Styled readOnly {...props} type="checkbox" />;
|
||||
const { className: cbClasses } = props;
|
||||
return <input
|
||||
readOnly
|
||||
{...props}
|
||||
type="checkbox"
|
||||
className={`checkbox w-5 h-5 rounded-md border border-solid border-slate-300 checked:border-[#1fe1f9] disabled:opacity-40 ${cbClasses}`}
|
||||
/>;
|
||||
}
|
||||
|
||||
@@ -1,31 +1,10 @@
|
||||
import { FC, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import IconSelect from "../../../assets/icons/check.sign.svg";
|
||||
import IconArrow from "../../../assets/icons/arrow.down.svg";
|
||||
import Menu from "./Menu";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Styled = styled.div`
|
||||
user-select: none;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.txt {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #475467;
|
||||
min-width: 76px;
|
||||
}
|
||||
> .icon {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface Option {
|
||||
icon?: string;
|
||||
@@ -80,10 +59,10 @@ const Select: FC<Props> = ({ options = [], updateSelect = null, current = null }
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Styled onClick={toggleVisible}>
|
||||
<span className="txt">{(current !== null ? current : curr)?.title || t("action.select")}</span>
|
||||
<IconArrow className="icon" />
|
||||
</Styled>
|
||||
<div className="select-none border border-solid border-slate-200 p-2 flex items-center gap-2" onClick={toggleVisible}>
|
||||
<span className="text-sm text-gray-500 min-w-[76px]">{(current !== null ? current : curr)?.title || t("action.select")}</span>
|
||||
<IconArrow className="!w-5 !h-5" />
|
||||
</div>
|
||||
</Tippy>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user