Browse Source

feat: close #380 collapse side bar

Yidadaa 2 years ago
parent
commit
82ad0573be
5 changed files with 125 additions and 35 deletions
  1. 22 9
      app/components/chat-list.tsx
  2. 68 1
      app/components/home.module.scss
  3. 4 9
      app/components/home.tsx
  4. 27 16
      app/components/sidebar.tsx
  5. 4 0
      app/constant.ts

+ 22 - 9
app/components/chat-list.tsx

@@ -22,6 +22,7 @@ export function ChatItem(props: {
   selected: boolean;
   id: number;
   index: number;
+  narrow?: boolean;
 }) {
   return (
     <Draggable draggableId={`${props.id}`} index={props.index}>
@@ -35,13 +36,20 @@ export function ChatItem(props: {
           {...provided.draggableProps}
           {...provided.dragHandleProps}
         >
-          <div className={styles["chat-item-title"]}>{props.title}</div>
-          <div className={styles["chat-item-info"]}>
-            <div className={styles["chat-item-count"]}>
-              {Locale.ChatItem.ChatItemCount(props.count)}
-            </div>
-            <div className={styles["chat-item-date"]}>{props.time}</div>
-          </div>
+          {props.narrow ? (
+            <div className={styles["chat-item-narrow"]}>{props.count}</div>
+          ) : (
+            <>
+              <div className={styles["chat-item-title"]}>{props.title}</div>
+              <div className={styles["chat-item-info"]}>
+                <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}>
             <DeleteIcon />
           </div>
@@ -51,7 +59,7 @@ export function ChatItem(props: {
   );
 }
 
-export function ChatList() {
+export function ChatList(props: { narrow?: boolean }) {
   const [sessions, selectedIndex, selectSession, removeSession, moveSession] =
     useChatStore((state) => [
       state.sessions,
@@ -101,7 +109,12 @@ export function ChatList() {
                   navigate(Path.Chat);
                   selectSession(i);
                 }}
-                onDelete={() => chatStore.deleteSession(i)}
+                onDelete={() => {
+                  if (!props.narrow || confirm(Locale.Home.DeleteChat)) {
+                    chatStore.deleteSession(i);
+                  }
+                }}
+                narrow={props.narrow}
               />
             ))}
             {provided.placeholder}

+ 68 - 1
app/components/home.module.scss

@@ -50,7 +50,7 @@
   flex-direction: column;
   box-shadow: inset -2px 0px 2px 0px rgb(0, 0, 0, 0.05);
   position: relative;
-  transition: width ease 0.1s;
+  transition: width ease 0.05s;
 }
 
 .sidebar-drag {
@@ -126,11 +126,13 @@
 .sidebar-title {
   font-size: 20px;
   font-weight: bold;
+  animation: slide-in ease 0.3s;
 }
 
 .sidebar-sub-title {
   font-size: 12px;
   font-weight: 400px;
+  animation: slide-in ease 0.3s;
 }
 
 .sidebar-body {
@@ -171,6 +173,7 @@
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
+  animation: slide-in ease 0.3s;
 }
 
 .chat-item-delete {
@@ -197,6 +200,7 @@
   color: rgb(166, 166, 166);
   font-size: 12px;
   margin-top: 8px;
+  animation: slide-in ease 0.3s;
 }
 
 .chat-item-count,
@@ -206,6 +210,69 @@
   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 {
   display: flex;
   justify-content: space-between;

+ 4 - 9
app/components/home.tsx

@@ -41,7 +41,7 @@ const SideBar = dynamic(async () => (await import("./sidebar")).SideBar, {
   loading: () => <Loading noLogo />,
 });
 
-function useSwitchTheme() {
+export function useSwitchTheme() {
   const config = useChatStore((state) => state.config);
 
   useEffect(() => {
@@ -83,7 +83,6 @@ const useHasHydrated = () => {
 };
 
 function WideScreen() {
-  // setting
   const config = useChatStore((state) => state.config);
 
   return (
@@ -92,9 +91,7 @@ function WideScreen() {
         config.tightBorder ? styles["tight-container"] : styles.container
       }`}
     >
-      <div className={styles.sidebar}>
-        <SideBar></SideBar>
-      </div>
+      <SideBar />
 
       <div className={styles["window-content"]}>
         <Routes>
@@ -113,9 +110,7 @@ function MobileScreen() {
 
   return (
     <div className={styles.container}>
-      <div className={`${styles.sidebar} ${isHome && styles["sidebar-show"]}`}>
-        <SideBar />
-      </div>
+      <SideBar className={isHome ? styles["sidebar-show"] : ""} />
 
       <div className={styles["window-content"]}>
         <Routes>
@@ -129,8 +124,8 @@ function MobileScreen() {
 }
 
 export function Home() {
-  useSwitchTheme();
   const isMobileScreen = useMobileScreen();
+  useSwitchTheme();
 
   if (!useHasHydrated()) {
     return <Loading />;

+ 27 - 16
app/components/sidebar.tsx

@@ -12,14 +12,20 @@ import Locale from "../locales";
 
 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 { useMobileScreen } from "../utils";
 import { ChatList } from "./chat-list";
 
 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 startX = useRef(0);
@@ -27,7 +33,7 @@ function useDragSideBar() {
   const lastUpdateTime = useRef(Date.now());
 
   const handleMouseMove = useRef((e: MouseEvent) => {
-    if (Date.now() < lastUpdateTime.current + 100) {
+    if (Date.now() < lastUpdateTime.current + 50) {
       return;
     }
     lastUpdateTime.current = Date.now();
@@ -49,29 +55,36 @@ function useDragSideBar() {
     window.addEventListener("mouseup", handleMouseUp.current);
   };
   const isMobileScreen = useMobileScreen();
+  const shouldNarrow =
+    !isMobileScreen && chatStore.config.sidebarWidth < MIN_SIDEBAR_WIDTH;
 
   useEffect(() => {
-    const sideBarWidth = isMobileScreen
-      ? "100vw"
-      : `${limit(chatStore.config.sidebarWidth ?? 300)}px`;
+    const barWidth = shouldNarrow
+      ? NARROW_SIDEBAR_WIDTH
+      : limit(chatStore.config.sidebarWidth ?? 300);
+    const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`;
     document.documentElement.style.setProperty("--sidebar-width", sideBarWidth);
-  }, [chatStore.config.sidebarWidth, isMobileScreen]);
+  }, [chatStore.config.sidebarWidth, isMobileScreen, shouldNarrow]);
 
   return {
     onDragMouseDown,
+    shouldNarrow,
   };
 }
 
-export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) {
+export function SideBar(props: { className?: string }) {
   const chatStore = useChatStore();
 
   // drag side bar
-  const { onDragMouseDown } = useDragSideBar();
+  const { onDragMouseDown, shouldNarrow } = useDragSideBar();
   const navigate = useNavigate();
-  const isMobileScreen = useMobileScreen();
 
   return (
-    <>
+    <div
+      className={`${styles.sidebar} ${props.className} ${
+        shouldNarrow && styles["narrow-sidebar"]
+      }`}
+    >
       <div className={styles["sidebar-header"]}>
         <div className={styles["sidebar-title"]}>ChatGPT Next</div>
         <div className={styles["sidebar-sub-title"]}>
@@ -88,10 +101,9 @@ export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) {
           if (e.target === e.currentTarget) {
             navigate(Path.Home);
           }
-          props.setShowSideBar?.(false);
         }}
       >
-        <ChatList />
+        <ChatList narrow={shouldNarrow} />
       </div>
 
       <div className={styles["sidebar-tail"]}>
@@ -116,10 +128,9 @@ export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) {
         <div>
           <IconButton
             icon={<AddIcon />}
-            text={Locale.Home.NewChat}
+            text={shouldNarrow ? undefined : Locale.Home.NewChat}
             onClick={() => {
               chatStore.newSession();
-              props.setShowSideBar?.(false);
             }}
             shadow
           />
@@ -130,6 +141,6 @@ export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) {
         className={styles["sidebar-drag"]}
         onMouseDown={(e) => onDragMouseDown(e as any)}
       ></div>
-    </>
+    </div>
   );
 }

+ 4 - 0
app/constant.ts

@@ -12,3 +12,7 @@ export enum Path {
   Chat = "/chat",
   Settings = "/settings",
 }
+
+export const MAX_SIDEBAR_WIDTH = 500;
+export const MIN_SIDEBAR_WIDTH = 230;
+export const NARROW_SIDEBAR_WIDTH = 100;