طراحی انتخابگر تاریخ سفارشی در React — به زبان ساده

۸۳ بازدید
آخرین به‌روزرسانی: ۲۰ شهریور ۱۴۰۲
زمان مطالعه: ۵ دقیقه
طراحی انتخابگر تاریخ سفارشی در React — به زبان ساده

در این مقاله ساده‌ترین روش برای ساخت یک انتخابگر تاریخ سفارشی در React توضیح داده شده است. در این مسیر از emotion/core@ (+) و datepicker-react/hooks@ (+) استفاده می‌کنیم.

نتیجه نهایی به صورت زیر خواهد بود:

انتخابگر تاریخ سفارشی در React

پیش‌فرض ما این است که شما با جاوا اسکریپت آشنا هستید و قبلاً کمی با 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@ (+) استفاده کنید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
front-end-weekly
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *