Tailwind Components and Responsive UI
Day 3 - ชั่วโมงที่ 2: ปรับ Component และตารางด้วย Tailwind
เป้าหมายของชั่วโมงนี้
หลังจบชั่วโมงนี้ นักศึกษาจะสามารถ:
- ใช้ Tailwind กำหนดหน้าตาของ Component ตาม Props ได้
- ปรับ
StatusBadgeให้แสดงสีตามสถานะ - ปรับ
IssueListให้อ่านง่ายขึ้น - ทำให้ตารางเลื่อนแนวนอนได้เมื่อพื้นที่ไม่พอ
- แสดง Empty State เมื่อไม่มีรายการ
- ลบ Custom CSS เดิมที่ไม่ใช้งานแล้วได้อย่างถูกต้อง
ไฟล์ที่ใช้
components/StatusBadge.tsx
components/IssueList.tsx
app/globals.css
app/issues/page.tsxSlide 1: ต่อจากหน้าตาเว็บใน Hour 1
เมนูนำทางและฟอร์มใช้ Tailwind แล้ว ส่วนป้ายสถานะ (StatusBadge) และตารางใน IssueList ยังใช้ CSS เดิมภายใน @layer base
ชั่วโมงนี้จะย้ายสอง Component ที่เหลือ แล้วเพิ่มข้อความเมื่อไม่มีรายการ
- ใช้
issuesและ Props เดิมจาก Day 2 - เก็บ Import, Type และ Link รายละเอียดไว้
- CSS ของ
.issue-listถูกลบใน Hour 1 แล้ว เพราะ<main>ใช้gap-6แทนระยะด้านบน mainยังเป็นรูปแบบพื้นฐานของหน้าที่ยังไม่ได้ย้าย
ลำดับงาน: สีสถานะ → ตารางบนจอเล็ก → ข้อความเมื่อไม่มีข้อมูล
Slide 2: ให้ฟังก์ชันเลือกคลาสสีตามสถานะ
function getStatusClass(status: IssueStatus): string { if (status === "OPEN") { return "bg-red-50 text-red-700 ring-red-200"; } if (status === "IN_PROGRESS") { return "bg-amber-50 text-amber-700 ring-amber-200"; } return "bg-emerald-50 text-emerald-700 ring-emerald-200";}
getStatusClass เป็นฟังก์ชันช่วยเลือกสีแดง เหลือง และเขียวตามสถานะ โดย ring-* กำหนดสีเส้นรอบป้ายที่จะเพิ่มในขั้นถัดไป
Slide 3: ใช้คลาส Tailwind กับป้ายสถานะ
<span className={`inline-flex rounded-full px-2.5 py-1 text-xs font-bold ring-1 ${getStatusClass(status)}`}> {status}</span>
rounded-full ทำให้มุมโค้ง ส่วน ring-1 แสดงเส้นรอบป้าย และ getStatusClass เลือกสีตาม status
Badge ควรยังแสดงข้อความ OPEN, IN_PROGRESS และ DONE พร้อมสีที่ต่างกัน
Slide 4: ลบ CSS เดิมของ Badge
ตัวอย่างย่อระบุ Block ที่ต้องลบทั้งก้อน ไม่คัดลอก Comment ไปแทนโค้ดจริง
.status { /* Property เดิม */ }.status-open { /* Property เดิม */ }.status-progress { /* Property เดิม */ }.status-done { /* Property เดิม */ }
หลังลบ สีและรูปทรงของ Badge ควรเหมือนก่อนลบ เพราะมาจาก Utility แล้ว
Slide 5: ปรับกรอบของ IssueList
<section aria-labelledby="issue-list-title" className="min-w-0 rounded-lg border border-slate-200 bg-white p-6">
คงหัวข้อและตารางทั้งหมดภายใน Section รวมถึง </section> เดิมไว้
min-w-0 ช่วยให้กล่องย่อได้เมื่ออยู่ใน Grid ส่วนพื้นขาว เส้นขอบ และระยะภายในมาจาก Utility
Slide 6: จัดหัวข้อและจำนวนรายการ
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <h2 id="issue-list-title" className="text-xl font-bold text-slate-950"> รายการปัญหาล่าสุด </h2> <p className="text-sm font-medium text-slate-600"> ทั้งหมด {issues.length} รายการ </p></div>
issues.length คือจำนวนรายการ บนจอเล็กหัวข้อและจำนวนเรียงลงมา ตั้งแต่ sm (640px) จะอยู่แถวเดียวกัน
ตารางเดิมต้องยังอยู่ถัดจาก </div> นี้ภายใน Section
Slide 7: ให้ตารางเลื่อนภายในกล่อง
<div className="mt-4 overflow-x-auto"> <table className="w-full min-w-[720px] border-collapse text-sm">
แสดงเฉพาะ Tag เปิดที่แก้ ให้เก็บ <thead>, <tbody> และ Tag ปิดเดิมไว้ครบ
overflow-x-autoให้กล่องเลื่อนแนวนอนได้เมื่อพื้นที่ไม่พอmin-w-[720px]กำหนดความกว้างขั้นต่ำของตารางเป็น 720pxw-fullให้ตารางกว้างเต็มกล่องเมื่อมีพื้นที่พอ
บนจอเล็กควรเลื่อนเฉพาะตาราง โดยหน้าเว็บไม่ล้นด้านข้าง
Slide 8: ปรับหัวตาราง
<thead> <tr className="border-b border-slate-200 bg-slate-50 text-left text-xs text-slate-500"> <th className="px-3 py-3 font-semibold">รหัส</th> <th className="px-3 py-3 font-semibold">หัวข้อ</th> <th className="px-3 py-3 font-semibold">ผู้แจ้ง</th> <th className="px-3 py-3 font-semibold">สถานะ</th> <th className="px-3 py-3 font-semibold">รายละเอียด</th> </tr></thead>
หัวตารางควรมีพื้นสีอ่อน ข้อความชิดซ้าย และระยะภายในแต่ละช่องเท่ากัน
ยังเก็บ CSS ของ Table ไว้ก่อน จนกว่าจะใส่ Utility ครบทั้งหัวตารางและแถวข้อมูล
Slide 9: เพิ่ม Style ให้แถวและช่องข้อมูล
<tr key={issue.id} className="border-b border-slate-100 last:border-0"> <td className="px-3 py-4 font-medium text-slate-700"> <td className="px-3 py-4 text-slate-950"> <td className="px-3 py-4 text-slate-600"> <td className="px-3 py-4"> <td className="px-3 py-4">
แสดงเฉพาะ Tag เปิดตามลำดับ: แถว → รหัส → หัวข้อ → ผู้แจ้ง → สถานะ → รายละเอียด
เก็บ .map(), ข้อมูลแต่ละช่อง, StatusBadge, Link และ Tag ปิดไว้ครบ ห้ามวางหกบรรทัดนี้ติดกันแทนทั้งแถว
last:border-0 เอาเส้นคั่นของแถวสุดท้ายออก
Slide 10: ปรับ Link รายละเอียด
<Link href={`/issues/${issue.id}`} className="font-semibold text-teal-700 hover:text-teal-900"> ดูรายละเอียด</Link>
คง <td> ที่ครอบ Link และ href เดิมไว้ แต่ละแถวยังเปิดรายละเอียดของ issue.id นั้น
ข้อความควรเป็นสีเขียวเข้มและเปลี่ยนสีเมื่อชี้เมาส์
Slide 11: ลบ CSS เดิมของรายการและตาราง
ตัวอย่างย่อระบุ Block ที่ลบทั้งก้อน ไม่คัดลอก Comment ไปแทนโค้ดจริง
.table-wrapper { /* Property เดิม */ }table { /* Property เดิม */ }th, td { /* Property เดิมของกฎร่วม */ }th { /* Property เดิม */ }section { /* Property เดิม */ }
.issue-list ถูกลบไปแล้วใน Hour 1 ส่วน IssueForm และ IssueList มี Utility ที่ Section แล้ว จึงลบ section กลางได้
เก็บ Import, วงเล็บของ @layer base, *, body และ main ไว้; หลังลบ Form และตารางควรยังมี Style ครบ
Slide 12: เพิ่มข้อความเมื่อไม่มีรายการ
Empty State คือข้อความที่ช่วยให้รู้ว่าไม่มีข้อมูล แทนการแสดงตารางว่าง
if (issues.length === 0) { return ( <section className="rounded-lg border border-dashed border-slate-300 bg-white p-8 text-center"> <h2 className="text-lg font-bold text-slate-950">ยังไม่มีรายการปัญหา</h2> <p className="mt-2 text-sm text-slate-600"> เมื่อมีการแจ้งปัญหา รายการจะแสดงที่นี่ </p> </section> );}
เมื่อ Array ว่าง Function จะคืนข้อความนี้ทันที ส่วน return หลักที่แสดงตารางยังเก็บไว้ด้านล่าง
Slide 13: จัดวางเนื้อหาของหน้า /issues
<main className="mx-auto grid grid-cols-1 max-w-5xl gap-6 px-6 py-8"> <h1 className="text-2xl font-bold text-slate-950">รายการปัญหา</h1> <IssueList issues={issues} /></main>
เก็บ Import ของ IssueList, issues และ Function IssuesPage เดิมไว้
หน้า /issues ควรมีความกว้างและระยะขอบเหมือน Home ส่วน grid-cols-1 ช่วยให้ตารางย่ออยู่ภายในพื้นที่ของหน้า
ยังเก็บ CSS กลางของ main ให้ Route อื่นที่ยังไม่ได้ย้าย
Slide 14: ลองส่งรายการว่าง
<IssueList issues={[]} />
หน้า /issues ควรแสดง ยังไม่มีรายการปัญหา และไม่แสดงตาราง
หลังตรวจแล้ว เปลี่ยนบรรทัดเดิมกลับเป็น:
<IssueList issues={issues} />
ตาราง Mock Data ควรกลับมา โดยไม่ต้องแก้ data/issue.ts
Slide 15: ลองใช้งานทั้งสองหน้า
ที่ความกว้าง 375px และ 1024px ควรได้ผลดังนี้:
- หน้าเว็บไม่ล้นด้านข้าง; บนจอเล็กเลื่อนเฉพาะตารางได้
- หัวข้อและจำนวนรายการเรียงลงมาบนจอเล็ก และอยู่แถวเดียวกันบนจอกว้าง
- Badge แสดงครบทั้งสามสถานะพร้อมสีที่ต่างกัน
- Link รายละเอียดเปิดรายการตรงกับแถวที่เลือก
- Console และ Terminal ไม่มี Error
ถ้าทั้งหน้าล้น ให้ตรวจ min-w-0 ที่ Section, grid-cols-1 ที่ Main และ overflow-x-auto ที่กล่องครอบ Table
Slide 16: พร้อมเพิ่มการทำงานให้ Form
ตอนนี้ IssueList รับ issues ชุดเดิม แล้วแสดงตารางหรือข้อความเมื่อไม่มีข้อมูลได้
- Home และ
/issuesใช้ Component ชุดเดียวกัน - Form, Badge และ Table ใช้ Tailwind แล้ว
- CSS พื้นฐานของ
mainยังรองรับ Route อื่น
ฟอร์มยังไม่เพิ่มรายการ เพราะเรายังไม่ได้เชื่อมการกดส่งกับข้อมูล
Hour 3 จะเพิ่มการเก็บรายการที่เปลี่ยนได้และการตรวจข้อมูล เพื่อให้ข้อมูลจากฟอร์มปรากฏในตาราง