mirror of
https://github.com/grafana/grafana.git
synced 2025-09-17 19:32:51 +08:00
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
This commit is contained in:
@ -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};
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
`,
|
||||
wrapperNoSpacing: css`
|
||||
display: flex;
|
||||
|
||||
> a,
|
||||
> button {
|
||||
border-radius: 0;
|
||||
border-right: 0;
|
||||
border-right-width: 0;
|
||||
|
||||
&.toolbar-button {
|
||||
margin-left: 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 {
|
||||
|
@ -1,6 +1,8 @@
|
||||
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>
|
||||
|
@ -1,15 +1,16 @@
|
||||
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};
|
||||
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;
|
||||
label: toolbar-button;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: ${theme.height.md}px;
|
||||
padding: 0 ${theme.spacing.sm};
|
||||
border-radius: ${theme.border.radius.sm};
|
||||
line-height: ${theme.height.md - 2}px;
|
||||
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}
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
@ -0,0 +1,35 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
@ -1,3 +1,4 @@
|
||||
export * from './Button';
|
||||
export { ButtonGroup } from './ButtonGroup';
|
||||
export { ToolbarButton } from './ToolbarButton';
|
||||
export { ToolbarButton, ToolbarButtonVariant } from './ToolbarButton';
|
||||
export { ToolbarButtonRow } from './ToolbarButtonRow';
|
||||
|
@ -1,7 +1,7 @@
|
||||
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,7 +26,9 @@ const getComponentWithState = (initialState: State, props: TimeRangePickerProps)
|
||||
<UseState initialState={initialState}>
|
||||
{(state, updateValue) => {
|
||||
return (
|
||||
<>
|
||||
<DashboardStoryCanvas>
|
||||
<VerticalGroup>
|
||||
<HorizontalGroup justify="flex-end">
|
||||
<TimeRangePicker
|
||||
{...props}
|
||||
timeZone={state.timeZone}
|
||||
@ -36,7 +40,9 @@ const getComponentWithState = (initialState: State, props: TimeRangePickerProps)
|
||||
...state,
|
||||
value,
|
||||
history:
|
||||
isDateTime(value.raw.from) && isDateTime(value.raw.to) ? [...state.history, value] : state.history,
|
||||
isDateTime(value.raw.from) && isDateTime(value.raw.to)
|
||||
? [...state.history, value]
|
||||
: state.history,
|
||||
});
|
||||
}}
|
||||
onChangeTimeZone={(timeZone) => {
|
||||
@ -56,6 +62,10 @@ const getComponentWithState = (initialState: State, props: TimeRangePickerProps)
|
||||
action('onZoom fired')();
|
||||
}}
|
||||
/>
|
||||
</HorizontalGroup>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<Button
|
||||
onClick={() => {
|
||||
updateValue({
|
||||
@ -66,7 +76,8 @@ const getComponentWithState = (initialState: State, props: TimeRangePickerProps)
|
||||
>
|
||||
Clear history
|
||||
</Button>
|
||||
</>
|
||||
</VerticalGroup>
|
||||
</DashboardStoryCanvas>
|
||||
);
|
||||
}}
|
||||
</UseState>
|
||||
|
@ -1,10 +1,9 @@
|
||||
// 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,28 +76,22 @@ 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>
|
||||
<ButtonGroup className={styles.container}>
|
||||
{hasAbsolute && <ToolbarButton variant={variant} onClick={onMoveBackward} icon="angle-left" narrow />}
|
||||
|
||||
<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" />
|
||||
<ToolbarButton
|
||||
aria-label="TimePicker Open Button"
|
||||
onClick={this.onOpen}
|
||||
icon="clock-nine"
|
||||
isOpen={isOpen}
|
||||
variant={variant}
|
||||
>
|
||||
<TimePickerButtonLabel {...this.props} />
|
||||
<span className={styles.caretIcon}>{<Icon name={isOpen ? 'angle-up' : 'angle-down'} size="lg" />}</span>
|
||||
</button>
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
{isOpen && (
|
||||
<ClickOutsideWrapper includeButtonPress={false} onClick={this.onClose}>
|
||||
@ -152,23 +108,15 @@ export class UnthemedTimeRangePicker extends PureComponent<TimeRangePickerProps,
|
||||
/>
|
||||
</ClickOutsideWrapper>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{timeSyncButton}
|
||||
|
||||
{hasAbsolute && (
|
||||
<button className="btn navbar-button navbar-button--tight" onClick={onMoveForward}>
|
||||
<Icon name="angle-right" size="lg" />
|
||||
</button>
|
||||
)}
|
||||
{hasAbsolute && <ToolbarButton onClick={onMoveForward} icon="angle-right" narrow variant={variant} />}
|
||||
|
||||
<Tooltip content={ZoomOutTooltip} placement="bottom">
|
||||
<button className="btn navbar-button navbar-button--zoom" onClick={onZoom}>
|
||||
<Icon name="search-minus" size="lg" />
|
||||
</button>
|
||||
<ToolbarButton onClick={onZoom} icon="search-minus" variant={variant} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</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;
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
@ -1,12 +1,11 @@
|
||||
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}
|
||||
<ToolbarButtonRow>
|
||||
<ReturnToDashboardButton exploreId={exploreId} />
|
||||
|
||||
{exploreId === 'left' && !splitted ? (
|
||||
<div className="explore-toolbar-content-item explore-icon-align">
|
||||
<ResponsiveButton
|
||||
splitted={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>
|
||||
>
|
||||
Split
|
||||
</ToolbarButton>
|
||||
) : null}
|
||||
<div className={'explore-toolbar-content-item'}>
|
||||
|
||||
<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">
|
||||
<ExploreTimeControls
|
||||
exploreId={exploreId}
|
||||
range={range}
|
||||
@ -199,21 +183,14 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
|
||||
hideText={showSmallTimePicker}
|
||||
onChangeTimeZone={onChangeTimeZone}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!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>
|
||||
<ToolbarButton title="Clear all" onClick={this.onClearAll} icon="trash-alt" iconOnly={splitted}>
|
||||
Clear all
|
||||
</ToolbarButton>
|
||||
)}
|
||||
<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}`}>
|
||||
<LiveTailControls exploreId={exploreId}>
|
||||
{(controls) => (
|
||||
<LiveTailButton
|
||||
@ -241,8 +217,8 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
|
||||
/>
|
||||
)}
|
||||
</LiveTailControls>
|
||||
</div>
|
||||
)}
|
||||
</ToolbarButtonRow>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,27 +1,16 @@
|
||||
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>
|
||||
<ToolbarButton variant={buttonVariant} onClick={stop} icon="square-shape" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
</>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
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" />
|
||||
|
@ -1,6 +1,5 @@
|
||||
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;
|
||||
|
Reference in New Issue
Block a user