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 useCallbackpull/18252/head
parent
ba6396822e
commit
0eb1e06ba1
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue