Skip to content

Commit

Permalink
Merge pull request #167 from dabapps/collapse-component
Browse files Browse the repository at this point in the history
Collapse component
  • Loading branch information
JakeSidSmith authored Dec 11, 2017
2 parents ac8f461 + 8454ad4 commit b6bbedd
Show file tree
Hide file tree
Showing 10 changed files with 1,116 additions and 41 deletions.
18 changes: 16 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "roe",
"version": "0.8.24",
"version": "0.8.25",
"description": "A Collection of React Components for Project Development",
"main": "dist/js/index.js",
"types": "dist/js/index.d.ts",
Expand Down Expand Up @@ -55,13 +55,18 @@
"typescript": "2.3.3"
},
"devDependencies": {
"@types/enzyme": "3.1.5",
"@types/enzyme-adapter-react-15": "1.0.1",
"@types/jest": "19.2.3",
"@types/node": "7.0.13",
"@types/react-test-renderer": "15.4.5",
"brfs": "1.4.3",
"concurrently": "3.4.0",
"css-loader": "0.28.7",
"envify": "4.0.0",
"enzyme": "3.2.0",
"enzyme-adapter-react-15": "1.0.5",
"enzyme-to-json": "3.2.2",
"http-server": "0.9.0",
"jest": "20.0.3",
"less-loader": "4.0.5",
Expand All @@ -87,6 +92,9 @@
"typescript": ">= 2.3.3 < 3"
},
"jest": {
"setupFiles": [
"<rootDir>/tests/helpers/setup.ts"
],
"coverageThreshold": {
"global": {
"branches": 100,
Expand All @@ -99,12 +107,18 @@
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/tests/.*|\\.(test|spec))\\.(ts|tsx|js|jsx)$",
"testPathIgnorePatterns": [
"<rootDir>/tests/helpers/"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx"
],
"mapCoverage": true
"mapCoverage": true,
"snapshotSerializers": [
"enzyme-to-json/serializer"
]
}
}
2 changes: 1 addition & 1 deletion scripts/dist
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ rm -rf dist/

mkdir -p dist/js/

tsc --sourceMap --declaration --listFiles --project 'src/ts/tsconfig.json' --rootDir 'src/ts/' --outDir 'dist/js/' --module 'CommonJS'
tsc --project 'src/ts/tsconfig.json'
47 changes: 47 additions & 0 deletions src/ts/components/collapse.examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#### Examples

```js
class CollapseExample extends React.Component {
constructor (props) {
super(props);

this.state = {
open: false
};

this.onClickToggleCollapse = this.onClickToggleCollapse.bind(this);
}

onClickToggleCollapse () {
const { open } = this.state;

this.setState({
open: !open
});
}

render () {
const { open } = this.state;

return (
<div>
<Collapse
open={open}
maxCollapsedHeight={100}
fadeOut
fadeColor="#FFF"
fadeHeight={50}
animationDuration={200}
>
<DabIpsum count={10} />
</Collapse>
<Button className="primary margin-top-base" onClick={this.onClickToggleCollapse}>
{open ? 'Collapse' : 'Expand'}
</Button>
</div>
);
}
}

<CollapseExample />
```
164 changes: 164 additions & 0 deletions src/ts/components/collapse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import * as classNames from 'classnames';
import * as React from 'react';
import { ComponentProps } from '../types';

const ENOUGH_TIME_FOR_RERENDER = 50;
const DEFAULT_HEIGHT = 0;
const DEFAULT_DURATION = 200;
const DEFAULT_FADE_HEIGHT = 50;
const DEFAULT_FADE_COLOR = '#FFF';

export interface CollapseProps extends ComponentProps, React.HTMLAttributes<HTMLDivElement> {
/**
* Whether the collapse is open or not
* @default false
*/
open: boolean;
/**
* Duration of the animation (milliseconds)
* @default 200
*/
animationDuration?: number;
/**
* Maximum height when collapsed
* @default 0
*/
maxCollapsedHeight?: number;
/**
* Whether to fade out the content
* @default false
*/
fadeOut?: boolean;
/**
* Color to fade to
* @default white
*/
fadeColor?: string;
/**
* Height of the faded area
* @default 50
*/
fadeHeight?: number;
}

export interface CollapseState { // tslint:disable-line:no-unused-variable
height: number;
opened: boolean;
opening: boolean;
}

/**
* Component to expand and collapse content, optionally displaying a small preview.
*/
export class Collapse extends React.PureComponent<CollapseProps, CollapseState> {
private element: Element;
private timeout: number;

public constructor (props: CollapseProps) {
super(props);

const { maxCollapsedHeight = DEFAULT_HEIGHT, open } = props;

this.state = {
height: maxCollapsedHeight,
opening: false,
opened: open
};
}

public componentDidUpdate (previousProps: CollapseProps) {
if (this.props.open !== previousProps.open) {
window.clearTimeout(this.timeout);

const { maxCollapsedHeight = DEFAULT_HEIGHT, animationDuration = DEFAULT_DURATION } = this.props;

this.setState({
opened: false,
opening: previousProps.open,
height: this.props.open ? maxCollapsedHeight : this.element.scrollHeight
});

this.timeout = window.setTimeout(() => {
this.setState({
opened: false,
opening: this.props.open,
height: this.props.open ? this.element.scrollHeight : maxCollapsedHeight
});

this.timeout = window.setTimeout(() => {
this.setState({
opened: this.props.open,
opening: this.props.open
});
}, animationDuration);
}, ENOUGH_TIME_FOR_RERENDER);
}
}

public componentDidMount () {
const { maxCollapsedHeight = DEFAULT_HEIGHT } = this.props;

this.setState({
height: this.props.open ? this.element.scrollHeight : maxCollapsedHeight
});
}

public componentWillMount () {
window.clearTimeout(this.timeout);
}

public render () {
const {
children,
className,
fadeOut,
fadeColor = DEFAULT_FADE_COLOR,
fadeHeight = DEFAULT_FADE_HEIGHT,
open,
maxCollapsedHeight,
animationDuration = DEFAULT_DURATION,
component: Component = 'div',
...remainingProps
} = this.props;

const { opening, opened, height } = this.state;

const collapseStyle = {
height: opened ? 'auto' : height,
position: 'relative' as 'relative',
overflow: 'hidden' as 'hidden',
transition: `ease-in-out ${animationDuration}ms height`
};

const fadeStyle = {
height: fadeHeight,
width: '100%',
position: 'absolute' as 'absolute',
bottom: 0,
opacity: opening ? 0 : 1,
background: `linear-gradient(transparent, ${fadeColor} 80%)`,
transition: `ease-in-out ${animationDuration}ms opacity`
};

return (
<Component
ref={(element: HTMLDivElement) => this.element = element}
{...remainingProps}
className={classNames('clearfix collapse', open ? 'collapse-open' : null, className)}
style={collapseStyle}
>
{children}
{
fadeOut && !opened && (
<div
className="collapse-fade"
style={fadeStyle}
/>
)
}
</Component>
);
}
}

export default Collapse;
63 changes: 32 additions & 31 deletions src/ts/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
export { Alert } from './components/alert';
export { Anchor } from './components/anchor';
export { Button } from './components/forms/button';
export { CodeBlock } from './components/code-block';
export { Column } from './components/grid/column';
export { Container } from './components/grid/container';
export { ContentBox } from './components/content/content-box';
export { ContentBoxHeader } from './components/content/content-box-header';
export { ContentBoxFooter } from './components/content/content-box-footer';
export { DabIpsum } from './components/prototyping/dab-ipsum';
export { FormGroup } from './components/forms/form-group';
export { InputGroup } from './components/forms/input-group';
export { InputGroupAddon } from './components/forms/input-group-addon';
export { Modal } from './components/modals/modal';
export { ModalBody } from './components/modals/modal-body';
export { ModalCloseIcon } from './components/modals/modal-close-icon';
export { ModalFooter } from './components/modals/modal-footer';
export { ModalHeader } from './components/modals/modal-header';
export { ModalRenderer } from './components/modals/modal-renderer';
export { Row } from './components/grid/row';
export { Section } from './components/content/section';
export { SpacedGroup } from './components/spaced-group';
export { Tabs } from './components/tabs/tabs';
export { Tab } from './components/tabs/tab';
export { Table } from './components/tables/table';
export { TableBody } from './components/tables/table-body';
export { TableCell } from './components/tables/table-cell';
export { TableHead } from './components/tables/table-head';
export { TableHeader } from './components/tables/table-header';
export { TableRow } from './components/tables/table-row';
export { Well } from './components/well';
export { default as Alert } from './components/alert';
export { default as Anchor } from './components/anchor';
export { default as Button } from './components/forms/button';
export { default as CodeBlock } from './components/code-block';
export { default as Collapse } from './components/collapse';
export { default as Column } from './components/grid/column';
export { default as Container } from './components/grid/container';
export { default as ContentBox } from './components/content/content-box';
export { default as ContentBoxHeader } from './components/content/content-box-header';
export { default as ContentBoxFooter } from './components/content/content-box-footer';
export { default as DabIpsum } from './components/prototyping/dab-ipsum';
export { default as FormGroup } from './components/forms/form-group';
export { default as InputGroup } from './components/forms/input-group';
export { default as InputGroupAddon } from './components/forms/input-group-addon';
export { default as Modal } from './components/modals/modal';
export { default as ModalBody } from './components/modals/modal-body';
export { default as ModalCloseIcon } from './components/modals/modal-close-icon';
export { default as ModalFooter } from './components/modals/modal-footer';
export { default as ModalHeader } from './components/modals/modal-header';
export { default as ModalRenderer } from './components/modals/modal-renderer';
export { default as Row } from './components/grid/row';
export { default as Section } from './components/content/section';
export { default as SpacedGroup } from './components/spaced-group';
export { default as Tabs } from './components/tabs/tabs';
export { default as Tab } from './components/tabs/tab';
export { default as Table } from './components/tables/table';
export { default as TableBody } from './components/tables/table-body';
export { default as TableCell } from './components/tables/table-cell';
export { default as TableHead } from './components/tables/table-head';
export { default as TableHeader } from './components/tables/table-header';
export { default as TableRow } from './components/tables/table-row';
export { default as Well } from './components/well';
16 changes: 12 additions & 4 deletions src/ts/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@
"jsx": "react",
"target": "es5",
"typeRoots": [
"../../node_modules/@types/",
"../../types/"
]
}
"../../node_modules/@types/*",
"../../types/*"
],
"declaration": true,
"sourceMap": true,
"listFiles": true,
"rootDir": "./",
"outDir": "../../dist/"
},
"exclude": [
"../../node_modules/@types/enzyme/*"
]
}
Loading

0 comments on commit b6bbedd

Please sign in to comment.