2024-07-10|閱讀時間 ‧ 約 50 分鐘

製作簡單的側邊欄side bar

這次的side bar不一樣了,上一次我好像做的太簡單了,所以完讀率只有14%,我好難過啊(;´д`)ゞ,所以我這準備的內容有多一點。



這次的目標

  • 按鈕的排版
  • 按鈕滑過去會有顏色變化
  • Side bar能夠展開
  • 箭頭能夠移動至被選擇的物件上


宣告

這次我創了兩個檔案 SideBar.tsx 跟 SideBarItem.tsx 並放在SideBar的資料夾

SideBarItem.tsx

const SideBarItem = ({ icon, handleClick, name, }: { icon: React.ReactNode, handleClick: Function, name: string}) => {

return (
<div className={`grid grid-cols-[80px-auto] bg-slate-900 rounded-lg`} onClick={() => { handleClick() }}>

<div className="col-start-1 flex justify-center items-center size-20">
{icon}
</div>
<p className={`col-start-2 hidden text-4xl`}>{name}</p>
</div>
)
}
export default SideBarItem

SideBarItem會有圖片以及展開後的文字,所以我用grid切出兩個區域,接著設定這些tag的顏色、大小

P.S. 展開後的文字等等再處理,所以我目前先讓他hidden

SideBar.tsx

import { IoIosMenu, IoIosCalendar, IoIosHome, IoIosSettings, IoIosHelp } from "react-icons/io";
import { MdOutlineTask } from "react-icons/md";
import SideBarItem from "./SideBarItem";
const SideBar = () => {
return (
<div className={`bg-black text-white p-3`}>
<SideBarItem handleClick={() => { }} icon={<IoIosMenu size="40" />} name=" " />

<SideBarItem handleClick={() => { }} icon={<IoIosHome size="40" />} name='Home' />
<SideBarItem handleClick={() => { }} icon={<MdOutlineTask size="40" />} name='Task' />
<SideBarItem handleClick={() => { }} icon={<IoIosCalendar size="40" />} name='Calendar' />
<SideBarItem handleClick={() => { }} icon={<IoIosHelp size="70" />} name='Help' />
<SideBarItem handleClick={() => { }} icon={<IoIosSettings size="40" />} name='Settings' />


<div className={` h-[3px] bg-slate-200 rounded-lg`}></div>

</div>
)
}
export default SideBar

接著在SideBar裡,把所需要的Icon import進來,並將資料都丟進去SideBarItem裡,

最後加入分隔線

<div className={` h-[3px] bg-slate-200 rounded-sm`}></div>

完成後會長這樣


排版

按鈕我分成三種

所以我讓最外層是grid,分出四格

<div className={`grid grid-rows-[auto_auto_1fr_auto] gap-5 bg-black text-white p-3`}>
<div className="row-start-1">
{/* 第一種 */}
<SideBarItem handleClick={() => { }} icon={<IoIosMenu size="40" />} name=" " />
</div>

<div className="row-start-4">
{/* 分隔線 */}
<div className={` h-[3px] bg-slate-200 rounded-lg`}></div>
</div>

<div className={`row-start-3 space-y-3`}>
{/* 第二種 */}
<SideBarItem handleClick={() => { }} icon={<IoIosHome size="40" />} name='Home' />
<SideBarItem handleClick={() => { }} icon={<MdOutlineTask size="40" />} name='Task' />
<SideBarItem handleClick={() => { }} icon={<IoIosCalendar size="40" />} name='Calendar' />
</div>

<div className="row-start-4 space-y-3">
{/* 第三種 */}
<SideBarItem handleClick={() => { }} icon={<IoIosHelp size="70" />} name='Help' />
<SideBarItem handleClick={() => { }} icon={<IoIosSettings size="40" />} name='Settings' />
</div>

</div>

:hover

為了讓滑鼠滑過去要有反應,我加了:hover跟cursor-pointer

在SideBarItem最外層的div上

hover:bg-slate-300 hover:text-black  //顏色(背景、圖片)
hover:rounded-[25px]                 //邊框      
cursor-pointer // 滑鼠鼠標

但這樣一瞬間變化太大,螢幕閃爍會害眼睛很不舒服,所以要在多加這一行

transition-all duration-200 ease-linear

Side Bar 展開

這是整篇最麻煩的,首先先創一個變數isOpen以及func控制Side Bar 的開關。

const [isOpen, setisOpen] = useState<boolean>(false)
function toggleIsOpen(): void{
setisOpen(!isOpen);
}

將toggleIsOpen()丟進去handleClicked

<SideBarItem handleClick={() => { toggleIsOpen() }} icon={<IoIosMenu size="40" />} name=" " />


接著把IsOpen傳入SideBarItem的props

並加入判斷是裡控制寬度

transition-all duration-200 ease-linear //這個也順便丟進去
<p className={`col-start-2 flex items-center text-white text-4xl ${isOpen ? 'w-52 ' : 'w-0'}
overflow-hidden
            transition-all duration-200 ease-linear`}>{name}</p>


<p> overflow

歐對了,如果沒加overflow-hidden,SideBar會變這樣

menu也展開

但這展開時,menu也伸長了


為了避免這個情況

要給裝menu的div flex屬性之外,還要讓SideBarItem判斷是否需要<p>

SideBarItem.tsx

{name === '' ? <></> :  <p className={`col-start-2 flex items-center text-4xl ${isOpen ? 'w-52 ' : 'w-0'} 
overflow-hidden
transition-all duration-200 ease-linear`}>{name}</p>}


目前的程式

SideBarItem.tsx

const SideBarItem = ({ icon, handleClicked, name, isOpen}: { icon: React.ReactNode, handleClicked: Function, name: string, isOpen: boolean}) => {

return (
<div className={`grid grid-cols-[80px-auto] bg-slate-900 rounded-lg
hover:bg-slate-300 hover:text-black hover:rounded-[25px]
transition-all duration-200 ease-linear cursor-pointer`} onClick={() => { handleClicked() }}>

<div className="col-start-1 flex justify-center items-center size-20">
{icon}
</div>
{name === '' ? <></> : <p className={`col-start-2 flex items-center text-4xl ${isOpen ? 'w-52 ' : 'w-0'}
overflow-hidden
transition-all duration-200 ease-linear`}>{name}</p>}

</div>
)
}
export default SideBarItem

SideBar.tsx

import { IoIosMenu, IoIosCalendar, IoIosHome, IoIosSettings, IoIosHelp } from "react-icons/io";
import { MdOutlineTask } from "react-icons/md";
import SideBarItem from "./SideBarItem";
import { useState } from "react";

const SideBar = () => {
const [isOpen, setisOpen] = useState<boolean>(false)
function toggleIsOpen(): void {
setisOpen(!isOpen);
}

return (
<div className={`grid grid-rows-[auto_auto_1fr_auto] gap-5 bg-black text-white p-3`}>
<div className="row-start-1 flex">
{/* 第一種 */}
<SideBarItem handleClicked={() => { toggleIsOpen();}} icon={<IoIosMenu size="40" />} name="" isOpen={isOpen}/>

</div>

<div>
{/* 分隔線 */}
<div className={` h-[3px] bg-slate-200 rounded-lg`}></div>
</div>


<div className={`row-start-3 space-y-3`}>
{/* 第二種 */}
<SideBarItem handleClicked={() => { }} icon={<IoIosHome size="40" />} name='Home' isOpen={isOpen}/>
<SideBarItem handleClicked={() => { }} icon={<MdOutlineTask size="40" />} name='Task' isOpen={isOpen}/>
<SideBarItem handleClicked={() => { }} icon={<IoIosCalendar size="40" />} name='Calendar' isOpen={isOpen}/>
</div>

<div className="row-start-4 space-y-3">
{/* 第三種 */}
<SideBarItem handleClicked={() => { }} icon={<IoIosHelp size="70" />} name='Help' isOpen={isOpen}/>
<SideBarItem handleClicked={() => { }} icon={<IoIosSettings size="40" />} name='Settings' isOpen={isOpen}/>
</div>

</div>
)
}
export default SideBar

箭頭

這個部份我是用position: absolute去做到的,所以我需要一個變數去儲存箭頭目的地的y座標

為此,我用了useState

const [arrowPosition, setArrowPosition] = useState<string>('[8px]')

接著

import箭頭到SideBar.tsx裡

import { FaCaretRight } from "react-icons/fa";

加進去div裡

<div className={`row-start-3 space-y-3 relative`}>

{/* 第二種 */}

<SideBarItem handleClicked={() => { }} icon={<IoIosHome size="40" />} name='Home' isOpen={isOpen}/>

<SideBarItem handleClicked={() => { }} icon={<MdOutlineTask size="40" />} name='Task' isOpen={isOpen}/>

<SideBarItem handleClicked={() => { }} icon={<IoIosCalendar size="40" />} name='Calendar' isOpen={isOpen}/>
<div className={`absolute top-${arrowPosition} left-[-20px] transition-all duration-200 ease-linear`}>
{/* 箭頭 */}
<FaCaretRight size="40"/>
</div>

</div>

P.S.怕會有人不知道,如果子項用了position: absolute且想以父項作為基準點的話,則父項也要有position: relative 或 absolute 或...


目前應該會做到這


function onNavItemClicked(position: string): void {
if(arrowPosition === position){
toggleIsOpen() //順便加的功能
return
}
setArrowPosition(position)
//打開某個頁面,還沒寫
}

把func加進去

<div className={`row-start-3 space-y-3 relative`}>
{/* 第二種 */}
<SideBarItem handleClicked={() => { onNavItemClicked('[8px]') }} icon={<IoIosHome size="40" />} name='Home' isOpen={isOpen}/>
<SideBarItem handleClicked={() => { onNavItemClicked('[100px]') }} icon={<MdOutlineTask size="40" />} name='Task' isOpen={isOpen}/>
<SideBarItem handleClicked={() => { onNavItemClicked('[192px]') }} icon={<IoIosCalendar size="40" />} name='Calendar' isOpen={isOpen}/>

<div className={`absolute top-${arrowPosition} left-[-20px] transition-all duration-200 ease-linear`}>
{/* 箭頭 */}
<FaCaretRight size="40"/>
</div>
</div>


後記

我知道top-[ooopx]這東西其實可以變成數學算式,好像廢話,畢竟我每個Item的大小以及padding都是固定的。但我好懶歐。可能以後多一些功能像是能自由更改icon大小,或是動態增減SideBarItem的時候,在更改好了。


我也知道我一開始的圖的menu是置中的,但後來的是在旁邊,這是因為,這個程式是我重寫的(我一開始寫的時候寫的很亂),結果我後來比較喜歡這種放邊邊的樣式,反正如果想要置中,就多打一個 justify-center在menu的div的flex旁邊

反正就先這樣了,希望你們會喜歡。

P.S. 喜歡的話拜託拜託按個愛心,我會很開心

這是我這次整個的程式

SideBarItem.tsx

const SideBarItem = ({ icon, handleClicked, name, isOpen}: { icon: React.ReactNode, handleClicked: Function, name: string, isOpen: boolean}) => {

return (
<div className={`grid grid-cols-[80px-auto] bg-slate-900 rounded-lg
hover:bg-slate-300 hover:text-black hover:rounded-[25px]
transition-all duration-200 ease-linear cursor-pointer`} onClick={() => { handleClicked() }}>

<div className="col-start-1 flex justify-center items-center size-20">
{icon}
</div>
{name === '' ? <></> : <p className={`col-start-2 flex items-center text-4xl ${isOpen ? 'w-52 ' : 'w-0'}
overflow-hidden
transition-all duration-200 ease-linear`}>{name}</p>}

</div>
)
}
export default SideBarItem


SideBar.tsx

import { IoIosMenu, IoIosCalendar, IoIosHome, IoIosSettings, IoIosHelp } from "react-icons/io";
import { MdOutlineTask } from "react-icons/md";
import SideBarItem from "./SideBarItem";
import { useState } from "react";
import { FaCaretRight } from "react-icons/fa";

const SideBar = () => {
const [isOpen, setisOpen] = useState<boolean>(false)
function toggleIsOpen(): void {
setisOpen(!isOpen);
}

const [arrowPosition, setArrowPosition] = useState<string>('[8px]')
function onNavItemClicked(position: string): void {
if (arrowPosition === position) {
toggleIsOpen()
return
}
setArrowPosition(position)
}

return (
<div className={`grid grid-rows-[auto_auto_1fr_auto] gap-5 bg-black text-white p-3`}>
<div className="row-start-1 flex">
{/* 第一種 */}
<SideBarItem handleClicked={() => { toggleIsOpen(); }} icon={<IoIosMenu size="40" />} name="" isOpen={isOpen} />
</div>

<div>
{/* 分隔線 */}
<div className={` h-[3px] bg-slate-200 rounded-lg`}></div>
</div>

<div className={`row-start-3 space-y-3 relative`}>
{/* 第二種 */}
<SideBarItem handleClicked={() => { onNavItemClicked('[8px]') }} icon={<IoIosHome size="40" />} name='Home' isOpen={isOpen} />
<SideBarItem handleClicked={() => { onNavItemClicked('[100px]') }} icon={<MdOutlineTask size="40" />} name='Task' isOpen={isOpen} />
<SideBarItem handleClicked={() => { onNavItemClicked('[192px]') }} icon={<IoIosCalendar size="40" />} name='Calendar' isOpen={isOpen} />

<div className={`absolute top-${arrowPosition} left-[-20px] transition-all duration-200 ease-linear`}>
{/* 箭頭 */}
<FaCaretRight size="40" />
</div>
</div>

<div className="row-start-4 space-y-3">
{/* 第三種 */}
<SideBarItem handleClicked={() => { }} icon={<IoIosHelp size="70" />} name='Help' isOpen={isOpen} />
<SideBarItem handleClicked={() => { }} icon={<IoIosSettings size="40" />} name='Settings' isOpen={isOpen} />
</div>

</div>
)
}
export default SideBar


分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.