feat(notebooks): make panels focusable (#18233)

* feat: make panels click to focus instead of hover

* chore: prettier

* fix: prevent event bubbling from move panel button

This causes the focus behavior to do weird things

* feat: add title attributes to notebook panel buttons

* refactor: change default cell name to "Cell"

* refactor: show panel buttons on hover and focus state

* refactor: wrap updatePanelFocus in useCallback
pull/18252/head
alexpaxton 2020-05-27 11:12:41 -07:00 committed by GitHub
parent ba6396822e
commit 0eb1e06ba1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 77 additions and 34 deletions

View File

@ -1,5 +1,5 @@
// Libraries
import React, {FC} from 'react'
import React, {FC, MouseEvent} from 'react'
// Components
import {SquareButton, IconFont, ComponentStatus} from '@influxdata/clockface'
@ -13,13 +13,23 @@ const MovePanelUpButton: FC<Props> = ({onClick, direction}) => {
const status = onClick ? ComponentStatus.Default : ComponentStatus.Disabled
const icon = direction === 'up' ? IconFont.CaretUp : IconFont.CaretDown
const handleClick = (): void => {
const handleClick = (e: MouseEvent<HTMLButtonElement>): void => {
if (onClick) {
e.stopPropagation()
onClick()
}
}
return <SquareButton icon={icon} onClick={handleClick} status={status} />
const title = `Move this cell ${direction}`
return (
<SquareButton
icon={icon}
onClick={handleClick}
titleText={title}
status={status}
/>
)
}
export default MovePanelUpButton

View File

@ -1,5 +1,5 @@
// Libraries
import React, {FC, useContext, useCallback, ReactNode} from 'react'
import React, {FC, useContext, useCallback, ReactNode, MouseEvent} from 'react'
import classnames from 'classnames'
// Components
@ -8,6 +8,7 @@ import {
ComponentSize,
AlignItems,
JustifyContent,
ClickOutside,
} from '@influxdata/clockface'
import RemovePanelButton from 'src/notebooks/components/panel/RemovePanelButton'
import PanelVisibilityToggle from 'src/notebooks/components/panel/PanelVisibilityToggle'
@ -18,7 +19,7 @@ import NotebookPanelTitle from 'src/notebooks/components/panel/NotebookPanelTitl
import {PipeContextProps} from 'src/notebooks'
// Contexts
import {NotebookContext} from 'src/notebooks/context/notebook'
import {NotebookContext, PipeMeta} from 'src/notebooks/context/notebook'
export interface Props extends PipeContextProps {
index: number
@ -75,20 +76,40 @@ const NotebookPanelHeader: FC<HeaderProps> = ({index, controls}) => {
}
const NotebookPanel: FC<Props> = ({index, children, controls}) => {
const {meta} = useContext(NotebookContext)
const {meta, updateMeta} = useContext(NotebookContext)
const isVisible = meta[index].visible
const isFocused = meta[index].focus
const panelClassName = classnames('notebook-panel', {
[`notebook-panel__visible`]: isVisible,
[`notebook-panel__hidden`]: !isVisible,
'notebook-panel__focus': isFocused,
})
const updatePanelFocus = useCallback(
(focus: boolean): void => {
updateMeta(index, {focus} as PipeMeta)
},
[index, meta]
)
const handleClick = (e: MouseEvent<HTMLDivElement>): void => {
e.stopPropagation()
updatePanelFocus(true)
}
const handleClickOutside = (): void => {
updatePanelFocus(false)
}
return (
<div className={panelClassName}>
<NotebookPanelHeader index={index} controls={controls} />
<div className="notebook-panel--body">{children}</div>
</div>
<ClickOutside onClickOutside={handleClickOutside}>
<div className={panelClassName} onClick={handleClick}>
<NotebookPanelHeader index={index} controls={controls} />
<div className="notebook-panel--body">{children}</div>
</div>
</ClickOutside>
)
}

View File

@ -13,6 +13,7 @@ const PanelVisibilityToggle: FC<Props> = ({index}) => {
const {meta, updateMeta} = useContext(NotebookContext)
const icon = meta[index].visible ? IconFont.EyeOpen : IconFont.EyeClosed
const title = meta[index].visible ? 'Collapse cell' : 'Expand cell'
const handleClick = (): void => {
updateMeta(index, {
@ -20,7 +21,7 @@ const PanelVisibilityToggle: FC<Props> = ({index}) => {
} as PipeMeta)
}
return <SquareButton icon={icon} onClick={handleClick} />
return <SquareButton icon={icon} onClick={handleClick} titleText={title} />
}
export default PanelVisibilityToggle

View File

@ -17,7 +17,13 @@ const RemoveButton: FC<Props> = ({onRemove}) => {
onRemove()
}
return <SquareButton icon={IconFont.Remove} onClick={handleClick} />
return (
<SquareButton
icon={IconFont.Remove}
onClick={handleClick}
titleText="Remove this cell"
/>
)
}
export default RemoveButton

View File

@ -4,6 +4,7 @@ import {PipeData} from 'src/notebooks'
export interface PipeMeta {
title: string
visible: boolean
focus: boolean
}
export interface NotebookContextType {
@ -63,7 +64,7 @@ export const NotebookProvider: FC = ({children}) => {
_setPipes(add(pipe))
_setMeta(
add({
title: `Notebook_${++GENERATOR_INDEX}`,
title: `Cell_${++GENERATOR_INDEX}`,
visible: true,
})
)

View File

@ -1,4 +1,4 @@
@import "@influxdata/clockface/dist/variables.scss";
@import '@influxdata/clockface/dist/variables.scss';
$notebook-header-height: 46px;
$notebook-size-toggle: 16px;
@ -29,23 +29,15 @@ $notebook-divider-height: ($cf-marg-a * 2) + $cf-border;
}
&:last-of-type:after {
display: none;
display: none;
}
}
// Highlight panel on hover
.notebook-panel--header,
.notebook-panel--body {
transition: background-color 0.25s ease;
}
.notebook-panel:hover {
.notebook-panel--header,
.notebook-panel--body {
background-color: $notebook-panel--bg;
}
}
.notebook-panel--header {
border-radius: $cf-radius $cf-radius 0 0;
height: $notebook-header-height;
@ -66,10 +58,6 @@ $notebook-divider-height: ($cf-marg-a * 2) + $cf-border;
opacity: 0;
}
.notebook-panel:hover .notebook-panel--header-right {
opacity: 1;
}
.notebook-panel--title,
.notebook-panel--data-source {
font-size: 14px;
@ -103,6 +91,21 @@ $notebook-divider-height: ($cf-marg-a * 2) + $cf-border;
}
}
// Focus state
.notebook-panel__focus,
.notebook-panel:hover {
.notebook-panel--header-right {
opacity: 1;
}
}
.notebook-panel__focus {
.notebook-panel--header,
.notebook-panel--body {
background-color: $notebook-panel--bg;
}
}
.notebook-panel--data-caret {
display: inline-block;
margin-left: $cf-marg-a;
@ -128,7 +131,8 @@ $notebook-divider-height: ($cf-marg-a * 2) + $cf-border;
left: 50%;
transform: translate(-50%, -50%);
width: $notebook-size-toggle;
transition: height 0.25s cubic-bezier(0.25, 1, 0.5, 1), border-color 0.25s ease;
transition: height 0.25s cubic-bezier(0.25, 1, 0.5, 1),
border-color 0.25s ease;
border: $cf-border solid $g8-storm;
border-radius: $cf-radius / 2;
}
@ -226,11 +230,11 @@ $notebook-divider-height: ($cf-marg-a * 2) + $cf-border;
}
.notebook-header--buttons {
display: inline-flex;
flex: 0 0 auto;
flex-wrap: wrap;
display: inline-flex;
flex: 0 0 auto;
flex-wrap: wrap;
> * {
margin-left: 4px;
}
> * {
margin-left: 4px;
}
}