-
Notifications
You must be signed in to change notification settings - Fork 180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(components): create new tab-styled vertical nav bar #2371
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// @flow | ||
import * as React from 'react' | ||
import {NavLink} from 'react-router-dom' | ||
import classnames from 'classnames' | ||
|
||
import styles from './navbar.css' | ||
import {Button} from '../buttons' | ||
import {NotificationIcon, type IconName} from '../icons' | ||
|
||
type NavTabProps= { | ||
/** optional click event for nav button */ | ||
onClick?: (event: SyntheticEvent<>) => void, | ||
/** optional url for nav button route */ | ||
url?: string, | ||
/** position a single button on the bottom of the page */ | ||
isBottom?: boolean, | ||
/** classes to apply */ | ||
className?: string, | ||
/** disabled attribute (setting disabled removes onClick) */ | ||
disabled?: boolean, | ||
/** optional title to display below the icon */ | ||
title?: string, | ||
/** Icon name for button's icon */ | ||
iconName: IconName, | ||
/** Display a notification dot */ | ||
notification?: boolean, | ||
/** selected styling (can also use react-router & `activeClassName`) */ | ||
selected?: boolean, | ||
} | ||
|
||
export default function NavTab (props: NavTabProps) { | ||
const {url} = props | ||
const className = classnames( | ||
props.className, | ||
styles.tab, | ||
{ | ||
[styles.disabled]: props.disabled, | ||
[styles.bottom]: props.isBottom, | ||
[styles.selected]: props.selected, | ||
} | ||
) | ||
|
||
let buttonProps = { | ||
className: className, | ||
disabled: props.disabled, | ||
onClick: props.onClick, | ||
} | ||
|
||
if (url) { | ||
buttonProps = { | ||
...buttonProps, | ||
Component: NavLink, | ||
to: url, | ||
activeClassName: styles.selected, | ||
} | ||
} | ||
|
||
return ( | ||
<Button {...buttonProps}> | ||
<NotificationIcon | ||
name={props.iconName} | ||
childName={props.notification ? 'circle' : null} | ||
className={styles.icon} | ||
childClassName={styles.notification} | ||
/> | ||
{props.title && ( | ||
<span className={styles.title}> | ||
{props.title} | ||
</span> | ||
)} | ||
</Button> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
Basic Usage: | ||
|
||
```js | ||
<div style={{width: '5rem'}}> | ||
<NavTab | ||
onClick={() => alert('you clicked me')} | ||
iconName='ot-connect' | ||
/> | ||
</div> | ||
``` | ||
Disabled: | ||
|
||
```js | ||
<div style={{width: '5rem'}}> | ||
<NavTab | ||
onClick={() => alert('you clicked me')} | ||
iconName='ot-connect' | ||
disabled | ||
/> | ||
</div> | ||
``` | ||
|
||
Currently Selected: | ||
|
||
```js | ||
<div style={{width: '5rem'}}> | ||
<NavTab | ||
onClick={() => alert('you clicked me')} | ||
iconName='ot-connect' | ||
selected | ||
/> | ||
</div> | ||
``` | ||
|
||
Optional Title: | ||
|
||
```js | ||
<div style={{width: '5rem'}}> | ||
<NavTab | ||
onClick={() => alert('you clicked me')} | ||
title='connect' | ||
iconName='ot-connect' | ||
title='connect' | ||
/> | ||
</div> | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// @flow | ||
import * as React from 'react' | ||
import cx from 'classnames' | ||
|
||
import styles from './navbar.css' | ||
import {Button} from '../buttons' | ||
import {NotificationIcon, type IconName} from '../icons' | ||
|
||
type Props = { | ||
/** optional click event for nav button */ | ||
onClick?: (event: SyntheticEvent<>) => mixed, | ||
/** link to outside URL */ | ||
to: string, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also not necessarily the time to address this, but |
||
/** position a single button on the bottom of the page */ | ||
isBottom?: boolean, | ||
/** classes to apply */ | ||
className?: string, | ||
/** disabled attribute (setting disabled removes onClick) */ | ||
disabled?: boolean, | ||
/** optional title to display below the icon */ | ||
title?: string, | ||
/** Icon name for button's icon */ | ||
iconName: IconName, | ||
/** Display a notification dot */ | ||
notification?: boolean, | ||
/** selected styling (can also use react-router & `activeClassName`) */ | ||
selected?: boolean, | ||
} | ||
|
||
/** Very much like NavTab, but used for opening external links in a new tab/window */ | ||
export default function OutsideLinkTab (props: Props) { | ||
const className = cx( | ||
props.className, | ||
styles.tab, | ||
styles.no_link, | ||
{ | ||
[styles.disabled]: props.disabled, | ||
[styles.bottom]: props.isBottom, | ||
[styles.selected]: props.selected, | ||
} | ||
) | ||
return ( | ||
<Button | ||
className={className} | ||
disabled={props.disabled} | ||
onClick={props.onClick} | ||
Component='a' | ||
href={props.disabled ? '' : props.to} | ||
target='_blank' | ||
rel='noopener noreferrer' | ||
> | ||
<NotificationIcon | ||
name={props.iconName} | ||
childName={props.notification ? 'circle' : null} | ||
className={styles.icon} | ||
childClassName={styles.notification} | ||
/> | ||
{props.title && ( | ||
<span className={styles.title}> | ||
{props.title} | ||
</span> | ||
)} | ||
</Button> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// @flow | ||
import * as React from 'react' | ||
import cx from 'classnames' | ||
import styles from './navbar.css' | ||
|
||
type Props= { | ||
className?: string, | ||
topChildren?: React.Node, | ||
bottomChildren?: React.Node, | ||
} | ||
|
||
export default function TabbedNavBar (props: Props) { | ||
const className = cx(styles.navbar, props.className) | ||
return ( | ||
<nav className={className}> | ||
<div className={styles.top_section}> | ||
{props.topChildren} | ||
</div> | ||
|
||
<div className={styles.filler} /> | ||
|
||
<div className={styles.bottom_section}> | ||
{props.bottomChildren} | ||
</div> | ||
</nav> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// @flow | ||
// navigational components | ||
import TabbedNavBar from './TabbedNavBar' | ||
import NavTab from './NavTab' | ||
import OutsideLinkTab from './OutsideLinkTab' | ||
|
||
export { | ||
TabbedNavBar, | ||
NavTab, | ||
OutsideLinkTab, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
@import '..'; | ||
|
||
.navbar { | ||
flex: none; | ||
width: 4rem; | ||
display: flex; | ||
flex-direction: column; | ||
} | ||
|
||
.navbar > * { | ||
flex: 1; | ||
} | ||
|
||
/* TODO(mc, 2018-03-21): @apply --button-default? */ | ||
.tab { | ||
display: inline-block; | ||
cursor: pointer; | ||
background-color: var(--c-white); | ||
color: var(--c-med-gray); | ||
outline: none; /* button reset? */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the app we have a global reset stylesheet that does this. We should revisit resets within the comp lib!
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah we should have 1 reset and do it everywhere, that would be great There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do do one reset, then I would prefer the reset lives on the component's class as opposed to a HTML tag scoped CSS rule. That way, in case there are other components in the library that might use the same tags under the hood, we're styling them in situ and not relying on global CSS. Keep it modular There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would any of these resets be applied by the TODO above? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mcous |
||
border: none; /* button reset >:( */ | ||
border-right: var(--bd-light); | ||
} | ||
|
||
.top_section > *, | ||
.bottom_section > *, | ||
.filler { | ||
width: 100%; | ||
border-right: var(--bd-light); | ||
} | ||
|
||
.top_section > .tab { | ||
border-bottom: var(--bd-light); | ||
} | ||
|
||
.bottom_section > .tab { | ||
border-top: var(--bd-light); | ||
} | ||
|
||
.filler { | ||
flex-grow: 9999; | ||
} | ||
|
||
.title { | ||
display: block; | ||
color: var(--c-font-dark); | ||
font-size: var(--fs-caption); | ||
font-weight: var(--fw-semibold); | ||
text-transform: uppercase; | ||
text-align: center; | ||
padding-bottom: 0.5rem; | ||
} | ||
|
||
.disabled { | ||
cursor: default; | ||
pointer-events: none; | ||
opacity: 0.75; | ||
} | ||
|
||
.selected { | ||
background-color: var(--c-bg-light); | ||
border-right-color: transparent; | ||
|
||
& > svg { | ||
fill: var(--c-dark-gray); | ||
} | ||
} | ||
|
||
.icon { | ||
height: 100%; | ||
width: 100%; | ||
padding: 0.5rem 0.5rem 0; | ||
color: var(--c-charcoal); | ||
} | ||
|
||
.notification { | ||
color: var(--c-orange); | ||
} | ||
|
||
.no_link { | ||
text-decoration: none; | ||
color: inherit; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dunno if now is the time to do it, but we do have the chance to remove this
react-router
dependency here by providing a prop to spread into thebuttonProps
variable below, allowing the component user to pass inLink
themselvesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like a good move. I'd rather do that at the same time we swap
nav/
fortabbedNav/
and do the work to integrate in into Run App, if we do