Various Polish Stories (#2377)

* Add helpful tooltip to download CSV button

* Expose settings and tokens in navbar

* Remove gap around auto refresh dropdown

* Refactor Empty state styles

* Standardize tasks and dashboards empty states

* Standardize resource empty states in home page

* Refactor dashboard empty state

* Amend

* Standardize name and appearance of new dashboards and cells

* Ensure pause button doesn't press up against dropdown
pull/10616/head
alexpaxton 2019-01-09 13:55:49 -08:00 committed by GitHub
parent 28ebc88b44
commit c5099e7be8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 219 additions and 94 deletions

View File

@ -3,6 +3,8 @@
------------------------------------------------------------------------------
*/
@import 'src/style/modules';
.empty-state {
display: flex;
width: 100%;
@ -12,42 +14,59 @@
.empty-state--text,
.empty-state--sub-text {
margin-bottom: 0;
text-align: center;
color: $empty-state-text;
@extend %no-user-select;
em {
font-weight: 600;
font-style: normal;
color: $empty-state-highlight;
}
}
/*
Size Modifiers
------------------------------------------------------------------------------
*/
@mixin emptyStateSizeModifier($fontSize, $padding) {
padding: ceil($padding * 2.75) 0;
.empty-state--text,
.empty-state--sub-text {
margin-top: ceil($fontSize * 0.75);
}
.button {
margin-top: $padding;
}
> *:last-child {
margin-bottom: ceil($fontSize * 0.75);
}
.empty-state--text {
font-size: ceil($fontSize * 1.25);
}
.empty-state--sub-text {
font-size: $fontSize;
}
}
.empty-state--xs {
padding: $ix-marg-a 0;
.empty-state--text,
.empty-state--sub-text {
margin: $ix-marg-a 0;
}
@include emptyStateSizeModifier($form-xs-font, $ix-marg-a);
}
.empty-state--sm {
padding: $ix-marg-c 0;
.empty-state--text,
.empty-state--sub-text {
margin: $ix-marg-b 0;
}
@include emptyStateSizeModifier($form-sm-font, $ix-marg-b);
}
.empty-state--md {
padding: $ix-marg-d 0;
.empty-state--text,
.empty-state--sub-text {
margin: $ix-marg-b 0;
}
@include emptyStateSizeModifier($form-md-font, $ix-marg-c);
}
.empty-state--lg {
padding: $ix-marg-f 0;
.empty-state--text,
.empty-state--sub-text {
margin: $ix-marg-c 0;
}
@include emptyStateSizeModifier($form-lg-font, $ix-marg-d);
}

View File

@ -8,6 +8,10 @@ import EmptyStateSubText from 'src/clockface/components/empty_state/EmptyStateSu
// Types
import {ComponentSize} from 'src/clockface/types'
// Styles
import 'src/clockface/components/empty_state/EmptyState.scss'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {

View File

@ -1,12 +1,38 @@
// Libraries
import React, {SFC} from 'react'
import React, {SFC, Fragment} from 'react'
import _ from 'lodash'
import uuid from 'uuid'
interface Props {
text: string
highlightWords?: string | string[]
}
const EmptyStateText: SFC<Props> = ({text}) => (
<h4 className="empty-state--text">{text}</h4>
const highlighter = (
text: string,
highlightWords: string | string[]
): JSX.Element[] => {
const splitString = text.replace(/[\\][n]/g, 'LINEBREAK').split(' ')
return splitString.map(word => {
if (_.includes(highlightWords, word)) {
return <em key={uuid.v4()}>{`${word}`}</em>
}
if (word === 'LINEBREAK') {
return <br />
}
if (word === 'SPACECHAR') {
return <Fragment key={uuid.v4()}>&nbsp;</Fragment>
}
return <Fragment key={uuid.v4()}>{`${word} `}</Fragment>
})
}
const EmptyStateText: SFC<Props> = ({text, highlightWords}) => (
<h4 className="empty-state--text">{highlighter(text, highlightWords)}</h4>
)
export default EmptyStateText

View File

@ -23,6 +23,7 @@ interface Props {
onPositionChange: (cells: Cell[]) => void
setScrollTop: (e: MouseEvent<HTMLElement>) => void
onEditView: (viewID: string) => void
onAddCell: () => void
}
@ErrorHandling
@ -40,6 +41,7 @@ class DashboardComponent extends PureComponent<Props> {
onPositionChange,
inPresentationMode,
setScrollTop,
onAddCell,
} = this.props
return (
@ -63,7 +65,7 @@ class DashboardComponent extends PureComponent<Props> {
onEditView={onEditView}
/>
) : (
<DashboardEmpty dashboard={dashboard} />
<DashboardEmpty onAddCell={onAddCell} />
)}
{/* This element is used as a portal container for note tooltips in cell headers */}
<div className="cell-header-note-tooltip-container" />

View File

@ -10,6 +10,12 @@ import GraphTips from 'src/shared/components/graph_tips/GraphTips'
import RenamablePageTitle from 'src/pageLayout/components/RenamablePageTitle'
import {Button, ButtonShape, ComponentColor, IconFont} from 'src/clockface'
// Constants
import {
DEFAULT_DASHBOARD_NAME,
DASHBOARD_NAME_MAX_LENGTH,
} from 'src/dashboards/constants/index'
// Actions
import {addNote} from 'src/dashboards/actions/v2/notes'
@ -122,8 +128,10 @@ class DashboardHeader extends Component<Props> {
if (dashboard) {
return (
<RenamablePageTitle
maxLength={DASHBOARD_NAME_MAX_LENGTH}
onRename={onRenameDashboard}
name={activeDashboard}
placeholder={DEFAULT_DASHBOARD_NAME}
/>
)
}

View File

@ -213,6 +213,7 @@ class DashboardPage extends Component<Props, State> {
onPositionChange={this.handlePositionChange}
onDeleteCell={this.handleDeleteDashboardCell}
onEditView={this.handleEditView}
onAddCell={this.handleAddCell}
/>
)}
<OverlayTechnology visible={isShowingVEO}>

View File

@ -12,6 +12,12 @@ import {
} from 'src/clockface'
import {Page} from 'src/pageLayout'
// Constants
import {
DEFAULT_CELL_NAME,
CELL_NAME_MAX_LENGTH,
} from 'src/dashboards/constants/index'
interface Props {
name: string
onSetName: (name: string) => void
@ -27,7 +33,12 @@ class VEOHeader extends PureComponent<Props> {
<div className="veo-header">
<Page.Header fullWidth={true}>
<Page.Header.Left>
<RenamablePageTitle name={name} onRename={onSetName} />
<RenamablePageTitle
name={name}
onRename={onSetName}
placeholder={DEFAULT_CELL_NAME}
maxLength={CELL_NAME_MAX_LENGTH}
/>
</Page.Header.Left>
<Page.Header.Right>
<Button

View File

@ -1,51 +1,45 @@
// Libraries
import React, {Component} from 'react'
import {connect} from 'react-redux'
// Actions
import {addCellAsync} from 'src/dashboards/actions/v2'
// Types
import {Dashboard} from 'src/api'
// Components
import {
Button,
IconFont,
ComponentColor,
EmptyState,
ComponentSize,
} from 'src/clockface'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
dashboard: Dashboard
}
interface Actions {
addDashboardCell: typeof addCellAsync
onAddCell: () => void
}
@ErrorHandling
class DashboardEmpty extends Component<Props & Actions> {
constructor(props) {
super(props)
}
public handleAddCell = __ => async () => {
const {dashboard, addDashboardCell} = this.props
await addDashboardCell(dashboard)
}
class DashboardEmpty extends Component<Props> {
public render() {
const {onAddCell} = this.props
return (
<div className="dashboard-empty">
<p>
This Dashboard doesn't have any <strong>Cells</strong>, why not add
one?
</p>
<EmptyState size={ComponentSize.Large}>
<EmptyState.Text
text="This Dashboard doesn't have any Cells , why not add
one?"
highlightWords={['Cells']}
/>
<Button
text="Add Cell"
size={ComponentSize.Medium}
icon={IconFont.AddCell}
color={ComponentColor.Primary}
onClick={onAddCell}
/>
</EmptyState>
</div>
)
}
}
const mdtp = {
addDashboardCell: addCellAsync,
}
export default connect(
null,
mdtp
)(DashboardEmpty)
export default DashboardEmpty

View File

@ -40,6 +40,9 @@ import {
dashboardCreateFailed,
} from 'src/shared/copy/notifications'
// Constants
import {DEFAULT_DASHBOARD_NAME} from 'src/dashboards/constants/index'
// Types
import {Notification} from 'src/types/notifications'
import {DashboardFile} from 'src/types/v2/dashboards'
@ -152,7 +155,7 @@ class DashboardIndex extends PureComponent<Props, State> {
const {router, notify} = this.props
try {
const newDashboard = {
name: 'Name this dashboard',
name: DEFAULT_DASHBOARD_NAME,
cells: [],
}
const data = await createDashboard(newDashboard)
@ -215,7 +218,7 @@ class DashboardIndex extends PureComponent<Props, State> {
h: 4,
}
const name = _.get(dashboard, 'name', 'Name this dashboard')
const name = _.get(dashboard, 'name', DEFAULT_DASHBOARD_NAME)
const cellsWithDefaultsApplied = getDeep<Cell[]>(
dashboard,
'cells',

View File

@ -141,19 +141,23 @@ class DashboardsTable extends PureComponent<Props & WithRouterProps, State> {
if (searchTerm) {
return (
<EmptyState size={ComponentSize.Large}>
<EmptyState.Text text="No dashboards match your search term" />
<EmptyState.Text text="No Dashboards match your search term" />
</EmptyState>
)
}
return (
<EmptyState size={ComponentSize.Large}>
<EmptyState.Text text="Looks like you dont have any dashboards" />
<EmptyState.Text
text="Looks like you dont have any Dashboards , why not create one?"
highlightWords={['Dashboards']}
/>
<Button
text="Create a Dashboard"
icon={IconFont.Plus}
color={ComponentColor.Primary}
onClick={onCreateDashboard}
size={ComponentSize.Medium}
/>
</EmptyState>
)

View File

@ -86,6 +86,7 @@ type NewDefaultDashboard = Pick<
cells: NewDefaultCell[]
}
>
export const DEFAULT_CELL_NAME = 'Name this Cell'
export const DEFAULT_DASHBOARD_NAME = 'Name this Dashboard'
export const NEW_DASHBOARD: NewDefaultDashboard = {
name: DEFAULT_DASHBOARD_NAME,
@ -96,6 +97,7 @@ export const TYPE_QUERY_CONFIG: string = 'queryConfig'
export const TYPE_SHIFTED: string = 'shifted queryConfig'
export const TYPE_FLUX: string = 'flux'
export const DASHBOARD_NAME_MAX_LENGTH: number = 50
export const CELL_NAME_MAX_LENGTH: number = 68
export enum CEOTabs {
Queries = 'Queries',

View File

@ -2,6 +2,9 @@
import React, {PureComponent} from 'react'
import {Link} from 'react-router'
// Components
import {EmptyState, ComponentSize} from 'src/clockface'
// Types
import {Dashboard} from 'src/types'
@ -12,8 +15,13 @@ interface Props {
export default class UserDashboardList extends PureComponent<Props> {
public render() {
const {dashboards} = this.props
if (this.isEmpty) {
return <div>Looks like you dont have any dashboards</div>
return (
<EmptyState size={ComponentSize.ExtraSmall}>
<EmptyState.Text text="You don't have any Dashboards" />
</EmptyState>
)
}
return (

View File

@ -2,6 +2,9 @@
import React, {PureComponent} from 'react'
import {Link} from 'react-router'
// Components
import {EmptyState, ComponentSize} from 'src/clockface'
// Types
import {Organization} from 'src/types/v2'
@ -14,7 +17,11 @@ export default class UserDashboardList extends PureComponent<Props> {
const {orgs} = this.props
if (this.isEmpty) {
return <div>Looks like you dont belong to any organizations</div>
return (
<EmptyState size={ComponentSize.ExtraSmall}>
<EmptyState.Text text="You don't belong to any Organizations" />
</EmptyState>
)
}
return (

View File

@ -6,12 +6,6 @@ import classnames from 'classnames'
import {Input} from 'src/clockface'
import {ClickOutside} from 'src/shared/components/ClickOutside'
// Constants
import {
DASHBOARD_NAME_MAX_LENGTH,
DEFAULT_DASHBOARD_NAME,
} from 'src/dashboards/constants/index'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
@ -21,6 +15,8 @@ import 'src/pageLayout/components/RenamablePageTitle.scss'
interface Props {
onRename: (name: string) => void
name: string
placeholder: string
maxLength: number
}
interface State {
@ -41,7 +37,7 @@ class RenamablePageTitle extends Component<Props, State> {
public render() {
const {isEditing} = this.state
const {name} = this.props
const {name, placeholder} = this.props
if (isEditing) {
return (
@ -56,7 +52,7 @@ class RenamablePageTitle extends Component<Props, State> {
return (
<div className="renamable-page-title">
<div className={this.titleClassName} onClick={this.handleStartEditing}>
{name || DEFAULT_DASHBOARD_NAME}
{name || placeholder}
<span className="icon pencil" />
</div>
</div>
@ -64,14 +60,15 @@ class RenamablePageTitle extends Component<Props, State> {
}
private get input(): JSX.Element {
const {placeholder, maxLength} = this.props
const {workingName} = this.state
return (
<Input
maxLength={DASHBOARD_NAME_MAX_LENGTH}
maxLength={maxLength}
autoFocus={true}
spellCheck={false}
placeholder={DEFAULT_DASHBOARD_NAME}
placeholder={placeholder}
onFocus={this.handleInputFocus}
onChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
@ -119,9 +116,9 @@ class RenamablePageTitle extends Component<Props, State> {
}
private get titleClassName(): string {
const {name} = this.props
const {name, placeholder} = this.props
const nameIsUntitled = name === DEFAULT_DASHBOARD_NAME || name === ''
const nameIsUntitled = name === placeholder || name === ''
return classnames('renamable-page-title--title', {
untitled: nameIsUntitled,

View File

@ -45,6 +45,18 @@ class SideNav extends PureComponent<Props> {
location={location.pathname}
highlightWhen={['me', 'account']}
>
<NavMenu.SubItem
title="Settings"
link="/account/settings"
location={location.pathname}
highlightWhen={['settings']}
/>
<NavMenu.SubItem
title="Tokens"
link="/account/tokens"
location={location.pathname}
highlightWhen={['tokens']}
/>
<NavMenu.SubItem
title="Logout"
link="/logout"

View File

@ -14,18 +14,37 @@ interface Props {
class CSVExportButton extends PureComponent<Props, {}> {
public render() {
const {files} = this.props
return (
<Button
titleText={this.titleText}
text="CSV"
icon={IconFont.Download}
onClick={this.handleClick}
status={files ? ComponentStatus.Default : ComponentStatus.Disabled}
status={this.buttonStatus}
/>
)
}
private get buttonStatus(): ComponentStatus {
const {files} = this.props
if (files) {
return ComponentStatus.Default
}
return ComponentStatus.Disabled
}
private get titleText(): string {
const {files} = this.props
if (files) {
return 'Download query results as a .CSV file'
}
return 'Create a query in order to download results as .CSV'
}
private handleClick = () => {
const {files} = this.props
const csv = files.join('\n\n')

View File

@ -3,15 +3,17 @@
------------------------------------------------------------------------------
*/
@import 'src/style/modules';
.autorefresh-dropdown {
display: flex;
flex-wrap: nowrap;
.dropdown {
margin-right: $ix-marg-a;
}
&.paused .dropdown--selected {
opacity: 0;
}
}
.autorefresh-dropdown--pause {
margin-left: $ix-marg-a;
}

View File

@ -11,6 +11,9 @@ import autoRefreshOptions, {
AutoRefreshOptionType,
} from 'src/shared/data/autoRefreshes'
// Styles
import 'src/shared/components/dropdown_auto_refresh/AutoRefreshDropdown.scss'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
@ -123,6 +126,7 @@ class AutoRefreshDropdown extends Component<Props> {
shape={ButtonShape.Square}
icon={IconFont.Refresh}
onClick={onManualRefresh}
customClass="autorefresh-dropdown--pause"
/>
)
}

View File

@ -17,10 +17,11 @@ import {
DEFAULT_GAUGE_COLORS,
DEFAULT_THRESHOLDS_LIST_COLORS,
} from 'src/shared/constants/thresholds'
import {DEFAULT_CELL_NAME} from 'src/dashboards/constants/index'
function defaultView() {
return {
name: 'Untitled',
name: DEFAULT_CELL_NAME,
}
}

View File

@ -104,6 +104,7 @@ $form-lg-font: 17px;
/* Empty State */
$empty-state-text: $g9-mountain;
$empty-state-highlight: $g13-mist;
$default-font: 'Roboto', Helvetica, sans-serif;
$code-font: 'RobotoMono', monospace;

View File

@ -16,7 +16,6 @@
// Components
// TODO: Import these styles into their respective components instead of this stylesheet
@import 'src/shared/components/ColorDropdown';
@import 'src/shared/components/dropdown_auto_refresh/AutoRefreshDropdown';
@import 'src/shared/components/avatar/Avatar';
@import 'src/shared/components/tables/TableGraphs';
@import 'src/shared/components/fancy_scrollbar/FancyScrollbar';

View File

@ -34,10 +34,11 @@ export default class EmptyTasksLists extends PureComponent<Props> {
return (
<EmptyState size={ComponentSize.Large}>
<EmptyState.Text
text={"Looks like you don't have any Tasks, why not create one?"}
text={"Looks like you don't have any Tasks , why not create one?"}
highlightWords={['Tasks']}
/>
<Button
size={ComponentSize.Small}
size={ComponentSize.Medium}
color={ComponentColor.Primary}
icon={IconFont.Plus}
text="Create Task"
@ -49,7 +50,7 @@ export default class EmptyTasksLists extends PureComponent<Props> {
return (
<EmptyState size={ComponentSize.Large}>
<EmptyState.Text text={'No tasks match your search term'} />
<EmptyState.Text text={'No Tasks match your search term'} />
</EmptyState>
)
}