delete 4 files and update 1 file

master
mahdi 3 years ago
parent 2b3e436156
commit 8f5a50f690
  1. 245
      src/components/Datepicker/CalendarComponents/Calendar.js
  2. 672
      src/components/Datepicker/CalendarComponents/DatePicker.css
  3. 203
      src/components/Datepicker/CalendarComponents/DatePicker.js
  4. 93
      src/components/Datepicker/CalendarComponents/DatePickerInput.js
  5. 399
      src/components/Datepicker/CalendarComponents/components/DaysList.js
  6. 205
      src/components/Datepicker/CalendarComponents/components/Header.js
  7. 74
      src/components/Datepicker/CalendarComponents/components/MonthSelector.js
  8. 99
      src/components/Datepicker/CalendarComponents/components/YearSelector.js
  9. 4
      src/components/Datepicker/CalendarComponents/components/index.js
  10. 3
      src/components/Datepicker/CalendarComponents/index.js
  11. 104
      src/components/Datepicker/CalendarComponents/shared/constants.js
  12. 69
      src/components/Datepicker/CalendarComponents/shared/generalUtils.js
  13. 10
      src/components/Datepicker/CalendarComponents/shared/hooks.js
  14. 44
      src/components/Datepicker/CalendarComponents/shared/keyboardNavigation.js
  15. 85
      src/components/Datepicker/CalendarComponents/shared/localeLanguages.js
  16. 61
      src/components/Datepicker/CalendarComponents/shared/localeUtils.js
  17. 33
      src/components/Datepicker/CalendarComponents/shared/sliderHelpers.js
  18. 0
      src/components/Datepicker/DateRangePicker/index.js
  19. 3
      src/components/Datepicker/RangeDatePicker/index.js
  20. 0
      src/components/Datepicker/RangeDatePicker/index.scss
  21. 2
      src/components/Datepicker/index.js
  22. 8
      src/redux/data.js

@ -0,0 +1,245 @@
import React, { useState, useRef, useEffect } from "react";
import {
getDateAccordingToMonth,
shallowClone,
getValueType,
} from "./shared/generalUtils";
import {
TYPE_SINGLE_DATE,
TYPE_RANGE,
TYPE_MUTLI_DATE,
} from "./shared/constants";
import { useLocaleUtils, useLocaleLanguage } from "./shared/hooks";
import { Header, MonthSelector, YearSelector, DaysList } from "./components";
const Calendar = ({
value,
onChange,
onDisabledDayError,
calendarClassName,
calendarTodayClassName,
calendarSelectedDayClassName,
calendarRangeStartClassName,
calendarRangeBetweenClassName,
calendarRangeEndClassName,
disabledDays,
colorPrimary,
colorPrimaryLight,
slideAnimationDuration,
minimumDate,
maximumDate,
selectorStartingYear,
selectorEndingYear,
locale,
shouldHighlightWeekends,
renderFooter,
customDaysClassName,
events,
renderEvents,
type,
}) => {
const calendarElement = useRef(null);
const [mainState, setMainState] = useState({
activeDate: null,
monthChangeDirection: "",
isMonthSelectorOpen: false,
isYearSelectorOpen: false,
});
const [eventInFooter, setEventInFooter] = useState(null);
useEffect(() => {
const handleKeyUp = ({ key }) => {
/* istanbul ignore else */
if (key === "Tab")
calendarElement.current.classList.remove("-noFocusOutline");
};
calendarElement.current.addEventListener("keyup", handleKeyUp, false);
return () => {
// calendarElement.current.removeEventListener('keyup', handleKeyUp, false);
};
});
const { getToday } = useLocaleUtils(locale);
const { weekDays: weekDaysList, isRtl } = useLocaleLanguage(locale);
const today = getToday();
const createStateToggler = (property) => () => {
setMainState({ ...mainState, [property]: !mainState[property] });
};
const toggleMonthSelector = createStateToggler("isMonthSelectorOpen");
const toggleYearSelector = createStateToggler("isYearSelectorOpen");
const getComputedActiveDate = () => {
const valueType = getValueType(value);
if (valueType === TYPE_MUTLI_DATE && value.length)
return shallowClone(value[0]);
if (valueType === TYPE_SINGLE_DATE && value) return shallowClone(value);
if (valueType === TYPE_RANGE && value.from) return shallowClone(value.from);
return shallowClone(today);
};
const activeDate = mainState.activeDate
? shallowClone(mainState.activeDate)
: getComputedActiveDate();
const weekdays = weekDaysList.map((weekDay) => (
<abbr
key={weekDay.name}
title={weekDay.name}
className={`Calendar__weekDay ${type}`}
>
{type == "dashboard" ? weekDay.name : weekDay.short}
</abbr>
));
const handleMonthChange = (direction) => {
setMainState({
...mainState,
monthChangeDirection: direction,
});
};
const updateDate = () => {
setMainState({
...mainState,
activeDate: getDateAccordingToMonth(
activeDate,
mainState.monthChangeDirection
),
monthChangeDirection: "",
});
};
const selectMonth = (newMonthNumber) => {
setMainState({
...mainState,
activeDate: { ...activeDate, month: newMonthNumber },
isMonthSelectorOpen: false,
});
};
const selectYear = (year) => {
setMainState({
...mainState,
activeDate: { ...activeDate, year },
isYearSelectorOpen: false,
});
};
return (
<div
className={`Calendar ${type} -noFocusOutline ${calendarClassName} -${
isRtl ? "rtl" : "ltr"
}`}
role="grid"
style={{
"--cl-color-primary": colorPrimary,
"--cl-color-primary-light": colorPrimaryLight,
"--animation-duration": slideAnimationDuration,
}}
ref={calendarElement}
>
<Header
maximumDate={maximumDate}
minimumDate={minimumDate}
activeDate={activeDate}
onMonthChange={handleMonthChange}
onMonthSelect={toggleMonthSelector}
onYearSelect={toggleYearSelector}
monthChangeDirection={mainState.monthChangeDirection}
isMonthSelectorOpen={mainState.isMonthSelectorOpen}
isYearSelectorOpen={mainState.isYearSelectorOpen}
locale={locale}
type={type}
/>
<MonthSelector
isOpen={mainState.isMonthSelectorOpen}
activeDate={activeDate}
onMonthSelect={selectMonth}
maximumDate={maximumDate}
minimumDate={minimumDate}
locale={locale}
/>
<YearSelector
isOpen={mainState.isYearSelectorOpen}
activeDate={activeDate}
onYearSelect={selectYear}
selectorStartingYear={selectorStartingYear}
selectorEndingYear={selectorEndingYear}
maximumDate={maximumDate}
minimumDate={minimumDate}
locale={locale}
/>
<div className={`Calendar__weekDays ${type}`}>{weekdays}</div>
<DaysList
activeDate={activeDate}
value={value}
monthChangeDirection={mainState.monthChangeDirection}
onSlideChange={updateDate}
disabledDays={disabledDays}
onDisabledDayError={onDisabledDayError}
minimumDate={minimumDate}
maximumDate={maximumDate}
onChange={onChange}
calendarTodayClassName={calendarTodayClassName}
calendarSelectedDayClassName={calendarSelectedDayClassName}
calendarRangeStartClassName={calendarRangeStartClassName}
calendarRangeEndClassName={calendarRangeEndClassName}
calendarRangeBetweenClassName={calendarRangeBetweenClassName}
locale={locale}
shouldHighlightWeekends={shouldHighlightWeekends}
customDaysClassName={customDaysClassName}
events={events}
renderFooter={renderFooter}
renderEvents={renderEvents}
isQuickSelectorOpen={
mainState.isYearSelectorOpen || mainState.isMonthSelectorOpen
}
showEvent={(event) => setEventInFooter(event)}
type={type}
/>
<div className="Calendar__footer">
<div className={`footer__Events ${type}`}>
{type == "dashboard" ? (
<>
<div className="text-xl font-black text-blue-400">
رویداد ها:{" "}
</div>
<p className="text-blue-300 font-sansbold">
{eventInFooter
? eventInFooter
: "رویدادی در این روز وجود ندارد!"}
</p>
</>
) : (
eventInFooter
)}
</div>
{renderFooter()}
</div>
</div>
);
};
Calendar.defaultProps = {
minimumDate: null,
maximumDate: null,
colorPrimary: "#0eca2d",
colorPrimaryLight: "#cff4d5",
slideAnimationDuration: "0.4s",
calendarClassName: "",
locale: "en",
value: null,
renderFooter: () => null,
renderEvents: () => null,
customDaysClassName: [],
events: [],
};
export default Calendar;

@ -0,0 +1,672 @@
.DatePicker {
position: relative;
display: inline-block;
z-index: 100;
}
.DatePicker__input {
background: #fff;
border: 1px solid #ddd;
padding: 0.4em 0.8em;
font-family: inherit;
text-align: center;
font-size: 12px;
}
.DatePicker__input.-rtl {
direction: rtl;
}
.DatePicker__input::placeholder {
color: #979797;
}
.DatePicker__calendarContainer.-top + .DatePicker__calendarArrow {
top: auto;
bottom: calc(100% + 10px);
transform: translateY(-2.5rem) rotate(180deg);
animation: fadeArrowFlipped 0.3s forwards;
}
.DatePicker__calendarContainer {
position: absolute;
top: calc(100% + 20px);
left: 50%;
transform: translateX(-50%);
}
.DatePicker__calendarContainer.-top {
top: auto;
bottom: calc(100% + 20px);
}
.Calendar,
.Calendar * {
margin: 0;
padding: 0;
box-sizing: border-box;
direction: ltr;
}
.Calendar,
.Calendar.-rtl * {
direction: rtl;
}
.DatePicker__calendarArrow {
position: absolute;
width: 0;
height: 0;
top: calc(100% + 10px);
left: 0;
right: 0;
margin: 0 auto;
border-style: solid;
z-index: 10;
border-width: 0 10px 10px 10px;
border-color: transparent transparent #fff transparent;
}
.Calendar {
--cl-color-black: #444444;
--cl-color-disabled: #d4d4d4;
--cl-color-error: #ff2929;
font-size: 10px;
background: #fff;
box-shadow: 0 1em 4em rgba(0, 0, 0, 0.07);
border-radius: 1em;
position: relative;
user-select: none;
padding-top: 1.2em;
display: flex;
flex-direction: column;
width: 33em;
z-index: 10;
max-width: 90vw;
min-height: 36.7em;
}
.DatePicker .Calendar,
.DatePicker__calendarArrow {
transform: translateY(2.5em);
opacity: 0;
animation: fadeCalendar 0.3s forwards;
}
.DatePicker__calendarContainer.-top .Calendar {
transform: translateY(-2.5em);
}
.Calendar.-noFocusOutline *:focus {
outline: none !important;
}
.Calendar > :not(.Calendar__footer) button {
font-family: inherit;
background: transparent;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
outline: none;
}
.Calendar__header {
display: flex;
color: var(--cl-color-black);
padding: 2em 2.9em;
align-items: center;
overflow: hidden;
}
.Calendar__monthArrowWrapper {
line-height: 0;
font-size: 1em;
padding: 3px;
position: relative;
border: none;
z-index: 1;
opacity: 1;
transition: 0.2s;
}
.Calendar__monthArrowWrapper:focus {
outline: 1px dashed rgba(0, 0, 0, 0.4);
outline-offset: 2px;
}
.Calendar__monthArrowWrapper:disabled,
.Calendar__monthArrowWrapper.-hidden {
opacity: 0;
pointer-events: none;
}
.Calendar__monthArrowWrapper.-left {
transform: rotate(90deg);
}
.Calendar.-rtl .Calendar__monthArrowWrapper.-left {
transform: rotate(-90deg);
}
.Calendar__monthArrowWrapper.-right {
transform: rotate(-90deg);
}
.Calendar.-rtl .Calendar__monthArrowWrapper.-right {
transform: rotate(90deg);
}
.Calendar__monthArrowWrapper:active .Calendar__monthArrow {
transform: scale(0.7);
}
.Calendar__monthArrow {
border-radius: 50%;
transition: var(--animation-duration) transform;
pointer-events: none;
background-repeat: no-repeat;
display: block;
width: 1.7em;
height: 1.7em;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg class='nc-icon-wrapper' fill='%23000000'%3E%3Cdefs stroke='none'%3E%3C/defs%3E%3Cpath class='cls-1' d='M12 23.25V.75' fill='none' stroke='%23000000' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5px'%3E%3C/path%3E%3Cpath class='cls-2' d='M22.5 11.25L12 .75 1.5 11.25' fill='none' stroke='%23000000' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5px' fill-rule='evenodd'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
background-size: 100% 100%;
}
.Calendar__monthYearContainer {
flex: 1;
position: relative;
}
.Calendar__monthYear {
font-size: 1.6em;
font-weight: 500;
display: flex;
align-items: center;
position: absolute;
top: 0;
bottom: 0;
left: 50%;
will-change: transform, opacity;
backface-visibility: hidden;
transform: translateZ(0);
transition: var(--animation-duration);
line-height: 1;
}
.Calendar__monthYear.-hiddenNext {
opacity: 0;
transform: translateX(50%);
}
.Calendar.-rtl .Calendar__monthYear.-hiddenNext {
transform: translateX(-150%);
}
.Calendar__monthYear.-hiddenPrevious {
opacity: 0;
transform: translateX(-150%);
}
.Calendar.-rtl .Calendar__monthYear.-hiddenPrevious {
transform: translateX(50%);
}
.Calendar__monthYear.-shown {
opacity: 1;
margin-top: auto;
margin-bottom: auto;
transform: translateX(-50%);
}
.Calendar__monthYear.-shownAnimated {
animation: var(--animation-duration) fadeTextToCenter forwards;
}
.Calendar__monthYear > * {
padding: 0.2em 0.5em;
border: 1px solid transparent;
transition: var(--animation-duration);
font-size: 1.05em;
display: flex;
justify-content: center;
align-items: center;
transform: translateX(0) scale(0.95);
will-change: transform;
border-radius: 5px;
}
.Calendar__monthYear:not(.-shown) > *,
.Calendar__monthYear > *.-hidden {
cursor: default;
pointer-events: none;
}
.Calendar__monthText {
margin-left: -0.3em;
}
.Calendar__yearText:last-child {
margin-right: -0.3em;
}
.Calendar__monthYear.-shown > *:hover,
.Calendar:not(.-noFocusOutline) .Calendar__monthYear.-shown > *:focus,
.Calendar__monthYear > *.-activeBackground {
background: #f5f5f5;
}
.Calendar__monthText:hover {
transform: translateX(-0.2em) scale(0.95);
}
.Calendar.-rtl .Calendar__monthText:hover {
transform: translateX(0.2em) scale(0.95);
}
.Calendar__yearText:hover {
transform: translateX(0.2em) scale(0.95);
}
.Calendar.-rtl .Calendar__yearText:hover {
transform: translateX(-0.2em) scale(0.95);
}
.Calendar__monthYear .Calendar__yearText.-hidden {
transform: translateX(50%);
opacity: 0;
}
.Calendar.-rtl .Calendar__monthYear .Calendar__yearText.-hidden {
transform: translateX(-50%);
}
.Calendar__monthYear .Calendar__monthText.-hidden {
transform: translateX(-50%);
opacity: 0;
}
.Calendar.-rtl .Calendar__monthYear .Calendar__monthText.-hidden {
transform: translateX(50%);
}
.Calendar__monthYear:not(.-shown) > * {
pointer-events: none;
}
.Calendar__monthSelectorAnimationWrapper,
.Calendar__yearSelectorAnimationWrapper {
position: absolute;
width: 100%;
height: 80%;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.Calendar__monthSelectorWrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.Calendar__monthSelector {
padding: 0 2.5em;
align-content: center;
padding-bottom: 2em;
}
.Calendar__monthSelector,
.Calendar__yearSelector {
display: flex;
flex-wrap: wrap;
position: relative;
z-index: 2;
background-color: #fff;
transform: translateY(-150%);
will-change: transform;
transition: 0.6s;
height: 100%;
}
.Calendar__yearSelectorWrapper {
width: 100%;
height: 100%;
}
.Calendar__yearSelectorWrapper::after,
.Calendar__yearSelectorWrapper::before {
content: '';
width: 100%;
height: 5em;
position: absolute;
left: 0;
opacity: 0;
transition: 0.4s;
transition-delay: 0.2s;
}
.Calendar__yearSelectorWrapper::after {
background-image: linear-gradient(to bottom, #fff, #fff 10%, rgba(245, 245, 245, 0));
top: -0.1em;
}
.Calendar__yearSelectorWrapper::before {
background-image: linear-gradient(to top, #fff, #fff 10%, rgba(245, 245, 245, 0));
bottom: 0;
}
.Calendar__yearSelectorWrapper.-faded::after,
.Calendar__yearSelectorWrapper.-faded::before {
opacity: 1;
z-index: 3;
}
.Calendar__yearSelector {
align-content: flex-start;
scrollbar-width: 0;
overflow: scroll;
position: relative;
width: 100%;
padding: 5em 2em;
-ms-overflow-style: none;
}
.Calendar__yearSelector::-webkit-scrollbar {
display: none;
}
.Calendar__yearSelectorItem {
width: 25%;
display: flex;
justify-content: center;
}
.Calendar__yearSelectorItem:not(:nth-child(-n + 4)) {
margin-top: 1.5em;
}
.Calendar__yearSelectorText {
border: none;
font-size: 1.4em;
min-width: 85%;
padding: 0.2em 0.5em;
border-radius: 8.5px;
}
.Calendar__monthSelector.-open,
.Calendar__yearSelector.-open {
transform: translateY(0);
}
.Calendar__yearSelectorText:focus,
.Calendar__monthSelectorItemText:focus {
outline: 1px dashed rgba(0, 0, 0, 0.4);
outline-offset: 2px;
}
.Calendar__monthSelectorItem {
width: calc(100% / 3);
display: flex;
justify-content: center;
}
.Calendar__monthSelectorItem:not(:nth-child(-n + 3)) {
margin-top: 2em;
}
.Calendar__monthSelectorItemText {
border: none;
padding: 0.4em 0.4em;
border-radius: 8.5px;
font-size: 1.3em;
min-width: 70%;
transition: 0.3s;
}
.Calendar__monthSelectorItem:not(.-active) .Calendar__monthSelectorItemText:not(:disabled):hover,
.Calendar__yearSelectorItem:not(.-active) .Calendar__yearSelectorText:not(:disabled):hover {
background: #f5f5f5;
}
.Calendar__monthSelectorItemText:disabled,
.Calendar__yearSelectorText:disabled {
opacity: 0.5;
cursor: default;
}
.Calendar__monthSelectorItem.-active .Calendar__monthSelectorItemText,
.Calendar__yearSelectorItem.-active .Calendar__yearSelectorText {
background-color: var(--cl-color-primary);
color: #fff;
}
.Calendar__weekDays {
display: flex;
justify-content: space-between;
color: var(--cl-color-disabled);
font-size: 1.2em;
margin-bottom: 0.7em;
padding: 0 2.6em;
position: relative;
}
.Calendar__weekDay {
display: block;
width: calc(100% / 7);
text-align: center;
text-decoration: none;
}
.Calendar__sectionWrapper {
position: relative;
min-height: 25.8em;
overflow: hidden;
}
.Calendar__section {
display: flex;
flex-direction: column;
padding: 0 3.2em;
position: absolute;
color: var(--cl-color-black);
top: 0;
padding-top: 0.5em;
left: 0;
width: 100%;
will-change: transform, opacity;
transform: translateZ(0);
backface-visibility: hidden;
transition: var(--animation-duration);
}
.Calendar__section.-hiddenPrevious {
opacity: 0.5;
transform: translateX(-90%);
}
.Calendar.-rtl .Calendar__section.-hiddenPrevious {
transform: translateX(90%);
}
.Calendar__section.-hiddenNext {
opacity: 0.5;
transform: translateX(90%);
}
.Calendar.-rtl .Calendar__section.-hiddenNext {
transform: translateX(-90%);
}
.Calendar__section.-shown {
opacity: 1;
transform: translateX(0);
}
.Calendar__section.-shownAnimated {
animation: var(--animation-duration) FadeContentToCenter forwards;
}
.Calendar__weekRow {
display: flex;
width: 100%;
}
.Calendar__day {
display: block;
width: calc(100% / 7);
text-align: center;
padding: calc(0.25em - 1px) 0;
font-size: 1.6em;
border-radius: 50%;
transition: 0.2s;
border: 1px solid transparent;
margin-bottom: 0.3em;
color: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.Calendar__day:focus {
outline: 1px dashed rgba(0, 0, 0, 0.4);
outline-offset: 2px;
}
.Calendar__day.-ltr {
min-height: 2.6em;
font-size: 1.45em;
}
.Calendar__day.-rtl {
font-size: 1.55em;
height: 2.45em;
}
.Calendar__day:not(.-blank):not(.-selectedStart):not(.-selectedEnd):not(.-selectedBetween):not(.-selected):hover {
background: #eaeaea;
border-radius: 50%;
color: var(--cl-color-black);
border-color: transparent;
}
.Calendar__day.-selected,
.Calendar__day.-selectedStart,
.Calendar__day.-selectedEnd {
background: var(--cl-color-primary);
color: #fff;
}
.Calendar__day.-ltr.-selectedStart {
border-radius: 0;
border-top-left-radius: 100em;
border-bottom-left-radius: 100em;
}
.Calendar__day.-rtl.-selectedStart {
border-radius: 0;
border-top-right-radius: 100em;
border-bottom-right-radius: 100em;
}
.Calendar__day.-selectedBetween {
background: var(--cl-color-primary-light);
color: var(--cl-color-primary);
border-radius: 0;
}
.Calendar__day.-ltr.-selectedEnd {
border-top-right-radius: 100em;
border-bottom-right-radius: 100em;
}
.Calendar__day.-rtl.-selectedEnd {
border-top-left-radius: 100em;
border-bottom-left-radius: 100em;
}
.Calendar__day.-weekend:not(.-selected):not(.-blank):not(.-selectedStart):not(.-selectedEnd):not(.-selectedBetween) {
color: var(--cl-color-error);
}
.Calendar__day.-weekend.-today:not(.-selectedStart):not(.-selectedEnd):not(.-selectedBetween)::after {
background: var(--cl-color-error);
}
.Calendar__day.-disabled {
color: var(--cl-color-disabled) !important;
background: transparent !important;
cursor: default !important;
}
.Calendar__day.-selected {
border-radius: 50%;
}
.Calendar__day.-today:not(.-selectedStart):not(.-selectedEnd):not(.-selectedBetween) {
font-weight: 600;
color: var(--cl-color-black);
color: #000;
position: relative;
}
.Calendar__day.-today:not(.-selectedStart):not(.-selectedEnd):not(.-selectedBetween)::after {
content: '';
position: absolute;
bottom: 0.2em;
display: block;
width: 0.6em;
height: 1px;
background: #000;
left: 50%;
opacity: 0.5;
transform: translateX(-50%);
transition: 0.2s;
}
.Calendar__day.-today:hover:not(.-selectedStart):not(.-selectedEnd):not(.-selectedBetween)::after {
opacity: 0;
}
.Calendar__day.-blank {
color: transparent;
cursor: default;
pointer-events: none;
}
.Calendar__footer {
position: relative;
z-index: 1;
}
@keyframes fadeCalendar {
from {
opacity: 0;
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeArrowFlipped {
from {
opacity: 0;
}
to {
opacity: 1;
transform: translateY(0) rotate(180deg);
}
}
@keyframes fadeTextToCenter {
to {
opacity: 1;
transform: translateX(-50%);
}
}
@keyframes FadeContentToCenter {
to {
opacity: 1;
transform: translateX(0);
}
}

@ -0,0 +1,203 @@
import React, { useState, useEffect, useRef, useLayoutEffect } from 'react';
import Calendar from './Calendar';
import DatePickerInput from './DatePickerInput';
import { getValueType } from './shared/generalUtils';
import { TYPE_SINGLE_DATE, TYPE_MUTLI_DATE, TYPE_RANGE } from './shared/constants';
const DatePicker = ({
value,
onChange,
formatInputText,
inputPlaceholder,
inputClassName,
inputName,
renderInput,
wrapperClassName,
calendarClassName,
calendarTodayClassName,
calendarSelectedDayClassName,
calendarRangeStartClassName,
calendarRangeBetweenClassName,
calendarRangeEndClassName,
calendarPopperPosition,
disabledDays,
onDisabledDayError,
colorPrimary,
colorPrimaryLight,
slideAnimationDuration,
minimumDate,
maximumDate,
selectorStartingYear,
selectorEndingYear,
locale,
shouldHighlightWeekends,
renderFooter,
customDaysClassName,
}) => {
const calendarContainerElement = useRef(null);
const inputElement = useRef(null);
const shouldPreventToggle = useRef(false);
const [isCalendarOpen, setCalendarVisiblity] = useState(false);
useEffect(() => {
const handleBlur = () => {
setCalendarVisiblity(false);
};
window.addEventListener('blur', handleBlur, false);
return () => {
window.removeEventListener('blur', handleBlur, false);
};
}, []);
// handle input focus/blur
useEffect(() => {
const valueType = getValueType(value);
if (valueType === TYPE_MUTLI_DATE) return; // no need to close the calendar
const shouldCloseCalendar =
valueType === TYPE_SINGLE_DATE ? !isCalendarOpen : !isCalendarOpen && value.from && value.to;
if (shouldCloseCalendar) inputElement.current.blur();
}, [value, isCalendarOpen]);
const handleBlur = e => {
e.persist();
if (!isCalendarOpen) return;
const isInnerElementFocused = calendarContainerElement.current.contains(e.relatedTarget);
if (shouldPreventToggle.current) {
shouldPreventToggle.current = false;
inputElement.current.focus();
} else if (isInnerElementFocused && e.relatedTarget) {
e.relatedTarget.focus();
} else {
setCalendarVisiblity(false);
}
};
const openCalendar = () => {
if (!shouldPreventToggle.current) setCalendarVisiblity(true);
};
// Keep the calendar in the screen bounds if input is near the window edges
useLayoutEffect(() => {
if (!isCalendarOpen) return;
const { left, width, height, top } = calendarContainerElement.current.getBoundingClientRect();
const { clientWidth, clientHeight } = document.documentElement;
const isOverflowingFromRight = left + width > clientWidth;
const isOverflowingFromLeft = left < 0;
const isOverflowingFromBottom = top + height > clientHeight;
const getLeftStyle = () => {
const overflowFromRightDistance = left + width - clientWidth;
if (!isOverflowingFromRight && !isOverflowingFromLeft) return;
const overflowFromLeftDistance = Math.abs(left);
const rightPosition = isOverflowingFromLeft ? overflowFromLeftDistance : 0;
const leftStyle = isOverflowingFromRight
? `calc(50% - ${overflowFromRightDistance}px)`
: `calc(50% + ${rightPosition}px)`;
return leftStyle;
};
calendarContainerElement.current.style.left = getLeftStyle();
if (
(calendarPopperPosition === 'auto' && isOverflowingFromBottom) ||
calendarPopperPosition === 'top'
) {
calendarContainerElement.current.classList.add('-top');
}
}, [isCalendarOpen]);
const handleCalendarChange = newValue => {
const valueType = getValueType(value);
onChange(newValue);
if (valueType === TYPE_SINGLE_DATE) setCalendarVisiblity(false);
else if (valueType === TYPE_RANGE && newValue.from && newValue.to) setCalendarVisiblity(false);
};
const handleKeyUp = ({ key }) => {
switch (key) {
case 'Enter':
setCalendarVisiblity(true);
break;
case 'Escape':
setCalendarVisiblity(false);
shouldPreventToggle.current = true;
break;
}
};
useEffect(() => {
if (!isCalendarOpen && shouldPreventToggle.current) {
inputElement.current.focus();
shouldPreventToggle.current = false;
}
}, [shouldPreventToggle, isCalendarOpen]);
return (
<div
onFocus={openCalendar}
onBlur={handleBlur}
onKeyUp={handleKeyUp}
className={`DatePicker ${wrapperClassName}`}
role="presentation"
>
<DatePickerInput
ref={inputElement}
formatInputText={formatInputText}
value={value}
inputPlaceholder={inputPlaceholder}
inputClassName={inputClassName}
renderInput={renderInput}
inputName={inputName}
locale={locale}
/>
{isCalendarOpen && (
<>
<div
ref={calendarContainerElement}
className="DatePicker__calendarContainer"
data-testid="calendar-container"
role="presentation"
onMouseDown={() => {
shouldPreventToggle.current = true;
}}
>
<Calendar
value={value}
onChange={handleCalendarChange}
calendarClassName={calendarClassName}
calendarTodayClassName={calendarTodayClassName}
calendarSelectedDayClassName={calendarSelectedDayClassName}
calendarRangeStartClassName={calendarRangeStartClassName}
calendarRangeBetweenClassName={calendarRangeBetweenClassName}
calendarRangeEndClassName={calendarRangeEndClassName}
disabledDays={disabledDays}
colorPrimary={colorPrimary}
colorPrimaryLight={colorPrimaryLight}
slideAnimationDuration={slideAnimationDuration}
onDisabledDayError={onDisabledDayError}
minimumDate={minimumDate}
maximumDate={maximumDate}
selectorStartingYear={selectorStartingYear}
selectorEndingYear={selectorEndingYear}
locale={locale}
shouldHighlightWeekends={shouldHighlightWeekends}
renderFooter={renderFooter}
customDaysClassName={customDaysClassName}
/>
</div>
<div className="DatePicker__calendarArrow" />
</>
)}
</div>
);
};
DatePicker.defaultProps = {
wrapperClassName: '',
locale: 'en',
calendarPopperPosition: 'auto',
};
export default DatePicker;

@ -0,0 +1,93 @@
import React from 'react';
import { useLocaleUtils, useLocaleLanguage } from './shared/hooks';
import { putZero, getValueType } from './shared/generalUtils';
import { TYPE_SINGLE_DATE, TYPE_RANGE, TYPE_MUTLI_DATE } from './shared/constants';
const DatePickerInput = React.forwardRef(
(
{ value, inputPlaceholder, inputClassName, inputName, formatInputText, renderInput, locale },
ref,
) => {
const { getLanguageDigits } = useLocaleUtils(locale);
const {
from: fromWord,
to: toWord,
yearLetterSkip,
digitSeparator,
defaultPlaceholder,
isRtl,
} = useLocaleLanguage(locale);
const getSingleDayValue = () => {
if (!value) return '';
const year = getLanguageDigits(value.year);
const month = getLanguageDigits(putZero(value.month));
const day = getLanguageDigits(putZero(value.day));
return `${year}/${month}/${day}`;
};
const getDayRangeValue = () => {
if (!value.from || !value.to) return '';
const { from, to } = value;
const fromText = `${getLanguageDigits(putZero(from.year))
.toString()
.slice(yearLetterSkip)}/${getLanguageDigits(putZero(from.month))}/${getLanguageDigits(
putZero(from.day),
)}`;
const toText = `${getLanguageDigits(putZero(to.year))
.toString()
.slice(yearLetterSkip)}/${getLanguageDigits(putZero(to.month))}/${getLanguageDigits(
putZero(to.day),
)}`;
return `${fromWord} ${fromText} ${toWord} ${toText}`;
};
const getMultiDateValue = () => {
return value.map(date => getLanguageDigits(date.day)).join(`${digitSeparator} `);
};
const getValue = () => {
if (formatInputText()) return formatInputText();
const valueType = getValueType(value);
switch (valueType) {
case TYPE_SINGLE_DATE:
return getSingleDayValue();
case TYPE_RANGE:
return getDayRangeValue();
case TYPE_MUTLI_DATE:
return getMultiDateValue();
}
};
const placeholderValue = inputPlaceholder || defaultPlaceholder;
const render = () => {
return (
renderInput({ ref }) || (
<input
data-testid="datepicker-input"
readOnly
ref={ref}
value={getValue()}
name={inputName}
placeholder={placeholderValue}
className={`DatePicker__input -${isRtl ? 'rtl' : 'ltr'} ${inputClassName}`}
aria-label={placeholderValue}
/>
)
);
};
return render();
},
);
DatePickerInput.defaultProps = {
formatInputText: () => '',
renderInput: () => null,
inputPlaceholder: '',
inputClassName: '',
inputName: '',
};
export default DatePickerInput;

@ -0,0 +1,399 @@
import React, { useRef, useEffect } from 'react';
import {
getSlideDate,
handleSlideAnimationEnd,
animateContent,
} from '../shared/sliderHelpers';
import {
deepCloneObject,
isSameDay,
createUniqueRange,
getValueType,
} from '../shared/generalUtils';
import {
TYPE_SINGLE_DATE,
TYPE_RANGE,
TYPE_MUTLI_DATE,
} from '../shared/constants';
import handleKeyboardNavigation from '../shared/keyboardNavigation';
import { useLocaleUtils, useLocaleLanguage } from '../shared/hooks';
const DaysList = ({
activeDate,
value,
monthChangeDirection,
onSlideChange,
disabledDays,
onDisabledDayError,
minimumDate,
maximumDate,
onChange,
locale,
calendarTodayClassName,
calendarSelectedDayClassName,
calendarRangeStartClassName,
calendarRangeEndClassName,
calendarRangeBetweenClassName,
shouldHighlightWeekends,
isQuickSelectorOpen,
customDaysClassName,
events,
showEvent,
type
}) => {
const calendarSectionWrapper = useRef(null);
const { isRtl, weekDays: weekDaysList } = useLocaleLanguage(locale);
const {
getToday,
isBeforeDate,
checkDayInDayRange,
getMonthFirstWeekday,
getMonthLength,
getLanguageDigits,
getMonthName,
} = useLocaleUtils(locale);
const today = getToday();
useEffect(() => {
if (!monthChangeDirection) return;
animateContent({
direction: monthChangeDirection,
parent: calendarSectionWrapper.current,
});
}, [monthChangeDirection]);
const getDayRangeValue = (day) => {
const clonedDayRange = deepCloneObject(value);
const dayRangeValue =
clonedDayRange.from && clonedDayRange.to
? { from: null, to: null }
: clonedDayRange;
const dayRangeProp = !dayRangeValue.from ? 'from' : 'to';
dayRangeValue[dayRangeProp] = day;
const { from, to } = dayRangeValue;
// swap from and to values if from is later than to
if (isBeforeDate(dayRangeValue.to, dayRangeValue.from)) {
dayRangeValue.from = to;
dayRangeValue.to = from;
}
const checkIncludingDisabledDay = (disabledDay) => {
return checkDayInDayRange({
day: disabledDay,
from: dayRangeValue.from,
to: dayRangeValue.to,
});
};
const includingDisabledDay = disabledDays.find(checkIncludingDisabledDay);
if (includingDisabledDay) {
onDisabledDayError(includingDisabledDay);
return value;
}
return dayRangeValue;
};
const getMultiDateValue = (day) => {
const isAlreadyExisting = value.some((valueDay) =>
isSameDay(valueDay, day)
);
const addedToValue = [...value, day];
const removedFromValue = value.filter(
(valueDay) => !isSameDay(valueDay, day)
);
return isAlreadyExisting ? removedFromValue : addedToValue;
};
const handleDayClick = (day) => {
const getNewValue = () => {
const valueType = getValueType(value);
switch (valueType) {
case TYPE_SINGLE_DATE:
return day;
case TYPE_RANGE:
return getDayRangeValue(day);
case TYPE_MUTLI_DATE:
return getMultiDateValue(day);
}
};
const newValue = getNewValue();
onChange(newValue);
};
const isSingleDateSelected = (day) => {
const valueType = getValueType(value);
if (valueType === TYPE_SINGLE_DATE) return isSameDay(day, value);
if (valueType === TYPE_MUTLI_DATE)
return value.some((valueDay) => isSameDay(valueDay, day));
};
const getDayStatus = (dayItem) => {
const isToday = isSameDay(dayItem, today);
const isSelected = isSingleDateSelected(dayItem);
const { from: startingDay, to: endingDay } = value || {};
const isStartingDayRange = isSameDay(dayItem, startingDay);
const isEndingDayRange = isSameDay(dayItem, endingDay);
const isWithinRange = checkDayInDayRange({
day: dayItem,
from: startingDay,
to: endingDay,
});
return {
isToday,
isSelected,
isStartingDayRange,
isEndingDayRange,
isWithinRange,
};
};
const getDayClassNames = (dayItem) => {
const {
isToday,
isSelected,
isStartingDayRange,
isEndingDayRange,
isWithinRange,
} = getDayStatus(dayItem);
const customDayItemClassName = customDaysClassName.find((day) =>
isSameDay(dayItem, day)
);
const classNames = ''
.concat(isToday && !isSelected ? ` -today ${calendarTodayClassName}` : '')
.concat(!dayItem.isStandard ? ' -blank' : '')
.concat(dayItem.isWeekend && shouldHighlightWeekends ? ' -weekend' : '')
.concat(
customDayItemClassName ? ` ${customDayItemClassName.className}` : ''
)
.concat(isSelected ? ` -selected ${calendarSelectedDayClassName}` : '')
.concat(
isStartingDayRange
? ` -selectedStart ${calendarRangeStartClassName}`
: ''
)
.concat(
isEndingDayRange ? ` -selectedEnd ${calendarRangeEndClassName}` : ''
)
.concat(
isWithinRange
? ` -selectedBetween ${calendarRangeBetweenClassName}`
: ''
)
.concat(dayItem.isDisabled ? ' -disabled' : '');
return classNames;
};
const getViewMonthDays = (date) => {
// to match month starting date with the correct weekday label
const prependingBlankDays = createUniqueRange(
getMonthFirstWeekday(date),
'starting-blank'
);
const standardDays = createUniqueRange(getMonthLength(date)).map((day) => ({
...day,
isStandard: true,
month: date.month,
year: date.year,
}));
const allDays = [...prependingBlankDays, ...standardDays];
return allDays;
};
const handleDayPress = ({ isDisabled, ...dayItem }) => {
if (isDisabled) {
onDisabledDayError(dayItem); // good for showing error messages
} else handleDayClick(dayItem);
};
const isDayReachableByKeyboard = ({
isOnActiveSlide,
isStandard,
isSelected,
isStartingDayRange,
isToday,
day,
}) => {
if (isQuickSelectorOpen || !isOnActiveSlide || !isStandard) return false;
if (isSelected || isStartingDayRange || isToday || day === 1) return true;
};
const renderEachWeekDays = (
{ id, value: day, month, year, isStandard },
index
) => {
const dayItem = { day, month, year };
const isInDisabledDaysRange = disabledDays.some((disabledDay) =>
isSameDay(dayItem, disabledDay)
);
const isBeforeMinimumDate = isBeforeDate(dayItem, minimumDate);
const isAfterMaximumDate = isBeforeDate(maximumDate, dayItem);
const isNotInValidRange =
isStandard && (isBeforeMinimumDate || isAfterMaximumDate);
const isDisabled = isInDisabledDaysRange || isNotInValidRange;
const isWeekend = weekDaysList.some(
(weekDayItem, weekDayItemIndex) =>
weekDayItem.isWeekend && weekDayItemIndex === index
);
const additionalClass = getDayClassNames({
...dayItem,
isWeekend,
isStandard,
isDisabled,
});
const dayLabel = `${weekDaysList[index].name}, ${day} ${getMonthName(
month
)} ${year}`;
const isOnActiveSlide = month === activeDate.month;
const dayStatus = getDayStatus(dayItem);
const { isSelected, isStartingDayRange, isEndingDayRange, isWithinRange } =
dayStatus;
const shouldEnableKeyboardNavigation = isDayReachableByKeyboard({
...dayItem,
...dayStatus,
isOnActiveSlide,
isStandard,
});
// showing Dots Of Events
const specialDate = (sDay, sMonth, sYear) => {
return events.map((e) => {
if (sDay == e.day && sMonth == e.month && sYear == e.year) {
return <span key={'dot' + e.id} style={{
color: e.color
}}>.</span>;
}
})
};
const showEventDescription = (sDay, sMonth, sYear) => {
return events.map((e) => {
if (sDay == e.day && sMonth == e.month && sYear == e.year) {
return (<div key={'description' + e.id}><p>{e.description}</p></div>)
}
})
}
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
tabIndex={shouldEnableKeyboardNavigation ? '0' : '-1'}
key={id}
className={`Calendar__day ${type} -${isRtl ? 'rtl' : 'ltr'} ${additionalClass}`}
onClick={() => {
handleDayPress({ ...dayItem, isDisabled });
// console.log(specialDate(day));
// console.log(showEventDescription(day))
showEvent(showEventDescription(day, month, year))
}}
onKeyDown={({ key }) => {
/* istanbul ignore else */
if (key === 'Enter') handleDayPress({ ...dayItem, isDisabled });
}}
aria-disabled={isDisabled}
aria-label={dayLabel}
aria-selected={
isSelected || isStartingDayRange || isEndingDayRange || isWithinRange
}
{...(!isStandard || !isOnActiveSlide || isQuickSelectorOpen
? { 'aria-hidden': true }
: {})}
role="gridcell"
data-is-default-selectable={shouldEnableKeyboardNavigation}
>
<p>{!isStandard ? '' : getLanguageDigits(day)}</p>
{/* Dots Of Events */}
<span>{specialDate(day, month, year)}</span>
</div>
);
};
const renderMonthDays = (isInitialActiveChild) => {
const date = getSlideDate({
activeDate,
isInitialActiveChild,
monthChangeDirection,
parent: calendarSectionWrapper.current,
});
const allDays = getViewMonthDays(date);
const renderSingleWeekRow = (weekRowIndex) => {
const eachWeekDays = allDays
.slice(weekRowIndex * 7, weekRowIndex * 7 + 7)
.map(renderEachWeekDays);
return (
<div
key={String(weekRowIndex)}
className="Calendar__weekRow"
role="row"
>
{eachWeekDays}
</div>
);
};
return Array.from(Array(6).keys()).map(renderSingleWeekRow);
};
const handleKeyDown = (e) => {
handleKeyboardNavigation(e, { allowVerticalArrows: true });
};
return (
<div
ref={calendarSectionWrapper}
className="Calendar__sectionWrapper"
role="presentation"
data-testid="days-section-wrapper"
onKeyDown={handleKeyDown}
>
<div
onAnimationEnd={(e) => {
handleSlideAnimationEnd(e);
onSlideChange();
}}
className="Calendar__section -shown"
role="rowgroup"
>
{renderMonthDays(true)}
</div>
<div
onAnimationEnd={(e) => {
handleSlideAnimationEnd(e);
onSlideChange();
}}
className="Calendar__section -hiddenNext"
role="rowgroup"
>
{renderMonthDays(false)}
</div>
</div>
);
};
DaysList.defaultProps = {
onChange: () => { },
onDisabledDayError: () => { },
disabledDays: [],
calendarTodayClassName: '',
calendarSelectedDayClassName: '',
calendarRangeStartClassName: '',
calendarRangeBetweenClassName: '',
calendarRangeEndClassName: '',
shouldHighlightWeekends: false,
};
export default DaysList;

@ -0,0 +1,205 @@
import React, { useEffect, useRef } from 'react';
import { isSameDay } from '../shared/generalUtils';
import { getSlideDate, animateContent, handleSlideAnimationEnd } from '../shared/sliderHelpers';
import { useLocaleUtils, useLocaleLanguage } from '../shared/hooks';
const Header = ({
maximumDate,
minimumDate,
onMonthChange,
activeDate,
monthChangeDirection,
onMonthSelect,
onYearSelect,
isMonthSelectorOpen,
isYearSelectorOpen,
locale,
type
}) => {
const headerElement = useRef(null);
const monthYearWrapperElement = useRef(null);
const { getMonthName, isBeforeDate, getLanguageDigits } = useLocaleUtils(locale);
const {
isRtl,
nextMonth,
previousMonth,
openMonthSelector,
closeMonthSelector,
openYearSelector,
closeYearSelector,
} = useLocaleLanguage(locale);
useEffect(() => {
if (!monthChangeDirection) return;
animateContent({
direction: monthChangeDirection,
parent: monthYearWrapperElement.current,
});
}, [monthChangeDirection]);
useEffect(() => {
const isOpen = isMonthSelectorOpen || isYearSelectorOpen;
const monthText = headerElement.current.querySelector(
'.Calendar__monthYear.-shown .Calendar__monthText',
);
const yearText = monthText.nextSibling;
const hasActiveBackground = element => element.classList.contains('-activeBackground');
const isInitialRender =
!isOpen && !hasActiveBackground(monthText) && !hasActiveBackground(yearText);
if (isInitialRender) return;
const arrows = [...headerElement.current.querySelectorAll('.Calendar__monthArrowWrapper')];
const hasMonthSelectorToggled = isMonthSelectorOpen || hasActiveBackground(monthText);
const primaryElement = hasMonthSelectorToggled ? monthText : yearText;
const secondaryElement = hasMonthSelectorToggled ? yearText : monthText;
let translateXDirection = hasMonthSelectorToggled ? 1 : -1;
if (isRtl) translateXDirection *= -1;
const scale = !isOpen ? 0.95 : 1;
const translateX = !isOpen ? 0 : `${(translateXDirection * secondaryElement.offsetWidth) / 2}`;
if (!isOpen) {
secondaryElement.removeAttribute('aria-hidden');
} else {
secondaryElement.setAttribute('aria-hidden', true);
}
secondaryElement.setAttribute('tabindex', isOpen ? '-1' : '0');
secondaryElement.style.transform = '';
primaryElement.style.transform = `scale(${scale}) ${
translateX ? `translateX(${translateX}px)` : ''
}`;
primaryElement.classList.toggle('-activeBackground');
secondaryElement.classList.toggle('-hidden');
arrows.forEach(arrow => {
const isHidden = arrow.classList.contains('-hidden');
arrow.classList.toggle('-hidden');
if (isHidden) {
arrow.removeAttribute('aria-hidden');
arrow.setAttribute('tabindex', '0');
} else {
arrow.setAttribute('aria-hidden', true);
arrow.setAttribute('tabindex', '-1');
}
});
}, [isMonthSelectorOpen, isYearSelectorOpen]);
const getMonthYearText = isInitialActiveChild => {
const date = getSlideDate({
isInitialActiveChild,
monthChangeDirection,
activeDate,
parent: monthYearWrapperElement.current,
});
const year = getLanguageDigits(date.year);
const month = getMonthName(date.month);
return { month, year };
};
const isNextMonthArrowDisabled =
maximumDate &&
isBeforeDate(maximumDate, { ...activeDate, month: activeDate.month + 1, day: 1 });
const isPreviousMonthArrowDisabled =
minimumDate &&
(isBeforeDate({ ...activeDate, day: 1 }, minimumDate) ||
isSameDay(minimumDate, { ...activeDate, day: 1 }));
const onMonthChangeTrigger = direction => {
const isMonthChanging = Array.from(monthYearWrapperElement.current.children).some(child =>
child.classList.contains('-shownAnimated'),
);
if (isMonthChanging) return;
onMonthChange(direction);
};
// first button text is the one who shows the current month and year(initial active child)
const monthYearButtons = [true, false].map(isInitialActiveChild => {
const { month, year } = getMonthYearText(isInitialActiveChild);
const isActiveMonth = month === getMonthName(activeDate.month);
const hiddenStatus = {
...(isActiveMonth ? {} : { 'aria-hidden': true }),
};
return (
<div
onAnimationEnd={handleSlideAnimationEnd}
className={`Calendar__monthYear ${isInitialActiveChild ? '-shown' : '-hiddenNext'}`}
role="presentation"
key={String(isInitialActiveChild)}
{...hiddenStatus}
>
<button
onClick={onMonthSelect}
type="button"
className="Calendar__monthText"
aria-label={isMonthSelectorOpen ? closeMonthSelector : openMonthSelector}
tabIndex={isActiveMonth ? '0' : '-1'}
{...hiddenStatus}
>
{month}
</button>
<button
onClick={onYearSelect}
type="button"
className="Calendar__yearText"
aria-label={isYearSelectorOpen ? closeYearSelector : openYearSelector}
tabIndex={isActiveMonth ? '0' : '-1'}
{...hiddenStatus}
>
{year}
</button>
</div>
);
});
return (
<div ref={headerElement} className={`Calendar__header ${type}`}>
{type == 'dashboard' ?
<div
className={`Calendar__monthYearContainer ${type}`}
ref={monthYearWrapperElement}
data-testid="month-year-container"
>
&nbsp;
{monthYearButtons}
</div>
:
<>
<button
className="Calendar__monthArrowWrapper -right"
onClick={() => {
onMonthChangeTrigger('PREVIOUS');
}}
aria-label={previousMonth}
type="button"
disabled={isPreviousMonthArrowDisabled}
>
<span className="Calendar__monthArrow" />
</button>
<div
className="Calendar__monthYearContainer"
ref={monthYearWrapperElement}
data-testid="month-year-container"
>
&nbsp;
{monthYearButtons}
</div>
<button
className="Calendar__monthArrowWrapper -left"
onClick={() => {
onMonthChangeTrigger('NEXT');
}}
aria-label={nextMonth}
type="button"
disabled={isNextMonthArrowDisabled}
>
<span className="Calendar__monthArrow" />
</button>
</>
}
</div>
);
};
export default Header;

@ -0,0 +1,74 @@
import React, { useRef, useEffect } from 'react';
import { isSameDay } from '../shared/generalUtils';
import handleKeyboardNavigation from '../shared/keyboardNavigation';
import { useLocaleUtils, useLocaleLanguage } from '../shared/hooks';
const MonthSelector = ({ activeDate, maximumDate, minimumDate, onMonthSelect, isOpen, locale }) => {
const monthSelector = useRef(null);
useEffect(() => {
const classToggleMethod = isOpen ? 'add' : 'remove';
monthSelector.current.classList[classToggleMethod]('-open');
}, [isOpen]);
const { getMonthNumber, isBeforeDate } = useLocaleUtils(locale);
const { months: monthsList } = useLocaleLanguage(locale);
const handleKeyDown = e => {
handleKeyboardNavigation(e, { allowVerticalArrows: false });
};
const renderMonthSelectorItems = () =>
monthsList.map(persianMonth => {
const monthNumber = getMonthNumber(persianMonth);
const monthDate = { day: 1, month: monthNumber, year: activeDate.year };
const isAfterMaximumDate =
maximumDate && isBeforeDate(maximumDate, { ...monthDate, month: monthNumber });
const isBeforeMinimumDate =
minimumDate &&
(isBeforeDate({ ...monthDate, month: monthNumber + 1 }, minimumDate) ||
isSameDay({ ...monthDate, month: monthNumber + 1 }, minimumDate));
const isSelected = monthNumber === activeDate.month;
return (
<li
key={persianMonth}
className={`Calendar__monthSelectorItem ${isSelected ? '-active' : ''}`}
>
<button
tabIndex={isSelected && isOpen ? '0' : '-1'}
onClick={() => {
onMonthSelect(monthNumber);
}}
className="Calendar__monthSelectorItemText"
type="button"
disabled={isAfterMaximumDate || isBeforeMinimumDate}
aria-pressed={isSelected}
data-is-default-selectable={isSelected}
>
{persianMonth}
</button>
</li>
);
});
return (
<div
role="presentation"
className="Calendar__monthSelectorAnimationWrapper"
{...(isOpen ? {} : { 'aria-hidden': true })}
>
<div
role="presentation"
data-testid="month-selector-wrapper"
className="Calendar__monthSelectorWrapper"
onKeyDown={handleKeyDown}
>
<ul ref={monthSelector} className="Calendar__monthSelector" data-testid="month-selector">
{renderMonthSelectorItems()}
</ul>
</div>
</div>
);
};
export default MonthSelector;

@ -0,0 +1,99 @@
import React, { useRef, useEffect } from 'react';
import { MINIMUM_SELECTABLE_YEAR_SUBTRACT, MAXIMUM_SELECTABLE_YEAR_SUM } from '../shared/constants';
import handleKeyboardNavigation from '../shared/keyboardNavigation';
import { useLocaleUtils } from '../shared/hooks';
const YearSelector = ({
isOpen,
activeDate,
onYearSelect,
selectorStartingYear,
selectorEndingYear,
maximumDate,
minimumDate,
locale,
}) => {
const wrapperElement = useRef(null);
const yearListElement = useRef(null);
const { getLanguageDigits, getToday } = useLocaleUtils(locale);
const startingYearValue =
selectorStartingYear || getToday().year - MINIMUM_SELECTABLE_YEAR_SUBTRACT;
const endingYearValue = selectorEndingYear || getToday().year + MAXIMUM_SELECTABLE_YEAR_SUM;
const allYears = [];
for (let i = startingYearValue; i <= endingYearValue; i += 1) {
allYears.push(i);
}
useEffect(() => {
const classToggleMethod = isOpen ? 'add' : 'remove';
const activeSelectorYear = wrapperElement.current.querySelector(
'.Calendar__yearSelectorItem.-active',
);
if (!activeSelectorYear) {
throw new RangeError(
`Provided value for year is out of selectable year range. You're probably using a wrong locale prop value or your provided value's locale is different from the date picker locale. Try changing the 'locale' prop or the value you've provided.`,
);
}
wrapperElement.current.classList[classToggleMethod]('-faded');
yearListElement.current.scrollTop =
activeSelectorYear.offsetTop - activeSelectorYear.offsetHeight * 5;
yearListElement.current.classList[classToggleMethod]('-open');
}, [isOpen]);
const renderSelectorYears = () => {
return allYears.map(item => {
const isAfterMaximumDate = maximumDate && item > maximumDate.year;
const isBeforeMinimumDate = minimumDate && item < minimumDate.year;
const isSelected = activeDate.year === item;
return (
<li key={item} className={`Calendar__yearSelectorItem ${isSelected ? '-active' : ''}`}>
<button
tabIndex={isSelected && isOpen ? '0' : '-1'}
className="Calendar__yearSelectorText"
type="button"
onClick={() => {
onYearSelect(item);
}}
disabled={isAfterMaximumDate || isBeforeMinimumDate}
aria-pressed={isSelected}
data-is-default-selectable={isSelected}
>
{getLanguageDigits(item)}
</button>
</li>
);
});
};
const handleKeyDown = e => {
handleKeyboardNavigation(e, { allowVerticalArrows: false });
};
return (
<div
className="Calendar__yearSelectorAnimationWrapper"
role="presentation"
{...(isOpen ? {} : { 'aria-hidden': true })}
>
<div
ref={wrapperElement}
className="Calendar__yearSelectorWrapper"
role="presentation"
data-testid="year-selector-wrapper"
onKeyDown={handleKeyDown}
>
<ul ref={yearListElement} className="Calendar__yearSelector" data-testid="year-selector">
{renderSelectorYears()}
</ul>
</div>
</div>
);
};
YearSelector.defaultProps = {
selectorStartingYear: 0,
selectorEndingYear: 0,
};
export default YearSelector;

@ -0,0 +1,4 @@
export { default as Header } from './Header';
export { default as MonthSelector } from './MonthSelector';
export { default as YearSelector } from './YearSelector';
export { default as DaysList } from './DaysList';

@ -0,0 +1,3 @@
export { default} from './DatePicker';
export * from './Calendar';
export { default as utils } from './shared/localeUtils';

@ -0,0 +1,104 @@
export const PERSIAN_NUMBERS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
export const PERSIAN_MONTHS = [
'فروردین',
'اردیبهشت',
'خرداد',
'تیر',
'مرداد',
'شهریور',
'مهر',
'آبان',
'آذر',
'دی',
'بهمن',
'اسفند',
];
export const GREGORIAN_MONTHS = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
export const PERSIAN_WEEK_DAYS = [
{
name: 'شنبه',
short: 'ش',
},
{
name: 'یکشنبه',
short: 'ی',
},
{
name: 'دوشنبه',
short: 'د',
},
{
name: 'سه شنبه',
short: 'س',
},
{
name: 'چهارشنبه',
short: 'چ',
},
{
name: 'پنجشنبه',
short: 'پ',
},
{
name: 'جمعه',
short: 'ج',
isWeekend: true,
},
];
export const GREGORIAN_WEEK_DAYS = [
{
name: 'Sunday',
short: 'S',
isWeekend: true,
},
{
name: 'Monday',
short: 'M',
},
{
name: 'Tuesday',
short: 'T',
},
{
name: 'Wednesday',
short: 'W',
},
{
name: 'Thursday',
short: 'T',
},
{
name: 'Friday',
short: 'F',
},
{
name: 'Saturday',
short: 'S',
isWeekend: true,
},
];
export const MINIMUM_SELECTABLE_YEAR_SUBTRACT = 100;
export const MAXIMUM_SELECTABLE_YEAR_SUM = 50;
export const TYPE_SINGLE_DATE = 'SINGLE_DATE';
export const TYPE_RANGE = 'RANGE';
export const TYPE_MUTLI_DATE = 'MUTLI_DATE';

@ -0,0 +1,69 @@
import { TYPE_SINGLE_DATE, TYPE_RANGE, TYPE_MUTLI_DATE } from './constants';
/*
These utility functions don't depend on locale of the date picker(Persian or Gregorian)
*/
const createUniqueRange = (number, startingId) =>
Array.from(Array(number).keys()).map(key => ({
value: key + 1,
id: `${startingId}-${key}`,
}));
const isSameDay = (day1, day2) => {
if (!day1 || !day2) return false;
return day1.day === day2.day && day1.month === day2.month && day1.year === day2.year;
};
const putZero = number => (number.toString().length === 1 ? `0${number}` : number);
const toExtendedDay = date => [date.year, date.month, date.day];
const shallowClone = value => ({ ...value });
const deepCloneObject = obj =>
JSON.parse(JSON.stringify(obj, (key, value) => (typeof value === 'undefined' ? null : value)));
const getDateAccordingToMonth = (date, direction) => {
const toSum = direction === 'NEXT' ? 1 : -1;
let newMonthIndex = date.month + toSum;
let newYear = date.year;
if (newMonthIndex < 1) {
newMonthIndex = 12;
newYear -= 1;
}
if (newMonthIndex > 12) {
newMonthIndex = 1;
newYear += 1;
}
const newDate = { year: newYear, month: newMonthIndex, day: 1 };
return newDate;
};
const hasProperty = (object, propertyName) =>
Object.prototype.hasOwnProperty.call(object || {}, propertyName);
const getValueType = value => {
if (Array.isArray(value)) return TYPE_MUTLI_DATE;
if (hasProperty(value, 'from') && hasProperty(value, 'to')) return TYPE_RANGE;
if (
!value ||
(hasProperty(value, 'year') && hasProperty(value, 'month') && hasProperty(value, 'day'))
) {
return TYPE_SINGLE_DATE;
}
throw new TypeError(
`The passed value is malformed! Please make sure you're using one of the valid value types for date picker.`,
);
};
export {
createUniqueRange,
isSameDay,
putZero,
toExtendedDay,
shallowClone,
deepCloneObject,
getDateAccordingToMonth,
getValueType,
};

@ -0,0 +1,10 @@
import { useMemo } from 'react';
import utils from './localeUtils';
import getLanguageText from './localeLanguages';
const useLocaleUtils = locale => useMemo(() => utils(locale), [locale]);
const useLocaleLanguage = locale => useMemo(() => getLanguageText(locale), [locale]);
export { useLocaleUtils, useLocaleLanguage };

@ -0,0 +1,44 @@
const handleArrowKeys = (e, { allowVerticalArrows }) => {
const { activeElement } = document;
const getNthChildSafe = (element, index) => (element ? element.children[index] : null);
const getStandardItem = item => item && (item.hasAttribute('aria-hidden') ? null : item);
const { nextSibling: nextRow, previousSibling: previousRow } = activeElement.parentElement;
const nextSibling = getStandardItem(activeElement.nextSibling || getNthChildSafe(nextRow, 0));
const previousRowLength = previousRow ? previousRow.children.length - 1 : 0;
const previousSibling = getStandardItem(
activeElement.previousSibling || getNthChildSafe(previousRow, previousRowLength),
);
const getVerticalSibling = row =>
getNthChildSafe(row, Array.from(activeElement.parentElement.children).indexOf(activeElement));
const downSibling = getStandardItem(getVerticalSibling(nextRow));
const upSibling = getStandardItem(getVerticalSibling(previousRow));
const isDefaultSelectable = activeElement.dataset.isDefaultSelectable === 'true';
if (!isDefaultSelectable) activeElement.tabIndex = '-1';
const focusIfAvailable = element => {
e.preventDefault();
/* istanbul ignore else */
if (element) {
element.setAttribute('tabindex', '0');
element.focus();
}
};
switch (e.key) {
case 'ArrowRight':
focusIfAvailable(nextSibling);
break;
case 'ArrowLeft':
focusIfAvailable(previousSibling);
break;
case 'ArrowDown':
/* istanbul ignore else */
if (allowVerticalArrows) focusIfAvailable(downSibling);
break;
case 'ArrowUp':
/* istanbul ignore else */
if (allowVerticalArrows) focusIfAvailable(upSibling);
break;
}
};
export default handleArrowKeys;

@ -0,0 +1,85 @@
import jalaali from 'jalaali-js';
import {
GREGORIAN_MONTHS,
PERSIAN_MONTHS,
GREGORIAN_WEEK_DAYS,
PERSIAN_WEEK_DAYS,
PERSIAN_NUMBERS,
} from './constants';
import { toExtendedDay } from './generalUtils';
const localeLanguages = {
en: {
months: GREGORIAN_MONTHS,
weekDays: GREGORIAN_WEEK_DAYS,
weekStartingIndex: 0,
getToday(gregorainTodayObject) {
return gregorainTodayObject;
},
toNativeDate(date) {
return new Date(date.year, date.month - 1, date.day);
},
getMonthLength(date) {
return new Date(date.year, date.month, 0).getDate();
},
transformDigit(digit) {
return digit;
},
nextMonth: 'Next Month',
previousMonth: 'Previous Month',
openMonthSelector: 'Open Month Selector',
openYearSelector: 'Open Year Selector',
closeMonthSelector: 'Close Month Selector',
closeYearSelector: 'Close Year Selector',
from: 'from',
to: 'to',
defaultPlaceholder: 'Select...',
digitSeparator: ',',
yearLetterSkip: 0,
isRtl: false,
},
fa: {
months: PERSIAN_MONTHS,
weekDays: PERSIAN_WEEK_DAYS,
weekStartingIndex: 1,
getToday({ year, month, day }) {
const { jy, jm, jd } = jalaali.toJalaali(year, month, day);
return { year: jy, month: jm, day: jd };
},
toNativeDate(date) {
const gregorian = jalaali.toGregorian(...toExtendedDay(date));
return new Date(gregorian.gy, gregorian.gm - 1, gregorian.gd);
},
getMonthLength(date) {
return jalaali.jalaaliMonthLength(date.year, date.month);
},
transformDigit(digit) {
return digit
.toString()
.split('')
.map(letter => PERSIAN_NUMBERS[Number(letter)])
.join('');
},
nextMonth: 'ماه بعد',
previousMonth: 'ماه قبل',
openMonthSelector: 'نمایش انتخابگر ماه',
openYearSelector: 'نمایش انتخابگر سال',
closeMonthSelector: 'بستن انتخابگر ماه',
closeYearSelector: 'بستن انتخابگر ماه',
from: 'از',
to: 'تا',
defaultPlaceholder: 'انتخاب...',
digitSeparator: '،',
yearLetterSkip: -2,
isRtl: true,
},
};
const getLocaleDetails = locale => {
if (typeof locale === 'string') return localeLanguages[locale];
return locale;
};
export { localeLanguages };
export default getLocaleDetails;

@ -0,0 +1,61 @@
/*
These utility functions highly depend on locale of the date picker(Persian or Gregorian)
*/
import getLocaleDetails from './localeLanguages';
const utils = (locale = 'en') => {
const {
months: monthsList,
getToday: localeGetToday,
toNativeDate,
getMonthLength,
weekStartingIndex,
transformDigit: getLanguageDigits,
} = typeof locale === 'string' ? getLocaleDetails(locale) : locale;
const getToday = () => {
const todayDate = new Date();
const year = todayDate.getFullYear();
const month = todayDate.getMonth() + 1;
const day = todayDate.getDate();
return localeGetToday({ year, month, day });
};
const getMonthName = month => monthsList[month - 1];
const getMonthNumber = monthName => monthsList.indexOf(monthName) + 1;
const getMonthFirstWeekday = date => {
const gregorianDate = toNativeDate({ ...date, day: 1 });
const weekday = gregorianDate.getDay();
const dayIndex = weekday + weekStartingIndex;
return dayIndex % 7;
};
const isBeforeDate = (day1, day2) => {
if (!day1 || !day2) return false;
return toNativeDate(day1) < toNativeDate(day2);
};
const checkDayInDayRange = ({ day, from, to }) => {
if (!day || !from || !to) return false;
const nativeDay = toNativeDate(day);
const nativeFrom = toNativeDate(from);
const nativeTo = toNativeDate(to);
return nativeDay > nativeFrom && nativeDay < nativeTo;
};
return {
getToday,
getMonthName,
getMonthNumber,
getMonthLength,
getMonthFirstWeekday,
isBeforeDate,
checkDayInDayRange,
getLanguageDigits,
};
};
export default utils;

@ -0,0 +1,33 @@
import { getDateAccordingToMonth } from './generalUtils';
const getSlideDate = ({ parent, isInitialActiveChild, activeDate, monthChangeDirection }) => {
if (!parent) {
return isInitialActiveChild ? activeDate : getDateAccordingToMonth(activeDate, 'NEXT');
}
const child = parent.children[isInitialActiveChild ? 0 : 1];
const isActiveSlide =
child.classList.contains('-shown') || child.classList.contains('-shownAnimated'); // check -shownAnimated for Safari bug
return isActiveSlide ? activeDate : getDateAccordingToMonth(activeDate, monthChangeDirection);
};
const animateContent = ({ parent, direction }) => {
const wrapperChildren = Array.from(parent.children);
const shownItem = wrapperChildren.find(child => child.classList.contains('-shown'));
const hiddenItem = wrapperChildren.find(child => child !== shownItem);
const baseClass = shownItem.classList[0];
const isNextMonth = direction === 'NEXT';
const getAnimationClass = value => (value ? '-hiddenNext' : '-hiddenPrevious');
hiddenItem.style.transition = 'none';
shownItem.style.transition = '';
shownItem.className = `${baseClass} ${getAnimationClass(!isNextMonth)}`;
hiddenItem.className = `${baseClass} ${getAnimationClass(isNextMonth)}`;
hiddenItem.classList.add('-shownAnimated');
};
const handleSlideAnimationEnd = ({ target }) => {
target.classList.remove('-hiddenNext');
target.classList.remove('-hiddenPrevious');
target.classList.replace('-shownAnimated', '-shown');
};
export { animateContent, getSlideDate, handleSlideAnimationEnd };

@ -1,6 +1,7 @@
import React, { useState } from "react";
import "./index.scss";
import DatePicker from "../Calendar/CalendarComponents";
import DatePicker from "../CalendarComponents";
const RangeDatePicker = () => {
const [selectedDayRange, setSelectedDayRange] = useState({

@ -1,10 +1,12 @@
import React, { useState } from "react";
import DateRangePicker from "./DateRangePicker";
import RangeDatePicker from "./RangeDatePicker";
function Calender() {
const list = {
1: <DateRangePicker />,
1: <RangeDatePicker />,
};
const [type, setType] = useState(1);
return (

@ -30,7 +30,7 @@ import ProductDescriptionTable from "../components/ProductDescriptionTable";
import QuestionAnswer from "../components/QuestionAnswer";
import RadioButton from "../components/RadioButton";
import Range from "../components/Range";
import Calender from "../components/Calender";
import Datepicker from "../components/Datepicker";
// mini components
import MiniComponents from "../components/MiniComponents";
@ -645,12 +645,12 @@ const components = [
},
},
{
name: "Calender",
name: "Datepicker",
author: "Andalib/Keshavarzi",
props: [],
content: {
name: "Calender",
component: <Calender />,
name: "Datepicker",
component: <Datepicker />,
code: null,
},
},

Loading…
Cancel
Save