mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TimePicker: Prevent TimePicker overflowing viewport on small screens (#59808)
* render timepicker in a modal style on small screens
* remove top: -1
* apply styles
* prevent bug where selecting a relative range wouldn't apply if the absolute ranges were expanded
* Revert "prevent bug where selecting a relative range wouldn't apply if the absolute ranges were expanded"
This reverts commit 7090443c12
.
This commit is contained in:
parent
90ece9d1f3
commit
1497ad4760
@ -7,9 +7,9 @@ import { dateTimeFormat, DateTime, dateTime, GrafanaTheme2, isDateTime } from '@
|
|||||||
|
|
||||||
import { Button, ClickOutsideWrapper, HorizontalGroup, Icon, InlineField, Input, Portal } from '../..';
|
import { Button, ClickOutsideWrapper, HorizontalGroup, Icon, InlineField, Input, Portal } from '../..';
|
||||||
import { useStyles2, useTheme2 } from '../../../themes';
|
import { useStyles2, useTheme2 } from '../../../themes';
|
||||||
|
import { getModalStyles } from '../../Modal/getModalStyles';
|
||||||
import { TimeOfDayPicker } from '../TimeOfDayPicker';
|
import { TimeOfDayPicker } from '../TimeOfDayPicker';
|
||||||
import { getBodyStyles } from '../TimeRangePicker/CalendarBody';
|
import { getBodyStyles } from '../TimeRangePicker/CalendarBody';
|
||||||
import { getStyles as getCalendarStyles } from '../TimeRangePicker/TimePickerCalendar';
|
|
||||||
import { isValid } from '../utils';
|
import { isValid } from '../utils';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -29,8 +29,8 @@ export const DateTimePicker: FC<Props> = ({ date, maxDate, label, onChange }) =>
|
|||||||
const [isOpen, setOpen] = useState(false);
|
const [isOpen, setOpen] = useState(false);
|
||||||
|
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
|
const { modalBackdrop } = getModalStyles(theme);
|
||||||
const isFullscreen = useMedia(`(min-width: ${theme.breakpoints.values.lg}px)`);
|
const isFullscreen = useMedia(`(min-width: ${theme.breakpoints.values.lg}px)`);
|
||||||
const containerStyles = useStyles2(getCalendarStyles);
|
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const onApply = useCallback(
|
const onApply = useCallback(
|
||||||
@ -69,7 +69,7 @@ export const DateTimePicker: FC<Props> = ({ date, maxDate, label, onChange }) =>
|
|||||||
<div className={styles.modal} onClick={stopPropagation}>
|
<div className={styles.modal} onClick={stopPropagation}>
|
||||||
<DateTimeCalendar date={date} onChange={onApply} isFullscreen={false} onClose={() => setOpen(false)} />
|
<DateTimeCalendar date={date} onChange={onApply} isFullscreen={false} onClose={() => setOpen(false)} />
|
||||||
</div>
|
</div>
|
||||||
<div className={containerStyles.backdrop} onClick={stopPropagation} />
|
<div className={modalBackdrop} onClick={stopPropagation} />
|
||||||
</ClickOutsideWrapper>
|
</ClickOutsideWrapper>
|
||||||
</Portal>
|
</Portal>
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { useDialog } from '@react-aria/dialog';
|
import { useDialog } from '@react-aria/dialog';
|
||||||
import { FocusScope } from '@react-aria/focus';
|
import { FocusScope } from '@react-aria/focus';
|
||||||
import { useOverlay } from '@react-aria/overlays';
|
import { useOverlay } from '@react-aria/overlays';
|
||||||
@ -16,9 +16,10 @@ import {
|
|||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
import { useStyles2 } from '../../themes/ThemeContext';
|
import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
|
||||||
import { t, Trans } from '../../utils/i18n';
|
import { t, Trans } from '../../utils/i18n';
|
||||||
import { ButtonGroup } from '../Button';
|
import { ButtonGroup } from '../Button';
|
||||||
|
import { getModalStyles } from '../Modal/getModalStyles';
|
||||||
import { ToolbarButton } from '../ToolbarButton';
|
import { ToolbarButton } from '../ToolbarButton';
|
||||||
import { Tooltip } from '../Tooltip/Tooltip';
|
import { Tooltip } from '../Tooltip/Tooltip';
|
||||||
|
|
||||||
@ -85,10 +86,12 @@ export function TimeRangePicker(props: TimeRangePickerProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ref = createRef<HTMLElement>();
|
const ref = createRef<HTMLElement>();
|
||||||
const { overlayProps } = useOverlay({ onClose, isDismissable: true, isOpen }, ref);
|
const { overlayProps, underlayProps } = useOverlay({ onClose, isDismissable: true, isOpen }, ref);
|
||||||
const { dialogProps } = useDialog({}, ref);
|
const { dialogProps } = useDialog({}, ref);
|
||||||
|
|
||||||
|
const theme = useTheme2();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
const { modalBackdrop } = getModalStyles(theme);
|
||||||
const hasAbsolute = isDateTime(value.raw.from) || isDateTime(value.raw.to);
|
const hasAbsolute = isDateTime(value.raw.from) || isDateTime(value.raw.to);
|
||||||
const variant = isSynced ? 'active' : isOnCanvas ? 'canvas' : 'default';
|
const variant = isSynced ? 'active' : isOnCanvas ? 'canvas' : 'default';
|
||||||
|
|
||||||
@ -122,23 +125,26 @@ export function TimeRangePicker(props: TimeRangePickerProps) {
|
|||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<section ref={ref} {...overlayProps} {...dialogProps}>
|
<>
|
||||||
<FocusScope contain autoFocus>
|
<div role="presentation" className={cx(modalBackdrop, styles.backdrop)} {...underlayProps} />
|
||||||
<TimePickerContent
|
<section className={styles.content} ref={ref} {...overlayProps} {...dialogProps}>
|
||||||
timeZone={timeZone}
|
<FocusScope contain autoFocus>
|
||||||
fiscalYearStartMonth={fiscalYearStartMonth}
|
<TimePickerContent
|
||||||
value={value}
|
timeZone={timeZone}
|
||||||
onChange={onChange}
|
fiscalYearStartMonth={fiscalYearStartMonth}
|
||||||
quickOptions={quickOptions}
|
value={value}
|
||||||
history={history}
|
onChange={onChange}
|
||||||
showHistory
|
quickOptions={quickOptions}
|
||||||
widthOverride={widthOverride}
|
history={history}
|
||||||
onChangeTimeZone={onChangeTimeZone}
|
showHistory
|
||||||
onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth}
|
widthOverride={widthOverride}
|
||||||
hideQuickRanges={hideQuickRanges}
|
onChangeTimeZone={onChangeTimeZone}
|
||||||
/>
|
onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth}
|
||||||
</FocusScope>
|
hideQuickRanges={hideQuickRanges}
|
||||||
</section>
|
/>
|
||||||
|
</FocusScope>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{timeSyncButton}
|
{timeSyncButton}
|
||||||
@ -219,13 +225,33 @@ const formattedRange = (value: TimeRange, timeZone?: TimeZone) => {
|
|||||||
return rangeUtil.describeTimeRange(adjustedTimeRange, timeZone);
|
return rangeUtil.describeTimeRange(adjustedTimeRange, timeZone);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStyles = () => {
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
`,
|
`,
|
||||||
|
backdrop: css({
|
||||||
|
display: 'none',
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
display: 'block',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
content: css({
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
top: '116%',
|
||||||
|
zIndex: theme.zIndex.dropdown,
|
||||||
|
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
position: 'fixed',
|
||||||
|
right: '50%',
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translate(50%, -50%)',
|
||||||
|
zIndex: theme.zIndex.modal,
|
||||||
|
},
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import { DateTime, GrafanaTheme2, TimeZone } from '@grafana/data';
|
|||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
import { useTheme2 } from '../../../themes';
|
import { useTheme2 } from '../../../themes';
|
||||||
|
import { getModalStyles } from '../../Modal/getModalStyles';
|
||||||
|
|
||||||
import { Body } from './CalendarBody';
|
import { Body } from './CalendarBody';
|
||||||
import { Footer } from './CalendarFooter';
|
import { Footer } from './CalendarFooter';
|
||||||
@ -16,7 +17,7 @@ import { Header } from './CalendarHeader';
|
|||||||
export const getStyles = (theme: GrafanaTheme2, isReversed = false) => {
|
export const getStyles = (theme: GrafanaTheme2, isReversed = false) => {
|
||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
top: -1px;
|
top: 0px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
${isReversed ? 'left' : 'right'}: 544px;
|
${isReversed ? 'left' : 'right'}: 544px;
|
||||||
box-shadow: ${theme.shadows.z3};
|
box-shadow: ${theme.shadows.z3};
|
||||||
@ -38,26 +39,17 @@ export const getStyles = (theme: GrafanaTheme2, isReversed = false) => {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
modal: css`
|
modal: css`
|
||||||
|
box-shadow: ${theme.shadows.z3};
|
||||||
|
left: 50%;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20%;
|
top: 50%;
|
||||||
width: 100%;
|
transform: translate(-50%, -50%);
|
||||||
z-index: ${theme.zIndex.modal};
|
z-index: ${theme.zIndex.modal};
|
||||||
`,
|
`,
|
||||||
content: css`
|
content: css`
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 268px;
|
width: 268px;
|
||||||
`,
|
`,
|
||||||
backdrop: css`
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
background: #202226;
|
|
||||||
opacity: 0.7;
|
|
||||||
z-index: ${theme.zIndex.modalBackdrop};
|
|
||||||
text-align: center;
|
|
||||||
`,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -77,6 +69,7 @@ const stopPropagation = (event: React.MouseEvent<HTMLDivElement>) => event.stopP
|
|||||||
|
|
||||||
function TimePickerCalendar(props: TimePickerCalendarProps) {
|
function TimePickerCalendar(props: TimePickerCalendarProps) {
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
|
const { modalBackdrop } = getModalStyles(theme);
|
||||||
const styles = getStyles(theme, props.isReversed);
|
const styles = getStyles(theme, props.isReversed);
|
||||||
const { isOpen, isFullscreen, onClose } = props;
|
const { isOpen, isFullscreen, onClose } = props;
|
||||||
const ref = React.createRef<HTMLElement>();
|
const ref = React.createRef<HTMLElement>();
|
||||||
@ -112,6 +105,7 @@ function TimePickerCalendar(props: TimePickerCalendarProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<OverlayContainer>
|
<OverlayContainer>
|
||||||
|
<div className={modalBackdrop} onClick={stopPropagation} />
|
||||||
<FocusScope contain autoFocus restoreFocus>
|
<FocusScope contain autoFocus restoreFocus>
|
||||||
<section className={styles.modal} onClick={stopPropagation} ref={ref} {...overlayProps} {...dialogProps}>
|
<section className={styles.modal} onClick={stopPropagation} ref={ref} {...overlayProps} {...dialogProps}>
|
||||||
<div className={styles.content} aria-label={selectors.components.TimePicker.calendar.label}>
|
<div className={styles.content} aria-label={selectors.components.TimePicker.calendar.label}>
|
||||||
@ -121,7 +115,6 @@ function TimePickerCalendar(props: TimePickerCalendarProps) {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</FocusScope>
|
</FocusScope>
|
||||||
<div className={styles.backdrop} onClick={stopPropagation} />
|
|
||||||
</OverlayContainer>
|
</OverlayContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -267,10 +267,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme2, isReversed, hideQuickRang
|
|||||||
container: css`
|
container: css`
|
||||||
background: ${theme.colors.background.primary};
|
background: ${theme.colors.background.primary};
|
||||||
box-shadow: ${theme.shadows.z3};
|
box-shadow: ${theme.shadows.z3};
|
||||||
position: absolute;
|
|
||||||
z-index: ${theme.zIndex.dropdown};
|
|
||||||
width: ${isFullscreen ? '546px' : '262px'};
|
width: ${isFullscreen ? '546px' : '262px'};
|
||||||
top: 116%;
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 1px solid ${theme.colors.border.weak};
|
border: 1px solid ${theme.colors.border.weak};
|
||||||
${isReversed ? 'left' : 'right'}: 0;
|
${isReversed ? 'left' : 'right'}: 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user