Skip to content

Commit

Permalink
Merge pull request #57 from revelrylabs/f-expanding-column
Browse files Browse the repository at this point in the history
Add expanding column component
  • Loading branch information
jwietelmann authored Dec 19, 2016
2 parents ce1ee05 + 3fa1aaa commit 70ff2a0
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 6 deletions.
19 changes: 13 additions & 6 deletions docs-src/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ function genJS(items) {
browserify(jsSourceFile, {debug: true})
.transform('babelify')
.bundle()
.on('end', () => fs.unlinkSync(jsSourceFile))
.on('end', () => {
try {
fs.unlinkSync(jsSourceFile)
} catch(err) {
console.log(err);
}
})
.pipe(fs.createWriteStream(path.join(__dirname, '..', 'docs', 'bundle.js')))
}

Expand Down Expand Up @@ -83,13 +89,14 @@ function genStyles(cb) {
path.join(__dirname, '../scss'),
],
sourceMap: true,
}, cb || function(err, {css}) {
}, cb || function(err, results) {
if(err) {
console.log(error.status)
console.log(error.column)
console.log(error.message)
console.log(error.line)
console.log(err.status)
console.log(err.column)
console.log(err.message)
console.log(err.line)
} else {
let {css} = results
fs.writeFile(CSS_OUTPUT_FILE, css, cb)
}
})
Expand Down
1 change: 1 addition & 0 deletions docs-src/site.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ $card-radius: 8px;
@import '../scss/Modal';
@import '../scss/CardLayout';
@import '../scss/Card';
@import '../scss/Expander';
@import '../scss/FileInput';
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"enzyme": "^2.4.1",
"gulp": "^3.9.1",
"gulp-webserver": "^0.9.1",
"jsdom": "9.8.3",
"jsdom-global": "2.1.0",
"mocha": "^3.1.2",
"node-dir": "^0.1.16",
"react-addons-test-utils": ">=0.14.8"
Expand Down
76 changes: 76 additions & 0 deletions scss/Expander.scss
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);
}
}
49 changes: 49 additions & 0 deletions src/ExpandingCol.example.js
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>
)
}
}
84 changes: 84 additions & 0 deletions src/ExpandingCol.js
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>
)
}
}
37 changes: 37 additions & 0 deletions src/ExpandingCol.test.js
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)
})
})

0 comments on commit 70ff2a0

Please sign in to comment.