Commit 80294b2d by Torkel Ödegaard Committed by GitHub

TimeRangePicker: Updates components to use new ToolbarButton & ButtonGroup (#30570)

* WIP: Using new components

* Progress

* Everything looks to be working

* Explore: Replaces navbar-button and overriden explore button css classes with ToolbarButton and cleans up scss & markup, removes ResponsiveButton (#30571)

* Explore: Replaces navbar-button and overriden explore button css classes with ToolbarButton and cleans up scss & markup, removes ResponsiveButton

* Change live button text when paused

* Fixed story

* For the dashboard toolbar button I need a transparent button so I refactored the states/variants into a new ToolbarButtonVariatn

* Changing my mind on the transparent variant

* review fixes

* Review fixes
parent 951b11a9
......@@ -71,7 +71,7 @@ export const Variants: Story<ButtonProps> = ({ children, ...args }) => {
<div />
<HorizontalGroup spacing="lg">
<div>Inside ButtonGroup</div>
<ButtonGroup noSpacing>
<ButtonGroup>
<Button icon="sync">Run query</Button>
<Button icon="angle-down" />
</ButtonGroup>
......
......@@ -5,21 +5,13 @@ import { useStyles } from '../../themes';
export interface Props extends HTMLAttributes<HTMLDivElement> {
className?: string;
noSpacing?: boolean;
}
export const ButtonGroup = forwardRef<HTMLDivElement, Props>(({ noSpacing, className, children, ...rest }, ref) => {
export const ButtonGroup = forwardRef<HTMLDivElement, Props>(({ className, children, ...rest }, ref) => {
const styles = useStyles(getStyles);
const mainClass = cx(
{
[styles.wrapper]: !noSpacing,
[styles.wrapperNoSpacing]: noSpacing,
},
className
);
return (
<div ref={ref} className={mainClass} {...rest}>
<div ref={ref} className={cx('button-group', styles.wrapper, className)} {...rest}>
{children}
</div>
);
......@@ -31,26 +23,17 @@ const getStyles = (theme: GrafanaTheme) => ({
wrapper: css`
display: flex;
> a,
> button {
margin-left: ${theme.spacing.sm};
border-radius: 0;
border-right-width: 0;
&:first-child {
&.toolbar-button {
margin-left: 0;
}
}
`,
wrapperNoSpacing: css`
display: flex;
> a,
> button {
border-radius: 0;
border-right: 0;
&:last-of-type {
border-radius: 0 ${theme.border.radius.sm} ${theme.border.radius.sm} 0;
border-right: 1px solid ${theme.colors.border2};
border-right-width: 1px;
}
&:first-child {
......
import React from 'react';
import { ToolbarButton, ButtonGroup, useTheme, VerticalGroup, HorizontalGroup } from '@grafana/ui';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { ToolbarButtonRow } from './ToolbarButtonRow';
import { ToolbarButtonVariant } from './ToolbarButton';
export default {
title: 'Buttons/ToolbarButton',
......@@ -11,12 +13,13 @@ export default {
export const List = () => {
const theme = useTheme();
const variants: ToolbarButtonVariant[] = ['default', 'active', 'primary', 'destructive'];
return (
<div style={{ background: theme.colors.dashboardBg, padding: '32px' }}>
<VerticalGroup>
Wrapped in normal ButtonGroup (md spacing)
<ButtonGroup>
Button states
<ToolbarButtonRow>
<ToolbarButton>Just text</ToolbarButton>
<ToolbarButton icon="sync" tooltip="Sync" />
<ToolbarButton imgSrc="./grafana_icon.svg">With imgSrc</ToolbarButton>
......@@ -26,31 +29,46 @@ export const List = () => {
<ToolbarButton icon="cloud" isOpen={false}>
isOpen = false
</ToolbarButton>
</ButtonGroup>
</ToolbarButtonRow>
<br />
disabled
<ToolbarButtonRow>
<ToolbarButton icon="sync" disabled>
Disabled
</ToolbarButton>
</ToolbarButtonRow>
<br />
Variants
<ToolbarButtonRow>
{variants.map((variant) => (
<ToolbarButton icon="sync" tooltip="Sync" variant={variant} key={variant}>
{variant}
</ToolbarButton>
))}
</ToolbarButtonRow>
<br />
Wrapped in noSpacing ButtonGroup
<ButtonGroup noSpacing>
<ButtonGroup>
<ToolbarButton icon="clock-nine" tooltip="Time picker">
2020-10-02
</ToolbarButton>
<ToolbarButton icon="search-minus" />
</ButtonGroup>
<br />
Wrapped in noSpacing ButtonGroup
<ButtonGroup noSpacing>
<ButtonGroup>
<ToolbarButton icon="sync" />
<ToolbarButton isOpen={false} narrow />
</ButtonGroup>
<br />
As primary and destructive variant
<HorizontalGroup>
<ButtonGroup noSpacing>
<ButtonGroup>
<ToolbarButton variant="primary" icon="sync">
Run query
</ToolbarButton>
<ToolbarButton isOpen={false} narrow variant="primary" />
</ButtonGroup>
<ButtonGroup noSpacing>
<ButtonGroup>
<ToolbarButton variant="destructive" icon="sync">
Run query
</ToolbarButton>
......
import React, { forwardRef, HTMLAttributes } from 'react';
import React, { forwardRef, ButtonHTMLAttributes } from 'react';
import { cx, css } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { styleMixins, useStyles } from '../../themes';
import { IconName } from '../../types/icon';
import { Tooltip } from '../Tooltip/Tooltip';
import { Icon } from '../Icon/Icon';
import { ButtonVariant, getPropertiesForVariant } from './Button';
import { getPropertiesForVariant } from './Button';
import { isString } from 'lodash';
export interface Props extends HTMLAttributes<HTMLButtonElement> {
export interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
/** Icon name */
icon?: IconName;
icon?: IconName | React.ReactNode;
/** Tooltip */
tooltip?: string;
/** For image icons */
......@@ -21,21 +22,28 @@ export interface Props extends HTMLAttributes<HTMLButtonElement> {
/** reduces padding to xs */
narrow?: boolean;
/** variant */
variant?: ButtonVariant;
variant?: ToolbarButtonVariant;
/** Hide any children and only show icon */
iconOnly?: boolean;
}
export type ToolbarButtonVariant = 'default' | 'primary' | 'destructive' | 'active';
export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
({ tooltip, icon, className, children, imgSrc, fullWidth, isOpen, narrow, variant, ...rest }, ref) => {
(
{ tooltip, icon, className, children, imgSrc, fullWidth, isOpen, narrow, variant = 'default', iconOnly, ...rest },
ref
) => {
const styles = useStyles(getStyles);
const buttonStyles = cx(
'toolbar-button',
{
[styles.button]: true,
[styles.buttonFullWidth]: fullWidth,
[styles.narrow]: narrow,
[styles.primaryVariant]: variant === 'primary',
[styles.destructiveVariant]: variant === 'destructive',
},
(styles as any)[variant],
className
);
......@@ -47,9 +55,9 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
const body = (
<button ref={ref} className={buttonStyles} {...rest}>
{icon && <Icon name={icon} size={'lg'} />}
{renderIcon(icon)}
{imgSrc && <img className={styles.img} src={imgSrc} />}
{children && <span className={contentStyles}>{children}</span>}
{children && !iconOnly && <span className={contentStyles}>{children}</span>}
{isOpen === false && <Icon name="angle-down" />}
{isOpen === true && <Icon name="angle-up" />}
</button>
......@@ -65,31 +73,76 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
}
);
function renderIcon(icon: IconName | React.ReactNode) {
if (!icon) {
return null;
}
if (isString(icon)) {
return <Icon name={icon as IconName} size={'lg'} />;
}
return icon;
}
const getStyles = (theme: GrafanaTheme) => {
const primaryVariant = getPropertiesForVariant(theme, 'primary');
const destructiveVariant = getPropertiesForVariant(theme, 'destructive');
return {
button: css`
background: ${theme.colors.bg1};
border: 1px solid ${theme.colors.border2};
label: toolbar-button;
display: flex;
align-items: center;
height: ${theme.height.md}px;
padding: 0 ${theme.spacing.sm};
color: ${theme.colors.textWeak};
border-radius: ${theme.border.radius.sm};
line-height: ${theme.height.md - 2}px;
display: flex;
align-items: center;
font-weight: ${theme.typography.weight.semibold};
border: 1px solid ${theme.colors.border2};
&:focus {
outline: none;
}
&[disabled],
&:disabled {
cursor: not-allowed;
opacity: 0.5;
&:hover {
color: ${theme.colors.textWeak};
background: ${theme.colors.bg1};
}
}
`,
default: css`
color: ${theme.colors.textWeak};
background-color: ${theme.colors.bg1};
&:hover {
color: ${theme.colors.text};
background: ${styleMixins.hoverColor(theme.colors.bg1, theme)};
}
`,
active: css`
color: ${theme.palette.orangeDark};
border-color: ${theme.palette.orangeDark};
background-color: transparent;
&:hover {
color: ${theme.colors.text};
background: ${styleMixins.hoverColor(theme.colors.bg1, theme)};
}
`,
primary: css`
border-color: ${primaryVariant.borderColor};
${primaryVariant.variantStyles}
`,
destructive: css`
border-color: ${destructiveVariant.borderColor};
${destructiveVariant.variantStyles}
`,
narrow: css`
padding: 0 ${theme.spacing.xs};
`,
......@@ -103,6 +156,11 @@ const getStyles = (theme: GrafanaTheme) => {
`,
content: css`
flex-grow: 1;
display: none;
@media only screen and (min-width: ${theme.breakpoints.md}) {
display: block;
}
`,
contentWithIcon: css`
padding-left: ${theme.spacing.sm};
......@@ -110,13 +168,5 @@ const getStyles = (theme: GrafanaTheme) => {
contentWithRightIcon: css`
padding-right: ${theme.spacing.xs};
`,
primaryVariant: css`
border-color: ${primaryVariant.borderColor};
${primaryVariant.variantStyles}
`,
destructiveVariant: css`
border-color: ${destructiveVariant.borderColor};
${destructiveVariant.variantStyles}
`,
};
};
import React, { forwardRef, HTMLAttributes } from 'react';
import { css, cx } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { useStyles } from '../../themes';
export interface Props extends HTMLAttributes<HTMLDivElement> {
className?: string;
}
export const ToolbarButtonRow = forwardRef<HTMLDivElement, Props>(({ className, children, ...rest }, ref) => {
const styles = useStyles(getStyles);
return (
<div ref={ref} className={cx(styles.wrapper, className)} {...rest}>
{children}
</div>
);
});
ToolbarButtonRow.displayName = 'ToolbarButtonRow';
const getStyles = (theme: GrafanaTheme) => ({
wrapper: css`
display: flex;
.button-group,
.toolbar-button {
margin-left: ${theme.spacing.sm};
&:first-child {
margin-left: 0;
}
}
`,
});
export * from './Button';
export { ButtonGroup } from './ButtonGroup';
export { ToolbarButton } from './ToolbarButton';
export { ToolbarButton, ToolbarButtonVariant } from './ToolbarButton';
export { ToolbarButtonRow } from './ToolbarButtonRow';
import React, { useState, HTMLAttributes } from 'react';
import { PopoverContent } from '../Tooltip/Tooltip';
import { GrafanaTheme, SelectableValue } from '@grafana/data';
import { ButtonVariant, ToolbarButton } from '../Button';
import { ToolbarButtonVariant, ToolbarButton } from '../Button';
import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper';
import { css } from 'emotion';
import { useStyles } from '../../themes/ThemeContext';
......@@ -15,7 +15,7 @@ export interface Props<T> extends HTMLAttributes<HTMLButtonElement> {
onChange: (item: SelectableValue<T>) => void;
tooltipContent?: PopoverContent;
narrow?: boolean;
variant?: ButtonVariant;
variant?: ToolbarButtonVariant;
}
/**
......
......@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
import { SelectableValue } from '@grafana/data';
import { Tooltip } from '../Tooltip/Tooltip';
import { ButtonSelect } from '../Dropdown/ButtonSelect';
import { ButtonGroup, ButtonVariant, ToolbarButton } from '../Button';
import { ButtonGroup, ToolbarButton, ToolbarButtonVariant } from '../Button';
import { selectors } from '@grafana/e2e-selectors';
// Default intervals used in the refresh picker component
......@@ -39,7 +39,7 @@ export class RefreshPicker extends PureComponent<Props> {
}
};
getVariant(): ButtonVariant | undefined {
getVariant(): ToolbarButtonVariant {
if (this.props.isLive) {
return 'primary';
}
......@@ -49,7 +49,7 @@ export class RefreshPicker extends PureComponent<Props> {
if (this.props.primary) {
return 'primary';
}
return undefined;
return 'default';
}
render() {
......@@ -67,7 +67,7 @@ export class RefreshPicker extends PureComponent<Props> {
return (
<div className="refresh-picker">
<ButtonGroup className="refresh-picker-buttons" noSpacing={true}>
<ButtonGroup className="refresh-picker-buttons">
<Tooltip placement="bottom" content={tooltip!}>
<ToolbarButton
onClick={onRefresh}
......
......@@ -119,6 +119,7 @@ const getStyles = (theme: GrafanaTheme) => {
justify-content: space-between;
cursor: pointer;
padding-right: 0;
line-height: ${theme.spacing.formInputHeight - 2}px;
${getFocusStyle(theme)};
`
),
......
......@@ -7,6 +7,8 @@ import { UseState } from '../../utils/storybook/UseState';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { dateTime, TimeRange, DefaultTimeZone, TimeZone, isDateTime } from '@grafana/data';
import { TimeRangePickerProps } from './TimeRangePicker';
import { DashboardStoryCanvas } from '../../utils/storybook/DashboardStoryCanvas';
import { HorizontalGroup, VerticalGroup } from '../Layout/Layout';
export default {
title: 'Pickers and Editors/TimePickers/TimeRangePicker',
......@@ -24,49 +26,58 @@ const getComponentWithState = (initialState: State, props: TimeRangePickerProps)
<UseState initialState={initialState}>
{(state, updateValue) => {
return (
<>
<TimeRangePicker
{...props}
timeZone={state.timeZone}
value={state.value}
history={state.history}
onChange={(value) => {
action('onChange fired')(value);
updateValue({
...state,
value,
history:
isDateTime(value.raw.from) && isDateTime(value.raw.to) ? [...state.history, value] : state.history,
});
}}
onChangeTimeZone={(timeZone) => {
action('onChangeTimeZone fired')(timeZone);
updateValue({
...state,
timeZone,
});
}}
onMoveBackward={() => {
action('onMoveBackward fired')();
}}
onMoveForward={() => {
action('onMoveForward fired')();
}}
onZoom={() => {
action('onZoom fired')();
}}
/>
<Button
onClick={() => {
updateValue({
...state,
history: [],
});
}}
>
Clear history
</Button>
</>
<DashboardStoryCanvas>
<VerticalGroup>
<HorizontalGroup justify="flex-end">
<TimeRangePicker
{...props}
timeZone={state.timeZone}
value={state.value}
history={state.history}
onChange={(value) => {
action('onChange fired')(value);
updateValue({
...state,
value,
history:
isDateTime(value.raw.from) && isDateTime(value.raw.to)
? [...state.history, value]
: state.history,
});
}}
onChangeTimeZone={(timeZone) => {
action('onChangeTimeZone fired')(timeZone);
updateValue({
...state,
timeZone,
});
}}
onMoveBackward={() => {
action('onMoveBackward fired')();
}}
onMoveForward={() => {
action('onMoveForward fired')();
}}
onZoom={() => {
action('onZoom fired')();
}}
/>
</HorizontalGroup>
<br />
<br />
<br />
<Button
onClick={() => {
updateValue({
...state,
history: [],
});
}}
>
Clear history
</Button>
</VerticalGroup>
</DashboardStoryCanvas>
);
}}
</UseState>
......
// Libraries
import React, { PureComponent, memo, FormEvent } from 'react';
import { css, cx } from 'emotion';
import { css } from 'emotion';
// Components
import { Tooltip } from '../Tooltip/Tooltip';
import { Icon } from '../Icon/Icon';
import { TimePickerContent } from './TimeRangePicker/TimePickerContent';
import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper';
......@@ -17,44 +16,7 @@ import { isDateTime, rangeUtil, GrafanaTheme, dateTimeFormat, timeZoneFormatUser
import { TimeRange, TimeZone, dateMath } from '@grafana/data';
import { Themeable } from '../../types';
import { otherOptions, quickOptions } from './rangeOptions';
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
container: css`
position: relative;
display: flex;
flex-flow: column nowrap;
`,
buttons: css`
display: flex;
`,
caretIcon: css`
margin-left: ${theme.spacing.xs};
`,
clockIcon: css`
margin-left: ${theme.spacing.xs};
margin-right: ${theme.spacing.xs};
`,
noRightBorderStyle: css`
label: noRightBorderStyle;
border-right: 0;
`,
};
});
const getLabelStyles = stylesFactory((theme: GrafanaTheme) => {
return {
container: css`
display: inline-block;
`,
utc: css`
color: ${theme.palette.orange};
font-size: 75%;
padding: 3px;
font-weight: ${theme.typography.weight.semibold};
`,
};
});
import { ButtonGroup, ToolbarButton } from '../Button';
export interface TimeRangePickerProps extends Themeable {
hideText?: boolean;
......@@ -114,61 +76,47 @@ export class UnthemedTimeRangePicker extends PureComponent<TimeRangePickerProps,
const { isOpen } = this.state;
const styles = getStyles(theme);
const hasAbsolute = isDateTime(value.raw.from) || isDateTime(value.raw.to);
const syncedTimePicker = timeSyncButton && isSynced;
const timePickerIconClass = cx({ ['icon-brand-gradient']: syncedTimePicker });
const timePickerButtonClass = cx('btn navbar-button navbar-button--tight', {
[`btn--radius-right-0 ${styles.noRightBorderStyle}`]: !!timeSyncButton,
[`explore-active-button`]: syncedTimePicker,
});
const variant = isSynced ? 'active' : 'default';
return (
<div className={styles.container}>
<div className={styles.buttons}>
{hasAbsolute && (
<button className="btn navbar-button navbar-button--tight" onClick={onMoveBackward}>
<Icon name="angle-left" size="lg" />
</button>
)}
<div>
<Tooltip content={<TimePickerTooltip timeRange={value} timeZone={timeZone} />} placement="bottom">
<button aria-label="TimePicker Open Button" className={timePickerButtonClass} onClick={this.onOpen}>
<Icon name="clock-nine" className={cx(styles.clockIcon, timePickerIconClass)} size="lg" />
<TimePickerButtonLabel {...this.props} />
<span className={styles.caretIcon}>{<Icon name={isOpen ? 'angle-up' : 'angle-down'} size="lg" />}</span>
</button>
</Tooltip>
{isOpen && (
<ClickOutsideWrapper includeButtonPress={false} onClick={this.onClose}>
<TimePickerContent
timeZone={timeZone}
value={value}
onChange={this.onChange}
otherOptions={otherOptions}
quickOptions={quickOptions}
history={history}
showHistory
onChangeTimeZone={onChangeTimeZone}
hideQuickRanges={hideQuickRanges}
/>
</ClickOutsideWrapper>
)}
</div>
{timeSyncButton}
{hasAbsolute && (
<button className="btn navbar-button navbar-button--tight" onClick={onMoveForward}>
<Icon name="angle-right" size="lg" />
</button>
)}
<Tooltip content={ZoomOutTooltip} placement="bottom">
<button className="btn navbar-button navbar-button--zoom" onClick={onZoom}>
<Icon name="search-minus" size="lg" />
</button>
</Tooltip>
</div>
</div>
<ButtonGroup className={styles.container}>
{hasAbsolute && <ToolbarButton variant={variant} onClick={onMoveBackward} icon="angle-left" narrow />}
<Tooltip content={<TimePickerTooltip timeRange={value} timeZone={timeZone} />} placement="bottom">
<ToolbarButton
aria-label="TimePicker Open Button"
onClick={this.onOpen}
icon="clock-nine"
isOpen={isOpen}
variant={variant}
>
<TimePickerButtonLabel {...this.props} />
</ToolbarButton>
</Tooltip>
{isOpen && (
<ClickOutsideWrapper includeButtonPress={false} onClick={this.onClose}>
<TimePickerContent
timeZone={timeZone}
value={value}
onChange={this.onChange}
otherOptions={otherOptions}
quickOptions={quickOptions}
history={history}
showHistory
onChangeTimeZone={onChangeTimeZone}
hideQuickRanges={hideQuickRanges}
/>
</ClickOutsideWrapper>
)}
{timeSyncButton}
{hasAbsolute && <ToolbarButton onClick={onMoveForward} icon="angle-right" narrow variant={variant} />}
<Tooltip content={ZoomOutTooltip} placement="bottom">
<ToolbarButton onClick={onZoom} icon="search-minus" variant={variant} />
</Tooltip>
</ButtonGroup>
);
}
}
......@@ -224,3 +172,29 @@ const formattedRange = (value: TimeRange, timeZone?: TimeZone) => {
};
export const TimeRangePicker = withTheme(UnthemedTimeRangePicker);
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
container: css`
position: relative;
display: flex;
vertical-align: middle;
`,
};
});
const getLabelStyles = stylesFactory((theme: GrafanaTheme) => {
return {
container: css`
display: inline-block;
`,
utc: css`
color: ${theme.palette.orange};
font-size: ${theme.typography.size.sm};
padding-left: 6px;
line-height: 28px;
vertical-align: bottom;
font-weight: ${theme.typography.weight.semibold};
`,
};
});
......@@ -91,6 +91,7 @@ const getFullScreenStyles = stylesFactory((theme: GrafanaTheme, hideQuickRanges?
display: flex;
flex-direction: column;
justify-content: flex-end;
padding-top: ${theme.spacing.sm};
`,
};
});
......
......@@ -132,7 +132,7 @@ export { FieldConfigItemHeaderTitle } from './FieldConfigs/FieldConfigItemHeader
// Next-gen forms
export { Form } from './Forms/Form';
export { InputControl } from './InputControl';
export { Button, LinkButton, ButtonVariant, ToolbarButton, ButtonGroup } from './Button';
export { Button, LinkButton, ButtonVariant, ToolbarButton, ButtonGroup, ToolbarButtonRow } from './Button';
export { ValuePicker } from './ValuePicker/ValuePicker';
export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI';
export { getFormStyles } from './Forms/getFormStyles';
......
......@@ -16,15 +16,6 @@ import { TimePickerWithHistory } from 'app/core/components/TimePicker/TimePicker
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { appEvents } from 'app/core/core';
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
container: css`
position: relative;
display: flex;
`,
};
});
export interface Props extends Themeable {
dashboard: DashboardModel;
location: LocationState;
......@@ -126,3 +117,12 @@ class UnthemedDashNavTimeControls extends Component<Props> {
}
export const DashNavTimeControls = withTheme(UnthemedDashNavTimeControls);
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
container: css`
position: relative;
display: flex;
`,
};
});
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';
import memoizeOne from 'memoize-one';
import classNames from 'classnames';
import { css } from 'emotion';
import { ExploreId, ExploreItemState } from 'app/types/explore';
import { Icon, IconButton, SetInterval, Tooltip } from '@grafana/ui';
import { Icon, IconButton, SetInterval, ToolbarButton, ToolbarButtonRow, Tooltip } from '@grafana/ui';
import { DataSourceInstanceSettings, RawTimeRange, TimeRange, TimeZone } from '@grafana/data';
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
import { StoreState } from 'app/types/store';
......@@ -18,23 +17,11 @@ import { getTimeZone } from '../profile/state/selectors';
import { updateTimeZoneForSession } from '../profile/state/reducers';
import { ExploreTimeControls } from './ExploreTimeControls';
import { LiveTailButton } from './LiveTailButton';
import { ResponsiveButton } from './ResponsiveButton';
import { RunButton } from './RunButton';
import { LiveTailControls } from './useLiveTailControls';
import { cancelQueries, clearQueries, runQueries } from './state/query';
import ReturnToDashboardButton from './ReturnToDashboardButton';
const getStyles = memoizeOne(() => {
return {
liveTailButtons: css`
margin-left: 10px;
@media (max-width: 1110px) {
margin-left: 4px;
}
`,
};
});
interface OwnProps {
exploreId: ExploreId;
onChangeTime: (range: RawTimeRange, changedByScanner?: boolean) => void;
......@@ -117,7 +104,6 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
onChangeTimeZone,
} = this.props;
const styles = getStyles();
const showSmallDataSourcePicker = (splitted ? containerWidth < 700 : containerWidth < 800) || false;
const showSmallTimePicker = splitted || containerWidth < 1210;
......@@ -163,31 +149,29 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
</div>
</div>
) : null}
<ReturnToDashboardButton exploreId={exploreId} />
{exploreId === 'left' && !splitted ? (
<div className="explore-toolbar-content-item explore-icon-align">
<ResponsiveButton
splitted={splitted}
<ToolbarButtonRow>
<ReturnToDashboardButton exploreId={exploreId} />
{exploreId === 'left' && !splitted ? (
<ToolbarButton
iconOnly={splitted}
title="Split"
/* This way ResponsiveButton doesn't add event as a parameter when invoking split function
/* This way ToolbarButton doesn't add event as a parameter when invoking split function
* which breaks splitting functionality
*/
onClick={() => split()}
icon="columns"
iconClassName="icon-margin-right"
disabled={isLive}
/>
</div>
) : null}
<div className={'explore-toolbar-content-item'}>
>
Split
</ToolbarButton>
) : null}
<Tooltip content={'Copy shortened link'} placement="bottom">
<button className={'btn navbar-button'} onClick={() => createAndCopyShortLink(window.location.href)}>
<Icon name="share-alt" />
</button>
<ToolbarButton icon="share-alt" onClick={() => createAndCopyShortLink(window.location.href)} />
</Tooltip>
</div>
{!isLive && (
<div className="explore-toolbar-content-item">
{!isLive && (
<ExploreTimeControls
exploreId={exploreId}
range={range}
......@@ -199,21 +183,14 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
hideText={showSmallTimePicker}
onChangeTimeZone={onChangeTimeZone}
/>
</div>
)}
)}
{!isLive && (
<ToolbarButton title="Clear all" onClick={this.onClearAll} icon="trash-alt" iconOnly={splitted}>
Clear all
</ToolbarButton>
)}
{!isLive && (
<div className="explore-toolbar-content-item explore-icon-align">
<ResponsiveButton
splitted={splitted}
title="Clear All"
onClick={this.onClearAll}
icon="trash-alt"
iconClassName="icon-margin-right"
/>
</div>
)}
<div className="explore-toolbar-content-item">
<RunButton
refreshInterval={refreshInterval}
onChangeRefreshInterval={this.onChangeRefreshInterval}
......@@ -223,11 +200,10 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
onRun={this.onRunQuery}
showDropdown={!isLive}
/>
{refreshInterval && <SetInterval func={this.onRunQuery} interval={refreshInterval} loading={loading} />}
</div>
{hasLiveOption && (
<div className={`explore-toolbar-content-item ${styles.liveTailButtons}`}>
{hasLiveOption && (
<LiveTailControls exploreId={exploreId}>
{(controls) => (
<LiveTailButton
......@@ -241,8 +217,8 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
/>
)}
</LiveTailControls>
</div>
)}
)}
</ToolbarButtonRow>
</div>
</div>
</div>
......
import React from 'react';
import classNames from 'classnames';
import tinycolor from 'tinycolor2';
import { css } from 'emotion';
import { CSSTransition } from 'react-transition-group';
import { useTheme, Tooltip, stylesFactory, selectThemeVariant, Icon } from '@grafana/ui';
import { useTheme, Tooltip, stylesFactory, selectThemeVariant, ButtonGroup, ToolbarButton } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data';
//Components
import { ResponsiveButton } from './ResponsiveButton';
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const bgColor = selectThemeVariant({ light: theme.palette.gray5, dark: theme.palette.dark1 }, theme.type);
const orangeLighter = tinycolor(theme.palette.orangeDark).lighten(10).toString();
const pulseTextColor = tinycolor(theme.palette.orangeDark).desaturate(90).toString();
return {
noRightBorderStyle: css`
label: noRightBorderStyle;
border-right: 0;
`,
liveButton: css`
label: liveButton;
margin: 0;
`,
isLive: css`
label: isLive;
border-color: ${theme.palette.orangeDark};
......@@ -107,25 +96,25 @@ export function LiveTailButton(props: LiveTailButtonProps) {
const { start, pause, resume, isLive, isPaused, stop, splitted } = props;
const theme = useTheme();
const styles = getStyles(theme);
const buttonVariant = isLive && !isPaused ? 'active' : 'default';
const onClickMain = isLive ? (isPaused ? resume : pause) : start;
return (
<>
<Tooltip content={isLive ? <>Pause the live stream</> : <>Live stream your logs</>} placement="bottom">
<ResponsiveButton
splitted={splitted}
buttonClassName={classNames('btn navbar-button', styles.liveButton, {
[`btn--radius-right-0 explore-active-button ${styles.noRightBorderStyle}`]: isLive,
[styles.isLive]: isLive && !isPaused,
[styles.isPaused]: isLive && isPaused,
})}
icon={!isLive ? 'play' : 'pause'}
iconClassName={isLive ? 'icon-brand-gradient' : undefined}
<ButtonGroup>
<Tooltip
content={isLive && !isPaused ? <>Pause the live stream</> : <>Start live stream your logs</>}
placement="bottom"
>
<ToolbarButton
iconOnly={splitted}
variant={buttonVariant}
icon={!isLive || isPaused ? 'play' : 'pause'}
onClick={onClickMain}
title={'\xa0Live'}
/>
>
{isLive && isPaused ? 'Paused' : 'Live'}
</ToolbarButton>
</Tooltip>
<CSSTransition
mountOnEnter={true}
unmountOnExit={true}
......@@ -138,17 +127,10 @@ export function LiveTailButton(props: LiveTailButtonProps) {
exitActive: styles.stopButtonExitActive,
}}
>
<div>
<Tooltip content={<>Stop and exit the live stream</>} placement="bottom">
<button
className={`btn navbar-button navbar-button--attached explore-active-button ${styles.isLive}`}
onClick={stop}
>
<Icon className="icon-brand-gradient" name="square-shape" size="lg" type="mono" />
</button>
</Tooltip>
</div>
<Tooltip content={<>Stop and exit the live stream</>} placement="bottom">
<ToolbarButton variant={buttonVariant} onClick={stop} icon="square-shape" />
</Tooltip>
</CSSTransition>
</>
</ButtonGroup>
);
}
import React, { forwardRef } from 'react';
import { IconName, Icon } from '@grafana/ui';
export enum IconSide {
left = 'left',
right = 'right',
}
interface Props extends React.HTMLAttributes<HTMLDivElement> {
splitted: boolean;
title: string;
onClick?: () => void;
buttonClassName?: string;
icon?: IconName;
iconClassName?: string;
iconSide?: IconSide;
disabled?: boolean;
}
function formatBtnTitle(title: string, iconSide?: string): string {
return iconSide === IconSide.left ? '\xA0' + title : iconSide === IconSide.right ? title + '\xA0' : title;
}
export const ResponsiveButton = forwardRef<HTMLButtonElement, Props>((props, ref) => {
const defaultProps = {
iconSide: IconSide.left,
};
props = { ...defaultProps, ...props };
const {
title,
onClick,
buttonClassName,
icon,
iconClassName,
splitted,
iconSide,
disabled,
...divElementProps
} = props;
return (
<div {...divElementProps}>
<button
ref={ref}
className={`btn navbar-button ${buttonClassName ? buttonClassName : ''}`}
onClick={onClick ?? undefined}
disabled={disabled || false}
>
{icon && iconSide === IconSide.left ? <Icon name={icon} className={iconClassName} size="lg" /> : null}
<span className="btn-title">{!splitted ? formatBtnTitle(title, iconSide) : ''}</span>
{icon && iconSide === IconSide.right ? <Icon name={icon} className={iconClassName} size="lg" /> : null}
</button>
</div>
);
});
ResponsiveButton.displayName = 'ResponsiveButton';
......@@ -66,7 +66,7 @@ export const UnconnectedReturnToDashboardButton: FC<Props> = ({
};
return (
<ButtonGroup className="explore-toolbar-content-item" noSpacing>
<ButtonGroup>
<Tooltip content={'Return to panel'} placement="bottom">
<ToolbarButton data-testid="returnButton" title={'Return to panel'} onClick={() => returnToPanel()}>
<Icon name="arrow-left" />
......
import React from 'react';
import classNames from 'classnames';
import { Tooltip, Icon } from '@grafana/ui';
import { Tooltip, ToolbarButton } from '@grafana/ui';
interface TimeSyncButtonProps {
isSynced: boolean;
......@@ -18,15 +17,12 @@ export function TimeSyncButton(props: TimeSyncButtonProps) {
return (
<Tooltip content={syncTimesTooltip} placement="bottom">
<button
className={classNames('btn navbar-button navbar-button--attached', {
[`explore-active-button`]: isSynced,
})}
<ToolbarButton
icon="link"
variant={isSynced ? 'active' : 'default'}
aria-label={isSynced ? 'Synced times' : 'Unsynced times'}
onClick={() => onClick()}
>
<Icon name="link" className={isSynced ? 'icon-brand-gradient' : ''} size="lg" />
</button>
onClick={onClick}
/>
</Tooltip>
);
}
......@@ -114,33 +114,7 @@
}
}
@media only screen and (max-width: 1545px) {
.explore-toolbar.splitted {
.timepicker-rangestring {
display: none;
}
}
}
@media only screen and (max-width: 1400px) {
.explore-toolbar.splitted {
.explore-toolbar-content-item {
.navbar-button {
span {
display: none;
}
}
}
}
}
@media only screen and (max-width: 1070px) {
.timepicker {
.timepicker-rangestring {
display: none;
}
}
.explore-toolbar-content {
justify-content: flex-start;
}
......@@ -152,16 +126,6 @@
}
}
@media only screen and (max-width: 897px) {
.explore-toolbar {
.explore-toolbar-content-item {
.navbar-button span {
display: none;
}
}
}
}
@media only screen and (max-width: 810px) {
.explore-toolbar {
.explore-toolbar-content-item {
......@@ -173,29 +137,6 @@
}
}
.explore-icon-align {
.navbar-button {
i {
position: relative;
top: -1px;
@media only screen and (max-width: 1320px) {
margin: 0 -3px;
}
}
}
}
.explore-toolbar.splitted {
.explore-icon-align {
.navbar-button {
i {
margin: 0 -3px;
}
}
}
}
.explore {
display: flex;
flex: 1 1 auto;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment