1import React, { useCallback } from 'react';
2import { useStyles2 } from '../../../themes';
3import Calendar from 'react-calendar';
4import { css } from '@emotion/css';
5import { Icon } from '../../Icon/Icon';
6import { TimePickerCalendarProps } from './TimePickerCalendar';
7import { GrafanaTheme2, dateTime, dateTimeParse, DateTime, TimeZone } from '@grafana/data';
8
9export function Body({ onChange, from, to, timeZone }: TimePickerCalendarProps) {
10  const value = inputToValue(from, to);
11  const onCalendarChange = useOnCalendarChange(onChange, timeZone);
12  const styles = useStyles2(getBodyStyles);
13
14  return (
15    <Calendar
16      selectRange={true}
17      next2Label={null}
18      prev2Label={null}
19      className={styles.body}
20      tileClassName={styles.title}
21      value={value}
22      nextLabel={<Icon name="angle-right" />}
23      prevLabel={<Icon name="angle-left" />}
24      onChange={onCalendarChange}
25      locale="en"
26    />
27  );
28}
29
30Body.displayName = 'Body';
31
32export function inputToValue(from: DateTime, to: DateTime, invalidDateDefault: Date = new Date()): Date[] {
33  const fromAsDate = from.toDate();
34  const toAsDate = to.toDate();
35  const fromAsValidDate = dateTime(fromAsDate).isValid() ? fromAsDate : invalidDateDefault;
36  const toAsValidDate = dateTime(toAsDate).isValid() ? toAsDate : invalidDateDefault;
37
38  if (fromAsValidDate > toAsValidDate) {
39    return [toAsValidDate, fromAsValidDate];
40  }
41  return [fromAsValidDate, toAsValidDate];
42}
43
44function useOnCalendarChange(onChange: (from: DateTime, to: DateTime) => void, timeZone?: TimeZone) {
45  return useCallback(
46    (value: Date | Date[]) => {
47      if (!Array.isArray(value)) {
48        return console.error('onCalendarChange: should be run in selectRange={true}');
49      }
50
51      const from = dateTimeParse(dateInfo(value[0]), { timeZone });
52      const to = dateTimeParse(dateInfo(value[1]), { timeZone });
53
54      onChange(from, to);
55    },
56    [onChange, timeZone]
57  );
58}
59
60function dateInfo(date: Date): number[] {
61  return [date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()];
62}
63
64export const getBodyStyles = (theme: GrafanaTheme2) => {
65  return {
66    title: css`
67      color: ${theme.colors.text};
68      background-color: ${theme.colors.background.primary};
69      font-size: ${theme.typography.size.md};
70      border: 1px solid transparent;
71
72      &:hover {
73        position: relative;
74      }
75    `,
76    body: css`
77      z-index: ${theme.zIndex.modal};
78      background-color: ${theme.colors.background.primary};
79      width: 268px;
80
81      .react-calendar__navigation__label,
82      .react-calendar__navigation__arrow,
83      .react-calendar__navigation {
84        padding-top: 4px;
85        background-color: inherit;
86        color: ${theme.colors.text};
87        border: 0;
88        font-weight: ${theme.typography.fontWeightMedium};
89      }
90
91      .react-calendar__month-view__weekdays {
92        background-color: inherit;
93        text-align: center;
94        color: ${theme.colors.primary.text};
95
96        abbr {
97          border: 0;
98          text-decoration: none;
99          cursor: default;
100          display: block;
101          padding: 4px 0 4px 0;
102        }
103      }
104
105      .react-calendar__month-view__days {
106        background-color: inherit;
107      }
108
109      .react-calendar__tile,
110      .react-calendar__tile--now {
111        margin-bottom: 4px;
112        background-color: inherit;
113        height: 26px;
114      }
115
116      .react-calendar__navigation__label,
117      .react-calendar__navigation > button:focus,
118      .time-picker-calendar-tile:focus {
119        outline: 0;
120      }
121
122      .react-calendar__tile--active,
123      .react-calendar__tile--active:hover {
124        color: ${theme.colors.primary.contrastText};
125        font-weight: ${theme.typography.fontWeightMedium};
126        background: ${theme.colors.primary.main};
127        box-shadow: none;
128        border: 0px;
129      }
130
131      .react-calendar__tile--rangeEnd,
132      .react-calendar__tile--rangeStart {
133        padding: 0;
134        border: 0px;
135        color: ${theme.colors.primary.contrastText};
136        font-weight: ${theme.typography.fontWeightMedium};
137        background: ${theme.colors.primary.main};
138
139        abbr {
140          background-color: ${theme.colors.primary.main};
141          border-radius: 100px;
142          display: block;
143          padding-top: 2px;
144          height: 26px;
145        }
146      }
147
148      .react-calendar__tile--rangeStart {
149        border-top-left-radius: 20px;
150        border-bottom-left-radius: 20px;
151      }
152
153      .react-calendar__tile--rangeEnd {
154        border-top-right-radius: 20px;
155        border-bottom-right-radius: 20px;
156      }
157    `,
158  };
159};
160