`component`
7 | const props = {
8 | meta: {},
9 | input: {
10 | label: 'Number of internets',
11 | name: 'internets',
12 | value: 10,
13 | },
14 | };
15 |
16 | it('renders a NumberInput component with expected attributes from mock data', () => {
17 | const component = shallow();
18 | expect(component).toMatchSnapshot();
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/src/forms/redux-form/selectInput.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import ReduxFormSelectInput from './SelectInput';
4 |
5 | describe('redux-form SelectInput', () => {
6 | const testOptions = [
7 | { label: 'One', value: '1' },
8 | { label: 'Two', value: '2' },
9 | { label: 'Three', value: '3' },
10 | ];
11 | const formAttrs = {
12 | required: true,
13 | meta: {
14 | touched: false,
15 | error: 'Did you mean Batman and Robin?',
16 | },
17 | input: {
18 | label: 'Countries',
19 | name: 'formSelectCountries',
20 | options: testOptions,
21 | },
22 | };
23 |
24 | it('renders a SelectInput component with expected attributes from mock data', () => {
25 | const component = shallow();
26 | expect(component).toMatchSnapshot();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/forms/redux-form/textInput.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import ReduxFormTextInput from './TextInput';
4 |
5 | describe('redux-form TextInput', function() {
6 | const MOCK_ERROR = 'Did you mean Batman and Robin?';
7 | const formAttrs = {
8 | input: {
9 | label: 'Super Hero',
10 | name: 'superhero',
11 | value: 'Wonder Woman and Robin',
12 | maxLength: 20,
13 | required: true,
14 | },
15 | meta: {
16 | error: MOCK_ERROR,
17 | },
18 | };
19 |
20 | it('renders a TextInput component with expected attributes from mock data', () => {
21 | const component = shallow();
22 | expect(component).toMatchSnapshot();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/forms/redux-form/textarea.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import ReduxFormTextarea from './Textarea';
4 |
5 | describe('redux-form Textarea', () => {
6 | const formAttrs = {
7 | input: {
8 | id: 'heroField',
9 | label: 'Super Hero',
10 | name: 'superhero',
11 | value: 'Wonder Woman and Robin',
12 | maxLength: 20,
13 | required: true,
14 | },
15 | meta: {
16 | error: 'Did you mean Batman and Robin?',
17 | },
18 | };
19 |
20 | it('renders a Textarea component with expected attributes from mock data', () => {
21 | const component = shallow();
22 | expect(component).toMatchSnapshot();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/forms/redux-form/timeInput.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import ReduxFormTimeInput from './TimeInput';
4 |
5 | describe('redux-form TimeInput', function() {
6 | // props given in the structure that
7 | // redux form would
8 | const reduxFormProps = {
9 | input: {
10 | label: 'What time is it?',
11 | name: 'partytime',
12 | value: '22:00',
13 | required: true,
14 | },
15 | meta: {
16 | error: 'Now approaching midnight!!?',
17 | },
18 | };
19 |
20 | it('renders a TimeInput component with expected attributes from mock data', () => {
21 | const component = shallow();
22 |
23 | expect(component).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/forms/redux-form/togglePill.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow, mount } from 'enzyme';
3 | import ReduxFormTogglePill from './TogglePill';
4 | import TogglePill from '../TogglePill';
5 |
6 | describe('redux-form TogglePill', function() {
7 | // props given in the structure that
8 | // redux form would
9 | const togglePillProps = {
10 | input: {
11 | id: 'parenting',
12 | label: 'Parenting',
13 | name: 'parenting',
14 | children: 'Parenting!',
15 | value: false, // as a checkbox, redux form will pass true / false for values
16 | },
17 | };
18 |
19 | it('renders a TogglePill component with expected attributes from mock data', () => {
20 | const component = shallow();
21 | expect(component).toMatchSnapshot();
22 | });
23 |
24 | it('renders a TogglePill with isActive prop as false when value is false', () => {
25 | const component = mount();
26 | expect(component.find(TogglePill).prop('isActive')).toBe(false);
27 | });
28 |
29 | it('renders a TogglePill with isActive prop as true when value is true', () => {
30 | togglePillProps.input.value = true;
31 | const component = mount();
32 | expect(component.find(TogglePill).prop('isActive')).toBe(true);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/src/forms/select.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import { SelectInput } from './Select';
4 |
5 | const testOptions = (
6 |
7 | ,
8 | ,
9 | ,
10 | ,
13 |
14 | );
15 |
16 | const BasicSelect = (
17 |
18 | {testOptions}
19 |
20 | );
21 | describe('SelectInput basic', () => {
22 | const component = shallow(BasicSelect);
23 |
24 | it('renders into the DOM', () => {
25 | expect(component).toMatchSnapshot();
26 | });
27 | });
28 |
29 | describe('A11y class pass through on labelClassName', () => {
30 | it('should hide the label when a11yHide is passed as a label class', () => {
31 | const component = shallow(
32 |
38 | {testOptions}
39 |
40 | );
41 |
42 | expect(component).toMatchSnapshot();
43 | });
44 |
45 | it('should only pass the a11y class', () => {
46 | const component = shallow(
47 |
53 | {testOptions}
54 |
55 | );
56 |
57 | expect(component).toMatchSnapshot();
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/src/forms/timeInput.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import TimeInput from './TimeInput';
4 |
5 | describe('TimeInput', function() {
6 | const props = {
7 | name: 'time',
8 | value: '11:15',
9 | onChange: () => {},
10 | required: true,
11 | };
12 |
13 | describe('TimeInput, with input[time] support', () => {
14 | it('renders a time html input with expected props', function() {
15 | expect(shallow()).toMatchSnapshot();
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/src/forms/togglePill.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'enzyme';
3 | import TogglePill from './TogglePill';
4 |
5 | describe('TogglePill', () => {
6 | const id = 'hikingCategory',
7 | name = 'meetupCategories',
8 | label = 'Hiking!',
9 | value = 'hiking';
10 |
11 | let togglePillComponent;
12 | const onChangeMock = jest.fn();
13 |
14 | beforeEach(() => {
15 | togglePillComponent = mount(
16 |
17 | {label}
18 |
19 | );
20 | });
21 |
22 | afterEach(() => {
23 | togglePillComponent = null;
24 | });
25 |
26 | it('renders a component with expected attributes', () => {
27 | expect(togglePillComponent).toMatchSnapshot();
28 | });
29 |
30 | it('executes onChange when clicked', () => {
31 | expect(onChangeMock).not.toHaveBeenCalled();
32 | togglePillComponent.simulate('click');
33 | expect(onChangeMock).toHaveBeenCalled();
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/src/forms/toggleSwitch.story.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 |
4 | import { decorateWithBasics, decorateWithInfo } from '../utils/decorators';
5 |
6 | import Chunk from '../layout/Chunk';
7 | import Flex from '../layout/Flex';
8 | import FlexItem from '../layout/FlexItem';
9 | import ToggleSwitch from './ToggleSwitch';
10 |
11 | storiesOf('Forms/ToggleSwitch', module)
12 | .addDecorator(decorateWithBasics)
13 | .addDecorator(decorateWithInfo)
14 | .add('Default', () => )
15 | .add('Checked', () => )
16 | .add('With label', () => )
17 | .add('With label outside of component', () => (
18 |
19 | -
20 |
21 |
22 |
23 | Is this thing on?
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | -
32 |
33 |
34 |
35 | And this one?
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ))
45 | .add('Disabled', () => );
46 |
--------------------------------------------------------------------------------
/src/forms/togglepill.story.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf, action } from '@storybook/react';
3 | import { decorateWithBasics, decorateWithInfo } from '../utils/decorators';
4 | import TogglePill from './TogglePill';
5 |
6 | const onChange = e => {
7 | action(`The value of the Toggle Pill clicked is: ${e.target.value}`)(e);
8 | };
9 |
10 | storiesOf('Forms/TogglePill', module)
11 | .addDecorator(decorateWithBasics)
12 | .addDecorator(decorateWithInfo)
13 | .addParameters({ info: { propTables: [TogglePill] } })
14 | .add('default', () => (
15 |
16 |
22 | Toggle Pill Label
23 |
24 |
25 | ))
26 | .add('Default Selected', () => (
27 |
34 | Toggle Pill Label
35 |
36 | ))
37 | .add('reset', () => (
38 |
39 |
46 | Toggle Pill Label
47 |
48 |
49 | ));
50 |
--------------------------------------------------------------------------------
/src/hscroll.story.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { decorateWithBasics, decorateWithInfo } from './utils/decorators';
4 | import { MEDIA_SIZES } from './utils/designConstants';
5 | import Hscroll from './Hscroll';
6 |
7 | const itemStyle = {
8 | display: 'inline-block',
9 | background: 'cornsilk',
10 | width: `${MEDIA_SIZES.xxl}px`,
11 | height: `${MEDIA_SIZES.xl}px`,
12 | outline: '1px dotted red',
13 | padding: `${MEDIA_SIZES.xs}px`,
14 | };
15 |
16 | const hscrollInlineStyle = {
17 | width: '50vw',
18 | };
19 |
20 | const listItems = [
21 | inline-block item
,
22 | inline-block item
,
23 | inline-block item
,
24 | inline-block item
,
25 | inline-block item
,
26 | inline-block item
,
27 | inline-block item
,
28 | inline-block item
,
29 | inline-block item
,
30 | ];
31 |
32 | storiesOf('Uncategorized/Hscroll', module)
33 | .addDecorator(decorateWithBasics)
34 | .addDecorator(decorateWithInfo)
35 | .add('Default', () => {listItems}, {
36 | info: { text: 'Basic horizontal scroll usage' },
37 | })
38 | .add(
39 | 'With gradient',
40 | () => (
41 |
42 | {listItems}
43 |
44 | ),
45 | { info: { text: 'Basic horizontal scroll with edge gradients' } }
46 | )
47 | .add(
48 | 'Media-conditional unclip',
49 | () => (
50 |
51 | {listItems}
52 |
53 | ),
54 | { info: { text: 'Unclip at a given breakpoint (large)' } }
55 | );
56 |
--------------------------------------------------------------------------------
/src/interactive/InfoToggle.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import Button from '../forms/Button';
5 | import Tooltip from './Tooltip';
6 |
7 | export const InfoTooltipTrigger = props => (
8 |
24 | );
25 |
26 | const InfoToggle = ({
27 | className,
28 | label,
29 | tooltipId,
30 | tooltipProps,
31 | tooltipContent,
32 | onClick,
33 | ...other
34 | }) => {
35 | return (
36 |
37 | {label}
38 | }
41 | content={tooltipContent}
42 | {...tooltipProps}
43 | />
44 |
45 | );
46 | };
47 |
48 | InfoToggle.defaultProps = {
49 | tooltipProps: {
50 | align: 'right',
51 | },
52 | };
53 |
54 | InfoToggle.propTypes = {
55 | /** The content that's rendered inside the tooltip's content bubble */
56 | tooltipContent: PropTypes.element,
57 |
58 | /** The label rendered next to the Tooltip's trigger */
59 | label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
60 |
61 | /** The unique identifier for the Tooltip */
62 | tooltipId: PropTypes.string.isRequired,
63 |
64 | /** Props to pass to the Tooltip component */
65 | tooltipProps: PropTypes.object,
66 | };
67 |
68 | export default InfoToggle;
69 |
--------------------------------------------------------------------------------
/src/interactive/__snapshots__/toast.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Toast renders into the DOM 1`] = `
4 |
10 |
14 |
20 |
27 | Your toast is ready
28 |
29 |
30 |
31 |
32 | `;
33 |
--------------------------------------------------------------------------------
/src/layout/Bounds.jsx:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React from 'react';
4 | import cx from 'classnames';
5 |
6 | import withLoading from '../utils/components/withLoading';
7 |
8 | export const BOUNDS_CLASS = 'bounds';
9 |
10 | type Props = {
11 | /** The child elements of the component */
12 | children: React$Node,
13 | className?: string,
14 | /** Whether the bounds max-width should use the narrow variant */
15 | narrow?: boolean,
16 | /** Props to pass to the `` component */
17 | loadingProps?: {
18 | color?: string,
19 | scrimColor?: string,
20 | size?: MediaSizes,
21 | },
22 | /** Whether the component is in a loading state */
23 | isLoading?: boolean,
24 | };
25 |
26 | /**
27 | * Design System Component: Provides `bounds` container for components
28 | * @module BoundsComponent
29 | */
30 |
31 | export class BoundsComponent extends React.PureComponent {
32 | render() {
33 | const {
34 | children,
35 | className,
36 | narrow,
37 | loadingProps = {}, // eslint-disable-line no-unused-vars
38 | isLoading,
39 | ...other
40 | } = this.props;
41 |
42 | const classNames = cx(
43 | BOUNDS_CLASS,
44 | {
45 | 'bounds--wide': !narrow,
46 | 'component--isLoading': isLoading,
47 | },
48 | className
49 | );
50 |
51 | return (
52 |
53 | {children}
54 |
55 | );
56 | }
57 | }
58 |
59 | const Bounds = withLoading(BoundsComponent);
60 | Bounds.displayName = 'Bounds';
61 | export default Bounds;
62 |
--------------------------------------------------------------------------------
/src/layout/Chunk.jsx:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React from 'react';
4 | import cx from 'classnames';
5 |
6 | import withLoading from '../utils/components/withLoading';
7 |
8 | type Props = {|
9 | /** The child elements of the component */
10 | children: React$Node,
11 | className?: string,
12 | /** Whether the component is in a loading state */
13 | isLoading?: boolean,
14 | /** Props to pass to the `` component */
15 | loadingProps?: {
16 | color?: string,
17 | scrimColor?: string,
18 | size?: MediaSizes,
19 | },
20 | |};
21 | /**
22 | * Design System Component: Provides `stripe` styled container for components
23 | * @module ChunkComponent
24 | */
25 | export class ChunkComponent extends React.Component {
26 | render() {
27 | const {
28 | children,
29 | className,
30 | loadingProps = {}, // eslint-disable-line no-unused-vars
31 | isLoading,
32 | ...other
33 | } = this.props;
34 |
35 | const classNames = cx('chunk', { 'component--isLoading': isLoading }, className);
36 |
37 | return (
38 |
39 | {children}
40 |
41 | );
42 | }
43 | }
44 |
45 | const Chunk = withLoading(ChunkComponent);
46 | Chunk.displayName = 'Chunk';
47 | export default Chunk;
48 |
--------------------------------------------------------------------------------
/src/layout/FormSection.jsx:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import cx from 'classnames';
4 |
5 | import Card from './Card';
6 | import Bounds from './Bounds';
7 | import Section from './Section';
8 |
9 | /**
10 | * Wrapping component to standardize the structure of form sections.
11 | * @module FormSection
12 | * @param {object} props React props
13 | * @return {React.element} Form section wrapping component
14 | */
15 |
16 | type Props = {
17 | /** Whether the inner `` has a separator (extra spacing and a border on the bottom) */
18 | withSeparator?: boolean,
19 |
20 | /** Whether the component is in a loading state */
21 | isLoading?: boolean,
22 |
23 | /** Props to pass to the `` component */
24 | loadingProps?: {
25 | color?: string,
26 | scrimColor?: string,
27 | size?: MediaSizes,
28 | },
29 | /** The child elements of the component */
30 | children: React$Node,
31 |
32 | /** Nearest DOM element's class name */
33 | className?: string,
34 | };
35 | function FormSection({
36 | className,
37 | children,
38 | withSeparator,
39 | loadingProps = {}, // eslint-disable-line no-unused-vars
40 | isLoading,
41 | }: Props) {
42 | return (
43 |
44 |
50 |
51 |
54 |
55 |
56 |
57 | );
58 | }
59 |
60 | export default FormSection;
61 |
--------------------------------------------------------------------------------
/src/layout/SectionTitle.jsx:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React from 'react';
4 | import cx from 'classnames';
5 | import Chunk from './Chunk';
6 | import Flex from './Flex';
7 | import FlexItem from './FlexItem';
8 |
9 | export const SECTIONTITLE_CLASS = 'sectionTitle';
10 |
11 | type Props = {
12 | /** Text to display */
13 | title: React$Element<*> | string,
14 |
15 | /** A button-like element to associate an action with the Section */
16 | action: React$Element<*>,
17 |
18 | /** Nearest DOM element's class name */
19 | className?: string,
20 | };
21 | /**
22 | * @module SectionTitle
23 | */
24 | class SectionTitle extends React.Component {
25 | render() {
26 | const { action, className, title, ...other } = this.props;
27 |
28 | const classNames = cx(SECTIONTITLE_CLASS, className);
29 |
30 | return (
31 |
32 |
33 |
34 | {title}
35 |
36 |
37 | {action && (
38 |
39 | {action}
40 |
41 | )}
42 |
43 | );
44 | }
45 | }
46 |
47 | export default SectionTitle;
48 |
--------------------------------------------------------------------------------
/src/layout/__snapshots__/bounds.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Bounds exists 1`] = `
4 |
7 | `;
8 |
--------------------------------------------------------------------------------
/src/layout/__snapshots__/card.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Card and variants renders a card 1`] = `
4 |
7 |
8 | Lorem Ipsum is simply dummy text of the printing and typesetting industry.
9 |
10 |
11 | `;
12 |
--------------------------------------------------------------------------------
/src/layout/__snapshots__/chunk.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Chunk exists 1`] = `
4 |
7 | `;
8 |
--------------------------------------------------------------------------------
/src/layout/__snapshots__/flex.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Flex exists 1`] = `
4 |
7 | `;
8 |
--------------------------------------------------------------------------------
/src/layout/__snapshots__/formSection.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`FormSection exists and contains form section wrapping elements with proper attributes 1`] = `
4 |
7 |
12 |
15 |
19 |
20 |
21 |
22 | `;
23 |
--------------------------------------------------------------------------------
/src/layout/__snapshots__/section.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Section exists 1`] = `
4 |
7 | `;
8 |
--------------------------------------------------------------------------------
/src/layout/__snapshots__/stripe.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Stripe exists 1`] = `
4 |
7 | `;
8 |
--------------------------------------------------------------------------------
/src/layout/bounds.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import { BoundsComponent } from './Bounds';
5 |
6 | const WIDE_CLASS = 'bounds--wide';
7 |
8 | describe('Bounds', function() {
9 | const bounds = shallow();
10 |
11 | it('exists', function() {
12 | expect(bounds).toMatchSnapshot();
13 | });
14 | it(`check that default component has '${WIDE_CLASS}' class`, function() {
15 | expect(bounds.find(`.${WIDE_CLASS}`).length).not.toBe(0);
16 | });
17 | it("check that narrow component does not have the 'bounds--wide' class", function() {
18 | const boundsNarrow = shallow();
19 | expect(boundsNarrow.find(`.${WIDE_CLASS}`).length).toBe(0);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/src/layout/chunk.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import { ChunkComponent } from './Chunk';
5 |
6 | describe('Chunk', function() {
7 | const chunk = shallow();
8 |
9 | it('exists', function() {
10 | expect(chunk).toMatchSnapshot();
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/src/layout/flexItem.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import {
5 | FlexItemComponent,
6 | FLEX_ITEM_CLASS,
7 | FLEX_ITEM_SHRINK_CLASS,
8 | FLEX_ITEM_GROW_CLASS,
9 | FLEX_GROW_FACTORS,
10 | } from './FlexItem';
11 |
12 | describe('FlexItem', function() {
13 | it('exists', function() {
14 | const flexItem = shallow();
15 | expect(flexItem).toMatchSnapshot;
16 | });
17 | describe('default', () => {
18 | it(`check that the component has '${FLEX_ITEM_CLASS}' class`, function() {
19 | const flexItem = shallow();
20 | expect(flexItem.hasClass(FLEX_ITEM_CLASS)).toBe(true);
21 | });
22 | });
23 | describe('shrink', () => {
24 | it(`check that the component has '${FLEX_ITEM_SHRINK_CLASS}' class`, function() {
25 | const flexItem = shallow();
26 | expect(flexItem.hasClass(FLEX_ITEM_CLASS)).toBe(true);
27 | expect(flexItem.hasClass(FLEX_ITEM_SHRINK_CLASS)).toBe(true);
28 | });
29 | });
30 | describe('growFactor', () => {
31 | it("check that component has correct 'growFactor' class", function() {
32 | FLEX_GROW_FACTORS.forEach(growFactor => {
33 | const flexItem = shallow();
34 | expect(flexItem.hasClass(FLEX_ITEM_CLASS)).toBe(true);
35 | expect(flexItem.hasClass(`${FLEX_ITEM_GROW_CLASS}${growFactor}`)).toBe(
36 | true
37 | );
38 | });
39 | });
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/src/layout/formSection.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import FormSection from './FormSection';
5 |
6 | describe('FormSection', () => {
7 | it('exists and contains form section wrapping elements with proper attributes', () => {
8 | expect(shallow()).toMatchSnapshot();
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/src/layout/inlineBlockList.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import {
4 | InlineBlockListComponent,
5 | INLINEBLOCKLIST_SEPERATED_CLASS,
6 | } from './InlineBlockList';
7 |
8 | const ITEMS = [
9 | 'English',
10 | 'English (Australian)',
11 | 'Deutsch',
12 | 'Español',
13 | 'Español (España)',
14 | 'Français',
15 | 'Italiano',
16 | 'Nederlands',
17 | 'Português',
18 | '日本語',
19 | '한국어',
20 | ],
21 | SEPARATOR = '☃';
22 |
23 | let inlineblockList, inlineblockListSeparated;
24 |
25 | describe('InlineBlockList', function() {
26 | beforeEach(() => {
27 | inlineblockList = shallow();
28 | inlineblockListSeparated = shallow(
29 |
30 | );
31 | });
32 | afterEach(() => {
33 | inlineblockList = null;
34 | inlineblockListSeparated = null;
35 | });
36 |
37 | it('exists', function() {
38 | expect(inlineblockList).toMatchSnapshot();
39 | });
40 |
41 | it(`should have a class of '${INLINEBLOCKLIST_SEPERATED_CLASS}' when a separator is defined`, () => {
42 | expect(
43 | inlineblockListSeparated.find('ul').hasClass(INLINEBLOCKLIST_SEPERATED_CLASS)
44 | ).toBe(true);
45 | });
46 |
47 | it('should set the data-separator attribute on item elements when a separator is defined', () => {
48 | const itemEl = inlineblockListSeparated.find('li').first();
49 | expect(itemEl.prop('data-separator')).toEqual(SEPARATOR);
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/src/media/__snapshots__/appBadges.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`App Badges exists 1`] = `
4 |
8 |
11 |
16 |
21 |
22 |
23 |
26 |
31 |
36 |
37 |
38 |
39 | `;
40 |
--------------------------------------------------------------------------------
/src/media/__snapshots__/loading.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Loading exists 1`] = `
4 |
12 |
40 |
41 | `;
42 |
--------------------------------------------------------------------------------
/src/media/appBadges.story.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 |
4 | import { decorateWithBasics, decorateWithInfo } from '../utils/decorators';
5 | import AppBadges from './AppBadges';
6 |
7 | storiesOf('Media/AppBadges', module)
8 | .addDecorator(decorateWithBasics)
9 | .addDecorator(decorateWithInfo)
10 | .add('default', () => )
11 | .add('only Google Play button', () => (
12 |
13 | ))
14 | .add('only App Store button', () => );
15 |
--------------------------------------------------------------------------------
/src/media/appBadges.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import AppBadges, { IOS_DOWNLOAD_LINK, ANDROID_DOWNLOAD_LINK } from './AppBadges';
5 |
6 | const testLanguage = 'fr';
7 | const isAndroidPhone = false;
8 | const isIosPhone = false;
9 | const IsMobile = false;
10 |
11 | describe('App Badges', () => {
12 | const appBadgesComponent = shallow(
13 |
19 | );
20 | it('exists', () => {
21 | expect(appBadgesComponent).toMatchSnapshot();
22 | });
23 | it('should link to the itunes store', () => {
24 | const itunesStoreLink = appBadgesComponent.find(`a[href="${IOS_DOWNLOAD_LINK}"]`);
25 | expect(itunesStoreLink.exists()).toBe(true);
26 | });
27 | it('should link to the google play store', () => {
28 | const googlePlayStoreLink = appBadgesComponent.find(
29 | `a[href="${ANDROID_DOWNLOAD_LINK}"]`
30 | );
31 | expect(googlePlayStoreLink.exists()).toBe(true);
32 | });
33 | it('should render the correct itunes and google play store images based on language', () => {
34 | const getAppStorePhoto = jest.fn();
35 | shallow(
36 |
37 | );
38 | expect(getAppStorePhoto).toHaveBeenCalledWith('android', testLanguage);
39 | expect(getAppStorePhoto).toHaveBeenCalledWith('ios', testLanguage);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/src/media/avatar.story.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf, action } from '@storybook/react';
3 | import { decorateWithBasics, decorateWithInfo } from '../utils/decorators';
4 | import Avatar from './Avatar.jsx';
5 |
6 | const MOCK_IMAGE_SRC = 'http://placekitten.com/g/400/400';
7 |
8 | storiesOf('Media/Avatar', module)
9 | .addDecorator(decorateWithBasics)
10 | .addDecorator(decorateWithInfo)
11 | .add('default', () => , {
12 | info: { text: 'This is the basic usage with the component.' },
13 | })
14 | .add('small', () => )
15 | .add('large', () => )
16 | .add('xxlarge', () => )
17 | .add(
18 | 'link to external URL',
19 | () => (
20 | {
23 | e.preventDefault();
24 | return action('go to http://google.com')(e);
25 | }}
26 | src={MOCK_IMAGE_SRC}
27 | alt="kitten"
28 | />
29 | ),
30 | { info: { text: 'To link within the app, supply a `to` prop instead of `href`' } }
31 | )
32 | .add('no photo', () => );
33 |
--------------------------------------------------------------------------------
/src/media/icon.story.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { Inverted } from '../utils/storyComponents';
4 | import { decorateWithBasics, decorateWithInfo } from '../utils/decorators';
5 | import Icon from './Icon';
6 |
7 | const ICON_NAME = 'heart-outline';
8 |
9 | storiesOf('Media/Icon', module)
10 | .addDecorator(decorateWithBasics)
11 | .addDecorator(decorateWithInfo)
12 | .add('default', () => )
13 | .add(
14 | 'Accessible',
15 | () => ,
16 | {
17 | info: {
18 | text: 'This Icon has an `aria-label` attribute to improve accesibility',
19 | },
20 | }
21 | )
22 | .add('Inverted', () => (
23 |
24 |
25 |
26 | ))
27 | .add('xx-Small', () => )
28 | .add('x-Small', () => )
29 | .add('Small', () => )
30 | .add('Large', () => )
31 | .add('X-Large', () => )
32 | .add('XX-Large', () => )
33 | .add('Passed color', () => )
34 | .add('Circled', () => , {
35 | info: {
36 | text: 'The boolean prop `circled` adds an enclosing circle around the icon.',
37 | },
38 | })
39 | .add('Loading indicator', () => , {
40 | info: { text: 'The `updates` icon is animated by default.' },
41 | });
42 |
--------------------------------------------------------------------------------
/src/media/loading.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { variantTest } from '../utils/testUtils';
3 | import { shallow } from 'enzyme';
4 |
5 | import Loading, {
6 | LOADING_CLASS,
7 | LOADING_SHAPE_CLASS,
8 | LOADING_SHAPE_PATH_CLASS,
9 | } from './Loading';
10 |
11 | const CUSTOM_COLOR = '#000';
12 | const CUSTOM_SCRIM_COLOR = '#FFF';
13 | const CUSTOM_SIZE = '42px';
14 |
15 | describe('Loading', () => {
16 | const loadingComponent = shallow(
17 |
23 | );
24 | it('exists', () => {
25 | expect(loadingComponent).toMatchSnapshot();
26 | });
27 |
28 | it('applies variant classes for each variant prop', () => {
29 | const variants = ['fullCover', 'partialCover'];
30 |
31 | variantTest(Loading, LOADING_CLASS, variants);
32 | });
33 |
34 | it('colors the loader', () => {
35 | const shapePath = loadingComponent.find(`.${LOADING_SHAPE_PATH_CLASS}`);
36 | expect(shapePath.prop('stroke')).toBe(CUSTOM_COLOR);
37 | });
38 |
39 | it('colors the scrim', () => {
40 | expect(loadingComponent.prop('style').backgroundColor).toBe(
41 | CUSTOM_SCRIM_COLOR
42 | );
43 | });
44 |
45 | it('resizes when a custom size is passed', () => {
46 | const shape = loadingComponent.find(`.${LOADING_SHAPE_CLASS}`);
47 | expect(shape.prop('style').width).toBe(CUSTOM_SIZE);
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/src/navigation/components/dashboard/DashboardDropdown.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import DashboardDropdown from './DashboardDropdown';
5 |
6 | const MOCK_PROPS = {
7 | mobileTabs: {
8 | analytics: {
9 | link: 'meetup.com',
10 | label: 'Analytics',
11 | },
12 | members: {
13 | link: 'meetup.com',
14 | label: 'Members',
15 | },
16 | groups: {
17 | link: 'meetup.com',
18 | label: 'Groups',
19 | },
20 | events: {
21 | link: 'meetup.com',
22 | label: 'Events',
23 | },
24 | templates: {
25 | link: 'meetup.com',
26 | label: 'Templates',
27 | },
28 | publicProfile: {
29 | link: 'meetup.com',
30 | label: 'Public Profile',
31 | },
32 | profile: {
33 | link: 'meetup.com',
34 | label: 'Your profile',
35 | },
36 | contact: {
37 | link: 'meetup.com',
38 | label: 'Contact',
39 | },
40 | help: {
41 | link: 'meetup.com',
42 | label: 'Help',
43 | },
44 | logout: {
45 | link: 'meetup.com',
46 | label: 'Logout',
47 | },
48 | },
49 | dismissAction: jest.fn(),
50 | };
51 |
52 | describe('Profile Dropdown', () => {
53 | it('should match snapshot', () => {
54 | const wrapper = shallow();
55 | expect(wrapper).toMatchSnapshot();
56 | });
57 | it('should match snapshot without members tab', () => {
58 | const mobileTabs = { ...MOCK_PROPS.mobileTabs };
59 | delete mobileTabs.members;
60 | const updatedProps = { ...MOCK_PROPS, mobileTabs };
61 | const wrapper = shallow();
62 | expect(wrapper).toMatchSnapshot();
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/src/navigation/components/groupDraftItem/GroupDraftItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import PropTypes from 'prop-types';
4 | import Icon from '../../../media/Icon';
5 |
6 | export const PROFILE_GROUP_DRAFT_LIST_ITEM_CLASS = 'profileDropdown-draft-group';
7 |
8 | export const GroupDraftItem = ({ groupDraft }) => {
9 | return (
10 |
14 | {groupDraft.name}
15 |
22 |
23 | );
24 | };
25 |
26 | export default GroupDraftItem;
27 |
28 | GroupDraftItem.propTypes = {
29 | groupDraft: PropTypes.shape({
30 | editLink: PropTypes.string,
31 | name: PropTypes.string,
32 | status: PropTypes.string,
33 | actionTitle: PropTypes.string,
34 | }),
35 | };
36 |
--------------------------------------------------------------------------------
/src/navigation/components/groupDraftItem/GroupDraftItem.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import GroupDraftItem from './GroupDraftItem';
5 |
6 | const MOCK_PROPS = {
7 | groupHome: jest.fn(),
8 | groupDraft: {
9 | editLink: 'create',
10 | name: 'Name of the group',
11 | status: 'In progress',
12 | actionTitle: 'Finish group',
13 | },
14 | };
15 |
16 | describe('Profile Dropdown', () => {
17 | const wrapper = shallow();
18 | it('should match snapshot', () => {
19 | expect(wrapper).toMatchSnapshot();
20 | });
21 |
22 | it('should show the draft group at the top ProfileDropdown for authenticated screens', () => {
23 | const wrapperWithDrafts = shallow();
24 |
25 | expect(wrapperWithDrafts.find('.profileDropdown-draft-group').length).toBe(1);
26 | });
27 |
28 | it('should has the class for the tracking integration', () => {
29 | const wrapperWithDrafts = shallow();
30 |
31 | expect(wrapperWithDrafts.find('.draftprofiledropdown').length).toBe(1);
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/navigation/components/groupDraftItem/__snapshots__/GroupDraftItem.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Profile Dropdown should match snapshot 1`] = `
4 |
8 |
9 | Name of the group
10 |
11 |
30 |
31 | `;
32 |
--------------------------------------------------------------------------------
/src/navigation/components/notifications/Notifications.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import { MOCK_NOTIFICATION_EVENT } from 'meetup-web-mocks/lib/notifications/api';
5 |
6 | import Notification, { getIconShape } from './Notifications';
7 |
8 | const MOCK_NOTIF = {
9 | ...MOCK_NOTIFICATION_EVENT,
10 | formattedTimeSince: 'Thu Mar 30 2017',
11 | };
12 |
13 | describe('Notification Icons', () => {
14 | it('returns messages icon for message-related notifs', () => {
15 | expect(getIconShape('convo')).toBe('messages');
16 | expect(getIconShape('mug_comm')).toBe('messages');
17 | expect(getIconShape('comment')).toBe('messages');
18 | });
19 | it('returns event icon for event-related notifs', () => {
20 | expect(getIconShape('event')).toBe('calendar');
21 | });
22 | it('returns default icon for unspecified notif kinds', () => {
23 | expect(getIconShape('someOtherNotif')).toBe('meetup-m');
24 | });
25 | });
26 |
27 | describe('Notification component', () => {
28 | const MOCK_HANDLERS = {
29 | markRead: jest.fn(id => id),
30 | };
31 |
32 | const component = shallow(
33 |
46 | );
47 |
48 | it('should match snapshot', () => {
49 | expect(component).toMatchSnapshot();
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/src/navigation/components/notifications/NotificationsDropdown.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import {
5 | MOCK_NOTIFICATION_COMMENT,
6 | MOCK_NOTIFICATIONS_LIST,
7 | } from 'meetup-web-mocks/lib/notifications/api';
8 |
9 | import { NotificationsDropdownComponent } from './NotificationsDropdown';
10 |
11 | const notifs = [
12 | ...MOCK_NOTIFICATIONS_LIST,
13 | {
14 | ...MOCK_NOTIFICATION_COMMENT,
15 | photo: null,
16 | },
17 | ];
18 |
19 | describe('Notifications Dropdown', () => {
20 | const wrapper = (props = {}) =>
21 | shallow(
22 |
27 | );
28 |
29 | it('should match the snapshot with notifications ', () => {
30 | expect(wrapper()).toMatchSnapshot();
31 | });
32 | it('should match the snapshot with an empty notifications', () => {
33 | expect(wrapper({ notifications: [] })).toMatchSnapshot();
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/src/navigation/components/notifications/__snapshots__/Notifications.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Notification component should match snapshot 1`] = `
4 |
8 |
9 |
13 |
18 |
19 |
22 | Meetup Engineering just announced a new Meetup for Apr 6. Only 13 spots.",
27 | }
28 | }
29 | />
30 |
33 |
37 |
42 |
43 |
44 |
45 |
46 | `;
47 |
--------------------------------------------------------------------------------
/src/navigation/components/notifications/__snapshots__/NotificationsDropdown.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Notifications Dropdown should match the snapshot with an empty notifications 1`] = `
4 |
7 |
10 |
11 |
12 | `;
13 |
14 | exports[`Notifications Dropdown should match the snapshot with notifications 1`] = `
15 |
18 |
21 |
22 |
23 | `;
24 |
--------------------------------------------------------------------------------
/src/navigation/components/profile/ProfileDropdown.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { MOCK_GROUP } from 'meetup-web-mocks/lib/api';
3 | import { shallow } from 'enzyme';
4 |
5 | import ProfileDropdown from './ProfileDropdown';
6 |
7 | const MOCK_PROPS = {
8 | groupHome: jest.fn(),
9 | groups: [
10 | { ...MOCK_GROUP, name: 'hello 1', urlname: '1111', id: '1111' },
11 | { ...MOCK_GROUP, name: 'hello 2', urlname: '2222', id: '2222' },
12 | { ...MOCK_GROUP, name: 'hello 3', urlname: '3333', id: '3333' },
13 | { ...MOCK_GROUP, name: 'hello 4', urlname: '4444', id: '4444' },
14 | ],
15 | allGroupsLabel: 'All Groups',
16 | allGroupsLink: 'meetup.com/find',
17 | profile: {
18 | link: 'meetup.com',
19 | label: 'Profile',
20 | },
21 | payments: {
22 | link: 'meetup.com',
23 | label: 'Payments made',
24 | },
25 | settings: {
26 | link: 'meetup.com',
27 | label: 'Settings',
28 | },
29 | help: {
30 | link: 'meetup.com',
31 | label: 'Help',
32 | },
33 | logout: {
34 | link: 'meetup.com',
35 | label: 'Logout',
36 | },
37 | savedEvents: {
38 | link: 'meetup.com',
39 | label: 'Saved events',
40 | },
41 | yourGroups: {
42 | link: 'meetup.com',
43 | label: 'Your groups',
44 | },
45 | yourEvents: {
46 | link: 'meetup.com',
47 | label: 'Your events',
48 | },
49 | };
50 |
51 | describe('Profile Dropdown', () => {
52 | const wrapper = shallow();
53 | it('should match snapshot', () => {
54 | expect(wrapper).toMatchSnapshot();
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/src/signupModal.story.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 |
4 | import { decorateWithBasics, decorateWithInfo } from './utils/decorators';
5 | import { SignupModal } from './SignupModal';
6 |
7 | export const signupOptions = {
8 | orLabel: 'Or',
9 | title: 'Sign up',
10 | apple: {
11 | link: 'apple.com',
12 | label: 'Continue with Apple',
13 | shouldRender: true,
14 | },
15 | google: {
16 | link: 'google.com',
17 | label: 'Continue with Google',
18 | },
19 | facebook: {
20 | link: 'facebook.com',
21 | label: 'Continue with Facebook',
22 | },
23 | email: {
24 | link: 'meetup.com/email',
25 | label: 'Sign up with email',
26 | },
27 | login: {
28 | text: 'Already a member?',
29 | label: 'Login',
30 | link: 'meetup.com/login',
31 | },
32 | };
33 |
34 | storiesOf('Uncategorized/SignupModal', module)
35 | .addDecorator(decorateWithBasics)
36 | .addDecorator(decorateWithInfo)
37 | .add('default', () => (
38 | {}}
41 | focusTrapActive={false}
42 | />
43 | ));
44 |
--------------------------------------------------------------------------------
/src/types.js:
--------------------------------------------------------------------------------
1 | import { bool, shape } from 'prop-types';
2 |
3 | export const Media = shape({
4 | isAtSmallUp: bool,
5 | isAtMediumUp: bool,
6 | isAtLargeUp: bool,
7 | isAtHugeUp: bool,
8 | });
9 |
--------------------------------------------------------------------------------
/src/utils/a11yPassThrough.js:
--------------------------------------------------------------------------------
1 | import cx from 'classnames';
2 |
3 | // needed to allow for a11y cases for screen readers with mutli step forms where inputs are
4 | // conditionally rendered
5 | export default (className = '') =>
6 | cx('tw-pb-1 tw-block', {
7 | 'visibility--a11yHide': className.includes('visibility--a11yHide'),
8 | 'visibility--a11yShow': className.includes('visiblity--a11yShow'),
9 | });
10 |
--------------------------------------------------------------------------------
/src/utils/bindAll.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @function bindAll
3 | * helper for binding prototype methods to class context
4 | *
5 | * for example, instead of doing this in a class `constructor`:
6 | * `this.handleClick = this.handleClick.bind(this)`
7 | *
8 | * you can use `bindAll`:
9 | * ```
10 | * bindAll(this,
11 | * handleClick,
12 | * someOtherMethod,
13 | * anotherMethod);
14 | * ```
15 | */
16 | const bindAll = (context, ...names) => {
17 | names.forEach(name => (context[name] = context[name].bind(context)));
18 | };
19 | export default bindAll;
20 |
--------------------------------------------------------------------------------
/src/utils/components/ConditionalWrap.jsx:
--------------------------------------------------------------------------------
1 | const ConditionalWrap = ({ condition, wrap, children }) =>
2 | condition ? wrap(children) : children;
3 |
4 | export default ConditionalWrap;
5 |
--------------------------------------------------------------------------------
/src/utils/components/DeprecationWarning.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // Swarm Usage & Expectations Doc -> https://docs.google.com/document/d/1aj8FGlpoXQbDzvFgVpaWHzlGn4DfiexHzx96r63Me_4/edit#heading=h.ozjf42vcdlpq
4 |
5 | // List of components to be removed from meetup-web-components on October 3, 2019 ->
6 | // + Button
7 | // + Checkbox
8 | // + TextInput
9 | // + CharCounter
10 | // + NumberInput
11 | // + RadioButton
12 | // + RadioButtonGroup
13 | // + Select
14 | // + SelectInput
15 | // + TogglePill
16 | // + ToggleSwitch
17 |
18 | // All questions and concerns should be directed to #swarm in slack
19 |
20 | // 'global' record of which components have already logged a warning
21 | const hasBeenWarned = {};
22 |
23 | function DeprecationWarning(WrappedComponent) {
24 | class Wrapped extends React.Component {
25 | componentDidMount() {
26 | if (!hasBeenWarned[WrappedComponent.name]) {
27 | console.warn(
28 | `The ${
29 | WrappedComponent.name
30 | } component has been deprecated and will be removed from meetup-web-components on October 21, 2019. Please upgrade to the latest from https://github.com/meetup/swarm-ui`
31 | );
32 | hasBeenWarned[WrappedComponent.name] = true;
33 | }
34 | }
35 |
36 | render() {
37 | return ;
38 | }
39 | }
40 | Wrapped.displayName = WrappedComponent.name;
41 | return Wrapped;
42 | }
43 |
44 | export default DeprecationWarning;
45 |
--------------------------------------------------------------------------------
/src/utils/components/__snapshots__/toggleWrapper.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`withLoading when this.props.type is checkbox matches snapshot 1`] = `
4 |
8 |
9 |
10 | `;
11 |
12 | exports[`withLoading when this.props.type is not checkbox or radio matches snapshot 1`] = `
13 |
17 |
18 |
19 | `;
20 |
21 | exports[`withLoading when this.props.type is radio matches snapshot 1`] = `
22 |
26 |
27 |
28 | `;
29 |
--------------------------------------------------------------------------------
/src/utils/components/__snapshots__/withErrorList.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`withErrorList wrapped component matches snapshot for component with id and error props 1`] = `
4 |
5 |
11 |
15 |
16 | `;
17 |
18 | exports[`withErrorList wrapped component matches snapshot for component without error prop 1`] = `
19 |
20 |
25 |
28 |
29 | `;
30 |
31 | exports[`withErrorList wrapped component matches snapshot for component without id or error props 1`] = `
32 |
33 |
36 |
37 |
38 | `;
39 |
--------------------------------------------------------------------------------
/src/utils/components/__snapshots__/withLoading.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`withLoading matches snapshot with 0 children 1`] = ``;
4 |
5 | exports[`withLoading matches snapshot with 1 child 1`] = `
6 |
7 |
8 | World
9 |
10 |
11 | `;
12 |
13 | exports[`withLoading matches snapshot with 2 children 1`] = `
14 |
15 |
16 | Hello
17 |
18 |
19 | World
20 |
21 |
22 | `;
23 |
24 | exports[`withLoading matches snapshot with isLoading 1`] = `
25 |
29 |
36 |
37 | `;
38 |
--------------------------------------------------------------------------------
/src/utils/components/withLoading.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import Loading from '../../media/Loading';
5 |
6 | const getDisplayName = WrappedComponent => {
7 | return WrappedComponent.displayName || WrappedComponent.name || 'Component';
8 | };
9 |
10 | const withLoading = WrappedComponent => {
11 | /**
12 | * @module WithLoading
13 | */
14 | class WithLoading extends React.Component {
15 | render() {
16 | const { children, isLoading, loadingProps, ...other } = this.props;
17 |
18 | return (
19 |
24 | {this.props.dangerouslySetInnerHTML
25 | ? null
26 | : [
27 | children,
28 | isLoading && (
29 |
34 | ),
35 | ]}
36 |
37 | );
38 | }
39 | }
40 |
41 | WithLoading.displayName = getDisplayName(WrappedComponent);
42 | return WithLoading;
43 | };
44 |
45 | withLoading.defaultProps = {
46 | isLoading: false,
47 | };
48 | withLoading.propTypes = {
49 | isLoading: PropTypes.bool,
50 | loadingProps: PropTypes.shape({
51 | color: PropTypes.string,
52 | scrimColor: PropTypes.string,
53 | size: PropTypes.string,
54 | }),
55 | };
56 |
57 | export default withLoading;
58 |
--------------------------------------------------------------------------------
/src/utils/components/withLoading.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Chunk from '../../layout/Chunk';
5 | import withLoading from './withLoading';
6 |
7 | const dangerousHTML = 'Dangerously setting inner HTML
';
8 | const makeDanger = () => ({ __html: dangerousHTML });
9 |
10 | // const testComponentClass = 'testClass';
11 | const TestComponent = ({ isLoading, ...other }) => (
12 |
13 | );
14 | const TestComponentWithLoading = withLoading(TestComponent);
15 |
16 | describe('withLoading', function() {
17 | const testComponentWithLoading = shallow(
18 |
19 | );
20 | const testComponentNoChildren = shallow();
21 | const testComponentWithOneChild = shallow(
22 |
23 | World
24 |
25 | );
26 | const testComponentWithTwoChildren = shallow(
27 |
28 | Hello
29 | World
30 |
31 | );
32 |
33 | it('matches snapshot with isLoading', () => {
34 | expect(testComponentWithLoading).toMatchSnapshot();
35 | });
36 |
37 | it('matches snapshot with 0 children', () => {
38 | expect(testComponentNoChildren).toMatchSnapshot();
39 | });
40 |
41 | it('matches snapshot with 1 child', () => {
42 | expect(testComponentWithOneChild).toMatchSnapshot();
43 | });
44 |
45 | it('matches snapshot with 2 children', () => {
46 | expect(testComponentWithTwoChildren).toMatchSnapshot();
47 | });
48 |
49 | it('can handle dangerouslySetInnerHTML', function() {
50 | const dangerousHTMLComponent = shallow(
51 |
52 | );
53 | expect(dangerousHTMLComponent.html()).toContain(dangerousHTML);
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/src/utils/decorators.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withInfo } from '@storybook/addon-info';
3 |
4 | export const decorateWithBasics = story => {
5 | /*
6 | * -- Inline SVG icon sprite --
7 | *
8 | * raw SVG sprite from `swarm-icons`
9 | */
10 | const iconSpriteStyle = { display: 'none' };
11 | const iconSprite = require('raw-loader!swarm-icons/dist/sprite/sprite.inc');
12 | const styles = {
13 | display: 'flex',
14 | alignItems: 'center',
15 | justifyContent: 'center',
16 | height: '100%',
17 | width: '100%',
18 | };
19 |
20 | return (
21 |
28 | );
29 | };
30 |
31 | export const decorateWithInfo = (story, context) =>
32 | withInfo(`${context.story} ${context.kind}`)(story)(context);
33 |
--------------------------------------------------------------------------------
/src/utils/designConstants.js:
--------------------------------------------------------------------------------
1 | import {
2 | BREAKPOINT_S,
3 | BREAKPOINT_M,
4 | BREAKPOINT_L,
5 | BREAKPOINT_XL,
6 | } from 'swarm-constants/dist/js/constants.js';
7 |
8 | export const MEDIA_QUERIES = {
9 | small: `screen and (min-width: ${BREAKPOINT_S})`,
10 | medium: `screen and (min-width: ${BREAKPOINT_M})`,
11 | large: `screen and (min-width: ${BREAKPOINT_L})`,
12 | huge: `screen and (min-width: ${BREAKPOINT_XL})`,
13 | };
14 |
15 | //
16 | // TODO: we should import these from swarm-constants,
17 | // but since these are responsive values, we don't
18 | // export them to the JS dist in swarm-constants.
19 | //
20 | // A fine solution would be to export the default
21 | // sizes (which are currently the same as below)
22 | // to JS and not worry about the sizes for viewports
23 | // larger than mobile.
24 | //
25 | export const MEDIA_SIZES = {
26 | xxs: '12',
27 | xs: '16',
28 | s: '24',
29 | m: '36',
30 | l: '48',
31 | xl: '72',
32 | xxl: '120',
33 | };
34 |
--------------------------------------------------------------------------------
/src/utils/getSocialLinks.js:
--------------------------------------------------------------------------------
1 | export const getSocialLinks = locale => {
2 | const socialLinks = {
3 | twitter: 'https://twitter.com/Meetup/',
4 | facebook: 'https://www.facebook.com/meetup/',
5 | youtube: 'https://www.youtube.com/meetup',
6 | googlePlus: 'https://plus.google.com/+Meetup',
7 | instagram: 'https://www.instagram.com/meetup/',
8 | medium: 'https://medium.com/meetup',
9 | };
10 |
11 | switch (locale) {
12 | case 'fr-FR':
13 | socialLinks.facebook = 'https://www.facebook.com/MeetupFR/';
14 | socialLinks.twitter = 'https://twitter.com/MeetupFR/';
15 | socialLinks.instagram = 'https://twitter.com/meetupfr/';
16 | break;
17 | case 'de-DE':
18 | socialLinks.facebook = 'https://www.facebook.com/meetupDE/';
19 | socialLinks.twitter = 'https://twitter.com/MeetupDE/';
20 | socialLinks.instagram = 'https://www.instagram.com/meetupde/';
21 | break;
22 | case 'ja-JP':
23 | socialLinks.facebook = 'https://www.facebook.com/meetupjp/';
24 | socialLinks.twitter = 'https://twitter.com/MeetupJP/';
25 | break;
26 | case 'ja':
27 | socialLinks.facebook = 'https://www.facebook.com/meetupjp/';
28 | socialLinks.twitter = 'https://twitter.com/MeetupJP/';
29 | break;
30 | case 'es':
31 | socialLinks.twitter = 'https://twitter.com/MeetupES/';
32 | break;
33 | case 'es-ES':
34 | socialLinks.twitter = 'https://twitter.com/MeetupES/';
35 | break;
36 | case 'it-IT':
37 | socialLinks.twitter = 'https://twitter.com/MeetupIT/';
38 | break;
39 | case 'pl-PL':
40 | socialLinks.twitter = 'https://twitter.com/MeetupPL/';
41 | break;
42 | case 'pt-BR':
43 | socialLinks.twitter = 'https://twitter.com/MeetupBR/';
44 | break;
45 | default:
46 | break;
47 | }
48 | return socialLinks;
49 | };
50 |
--------------------------------------------------------------------------------
/src/utils/storyComponents.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | /**
4 | * Inverted
5 | *
6 | * Creates an inverted stripe for use in a story
7 | */
8 | export const Inverted = props => (
9 |
19 | {props.children}
20 |
21 | );
22 |
23 | /**
24 | * StoryLink
25 | *
26 | * `Link` component for use in stories
27 | * where a router context is not necessary
28 | *
29 | * Also disables link from being followed on click
30 | */
31 | export const StoryLink = props => (
32 |
37 | {props.children}
38 |
39 | );
40 |
--------------------------------------------------------------------------------
/src/utils/testUtils.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | // import TestUtils from 'react-dom/test-utils';
4 |
5 | export const variantTest = (FoundationComponent, className, variants) => {
6 | variants.forEach(variant => {
7 | const props = {
8 | [variant]: true,
9 | };
10 |
11 | const component = shallow();
12 | expect(component.find(`.${className}--${variant}`).exists()).toBe(true);
13 | });
14 | };
15 |
16 | export const hasChildByClassName = (el, className) => {
17 | const hasClass = !!el.getElementsByClassName(className).length;
18 | expect(hasClass).toBe(true);
19 | };
20 |
21 | export const hasAttribute = (el, attr, value) => {
22 | const elAttrValue = el.getAttribute(attr);
23 | expect(elAttrValue).not.toBeNull();
24 | if (value !== undefined) {
25 | expect(` ${elAttrValue} `.indexOf(` ${value} `)).not.toBe(-1);
26 | }
27 | };
28 |
29 | export const hasRoleAttribute = (el, roleName) => {
30 | hasAttribute(el, 'role', roleName);
31 | };
32 |
33 | export const componentHasProperty = (component, prop, value) => {
34 | expect(component && component.props && component.props[prop] === value).toBe(
35 | true
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | prefix: 'tw-',
3 | purge: {
4 | enabled: true,
5 | content: ['./src/**/*.html', './src/**/*.jsx', './src/**/*.tsx', './src/**/*.js'],
6 | },
7 | darkMode: false, // or 'media' or 'class'
8 | theme: {
9 | screens: {
10 | xs: '0px',
11 | sm: '577px',
12 | md: '769px',
13 | lg: '993px',
14 | xl: '1201px',
15 | },
16 | extend: {
17 | screens: {
18 | xs: '0px',
19 | },
20 | },
21 | colors: {
22 | lightBlue: '#F7FEFF',
23 | merlot: '#7A1D47',
24 | meetupRed: '#E32359',
25 | peach: '#F65858',
26 | lightPeach: '#F77070',
27 | marigold: '#FFAD43',
28 | indigo: '#00455D',
29 | viridian: '#00798A',
30 | beach: '#97CAD1',
31 | oldGold: '#947F5F',
32 | darkGold: '#877457',
33 | white: '#FFFFFF',
34 | black: '#000000',
35 | alertRed: '#C80000',
36 | alertBg: '#FBE1E4',
37 | beachHighlight: '#F7FEFF',
38 | tooltipBlue: '#3679E4',
39 | tooltipDark: '#353e48',
40 | confirmGreen: '#06A002',
41 | warningOrange: '#E5800B',
42 | gray1: '#F6F7F8',
43 | gray2: '#E6E6E6',
44 | gray3: '#D9D9D9',
45 | gray4: '#C5C4C4',
46 | gray5: '#A2A2A2',
47 | gray6: '#757575',
48 | gray7: '#212121',
49 | titleColor: '#2e3e48',
50 | shadowColor: 'rgba(46, 62, 72, 0.12)',
51 | paleBeach: 'rgba(151, 202, 209, 0.2)',
52 | linearGradient:
53 | 'linear-gradient(-180deg, rgba(251, 252, 252, 0) 0%, rgb(246, 247, 248) 100%)',
54 | midnightDark: '#1c1c1e',
55 | yellow: '#FFF200',
56 | sand: '#FFF7ED',
57 | linkBlue: '#0098AB',
58 | submarine: '#8b9898',
59 | },
60 | },
61 | variants: {
62 | extend: {},
63 | },
64 | plugins: [],
65 | };
66 |
--------------------------------------------------------------------------------
/util/jestSystemShim.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const System = {
3 | import: function(pathRelToCallingScript) {
4 | // we can't pass `pathRelToCallingScript` directly to `require` because it
5 | // will be resolved relative to this shim - instead, we need to find out
6 | // the path of the calling script and use that to get the absolute
7 | // path of pathRelToCallingScript
8 |
9 | // easiest access to the call stack is through the `Error` object
10 | const stack = new Error().stack;
11 | // in the error stack, the calling script will be on the third line
12 | const stackLineIndexOfCallingScript = 2;
13 | // in the calling script line, we can get the absolute path from inside the
14 | // parens, before the `:line:char` info
15 | const matchPathFromStack = /\(([^:]+)/;
16 |
17 | const pathToCallingScript = stack
18 | .split('\n')[stackLineIndexOfCallingScript]
19 | .match(matchPathFromStack)[1];
20 |
21 | // we want to resolve the imported module relative to the directory
22 | const absoluteDir = path.dirname(pathToCallingScript);
23 |
24 | // resolve the absolute path
25 | const absolutePath = path.resolve(absoluteDir, pathRelToCallingScript);
26 |
27 | // **now** we can require it and return a Promise
28 | return Promise.resolve(require(absolutePath));
29 | }
30 | };
31 |
32 | global.System = System;
33 |
34 |
--------------------------------------------------------------------------------
/util/sassRelativeImporter.js:
--------------------------------------------------------------------------------
1 | // node-sass importer to resolve path names locally of imports from
2 | // node_modules the same way css-loader does with ~ as a prefix
3 | // used in build:css to build css from sass
4 |
5 | const path = require('path');
6 |
7 | module.exports = function(url, prev, done) {
8 | if (url[0] === '~') {
9 | url = path.resolve('node_modules', url.substr(1));
10 | }
11 | return { file: url };
12 | };
13 |
--------------------------------------------------------------------------------
/util/setupTest.js:
--------------------------------------------------------------------------------
1 | require('raf/polyfill');
2 | require('./jestSystemShim');
3 | const Enzyme = require('enzyme');
4 | const Adapter = require('enzyme-adapter-react-16');
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 |
--------------------------------------------------------------------------------