forked from metabase/metabase
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Entity menu components + tests (metabase#6162)
* 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
Showing
12 changed files
with
648 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.