ตัวอย่างการทำ Tab ด้วย Headless UI ของดีจาก Tailwind Labs

Headlessui เป็น UI Components จากทีม Tailwind Labs ปัจจุบัน รองรับ React.js และ Vue.js โดยปัจจุบันมี Component ให้เลือก เช่น Dropdown, Menu, Toggle, Switch, Listbox, Tab, Dialog เป็นต้นครับ
เนื่องจากมันเป็น Headless UI ก็จะไม่มี css หรือ default style มาให้ เราสามารถใช้ CSS อะไรก็ได้ (แต่ตัวอย่างนี้ผมใช้ Tailwind CSS เนื่องจากเป็นทีมเดียวกันกับ headdlessui ครับ)
วันนี้ก็เลยลองเล่นตัว Tabs ซักหน่อย ข้อดีนอกจากมี UI มาให้แล้ว คือมีเรื่องของ ARIA มาให้ด้วย ไม่ต้องกำหนด role
และ attribute
เพิ่มเอง ทำให้สามารถใช้ Keyboard short cut ได้เลย
ลองทำ headlessui Tabs
ทดลองสร้างโปรเจ็คขึ้นมาเลยดีกว่า โดยตัวโปรเจ็คจะใช้ Vite.js ขึ้นโปรเจ็คเป็น React.js ครับ ซึ่ง Library ต่างๆ ที่ใช้ในบทความประกอบไปด้วย
- React v18.2.0
- Headlessui v1.6.6
- Tailwind v3.1
- Vite v3.0.7
1. สร้างโปรเจ็คด้วย Vite
npm create vite@latest
จากนั้นตั้งชื่อโปรเจ็คและเลือก react และ react (ใครจะเลือก typesript ก็ได้นะครับ)
✔ Project name: … headlessui-tabs-vitejs✔ Select a framework: › react✔ Select a variant: › react
เมื่อ setup Vite เสร็จแล้ว ก็ทำการเปิดโฟลเดอร์ที่เราเพิ่ง init แล้ว install dependencies
cd headlessui-tabs-vitejsnpm installnpm run dev
2. ทำการติดตั้ง tailwindcss
เราจะใช้ tailwind เป็น css หลัก
npm install -D tailwindcss postcss autoprefixer
ต่อมา init tailwindcss จะได้ไฟล์ tailwind.config.js
และ postcss.config.js
npx tailwindcss init -p
แก้ไขไฟล์ tailwind.css.js
เพื่อให้มันรู้ว่าไฟล์ index.html
และไฟล์ต่างๆของ react จะใช้ tailwind
/** @type {import('tailwindcss').Config} */module.exports = { content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], theme: { extend: {} }, plugins: []}
สุดท้าย เพิ่มตรงนี้ลงไป ที่ไฟล์ index.css
@tailwind base;@tailwind components;@tailwind utilities;
3. headlessui tabs
ติดตั้ง headlessui tabs
npm install @headlessui/react
ตัวโครงสร้างในการสร้าง Tab ประกอบไปด้วย Tab.Group
, Tab.List
, Tab
, Tab.Panels
, และ Tab.Panel
โดย Component เริ่มต้นจะมีหน้าตาประมาณนี้
import { Tab } from '@headlessui/react'
function MyTabs() { return ( <Tab.Group> <Tab.List> <Tab>Tab 1</Tab> <Tab>Tab 2</Tab> <Tab>Tab 3</Tab> </Tab.List> <Tab.Panels> <Tab.Panel>Content 1</Tab.Panel> <Tab.Panel>Content 2</Tab.Panel> <Tab.Panel>Content 3</Tab.Panel> </Tab.Panels> </Tab.Group> )}
ตัวอย่างภาพ เพื่อให้เห็นภาพมากขึ้น ว่า Tab.List
และ Tab.Panels
คือส่วนไหน
สร้างไฟล์ TabExample.jsx
ขึ้นมา ใน folder components
ซึ่งตัวอย่างโค๊ด ก็เป็นตัวอย่างเดียวกับเว็บ headless ui ครับ
import { useState } from 'react'import { Tab } from '@headlessui/react'
function classNames(...classes) { return classes.filter(Boolean).join(' ')}
export default function Example() { let [categories] = useState({ Recent: [ { id: 1, title: 'Does drinking coffee make you smarter?', date: '5h ago', commentCount: 5, shareCount: 2 }, { id: 2, title: "So you've bought coffee... now what?", date: '2h ago', commentCount: 3, shareCount: 2 } ], Popular: [ { id: 1, title: 'Is tech making coffee better or worse?', date: 'Jan 7', commentCount: 29, shareCount: 16 }, { id: 2, title: 'The most innovative things happening in coffee', date: 'Mar 19', commentCount: 24, shareCount: 12 } ], Trending: [ { id: 1, title: 'Ask Me Anything: 10 answers to your questions about coffee', date: '2d ago', commentCount: 9, shareCount: 5 }, { id: 2, title: "The worst advice we've ever heard about coffee", date: '4d ago', commentCount: 1, shareCount: 2 } ] })
return ( <div className="w-full max-w-md px-2 py-16 sm:px-0"> <Tab.Group> <Tab.List className="flex space-x-1 rounded-xl bg-blue-900/20 p-1"> {Object.keys(categories).map((category) => ( <Tab key={category} className={({ selected }) => classNames( 'w-full rounded-lg py-2.5 text-sm leading-5 font-medium text-blue-700', 'ring-opacity-60 focus:online-hidden ring-white ring-offset-2 ring-offset-blue-400 focus:ring-2', selected ? 'bg-white shadow' : 'text-blue-100 hover:bg-white/[0.12] hover:text-white' ) } > {category} </Tab> ))} </Tab.List> <Tab.Panels className="mt-2"> {Object.values(categories).map((posts, idx) => ( <Tab.Panel key={idx} className={classNames( 'rounded-xl bg-white p-3', 'ring-opacity-60 focus:online-hidden ring-white ring-offset-2 ring-offset-blue-400 focus:ring-2' )} > <ul> {posts.map((post) => ( <li key={post.id} className="relative rounded-md p-3 hover:bg-gray-100"> <h3 className="text-left text-sm leading-5 font-medium text-black"> {post.title} </h3>
<ul className="mt-1 flex space-x-1 text-xs leading-4 font-normal text-gray-500"> <li>{post.date}</li> <li>·</li> <li>{post.commentCount} comments</li> <li>·</li> <li>{post.shareCount} shares</li> </ul>
<a href="#" className={classNames( 'absolute inset-0 rounded-md', 'focus:online-hidden ring-blue-400 focus:z-10 focus:ring-2' )} /> </li> ))} </ul> </Tab.Panel> ))} </Tab.Panels> </Tab.Group> </div> )}
แก้ไขไฟล์ App.jsx
นิดหน่อย (ลบ state counter ออก) และทำการ import TabExample
มาใช้
import reactLogo from './assets/react.svg'import './App.css'
import TabExample from './components/TabExample'
function App() { return ( <div className="App"> <div> <a href="https://vitejs.dev" target="_blank"> <img src="/vite.svg" className="logo" alt="Vite logo" /> </a> <a href="https://reactjs.org" target="_blank"> <img src={reactLogo} className="logo react" alt="React logo" /> </a> </div> <h1>Vite + React</h1>
<p className="text-lg font-bold text-purple-400">Headlessui Tab Example</p>
<TabExample /> </div> )}
export default App
ทดลองรันโปรแกรม
npm run dev
และเปิดหน้าเว็บขึ้นมาดูผลลัพธ์ http://localhost:5173
References
- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust