-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #57 from revelrylabs/f-expanding-column
Add expanding column component
- Loading branch information
Showing
7 changed files
with
262 additions
and
6 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
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
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
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,76 @@ | ||
@import "../node_modules/foundation-sites/scss/grid/gutter"; | ||
|
||
$expanding-col-transition-time: 0.25s; | ||
$expanding-col-transition-timing-function: cubic-bezier(0.0,0.0,0.2,1); | ||
$expanding-col-button-width: 2.6rem; | ||
|
||
.rev-ExpandingCol-closer, | ||
.rev-ExpandingCol-expander { | ||
position: absolute; | ||
width: $expanding-col-button-width; | ||
// Remove focus style because it doesn't look right when using the button | ||
// as a tab to open/close the sidebar. Different than button in form scenario | ||
&:focus { | ||
outline: 0; | ||
} | ||
// Some breakpoints have different column gutter widths, so adjust the edge | ||
// of the button to match-- with one rule per breakpoint | ||
@each $breakpoint, $value in $grid-column-gutter { | ||
// We take half, because the foundation gutter amount is the sum of both | ||
// sides, not a single side's gutter | ||
$padding: rem-calc($value) / 2; | ||
|
||
@include breakpoint($breakpoint) { | ||
// rem-calc(1) adjusts for the border on the button-- which is hard coded | ||
// in the button mixin, so I don't have a variable to key on | ||
right: $padding + rem-calc(1); | ||
} | ||
} | ||
} | ||
.rev-ExpandingCol-pane { | ||
// So that when this is collapsed, if there is content which has padding or | ||
// margins, it will still be hidden (otherwise, this forces a width and can | ||
// make content appear) | ||
overflow-x: hidden; | ||
// Position is relative so that we can position the buttons absolutely against | ||
// this pane (in the upper right) | ||
position: relative; | ||
transition: max-width $expanding-col-transition-time; | ||
transition-timing-function: $expanding-col-transition-timing-function; | ||
&.is-closed { | ||
max-width: 4.5rem; | ||
// Some breakpoints have different column gutter widths, so the closed width | ||
// needs to be that gutter, plus the width of the button. Note, the gutter | ||
// value is the total gutter for both sides | ||
@each $breakpoint, $value in $grid-column-gutter { | ||
@include breakpoint($breakpoint) { | ||
max-width: rem-calc($value) + $expanding-col-button-width; | ||
} | ||
} | ||
} | ||
&.rev-ExpandingCol--expanderless.is-closed { | ||
// Expanderless versions don't need to hold space for the button, and don't | ||
// need gutters when collapsed (because you want the next column to start | ||
// immediately against the edge, not a couple rems away from the edge of | ||
// the row) | ||
max-width: 0; | ||
padding-left: 0; | ||
padding-right: 0; | ||
} | ||
} | ||
.rev-ExpandingCol-pane-content { | ||
// The expander button will be between the content area and the next column | ||
// as a sort of "tab" | ||
margin-right: $expanding-col-button-width; | ||
// So that when this is collapsed, if there is content which has padding or | ||
// margins, it will still be hidden (otherwise, this forces a width and can | ||
// make content appear) | ||
overflow-x: hidden; | ||
.rev-ExpandingCol--expanderless & { | ||
// With no button, we only need a "one pixel" margin. Why not zero? | ||
// With zero, the pane ends up being one pixel too small, which'll cut off | ||
// borders in a lot of common situations. So add a pixel so it goes right | ||
// to the edge of the content. | ||
margin-right: rem-calc(1); | ||
} | ||
} |
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,49 @@ | ||
import React, {Component} from 'react' | ||
import {ExpandingColStateContainer, ExpandingCol} from './ExpandingCol' | ||
import Callout from './Callout' | ||
import {Row, Col} from './grid' | ||
|
||
export class DefaultStateful extends Component { | ||
render() { | ||
return ( | ||
<Row> | ||
<ExpandingColStateContainer small={3}> | ||
<Callout primary> | ||
This state will be shown or hidden with the buttons. | ||
</Callout> | ||
</ExpandingColStateContainer> | ||
<Col small={9}> | ||
This is outside the expander. | ||
</Col> | ||
</Row> | ||
) | ||
} | ||
} | ||
|
||
export class DefaultStateless extends Component { | ||
constructor(props) { | ||
super(props) | ||
this.state = { | ||
open: false | ||
} | ||
} | ||
|
||
// Don't do toggle methods in real life, Joel will be sad | ||
toggleOpen = () => this.setState({open: !this.state.open}) | ||
|
||
render() { | ||
let state = this.state || {open: false} | ||
return ( | ||
<Row> | ||
<ExpandingCol small={3} open={state.open}> | ||
<Callout primary> | ||
You did it! | ||
</Callout> | ||
</ExpandingCol> | ||
<Col small={9} onClick={this.toggleOpen}> | ||
Click me! | ||
</Col> | ||
</Row> | ||
) | ||
} | ||
} |
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,84 @@ | ||
import React, {cloneElement, PropTypes} from 'react' | ||
import {Row, Col} from './grid' | ||
import Button from './Button' | ||
|
||
// An expanding column that manages its own state. This is to be used in | ||
// non-Redux applications where the state is not managed by another component | ||
// If you are using Redux, or if a different component controls whether the | ||
// column is open or not, use an ExpandingCol instead | ||
export class ExpandingColStateContainer extends React.Component { | ||
static propTypes = { | ||
children: PropTypes.node, | ||
className: PropTypes.string, | ||
closer: PropTypes.element, | ||
expander: PropTypes.element, | ||
} | ||
|
||
static defaultProps = { | ||
// intentionally blank to override the 'expanderless' class of the stateless | ||
// version of the component | ||
className: '', | ||
closer: <Button className="rev-ExpandingCol-closer secondary" icon="minus" />, | ||
expander: <Button className="rev-ExpandingCol-expander secondary" icon="plus" />, | ||
} | ||
|
||
constructor(props) { | ||
super(props) | ||
this.state = { | ||
open: false, | ||
} | ||
} | ||
|
||
expandPane = () => { | ||
this.setState({open: true}) | ||
} | ||
|
||
closePane = () => { | ||
this.setState({open: false}) | ||
} | ||
|
||
// Feeds open state, closer and opener down to an ExpandingCol. Note that | ||
// we clone the closer and expander so that we can accept them as props | ||
// from up the tree but still inject our onClick handlers for open/close | ||
render() { | ||
let {children, closer, expander, ...remainingProps} = this.props | ||
return <ExpandingCol | ||
open={this.state.open} | ||
closer={cloneElement(closer, {onClick: this.closePane})} | ||
expander={cloneElement(expander, {onClick: this.expandPane})} | ||
{...remainingProps} | ||
> | ||
{children} | ||
</ExpandingCol> | ||
} | ||
} | ||
|
||
export class ExpandingCol extends React.Component { | ||
static defaultProps = { | ||
className: 'rev-ExpandingCol--expanderless', | ||
} | ||
|
||
static propTypes = { | ||
children: PropTypes.node, | ||
className: PropTypes.string, | ||
closer: PropTypes.node, | ||
open: PropTypes.bool, | ||
} | ||
|
||
render() { | ||
let {children, closer, expander, open, className, ...remainingProps} = this.props | ||
let openClass = open ? 'is-open' : 'is-closed' | ||
|
||
return ( | ||
<Col | ||
{...remainingProps} | ||
className={`rev-ExpandingCol-pane ${className} ${openClass}`} | ||
> | ||
{open ? closer : expander} | ||
<div className="rev-ExpandingCol-pane-content"> | ||
{children} | ||
</div> | ||
</Col> | ||
) | ||
} | ||
} |
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 'jsdom-global/register' | ||
import {ExpandingCol, ExpandingColStateContainer} from './ExpandingCol' | ||
|
||
describe('ExpandingCol', () => { | ||
it('should render closed with is-closed', () => { | ||
let classes = shallow( | ||
<ExpandingCol>Test</ExpandingCol> | ||
).first().prop('className') | ||
|
||
expect(classes).to.contain('is-closed') | ||
}) | ||
|
||
it('should render open with is-open', () => { | ||
let classes = shallow( | ||
<ExpandingCol open>Test</ExpandingCol> | ||
).first().prop('className') | ||
|
||
expect(classes).to.contain('is-open') | ||
}) | ||
}) | ||
|
||
describe('ExpandingColStateContainer', () => { | ||
it('should render without throwing', () => { | ||
shallow(<ExpandingColStateContainer>Test</ExpandingColStateContainer>) | ||
}) | ||
|
||
it('should be openable', () => { | ||
let container = mount( | ||
<ExpandingColStateContainer>Test</ExpandingColStateContainer> | ||
) | ||
let expander = container.find('.rev-ExpandingCol-expander') | ||
|
||
expander.simulate('click') | ||
|
||
expect(container.state('open')).to.eq(true) | ||
}) | ||
}) |