การสร้าง UI แบบกำหนดเอง
คู่มือนี้จะแนะนำรูปแบบการสร้าง UI ตัวเลือกวันที่ของคุณเองบน hook แบบ headless
รูปแบบ
hook แบบ headless ทุกตัวใช้รูปแบบเดียวกัน:
- เรียกใช้ hook พร้อมกับตัวเลือกของคุณ (
value,onChange,configuration) - แยกโครงสร้างค่าที่ส่งคืน เพื่อรับ
state, ค่าที่คำนวณได้ และhandlers - แสดงผล markup ของคุณเอง โดยใช้ค่าที่ส่งคืน
- เชื่อมต่อการโต้ตอบ โดยการเรียกใช้
handlersที่ให้มา
hook จะจัดการความซับซ้อนทั้งหมด — การสร้างปฏิทิน, การคำนวณวันที่, สถานะการเปิด/ปิด, การนำทางด้วยคีย์บอร์ด, ตรรกะของช่วง — และคุณก็มุ่งเน้นไปที่การนำเสนอเพียงอย่างเดียว
ตัวอย่าง: ตัวเลือกวันที่แบบกำหนดเอง
import { useState } from "react";import { useDatePicker } from "react-date-range-picker-headless";
function CustomDatePicker() { const [value, setValue] = useState<Date | null>(null);
const { isOpen, calendar, getDayProps, displayValue, hasValue, canConfirm, locale, handleToggle, handleDateClick, handleConfirm, handleCancel, handleClear, handlePrevMonth, handleNextMonth, handleKeyDown, containerRef, popupRef, } = useDatePicker({ value, onChange: setValue });
return ( <div ref={containerRef} style={{ position: "relative", display: "inline-block" }}> {/* Trigger */} <button onClick={handleToggle} style={{ padding: "8px 16px", border: "1px solid #d1d5db", borderRadius: 6, background: "white", cursor: "pointer", minWidth: 180, textAlign: "left", }} > {displayValue || locale.placeholder} </button>
{/* Popup */} {isOpen && ( <div ref={popupRef} onKeyDown={handleKeyDown} style={{ position: "absolute", top: "100%", left: 0, marginTop: 4, padding: 16, background: "white", border: "1px solid #e5e7eb", borderRadius: 8, boxShadow: "0 4px 12px rgba(0,0,0,0.1)", zIndex: 50, }} > {/* Header */} <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12, }} > <button onClick={handlePrevMonth}>{locale.prevMonth}</button> <span style={{ fontWeight: 600 }}>{locale.formatMonthYear(calendar.month)}</span> <button onClick={handleNextMonth}>{locale.nextMonth}</button> </div>
{/* Weekday headers */} <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 36px)", gap: 2, textAlign: "center", }} > {locale.weekdays.map((wd) => ( <span key={wd} style={{ fontSize: 12, color: "#9ca3af", padding: 4 }}> {wd} </span> ))} </div>
{/* Calendar grid */} <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 36px)", gap: 2 }}> {calendar.weeks.flat().map((day, i) => { if (!day) return <span key={i} />; const dp = getDayProps(day); return ( <button key={i} onClick={() => handleDateClick(day)} disabled={dp.isDisabled} style={{ width: 36, height: 36, borderRadius: "50%", border: dp.isFocused ? "2px solid #0ea5e9" : "none", background: dp.isSelected ? "#0ea5e9" : dp.isToday ? "#f0f9ff" : "transparent", color: dp.isSelected ? "white" : dp.isDisabled ? "#d1d5db" : "inherit", cursor: dp.isDisabled ? "not-allowed" : "pointer", fontWeight: dp.isToday ? 600 : 400, }} > {dp.day} </button> ); })} </div>
{/* Footer */} <div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 12 }}> {hasValue && ( <button onClick={handleClear} style={{ color: "#ef4444" }}> {locale.clear} </button> )} <button onClick={handleCancel}>{locale.cancel}</button> <button onClick={handleConfirm} disabled={!canConfirm} style={{ background: canConfirm ? "#0ea5e9" : "#e5e7eb", color: canConfirm ? "white" : "#9ca3af", padding: "4px 16px", borderRadius: 4, border: "none", }} > {locale.confirm} </button> </div> </div> )} </div> );}แนวคิดหลัก
getDayProps
ฟังก์ชัน getDayProps เป็นหัวใจหลักของการแสดงผลปฏิทิน สำหรับแต่ละเซลล์วันที่ ฟังก์ชันจะส่งคืนอ็อบเจกต์ DayProps ที่มีแฟล็กแบบ boolean/number 18 ตัวที่อธิบายสถานะของเซลล์:
const dp = getDayProps(day);
// สถานะการเลือกdp.isSelected; // วันที่ที่เลือกในปัจจุบันdp.isToday; // วันที่ปัจจุบัน
// สถานะช่วง (สำหรับตัวเลือกช่วง)dp.isInRange; // ระหว่างวันที่เริ่มต้นและสิ้นสุดdp.isRangeStart; // วันที่แรกของช่วงdp.isRangeEnd; // วันที่สุดท้ายของช่วงdp.isInHoverRange; // ในช่วงแสดงตัวอย่างเมื่อโฮเวอร์dp.isHoverTarget; // วันที่ที่โฮเวอร์อยู่dp.isRangeSingle; // ทั้งจุดเริ่มต้นและสิ้นสุดของช่วง (ช่วงวันเดียว)
// ตัวช่วยเชื่อมต่อภาพdp.hasLeftConnection; // เชื่อมต่อกับวันก่อนหน้า (สำหรับพื้นหลังของช่วง)dp.hasRightConnection; // เชื่อมต่อกับวันถัดไปdp.isConsecutiveRange; // เป็นส่วนหนึ่งของช่วงหลายวัน
// สถานะdp.isDisabled; // ถูกปิดใช้งานโดย minDate/maxDate/isDateUnavailabledp.isFocused; // มีโฟกัสจากคีย์บอร์ดdp.isOutsideDay; // เป็นของเดือนที่อยู่ติดกัน (เมื่อเปิดใช้งาน showOutsideDays)dp.isHighlighted; // อยู่ในอาร์เรย์ highlightDates
// ข้อมูลdp.date; // อ็อบเจกต์ Datedp.day; // เลขวันที่ของเดือนdp.dayOfWeek; // 0-6 (วันอาทิตย์-วันเสาร์)Refs สำหรับการคลิกภายนอก
hook จะส่งคืน containerRef และ popupRef ให้แนบสิ่งเหล่านี้เข้ากับองค์ประกอบ wrapper และ popup ของคุณ — hook ใช้สิ่งเหล่านี้เพื่อตรวจจับการคลิกภายนอกและปิด popup โดยอัตโนมัติ
การนำทางด้วยคีย์บอร์ด
handler handleKeyDown รองรับปุ่มลูกศร, Enter, Escape และ Tab สำหรับการนำทางด้วยคีย์บอร์ดเต็มรูปแบบ ให้แนบเข้ากับคอนเทนเนอร์ popup
Locale
สตริงทั้งหมดที่ผู้ใช้เห็นมาจากอ็อบเจกต์ locale คุณสามารถปรับแต่งสตริงใดก็ได้โดยส่งตัวเลือก locale บางส่วน:
const picker = useDatePicker({ value, onChange: setValue, locale: { confirm: "ตกลง", cancel: "ย้อนกลับ", placeholder: "เลือกวันที่...", },});ขั้นตอนถัดไป
- Hook Reference — ตัวเลือกและค่าที่ส่งคืนทั้งหมดสำหรับแต่ละ hook
- Contexts — ใช้ providers สำหรับรูปแบบส่วนประกอบแบบผสม (compound component patterns)
- Date Utilities — ฟังก์ชันช่วยเหลือสำหรับการจัดการวันที่