chore: more tailwind
This commit is contained in:
+1
-4
@@ -123,8 +123,5 @@ export const KEY_AFTER_MID = "VOCECHAT_AFTER_MID";
|
||||
export const KEY_PWA_INSTALLED = "VOCECHAT_PWA_INSTALLED";
|
||||
export const KEY_LOCAL_MAGIC_TOKEN = "VOCECHAT_LOCAL_MAGIC_TOKEN";
|
||||
export const Emojis = ["👍", "❤️", "😄", "👀", "👎", "🎉", "🙁", "🚀"];
|
||||
export const Views = {
|
||||
item: "item",
|
||||
grid: "grid"
|
||||
};
|
||||
|
||||
export default BASE_URL;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { Views } from "../config";
|
||||
|
||||
export type ListView = "item" | "grid"
|
||||
export interface State {
|
||||
online: boolean;
|
||||
ready: boolean;
|
||||
inputMode: "text";
|
||||
menuExpand: boolean;
|
||||
// todo
|
||||
fileListView: string;
|
||||
fileListView: ListView;
|
||||
uploadFiles: {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
@@ -30,7 +30,7 @@ const initialState: State = {
|
||||
ready: false,
|
||||
inputMode: "text",
|
||||
menuExpand: false,
|
||||
fileListView: Views.grid,
|
||||
fileListView: "grid",
|
||||
uploadFiles: {},
|
||||
selectMessages: {},
|
||||
draftMarkdown: {},
|
||||
|
||||
@@ -86,9 +86,8 @@ const FileBox: FC<Props> = ({
|
||||
const withPreview = preview && previewContent;
|
||||
return (
|
||||
<Styled
|
||||
className={`file_box ${flex ? "flex" : ""} ${withPreview ? "preview" : ""} ${
|
||||
file_type.startsWith("audio") ? "audio" : ""
|
||||
}`}
|
||||
className={`file_box ${flex ? "flex" : ""} ${withPreview ? "preview" : ""} ${file_type.startsWith("audio") ? "audio" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="basic">
|
||||
{icon}
|
||||
@@ -103,7 +102,7 @@ const FileBox: FC<Props> = ({
|
||||
</span>
|
||||
</div>
|
||||
<a className="download" download={name} href={`${content}&download=true`}>
|
||||
<IconDownload />
|
||||
<IconDownload className="fill-gray-500" />
|
||||
</a>
|
||||
</div>
|
||||
{withPreview && <div className="preview">{previewContent}</div>}
|
||||
|
||||
@@ -22,6 +22,7 @@ const Styled = styled.div`
|
||||
}
|
||||
}
|
||||
.basic {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState, useEffect, FC, MouseEvent, ChangeEvent } from "react";
|
||||
import styled from "styled-components";
|
||||
import toast from "react-hot-toast";
|
||||
import Button from "../styled/Button";
|
||||
import Input from "../styled/Input";
|
||||
@@ -10,88 +9,6 @@ import CloseIcon from "../../../assets/icons/close.svg";
|
||||
import useFilteredUsers from "../../hook/useFilteredUsers";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
|
||||
const Styled = styled.div`
|
||||
padding-top: 16px;
|
||||
> .filter {
|
||||
width: 376px;
|
||||
min-height: 40px;
|
||||
padding: 6px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 1px 2px rgba(31, 41, 55, 0.08);
|
||||
border-radius: 4px;
|
||||
.selects {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
overflow: scroll;
|
||||
&::-webkit-scrollbar {
|
||||
width: 0; /* Remove scrollbar space */
|
||||
height: 0; /* Remove scrollbar space */
|
||||
background: transparent; /* Optional: just make scrollbar invisible */
|
||||
}
|
||||
.select {
|
||||
padding: 4px 6px;
|
||||
background: #52edff;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
.close {
|
||||
cursor: pointer;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
path {
|
||||
fill: #fff;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.input {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
.users {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 20px;
|
||||
max-height: 364px;
|
||||
overflow: scroll;
|
||||
.user {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
width: -webkit-fill-available;
|
||||
border-radius: 8px;
|
||||
&:hover {
|
||||
background: rgba(116, 127, 141, 0.1);
|
||||
}
|
||||
> div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .btn {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
cid?: number;
|
||||
closeModal: () => void;
|
||||
@@ -138,34 +55,34 @@ const AddMembers: FC<Props> = ({ cid = 0, closeModal }) => {
|
||||
const userIds = users.map((u) => u?.uid || 0);
|
||||
|
||||
return (
|
||||
<Styled>
|
||||
<div className="filter">
|
||||
<ul className="selects">
|
||||
<div className="pt-4">
|
||||
<div className="flex items-center w-[376px] min-h-[40px] px-2 py-1.5 mb-3 border border-solid border-slate-100 shadow rounded">
|
||||
<ul className="flex items-center flex-wrap gap-1 w-full overflow-scroll">
|
||||
{selects.map((uid) => {
|
||||
return (
|
||||
<li className="select" key={uid}>
|
||||
<li className="px-1.5 py-1 rounded text-sm bg-primary-300 text-white flex items-center justify-between gap-1" key={uid}>
|
||||
{userData[uid]?.name}
|
||||
<CloseIcon data-uid={uid} onClick={toggleCheckMember} className="close" />
|
||||
<CloseIcon data-uid={uid} onClick={toggleCheckMember} className="cursor-pointer w-3 h-3 fill-white" />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
<Input
|
||||
autoFocus
|
||||
type="text"
|
||||
className="input none"
|
||||
className="!w-fit none"
|
||||
value={input}
|
||||
onChange={handleFilterInput}
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
<ul className="users">
|
||||
<ul className="flex flex-col pb-5 max-h-[364px] overflow-scroll">
|
||||
{userIds.map((uid) => {
|
||||
const added = uids.includes(uid);
|
||||
return (
|
||||
<li
|
||||
key={uid}
|
||||
data-uid={uid}
|
||||
className="user"
|
||||
className="cursor-pointer flex items-center px-2 py-1 rounded-lg hover:bg-slate-400/20"
|
||||
onClick={added ? undefined : toggleCheckMember}
|
||||
>
|
||||
<StyledCheckbox
|
||||
@@ -180,10 +97,10 @@ const AddMembers: FC<Props> = ({ cid = 0, closeModal }) => {
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<Button disabled={selects.length == 0 || isAdding} className="btn" onClick={handleAddMembers}>
|
||||
<Button disabled={selects.length == 0 || isAdding} className="flex mt-4 justify-center" onClick={handleAddMembers}>
|
||||
{isAdding ? `Adding` : "Add"} to #{channel.name}
|
||||
</Button>
|
||||
</Styled>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ const StyledWrapper = styled.div`
|
||||
const StyledInput = styled.input`
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import React from "react";
|
||||
import { RiAddFill } from "react-icons/ri";
|
||||
|
||||
import styled from "styled-components";
|
||||
import { IoLogoGithub } from "react-icons/io5";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
padding: 0 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
> hr {
|
||||
border: none;
|
||||
width: 40%;
|
||||
height: 1px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.tools {
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
.tool,
|
||||
.add {
|
||||
cursor: pointer;
|
||||
gap: 9px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #4b5563;
|
||||
}
|
||||
.tool {
|
||||
padding: 5px 4px;
|
||||
.logo {
|
||||
border-radius: 5.5px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
white-space: nowrap;
|
||||
}
|
||||
&.add .logo {
|
||||
background: none;
|
||||
.icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
type Props = {
|
||||
expand?: boolean;
|
||||
};
|
||||
const Tools: React.FC<Props> = ({ expand = true }) => {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<hr />
|
||||
<ul className="tools">
|
||||
<li className="tool">
|
||||
<div className="logo">
|
||||
<img
|
||||
className="icon"
|
||||
src="https://static.nicegoodthings.com/project/ext/webrowse.logo.png"
|
||||
alt="logo"
|
||||
/>
|
||||
</div>
|
||||
{expand && <h2 className="title animate__animated animate__fadeIn">Webrowse</h2>}
|
||||
</li>
|
||||
<li className="tool">
|
||||
<div className="logo">
|
||||
<IoLogoGithub size={40} className="icon" />
|
||||
</div>
|
||||
{expand && <h2 className="title animate__animated animate__fadeIn">GitHub</h2>}
|
||||
</li>
|
||||
<li className="tool add">
|
||||
<div className="logo">
|
||||
<RiAddFill className="icon" size={40} color="#4B5563" />
|
||||
</div>
|
||||
{expand && <h2 className="title animate__animated animate__fadeIn">Add new app</h2>}
|
||||
</li>
|
||||
</ul>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default Tools;
|
||||
@@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import StyledWrapper from "./styled";
|
||||
import User from "./User";
|
||||
// import Tools from "./Tools";
|
||||
import Loading from "../../common/component/Loading";
|
||||
import Menu from "./Menu";
|
||||
import usePreload from "../../common/hook/usePreload";
|
||||
|
||||
@@ -1,31 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import { ChangeEvent, FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import iconSearch from "../../assets/icons/search.svg?url";
|
||||
|
||||
const Styled = styled.div`
|
||||
width: 100%;
|
||||
padding: 6px 16px;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
|
||||
&.embed {
|
||||
padding: 6px 8px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.search {
|
||||
outline: none;
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
border-radius: 25px;
|
||||
padding: 10px 8px 10px 36px;
|
||||
color: #a1a1aa;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
background-image: url(${iconSearch});
|
||||
background-repeat: no-repeat;
|
||||
background-position: 8px center;
|
||||
}
|
||||
`;
|
||||
|
||||
import IconSearch from "../../assets/icons/search.svg";
|
||||
interface Props {
|
||||
value?: string;
|
||||
updateSearchValue?: (value: string) => void;
|
||||
@@ -41,9 +17,10 @@ const Search: FC<Props> = ({ value = "", updateSearchValue = null, embed = false
|
||||
};
|
||||
|
||||
return (
|
||||
<Styled className={embed ? "embed" : ""}>
|
||||
<input value={value} onChange={handleChange} className="search" placeholder={`${t("action.search")}...`} />
|
||||
</Styled>
|
||||
<div className={clsx(`relative w-full py-1.5 px-4 shadow`, embed && "py-2 shadow-none")}>
|
||||
<IconSearch className="absolute left-6 top-1/2 -translate-y-1/2" />
|
||||
<input value={value} onChange={handleChange} className="bg-black/5 rounded-full text-sm text-gray-500 py-2.5 pl-9" placeholder={`${t("action.search")}...`} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { MouseEvent } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { updateFileListView } from "../../app/slices/ui";
|
||||
import { Views } from "../../app/config";
|
||||
import IconList from "../../assets/icons/file.list.svg";
|
||||
import IconGrid from "../../assets/icons/file.grid.svg";
|
||||
|
||||
@@ -35,20 +34,20 @@ const Styled = styled.ul`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function View({ view = Views.item }) {
|
||||
export default function View({ view = "item" }) {
|
||||
const dispatch = useDispatch();
|
||||
const handleChangeView = (evt: MouseEvent<HTMLLIElement>) => {
|
||||
const { view: clickView } = evt.currentTarget.dataset;
|
||||
if (clickView == view) return;
|
||||
dispatch(updateFileListView(view == Views.item ? Views.grid : Views.item));
|
||||
dispatch(updateFileListView(view == "item" ? "grid" : "item"));
|
||||
};
|
||||
|
||||
return (
|
||||
<Styled className={view}>
|
||||
<li className="view item" data-view={Views.item} onClick={handleChangeView}>
|
||||
<li className="view item" data-view={"item"} onClick={handleChangeView}>
|
||||
<IconList />
|
||||
</li>
|
||||
<li className="view grid" data-view={Views.grid} onClick={handleChangeView}>
|
||||
<li className="view grid" data-view={"grid"} onClick={handleChangeView}>
|
||||
<IconGrid />
|
||||
</li>
|
||||
</Styled>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// @ts-nocheck
|
||||
import { useState, useEffect, useRef, memo } from "react";
|
||||
import Masonry from "masonry-layout";
|
||||
import Styled from "./styled";
|
||||
import { Views } from "../../app/config";
|
||||
import View from "./View";
|
||||
import Search from "./Search";
|
||||
import Filter from "./Filter";
|
||||
import FileBox from "../../common/component/FileBox";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
import clsx from "clsx";
|
||||
const checkFilter = (data, filter, channelMessage) => {
|
||||
let selected = true;
|
||||
const { mid, file_type, created_at, from_uid, properties } = data;
|
||||
@@ -60,7 +59,7 @@ function ResourceManagement({ fileMessages }) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (view == Views.grid && listContainerRef) {
|
||||
if (view == "grid" && listContainerRef) {
|
||||
const container = listContainerRef.current;
|
||||
if (!container) return;
|
||||
const cWidth = container.getBoundingClientRect().width - 16 * 2;
|
||||
@@ -72,7 +71,7 @@ function ResourceManagement({ fileMessages }) {
|
||||
// options
|
||||
fitWidth: true,
|
||||
gutter,
|
||||
itemSelector: ".file_box"
|
||||
itemSelector: ".grid-box"
|
||||
// columnWidth: 200
|
||||
});
|
||||
} else {
|
||||
@@ -83,14 +82,17 @@ function ResourceManagement({ fileMessages }) {
|
||||
}, [view, filter]);
|
||||
|
||||
return (
|
||||
<Styled>
|
||||
<div className="h-screen overflow-y-scroll flex flex-col items-start my-2 mr-6 rounded-2xl bg-white">
|
||||
<Search value={filter.name} updateSearchValue={handleUpdateSearch} />
|
||||
<div className="divider"></div>
|
||||
<div className="opts">
|
||||
<div className="flex justify-between w-full px-4 py-5">
|
||||
<Filter filter={filter} updateFilter={updateFilter} />
|
||||
<View view={view} />
|
||||
</div>
|
||||
<div className={`list ${view}`} ref={listContainerRef}>
|
||||
<div className={clsx(`w-full h-full px-4 overflow-scroll flex`,
|
||||
view == "item" && 'gap-2 flex-col',
|
||||
view == "grid" && "flex-wrap"
|
||||
)} ref={listContainerRef}>
|
||||
{fileMessages.map((id) => {
|
||||
const data = message[id];
|
||||
if (!data) return null;
|
||||
@@ -99,21 +101,23 @@ function ResourceManagement({ fileMessages }) {
|
||||
const { mid, content, created_at, from_uid, properties } = data;
|
||||
const { name, content_type, size } = properties ?? {};
|
||||
return (
|
||||
<FileBox
|
||||
preview={view == Views.grid}
|
||||
flex={view == Views.item}
|
||||
key={mid}
|
||||
file_type={content_type}
|
||||
content={content}
|
||||
created_at={created_at}
|
||||
from_uid={from_uid}
|
||||
size={size}
|
||||
name={name}
|
||||
/>
|
||||
<div key={mid} className="grid-box mb-2">
|
||||
<FileBox
|
||||
preview={view == "grid"}
|
||||
flex={view == "item"}
|
||||
key={mid}
|
||||
file_type={content_type}
|
||||
content={content}
|
||||
created_at={created_at}
|
||||
from_uid={from_uid}
|
||||
size={size}
|
||||
name={name}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Styled>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user