製作簡單的側邊欄side bar

閱讀時間約 34 分鐘
這次的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>

完成後會長這樣

raw-image


排版

按鈕我分成三種

raw-image

所以我讓最外層是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會變這樣

raw-image

menu也展開

但這展開時,menu也伸長了

raw-image


為了避免這個情況

要給裝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 或...


目前應該會做到這

raw-image


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


2會員
5內容數
這是一篇帶新手快速入門的Web教學。會帶你製作第一個todo-list,雖然看起來很簡單,但其實蘊含很多觀念。
留言0
查看全部
發表第一個留言支持創作者!
Web開發日誌 的其他內容
我用了vite react tailwind , 並用typescript寫 跟他一樣 [vite] 使用 Vite 快速建立 React + TypeScript + Tailwind CSS 專案 | WeiWei 的前端程式教學與筆記 (wei-docusaurus-vercel.verc
我用了vite react tailwind , 並用typescript寫 跟他一樣 [vite] 使用 Vite 快速建立 React + TypeScript + Tailwind CSS 專案 | WeiWei 的前端程式教學與筆記 (wei-docusaurus-vercel.verc
你可能也想看
Thumbnail
「設計不僅僅是外觀和感覺。設計是其運作的方式。」 — Steve Jobs 身為一個獨立文案,許多人會以為我們的生活只需要面對電腦,從無到有,用精巧的文字填滿空白的螢幕,呈現心目中獨具風格的作品。 ——有的時候可以如此,但其實這是我們夢寐以求的偶發日常。 更多的時候,白天的工作時間總被各種繁雜
Thumbnail
台股、美股近期明顯回檔,市場敘事發生改變,壞消息一樁接一樁出現,下一步該怎麼走呢?本文將探討近期的宏觀經濟事件,並分享個人的操作思考。
Thumbnail
最近在練習使用 CSS 來製作一些簡單的動畫,以下是我收集資料與實作的成果。製作一張可以水平翻轉的卡片,這邊會使用 Vue.js 來簡化邏輯,主要是解釋 CSS 的部分。
Thumbnail
Gamma是可以使用AI在幾秒鐘內創建精美的文件,簡報和網頁。他可以快速幫你針對主題生成簡報大綱並且幫你快速產生排版美麗的簡報。
Thumbnail
讓「Gamma」幫你做簡報,還可以幫你整理簡報大綱,簡報排版美化!靈感激盪跟簡報製作就靠它了!
Thumbnail
這篇文章我想要參加Chat gpt的主題徵文 主要是讓chat gpt自行生成成語接龍遊戲的遊戲代碼
Thumbnail
如果你的時間、預算有限,那麼請外包商製作一個 具有預約功能的網站,動輒 2–3 個月的大工程,可能不太適合你。 不過現在你也能透過線上數位工具,找到簡單快速的解決方案,並在短短 5 天之內,就擁有一個簡易的預約官網,開始在網路上販售自己的活動。 擁有預約官網之後,接下來只要需要大約 1 個月的時間,
Thumbnail
家裡往年都會準備年貨過冬,加上今年的年假長達十天,沒有零食怎麼渡過這漫漫長假!於是老媽賦予我採買年貨的重責大任,所以我趁著上班午休時間到處逛網拍賣場準備存糧,像是肉乾、鱈魚香絲、餅乾都是必買清單! 但又想說家裡長輩跟自己也上了年紀,口味不宜吃太重鹹,這趟採買時多半搜尋一些較清爽沒負擔的零食,但零食好
Thumbnail
😻🤎😻我用的是小玻璃的瓶子,原本就是裝果醬用的,小小的剛剛好 唯一比較注意的他是玻璃的喔 如果家裡是磁磚的就不建議使用了 ! Step1: 先將瓶蓋用紙膠帶包好, 將紙膠帶先一條一條接好,變成方形的再包在瓶蓋上,外形修成圓形,記得剪虎口喔 Step2: 倒入保麗龍膠,加入一點紅色的壓克力顏料
Thumbnail
魚和豆腐都含有豐富的蛋白質及營養素,可增強抵抗力,也有助於頭腦和肌肉發展。 此道料理熱量低,也很適合當作減脂餐。 大家一起來試試看吧!
Thumbnail
「設計不僅僅是外觀和感覺。設計是其運作的方式。」 — Steve Jobs 身為一個獨立文案,許多人會以為我們的生活只需要面對電腦,從無到有,用精巧的文字填滿空白的螢幕,呈現心目中獨具風格的作品。 ——有的時候可以如此,但其實這是我們夢寐以求的偶發日常。 更多的時候,白天的工作時間總被各種繁雜
Thumbnail
台股、美股近期明顯回檔,市場敘事發生改變,壞消息一樁接一樁出現,下一步該怎麼走呢?本文將探討近期的宏觀經濟事件,並分享個人的操作思考。
Thumbnail
最近在練習使用 CSS 來製作一些簡單的動畫,以下是我收集資料與實作的成果。製作一張可以水平翻轉的卡片,這邊會使用 Vue.js 來簡化邏輯,主要是解釋 CSS 的部分。
Thumbnail
Gamma是可以使用AI在幾秒鐘內創建精美的文件,簡報和網頁。他可以快速幫你針對主題生成簡報大綱並且幫你快速產生排版美麗的簡報。
Thumbnail
讓「Gamma」幫你做簡報,還可以幫你整理簡報大綱,簡報排版美化!靈感激盪跟簡報製作就靠它了!
Thumbnail
這篇文章我想要參加Chat gpt的主題徵文 主要是讓chat gpt自行生成成語接龍遊戲的遊戲代碼
Thumbnail
如果你的時間、預算有限,那麼請外包商製作一個 具有預約功能的網站,動輒 2–3 個月的大工程,可能不太適合你。 不過現在你也能透過線上數位工具,找到簡單快速的解決方案,並在短短 5 天之內,就擁有一個簡易的預約官網,開始在網路上販售自己的活動。 擁有預約官網之後,接下來只要需要大約 1 個月的時間,
Thumbnail
家裡往年都會準備年貨過冬,加上今年的年假長達十天,沒有零食怎麼渡過這漫漫長假!於是老媽賦予我採買年貨的重責大任,所以我趁著上班午休時間到處逛網拍賣場準備存糧,像是肉乾、鱈魚香絲、餅乾都是必買清單! 但又想說家裡長輩跟自己也上了年紀,口味不宜吃太重鹹,這趟採買時多半搜尋一些較清爽沒負擔的零食,但零食好
Thumbnail
😻🤎😻我用的是小玻璃的瓶子,原本就是裝果醬用的,小小的剛剛好 唯一比較注意的他是玻璃的喔 如果家裡是磁磚的就不建議使用了 ! Step1: 先將瓶蓋用紙膠帶包好, 將紙膠帶先一條一條接好,變成方形的再包在瓶蓋上,外形修成圓形,記得剪虎口喔 Step2: 倒入保麗龍膠,加入一點紅色的壓克力顏料
Thumbnail
魚和豆腐都含有豐富的蛋白質及營養素,可增強抵抗力,也有助於頭腦和肌肉發展。 此道料理熱量低,也很適合當作減脂餐。 大家一起來試試看吧!