Skip to content

Commit

Permalink
Entity menu components + tests (metabase#6162)
Browse files Browse the repository at this point in the history
* init wip

* entity menu, examples, tests

* fix usage examples

* note react motion flow issue

* spacing and color tweaks

* icon hover transition

* add new icons

* tweak icon positioning / svg defs

* alert positioning

* icon x spacing

* set icon size based on the viewBox

* always set a width and height

* update download icon

* tweak icon positioning

* file cleanup

* update snap

* fix burger
  • Loading branch information
kdoh authored Oct 17, 2017
1 parent 97b0475 commit c9fb340
Show file tree
Hide file tree
Showing 12 changed files with 648 additions and 12 deletions.
8 changes: 8 additions & 0 deletions frontend/src/metabase/components/Card.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react'

const Card = ({ children }) =>
<div className="bordered rounded shadowed bg-white">
{ children }
</div>

export default Card
83 changes: 83 additions & 0 deletions frontend/src/metabase/components/EntityMenu.info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react'

import EntityMenu from 'metabase/components/EntityMenu'

export const component = EntityMenu

export const description = `
A menu with varios entity related options grouped by context.
`

const DemoAlignRight = ({ children }) =>
<div className="flex flex-full">
<div className="flex align-center ml-auto">
{children}
</div>
</div>

export const examples = {
'Edit menu': (
<DemoAlignRight>
<EntityMenu
triggerIcon='pencil'
items={[
{ title: "Edit this question", icon: "editdocument", action: () => alert('Action type') },
{ title: "View revision history", icon: "history", link: '/derp' },
{ title: "Move", icon: "move", action: () => alert('Move action') },
{ title: "Archive", icon: "archive", action: () => alert('Archive action') }
]}
/>
</DemoAlignRight>
),
'Share menu': (
<DemoAlignRight>
<EntityMenu
triggerIcon='share'
items={[
{ title: "Add to dashboard", icon: "addtodash", action: () => alert('Action type') },
{ title: "Download results", icon: "download", link: '/download' },
{ title: "Sharing and embedding", icon: "embed", action: () => alert('Another action type') },
]}
/>
</DemoAlignRight>
),
'More menu': (
<DemoAlignRight>
<EntityMenu
triggerIcon='burger'
items={[
{ title: "Get alerts about this", icon: "alert", action: () => alert('Get alerts about this') },
{ title: "View the SQL", icon: "sql", link: '/download' },
]}
/>
</DemoAlignRight>
),
'Multiple menus': (
<DemoAlignRight>
<EntityMenu
triggerIcon='pencil'
items={[
{ title: "Edit this question", icon: "editdocument", action: () => alert('Action type') },
{ title: "View revision history", icon: "history", link: '/derp' },
{ title: "Move", icon: "move", action: () => alert('Move action') },
{ title: "Archive", icon: "archive", action: () => alert('Archive action') }
]}
/>
<EntityMenu
triggerIcon='share'
items={[
{ title: "Add to dashboard", icon: "addtodash", action: () => alert('Action type') },
{ title: "Download results", icon: "download", link: '/download' },
{ title: "Sharing and embedding", icon: "embed", action: () => alert('Another action type') },
]}
/>
<EntityMenu
triggerIcon='burger'
items={[
{ title: "Get alerts about this", icon: "alert", action: () => alert('Get alerts about this') },
{ title: "View the SQL", icon: "sql", link: '/download' },
]}
/>
</DemoAlignRight>
)
}
99 changes: 99 additions & 0 deletions frontend/src/metabase/components/EntityMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* @flow */
import React, { Component } from 'react'
import { Motion, spring } from 'react-motion'

import OnClickOutsideWrapper from 'metabase/components/OnClickOutsideWrapper'

import Card from 'metabase/components/Card'
import EntityMenuTrigger from 'metabase/components/EntityMenuTrigger'
import EntityMenuItem from 'metabase/components/EntityMenuItem'

type EntityMenuOption = {
icon: string,
title: string,
action?: () => void,
link?: string
}

type Props = {
items: Array<EntityMenuOption>,
triggerIcon: string
}

class EntityMenu extends Component {

props: Props

state = {
open: false
}

toggleMenu = () => {
const open = !this.state.open
this.setState({ open })
}

render () {
const { items, triggerIcon } = this.props
const { open } = this.state
return (
<div className="relative">
<EntityMenuTrigger
icon={triggerIcon}
onClick={this.toggleMenu}
open={open}
/>
{ open && (
/* Note: @kdoh 10/12/17
* React Motion has a flow type problem with children see
* https://github.com/chenglou/react-motion/issues/375
* TODO This can be removed if we upgrade to flow 0.53 and react-motion >= 0.5.1
* $FlowFixMe */
<Motion
defaultStyle={{
opacity: 0,
translateY: 0
}}
style={{
opacity: open ? spring(1): spring(0),
translateY: open ? spring(10) : spring(0)
}}
>
{ ({ opacity, translateY }) =>
<OnClickOutsideWrapper handleDismissal={this.toggleMenu}>
<div
className="absolute right"
style={{
top: 35,
opacity: opacity,
transform: `translateY(${translateY}px)`
}}
>
<Card>
<ol className="py1" style={{ minWidth: 210 }}>
{items.map(item => {
return (
<li key={item.title}>
<EntityMenuItem
icon={item.icon}
title={item.title}
action={item.action}
link={item.link}
/>
</li>
)
})}
</ol>
</Card>
</div>
</OnClickOutsideWrapper>
}
</Motion>
)}
</div>
)
}
}

export default EntityMenu

91 changes: 91 additions & 0 deletions frontend/src/metabase/components/EntityMenuItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import cxs from 'cxs'
import React from 'react'
import { Link } from 'react-router'

import Icon from 'metabase/components/Icon'

const itemClasses = cxs({
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
color: '#616D75',
paddingLeft: '1.45em',
paddingRight: '1.45em',
paddingTop: '0.85em',
paddingBottom: '0.85em',
textDecoration: 'none',
transition: 'all 300ms linear',
':hover': {
color: '#509ee3'
},
'> .Icon': {
color: '#BCC5CA',
marginRight: '0.65em'
},
':hover > .Icon': {
color: '#509ee3',
transition: 'all 300ms linear',
},
// icon specific tweaks
// the alert icon should be optically aligned with the x-height of the text
'> .Icon.Icon-alert': {
transform: `translateY(1px)`
},
// the embed icon should be optically aligned with the x-height of the text
'> .Icon.Icon-embed': {
transform: `translateY(1px)`
},
// the download icon should be optically aligned with the x-height of the text
'> .Icon.Icon-download': {
transform: `translateY(1px)`
},
// the history icon is wider so it needs adjustement to center it with other
// icons
'> .Icon.Icon-history': {
transform: `translateX(-2px)`
}
})

const LinkMenuItem = ({ children, link }) =>
<Link className={itemClasses} to={link}>
{children}
</Link>

const ActionMenuItem = ({ children, action }) =>
<div className={itemClasses} onClick={action}>
{children}
</div>

const EntityMenuItem = ({
action,
title,
icon,
link
}) => {
if(link && action) {
console.warn('EntityMenuItem Error: You cannot specify both action and link props')
return <div></div>
}

const content = [
<Icon name={icon} />,
<span className="text-bold">{title}</span>
]

if(link) {
return (
<LinkMenuItem link={link}>
{content}
</LinkMenuItem>
)
}
if(action) {
return (
<ActionMenuItem action={action}>
{content}
</ActionMenuItem>
)
}
}

export default EntityMenuItem
37 changes: 37 additions & 0 deletions frontend/src/metabase/components/EntityMenuTrigger.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react'
import Icon from 'metabase/components/Icon'
import cxs from 'cxs'

const EntityzMenuTrigger = ({ icon, onClick, open }) => {
const interactionColor = '#F2F4F5'
const classes = cxs({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 40,
height: 40,
borderRadius: 99,
cursor: 'pointer',
color: open ? '#509ee3' : 'inherit',
backgroundColor: open ? interactionColor : 'transparent',
':hover': {
backgroundColor: interactionColor,
color: '#509ee3',
transition: 'all 300ms linear'
},
// special cases for certain icons
// Icon-share has a taller viewvbox than most so to optically center
// the icon we need to translate it upwards
'> .Icon.Icon-share': {
transform: `translateY(-2px)`
}
})

return (
<div onClick={onClick} className={classes}>
<Icon name={icon} className="m1" />
</div>
)
}

export default EntityzMenuTrigger
2 changes: 2 additions & 0 deletions frontend/src/metabase/css/query_builder.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
border-right: none;
}

/*
.Icon-download,
.Icon-addToDash {
fill: #919191;
Expand All @@ -42,6 +43,7 @@
fill: var(--brand-color);
transition: fill .3s linear;
}
*/

/* a section of the graphical query itself */
.Query-section {
Expand Down
Loading

0 comments on commit c9fb340

Please sign in to comment.