feat: close #380 collapse side bar
This commit is contained in:
parent
5185166e3b
commit
82ad0573be
|
@ -22,6 +22,7 @@ export function ChatItem(props: {
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
id: number;
|
id: number;
|
||||||
index: number;
|
index: number;
|
||||||
|
narrow?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Draggable draggableId={`${props.id}`} index={props.index}>
|
<Draggable draggableId={`${props.id}`} index={props.index}>
|
||||||
|
@ -35,13 +36,20 @@ export function ChatItem(props: {
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
{...provided.dragHandleProps}
|
{...provided.dragHandleProps}
|
||||||
>
|
>
|
||||||
<div className={styles["chat-item-title"]}>{props.title}</div>
|
{props.narrow ? (
|
||||||
<div className={styles["chat-item-info"]}>
|
<div className={styles["chat-item-narrow"]}>{props.count}</div>
|
||||||
<div className={styles["chat-item-count"]}>
|
) : (
|
||||||
{Locale.ChatItem.ChatItemCount(props.count)}
|
<>
|
||||||
</div>
|
<div className={styles["chat-item-title"]}>{props.title}</div>
|
||||||
<div className={styles["chat-item-date"]}>{props.time}</div>
|
<div className={styles["chat-item-info"]}>
|
||||||
</div>
|
<div className={styles["chat-item-count"]}>
|
||||||
|
{Locale.ChatItem.ChatItemCount(props.count)}
|
||||||
|
</div>
|
||||||
|
<div className={styles["chat-item-date"]}>{props.time}</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={styles["chat-item-delete"]} onClick={props.onDelete}>
|
<div className={styles["chat-item-delete"]} onClick={props.onDelete}>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,7 +59,7 @@ export function ChatItem(props: {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatList() {
|
export function ChatList(props: { narrow?: boolean }) {
|
||||||
const [sessions, selectedIndex, selectSession, removeSession, moveSession] =
|
const [sessions, selectedIndex, selectSession, removeSession, moveSession] =
|
||||||
useChatStore((state) => [
|
useChatStore((state) => [
|
||||||
state.sessions,
|
state.sessions,
|
||||||
|
@ -101,7 +109,12 @@ export function ChatList() {
|
||||||
navigate(Path.Chat);
|
navigate(Path.Chat);
|
||||||
selectSession(i);
|
selectSession(i);
|
||||||
}}
|
}}
|
||||||
onDelete={() => chatStore.deleteSession(i)}
|
onDelete={() => {
|
||||||
|
if (!props.narrow || confirm(Locale.Home.DeleteChat)) {
|
||||||
|
chatStore.deleteSession(i);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
narrow={props.narrow}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-shadow: inset -2px 0px 2px 0px rgb(0, 0, 0, 0.05);
|
box-shadow: inset -2px 0px 2px 0px rgb(0, 0, 0, 0.05);
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: width ease 0.1s;
|
transition: width ease 0.05s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-drag {
|
.sidebar-drag {
|
||||||
|
@ -126,11 +126,13 @@
|
||||||
.sidebar-title {
|
.sidebar-title {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
animation: slide-in ease 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-sub-title {
|
.sidebar-sub-title {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400px;
|
font-weight: 400px;
|
||||||
|
animation: slide-in ease 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-body {
|
.sidebar-body {
|
||||||
|
@ -171,6 +173,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
animation: slide-in ease 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-item-delete {
|
.chat-item-delete {
|
||||||
|
@ -197,6 +200,7 @@
|
||||||
color: rgb(166, 166, 166);
|
color: rgb(166, 166, 166);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
animation: slide-in ease 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-item-count,
|
.chat-item-count,
|
||||||
|
@ -206,6 +210,69 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.narrow-sidebar {
|
||||||
|
.sidebar-title,
|
||||||
|
.sidebar-sub-title {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.sidebar-logo {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item {
|
||||||
|
padding: 0;
|
||||||
|
min-height: 50px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
transition: all ease 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.chat-item-narrow {
|
||||||
|
transform: scale(0.7) translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item-narrow {
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 0;
|
||||||
|
font-weight: lighter;
|
||||||
|
color: var(--black);
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: all ease 0.3s;
|
||||||
|
opacity: 0.1;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item-delete {
|
||||||
|
top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item:hover > .chat-item-delete {
|
||||||
|
opacity: 0.5;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tail {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.sidebar-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.sidebar-action {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-tail {
|
.sidebar-tail {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
|
@ -41,7 +41,7 @@ const SideBar = dynamic(async () => (await import("./sidebar")).SideBar, {
|
||||||
loading: () => <Loading noLogo />,
|
loading: () => <Loading noLogo />,
|
||||||
});
|
});
|
||||||
|
|
||||||
function useSwitchTheme() {
|
export function useSwitchTheme() {
|
||||||
const config = useChatStore((state) => state.config);
|
const config = useChatStore((state) => state.config);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -83,7 +83,6 @@ const useHasHydrated = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
function WideScreen() {
|
function WideScreen() {
|
||||||
// setting
|
|
||||||
const config = useChatStore((state) => state.config);
|
const config = useChatStore((state) => state.config);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -92,9 +91,7 @@ function WideScreen() {
|
||||||
config.tightBorder ? styles["tight-container"] : styles.container
|
config.tightBorder ? styles["tight-container"] : styles.container
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className={styles.sidebar}>
|
<SideBar />
|
||||||
<SideBar></SideBar>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles["window-content"]}>
|
<div className={styles["window-content"]}>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
@ -113,9 +110,7 @@ function MobileScreen() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={`${styles.sidebar} ${isHome && styles["sidebar-show"]}`}>
|
<SideBar className={isHome ? styles["sidebar-show"] : ""} />
|
||||||
<SideBar />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles["window-content"]}>
|
<div className={styles["window-content"]}>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
@ -129,8 +124,8 @@ function MobileScreen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Home() {
|
export function Home() {
|
||||||
useSwitchTheme();
|
|
||||||
const isMobileScreen = useMobileScreen();
|
const isMobileScreen = useMobileScreen();
|
||||||
|
useSwitchTheme();
|
||||||
|
|
||||||
if (!useHasHydrated()) {
|
if (!useHasHydrated()) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
|
|
|
@ -12,14 +12,20 @@ import Locale from "../locales";
|
||||||
|
|
||||||
import { useChatStore } from "../store";
|
import { useChatStore } from "../store";
|
||||||
|
|
||||||
import { Path, REPO_URL } from "../constant";
|
import {
|
||||||
|
MAX_SIDEBAR_WIDTH,
|
||||||
|
MIN_SIDEBAR_WIDTH,
|
||||||
|
NARROW_SIDEBAR_WIDTH,
|
||||||
|
Path,
|
||||||
|
REPO_URL,
|
||||||
|
} from "../constant";
|
||||||
|
|
||||||
import { HashRouter as Router, Link, useNavigate } from "react-router-dom";
|
import { HashRouter as Router, Link, useNavigate } from "react-router-dom";
|
||||||
import { useMobileScreen } from "../utils";
|
import { useMobileScreen } from "../utils";
|
||||||
import { ChatList } from "./chat-list";
|
import { ChatList } from "./chat-list";
|
||||||
|
|
||||||
function useDragSideBar() {
|
function useDragSideBar() {
|
||||||
const limit = (x: number) => Math.min(500, Math.max(220, x));
|
const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
|
||||||
|
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
const startX = useRef(0);
|
const startX = useRef(0);
|
||||||
|
@ -27,7 +33,7 @@ function useDragSideBar() {
|
||||||
const lastUpdateTime = useRef(Date.now());
|
const lastUpdateTime = useRef(Date.now());
|
||||||
|
|
||||||
const handleMouseMove = useRef((e: MouseEvent) => {
|
const handleMouseMove = useRef((e: MouseEvent) => {
|
||||||
if (Date.now() < lastUpdateTime.current + 100) {
|
if (Date.now() < lastUpdateTime.current + 50) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastUpdateTime.current = Date.now();
|
lastUpdateTime.current = Date.now();
|
||||||
|
@ -49,29 +55,36 @@ function useDragSideBar() {
|
||||||
window.addEventListener("mouseup", handleMouseUp.current);
|
window.addEventListener("mouseup", handleMouseUp.current);
|
||||||
};
|
};
|
||||||
const isMobileScreen = useMobileScreen();
|
const isMobileScreen = useMobileScreen();
|
||||||
|
const shouldNarrow =
|
||||||
|
!isMobileScreen && chatStore.config.sidebarWidth < MIN_SIDEBAR_WIDTH;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sideBarWidth = isMobileScreen
|
const barWidth = shouldNarrow
|
||||||
? "100vw"
|
? NARROW_SIDEBAR_WIDTH
|
||||||
: `${limit(chatStore.config.sidebarWidth ?? 300)}px`;
|
: limit(chatStore.config.sidebarWidth ?? 300);
|
||||||
|
const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`;
|
||||||
document.documentElement.style.setProperty("--sidebar-width", sideBarWidth);
|
document.documentElement.style.setProperty("--sidebar-width", sideBarWidth);
|
||||||
}, [chatStore.config.sidebarWidth, isMobileScreen]);
|
}, [chatStore.config.sidebarWidth, isMobileScreen, shouldNarrow]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onDragMouseDown,
|
onDragMouseDown,
|
||||||
|
shouldNarrow,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) {
|
export function SideBar(props: { className?: string }) {
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
|
|
||||||
// drag side bar
|
// drag side bar
|
||||||
const { onDragMouseDown } = useDragSideBar();
|
const { onDragMouseDown, shouldNarrow } = useDragSideBar();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isMobileScreen = useMobileScreen();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div
|
||||||
|
className={`${styles.sidebar} ${props.className} ${
|
||||||
|
shouldNarrow && styles["narrow-sidebar"]
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<div className={styles["sidebar-header"]}>
|
<div className={styles["sidebar-header"]}>
|
||||||
<div className={styles["sidebar-title"]}>ChatGPT Next</div>
|
<div className={styles["sidebar-title"]}>ChatGPT Next</div>
|
||||||
<div className={styles["sidebar-sub-title"]}>
|
<div className={styles["sidebar-sub-title"]}>
|
||||||
|
@ -88,10 +101,9 @@ export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) {
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
navigate(Path.Home);
|
navigate(Path.Home);
|
||||||
}
|
}
|
||||||
props.setShowSideBar?.(false);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ChatList />
|
<ChatList narrow={shouldNarrow} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles["sidebar-tail"]}>
|
<div className={styles["sidebar-tail"]}>
|
||||||
|
@ -116,10 +128,9 @@ export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) {
|
||||||
<div>
|
<div>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<AddIcon />}
|
icon={<AddIcon />}
|
||||||
text={Locale.Home.NewChat}
|
text={shouldNarrow ? undefined : Locale.Home.NewChat}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
chatStore.newSession();
|
chatStore.newSession();
|
||||||
props.setShowSideBar?.(false);
|
|
||||||
}}
|
}}
|
||||||
shadow
|
shadow
|
||||||
/>
|
/>
|
||||||
|
@ -130,6 +141,6 @@ export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) {
|
||||||
className={styles["sidebar-drag"]}
|
className={styles["sidebar-drag"]}
|
||||||
onMouseDown={(e) => onDragMouseDown(e as any)}
|
onMouseDown={(e) => onDragMouseDown(e as any)}
|
||||||
></div>
|
></div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,3 +12,7 @@ export enum Path {
|
||||||
Chat = "/chat",
|
Chat = "/chat",
|
||||||
Settings = "/settings",
|
Settings = "/settings",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MAX_SIDEBAR_WIDTH = 500;
|
||||||
|
export const MIN_SIDEBAR_WIDTH = 230;
|
||||||
|
export const NARROW_SIDEBAR_WIDTH = 100;
|
||||||
|
|
Loading…
Reference in New Issue