refactor: more tailwind components

This commit is contained in:
Tristan Yang
2023-01-30 18:56:37 +08:00
parent 5a2ac0f4ed
commit f5aabee719
46 changed files with 185 additions and 796 deletions
+13 -43
View File
@@ -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} />}
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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"
};
+2 -9
View File
@@ -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;
+5 -52
View File
@@ -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>
);
};
+1 -1
View File
@@ -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>
);
+1 -1
View File
@@ -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]);
+3 -2
View File
@@ -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} />;
+19 -103
View File
@@ -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>
);
}
+13 -11
View File
@@ -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>
);
};
-64
View File
@@ -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;
+11 -55
View File
@@ -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>
);
};
+5 -29
View File
@@ -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>
);
}
+2 -2
View File
@@ -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">
-13
View File
@@ -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
};
}
+8 -52
View File
@@ -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>
);
};
+7 -36
View File
@@ -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}`}
/>;
}
+4 -25
View File
@@ -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>
);
};