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