Skip to content

Commit 85fdffb

Browse files
Brian Stonestonebk
Brian Stone
authored andcommitted
feat: add Toast, ToastProvider, CloseButton, and AnimatedList components
1 parent e9f548f commit 85fdffb

25 files changed

+2364
-27
lines changed

.eslintrc

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
{
2+
"env": {
3+
"browser": true,
4+
},
25
"extends": ["plugin:zillow/recommended", "plugin:zillow/jest"]
36
}

package-lock.json

+46-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@
3535
}
3636
},
3737
"dependencies": {
38-
"prop-types": "^15.7.2"
38+
"prop-types": "^15.7.2",
39+
"react-spring": "^8.0.27"
3940
},
4041
"peerDependencies": {
4142
"react": "^16.8.0",
43+
"react-dom": "^16.8.0",
4244
"styled-components": ">=4.1.2"
4345
},
4446
"devDependencies": {
@@ -51,6 +53,7 @@
5153
"glob": "^7.1.4",
5254
"husky": "^3.0.2",
5355
"jest-styled-components": "^6.3.1",
56+
"lolex": "^4.1.0",
5457
"react": "^16.8.6",
5558
"react-dom": "^16.8.6",
5659
"react-test-renderer": "^16.8.6",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import styled from 'styled-components';
4+
import { animated, Transition } from 'react-spring/renderprops.cjs';
5+
import getTheme from '../../theme/getTheme';
6+
7+
const Item = styled(animated.li)`
8+
${getTheme('AnimatedListItem')}
9+
`;
10+
11+
/**
12+
* An `AnimatedList` provides a default animation for addition and removal of items in a list.
13+
* The animation is provided by the [react-spring](https://www.react-spring.io/) library.
14+
*
15+
* ##### Browser Support
16+
* react-spring [requires polyfills](https://github.com/vai0/react-spring/blob/f579b20709/documentation/Gotchas.mdx#browser-support)
17+
* for Internet Explorer -- if those polyfills are not detected, the list will not animate.
18+
*/
19+
const AnimatedList = styled(({ children, enter, from, leave, ...rest }) => {
20+
// feature detect for react-spring compatability, otherwise render static items
21+
let content;
22+
if (Object.entries) {
23+
content = (
24+
<Transition
25+
native
26+
keys={child => child.key || child}
27+
items={children}
28+
from={from}
29+
enter={enter}
30+
leave={leave}
31+
>
32+
{child => style => <Item style={style}>{child}</Item>}
33+
</Transition>
34+
);
35+
} else {
36+
content = React.Children.map(children, child => <Item>{child}</Item>);
37+
}
38+
39+
return <ul {...rest}>{content}</ul>;
40+
})`
41+
${getTheme('AnimatedList')}
42+
`;
43+
44+
AnimatedList.propTypes = {
45+
/**
46+
* A dynamic list of items. Each item will be wrapped by an `<AnimatedList.Item>` component.
47+
* By default, the content of each item will be used as the key. For non-trivial lists, you will
48+
* want to add a `key` attribute to each item.
49+
*/
50+
children: PropTypes.node,
51+
/**
52+
* The react-spring [`Transition`](https://www.react-spring.io/docs/props/transition) `enter` prop.
53+
*/
54+
// eslint-disable-next-line zillow/react/forbid-prop-types
55+
enter: PropTypes.any,
56+
/**
57+
* The react-spring [`Transition`](https://www.react-spring.io/docs/props/transition) `from` prop.
58+
*/
59+
// eslint-disable-next-line zillow/react/forbid-prop-types
60+
from: PropTypes.any,
61+
/**
62+
* The react-spring [`Transition`](https://www.react-spring.io/docs/props/transition) `leave` prop.
63+
*/
64+
// eslint-disable-next-line zillow/react/forbid-prop-types
65+
leave: PropTypes.any,
66+
};
67+
68+
AnimatedList.defaultProps = {
69+
enter: { opacity: 1, height: 'auto' },
70+
from: { opacity: 0, height: 0 },
71+
leave: { opacity: 0, height: 0 },
72+
};
73+
74+
AnimatedList.Item = Item;
75+
76+
/** @component */
77+
export default AnimatedList;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
```jsx
2+
import { Button } from '../../index';
3+
4+
initialState = {
5+
items: []
6+
};
7+
8+
const onAddClick = () => {
9+
setState({
10+
items: [ ...state.items, Math.random() ]
11+
});
12+
};
13+
14+
const onRemoveClick = () => {
15+
setState({ items: state.items.slice(1) });
16+
};
17+
18+
<React.Fragment>
19+
<Button onClick={onAddClick}>Add</Button>{' '}
20+
<Button onClick={onRemoveClick}>Remove</Button>
21+
<AnimatedList>
22+
{state.items}
23+
</AnimatedList>
24+
</React.Fragment>
25+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react';
2+
import TestRenderer from 'react-test-renderer';
3+
import { ThemeProvider, css, isStyledComponent } from 'styled-components';
4+
import { AnimatedList } from '../../../index';
5+
import { createTestTheme } from '../../../test/util';
6+
7+
describe('<AnimatedList>', () => {
8+
it('can render without theme', () => {
9+
const testRenderer = TestRenderer.create(
10+
<AnimatedList>{['one', 'two', 'three']}</AnimatedList>
11+
);
12+
expect(testRenderer.toJSON()).toMatchSnapshot();
13+
});
14+
15+
describe('without Object.entries', () => {
16+
const { entries } = global.Object;
17+
beforeEach(() => {
18+
delete global.Object.entries;
19+
});
20+
afterEach(() => {
21+
global.Object.entries = entries;
22+
});
23+
it('renders a static list when react-spring is not supported', () => {
24+
const testRenderer = TestRenderer.create(
25+
<AnimatedList>{['one', 'two', 'three']}</AnimatedList>
26+
);
27+
expect(testRenderer.toJSON()).toMatchSnapshot();
28+
});
29+
});
30+
31+
it('can render with theme', () => {
32+
const theme = createTestTheme({
33+
AnimatedList: css`
34+
list-style: none;
35+
`,
36+
});
37+
const testRenderer = TestRenderer.create(
38+
<ThemeProvider theme={theme}>
39+
<AnimatedList>{['one', 'two', 'three']}</AnimatedList>
40+
</ThemeProvider>
41+
);
42+
const tree = testRenderer.toJSON();
43+
expect(tree).toHaveStyleRule('list-style', 'none');
44+
expect(tree).toMatchSnapshot();
45+
});
46+
47+
it('is a styled-component', () => {
48+
expect(isStyledComponent(AnimatedList)).toBe(true);
49+
});
50+
});

0 commit comments

Comments
 (0)