+
{props.text}
);
@@ -13,7 +13,10 @@ const Heading = (props) => {
Heading.propTypes = {
/** The heading text */
text: PropTypes.string.isRequired,
- level: PropTypes.number
+ /** The heading level */
+ level: PropTypes.number,
+ /** A passable heading classname */
+ class: PropTypes.string
};
Heading.defaultProps = {
diff --git a/react/src/components/atoms/icons/SvgCircleChevron/SvgCircleChevron.stories.js b/react/src/components/atoms/icons/SvgCircleChevron/SvgCircleChevron.stories.js
new file mode 100644
index 0000000000..3545d3f481
--- /dev/null
+++ b/react/src/components/atoms/icons/SvgCircleChevron/SvgCircleChevron.stories.js
@@ -0,0 +1,10 @@
+import React from 'react';
+
+import { storiesOf } from '@storybook/react';
+import { withInfo } from '@storybook/addon-info';
+
+import SvgCircleChevron from './index';
+
+storiesOf('atoms/icons', module)
+ .add('SvgCircleChevron', withInfo()(() => ));
+
diff --git a/react/src/components/atoms/icons/SvgCircleChevron/index.js b/react/src/components/atoms/icons/SvgCircleChevron/index.js
new file mode 100644
index 0000000000..4a28699871
--- /dev/null
+++ b/react/src/components/atoms/icons/SvgCircleChevron/index.js
@@ -0,0 +1,9 @@
+import React from 'react';
+
+const SvgCircleChevron = () => (
+
+);
+
+export default SvgCircleChevron;
diff --git a/react/src/components/molecules/AccordionItem/AccordionItem.md b/react/src/components/molecules/AccordionItem/AccordionItem.md
new file mode 100644
index 0000000000..3d2b70b6f3
--- /dev/null
+++ b/react/src/components/molecules/AccordionItem/AccordionItem.md
@@ -0,0 +1 @@
+This pattern shows a single accordion item.
\ No newline at end of file
diff --git a/react/src/components/molecules/AccordionItem/AccordionItem.stories.js b/react/src/components/molecules/AccordionItem/AccordionItem.stories.js
new file mode 100644
index 0000000000..b816443df9
--- /dev/null
+++ b/react/src/components/molecules/AccordionItem/AccordionItem.stories.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { withInfo } from '@storybook/addon-info';
+import { withKnobs, text, select, boolean } from '@storybook/addon-knobs/react';
+import Paragraph from '../../atoms/text/Paragraph';
+
+import AccordionItem from './index';
+import AccordionDocs from './AccordionItem.md';
+
+import SvgCircleChevron from '../../atoms/icons/SvgCircleChevron';
+import SvgLaptop from '../../atoms/icons/SvgLaptop';
+import SvgPhone from '../../atoms/icons/SvgPhone';
+import SvgFax from '../../atoms/icons/SvgFax';
+
+const icons = {
+ circlechevron: ,
+ laptop: ,
+ phone: ,
+ fax: ,
+ none: null
+};
+
+storiesOf('molecules', module).addDecorator(withKnobs)
+ .add('AccordionItem', withInfo(`${AccordionDocs}
`)(() => {
+ const props = {
+ title: text('accordion.title', 'Collapsible Header'),
+ info: text('accordion.info', 'Collapsible Header'),
+ icon: select('accordion.icon', Object.keys(icons), 'circlechevron'),
+ border: boolean('accordion.border', true),
+ emphasize: boolean('accordion.emphasize', true),
+ secondary: boolean('accordion.secondary', false),
+ headerLevel: select('accordion.headerLevel', [1, 2, 3, 4, 5, 6], 2)
+ };
+
+ // Example of child element, paragraph, passable to accordion
+ const child = {
+ paragraph: text('children.paragraph.text (example)', 'Most parks and beaches that charge daily parking fees sell MassParks Passes at their contact stations during their paid parking seasons. Just ask to purchase a MassParks Pass and show your driver’s license or proof of residency. Please note: most parks cannot accept credit cards, so you’ll have to pay with cash or a check')
+ };
+
+ // Set the icon prop to the actual element based on knob selection.
+ props.icon = icons[props.icon];
+
+ return(
+
+
+
+ );
+ }));
diff --git a/react/src/components/molecules/AccordionItem/index.js b/react/src/components/molecules/AccordionItem/index.js
new file mode 100644
index 0000000000..6123a8f947
--- /dev/null
+++ b/react/src/components/molecules/AccordionItem/index.js
@@ -0,0 +1,103 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import SvgCircleChevron from '../../atoms/icons/SvgCircleChevron';
+import SvgChevron from '../../atoms/icons/SvgChevron';
+import Heading from '../../atoms/headings/Heading';
+import Collapse from '../../animations/Collapse';
+import './style.css';
+
+class AccordionItem extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ open: false
+ };
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick() {
+ this.setState({
+ open: !this.state.open
+ });
+ }
+
+ render() {
+ const accordionClasses = classNames({
+ 'ma__accordion-item': !this.props.secondary,
+ 'ma__accordion-item--secondary': this.props.secondary,
+ 'ma__accordion-item--borderless': !this.props.border && !this.props.secondary
+ });
+ const buttonClasses = classNames({
+ 'ma__accordion-header__button': !this.props.secondary,
+ 'ma__accordion-header__button--secondary': this.props.secondary,
+ 'is-open': this.state.open,
+ 'ma__accordion-header__button--solid': this.props.emphasize && !this.props.secondary,
+ 'ma__accordion-header__button--trans': !this.props.emphasize && !this.props.secondary
+ });
+ const headingClasses = classNames({
+ 'ma__accordion-header__title': !this.props.secondary,
+ 'ma__accordion-header__title--secondary': this.props.secondary
+ });
+
+ return(
+
+
+
+
+
+
+ {this.props.children}
+
+
+
+ );
+ }
+}
+
+AccordionItem.propTypes = {
+ /** The title of the accordion header */
+ title: PropTypes.string.isRequired,
+ /** Accessible aria label */
+ info: PropTypes.string.isRequired,
+ /** The icon to display in the collapsible header */
+ icon: PropTypes.element,
+ /** Whether the accordion should have a border or not, default is true (border) */
+ border: PropTypes.bool,
+ /** Where the accordion header's background should remain colored when expanded */
+ emphasize: PropTypes.bool,
+ /** Content rendered in the collapsed section. Only Paragraph, Table, Heading, OrderedList
+ and UnorderList are valid children components to pass to AccordionItem */
+ children: PropTypes.node.isRequired,
+ /** Whether to style the accordion as secondary or not. */
+ secondary: PropTypes.bool,
+ /** Heading level for accordion title */
+ headerLevel: PropTypes.number
+};
+
+AccordionItem.defaultProps = {
+ icon: ,
+ border: true,
+ emphasize: true,
+ secondary: false,
+ headerLevel: 2
+};
+
+export default AccordionItem;
diff --git a/react/src/components/molecules/AccordionItem/style.scss b/react/src/components/molecules/AccordionItem/style.scss
new file mode 100644
index 0000000000..65af916f90
--- /dev/null
+++ b/react/src/components/molecules/AccordionItem/style.scss
@@ -0,0 +1,3 @@
+@import "global";
+@import '00-base/mixins';
+@import '02-molecules/accordion-item';
diff --git a/react/src/components/molecules/CalloutLink/CalloutLink.stories.js b/react/src/components/molecules/CalloutLink/CalloutLink.stories.js
index cd85ca8605..e6edba846e 100644
--- a/react/src/components/molecules/CalloutLink/CalloutLink.stories.js
+++ b/react/src/components/molecules/CalloutLink/CalloutLink.stories.js
@@ -8,7 +8,7 @@ import CalloutLink from './index';
import CalloutLinkDocs from './CalloutLink.md';
import calloutLinkOptions from './CalloutLink.knobs.options';
-storiesOf('molecules/CalloutLink', module).addDecorator(withKnobs)
+storiesOf('molecules', module).addDecorator(withKnobs)
.add('CalloutLink', withInfo(`${CalloutLinkDocs}
`)(() => {
const props = {
text: text('calloutLink.text', 'Link to another page'),
@@ -23,7 +23,9 @@ storiesOf('molecules/CalloutLink', module).addDecorator(withKnobs)
return(
);
- }))
+ }));
+
+storiesOf('molecules/CalloutLink', module).addDecorator(withKnobs)
.add('CalloutLink with Description', withInfo(`${CalloutLinkDocs}
`)(() => {
const props = {
text: text('calloutLink.text', 'Link to another page'),
diff --git a/react/src/components/molecules/ImagePromo/ImagePromo.stories.js b/react/src/components/molecules/ImagePromo/ImagePromo.stories.js
index 218b57e8d8..be891293ee 100644
--- a/react/src/components/molecules/ImagePromo/ImagePromo.stories.js
+++ b/react/src/components/molecules/ImagePromo/ImagePromo.stories.js
@@ -34,12 +34,14 @@ const getCommonPropsWithKnobs = () => ({
phone: null
});
-storiesOf('molecules/ImagePromo', module).addDecorator(withKnobs)
+storiesOf('molecules', module).addDecorator(withKnobs)
.add('ImagePromo', withInfo(`${ImagePromoDocs}
`)(() => {
const props = getCommonPropsWithKnobs();
return();
- }))
+ }));
+
+storiesOf('molecules/ImagePromo', module).addDecorator(withKnobs)
.add('ImagePromo as orgInfo', withInfo(`${ImagePromoDocs}
`)(() => {
// Override some props/knobs for "with map link" variation example.
const commonProps = getCommonPropsWithKnobs();
diff --git a/react/src/components/molecules/ResultsHeading/ResultsHeading.stories.js b/react/src/components/molecules/ResultsHeading/ResultsHeading.stories.js
index deef6ab875..f077c4ebf9 100644
--- a/react/src/components/molecules/ResultsHeading/ResultsHeading.stories.js
+++ b/react/src/components/molecules/ResultsHeading/ResultsHeading.stories.js
@@ -9,7 +9,6 @@ import ResultsHeading from './index';
import ResultsHeadingDocs from './ResultsHeading.md';
import { TagsData, SortData } from './ResultsHeading.knobs.options';
import buttonToggleOptions from '../../atoms/buttons/ButtonToggle/ButtonToggle.knobs.options';
-import selectOptions from '../../atoms/forms/SelectBox/SelectBox.knobs.options';
storiesOf('molecules', module).addDecorator(withKnobs)
.add('ResultsHeading', withInfo(`${ResultsHeadingDocs}
`)(() => {
diff --git a/react/src/components/molecules/SearchBannerForm/SearchBannerForm.stories.js b/react/src/components/molecules/SearchBannerForm/SearchBannerForm.stories.js
index 35545019b9..cc368774ef 100644
--- a/react/src/components/molecules/SearchBannerForm/SearchBannerForm.stories.js
+++ b/react/src/components/molecules/SearchBannerForm/SearchBannerForm.stories.js
@@ -9,7 +9,7 @@ import SearchBannerForm from '.';
import SearchBannerDocs from './SearchBannerForm.md';
storiesOf('molecules', module).addDecorator(withKnobs)
- .add('Search Banner Form', withInfo(`${SearchBannerDocs}
`)(() => {
+ .add('SearchBannerForm', withInfo(`${SearchBannerDocs}
`)(() => {
const props = {
action: '#',
onSubmit: action('Form submitted'),
diff --git a/react/src/components/organisms/AccordionWrapper/AccordionWrapper.md b/react/src/components/organisms/AccordionWrapper/AccordionWrapper.md
new file mode 100644
index 0000000000..cb8d7aee9b
--- /dev/null
+++ b/react/src/components/organisms/AccordionWrapper/AccordionWrapper.md
@@ -0,0 +1 @@
+This is a wrapper around multiple accordion items.
\ No newline at end of file
diff --git a/react/src/components/organisms/AccordionWrapper/AccordionWrapper.stories.js b/react/src/components/organisms/AccordionWrapper/AccordionWrapper.stories.js
new file mode 100644
index 0000000000..589977759f
--- /dev/null
+++ b/react/src/components/organisms/AccordionWrapper/AccordionWrapper.stories.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { withInfo } from '@storybook/addon-info';
+import { withKnobs, text, select, boolean } from '@storybook/addon-knobs/react';
+
+import AccordionWrapper from './index';
+import AccordionWrapperDocs from './AccordionWrapper.md';
+
+import AccordionItem from '../../molecules/AccordionItem';
+import SvgCircleChevron from '../../atoms/icons/SvgCircleChevron';
+import SvgLaptop from '../../atoms/icons/SvgLaptop';
+import SvgPhone from '../../atoms/icons/SvgPhone';
+import SvgFax from '../../atoms/icons/SvgFax';
+import Paragraph from '../../atoms/text/Paragraph';
+import OrderedList from '../../atoms/lists/OrderedList';
+
+const icons = {
+ circlechevron: ,
+ laptop: ,
+ phone: ,
+ fax: ,
+ none: null
+};
+
+storiesOf('organisms', module).addDecorator(withKnobs)
+ .add('AccordionWrapper', withInfo(`${AccordionWrapperDocs}
`)(() => {
+ const AccordionItem1Props = {
+ title: text('accordion1.title', 'Collapsible Header One'),
+ info: text('accordion1.info', 'Collapsible Header One'),
+ icon: select('accordion1.icon', Object.keys(icons), 'circlechevron')
+ };
+ const AccordionItem2Props = {
+ title: text('accordion2.title', 'Collapsible Header Two'),
+ info: text('accordion2.info', 'Collapsible Header Two'),
+ icon: select('accordion2.icon', Object.keys(icons), 'laptop')
+ };
+ const AccordionWrapperProps = {
+ border: boolean('accordionWrapper.border', true),
+ secondary: boolean('accordionWrapper.secondary', false),
+ emphasize: boolean('accordionWrapper.emphasize', true),
+ headerLevel: select('accordionWrapper.headerLevel', [1, 2, 3, 4, 5, 6], 2)
+ };
+
+ // Set the icon prop to the actual element based on knob selection.
+ AccordionItem1Props.icon = icons[AccordionItem1Props.icon];
+ AccordionItem2Props.icon = icons[AccordionItem2Props.icon];
+
+ return(
+
+
+
+
+
+
+
+
+ );
+ }));
diff --git a/react/src/components/organisms/AccordionWrapper/index.js b/react/src/components/organisms/AccordionWrapper/index.js
new file mode 100644
index 0000000000..f26406c28d
--- /dev/null
+++ b/react/src/components/organisms/AccordionWrapper/index.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import './style.css';
+
+const AccordionWrapper = (props) => {
+ const children = props.children;
+ return(
+
+ { React.Children.map(children, (child) => {
+ if (React.isValidElement(child) && child.type.name === 'AccordionItem') {
+ const clone = React.cloneElement(child, {
+ secondary: props.secondary,
+ emphasize: props.emphasize,
+ border: props.border,
+ headerLevel: props.headerLevel
+ });
+ return clone;
+ }
+ return(
+ /* eslint-disable no-console */
+ console.log(`Warning! You cannot pass a ${child.type.name} child to AccordionWrapper`)
+ );
+ })}
+
+ );
+};
+
+AccordionWrapper.propTypes = {
+ /** Only AccordionItem can be passed as a Child to the AccordionWrapper */
+ children: PropTypes.node.isRequired,
+ /** Whether accordion children are emphasized or not. */
+ emphasize: PropTypes.bool,
+ /** Whether accordion children are with border or not. */
+ border: PropTypes.bool,
+ /** Whether accordion is a primary or secondary accordion. */
+ secondary: PropTypes.bool,
+ /** The heading levels of children accordion */
+ headerLevel: PropTypes.number
+};
+
+AccordionWrapper.defaultProps = {
+ border: true,
+ emphasize: true,
+ secondary: false,
+ headerLevel: 2
+};
+
+export default AccordionWrapper;
diff --git a/react/src/components/organisms/AccordionWrapper/style.scss b/react/src/components/organisms/AccordionWrapper/style.scss
new file mode 100644
index 0000000000..630e270a39
--- /dev/null
+++ b/react/src/components/organisms/AccordionWrapper/style.scss
@@ -0,0 +1 @@
+@import '03-organisms/accordion-wrapper';
diff --git a/react/src/components/organisms/Footer/Footer.stories.js b/react/src/components/organisms/Footer/Footer.stories.js
index c09f9c4654..8ea8b547f0 100644
--- a/react/src/components/organisms/Footer/Footer.stories.js
+++ b/react/src/components/organisms/Footer/Footer.stories.js
@@ -1,7 +1,7 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
-import { withKnobs, text, object } from '@storybook/addon-knobs/react';
+import { withKnobs, object } from '@storybook/addon-knobs/react';
import Footer from '.';
import FooterReadme from './Footer.md';
@@ -13,7 +13,7 @@ import SocialLinksLiveData from '../../molecules/SocialLinks/SocialLinksLive.jso
import stateSeal from '../../../../../assets/images/stateseal.png';
-storiesOf('organisms/Footer', module).addDecorator(withKnobs)
+storiesOf('organisms', module).addDecorator(withKnobs)
.add('Footer', withInfo({ FooterReadme })(() => {
const props = {
footerLinks: object('footer.footerLinksData', FooterLinksData),
@@ -32,7 +32,9 @@ storiesOf('organisms/Footer', module).addDecorator(withKnobs)
})
};
return();
- }))
+ }));
+
+storiesOf('organisms/Footer', module).addDecorator(withKnobs)
.add('Footer with live JSON', withInfo({ FooterReadme })(() => {
const props = {
footerLinks: object('footer.footerLinksLiveData', FooterLinksLiveData),
diff --git a/react/src/index.js b/react/src/index.js
index 192bb29395..851ba3d20f 100644
--- a/react/src/index.js
+++ b/react/src/index.js
@@ -4,7 +4,7 @@
*/
//@base
-export Placeholder from './components/base/Placeholder'
+export Placeholder from './components/base/Placeholder';
// @atoms
export Divider from './components/atoms/Divider';
@@ -35,13 +35,14 @@ export SvgPhone from './components/atoms/icons/SvgPhone';
export SvgLaptop from './components/atoms/icons/SvgLaptop';
export SvgMarker from './components/atoms/icons/SvgMarker';
export SvgFax from './components/atoms/icons/SvgFax';
-export SvgDocGeneric from './components/atoms/icons/SvgDocGeneric'
-export SvgDocDocx from './components/atoms/icons/SvgDocDocx'
-export SvgDocPdf from './components/atoms/icons/SvgDocPdf'
-export SvgDocXlxs from './components/atoms/icons/SvgDocXlxs'
-export SvgChevron from './components/atoms/icons/SvgChevron'
-export SvgWheelchair from './components/atoms/icons/SvgWheelchair'
-export SvgOpenNow from './components/atoms/icons/SvgOpenNow'
+export SvgDocGeneric from './components/atoms/icons/SvgDocGeneric';
+export SvgDocDocx from './components/atoms/icons/SvgDocDocx';
+export SvgDocPdf from './components/atoms/icons/SvgDocPdf';
+export SvgDocXlxs from './components/atoms/icons/SvgDocXlxs';
+export SvgChevron from './components/atoms/icons/SvgChevron';
+export SvgWheelchair from './components/atoms/icons/SvgWheelchair';
+export SvgOpenNow from './components/atoms/icons/SvgOpenNow';
+export SvgCircleChevron from './components/atoms/icons/SvgCircleChevron';
// @atoms/@links
export DecorativeLink from './components/atoms/links/DecorativeLink';
// @atoms/@lists
@@ -72,6 +73,7 @@ export ContactGroup from './components/molecules/ContactGroup';
export ImagePromo from './components/molecules/ImagePromo';
export Tabs from './components/molecules/Tabs';
export TypeAheadDropdown from './components/molecules/TypeAheadDropdown';
+export AccordionItem from './components/molecules/AccordionItem';
// @organisms
export Footer from './components/organisms/Footer';
@@ -86,6 +88,7 @@ export PageHeader from './components/organisms/PageHeader';
export IllustratedHeader from './components/organisms/IllustratedHeader';
export FilterBox from './components/organisms/FilterBox';
export SearchBanner from './components/organisms/SearchBanner';
+export AccordionWrapper from './components/organisms/AccordionWrapper';
//@templates
export NarrowTemplate from './components/templates/NarrowTemplate';
@@ -94,3 +97,6 @@ export NarrowTemplate from './components/templates/NarrowTemplate';
export Error403 from './components/pages/Error403';
export Error404 from './components/pages/Error404';
export Error500 from './components/pages/Error500';
+
+//@animations
+export Collapse from './components/animations/Collapse';