طراحی انتخابگر تاریخ سفارشی در React — به زبان ساده
در این مقاله سادهترین روش برای ساخت یک انتخابگر تاریخ سفارشی در React توضیح داده شده است. در این مسیر از emotion/core@ (+) و datepicker-react/hooks@ (+) استفاده میکنیم.
نتیجه نهایی به صورت زیر خواهد بود:
پیشفرض ما این است که شما با جاوا اسکریپت آشنا هستید و قبلاً کمی با React و قلابهای آن کار کردهاید. به جز آن هیچ پیشنیاز دیگری برای مطالعه این راهنما وجود ندارد.
گام 1: نصب وابستگیها
ابتدا باید وابستگیهای خود را نصب کنیم. چنان که پیشتر اشاره کردیم از emotion و datepicker-react/hooks@ استفاده میکنیم:
yarn add @emotion/core @datepicker-react/hooks
گام 2: چارچوب انتخابگر تاریخ
ما یک چارچوب (Context) برای اشتراک حالت انتخابگر تاریخ و callback-ها از طریق درخت کامپوننت انتخابگر تاریخ ایجاد خواهیم کرد. این Context روشی برای ارسال دادهها از طریق درخت کامپوننت بدون الزام به ارسال props به سمت پایین به صورت دستی در هر سطح تأمین میکند.
فایل datepickerContext.js
1import React from "react";
2
3export const datepickerContextDefaultValue = {
4 focusedDate: null,
5 isDateFocused: () => false,
6 isDateSelected: () => false,
7 isDateHovered: () => false,
8 isDateBlocked: () => false,
9 isFirstOrLastSelectedDate: () => false,
10 onDateFocus: () => {},
11 onDateHover: () => {},
12 onDateSelect: () => {}
13};
14
15export default React.createContext(datepickerContextDefaultValue);
کد فوق یک شیء Context ایجاد میکند که شامل حالت پیشفرض و Callback-ها است. زمانی که ریاکت یک کامپوننت را رندر میکند که در این شیء Context مشترک شده است، شروع به خواندن مقدار چارچوب جاری از نزدیکترین Provider مطابق بالا در درخت میکند. اگر یک Provider منطبق وجود نداشته باشند، Context جاری مقادیر پیشفرض را خواهد خواند.
گام 3: کامپوننت NavButton
در این گام یک دکمه ایجاد خواهیم کرد که به ما امکان میدهد تا در میان ماهها حرکت کنیم. یک کامپوننت Button دو props دریافت میکند که یکی children و دیگری callback-ی به نام onClick است.
فایل NavButton.js
1/** @jsx jsx */
2import { jsx } from "@emotion/core";
3
4export default function NavButton({ children, onClick }) {
5 return (
6 <button
7 type="button"
8 onClick={onClick}
9 css={{
10 border: "1px solid #929598",
11 background: "transparent",
12 padding: "8px",
13 fontSize: "12px"
14 }}
15 >
16 {children}
17 </button>
18 );
19}
گام 4: کامپوننت Datepicker
این همان کامپوننت مرکزی ما است که شامل منطق انتخابگر تاریخ ما است. دستکاری تاریخ در جاوا اسکریپت کار چندان سادهای نیست. به همین دلیل است که استفاده از کتابخانههایی مانند moment ،date-fns و luxon رواج زیادی دارد.
خوشبختانه ما نیازی به استفاده از هیچ کدام آنها نداریم چون از dapicker-react/hooks@ استفاده میکنیم که همه کارهای دشوار را به جای ما انجام میدهد. ما باید از قلاب صحیح استفاده کرده و آرگومانهای مناسب را ارسال کنیم.
dapicker-react/hooks@ سه قلاب اکسپورت شده دارد:
- useDatepicker
- useMonth
- useDay
از روی نام آنها میتوان کارکردشان را حدس زد. ما از useDatepicker در کامپوننت Datepicker استفاده خواهیم کرد. در ادامه کامپوننت Datepicker را برنامهنویسی میکنیم.
1. تعریف حالت
ابتدا باید تاریخهای آغاز و پایان را مدیریت کنیم که این کار با استفاده از قلاب useState قابل اجرا است.
فایل Datepicker.js
1import React, { useState } from "react";
2import { START_DATE } from "@datepicker-react/hooks";
3
4function Datepicker() {
5 const [state, setState] = useState({
6 startDate: null,
7 endDate: null,
8 focusedInput: START_DATE
9 });
10
11 return null
12}
13
14export default Datepicker;
در ادامه مقدار حالت اولیه را تعیین میکنیم. در آغاز یک تاریخ آغاز و پایان خالی داریم. همچنین در حالت شیء با focusedInput مواجه میشویم که باید تعیین شود و در غیر این صورت باید تاریخهای آغاز و پایان معلوم شوند.
2. قلاب useDatepicker
آرگومانهای مختلفی را که در بخش فوق دیدیم به قلاب useDatepicker ارسال میکنیم، اما صرفاً موارد الزام شده را میفرستیم. useDatepicker یک شیء بازگشت خواهد داد که شامل callback-ها و متغیرها است و برای مدیریت انتخابگر تاریخ نیاز داریم. برخی از آنها به Context ارسال میشوند که در گام 2 دیدیم.
فایل Datepicker.js
1import React, { useState } from "react";
2import { useDatepicker, START_DATE } from "@datepicker-react/hooks";
3
4function Datepicker() {
5 const [state, setState] = useState({
6 startDate: null,
7 endDate: null,
8 focusedInput: START_DATE
9 });
10 const {
11 firstDayOfWeek,
12 activeMonths,
13 isDateSelected,
14 isDateHovered,
15 isFirstOrLastSelectedDate,
16 isDateBlocked,
17 isDateFocused,
18 focusedDate,
19 onDateHover,
20 onDateSelect,
21 onDateFocus,
22 goToPreviousMonths,
23 goToNextMonths
24 } = useDatepicker({
25 startDate: state.startDate,
26 endDate: state.endDate,
27 focusedInput: state.focusedInput,
28 onDatesChange: handleDateChange
29 });
30
31 function handleDateChange(data) {
32 if (!data.focusedInput) {
33 setState({ ...data, focusedInput: START_DATE });
34 } else {
35 setState(data);
36 }
37 }
38
39 return null
40}
41
42export default Datepicker;
3. کامپوننت Datepicker در نهایت به صورت زیر در میآید
فایل Datepicker.js
1/** @jsx jsx */
2import { useState } from "react";
3import { useDatepicker, START_DATE } from "@datepicker-react/hooks";
4import { jsx } from "@emotion/core";
5import Month from "./Month";
6import NavButton from "./NavButton";
7import DatepickerContext from "./datepickerContext";
8
9function Datepicker() {
10 const [state, setState] = useState({
11 startDate: null,
12 endDate: null,
13 focusedInput: START_DATE
14 });
15 const {
16 firstDayOfWeek,
17 activeMonths,
18 isDateSelected,
19 isDateHovered,
20 isFirstOrLastSelectedDate,
21 isDateBlocked,
22 isDateFocused,
23 focusedDate,
24 onDateHover,
25 onDateSelect,
26 onDateFocus,
27 goToPreviousMonths,
28 goToNextMonths
29 } = useDatepicker({
30 startDate: state.startDate,
31 endDate: state.endDate,
32 focusedInput: state.focusedInput,
33 onDatesChange: handleDateChange
34 });
35
36 function handleDateChange(data) {
37 if (!data.focusedInput) {
38 setState({ ...data, focusedInput: START_DATE });
39 } else {
40 setState(data);
41 }
42 }
43
44 return (
45 <DatepickerContext.Provider
46 value={{
47 focusedDate,
48 isDateFocused,
49 isDateSelected,
50 isDateHovered,
51 isDateBlocked,
52 isFirstOrLastSelectedDate,
53 onDateSelect,
54 onDateFocus,
55 onDateHover
56 }}
57 >
58 <div>
59 <strong>Focused input: </strong>
60 {state.focusedInput}
61 </div>
62 <div>
63 <strong>Start date: </strong>
64 {state.startDate && state.startDate.toLocaleString()}
65 </div>
66 <div>
67 <strong>End date: </strong>
68 {state.endDate && state.endDate.toLocaleString()}
69 </div>
70
71 <NavButton onClick={goToPreviousMonths}>Previous</NavButton>
72 <NavButton onClick={goToNextMonths}>Next</NavButton>
73
74 <div
75 css={{
76 display: "grid",
77 margin: "32px 0 0",
78 gridTemplateColumns: `repeat(${activeMonths.length}, 300px)`,
79 gridGap: "0 64px"
80 }}
81 >
82 {activeMonths.map(month => (
83 <Month
84 key={`${month.year}-${month.month}`}
85 year={month.year}
86 month={month.month}
87 firstDayOfWeek={firstDayOfWeek}
88 />
89 ))}
90 </div>
91 </DatepickerContext.Provider>
92 );
93}
94
95export default Datepicker;
گام 5: کامپوننت Month
احتمالاً کامپوننت Month را که درون کامپوننت Datepicker قرار دارد مشاهده کردهاید. این کامپوننت سه prop به نامهای year ،month و firstDayOfWeek میگیرد. همه این موارد به قلاب useMonth ارسال میشوند که روزهای ماه جاری، برچسب هفته و برچسب ماه را بازگشت میدهند:
فایل Month.js
1/** @jsx jsx */
2import { useMonth } from "@datepicker-react/hooks";
3import { jsx } from "@emotion/core";
4import Day from "./Day";
5
6function Month({ year, month, firstDayOfWeek }) {
7 const { days, weekdayLabels, monthLabel } = useMonth({
8 year,
9 month,
10 firstDayOfWeek
11 });
12
13 return (
14 <div>
15 <div css={{ textAlign: "center", margin: "0 0 16px" }}>
16 <strong>{monthLabel}</strong>
17 </div>
18 <div
19 css={{
20 display: "grid",
21 gridTemplateColumns: "repeat(7, 1fr)",
22 justifyContent: "center",
23 marginBottom: "10px",
24 fontSize: "10px"
25 }}
26 >
27 {weekdayLabels.map(dayLabel => (
28 <div css={{ textAlign: "center" }} key={dayLabel}>
29 {dayLabel}
30 </div>
31 ))}
32 </div>
33 <div
34 css={{
35 display: "grid",
36 gridTemplateColumns: "repeat(7, 1fr)",
37 justifyContent: "center"
38 }}
39 >
40 {days.map((day, index) => {
41 if (typeof day === "object") {
42 return (
43 <Day
44 date={day.date}
45 key={day.date.toString()}
46 dayLabel={day.dayLabel}
47 />
48 );
49 }
50
51 return <div key={index} />;
52 })}
53 </div>
54 </div>
55 );
56}
57
58export default Month;
گام 6: کامپوننت Day
اینک به پایان کار خود نزدیک شدهایم و کافی است یک کامپوننت دیگر به نام Day اضافه کنیم. این کامپوننت prop-هایی به نام date و dayLabel میگیرد. ما prop به نام date را همراه با callback-هایی از چارچوب انتخابگر تاریخ به قلاب useDay ارسال میکنیم.
فایل Day.js
1/** @jsx jsx */
2import { useRef, useContext } from "react";
3import { useDay } from "@datepicker-react/hooks";
4import { jsx } from "@emotion/core";
5import DatepickerContext from "./datepickerContext";
6
7function getColor(
8 isSelected,
9 isSelectedStartOrEnd,
10 isWithinHoverRange,
11 isDisabled
12) {
13 return ({
14 selectedFirstOrLastColor,
15 normalColor,
16 selectedColor,
17 rangeHoverColor,
18 disabledColor
19 }) => {
20 if (isSelectedStartOrEnd) {
21 return selectedFirstOrLastColor;
22 } else if (isSelected) {
23 return selectedColor;
24 } else if (isWithinHoverRange) {
25 return rangeHoverColor;
26 } else if (isDisabled) {
27 return disabledColor;
28 } else {
29 return normalColor;
30 }
31 };
32}
33
34function Day({ dayLabel, date }) {
35 const dayRef = useRef(null);
36 const {
37 focusedDate,
38 isDateFocused,
39 isDateSelected,
40 isDateHovered,
41 isDateBlocked,
42 isFirstOrLastSelectedDate,
43 onDateSelect,
44 onDateFocus,
45 onDateHover
46 } = useContext(DatepickerContext);
47 const {
48 isSelected,
49 isSelectedStartOrEnd,
50 isWithinHoverRange,
51 disabledDate,
52 onClick,
53 onKeyDown,
54 onMouseEnter,
55 tabIndex
56 } = useDay({
57 date,
58 focusedDate,
59 isDateFocused,
60 isDateSelected,
61 isDateHovered,
62 isDateBlocked,
63 isFirstOrLastSelectedDate,
64 onDateFocus,
65 onDateSelect,
66 onDateHover,
67 dayRef
68 });
69
70 if (!dayLabel) {
71 return <div />;
72 }
73
74 const getColorFn = getColor(
75 isSelected,
76 isSelectedStartOrEnd,
77 isWithinHoverRange,
78 disabledDate
79 );
80
81 return (
82 <button
83 onClick={onClick}
84 onKeyDown={onKeyDown}
85 onMouseEnter={onMouseEnter}
86 tabIndex={tabIndex}
87 type="button"
88 ref={dayRef}
89 css={{
90 padding: "8px",
91 border: 0,
92 color: getColorFn({
93 selectedFirstOrLastColor: "#FFFFFF",
94 normalColor: "#001217",
95 selectedColor: "#FFFFFF",
96 rangeHoverColor: "#FFFFFF",
97 disabledColor: "#808285"
98 }),
99 background: getColorFn({
100 selectedFirstOrLastColor: "#00aeef",
101 normalColor: "#FFFFFF",
102 selectedColor: "#71c9ed",
103 rangeHoverColor: "#71c9ed",
104 disabledColor: "#FFFFFF"
105 })
106 }}
107 >
108 {dayLabel}
109 </button>
110 );
111}
112
113export default Day;
سخن پایانی
اینک شما موفق شدهاید یک کامپوننت ابتدایی انتخابگر تاریخ بسازید و بدین ترتیب درکی مقدماتی از context و قلابهای React به دست آوردهاید.
ما تلاش کردیم نشان دهیم که انجام این کار با بهرهگیری از کتابخانه datepicker-react/hooks@ تا چه حد آسان است. اگر میخواهید آن را به نوع تقویمهای دیگر توسعه دهید، آن را دسترسپذیر بسازید و یا به حالت مناسب برای موبایل درآورید، میتوانید از کتابخانه datepicker-react/style@ (+) استفاده کنید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- آموزش ریاکت (React) — مجموعه مقالات مجله فرادرس
- تعریف حلقه روی آرایهها در React — راهنمای کاربردی
- ریاکت (React) — راهنمای جامع برای شروع به کار
==