refactor: fav UX
This commit is contained in:
@@ -67,14 +67,6 @@ const Styled = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
path {
|
|
||||||
fill-opacity: 1;
|
|
||||||
fill: #667085;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover .opts {
|
&:hover .opts {
|
||||||
@@ -112,7 +104,7 @@ const PinList: FC<Props> = ({ id }: Props) => {
|
|||||||
<div className="opts">
|
<div className="opts">
|
||||||
{canPin && (
|
{canPin && (
|
||||||
<button className="btn" data-mid={data.mid} onClick={handleUnpin}>
|
<button className="btn" data-mid={data.mid} onClick={handleUnpin}>
|
||||||
<IconClose />
|
<IconClose className="fill-slate-900" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+12
-77
@@ -7,81 +7,16 @@ import useFavMessage from "../../common/hook/useFavMessage";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const Styled = styled.div`
|
const Styled = styled.div`
|
||||||
padding: 16px;
|
|
||||||
background: #f9fafb;
|
|
||||||
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
|
|
||||||
border-radius: 12px;
|
|
||||||
min-width: 500px;
|
|
||||||
max-height: 500px;
|
|
||||||
overflow: auto;
|
|
||||||
> .head {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 24px;
|
|
||||||
color: #344054;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
> .none {
|
|
||||||
padding: 16px;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
.tip {
|
|
||||||
width: 240px;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 24px;
|
|
||||||
text-align: center;
|
|
||||||
color: #475467;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
.fav {
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid #f2f4f7;
|
|
||||||
border-radius: var(--br);
|
|
||||||
.favorite {
|
.favorite {
|
||||||
background: none;
|
background: none;
|
||||||
|
&:hover{
|
||||||
|
background: #f5f6f7;
|
||||||
|
}
|
||||||
.down img {
|
.down img {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
> .opts {
|
|
||||||
visibility: hidden;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
position: absolute;
|
|
||||||
top: 8px;
|
|
||||||
right: 8px;
|
|
||||||
padding: 4px;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
||||||
border-radius: 6px;
|
|
||||||
.btn {
|
|
||||||
display: flex;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
svg {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
path {
|
|
||||||
fill: #667085;
|
|
||||||
fill-opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover .opts {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
type Props = { cid?: number; uid?: number };
|
type Props = { cid?: number; uid?: number };
|
||||||
const FavList: FC<Props> = ({ cid = null, uid = null }) => {
|
const FavList: FC<Props> = ({ cid = null, uid = null }) => {
|
||||||
@@ -94,22 +29,22 @@ const FavList: FC<Props> = ({ cid = null, uid = null }) => {
|
|||||||
};
|
};
|
||||||
const noFavs = favorites.length == 0;
|
const noFavs = favorites.length == 0;
|
||||||
return (
|
return (
|
||||||
<Styled>
|
<Styled className="p-4 bg-slate-50 rounded-xl min-w-[500px] max-h-[500px] overflow-auto drop-shadow-[0px_25px_50px_rgba(31,_41,_55,_0.25)]">
|
||||||
<h4 className="head">{t('fav_msg')}({favorites.length})</h4>
|
<h4 className="font-bold text-base text-gray-600 mb-4">{t('fav_msg')}({favorites.length})</h4>
|
||||||
{noFavs ? (
|
{noFavs ? (
|
||||||
<div className="none">
|
<div className="flex flex-col gap-2 w-full items-center p-4">
|
||||||
<IconSurprise />
|
<IconSurprise />
|
||||||
<div className="tip">{t("fav_empty_tip")}</div>
|
<div className="w-60 text-base text-gray-600 text-center font-bold">{t("fav_empty_tip")}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ul className="list">
|
<ul className="flex flex-col gap-2">
|
||||||
{favorites.map(({ id }) => {
|
{favorites.map(({ id }) => {
|
||||||
return (
|
return (
|
||||||
<li key={id} className="fav">
|
<li key={id} className="relative border border-solid border-slate-200 rounded-md group">
|
||||||
<FavoredMessage id={id} />
|
<FavoredMessage id={id} />
|
||||||
<div className="opts">
|
<div className="flex items-center absolute top-2 right-2 border border-solid border-gray-300 rounded-md overflow-hidden invisible group-hover:visible">
|
||||||
<button className="btn" data-id={id} onClick={handleRemove}>
|
<button className="flex justify-center items-center w-6 h-6 p-1" data-id={id} onClick={handleRemove}>
|
||||||
<IconRemove />
|
<IconRemove className="fill-slate-900" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
+33
-25
@@ -1,5 +1,4 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, MouseEvent } from "react";
|
||||||
import Styled from "./styled";
|
|
||||||
import FavoredMessage from "../../common/component/Message/FavoredMessage";
|
import FavoredMessage from "../../common/component/Message/FavoredMessage";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import IconAudio from "../../assets/icons/file.audio.svg";
|
import IconAudio from "../../assets/icons/file.audio.svg";
|
||||||
@@ -7,34 +6,38 @@ import IconVideo from "../../assets/icons/file.video.svg";
|
|||||||
import IconUnknown from "../../assets/icons/file.unknown.svg";
|
import IconUnknown from "../../assets/icons/file.unknown.svg";
|
||||||
import IconImage from "../../assets/icons/file.image.svg";
|
import IconImage from "../../assets/icons/file.image.svg";
|
||||||
import IconChannel from "../../assets/icons/channel.svg";
|
import IconChannel from "../../assets/icons/channel.svg";
|
||||||
|
import IconRemove from "../../assets/icons/close.svg";
|
||||||
import { ContentTypes } from "../../app/config";
|
import { ContentTypes } from "../../app/config";
|
||||||
import { useAppSelector } from "../../app/store";
|
import { useAppSelector } from "../../app/store";
|
||||||
import { Favorite } from "../../app/slices/favorites";
|
import { Favorite } from "../../app/slices/favorites";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import useFavMessage from "../../common/hook/useFavMessage";
|
||||||
|
|
||||||
type filter = "audio" | "video" | "image" | "";
|
type filter = "audio" | "video" | "image" | "";
|
||||||
function FavsPage() {
|
function FavsPage() {
|
||||||
const { t } = useTranslation("fav");
|
const { t } = useTranslation("fav");
|
||||||
const [filter, setFilter] = useState<filter>("");
|
const [filter, setFilter] = useState<filter>("");
|
||||||
const [favs, setFavs] = useState<Favorite[]>([]);
|
const [favs, setFavs] = useState<Favorite[]>([]);
|
||||||
|
const { removeFavorite } = useFavMessage({});
|
||||||
const Filters = [
|
const Filters = [
|
||||||
{
|
{
|
||||||
icon: <IconUnknown className="icon" />,
|
icon: <IconUnknown className="w-[15px] h-5" />,
|
||||||
title: t("all_items"),
|
title: t("all_items"),
|
||||||
filter: ""
|
filter: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconImage className="icon" />,
|
icon: <IconImage className="w-[15px] h-5" />,
|
||||||
title: t("image"),
|
title: t("image"),
|
||||||
filter: "image"
|
filter: "image"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconVideo className="icon" />,
|
icon: <IconVideo className="w-[15px] h-5" />,
|
||||||
title: t("video"),
|
title: t("video"),
|
||||||
filter: "video"
|
filter: "video"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconAudio className="icon" />,
|
icon: <IconAudio className="w-[15px] h-5" />,
|
||||||
title: t("audio"),
|
title: t("audio"),
|
||||||
filter: "audio"
|
filter: "audio"
|
||||||
}
|
}
|
||||||
@@ -109,26 +112,30 @@ function FavsPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [filter, favorites]);
|
}, [filter, favorites]);
|
||||||
|
const handleRemove = (evt: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
const { id = "" } = evt.currentTarget.dataset;
|
||||||
|
// console.log("remove fav", id);
|
||||||
|
removeFavorite(id);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Styled>
|
<div className="h-screen flex bg-white mt-2 mr-6 mb-2.5 overflow-auto">
|
||||||
<div className="left">
|
<div className="min-w-[268px] p-2 shadow-inner-[-1px_0px_0px_rgba(0,_0,_0,_0.1)]">
|
||||||
<ul className="filters">
|
<ul className="flex flex-col gap-0.5">
|
||||||
{Filters.map(({ icon, title, filter: f }) => {
|
{Filters.map(({ icon, title, filter: f }) => {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={f}
|
key={f}
|
||||||
className={`filter ${f == filter ? "active" : ""}`}
|
className={clsx(f == filter && 'bg-[rgba(116,_127,_141,_0.2)]', `cursor-pointer flex items-center gap-2 p-2 rounded-lg hover:bg-[rgba(116,_127,_141,_0.2)]`)}
|
||||||
onClick={handleFilter.bind(null, f as filter)}
|
onClick={handleFilter.bind(null, f as filter)}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
<span className="txt">{title}</span>
|
<span className="font-bold text-sm text-gray-600">{title}</span>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="right">
|
<div className="w-full p-4 flex flex-col overflow-y-scroll gap-8">
|
||||||
{favs.map(({ id, created_at, messages }) => {
|
{favs.map(({ id, created_at, messages }) => {
|
||||||
if (!messages || messages.length == 0) return null;
|
if (!messages || messages.length == 0) return null;
|
||||||
const [
|
const [
|
||||||
@@ -136,27 +143,28 @@ function FavsPage() {
|
|||||||
source: { gid, uid }
|
source: { gid, uid }
|
||||||
}
|
}
|
||||||
] = messages;
|
] = messages;
|
||||||
const tip = gid ? (
|
const tip = <span className={clsx("inline-flex items-center gap-1 mr-2")}>
|
||||||
<span className="from channel">
|
{gid ? <><IconChannel className="w-3 h-3" /> {channelData[gid]?.name}</> : <>
|
||||||
<IconChannel className="icon" /> {channelData[gid]?.name}
|
From <strong className="font-bold text-gray-800">{userData[uid]?.name}</strong>
|
||||||
</span>
|
</>}
|
||||||
) : (
|
</span>;
|
||||||
<span className="from user">
|
|
||||||
From <strong>{userData[uid]?.name}</strong>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<div className="container" key={id}>
|
<div className="max-w-[600px] flex flex-col gap-1" key={id}>
|
||||||
<h4 className="tip">
|
<h4 className="inline-flex items-center text-xs text-gray-400">
|
||||||
{tip}
|
{tip}
|
||||||
{dayjs(created_at).format("YYYY-MM-DD")}
|
{dayjs(created_at).format("YYYY-MM-DD")}
|
||||||
</h4>
|
</h4>
|
||||||
|
<div className="relative group">
|
||||||
<FavoredMessage key={id} id={id} />
|
<FavoredMessage key={id} id={id} />
|
||||||
|
<button className="absolute top-2 right-2 flex justify-center items-center w-6 h-6 p-1 border border-solid border-slate-200 rounded invisible group-hover:visible" data-id={id} onClick={handleRemove} >
|
||||||
|
<IconRemove className="fill-slate-900" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</Styled>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default FavsPage;
|
export default FavsPage;
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
import styled from "styled-components";
|
|
||||||
const Styled = styled.div`
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 8px 24px 10px 0;
|
|
||||||
border-radius: 16px;
|
|
||||||
overflow: auto;
|
|
||||||
.left {
|
|
||||||
padding: 8px;
|
|
||||||
min-width: 268px;
|
|
||||||
box-shadow: inset -1px 0px 0px rgba(0, 0, 0, 0.1);
|
|
||||||
.filters {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
.filter {
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: var(--br);
|
|
||||||
.icon {
|
|
||||||
width: 15px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
.txt {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 20px;
|
|
||||||
color: #667085;
|
|
||||||
}
|
|
||||||
&:hover,
|
|
||||||
&.active {
|
|
||||||
background: rgba(116, 127, 141, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.right {
|
|
||||||
width: 100%;
|
|
||||||
padding: 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 32px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
.container {
|
|
||||||
max-width: 600px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
> .favorite {
|
|
||||||
background: #f2f4f7;
|
|
||||||
}
|
|
||||||
.tip {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 18px;
|
|
||||||
color: #bfbfbf;
|
|
||||||
.from {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
margin-right: 8px;
|
|
||||||
&.channel,
|
|
||||||
&.user strong {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #344054;
|
|
||||||
.icon {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default Styled;
|
|
||||||
Reference in New Issue
Block a user