forked from carbon-design-system/carbon
-
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.
feat(tile): added tile component (carbon-design-system#230)
* feat(tile): started work on tile * feat(tile): started * feat(tile): finished up expandable tile * feat(tile): finished tests * feat(tile): added in props and state * feat(tile): fixed index
- Loading branch information
1 parent
bba5d71
commit d2d64a2
Showing
4 changed files
with
552 additions
and
0 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,48 @@ | ||
import React from "react"; | ||
import { storiesOf, action } from "@storybook/react"; | ||
import { Tile, ClickableTile, SelectableTile, ExpandableTile, TileAboveTheFoldContent, TileBelowTheFoldContent } from "../../components/Tile"; | ||
|
||
storiesOf("Tile", module) | ||
.addWithInfo( | ||
"Default", | ||
` | ||
Default tile without any interactions | ||
`, | ||
() => | ||
<Tile>Default tile</Tile> | ||
) | ||
.addWithInfo( | ||
"Clickable", | ||
` | ||
Clickable tile | ||
`, | ||
() => | ||
<ClickableTile>Clickable Tile</ClickableTile> | ||
) | ||
.addWithInfo( | ||
"Selectable", | ||
` | ||
Selectable tile | ||
`, | ||
() => | ||
<SelectableTile id="tile-1" name="tiles">Selectable Tile</SelectableTile> | ||
) | ||
.addWithInfo( | ||
"Expandable", | ||
` | ||
Expandable tile | ||
`, | ||
() => | ||
<ExpandableTile> | ||
<TileAboveTheFoldContent> | ||
<div style={{ height: '200px' }}> | ||
Above the fold content here | ||
</div> | ||
</TileAboveTheFoldContent> | ||
<TileBelowTheFoldContent> | ||
<div style={{ height: '400px' }}> | ||
Below the fold content here | ||
</div> | ||
</TileBelowTheFoldContent> | ||
</ExpandableTile> | ||
) |
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,340 @@ | ||
import React, { Component } from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import PropTypes from 'prop-types'; | ||
import classNames from 'classnames'; | ||
import Icon from './Icon'; | ||
|
||
class Tile extends Component { | ||
static propTypes = { | ||
children: PropTypes.node, | ||
className: PropTypes.string, | ||
}; | ||
|
||
render() { | ||
const { children, className, ...other } = this.props; | ||
const tileClasses = classNames('bx--tile', className); | ||
|
||
return ( | ||
<div className={tileClasses} {...other}> | ||
{children} | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
class ClickableTile extends Component { | ||
state = { | ||
clicked: this.props.clicked, | ||
}; | ||
|
||
static propTypes = { | ||
children: PropTypes.node, | ||
className: PropTypes.string, | ||
href: PropTypes.string, | ||
}; | ||
|
||
static defaultProps = { | ||
clicked: false, | ||
handleClick: () => {}, | ||
handleKeyDown: () => {}, | ||
}; | ||
|
||
handleClick = () => { | ||
this.setState({ | ||
clicked: !this.state.clicked, | ||
}); | ||
this.props.handleClick(); | ||
}; | ||
|
||
handleKeyDown = evt => { | ||
if (evt.which === 13 || evt.which === 32) { | ||
this.setState({ | ||
clicked: !this.state.clicked, | ||
}); | ||
} | ||
this.props.handleKeyDown(); | ||
}; | ||
|
||
render() { | ||
const { | ||
children, | ||
href, | ||
className, | ||
handleClick, // eslint-disable-line | ||
handleKeyDown, // eslint-disable-line | ||
clicked, // eslint-disable-line | ||
...other | ||
} = this.props; | ||
|
||
const classes = classNames( | ||
'bx--tile', | ||
'bx--tile--clickable', | ||
{ | ||
'bx--tile--is-clicked': this.state.clicked, | ||
}, | ||
className, | ||
); | ||
|
||
return ( | ||
<a | ||
href={href} | ||
className={classes} | ||
{...other} | ||
onClick={this.handleClick} | ||
onKeyDown={this.handleKeyDown} | ||
> | ||
{children} | ||
</a> | ||
); | ||
} | ||
} | ||
|
||
class SelectableTile extends Component { | ||
state = { | ||
selected: this.props.selected, | ||
}; | ||
|
||
static propTypes = { | ||
children: PropTypes.node, | ||
className: PropTypes.string, | ||
selected: PropTypes.bool, | ||
id: PropTypes.string, | ||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, | ||
name: PropTypes.string, | ||
title: PropTypes.string, | ||
}; | ||
|
||
static defaultProps = { | ||
value: 'value', | ||
title: 'title', | ||
selected: false, | ||
handleClick: () => {}, | ||
handleKeyDown: () => {}, | ||
}; | ||
|
||
handleClick = evt => { | ||
const isInput = evt.target === this.input; | ||
if (!isInput) { | ||
this.setState({ | ||
selected: !this.state.selected, | ||
}); | ||
} | ||
this.props.handleClick(); | ||
}; | ||
|
||
handleKeyDown = evt => { | ||
if (evt.which === 13 || evt.which === 32) { | ||
this.setState({ | ||
selected: !this.state.selected, | ||
}); | ||
} | ||
this.props.handleKeyDown(); | ||
}; | ||
|
||
render() { | ||
const { | ||
children, | ||
id, | ||
tabIndex, | ||
value, | ||
name, | ||
title, | ||
className, | ||
handleClick, // eslint-disable-line | ||
handleKeyDown, // eslint-disable-line | ||
...other | ||
} = this.props; | ||
|
||
const classes = classNames( | ||
'bx--tile', | ||
'bx--tile--selectable', | ||
{ | ||
'bx--tile--is-selected': this.state.selected, | ||
}, | ||
className, | ||
); | ||
|
||
return ( | ||
<label | ||
htmlFor={id} | ||
className={classes} | ||
tabIndex={tabIndex} | ||
{...other} | ||
onClick={this.handleClick} | ||
onKeyDown={this.handleKeyDown} | ||
> | ||
<input | ||
ref={input => { | ||
this.input = input; | ||
}} | ||
tabIndex={-1} | ||
id={id} | ||
className="bx--tile-input" | ||
value={value} | ||
type="checkbox" | ||
name={name} | ||
title={title} | ||
checked={this.state.selected} | ||
/> | ||
<div className="bx--tile__checkmark"> | ||
<Icon name="checkmark--glyph" description="Tile checkmark" /> | ||
</div> | ||
<div className="bx--tile-content"> | ||
{children} | ||
</div> | ||
</label> | ||
); | ||
} | ||
} | ||
|
||
class ExpandableTile extends Component { | ||
state = { | ||
expanded: this.props.expanded, | ||
tileMaxHeight: this.props.tileMaxHeight, | ||
}; | ||
|
||
static propTypes = { | ||
children: PropTypes.node, | ||
className: PropTypes.string, | ||
expanded: PropTypes.bool, | ||
tabIndex: PropTypes.number, | ||
}; | ||
|
||
static defaultProps = { | ||
tabIndex: 0, | ||
expanded: false, | ||
tileMaxHeight: '0', | ||
handleClick: () => {}, | ||
}; | ||
|
||
componentDidMount = () => { | ||
if (this.refs[0]) { | ||
this.aboveTheFold = ReactDOM.findDOMNode(this.refs[0]); // eslint-disable-line | ||
} | ||
this.setState({ | ||
tileMaxHeight: this.aboveTheFold.getBoundingClientRect().height, | ||
}); | ||
}; | ||
|
||
setMaxHeight = () => { | ||
if (this.state.expanded) { | ||
this.setState({ | ||
tileMaxHeight: this.tileContent.getBoundingClientRect().height, | ||
}); | ||
} else { | ||
this.setState({ | ||
tileMaxHeight: this.aboveTheFold.getBoundingClientRect().height, | ||
}); | ||
} | ||
}; | ||
|
||
handleClick = () => { | ||
this.setState( | ||
{ | ||
expanded: !this.state.expanded, | ||
}, | ||
() => { | ||
this.setMaxHeight(); | ||
}, | ||
); | ||
this.props.handleClick(); | ||
}; | ||
|
||
getChildren = () => { | ||
return React.Children.map(this.props.children, child => child); | ||
}; | ||
|
||
render() { | ||
const { | ||
tabIndex, | ||
className, | ||
tileMaxHeight, // eslint-disable-line | ||
handleClick, // eslint-disable-line | ||
expanded, // eslint-disable-line | ||
...other | ||
} = this.props; | ||
|
||
const classes = classNames( | ||
'bx--tile', | ||
'bx--tile--expandable', | ||
{ | ||
'bx--tile--is-expanded': this.state.expanded, | ||
}, | ||
className, | ||
); | ||
const tileStyle = { | ||
maxHeight: this.state.tileMaxHeight, | ||
}; | ||
const content = this.getChildren().map((child, index) => { | ||
return React.cloneElement(child, { ref: index }); | ||
}); | ||
return ( | ||
<div | ||
ref={tile => { | ||
this.tile = tile; | ||
}} | ||
style={tileStyle} | ||
className={classes} | ||
{...other} | ||
role="button" | ||
onClick={this.handleClick} | ||
tabIndex={tabIndex} | ||
> | ||
<button className="bx--tile__chevron"> | ||
<Icon name="chevron--down" description="Tile chevron" /> | ||
</button> | ||
<div | ||
ref={tileContent => { | ||
this.tileContent = tileContent; | ||
}} | ||
className="bx--tile-content" | ||
> | ||
{content} | ||
</div> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
class TileAboveTheFoldContent extends Component { | ||
static propTypes = { | ||
children: PropTypes.node, | ||
className: PropTypes.string, | ||
}; | ||
|
||
render() { | ||
const { children } = this.props; | ||
|
||
return ( | ||
<span className="bx--tile-content__above-the-fold"> | ||
{children} | ||
</span> | ||
); | ||
} | ||
} | ||
|
||
class TileBelowTheFoldContent extends Component { | ||
static propTypes = { | ||
children: PropTypes.node, | ||
className: PropTypes.string, | ||
}; | ||
|
||
render() { | ||
const { children } = this.props; | ||
|
||
return ( | ||
<span className="bx--tile-content__below-the-fold"> | ||
{children} | ||
</span> | ||
); | ||
} | ||
} | ||
|
||
export { | ||
Tile, | ||
ClickableTile, | ||
SelectableTile, | ||
ExpandableTile, | ||
TileAboveTheFoldContent, | ||
TileBelowTheFoldContent, | ||
}; |
Oops, something went wrong.