Przeglądaj źródła

feat: app sidebar support collapse (#1997)

zxhlyh 1 rok temu
rodzic
commit
1372bf784f

+ 2 - 2
web/app/components/app-sidebar/basic.tsx

@@ -15,7 +15,7 @@ export type IAppBasicProps = {
   hoverTip?: string
   textStyle?: { main?: string; extra?: string }
   isExtraInLine?: boolean
-  mode?: 'expand' | 'collapse'
+  mode?: string
 }
 
 const ApiSvg = <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -56,7 +56,7 @@ const ICON_MAP = {
   notion: <AppIcon innerIcon={NotionSvg} className='!border-[0.5px] !border-indigo-100 !bg-white' />,
 }
 
-export default function AppBasic({ icon, icon_background, name, type, hoverTip, textStyle, mode = 'expand', iconType = 'app', isExtraInLine }: IAppBasicProps) {
+export default function AppBasic({ icon, icon_background, name, type, hoverTip, textStyle, mode = 'expand', iconType = 'app' }: IAppBasicProps) {
   return (
     <div className="flex items-start">
       {icon && icon_background && iconType === 'app' && (

+ 64 - 6
web/app/components/app-sidebar/index.tsx

@@ -1,8 +1,12 @@
-import React from 'react'
+import React, { useCallback, useState } from 'react'
 import NavLink from './navLink'
 import type { NavIcon } from './navLink'
 import AppBasic from './basic'
 import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
+import {
+  AlignLeft01,
+  AlignRight01,
+} from '@/app/components/base/icons/src/vender/line/layout'
 
 export type IAppDetailNavProps = {
   iconType?: 'app' | 'dataset' | 'notion'
@@ -20,23 +24,77 @@ export type IAppDetailNavProps = {
 }
 
 const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInfo, iconType = 'app' }: IAppDetailNavProps) => {
+  const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
   const media = useBreakpoints()
   const isMobile = media === MediaType.mobile
   const mode = isMobile ? 'collapse' : 'expand'
+  const [modeState, setModeState] = useState(isMobile ? mode : localeMode)
+  const expand = modeState === 'expand'
+
+  const handleToggle = useCallback(() => {
+    setModeState((prev) => {
+      const next = prev === 'expand' ? 'collapse' : 'expand'
+      localStorage.setItem('app-detail-collapse-or-expand', next)
+      return next
+    })
+  }, [])
 
   return (
-    <div className="flex flex-col sm:w-56 w-16 overflow-y-auto bg-white border-r border-gray-200 shrink-0 mobile:h-screen">
-      <div className="flex flex-shrink-0 p-4">
-        <AppBasic mode={mode} iconType={iconType} icon={icon} icon_background={icon_background} name={title} type={desc} />
+    <div
+      className={`
+        shrink-0 flex flex-col bg-white border-r border-gray-200 transition-all
+        ${expand ? 'w-[216px]' : 'w-14'}
+      `}
+    >
+      <div
+        className={`
+          shrink-0
+          ${expand ? 'p-4' : 'p-2'}
+        `}
+      >
+        <AppBasic
+          mode={modeState}
+          iconType={iconType}
+          icon={icon}
+          icon_background={icon_background}
+          name={title}
+          type={desc}
+        />
       </div>
-      <nav className="flex-1 p-4 space-y-1 bg-white">
+      <nav
+        className={`
+          grow space-y-1 bg-white
+          ${expand ? 'p-4' : 'px-2.5 py-4'}
+        `}
+      >
         {navigation.map((item, index) => {
           return (
-            <NavLink key={index} mode={mode} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} />
+            <NavLink key={index} mode={modeState} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} />
           )
         })}
         {extraInfo ?? null}
       </nav>
+      {
+        !isMobile && (
+          <div
+            className={`
+              shrink-0 py-3
+              ${expand ? 'px-6' : 'px-4'}
+            `}
+          >
+            <div
+              className='flex items-center justify-center w-6 h-6 text-gray-500 cursor-pointer'
+              onClick={handleToggle}
+            >
+              {
+                expand
+                  ? <AlignLeft01 className='w-[14px] h-[14px]' />
+                  : <AlignRight01 className='w-[14px] h-[14px]' />
+              }
+            </div>
+          </div>
+        )
+      }
     </div>
   )
 }

+ 5 - 3
web/app/components/app-sidebar/navLink.tsx

@@ -18,7 +18,7 @@ export type NavLinkProps = {
     selected: NavIcon
     normal: NavIcon
   }
-  mode?: 'expand' | 'collapse'
+  mode?: string
 }
 
 export default function NavLink({
@@ -45,13 +45,15 @@ export default function NavLink({
       href={href}
       className={classNames(
         isActive ? 'bg-primary-50 text-primary-600 font-semibold' : 'text-gray-700 hover:bg-gray-100 hover:text-gray-700',
-        'group flex items-center rounded-md px-2 py-2 text-sm font-normal',
+        'group flex items-center h-9 rounded-md py-2 text-sm font-normal',
+        mode === 'expand' ? 'px-3' : 'px-2.5',
       )}
     >
       <NavIcon
         className={classNames(
-          'mr-2 h-4 w-4 flex-shrink-0',
+          'h-4 w-4 flex-shrink-0',
           isActive ? 'text-primary-600' : 'text-gray-700',
+          mode === 'expand' ? 'mr-2' : 'mr-0',
         )}
         aria-hidden="true"
       />

+ 5 - 0
web/app/components/base/icons/assets/vender/line/layout/align-left-01.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="align-left-01">
+<path id="Icon" d="M3 3V21M21 12H7M7 12L14 19M7 12L14 5" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+</svg>

+ 5 - 0
web/app/components/base/icons/assets/vender/line/layout/align-right-01.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="align-right-01">
+<path id="Icon" d="M21 21V3M3 12H17M17 12L10 5M17 12L10 19" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+</svg>

+ 39 - 0
web/app/components/base/icons/src/vender/line/layout/AlignLeft01.json

@@ -0,0 +1,39 @@
+{
+	"icon": {
+		"type": "element",
+		"isRootNode": true,
+		"name": "svg",
+		"attributes": {
+			"width": "24",
+			"height": "24",
+			"viewBox": "0 0 24 24",
+			"fill": "none",
+			"xmlns": "http://www.w3.org/2000/svg"
+		},
+		"children": [
+			{
+				"type": "element",
+				"name": "g",
+				"attributes": {
+					"id": "align-left-01"
+				},
+				"children": [
+					{
+						"type": "element",
+						"name": "path",
+						"attributes": {
+							"id": "Icon",
+							"d": "M3 3V21M21 12H7M7 12L14 19M7 12L14 5",
+							"stroke": "currentColor",
+							"stroke-width": "2",
+							"stroke-linecap": "round",
+							"stroke-linejoin": "round"
+						},
+						"children": []
+					}
+				]
+			}
+		]
+	},
+	"name": "AlignLeft01"
+}

+ 16 - 0
web/app/components/base/icons/src/vender/line/layout/AlignLeft01.tsx

@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './AlignLeft01.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
+  props,
+  ref,
+) => <IconBase {...props} ref={ref} data={data as IconData} />)
+
+Icon.displayName = 'AlignLeft01'
+
+export default Icon

+ 39 - 0
web/app/components/base/icons/src/vender/line/layout/AlignRight01.json

@@ -0,0 +1,39 @@
+{
+	"icon": {
+		"type": "element",
+		"isRootNode": true,
+		"name": "svg",
+		"attributes": {
+			"width": "24",
+			"height": "24",
+			"viewBox": "0 0 24 24",
+			"fill": "none",
+			"xmlns": "http://www.w3.org/2000/svg"
+		},
+		"children": [
+			{
+				"type": "element",
+				"name": "g",
+				"attributes": {
+					"id": "align-right-01"
+				},
+				"children": [
+					{
+						"type": "element",
+						"name": "path",
+						"attributes": {
+							"id": "Icon",
+							"d": "M21 21V3M3 12H17M17 12L10 5M17 12L10 19",
+							"stroke": "currentColor",
+							"stroke-width": "2",
+							"stroke-linecap": "round",
+							"stroke-linejoin": "round"
+						},
+						"children": []
+					}
+				]
+			}
+		]
+	},
+	"name": "AlignRight01"
+}

+ 16 - 0
web/app/components/base/icons/src/vender/line/layout/AlignRight01.tsx

@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './AlignRight01.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
+  props,
+  ref,
+) => <IconBase {...props} ref={ref} data={data as IconData} />)
+
+Icon.displayName = 'AlignRight01'
+
+export default Icon

+ 2 - 0
web/app/components/base/icons/src/vender/line/layout/index.ts

@@ -1 +1,3 @@
+export { default as AlignLeft01 } from './AlignLeft01'
+export { default as AlignRight01 } from './AlignRight01'
 export { default as Grid01 } from './Grid01'