소스 검색

fix(editor): ui enhancements and code refactor (#1069)

Akki 1 년 전
부모
커밋
8b9f13b1ed

+ 1 - 1
frontend/src/components/CodeEditor.tsx

@@ -45,7 +45,7 @@ function CodeEditor(): JSX.Element {
               "w-full relative rounded-none bg-neutral-900 p-0 border-divider",
             cursor: "w-full bg-neutral-600 rounded-none",
             tab: "max-w-fit px-4 h-[36px]",
-            tabContent: "group-data-[selected=true]:text-neutral-50 ",
+            tabContent: "group-data-[selected=true]:text-white ",
           }}
           aria-label="Options"
         >

+ 84 - 66
frontend/src/components/Files.tsx

@@ -1,20 +1,25 @@
-import { Accordion, AccordionItem, Button } from "@nextui-org/react";
+import { Accordion, AccordionItem } from "@nextui-org/react";
 import React, { useEffect } from "react";
-import TreeView, { flattenTree } from "react-accessible-treeview";
+import TreeView, {
+  ITreeViewOnNodeSelectProps,
+  flattenTree,
+} from "react-accessible-treeview";
 import { AiOutlineFolder } from "react-icons/ai";
+
 import {
-  TbLayoutSidebarLeftCollapseFilled,
-  TbLayoutSidebarRightCollapseFilled,
-} from "react-icons/tb";
+  IoIosArrowDown,
+  IoIosArrowForward,
+  IoIosArrowBack,
+  IoIosRefresh,
+} from "react-icons/io";
 
-import { IoIosArrowDown } from "react-icons/io";
-import { VscRefresh } from "react-icons/vsc";
-import { useSelector } from "react-redux";
+import { useDispatch, useSelector } from "react-redux";
 import { getWorkspace, selectFile } from "../services/fileService";
 import { setCode, updateWorkspace } from "../state/codeSlice";
-import store, { RootState } from "../store";
+import { RootState } from "../store";
 import FileIcon from "./FileIcons";
 import FolderIcon from "./FolderIcon";
+import IconButton, { IconButtonProps } from "./IconButton";
 
 interface FilesProps {
   setSelectedFileName: React.Dispatch<React.SetStateAction<string>>;
@@ -22,11 +27,38 @@ interface FilesProps {
   explorerOpen: boolean;
 }
 
+function RefreshButton({
+  onClick,
+  ariaLabel,
+}: Omit<IconButtonProps, "icon">): React.ReactElement {
+  return (
+    <IconButton
+      icon={<IoIosRefresh size={20} />}
+      onClick={onClick}
+      ariaLabel={ariaLabel}
+    />
+  );
+}
+
+function CloseButton({
+  onClick,
+  ariaLabel,
+}: Omit<IconButtonProps, "icon">): React.ReactElement {
+  return (
+    <IconButton
+      icon={<IoIosArrowBack size={20} />}
+      onClick={onClick}
+      ariaLabel={ariaLabel}
+    />
+  );
+}
+
 function Files({
   setSelectedFileName,
   setExplorerOpen,
   explorerOpen,
 }: FilesProps): JSX.Element {
+  const dispatch = useDispatch();
   const workspaceFolder = useSelector(
     (state: RootState) => state.code.workspaceFolder,
   );
@@ -35,7 +67,7 @@ function Files({
   const workspaceTree = flattenTree(workspaceFolder);
 
   useEffect(() => {
-    getWorkspace().then((file) => store.dispatch(updateWorkspace(file)));
+    getWorkspace().then((file) => dispatch(updateWorkspace(file)));
   }, []);
 
   if (workspaceTree.length <= 1) {
@@ -46,9 +78,10 @@ function Files({
 
   if (!explorerOpen) {
     return (
-      <div className="h-full bg-neutral-800 border-neutral-600 items-center border-r-1 flex flex-col">
-        <div className="flex mt-2 p-1 justify-end">
-          <TbLayoutSidebarRightCollapseFilled
+      <div className="h-full min-w-[48px] bg-neutral-800 border-neutral-600 items-center border-r-1 flex flex-col transition-all ease-soft-spring">
+        <div className="flex mt-2 p-2 justify-end">
+          <IoIosArrowForward
+            size={20}
             className="cursor-pointer"
             onClick={() => setExplorerOpen(true)}
           />
@@ -56,8 +89,28 @@ function Files({
       </div>
     );
   }
+
+  const handleNodeSelect = (node: ITreeViewOnNodeSelectProps) => {
+    if (!node.isBranch) {
+      let fullPath = node.element.name;
+      setSelectedFileName(fullPath);
+      let currentNode = workspaceTree.find(
+        (file) => file.id === node.element.id,
+      );
+      while (currentNode !== undefined && currentNode.parent) {
+        currentNode = workspaceTree.find(
+          (file) => file.id === node.element.parent,
+        );
+        fullPath = `${currentNode?.name}/${fullPath}`;
+      }
+      selectFile(fullPath).then((code) => {
+        dispatch(setCode(code));
+      });
+    }
+  };
+
   return (
-    <div className="bg-neutral-800 h-full border-r-1 border-r-neutral-600 flex flex-col w-[250px]">
+    <div className="bg-neutral-800 min-w-[228px] h-full border-r-1 border-r-neutral-600 flex flex-col transition-all ease-soft-spring">
       <div className="flex p-2 items-center justify-between ">
         <Accordion className="px-0" defaultExpandedKeys={["1"]} isCompact>
           <AccordionItem
@@ -69,41 +122,23 @@ function Files({
             key="1"
             aria-label={workspaceFolder.name}
             title={
-              <div className="group flex items-center justify-between ">
-                <span className="text-neutral-400">{workspaceFolder.name}</span>
-                <div className="opacity-0 translate-y-[10px] transition-all ease-in-out  group-hover:opacity-100 transform group-hover:-translate-y-0 ">
-                  <Button
-                    type="button"
-                    style={{
-                      width: "24px",
-                      height: "24px",
-                    }}
-                    variant="flat"
+              <div className="group flex items-center justify-between">
+                <span className="text-neutral-400 text-sm">
+                  {workspaceFolder.name}
+                </span>
+                <div className="opacity-0 translate-y-[10px] transition-all ease-in-out group-hover:opacity-100 transform group-hover:-translate-y-0 flex h-[24px] items-center gap-1">
+                  <RefreshButton
                     onClick={() =>
                       getWorkspace().then((file) =>
-                        store.dispatch(updateWorkspace(file)),
+                        dispatch(updateWorkspace(file)),
                       )
                     }
-                    className="cursor-pointer text-[12px] bg-neutral-800"
-                    isIconOnly
-                    aria-label="Refresh"
-                  >
-                    <VscRefresh />
-                  </Button>
-                  <Button
-                    type="button"
-                    style={{
-                      width: "24px",
-                      height: "24px",
-                    }}
-                    variant="flat"
+                    ariaLabel="Refresh"
+                  />
+                  <CloseButton
                     onClick={() => setExplorerOpen(false)}
-                    className="cursor-pointer text-[12px]  bg-neutral-800"
-                    isIconOnly
-                    aria-label="Refresh"
-                  >
-                    <TbLayoutSidebarLeftCollapseFilled />
-                  </Button>
+                    ariaLabel="Close Explorer"
+                  />
                 </div>
               </div>
             }
@@ -115,30 +150,13 @@ function Files({
               </div>
             }
           >
-            <div className="w-full overflow-x-auto h-full py-2">
+            <div className="w-full overflow-x-auto h-full pt-[4px]">
               <TreeView
-                className="font-mono text-sm text-neutral-400"
+                className="text-sm text-neutral-400"
                 data={workspaceTree}
                 selectedIds={selectedIds}
                 expandedIds={workspaceTree.map((node) => node.id)}
-                onNodeSelect={(node) => {
-                  if (!node.isBranch) {
-                    let fullPath = node.element.name;
-                    setSelectedFileName(fullPath);
-                    let currentNode = workspaceTree.find(
-                      (file) => file.id === node.element.id,
-                    );
-                    while (currentNode !== undefined && currentNode.parent) {
-                      currentNode = workspaceTree.find(
-                        (file) => file.id === node.element.parent,
-                      );
-                      fullPath = `${currentNode!.name}/${fullPath}`;
-                    }
-                    selectFile(fullPath).then((code) => {
-                      store.dispatch(setCode(code));
-                    });
-                  }
-                }}
+                onNodeSelect={handleNodeSelect}
                 // eslint-disable-next-line react/no-unstable-nested-components
                 nodeRenderer={({
                   element,
@@ -151,9 +169,9 @@ function Files({
                     // eslint-disable-next-line react/jsx-props-no-spreading
                     {...getNodeProps()}
                     style={{ paddingLeft: 20 * (level - 1) }}
-                    className="cursor-pointer rounded-[5px] p-1 nowrap flex items-center gap-2 aria-selected:bg-neutral-600 aria-selected:text-neutral-50 hover:text-neutral-50"
+                    className="cursor-pointer rounded-[5px] p-1 nowrap flex items-center gap-2 aria-selected:bg-neutral-600 aria-selected:text-white hover:text-white"
                   >
-                    <div className="shrink-0 pl-5">
+                    <div className="shrink-0 pl-[48px]">
                       {isBranch ? (
                         <FolderIcon isOpen={isExpanded} />
                       ) : (

+ 28 - 0
frontend/src/components/IconButton.tsx

@@ -0,0 +1,28 @@
+import { Button } from "@nextui-org/react";
+import React, { MouseEventHandler, ReactElement } from "react";
+
+export interface IconButtonProps {
+  icon: ReactElement;
+  onClick: MouseEventHandler<HTMLButtonElement>;
+  ariaLabel: string;
+}
+
+function IconButton({
+  icon,
+  onClick,
+  ariaLabel,
+}: IconButtonProps): React.ReactElement {
+  return (
+    <Button
+      type="button"
+      variant="flat"
+      onClick={onClick}
+      className="cursor-pointer text-[12px] bg-transparent aspect-square px-0 min-w-[20px] h-[20px]"
+      aria-label={ariaLabel}
+    >
+      {icon}
+    </Button>
+  );
+}
+
+export default IconButton;

+ 2 - 2
frontend/src/components/Workspace.tsx

@@ -47,8 +47,8 @@ function Workspace() {
             base: "w-full",
             tabList:
               "w-full relative rounded-none bg-neutral-900 p-0 gap-0 h-[36px] flex",
-            tab: "rounded-none border-neutral-600 data-[selected=true]:bg-neutral-800",
-            tabContent: "group-data-[selected=true]:text-neutral-50",
+            tab: "rounded-none border-neutral-600 data-[selected=true]:bg-neutral-800 justify-start",
+            tabContent: "group-data-[selected=true]:text-white",
           }}
           size="lg"
           onSelectionChange={(v) => {

+ 1 - 1
frontend/src/index.css

@@ -14,7 +14,7 @@
 
 body {
   margin: 0;
-  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
+  font-family: -apple-system, "SF Pro", BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
     "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
     sans-serif;
   -webkit-font-smoothing: antialiased;