baseui
Version:
A React Component library implementing the Base design language
580 lines (573 loc) • 23.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _chevronRight = _interopRequireDefault(require("../icon/chevron-right"));
var _chevronLeft = _interopRequireDefault(require("../icon/chevron-left"));
var _chevronDown = _interopRequireDefault(require("../icon/chevron-down"));
var _dateFnsAdapter = _interopRequireDefault(require("./utils/date-fns-adapter"));
var _dateHelpers = _interopRequireDefault(require("./utils/date-helpers"));
var _calendarHeaderHelpers = require("./utils/calendar-header-helpers");
var _menu = require("../menu");
var _popover = require("../popover");
var _locale = require("../locale");
var _themeProvider = require("../styles/theme-provider");
var _styledComponents = require("./styled-components");
var _constants = require("./constants");
var _overrides = require("../helpers/overrides");
var _focusVisible = require("../utils/focusVisible");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /*
Copyright (c) Uber Technologies, Inc.
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// @ts-ignore
const navBtnStyle = ({
$theme
}) => ({
cursor: 'pointer'
});
const MIN_YEAR = 2000;
const MAX_YEAR = 2030;
const MIN_MONTH = 0;
const MAX_MONTH = 11;
const DIRECTION = {
NEXT: 'next',
PREVIOUS: 'previous'
};
// When multiple calendar months are rendered, the month selection dropdown
// must account for which of the rendered calendar months it corresponds with
function adjustForCalendarOrder(monthId, year, order) {
let adjustedMonth = Number(monthId) - order;
let adjustedYear = year;
if (adjustedMonth < 0) {
adjustedMonth = 11;
adjustedYear = adjustedYear - 1;
}
return {
adjustedMonthId: adjustedMonth.toString(),
adjustedYear: adjustedYear
};
}
// @ts-ignore
function idToYearMonth(id) {
return id.split('-').map(Number);
}
class CalendarHeader extends React.Component {
constructor(props) {
super(props);
// @ts-ignore
_defineProperty(this, "dateHelpers", void 0);
_defineProperty(this, "monthItems", void 0);
_defineProperty(this, "yearItems", void 0);
_defineProperty(this, "state", {
isMonthDropdownOpen: false,
isYearDropdownOpen: false,
isFocusVisible: false
});
_defineProperty(this, "getDateProp", () => {
return this.props.date || this.dateHelpers.date();
});
_defineProperty(this, "getYearItems", () => {
const date = this.getDateProp();
const maxDate = this.props.maxDate;
const minDate = this.props.minDate;
const maxYear = maxDate ? this.dateHelpers.getYear(maxDate) : MAX_YEAR;
const minYear = minDate ? this.dateHelpers.getYear(minDate) : MIN_YEAR;
const selectedMonth = this.dateHelpers.getMonth(date);
// TODO: this logic can be optimized to only run when minDate / maxDate change
this.yearItems = Array.from({
length: maxYear - minYear + 1
}, (_, i) => minYear + i).map(year => ({
id: year.toString(),
label: year.toString()
}));
const monthOfMaxDate = maxDate ? this.dateHelpers.getMonth(maxDate) : MAX_MONTH;
const monthOfMinDate = minDate ? this.dateHelpers.getMonth(minDate) : MIN_MONTH;
// Generates array like [0,1,.... monthOfMaxDate]
const maxYearMonths = Array.from({
length: monthOfMaxDate + 1
}, (x, i) => i);
// Generates array like [monthOfMinDate, ...., 10, 11]
const minYearMonths = Array.from({
length: 12 - monthOfMinDate
}, (x, i) => i + monthOfMinDate);
if (selectedMonth > maxYearMonths[maxYearMonths.length - 1]) {
const lastIdx = this.yearItems.length - 1;
this.yearItems[lastIdx] = {
...this.yearItems[lastIdx],
disabled: true
};
}
if (selectedMonth < minYearMonths[0]) {
this.yearItems[0] = {
...this.yearItems[0],
disabled: true
};
}
});
_defineProperty(this, "getMonthItems", () => {
const date = this.getDateProp();
const year = this.dateHelpers.getYear(date);
const maxDate = this.props.maxDate;
const minDate = this.props.minDate;
const maxYear = maxDate ? this.dateHelpers.getYear(maxDate) : MAX_YEAR;
const minYear = minDate ? this.dateHelpers.getYear(minDate) : MIN_YEAR;
const monthOfMaxDate = maxDate ? this.dateHelpers.getMonth(maxDate) : MAX_MONTH;
// Generates array like [0,1,.... monthOfMaxDate]
const maxYearMonths = Array.from({
length: monthOfMaxDate + 1
}, (x, i) => i);
const monthOfMinDate = minDate ? this.dateHelpers.getMonth(minDate) : MIN_MONTH;
// Generates array like [monthOfMinDate, ...., 10, 11]
const minYearMonths = Array.from({
length: 12 - monthOfMinDate
}, (x, i) => i + monthOfMinDate);
const maxMinYearMonthsIntersection = maxYearMonths.filter(year => minYearMonths.includes(year));
const filterMonthsList = year === maxYear && year === minYear ? maxMinYearMonthsIntersection : year === maxYear ? maxYearMonths : year === minYear ? minYearMonths : null;
// @ts-ignore
const formatMonthLabel = month => this.dateHelpers.getMonthInLocale(month, this.props.locale);
this.monthItems = (0, _calendarHeaderHelpers.getFilteredMonthItems)({
filterMonthsList,
formatMonthLabel
});
});
_defineProperty(this, "increaseMonth", () => {
if (this.props.onMonthChange) {
this.props.onMonthChange({
date: this.dateHelpers.addMonths(this.getDateProp(),
// in a multi-month context, `order` is the number months ahead of
// the root Calendar month that this CalendarHeader displays. We account
// for this by incrementing the month by 1, less the value of `order`.
1 - this.props.order)
});
}
});
_defineProperty(this, "decreaseMonth", () => {
if (this.props.onMonthChange) {
this.props.onMonthChange({
date: this.dateHelpers.subMonths(this.getDateProp(), 1)
});
}
});
_defineProperty(this, "isMultiMonthHorizontal", () => {
const {
monthsShown,
orientation
} = this.props;
if (!monthsShown) {
return false;
}
return orientation === _constants.ORIENTATION.horizontal && monthsShown > 1;
});
_defineProperty(this, "isHiddenPaginationButton", direction => {
const {
monthsShown,
order
} = this.props;
if (!!monthsShown && this.isMultiMonthHorizontal()) {
if (direction === DIRECTION.NEXT) {
const isLastMonth = order === monthsShown - 1;
return !isLastMonth;
} else {
const isFirstMonth = order === 0;
return !isFirstMonth;
}
}
return false;
});
_defineProperty(this, "handleFocus", event => {
if ((0, _focusVisible.isFocusVisible)(event)) {
this.setState({
isFocusVisible: true
});
}
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_defineProperty(this, "handleBlur", event => {
if (this.state.isFocusVisible !== false) {
this.setState({
isFocusVisible: false
});
}
});
_defineProperty(this, "renderPreviousMonthButton", ({
locale,
theme
}) => {
const date = this.getDateProp();
const {
overrides = {},
density
} = this.props;
const allPrevDaysDisabled = this.dateHelpers.monthDisabledBefore(date, this.props);
let isDisabled = false;
if (allPrevDaysDisabled) {
isDisabled = true;
}
const nextMonth = this.dateHelpers.subMonths(date, 1);
const minYear = this.props.minDate ? this.dateHelpers.getYear(this.props.minDate) : MIN_YEAR;
if (this.dateHelpers.getYear(nextMonth) < minYear) {
isDisabled = true;
}
const isHidden = this.isHiddenPaginationButton(DIRECTION.PREVIOUS);
if (isHidden) {
isDisabled = true;
}
const [PrevButton, prevButtonProps] = (0, _overrides.getOverrides)(overrides.PrevButton, _styledComponents.StyledPrevButton);
const [PrevButtonIcon, prevButtonIconProps] = (0, _overrides.getOverrides)(overrides.PrevButtonIcon, theme.direction === 'rtl' ? _chevronRight.default : _chevronLeft.default);
let clickHandler = this.decreaseMonth;
if (allPrevDaysDisabled) {
// @ts-ignore
clickHandler = null;
}
return /*#__PURE__*/React.createElement(PrevButton, _extends({
"aria-label": locale.datepicker.previousMonth,
tabIndex: 0,
onClick: clickHandler,
disabled: isDisabled,
$isFocusVisible: this.state.isFocusVisible,
type: "button",
$disabled: isDisabled,
$order: this.props.order,
$density: this.props.density
}, prevButtonProps), isHidden ? null : /*#__PURE__*/React.createElement(PrevButtonIcon, _extends({
size: density === _constants.DENSITY.high ? 24 : 36,
overrides: {
Svg: {
style: navBtnStyle
}
}
}, prevButtonIconProps)));
});
_defineProperty(this, "renderNextMonthButton", ({
locale,
theme
}) => {
const date = this.getDateProp();
const {
overrides = {},
density
} = this.props;
const allNextDaysDisabled = this.dateHelpers.monthDisabledAfter(date, this.props);
let isDisabled = false;
if (allNextDaysDisabled) {
isDisabled = true;
}
const nextMonth = this.dateHelpers.addMonths(date, 1);
const maxYear = this.props.maxDate ? this.dateHelpers.getYear(this.props.maxDate) : MAX_YEAR;
if (this.dateHelpers.getYear(nextMonth) > maxYear) {
isDisabled = true;
}
const isHidden = this.isHiddenPaginationButton(DIRECTION.NEXT);
if (isHidden) {
isDisabled = true;
}
const [NextButton, nextButtonProps] = (0, _overrides.getOverrides)(overrides.NextButton, _styledComponents.StyledNextButton);
const [NextButtonIcon, nextButtonIconProps] = (0, _overrides.getOverrides)(overrides.NextButtonIcon, theme.direction === 'rtl' ? _chevronLeft.default : _chevronRight.default);
let clickHandler = this.increaseMonth;
// The other option is to always provide a click handler and let customers
// override its functionality based on the `$allPrevDaysDisabled` prop
// in a custom NextButton component override
// Their options would be to render `null` or not apply the components handler
// on click or do nothing
if (allNextDaysDisabled) {
// @ts-ignore
clickHandler = null;
}
return /*#__PURE__*/React.createElement(NextButton, _extends({
"aria-label": locale.datepicker.nextMonth,
tabIndex: 0,
onClick: clickHandler,
disabled: isDisabled,
type: "button",
$disabled: isDisabled,
$isFocusVisible: this.state.isFocusVisible,
$order: this.props.order,
$density: this.props.density,
$isTrailing: true
}, nextButtonProps), isHidden ? null : /*#__PURE__*/React.createElement(NextButtonIcon, _extends({
size: density === _constants.DENSITY.high ? 24 : 36,
overrides: {
Svg: {
style: navBtnStyle
}
}
}, nextButtonIconProps)));
});
_defineProperty(this, "canArrowsOpenDropdown", event => {
if (!this.state.isMonthDropdownOpen && !this.state.isYearDropdownOpen) {
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
return true;
}
}
return false;
});
_defineProperty(this, "renderMonthYearDropdown", () => {
const date = this.getDateProp();
const month = this.dateHelpers.getMonth(date);
const year = this.dateHelpers.getYear(date);
const order = this.props.order;
const {
locale,
overrides = {},
density
} = this.props;
const [MonthYearSelectButton, monthYearSelectButtonProps] = (0, _overrides.getOverrides)(overrides.MonthYearSelectButton, _styledComponents.StyledMonthYearSelectButton);
const [MonthYearSelectIconContainer, monthYearSelectIconContainerProps] = (0, _overrides.getOverrides)(overrides.MonthYearSelectIconContainer, _styledComponents.StyledMonthYearSelectIconContainer);
const [OverriddenPopover, popoverProps] = (0, _overrides.getOverrides)(overrides.MonthYearSelectPopover, _popover.Popover);
const [OverriddenStatefulMenu, menuProps] = (0, _overrides.getOverrides)(overrides.MonthYearSelectStatefulMenu, _menu.StatefulMenu);
menuProps.overrides = (0, _overrides.mergeOverrides)({
List: {
style: {
height: 'auto',
maxHeight: '257px'
}
}
}, menuProps && menuProps.overrides);
const initialMonthIndex = this.monthItems.findIndex(month => month.id === this.dateHelpers.getMonth(date).toString());
const initialYearIndex = this.yearItems.findIndex(year => year.id === this.dateHelpers.getYear(date).toString());
const monthTitle = `${this.dateHelpers.getMonthInLocale(this.dateHelpers.getMonth(date), locale)}`;
const yearTitle = `${this.dateHelpers.getYear(date)}`;
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(OverriddenPopover, _extends({
placement: "bottom",
autoFocus: true,
focusLock: true,
isOpen: this.state.isMonthDropdownOpen,
onClick: () => {
this.setState(prev => ({
isMonthDropdownOpen: !prev.isMonthDropdownOpen
}));
},
onClickOutside: () => this.setState({
isMonthDropdownOpen: false
}),
onEsc: () => this.setState({
isMonthDropdownOpen: false
}),
content: () => /*#__PURE__*/React.createElement(OverriddenStatefulMenu, _extends({
initialState: {
highlightedIndex: initialMonthIndex,
isFocused: true
},
items: this.monthItems
// @ts-ignore
,
onItemSelect: ({
item,
event
}) => {
event.preventDefault();
const {
adjustedMonthId,
adjustedYear
} = adjustForCalendarOrder(item.id, year, order);
const updatedDate = this.dateHelpers.set(date, {
year: adjustedYear,
month: idToYearMonth(adjustedMonthId)
});
this.props.onMonthChange && this.props.onMonthChange({
date: updatedDate
});
this.setState({
isMonthDropdownOpen: false
});
}
}, menuProps))
}, popoverProps), /*#__PURE__*/React.createElement(MonthYearSelectButton, _extends({
"aria-live": "polite",
type: "button",
$isFocusVisible: this.state.isFocusVisible,
$density: density
// @ts-ignore
,
onKeyUp: event => {
if (this.canArrowsOpenDropdown(event)) {
this.setState({
isMonthDropdownOpen: true
});
}
}
// @ts-ignore
,
onKeyDown: event => {
if (this.canArrowsOpenDropdown(event)) {
// disables page scroll
event.preventDefault();
}
if (event.key === 'Tab') {
this.setState({
isMonthDropdownOpen: false
});
}
}
}, monthYearSelectButtonProps, {
"aria-label": `Month, ${monthTitle}`
}), monthTitle, /*#__PURE__*/React.createElement(MonthYearSelectIconContainer, monthYearSelectIconContainerProps, /*#__PURE__*/React.createElement(_chevronDown.default, {
title: "",
overrides: {
Svg: {
props: {
role: 'presentation'
}
}
},
size: density === _constants.DENSITY.high ? 16 : 24
})))), /*#__PURE__*/React.createElement(OverriddenPopover, _extends({
placement: "bottom",
focusLock: true,
isOpen: this.state.isYearDropdownOpen,
onClick: () => {
this.setState(prev => ({
isYearDropdownOpen: !prev.isYearDropdownOpen
}));
},
onClickOutside: () => this.setState({
isYearDropdownOpen: false
}),
onEsc: () => this.setState({
isYearDropdownOpen: false
}),
content: () => /*#__PURE__*/React.createElement(OverriddenStatefulMenu, _extends({
initialState: {
highlightedIndex: initialYearIndex,
isFocused: true
},
items: this.yearItems
// @ts-ignore
,
onItemSelect: ({
item,
event
}) => {
event.preventDefault();
const year = idToYearMonth(item.id);
const updatedDate = this.dateHelpers.set(date, {
year,
month
});
this.props.onYearChange && this.props.onYearChange({
date: updatedDate
});
this.setState({
isYearDropdownOpen: false
});
}
}, menuProps))
}, popoverProps), /*#__PURE__*/React.createElement(MonthYearSelectButton, _extends({
"aria-live": "polite",
type: "button",
$isFocusVisible: this.state.isFocusVisible,
$density: density
// @ts-ignore
,
onKeyUp: event => {
if (this.canArrowsOpenDropdown(event)) {
this.setState({
isYearDropdownOpen: true
});
}
}
// @ts-ignore
,
onKeyDown: event => {
if (this.canArrowsOpenDropdown(event)) {
// disables page scroll
event.preventDefault();
}
if (event.key === 'Tab') {
this.setState({
isYearDropdownOpen: false
});
}
},
"aria-label": `Year, ${yearTitle}`
}, monthYearSelectButtonProps), yearTitle, /*#__PURE__*/React.createElement(MonthYearSelectIconContainer, monthYearSelectIconContainerProps, /*#__PURE__*/React.createElement(_chevronDown.default, {
title: "",
overrides: {
Svg: {
props: {
role: 'presentation'
}
}
},
size: density === _constants.DENSITY.high ? 16 : 24
})))));
});
this.dateHelpers = new _dateHelpers.default(props.adapter);
this.monthItems = [];
this.yearItems = [];
}
componentDidMount() {
this.getYearItems();
this.getMonthItems();
}
componentDidUpdate(prevProps) {
const selectedMonthDidChange = this.dateHelpers.getMonth(this.props.date) !== this.dateHelpers.getMonth(prevProps.date);
const selectedYearDidChange = this.dateHelpers.getYear(this.props.date) !== this.dateHelpers.getYear(prevProps.date);
if (selectedMonthDidChange) {
// re-calculate yearItems
this.getYearItems();
}
if (selectedYearDidChange) {
// re-calculate monthItems
this.getMonthItems();
}
}
render() {
const {
overrides = {},
density
} = this.props;
const [CalendarHeader, calendarHeaderProps] = (0, _overrides.getOverrides)(overrides.CalendarHeader, _styledComponents.StyledCalendarHeader);
const [MonthHeader, monthHeaderProps] = (0, _overrides.getOverrides)(overrides.MonthHeader, _styledComponents.StyledMonthHeader);
const [WeekdayHeader, weekdayHeaderProps] = (0, _overrides.getOverrides)(overrides.WeekdayHeader, _styledComponents.StyledWeekdayHeader);
const startOfWeek = this.dateHelpers.getStartOfWeek(this.getDateProp(), this.props.locale);
return /*#__PURE__*/React.createElement(_themeProvider.ThemeContext.Consumer, null, theme => /*#__PURE__*/React.createElement(_locale.LocaleContext.Consumer, null, locale => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(CalendarHeader, _extends({}, calendarHeaderProps, {
$density: this.props.density,
onFocus: (0, _focusVisible.forkFocus)(calendarHeaderProps, this.handleFocus),
onBlur: (0, _focusVisible.forkBlur)(calendarHeaderProps, this.handleBlur)
}), this.renderPreviousMonthButton({
locale,
theme
}), this.renderMonthYearDropdown(), this.renderNextMonthButton({
locale,
theme
})), /*#__PURE__*/React.createElement(MonthHeader, _extends({
role: "presentation"
}, monthHeaderProps), _constants.WEEKDAYS.map(offset => {
const day = this.dateHelpers.addDays(startOfWeek, offset);
return /*#__PURE__*/React.createElement(WeekdayHeader, _extends({
key: offset
}, weekdayHeaderProps, {
$density: density
}), /*#__PURE__*/React.createElement("abbr", {
style: {
textDecoration: 'none'
},
title: this.dateHelpers.getWeekdayInLocale(day, this.props.locale)
}, this.dateHelpers.getWeekdayMinInLocale(day, this.props.locale)));
})))));
}
}
exports.default = CalendarHeader;
_defineProperty(CalendarHeader, "defaultProps", {
adapter: _dateFnsAdapter.default,
// @ts-ignore
locale: null,
// @ts-ignore
maxDate: null,
// @ts-ignore
minDate: null,
onYearChange: () => {},
overrides: {}
});