diff --git a/src/components/dataPicker2/dataPicker2/README.md b/src/components/dataPicker2/dataPicker2/README.md new file mode 100644 index 0000000..54e08cd --- /dev/null +++ b/src/components/dataPicker2/dataPicker2/README.md @@ -0,0 +1,34 @@ +## **DatePicker** + +width - 380px. Max height - flexible. + +### Props: + +- **disabled** : _bool_, optional, default = false +- **endDate** : _undefined_,_Date_, optional, default = undefined +- **startDate** : _undefined_,_Date_, optional, default = undefined +- **customClassName** : _string_, optional, default : "" +- **customTimeInput** : _ReactElement_, optional, default = undefined +- **shouldCloseOnSelect** : _bool_, optional, default = false +- **showPopperArrow** : _bool_, optional, default=false +- **popperClassName** : _string_, optional, default = "" +- **calendarClassName** : _string_, optional, default = "" +- **fixedHeight** : _bool_, optional, default = false +- **headerNodes** : _node_, optional, default = null +- **value** : _string_, optional, default = new Date() +- **language** : _string_ or _Locale_, optional, default = 'en' +- **yearsOptions** : _number[]_, optional, default = 'en' +- **placeholder** : _string_, optional, default = 'MM-DD-YYYY' +- **dateFormat** : _string_, optional, default = 'MM-dd-yyyy' +- **selects** : _string('start' | 'end')_, optional, default = '' +- **value** : _Date_, optional, default = null + +### Events: + +- **onChange** +- **onBlur** +- **onFocus** + + +### Setup: +- **registerDatePickerLocale** diff --git a/src/components/dataPicker2/dataPicker2/datePicker.module.scss b/src/components/dataPicker2/dataPicker2/datePicker.module.scss new file mode 100644 index 0000000..51238cb --- /dev/null +++ b/src/components/dataPicker2/dataPicker2/datePicker.module.scss @@ -0,0 +1,262 @@ +@import 'src/assets/styles/variables/typography'; +@import 'src/assets/styles/mixins/font-scale'; + +$DAY_OF_THE_WEEK_Z_INDEX: 3; +$DAY_OF_THE_WEEK_HOVERED_Z_INDEX: 2; +$POPOVER_Z_INDEX: 10; + +@mixin setMonthContainerProperties { + .react-datepicker__month-container { + width: 100%; + height: 100%; + float: none; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + @include setCustomHeaderProperties; + @include setMothProperties; + } +} + +@mixin setCustomHeaderProperties { + .react-datepicker__header.react-datepicker__header--custom { + width: 100%; + background-color: var(--rp-ui-base-bg-000); + border-bottom: none; + padding: 0; + @include setDaysNameOfTheWeekProperties; + } +} + +@mixin setMothProperties { + .react-datepicker__month { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + row-gap: 8px; + @include setDaysDigitsContainerProperties; + } +} + +@mixin setDaysDigitsContainerProperties { + .react-datepicker__week { + height: 32px; + display: flex; + align-items: center; + justify-content: space-between; + font-family: var(--rp-ui-base-font-family); + color: var(--rp-ui-base-dark-e-500); + @include font-scale(); + .react-datepicker__day--range-end:first-child:before { + display: none; + } + } + + .react-datepicker__day { + cursor: pointer; + + &.react-datepicker__day--in-selecting-range { + &:not(.react-datepicker__day--selecting-range-end, .react-datepicker__day--selecting-range-start, .react-datepicker__day--range-start, .react-datepicker__day--range-end) { + background-color: var(--rp-ui-base-bg-200); + height: 32px; + line-height: 32px; + position: relative; + } + } + + &.react-datepicker__day--selected { + position: relative; + border-radius: 50%; + background-color: var(--rp-ui-base-topaz); + font-family: var(--rp-ui-base-font-family); + font-weight: $fw-bold; + color: var(--rp-ui-base-bg-000); + } + } + + .react-datepicker__day--disabled { + cursor: default; + + &:hover { + border: none !important; + line-height: 40px !important; + } + } +} + +@mixin setDaysNameOfTheWeekProperties { + .react-datepicker__day-names { + display: flex; + height: 40px; + justify-content: space-between; + font-family: var(--rp-ui-base-font-family); + font-weight: $fw-bold; + @include font-scale(); + color: var(--rp-ui-base-dark-e-500); + vertical-align: middle; + + .react-datepicker__day-name { + width: 40px; + text-align: center; + } + } +} + +@mixin removeAriaLive { + .react-datepicker__aria-live { + display: none; + } +} + +@mixin setHoverState($backgroundColor, $textColor: inherit) { + border-radius: 50%; + border: 1px solid var(--rp-ui-base-topaz); + background-color: $backgroundColor; + line-height: 38px; + color: $textColor; +} + +@mixin removeOutline { + &:focus-visible { + outline: none; + } +} + +@mixin verticalAlign($height) { + height: $height; + line-height: $height; +} + +@mixin drawBeforePseudoElement { + position: absolute; + content: ''; + height: 32px; + background-color: var(--rp-ui-base-bg-200); + width: 16px; + top: 0; + left: -9px; + z-index: 1; +} + +@mixin drawAfterPseudoElement($displayValue, $borderWidth) { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + border: $borderWidth solid var(--rp-ui-base-topaz); + border-radius: 50%; + display: $displayValue; + z-index: $DAY_OF_THE_WEEK_HOVERED_Z_INDEX; + box-sizing: border-box; +} + +:global { + @include removeAriaLive; +} + +.calendar { + box-sizing: border-box; + background-color: var(--rp-ui-base-bg-000); + width: 344px; + padding: 30px 32px 32px; + border-radius: 8px; + box-shadow: 0px 8px 40px var(--rp-ui-base-dark-bg-light); + border: none; + margin-top: 4px; + + :global { + @include setMonthContainerProperties; + } + + .current-date, + .date { + width: 40px; + margin: 0; + box-sizing: border-box; + @include verticalAlign(40px); + @include removeOutline; + } + + .date { + background-color: transparent; + border-radius: unset; + color: inherit; + text-align: center; + } + + .current-date, + .current-date:hover { + position: relative; + z-index: $DAY_OF_THE_WEEK_Z_INDEX; + font-family: var(--rp-ui-base-font-family); + font-weight: $fw-bold; + @include setHoverState(var(--rp-ui-base-topaz), var(--rp-ui-base-bg-000)); + } + + .date:hover:not(.current-date):not(.selected-range):not(.end-date) { + @include setHoverState(transparent); + } + + .end-date { + position: relative; + border-radius: 50%; + background-color: var(--rp-ui-base-topaz); + font-family: var(--rp-ui-base-font-family); + font-weight: $fw-bold; + color: var(--rp-ui-base-bg-000); + } + + .end-date::after { + @include drawAfterPseudoElement(block, 10px); + } + + .end-date::before { + @include drawBeforePseudoElement; + top: 4px; + } + + .selected-range { + background-color: var(--rp-ui-base-bg-200); + border-radius: 8px; + @include verticalAlign(32px); + position: relative; + &:hover { + @include verticalAlign(40px); + border-radius: 50%; + background: var(--rp-ui-base-bg-200); + &::after { + display: block; + } + &:not(:first-child)::before { + top: 4px; + } + } + } + + .selected-range::after { + @include drawAfterPseudoElement(none, 1px); + } + + .selected-range:not(:first-child)::before { + @include drawBeforePseudoElement; + } + + .disabled { + color: var(--rp-ui-base-e-400); + background-color: transparent; + @include removeOutline; + } +} +.popper { + z-index: $POPOVER_Z_INDEX; +} + +.input { + width: 100%; + min-width: 138px; +} diff --git a/src/components/dataPicker2/dataPicker2/datePicker.tsx b/src/components/dataPicker2/dataPicker2/datePicker.tsx new file mode 100644 index 0000000..f187286 --- /dev/null +++ b/src/components/dataPicker2/dataPicker2/datePicker.tsx @@ -0,0 +1,121 @@ +import ReactDatePicker, { ReactDatePickerCustomHeaderProps } from 'react-datepicker'; +import classNames from 'classnames/bind'; +import { FC, ReactNode, useRef, ReactElement } from 'react'; +import { FieldText } from '@components/fieldText'; +import { CalendarIcon } from '@components/icons'; +import { DatePickerHeader } from './header/datePickerHeader'; +import styles from './datePicker.module.scss'; + +const cx = classNames.bind(styles); + +const DEFAULT_LANGUAGE = 'en'; +const DEFAULT_DATE_FORMAT = 'MM-dd-yyyy'; + +interface DatePickerProps { + onChange?: (date: Date | any) => void; + onBlur?: () => void; + onFocus?: () => void; + headerNodes?: ReactNode; + disabled?: boolean; + shouldCloseOnSelect?: boolean; + fixedHeight?: boolean; + startDate?: Date | undefined; + endDate?: Date | undefined; + customClassName?: string; + popperClassName?: string; + calendarClassName?: string; + customTimeInput?: ReactElement; + language?: string; + yearsOptions?: number[]; + placeholder?: string; + dateFormat?: string; + selects?: 'start' | 'end' | 'none'; + value?: Date | null; +} + +export const DatePicker2: FC = ({ + onChange = () => {}, + disabled = false, + onBlur = () => {}, + onFocus = () => {}, + endDate = undefined, + startDate = undefined, + headerNodes = null, + customClassName = '', + customTimeInput = undefined, + shouldCloseOnSelect = true, + popperClassName = '', + calendarClassName = '', + fixedHeight = false, + language = DEFAULT_LANGUAGE, + yearsOptions = [], + placeholder = DEFAULT_DATE_FORMAT.toUpperCase(), + dateFormat = DEFAULT_DATE_FORMAT, + selects = 'start', + value = null, +}) => { + const inputRef = useRef(null); + const startDateString = startDate?.toDateString(); + const endDateString = endDate?.toDateString(); + const isValidEndDate = endDate && startDate && endDate > startDate; + + const getDayClassName = (displayedDates: Date) => { + const displayedDateString = displayedDates.toDateString(); + const isCurrentDate = displayedDateString === startDateString; + const isEndDate = isValidEndDate && displayedDateString === endDateString; + + const isInsideSelectedRange = + startDate && endDate && displayedDates > startDate && displayedDates < endDate; + + return cx('date', { + 'current-date': isCurrentDate, + 'selected-range': isInsideSelectedRange && !isEndDate, + 'end-date': isEndDate && isValidEndDate, + disabled, + }); + }; + + return ( + } + ref={inputRef} + /> + } + placeholderText={placeholder} + selected={value} + startDate={startDate} + endDate={endDate} + minDate={selects === 'end' ? startDate : undefined} + disabled={disabled} + shouldCloseOnSelect={shouldCloseOnSelect} + fixedHeight={fixedHeight} + locale={language} + showPopperArrow={false} + dayClassName={getDayClassName} + calendarClassName={cx(calendarClassName, 'calendar')} + renderCustomHeader={(customHeaderProps: ReactDatePickerCustomHeaderProps) => ( + + )} + onChange={onChange} + onBlur={onBlur} + onFocus={onFocus} + customTimeInput={customTimeInput} + showTimeInput={Boolean(customTimeInput)} + popperClassName={cx(popperClassName, 'popper')} + dateFormat={dateFormat} + selectsStart={selects === 'start'} + selectsEnd={selects === 'end'} + className={cx('datepicker')} + /> + ); +}; diff --git a/src/components/dataPicker2/dataPicker2/header/datePickerHeader.module.scss b/src/components/dataPicker2/dataPicker2/header/datePickerHeader.module.scss new file mode 100644 index 0000000..256559c --- /dev/null +++ b/src/components/dataPicker2/dataPicker2/header/datePickerHeader.module.scss @@ -0,0 +1,72 @@ +@import 'src/assets/styles/variables/typography'; + +@mixin arrowHoverState($arrow-color) { + &:hover:not(.disabled) { + cursor: pointer; + svg > path { + fill: $arrow-color; + } + } +} + +@mixin disabledState { + opacity: 0.3; + pointer-events: none; +} + +@mixin setArrowDefaultProps { + align-self: center; + width: 16px; + height: 16px; + svg { + width: 16px; + height: 16px; + } +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + background-color: var(--rp-ui-base-bg-000); + padding-bottom: 18px; + + .dropdowns-wrapper { + display: flex; + align-items: center; + column-gap: 8px; + } + + .button-prev, + .button-next { + all: unset; + + @include setArrowDefaultProps(); + @include arrowHoverState(var(--rp-ui-base-e-400)); + &.disabled { + @include disabledState; + } + } + + .button-next { + transform: rotate(180deg); + } + + .dropdown { + width: auto; + + &.month-dropdown { + width: 117px; + } + + .toggle-button > span { + color: var(--rp-ui-base-topaz); + font-family: var(--rp-ui-base-font-family); + font-weight: $fw-bold; + } + + .toggle-button:hover > span { + color: var(-rp-ui-base-topaz-hover); + } + } +} diff --git a/src/components/dataPicker2/dataPicker2/header/datePickerHeader.tsx b/src/components/dataPicker2/dataPicker2/header/datePickerHeader.tsx new file mode 100644 index 0000000..2477e2a --- /dev/null +++ b/src/components/dataPicker2/dataPicker2/header/datePickerHeader.tsx @@ -0,0 +1,117 @@ +import { FC, ReactNode, useMemo } from 'react'; +import classNames from 'classnames/bind'; +import { Dropdown } from '@components/dropdown'; +import { CalendarArrowIcon } from '@components/icons'; +import { DropdownOptionType, DropdownValue } from '@components/dropdown/types'; +import { getYearsFrom } from '../utils'; +import styles from './datePickerHeader.module.scss'; + +const cx = classNames.bind(styles); + +export interface DatePickerHeaderProps { + changeYear: (year: number) => void; + changeMonth: (month: number) => void; + decreaseMonth: () => void; + increaseMonth: () => void; + headerNodes: ReactNode; + date: Date; + prevMonthButtonDisabled: boolean; + nextMonthButtonDisabled: boolean; + customClassName: string; + yearsOptions: number[]; + locale: string; +} + +export const DatePickerHeader: FC = ({ + date = new Date(), + changeYear = () => {}, + changeMonth = () => {}, + decreaseMonth = () => {}, + increaseMonth = () => {}, + prevMonthButtonDisabled = false, + nextMonthButtonDisabled = false, + headerNodes = null, + customClassName = '', + yearsOptions = [], + locale, +}) => { + const year = date.getFullYear(); + const month = date.getMonth(); + + const monthDropdownOptions = useMemo(() => { + const monthList = Array(12).keys(); + const formatter = new Intl.DateTimeFormat(locale, { + month: 'long', + }); + const getMonthName = (monthIndex: number) => formatter.format(new Date(year, monthIndex)); + const months = Array.from(monthList, getMonthName); + + return months.reduce((acc: DropdownOptionType[], monthValue, monthNumber) => { + return acc.concat({ + value: monthNumber, + label: monthValue, + }); + }, []); + }, []); + + const yearDropdownOptions: DropdownOptionType[] = useMemo(() => { + const yearValues = yearsOptions.length > 0 ? yearsOptions : getYearsFrom(year); + return yearValues.reduce( + (acc: DropdownOptionType[], yearValue) => + acc.concat({ value: yearValue, label: `${yearValue}` }), + [], + ); + }, [yearsOptions]); + + const onMonthChange = (changedMonth: DropdownValue) => { + const numberMonth: number = changedMonth as number; + changeMonth(numberMonth); + }; + + const onYearChange = (changedYear: DropdownValue) => { + const numberYear: number = changedYear as number; + changeYear(numberYear); + }; + + return ( + <> + {headerNodes &&
{headerNodes}
} +
+ +
+ + +
+ +
+ + ); +}; diff --git a/src/components/dataPicker2/dataPicker2/header/index.ts b/src/components/dataPicker2/dataPicker2/header/index.ts new file mode 100644 index 0000000..a25cc52 --- /dev/null +++ b/src/components/dataPicker2/dataPicker2/header/index.ts @@ -0,0 +1,5 @@ +import { DatePickerHeader } from './datePickerHeader'; + +export { DatePickerHeader }; + +export default DatePickerHeader; diff --git a/src/components/dataPicker2/dataPicker2/index.ts b/src/components/dataPicker2/dataPicker2/index.ts new file mode 100644 index 0000000..99fb13e --- /dev/null +++ b/src/components/dataPicker2/dataPicker2/index.ts @@ -0,0 +1,5 @@ +import { DatePicker2 } from './datePicker'; + +export { DatePicker2 }; + +export default DatePicker2; diff --git a/src/components/dataPicker2/dataPicker2/utils.ts b/src/components/dataPicker2/dataPicker2/utils.ts new file mode 100644 index 0000000..82ebaee --- /dev/null +++ b/src/components/dataPicker2/dataPicker2/utils.ts @@ -0,0 +1,11 @@ +import { Locale } from 'date-fns'; +import { registerLocale } from 'react-datepicker'; + +export const registerDatePickerLocale = (language: string, locale: Locale) => { + registerLocale(language, locale); +}; + +export const getYearsFrom = (start: number, amountYearsToGenerate = 20) => { + const yearsFromCurrent = start + amountYearsToGenerate; + return new Array(yearsFromCurrent - start).fill(undefined).map((_, i) => start - i); +}; diff --git a/src/components/date2/date2.tsx b/src/components/date2/date2.tsx new file mode 100644 index 0000000..6351422 --- /dev/null +++ b/src/components/date2/date2.tsx @@ -0,0 +1,67 @@ +import ReactDatePicker from 'react-datepicker'; +import { FC, ReactNode, ReactElement } from 'react'; + +const DEFAULT_LANGUAGE = 'en'; +const DEFAULT_DATE_FORMAT = 'MM-dd-yyyy'; + +interface DatePickerProps { + onChange?: (date: Date | any) => void; + onBlur?: () => void; + onFocus?: () => void; + headerNodes?: ReactNode; + disabled?: boolean; + shouldCloseOnSelect?: boolean; + fixedHeight?: boolean; + startDate?: Date | undefined; + endDate?: Date | undefined; + customClassName?: string; + popperClassName?: string; + calendarClassName?: string; + customTimeInput?: ReactElement; + language?: string; + yearsOptions?: number[]; + placeholder?: string; + dateFormat?: string; + selects?: 'start' | 'end' | 'none'; + value?: Date | null; +} + +export const Date2: FC = ({ + onChange = () => {}, + disabled = false, + onBlur = () => {}, + onFocus = () => {}, + endDate = undefined, + startDate = undefined, + customTimeInput = undefined, + shouldCloseOnSelect = true, + fixedHeight = false, + language = DEFAULT_LANGUAGE, + placeholder = DEFAULT_DATE_FORMAT.toUpperCase(), + dateFormat = DEFAULT_DATE_FORMAT, + selects = 'start', + value = null, +}) => { + return ( + + ); +}; diff --git a/src/components/date2/index.ts b/src/components/date2/index.ts new file mode 100644 index 0000000..57b7e90 --- /dev/null +++ b/src/components/date2/index.ts @@ -0,0 +1,5 @@ +import { Date2 } from './date2'; + +export { Date2 }; + +export default Date2; diff --git a/src/components/datePicker1/README.md b/src/components/datePicker1/README.md new file mode 100644 index 0000000..54e08cd --- /dev/null +++ b/src/components/datePicker1/README.md @@ -0,0 +1,34 @@ +## **DatePicker** + +width - 380px. Max height - flexible. + +### Props: + +- **disabled** : _bool_, optional, default = false +- **endDate** : _undefined_,_Date_, optional, default = undefined +- **startDate** : _undefined_,_Date_, optional, default = undefined +- **customClassName** : _string_, optional, default : "" +- **customTimeInput** : _ReactElement_, optional, default = undefined +- **shouldCloseOnSelect** : _bool_, optional, default = false +- **showPopperArrow** : _bool_, optional, default=false +- **popperClassName** : _string_, optional, default = "" +- **calendarClassName** : _string_, optional, default = "" +- **fixedHeight** : _bool_, optional, default = false +- **headerNodes** : _node_, optional, default = null +- **value** : _string_, optional, default = new Date() +- **language** : _string_ or _Locale_, optional, default = 'en' +- **yearsOptions** : _number[]_, optional, default = 'en' +- **placeholder** : _string_, optional, default = 'MM-DD-YYYY' +- **dateFormat** : _string_, optional, default = 'MM-dd-yyyy' +- **selects** : _string('start' | 'end')_, optional, default = '' +- **value** : _Date_, optional, default = null + +### Events: + +- **onChange** +- **onBlur** +- **onFocus** + + +### Setup: +- **registerDatePickerLocale** diff --git a/src/components/datePicker1/datePicker.module.scss b/src/components/datePicker1/datePicker.module.scss new file mode 100644 index 0000000..51238cb --- /dev/null +++ b/src/components/datePicker1/datePicker.module.scss @@ -0,0 +1,262 @@ +@import 'src/assets/styles/variables/typography'; +@import 'src/assets/styles/mixins/font-scale'; + +$DAY_OF_THE_WEEK_Z_INDEX: 3; +$DAY_OF_THE_WEEK_HOVERED_Z_INDEX: 2; +$POPOVER_Z_INDEX: 10; + +@mixin setMonthContainerProperties { + .react-datepicker__month-container { + width: 100%; + height: 100%; + float: none; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + @include setCustomHeaderProperties; + @include setMothProperties; + } +} + +@mixin setCustomHeaderProperties { + .react-datepicker__header.react-datepicker__header--custom { + width: 100%; + background-color: var(--rp-ui-base-bg-000); + border-bottom: none; + padding: 0; + @include setDaysNameOfTheWeekProperties; + } +} + +@mixin setMothProperties { + .react-datepicker__month { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + row-gap: 8px; + @include setDaysDigitsContainerProperties; + } +} + +@mixin setDaysDigitsContainerProperties { + .react-datepicker__week { + height: 32px; + display: flex; + align-items: center; + justify-content: space-between; + font-family: var(--rp-ui-base-font-family); + color: var(--rp-ui-base-dark-e-500); + @include font-scale(); + .react-datepicker__day--range-end:first-child:before { + display: none; + } + } + + .react-datepicker__day { + cursor: pointer; + + &.react-datepicker__day--in-selecting-range { + &:not(.react-datepicker__day--selecting-range-end, .react-datepicker__day--selecting-range-start, .react-datepicker__day--range-start, .react-datepicker__day--range-end) { + background-color: var(--rp-ui-base-bg-200); + height: 32px; + line-height: 32px; + position: relative; + } + } + + &.react-datepicker__day--selected { + position: relative; + border-radius: 50%; + background-color: var(--rp-ui-base-topaz); + font-family: var(--rp-ui-base-font-family); + font-weight: $fw-bold; + color: var(--rp-ui-base-bg-000); + } + } + + .react-datepicker__day--disabled { + cursor: default; + + &:hover { + border: none !important; + line-height: 40px !important; + } + } +} + +@mixin setDaysNameOfTheWeekProperties { + .react-datepicker__day-names { + display: flex; + height: 40px; + justify-content: space-between; + font-family: var(--rp-ui-base-font-family); + font-weight: $fw-bold; + @include font-scale(); + color: var(--rp-ui-base-dark-e-500); + vertical-align: middle; + + .react-datepicker__day-name { + width: 40px; + text-align: center; + } + } +} + +@mixin removeAriaLive { + .react-datepicker__aria-live { + display: none; + } +} + +@mixin setHoverState($backgroundColor, $textColor: inherit) { + border-radius: 50%; + border: 1px solid var(--rp-ui-base-topaz); + background-color: $backgroundColor; + line-height: 38px; + color: $textColor; +} + +@mixin removeOutline { + &:focus-visible { + outline: none; + } +} + +@mixin verticalAlign($height) { + height: $height; + line-height: $height; +} + +@mixin drawBeforePseudoElement { + position: absolute; + content: ''; + height: 32px; + background-color: var(--rp-ui-base-bg-200); + width: 16px; + top: 0; + left: -9px; + z-index: 1; +} + +@mixin drawAfterPseudoElement($displayValue, $borderWidth) { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + border: $borderWidth solid var(--rp-ui-base-topaz); + border-radius: 50%; + display: $displayValue; + z-index: $DAY_OF_THE_WEEK_HOVERED_Z_INDEX; + box-sizing: border-box; +} + +:global { + @include removeAriaLive; +} + +.calendar { + box-sizing: border-box; + background-color: var(--rp-ui-base-bg-000); + width: 344px; + padding: 30px 32px 32px; + border-radius: 8px; + box-shadow: 0px 8px 40px var(--rp-ui-base-dark-bg-light); + border: none; + margin-top: 4px; + + :global { + @include setMonthContainerProperties; + } + + .current-date, + .date { + width: 40px; + margin: 0; + box-sizing: border-box; + @include verticalAlign(40px); + @include removeOutline; + } + + .date { + background-color: transparent; + border-radius: unset; + color: inherit; + text-align: center; + } + + .current-date, + .current-date:hover { + position: relative; + z-index: $DAY_OF_THE_WEEK_Z_INDEX; + font-family: var(--rp-ui-base-font-family); + font-weight: $fw-bold; + @include setHoverState(var(--rp-ui-base-topaz), var(--rp-ui-base-bg-000)); + } + + .date:hover:not(.current-date):not(.selected-range):not(.end-date) { + @include setHoverState(transparent); + } + + .end-date { + position: relative; + border-radius: 50%; + background-color: var(--rp-ui-base-topaz); + font-family: var(--rp-ui-base-font-family); + font-weight: $fw-bold; + color: var(--rp-ui-base-bg-000); + } + + .end-date::after { + @include drawAfterPseudoElement(block, 10px); + } + + .end-date::before { + @include drawBeforePseudoElement; + top: 4px; + } + + .selected-range { + background-color: var(--rp-ui-base-bg-200); + border-radius: 8px; + @include verticalAlign(32px); + position: relative; + &:hover { + @include verticalAlign(40px); + border-radius: 50%; + background: var(--rp-ui-base-bg-200); + &::after { + display: block; + } + &:not(:first-child)::before { + top: 4px; + } + } + } + + .selected-range::after { + @include drawAfterPseudoElement(none, 1px); + } + + .selected-range:not(:first-child)::before { + @include drawBeforePseudoElement; + } + + .disabled { + color: var(--rp-ui-base-e-400); + background-color: transparent; + @include removeOutline; + } +} +.popper { + z-index: $POPOVER_Z_INDEX; +} + +.input { + width: 100%; + min-width: 138px; +} diff --git a/src/components/datePicker1/datePicker.tsx b/src/components/datePicker1/datePicker.tsx new file mode 100644 index 0000000..81d71aa --- /dev/null +++ b/src/components/datePicker1/datePicker.tsx @@ -0,0 +1,121 @@ +import ReactDatePicker, { ReactDatePickerCustomHeaderProps } from 'react-datepicker'; +import classNames from 'classnames/bind'; +import { FC, ReactNode, useRef, ReactElement } from 'react'; +import { FieldText } from '@components/fieldText'; +import { CalendarIcon } from '@components/icons'; +import { DatePickerHeader } from './header/datePickerHeader'; +import styles from './datePicker.module.scss'; + +const cx = classNames.bind(styles); + +const DEFAULT_LANGUAGE = 'en'; +const DEFAULT_DATE_FORMAT = 'MM-dd-yyyy'; + +interface DatePickerProps { + onChange?: (date: Date | any) => void; + onBlur?: () => void; + onFocus?: () => void; + headerNodes?: ReactNode; + disabled?: boolean; + shouldCloseOnSelect?: boolean; + fixedHeight?: boolean; + startDate?: Date | undefined; + endDate?: Date | undefined; + customClassName?: string; + popperClassName?: string; + calendarClassName?: string; + customTimeInput?: ReactElement; + language?: string; + yearsOptions?: number[]; + placeholder?: string; + dateFormat?: string; + selects?: 'start' | 'end' | 'none'; + value?: Date | null; +} + +export const DatePicker1: FC = ({ + onChange = () => {}, + disabled = false, + onBlur = () => {}, + onFocus = () => {}, + endDate = undefined, + startDate = undefined, + headerNodes = null, + customClassName = '', + customTimeInput = undefined, + shouldCloseOnSelect = true, + popperClassName = '', + calendarClassName = '', + fixedHeight = false, + language = DEFAULT_LANGUAGE, + yearsOptions = [], + placeholder = DEFAULT_DATE_FORMAT.toUpperCase(), + dateFormat = DEFAULT_DATE_FORMAT, + selects = 'start', + value = null, +}) => { + const inputRef = useRef(null); + const startDateString = startDate?.toDateString(); + const endDateString = endDate?.toDateString(); + const isValidEndDate = endDate && startDate && endDate > startDate; + + const getDayClassName = (displayedDates: Date) => { + const displayedDateString = displayedDates.toDateString(); + const isCurrentDate = displayedDateString === startDateString; + const isEndDate = isValidEndDate && displayedDateString === endDateString; + + const isInsideSelectedRange = + startDate && endDate && displayedDates > startDate && displayedDates < endDate; + + return cx('date', { + 'current-date': isCurrentDate, + 'selected-range': isInsideSelectedRange && !isEndDate, + 'end-date': isEndDate && isValidEndDate, + disabled, + }); + }; + + return ( + } + ref={inputRef} + /> + } + placeholderText={placeholder} + selected={value} + startDate={startDate} + endDate={endDate} + minDate={selects === 'end' ? startDate : undefined} + disabled={disabled} + shouldCloseOnSelect={shouldCloseOnSelect} + fixedHeight={fixedHeight} + locale={language} + showPopperArrow={false} + dayClassName={getDayClassName} + calendarClassName={cx(calendarClassName, 'calendar')} + renderCustomHeader={(customHeaderProps: ReactDatePickerCustomHeaderProps) => ( + + )} + onChange={onChange} + onBlur={onBlur} + onFocus={onFocus} + customTimeInput={customTimeInput} + showTimeInput={Boolean(customTimeInput)} + popperClassName={cx(popperClassName, 'popper')} + dateFormat={dateFormat} + selectsStart={selects === 'start'} + selectsEnd={selects === 'end'} + className={cx('datepicker')} + /> + ); +}; diff --git a/src/components/datePicker1/header/datePickerHeader.module.scss b/src/components/datePicker1/header/datePickerHeader.module.scss new file mode 100644 index 0000000..256559c --- /dev/null +++ b/src/components/datePicker1/header/datePickerHeader.module.scss @@ -0,0 +1,72 @@ +@import 'src/assets/styles/variables/typography'; + +@mixin arrowHoverState($arrow-color) { + &:hover:not(.disabled) { + cursor: pointer; + svg > path { + fill: $arrow-color; + } + } +} + +@mixin disabledState { + opacity: 0.3; + pointer-events: none; +} + +@mixin setArrowDefaultProps { + align-self: center; + width: 16px; + height: 16px; + svg { + width: 16px; + height: 16px; + } +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + background-color: var(--rp-ui-base-bg-000); + padding-bottom: 18px; + + .dropdowns-wrapper { + display: flex; + align-items: center; + column-gap: 8px; + } + + .button-prev, + .button-next { + all: unset; + + @include setArrowDefaultProps(); + @include arrowHoverState(var(--rp-ui-base-e-400)); + &.disabled { + @include disabledState; + } + } + + .button-next { + transform: rotate(180deg); + } + + .dropdown { + width: auto; + + &.month-dropdown { + width: 117px; + } + + .toggle-button > span { + color: var(--rp-ui-base-topaz); + font-family: var(--rp-ui-base-font-family); + font-weight: $fw-bold; + } + + .toggle-button:hover > span { + color: var(-rp-ui-base-topaz-hover); + } + } +} diff --git a/src/components/datePicker1/header/datePickerHeader.tsx b/src/components/datePicker1/header/datePickerHeader.tsx new file mode 100644 index 0000000..2477e2a --- /dev/null +++ b/src/components/datePicker1/header/datePickerHeader.tsx @@ -0,0 +1,117 @@ +import { FC, ReactNode, useMemo } from 'react'; +import classNames from 'classnames/bind'; +import { Dropdown } from '@components/dropdown'; +import { CalendarArrowIcon } from '@components/icons'; +import { DropdownOptionType, DropdownValue } from '@components/dropdown/types'; +import { getYearsFrom } from '../utils'; +import styles from './datePickerHeader.module.scss'; + +const cx = classNames.bind(styles); + +export interface DatePickerHeaderProps { + changeYear: (year: number) => void; + changeMonth: (month: number) => void; + decreaseMonth: () => void; + increaseMonth: () => void; + headerNodes: ReactNode; + date: Date; + prevMonthButtonDisabled: boolean; + nextMonthButtonDisabled: boolean; + customClassName: string; + yearsOptions: number[]; + locale: string; +} + +export const DatePickerHeader: FC = ({ + date = new Date(), + changeYear = () => {}, + changeMonth = () => {}, + decreaseMonth = () => {}, + increaseMonth = () => {}, + prevMonthButtonDisabled = false, + nextMonthButtonDisabled = false, + headerNodes = null, + customClassName = '', + yearsOptions = [], + locale, +}) => { + const year = date.getFullYear(); + const month = date.getMonth(); + + const monthDropdownOptions = useMemo(() => { + const monthList = Array(12).keys(); + const formatter = new Intl.DateTimeFormat(locale, { + month: 'long', + }); + const getMonthName = (monthIndex: number) => formatter.format(new Date(year, monthIndex)); + const months = Array.from(monthList, getMonthName); + + return months.reduce((acc: DropdownOptionType[], monthValue, monthNumber) => { + return acc.concat({ + value: monthNumber, + label: monthValue, + }); + }, []); + }, []); + + const yearDropdownOptions: DropdownOptionType[] = useMemo(() => { + const yearValues = yearsOptions.length > 0 ? yearsOptions : getYearsFrom(year); + return yearValues.reduce( + (acc: DropdownOptionType[], yearValue) => + acc.concat({ value: yearValue, label: `${yearValue}` }), + [], + ); + }, [yearsOptions]); + + const onMonthChange = (changedMonth: DropdownValue) => { + const numberMonth: number = changedMonth as number; + changeMonth(numberMonth); + }; + + const onYearChange = (changedYear: DropdownValue) => { + const numberYear: number = changedYear as number; + changeYear(numberYear); + }; + + return ( + <> + {headerNodes &&
{headerNodes}
} +
+ +
+ + +
+ +
+ + ); +}; diff --git a/src/components/datePicker1/header/index.ts b/src/components/datePicker1/header/index.ts new file mode 100644 index 0000000..a25cc52 --- /dev/null +++ b/src/components/datePicker1/header/index.ts @@ -0,0 +1,5 @@ +import { DatePickerHeader } from './datePickerHeader'; + +export { DatePickerHeader }; + +export default DatePickerHeader; diff --git a/src/components/datePicker1/index.ts b/src/components/datePicker1/index.ts new file mode 100644 index 0000000..0ba184b --- /dev/null +++ b/src/components/datePicker1/index.ts @@ -0,0 +1,5 @@ +import { DatePicker1 } from './datePicker'; + +export { DatePicker1 }; + +export default DatePicker1; diff --git a/src/components/datePicker1/utils.ts b/src/components/datePicker1/utils.ts new file mode 100644 index 0000000..82ebaee --- /dev/null +++ b/src/components/datePicker1/utils.ts @@ -0,0 +1,11 @@ +import { Locale } from 'date-fns'; +import { registerLocale } from 'react-datepicker'; + +export const registerDatePickerLocale = (language: string, locale: Locale) => { + registerLocale(language, locale); +}; + +export const getYearsFrom = (start: number, amountYearsToGenerate = 20) => { + const yearsFromCurrent = start + amountYearsToGenerate; + return new Array(yearsFromCurrent - start).fill(undefined).map((_, i) => start - i); +}; diff --git a/src/components/index.ts b/src/components/index.ts index f2e7911..28e3ab4 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -16,5 +16,8 @@ export { Popover } from './popover'; export { Pagination } from './pagination'; export { Table } from './table'; export { DatePicker } from './datePicker'; +export { DatePicker1 } from './datePicker1'; +export { DatePicker2 } from './dataPicker2/dataPicker2'; +export { Date2 } from './date2'; export { SystemAlert } from './systemAlert'; export * from './icons'; diff --git a/vite.config.ts b/vite.config.ts index 2e0f76f..9cf0ecb 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -49,6 +49,8 @@ export default defineConfig(() => ({ pagination: resolve('src', 'components', 'pagination'), table: resolve('src', 'components', 'table'), datePicker: resolve('src', 'components', 'datePicker'), + datePicker1: resolve('src', 'components', 'datePicker1'), + date2: resolve('src', 'components', 'date2'), }, name: 'ui-kit', formats: ['es'],