Skip to content

Commit

Permalink
feat(TitleBar): ActionButton, ActionButtonBar, used to make responsiv…
Browse files Browse the repository at this point in the history
…e title bar

- if there is a long title, truncate and show and ellipsis. If the title is long, and you click it, it expands to show the full title
- Action buttons in the ActionBar look at the width of the div, if they are too large to fit in the width, they collapse to the hamburger.
  • Loading branch information
ramfox committed Jan 28, 2020
1 parent ae58313 commit 4ac5f51
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 24 deletions.
3 changes: 2 additions & 1 deletion app/components/chrome/ActionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import Icon from './Icon'
import sizeMe from 'react-sizeme'

export interface ActionButtonProps {
icon: string
Expand All @@ -18,4 +19,4 @@ const ActionButton: React.FunctionComponent<ActionButtonProps> = (props) => {
)
}

export default ActionButton
export default sizeMe()(ActionButton)
70 changes: 70 additions & 0 deletions app/components/chrome/ActionButtonBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react'
import ActionButton, { ActionButtonProps } from './ActionButton'
import Hamburger from './Hamburger'
import classNames from 'classnames'
import sizeMe, { SizeMeProps } from 'react-sizeme'

interface ActionButtonBarProps {
data: ActionButtonProps[]
size: SizeMeProps['size']
}

const ActionButtonBar: React.FunctionComponent<ActionButtonBarProps> = (props) => {
const { data, size } = props
if (data.length === 0) return null
if (size.width && size.width < 40) {
return <Hamburger data={data} />
}
const [areVisible, setAreVisible] = React.useState<boolean[]>(data.map(() => true))

const [buttonSizes, setButtonSizes] = React.useState<number[]>(data.map(() => 0))

const handleSize = (width: number | null, index: number) => {
setButtonSizes((prev: number[]) => {
if (width === null) return prev
let buttonSizes = prev.slice()
buttonSizes[index] = width
return buttonSizes
})
}

const buttons = data.map((d: ActionButtonProps, i: number) =>
<div className={classNames({ 'closed': !areVisible[i] })} key={i}>
<ActionButton icon={d.icon} onClick={d.onClick} text={d.text} onSize={(size: { width: number | null, height: number | null }) => handleSize(size.width, i)}/>
</div>
)

React.useEffect(() => {
console.log(size)
console.log(buttonSizes)
if (size.width === null) return

let cumulativeSize = 40 // hamburger width
for (let i = 0; i < data.length; i++) {
cumulativeSize += buttonSizes[i]
// if the cumulativeSize is larger then size, set all the buttons before this
// point visible, and after this point not visible.
if (cumulativeSize > size.width) {
setAreVisible((prev: boolean[]) => {
return prev.map((val: boolean, j: number) => j < i)
})
break
}
if (i === data.length - 1) {
setAreVisible((prev: boolean[]) => prev.map(() => true))
}
}
}, [size, buttonSizes])
return (
<div className='action-button-bar'>
{buttons}
{/* If any are not visible, show the hamburger */}
{areVisible.some((val: boolean) => val === false) && <Hamburger data={data.filter((d: ActionButtonProps, i: number) => {
if (!areVisible[i]) return d
else return undefined
})}/>}
</div>
)
}

export default sizeMe()(ActionButtonBar)
61 changes: 56 additions & 5 deletions app/components/dataset/TitleBar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react'
import ActionButtonBar from '../chrome/ActionButtonBar'
import { ActionButtonProps } from '../chrome/ActionButton'
import Icon from '../chrome/Icon'

import classNames from 'classnames'

interface TitleBarProps {
icon?: string
title: string
Expand All @@ -10,17 +13,65 @@ interface TitleBarProps {

const TitleBar: React.FunctionComponent<TitleBarProps> = (props) => {
const { icon, title, data } = props

const [expanded, setExpanded] = React.useState(false)
const [expandable, setExpandable] = React.useState(false)

const wrapRef = React.useRef<HTMLDivElement>(null)

const titleRefWithCallback = () => {
const ref = React.useRef<HTMLDivElement>(null)
const setRef = React.useCallback((el: HTMLDivElement) => {
if (el !== null) {
if (el.offsetWidth < el.scrollWidth) {
setExpandable(true)
return
}
if (expandable) setExpandable(false)
}
}, [])
ref.current = setRef
return [setRef]
}

const [titleRef] = titleRefWithCallback()

// const titleRef = React.useRef<HTMLDivElement>(null)
const rightRef = React.useRef<HTMLDivElement>(null)

const handleClick = (e: React.MouseEvent) => {
e.preventDefault()
// if you click on the title while there is an elipses
// expand the content
if (expandable) {
if (expanded) setExpanded(false)
else setExpanded(true)
return
}
// otherwise, if expanded is true, set it false
if (expanded) setExpanded(false)
}

return (
<div className='title-bar'>
<div className='left' >
<div ref={wrapRef} className='title-bar'>
<div
onClick={handleClick}
className={classNames('left', {
'expandable': expandable,
'expanded': expanded
})}>
{icon &&
<div className='title-bar-icon'>
<Icon icon={icon} size='lg' />
<Icon icon={icon} size='md' />
</div>
}
<div className='title-bar-title'>{title}</div>
<div ref={titleRef} className={classNames('title-bar-title', {
'expanded': expanded
})}>{title}</div>
</div>
<div ref={rightRef} className='right'>
<ActionButtonBar data={data}/>
</div>
<div className='right'></div>
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion app/components/overlay/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const Overlay: React.FunctionComponent<OverlayProps> = ({
return (
<div
style={{ maxHeight: height, maxWidth: width }}
className={classNames('overlay', { 'visible': open })}
className={classNames('overlay', { 'closed': !open })}
ref={overlayRef}
>
{title && <div className='title-bar'>
Expand Down
17 changes: 17 additions & 0 deletions app/scss/0.4.0/chrome.scss
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@
font-weight: 300;
display: flex;
align-items: center;
cursor: pointer;
padding: 10px;
white-space: nowrap;

.text {
margin-left: 10px;
Expand Down Expand Up @@ -191,4 +194,18 @@
}
}
}
}

.action-button-bar {
display: flex;
justify-content: flex-end;
align-items: center;

> div {
display: inline-block;

&.closed {
display: none;
}
}
}
41 changes: 40 additions & 1 deletion app/scss/0.4.0/dataset.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,47 @@
.title-bar {
display: flex;
justify-content: space-between;
align-items: flex-start;
width: 100%;
padding: 5px 20px;
flex-flow: nowrap;

.left {
flex: 0 0
display: flex;
align-items: flex-start;
min-width: 0;
float: unset;
overflow: hidden;
white-space: nowrap;

&.expanded {
cursor: pointer;
}

&.expandable {
cursor: pointer;
}

.title-bar-icon {
margin-top: 5px;
margin-right: 10px;
}

.title-bar-title {
font-size: 24px;
font-weight: 900;
white-space: nowrap;
text-overflow: ellipsis;
overflow-x: hidden;

&.expanded {
white-space: normal;
}
}
}
.right {
flex-grow: 1;
flex-Basis: 40px;
float: unset;
}
}
8 changes: 2 additions & 6 deletions app/scss/0.4.0/overlay.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@
flex-direction: column;
box-shadow: 0px 0px 6px rgba(0,0,0,0.20);
background: #fff;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.3s ease-in-out;

&.visible {
visibility: visible;
opacity: 1;
&.closed {
display: none;
}

.title-bar {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@
"react-router-dom": "5.1.2",
"react-select": "3.0.8",
"react-simplemde-editor": "4.1.0",
"react-sizeme": "^2.6.12",
"react-tag-input": "6.4.1",
"react-tooltip": "3.11.1",
"react-transition-group": "4.3.0",
Expand Down
50 changes: 50 additions & 0 deletions stories/10-Dataset.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react'

import DatasetDetailsSubtext from '../app/components/dataset/DatasetDetailsSubtext'
import Dataset from '../app/models/dataset'
import TitleBar from '../app/components/dataset/TitleBar'
import { ActionButtonProps } from '../app/components/chrome/ActionButton'

export default {
title: 'Dataset',
Expand All @@ -10,6 +12,29 @@ export default {
}
}

const titleBarActions: ActionButtonProps[] = [
{ icon: 'publish', text: 'Publish', onClick: (e: MouseEvent<Element, MouseEvent>) => { console.log('Publish!', e) } },
{ icon: 'close', text: 'Unpublish', onClick: (e: MouseEvent<Element, MouseEvent>) => { console.log('UnPublish!', e) } },
{ icon: 'openInFinder', text: 'Open in finder', onClick: (e: MouseEvent<Element, MouseEvent>) => { console.log('Open in Finder!', e) } },
{ icon: 'clone', text: 'Clone', onClick: (e: MouseEvent<Element, MouseEvent>) => { console.log('Clone!', e) } }
]

export const titleBar = () => {
return (
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '100%', width: '100%' }}>
<TitleBar icon='structure' title='Short test title' data={titleBarActions} />
<TitleBar title='Long test title with no icon that should overflow ellipsis unless you click it in which case it should expand' data={titleBarActions} />
</div>
)
}

titleBar.story = {
name: 'Title Bar',
parameters: {
notes: 'four actions max will appear at the top bar, if the title is long or the space is small, they will collapse into the hamburger'
}
}

export const detailsSubtext = () => {
const date: Date = new Date(1579892323028)
const data: Dataset = {
Expand Down Expand Up @@ -64,3 +89,28 @@ detailsSubtext.story = {
notes: 'sm/md, light/muted/dark'
}
}

export const titleBarTest = () => {
return (
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '100%', width: '100%', margin: 10, position: 'relative' }}>
<div style={{
display: 'flex',
flexFlow: 'nowrap',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
overflow: 'hidden'
}}>
<div style={{ height: 100, background: 'red', overflow: 'hidden', whiteSpace: 'nowrap' }}>Here is some text that is</div>
<div style={{ height: 100, background: 'green', flexGrow: 1, flexBasis: 40 }}>What about now when I add stuff to this one?</div>
</div>
</div>
)
}

titleBarTest.story = {
name: 'Title Bar Test',
parameters: {
notes: 'four actions max will appear at the top bar, if the title is long or the space is small, they will collapse into the hamburger'
}
}
Loading

0 comments on commit 4ac5f51

Please sign in to comment.