- A dog is a type of domesticated animal. Known for its loyalty and faithfulness,
- {' '}it can be found as a welcome guest in many households across the world.
-
-
-
-
- What kinds of dogs are there?
-
-
-
- There are many breeds of dogs. Each breed varies in size and temperament.
- {' '}Owners often select a breed of dog that they find to be compatible
- with their own lifestyle and desires from a companion.
-
-
-
-
- How do you acquire a dog?
-
-
-
- Three common ways for a prospective owner to acquire a dog is from pet shops,
- {' '}private owners, or shelters.
-
-
A pet shop may be the most convenient way to buy a dog.
- {' '}Buying a dog from a private owner allows you to assess the pedigree and
- {' '}upbringing of your dog before choosing to take it home. Lastly, finding your dog
- {' '}from a shelter, helps give a good home to a dog who may not find one so readily.
-
+ A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a
+ {' '}welcome guest in many households across the world.
+
+
+
+
+
+ What kinds of dogs are there?
+
+
+
+ There are many breeds of dogs. Each breed varies in size and temperament. Owners often select a breed of
+ {' '}dog that they find to be compatible with their own lifestyle and desires from a companion.
+
+
+
+
+
+ How do you acquire a dog?
+
+
+
+ Three common ways for a prospective owner to acquire a dog is from pet shops, private owners, or shelters.
+
+
+ A pet shop may be the most convenient way to buy a dog. Buying a dog from a private owner allows you to
+ {' '}assess the pedigree and upbringing of your dog before choosing to take it home. Lastly, finding your
+ {' '}dog from a shelter, helps give a good home to a dog who may not find one so readily.
+
+
+
+ )
+ }
+}
diff --git a/docs/app/Examples/modules/Accordion/Types/AccordionExampleStandardShorthand.js b/docs/app/Examples/modules/Accordion/Types/AccordionExampleStandardShorthand.js
new file mode 100644
index 0000000000..3232681cfd
--- /dev/null
+++ b/docs/app/Examples/modules/Accordion/Types/AccordionExampleStandardShorthand.js
@@ -0,0 +1,43 @@
+import React from 'react'
+import { Accordion } from 'semantic-ui-react'
+
+const panels = [
+ {
+ title: 'What is a dog?',
+ content: [
+ 'A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a welcome',
+ 'guest in many households across the world.',
+ ].join(' '),
+ },
+ {
+ title: 'What kinds of dogs are there?',
+ content: [
+ 'There are many breeds of dogs. Each breed varies in size and temperament. Owners often select a breed of dog',
+ 'that they find to be compatible with their own lifestyle and desires from a companion.',
+ ].join(' '),
+ },
+ {
+ title: 'How do you acquire a dog?',
+ content: {
+ content: (
+
+
+ Three common ways for a prospective owner to acquire a dog is from pet shops, private owners, or shelters.
+
+
+ A pet shop may be the most convenient way to buy a dog. Buying a dog from a private owner allows you to
+ {' '}assess the pedigree and upbringing of your dog before choosing to take it home. Lastly, finding your
+ {' '}dog from a shelter, helps give a good home to a dog who may not find one so readily.
+
+ A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a
+ {' '}welcome guest in many households across the world.
+
+
+
+
+
+ What kinds of dogs are there?
+
+
+
+ There are many breeds of dogs. Each breed varies in size and temperament. Owners often select a breed of
+ {' '}dog that they find to be compatible with their own lifestyle and desires from a companion.
+
+
+
+
+
+ How do you acquire a dog?
+
+
+
+ Three common ways for a prospective owner to acquire a dog is from pet shops, private owners, or shelters.
+
+
+ A pet shop may be the most convenient way to buy a dog. Buying a dog from a private owner allows you to
+ {' '}assess the pedigree and upbringing of your dog before choosing to take it home. Lastly, finding your
+ {' '}dog from a shelter, helps give a good home to a dog who may not find one so readily.
+
+
+
+ )
+ }
+}
diff --git a/docs/app/Examples/modules/Accordion/Types/index.js b/docs/app/Examples/modules/Accordion/Types/index.js
index c1db7b86ab..19a11b3980 100644
--- a/docs/app/Examples/modules/Accordion/Types/index.js
+++ b/docs/app/Examples/modules/Accordion/Types/index.js
@@ -1,9 +1,8 @@
import React from 'react'
+
import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'
-import { Message } from 'semantic-ui-react'
-
const AccordionTypesExamples = () => (
(
examplePath='modules/Accordion/Types/AccordionExampleStandard'
/>
An Accordion can be defined using the panels prop.}
- examplePath='modules/Accordion/Types/AccordionExamplePanelsProp'
- >
-
- Panel objects can define an active key to open/close the panel.
- {' '}They can also define an onClick key to be applied to the Accordion.Title.
-
-
+ description='Accordion can be rendered via shorthand prop. It will automatically manage the component state.'
+ examplePath='modules/Accordion/Types/AccordionExampleStandardShorthand'
+ />
({
title: faker.lorem.sentence(),
content: faker.lorem.paragraphs(),
}))
-class AccordionExampleActiveIndex extends Component {
+export default class AccordionExampleActiveIndex extends Component {
state = { activeIndex: 0 }
- handleSliderChange = e => this.setState({
- activeIndex: Number(e.target.value),
- })
+ handleSliderChange = e => this.setState({ activeIndex: Number(e.target.value) })
- handleTitleClick = (e, i) => this.setState({
- activeIndex: this.state.activeIndex === i ? -1 : i,
- })
+ handleTitleClick = (e, itemProps) => {
+ const { index } = itemProps
+ const { activeIndex } = this.state
+ const newIndex = activeIndex === index ? -1 : index
+
+ this.setState({ activeIndex: newIndex })
+ }
render() {
const { activeIndex } = this.state
+
return (
-
activeIndex: {activeIndex}
-
-
+
+
activeIndex: {activeIndex}
+
+
+
+
)
}
}
-
-export default AccordionExampleActiveIndex
diff --git a/docs/app/Examples/modules/Accordion/Variations/AccordionExampleExclusive.js b/docs/app/Examples/modules/Accordion/Usage/AccordionExampleExclusive.js
similarity index 79%
rename from docs/app/Examples/modules/Accordion/Variations/AccordionExampleExclusive.js
rename to docs/app/Examples/modules/Accordion/Usage/AccordionExampleExclusive.js
index f123b8923e..0d699e3228 100644
--- a/docs/app/Examples/modules/Accordion/Variations/AccordionExampleExclusive.js
+++ b/docs/app/Examples/modules/Accordion/Usage/AccordionExampleExclusive.js
@@ -1,5 +1,5 @@
-import _ from 'lodash'
import faker from 'faker'
+import _ from 'lodash'
import React from 'react'
import { Accordion } from 'semantic-ui-react'
@@ -9,7 +9,7 @@ const panels = _.times(3, () => ({
}))
const AccordionExampleExclusive = () => (
-
+
)
export default AccordionExampleExclusive
diff --git a/docs/app/Examples/modules/Accordion/Usage/AccordionExamplePanelsPropWithCustomTitleAndContent.js b/docs/app/Examples/modules/Accordion/Usage/AccordionExamplePanelsPropWithCustomTitleAndContent.js
deleted file mode 100644
index 5905dbb705..0000000000
--- a/docs/app/Examples/modules/Accordion/Usage/AccordionExamplePanelsPropWithCustomTitleAndContent.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react'
-import { Accordion, Label, Message } from 'semantic-ui-react'
-import faker from 'faker'
-import _ from 'lodash'
-
-const panels = _.times(3, i => ({
- key: `panel-${i}`,
- title: ,
- content: (
-
- ),
-}))
-
-const AccordionExamplePanelsPropWithCustomTitleAndContent = () => (
-
-)
-
-export default AccordionExamplePanelsPropWithCustomTitleAndContent
diff --git a/docs/app/Examples/modules/Accordion/Usage/index.js b/docs/app/Examples/modules/Accordion/Usage/index.js
index d937e4317d..9cc745bdfa 100644
--- a/docs/app/Examples/modules/Accordion/Usage/index.js
+++ b/docs/app/Examples/modules/Accordion/Usage/index.js
@@ -3,24 +3,17 @@ import React from 'react'
import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'
-import { Message } from 'semantic-ui-react'
-
const AccordionUsageExamples = () => (
-
- An active prop on an
- {' '}<Accordion.Title> or <Accordion.Content>
- {' '}will override the <Accordion><activeIndex> prop.
-
-
+ />
)
diff --git a/docs/app/Examples/modules/Accordion/Variations/AccordionExampleFluid.js b/docs/app/Examples/modules/Accordion/Variations/AccordionExampleFluid.js
index 9a68aed9cb..25ae84b756 100644
--- a/docs/app/Examples/modules/Accordion/Variations/AccordionExampleFluid.js
+++ b/docs/app/Examples/modules/Accordion/Variations/AccordionExampleFluid.js
@@ -1,15 +1,59 @@
-import _ from 'lodash'
-import faker from 'faker'
-import React from 'react'
-import { Accordion } from 'semantic-ui-react'
+import React, { Component } from 'react'
+import { Accordion, Icon } from 'semantic-ui-react'
-const panels = _.times(3, () => ({
- title: faker.lorem.sentence(),
- content: faker.lorem.paragraphs(),
-}))
+export default class AccordionExampleFluid extends Component {
+ state = { activeIndex: 0 }
-const AccordionExampleFluid = () => (
-
-)
+ handleClick = (e, titleProps) => {
+ const { index } = titleProps
+ const { activeIndex } = this.state
+ const newIndex = activeIndex === index ? -1 : index
-export default AccordionExampleFluid
+ this.setState({ activeIndex: newIndex })
+ }
+
+ render() {
+ const { activeIndex } = this.state
+
+ return (
+
+
+
+ What is a dog?
+
+
+
+ A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a
+ {' '}welcome guest in many households across the world.
+
+
+
+
+
+ What kinds of dogs are there?
+
+
+
+ There are many breeds of dogs. Each breed varies in size and temperament. Owners often select a breed of
+ {' '}dog that they find to be compatible with their own lifestyle and desires from a companion.
+
+
+
+
+
+ How do you acquire a dog?
+
+
+
+ Three common ways for a prospective owner to acquire a dog is from pet shops, private owners, or shelters.
+
+
+ A pet shop may be the most convenient way to buy a dog. Buying a dog from a private owner allows you to
+ {' '}assess the pedigree and upbringing of your dog before choosing to take it home. Lastly, finding your
+ {' '}dog from a shelter, helps give a good home to a dog who may not find one so readily.
+
+ A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a
+ {' '}welcome guest in many households across the world.
+
+
+
+
+
+ What kinds of dogs are there?
+
+
+
+ There are many breeds of dogs. Each breed varies in size and temperament. Owners often select a breed of
+ {' '}dog that they find to be compatible with their own lifestyle and desires from a companion.
+
+
+
+
+
+ How do you acquire a dog?
+
+
+
+ Three common ways for a prospective owner to acquire a dog is from pet shops, private owners, or shelters.
+
+
+ A pet shop may be the most convenient way to buy a dog. Buying a dog from a private owner allows you to
+ {' '}assess the pedigree and upbringing of your dog before choosing to take it home. Lastly, finding your
+ {' '}dog from a shelter, helps give a good home to a dog who may not find one so readily.
+
+
+
+
+ )
+ }
+}
diff --git a/docs/app/Examples/modules/Accordion/Variations/index.js b/docs/app/Examples/modules/Accordion/Variations/index.js
index 1e3da67ba2..0c070d76e2 100644
--- a/docs/app/Examples/modules/Accordion/Variations/index.js
+++ b/docs/app/Examples/modules/Accordion/Variations/index.js
@@ -1,4 +1,5 @@
import React from 'react'
+
import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'
@@ -14,11 +15,6 @@ const AccordionTypesExamples = () => (
description='An accordion can be formatted to appear on dark backgrounds.'
examplePath='modules/Accordion/Variations/AccordionExampleInverted'
/>
-
)
diff --git a/docs/app/Examples/modules/Accordion/index.js b/docs/app/Examples/modules/Accordion/index.js
index 5b5da95be1..ba62cb462a 100644
--- a/docs/app/Examples/modules/Accordion/index.js
+++ b/docs/app/Examples/modules/Accordion/index.js
@@ -1,4 +1,6 @@
import React from 'react'
+
+import Advanced from './Advanced'
import Types from './Types'
import Variations from './Variations'
import Usage from './Usage'
@@ -8,6 +10,7 @@ const AccordionExamples = () => (
+
)
diff --git a/index.d.ts b/index.d.ts
index 167d3b1842..ea87944c3b 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -116,6 +116,11 @@ export * from './dist/commonjs';
// Modules
export { default as Accordion, AccordionProps } from './dist/commonjs/modules/Accordion/Accordion';
+export {
+ default as AccordionAccordion,
+ AccordionAccordionProps,
+ AccordionPanelProps
+} from './dist/commonjs/modules/Accordion/AccordionAccordion';
export { default as AccordionContent, AccordionContentProps } from './dist/commonjs/modules/Accordion/AccordionContent';
export { default as AccordionTitle, AccordionTitleProps } from './dist/commonjs/modules/Accordion/AccordionTitle';
diff --git a/src/index.js b/src/index.js
index 356e9e74cb..1fb972d099 100644
--- a/src/index.js
+++ b/src/index.js
@@ -101,6 +101,7 @@ export { default as StepTitle } from './elements/Step/StepTitle'
// Modules
export { default as Accordion } from './modules/Accordion/Accordion'
+export { default as AccordionAccordion } from './modules/Accordion/AccordionAccordion'
export { default as AccordionContent } from './modules/Accordion/AccordionContent'
export { default as AccordionTitle } from './modules/Accordion/AccordionTitle'
diff --git a/src/modules/Accordion/Accordion.d.ts b/src/modules/Accordion/Accordion.d.ts
index 84c53a2f47..23c29b748f 100644
--- a/src/modules/Accordion/Accordion.d.ts
+++ b/src/modules/Accordion/Accordion.d.ts
@@ -1,56 +1,27 @@
import * as React from 'react';
+import { default as AccordionAccordion, AccordionAccordionProps } from './AccordionAccordion';
import { default as AccordionContent } from './AccordionContent';
import { default as AccordionTitle } from './AccordionTitle';
-export interface AccordionProps {
+export interface AccordionProps extends AccordionAccordionProps {
[key: string]: any;
- /** An element type to render as (string or function). */
- as?: any;
-
- /** Index of the currently active panel. */
- activeIndex?: number | number[];
-
- /** Primary content. */
- children?: React.ReactNode;
-
/** Additional classes. */
className?: string;
- /** Initial activeIndex value. */
- defaultActiveIndex?: number | number[];
-
- /** Only allow one panel open at a time. */
- exclusive?: boolean;
-
/** Format to take up the width of it's container. */
fluid?: boolean;
/** Format for dark backgrounds. */
inverted?: boolean;
- /**
- * Called when a panel title is clicked.
- *
- * @param {SyntheticEvent} event - React's original SyntheticEvent.
- * @param {number} index - The index of the clicked panel.
- */
- onTitleClick?: (event: React.MouseEvent, index: number | number[]) => void;
-
- /**
- * Create simple accordion panels from an array of { text: , content: } objects.
- * Object can optionally define an `active` key to open/close the panel.
- * Object can opitonally define a `key` key used for title and content nodes' keys.
- * Mutually exclusive with children.
- */
- panels?: Array;
-
/** Adds some basic styling to accordion panels. */
styled?: boolean;
}
interface AccordionComponent extends React.ComponentClass {
+ Accordion: typeof AccordionAccordion;
Content: typeof AccordionContent;
Title: typeof AccordionTitle;
}
diff --git a/src/modules/Accordion/Accordion.js b/src/modules/Accordion/Accordion.js
index 44c675ffbe..8aca1a4d1a 100644
--- a/src/modules/Accordion/Accordion.js
+++ b/src/modules/Accordion/Accordion.js
@@ -1,203 +1,60 @@
import cx from 'classnames'
-import _ from 'lodash'
import PropTypes from 'prop-types'
-import React, { Children, cloneElement } from 'react'
+import React from 'react'
import {
- AutoControlledComponent as Component,
- customPropTypes,
- getElementType,
+ getUnhandledProps,
META,
useKeyOnly,
} from '../../lib'
-
+import AccordionAccordion from './AccordionAccordion'
import AccordionContent from './AccordionContent'
import AccordionTitle from './AccordionTitle'
/**
* An accordion allows users to toggle the display of sections of content.
*/
-export default class Accordion extends Component {
- static propTypes = {
- /** An element type to render as (string or function). */
- as: customPropTypes.as,
-
- /** Index of the currently active panel. */
- activeIndex: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.number),
- PropTypes.number,
- ]),
-
- /** Primary content. */
- children: PropTypes.node,
-
- /** Additional classes. */
- className: PropTypes.string,
-
- /** Initial activeIndex value. */
- defaultActiveIndex: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.number),
- PropTypes.number,
- ]),
-
- /** Only allow one panel open at a time. */
- exclusive: PropTypes.bool,
-
- /** Format to take up the width of it's container. */
- fluid: PropTypes.bool,
-
- /** Format for dark backgrounds. */
- inverted: PropTypes.bool,
-
- /**
- * Called when a panel title is clicked.
- *
- * @param {SyntheticEvent} event - React's original SyntheticEvent.
- * @param {number} index - The index of the clicked panel.
- */
- onTitleClick: PropTypes.func,
-
- /**
- * Create simple accordion panels from an array of { text: , content: } objects.
- * Object can optionally define an `active` key to open/close the panel.
- * Object can opitonally define a `key` key used for title and content nodes' keys.
- * Mutually exclusive with children.
- * TODO: AccordionPanel should be a sub-component
- */
- panels: customPropTypes.every([
- customPropTypes.disallow(['children']),
- PropTypes.arrayOf(PropTypes.shape({
- key: PropTypes.string,
- active: PropTypes.bool,
- title: customPropTypes.contentShorthand,
- content: customPropTypes.contentShorthand,
- onClick: PropTypes.func,
- })),
- ]),
-
- /** Adds some basic styling to accordion panels. */
- styled: PropTypes.bool,
- }
-
- static defaultProps = {
- exclusive: true,
- }
-
- static autoControlledProps = [
- 'activeIndex',
- ]
-
- static _meta = {
- name: 'Accordion',
- type: META.TYPES.MODULE,
- }
-
- static Content = AccordionContent
- static Title = AccordionTitle
-
- getInitialAutoControlledState({ exclusive }) {
- return { activeIndex: exclusive ? -1 : [-1] }
- }
-
- handleTitleClick = (e, index) => {
- const { onTitleClick, exclusive } = this.props
- const { activeIndex } = this.state
-
- let newIndex
- if (exclusive) {
- newIndex = index === activeIndex ? -1 : index
- } else {
- // check to see if index is in array, and remove it, if not then add it
- newIndex = _.includes(activeIndex, index) ? _.without(activeIndex, index) : [...activeIndex, index]
- }
- this.trySetState({ activeIndex: newIndex })
- if (onTitleClick) onTitleClick(e, index)
- }
-
- isIndexActive = (index) => {
- const { exclusive } = this.props
- const { activeIndex } = this.state
- return exclusive ? activeIndex === index : _.includes(activeIndex, index)
- }
-
- renderChildren = () => {
- const { children } = this.props
- let titleIndex = 0
- let contentIndex = 0
-
- return Children.map(children, (child) => {
- const isTitle = child.type === AccordionTitle
- const isContent = child.type === AccordionContent
-
- if (isTitle) {
- const currentIndex = titleIndex
- const isActive = _.has(child, 'props.active') ? child.props.active : this.isIndexActive(titleIndex)
- const onClick = (e) => {
- this.handleTitleClick(e, currentIndex)
- if (child.props.onClick) child.props.onClick(e, currentIndex)
- }
- titleIndex += 1
- return cloneElement(child, { ...child.props, active: isActive, onClick })
- }
-
- if (isContent) {
- const isActive = _.has(child, 'props.active') ? child.props.active : this.isIndexActive(contentIndex)
- contentIndex += 1
- return cloneElement(child, { ...child.props, active: isActive })
- }
-
- return child
- })
- }
-
- renderPanels = () => {
- const { panels } = this.props
- const children = []
+function Accordion(props) {
+ const {
+ className,
+ fluid,
+ inverted,
+ styled,
+ } = props
+
+ const classes = cx(
+ 'ui',
+ useKeyOnly(fluid, 'fluid'),
+ useKeyOnly(inverted, 'inverted'),
+ useKeyOnly(styled, 'styled'),
+ className,
+ )
+ const rest = getUnhandledProps(Accordion, props)
+
+ return
+}
- _.each(panels, (panel, i) => {
- const isActive = _.has(panel, 'active') ? panel.active : this.isIndexActive(i)
- const onClick = (e) => {
- this.handleTitleClick(e, i)
- if (panel.onClick) panel.onClick(e, i)
- }
+Accordion._meta = {
+ name: 'Accordion',
+ type: META.TYPES.MODULE,
+}
- // implement all methods of creating a key that are supported in factories
- const key = panel.key
- || (_.isFunction(panel.childKey) && panel.childKey(panel))
- || (panel.childKey && panel.childKey)
- || panel.title
+Accordion.propTypes = {
+ /** Additional classes. */
+ className: PropTypes.string,
- children.push(AccordionTitle.create({ active: isActive, onClick, key: `${key}-title`, content: panel.title }))
- children.push(AccordionContent.create({ active: isActive, key: `${key}-content`, content: panel.content }))
- })
+ /** Format to take up the width of it's container. */
+ fluid: PropTypes.bool,
- return children
- }
+ /** Format for dark backgrounds. */
+ inverted: PropTypes.bool,
- render() {
- const {
- className,
- fluid,
- inverted,
- panels,
- styled,
- } = this.props
+ /** Adds some basic styling to accordion panels. */
+ styled: PropTypes.bool,
+}
- const classes = cx(
- 'ui',
- useKeyOnly(fluid, 'fluid'),
- useKeyOnly(inverted, 'inverted'),
- useKeyOnly(styled, 'styled'),
- 'accordion',
- className,
- )
- const rest = _.omit(this.props, _.keys(Accordion.propTypes))
- const ElementType = getElementType(Accordion, this.props)
+Accordion.Accordion = AccordionAccordion
+Accordion.Content = AccordionContent
+Accordion.Title = AccordionTitle
- return (
-
- {panels ? this.renderPanels() : this.renderChildren()}
-
- )
- }
-}
+export default Accordion
diff --git a/src/modules/Accordion/AccordionAccordion.d.ts b/src/modules/Accordion/AccordionAccordion.d.ts
new file mode 100644
index 0000000000..d7ce7e5f39
--- /dev/null
+++ b/src/modules/Accordion/AccordionAccordion.d.ts
@@ -0,0 +1,47 @@
+import * as React from 'react';
+
+import { SemanticShorthandCollection, SemanticShorthandItem } from '../../';
+import { AccordionContentProps } from './AccordionContent';
+import { AccordionTitleProps } from './AccordionTitle';
+
+export interface AccordionAccordionProps {
+ [key: string]: any;
+
+ /** An element type to render as (string or function). */
+ as?: any;
+
+ /** Index of the currently active panel. */
+ activeIndex?: number | number[];
+
+ /** Primary content. */
+ children?: React.ReactNode;
+
+ /** Additional classes. */
+ className?: string;
+
+ /** Initial activeIndex value. */
+ defaultActiveIndex?: number | number[];
+
+ /** Only allow one panel open at a time. */
+ exclusive?: boolean;
+
+ /**
+ * Called when a panel title is clicked.
+ *
+ * @param {SyntheticEvent} event - React's original SyntheticEvent.
+ * @param {number} index - The index of the clicked panel.
+ */
+ onTitleClick?: (event: React.MouseEvent, index: number | number[]) => void;
+
+ /** Shorthand array of props for Accordion. */
+ panels?: SemanticShorthandCollection;
+}
+
+export interface AccordionPanelProps {
+ content: SemanticShorthandItem;
+ title: SemanticShorthandItem;
+}
+
+declare const AccordionAccordion: AccordionAccordionProps;
+
+export default AccordionAccordion;
diff --git a/src/modules/Accordion/AccordionAccordion.js b/src/modules/Accordion/AccordionAccordion.js
new file mode 100644
index 0000000000..5b75a7e7bc
--- /dev/null
+++ b/src/modules/Accordion/AccordionAccordion.js
@@ -0,0 +1,154 @@
+import cx from 'classnames'
+import _ from 'lodash'
+import PropTypes from 'prop-types'
+import React from 'react'
+
+import {
+ AutoControlledComponent as Component,
+ createShorthandFactory,
+ customPropTypes,
+ getElementType,
+ getUnhandledProps,
+ META,
+} from '../../lib'
+import AccordionContent from './AccordionContent'
+import AccordionTitle from './AccordionTitle'
+
+/**
+ * An Accordion can contain sub-accordions.
+ */
+export default class AccordionAccordion extends Component {
+ static propTypes = {
+ /** An element type to render as (string or function). */
+ as: customPropTypes.as,
+
+ /** Index of the currently active panel. */
+ activeIndex: customPropTypes.every([
+ customPropTypes.disallow(['children']),
+ PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.number),
+ PropTypes.number,
+ ]),
+ ]),
+
+ /** Primary content. */
+ children: PropTypes.node,
+
+ /** Additional classes. */
+ className: PropTypes.string,
+
+ /** Initial activeIndex value. */
+ defaultActiveIndex: customPropTypes.every([
+ customPropTypes.disallow(['children']),
+ PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.number),
+ PropTypes.number,
+ ]),
+ ]),
+
+ /** Only allow one panel open at a time. */
+ exclusive: PropTypes.bool,
+
+ /**
+ * Called when a panel title is clicked.
+ *
+ * @param {SyntheticEvent} event - React's original SyntheticEvent.
+ * @param {object} data - All item props.
+ */
+ onTitleClick: customPropTypes.every([
+ customPropTypes.disallow(['children']),
+ PropTypes.func,
+ ]),
+
+ /** Shorthand array of props for Accordion. */
+ panels: customPropTypes.every([
+ customPropTypes.disallow(['children']),
+ PropTypes.arrayOf(
+ PropTypes.shape({
+ content: customPropTypes.itemShorthand,
+ title: customPropTypes.itemShorthand,
+ }),
+ ),
+ ]),
+ }
+
+ static defaultProps = {
+ exclusive: true,
+ }
+
+ static autoControlledProps = [
+ 'activeIndex',
+ ]
+
+ static _meta = {
+ name: 'AccordionAccordion',
+ type: META.TYPES.MODULE,
+ parent: 'Accordion',
+ }
+
+ getInitialAutoControlledState({ exclusive }) {
+ return { activeIndex: exclusive ? -1 : [-1] }
+ }
+
+ computeNewIndex = (index) => {
+ const { exclusive } = this.props
+ const { activeIndex } = this.state
+
+ if (exclusive) return index === activeIndex ? -1 : index
+ // check to see if index is in array, and remove it, if not then add it
+ return _.includes(activeIndex, index) ? _.without(activeIndex, index) : [...activeIndex, index]
+ }
+
+ handleTitleOverrides = predefinedProps => ({
+ onClick: (e, titleProps) => {
+ const { index } = titleProps
+ const activeIndex = this.computeNewIndex(index)
+
+ this.trySetState({ activeIndex })
+
+ _.invoke(predefinedProps, 'onClick', e, titleProps)
+ _.invoke(this.props, 'onTitleClick', e, titleProps)
+ },
+ })
+
+ isIndexActive = (index) => {
+ const { exclusive } = this.props
+ const { activeIndex } = this.state
+
+ return exclusive ? activeIndex === index : _.includes(activeIndex, index)
+ }
+
+ renderPanels = () => {
+ const children = []
+ const { panels } = this.props
+
+ _.each(panels, (panel, index) => {
+ const { content, title } = panel
+ const active = this.isIndexActive(index)
+
+ children.push(AccordionTitle.create(title, {
+ defaultProps: { active, index },
+ overrideProps: this.handleTitleOverrides,
+ }))
+ children.push(AccordionContent.create(content, { defaultProps: { active } }))
+ })
+
+ return children
+ }
+
+ render() {
+ const { className, children } = this.props
+
+ const classes = cx('accordion', className)
+ const rest = getUnhandledProps(AccordionAccordion, this.props)
+ const ElementType = getElementType(AccordionAccordion, this.props)
+
+ return (
+
+ {_.isNil(children) ? this.renderPanels() : children}
+
+ )
+ }
+}
+
+AccordionAccordion.create = createShorthandFactory(AccordionAccordion, content => ({ content }))
diff --git a/src/modules/Accordion/AccordionContent.js b/src/modules/Accordion/AccordionContent.js
index 1baac6d008..4c9e16156d 100644
--- a/src/modules/Accordion/AccordionContent.js
+++ b/src/modules/Accordion/AccordionContent.js
@@ -4,12 +4,12 @@ import React from 'react'
import {
childrenUtils,
+ createShorthandFactory,
customPropTypes,
getElementType,
getUnhandledProps,
META,
useKeyOnly,
- createShorthandFactory,
} from '../../lib'
/**
diff --git a/src/modules/Accordion/AccordionTitle.d.ts b/src/modules/Accordion/AccordionTitle.d.ts
index 1a10b93e90..08ac8c1319 100644
--- a/src/modules/Accordion/AccordionTitle.d.ts
+++ b/src/modules/Accordion/AccordionTitle.d.ts
@@ -19,6 +19,9 @@ export interface AccordionTitleProps {
/** Shorthand for primary content. */
content?: SemanticShorthandContent;
+ /** AccordionTitle index inside Accordion. */
+ index?: number | string;
+
/**
* Called on click.
*
diff --git a/src/modules/Accordion/AccordionTitle.js b/src/modules/Accordion/AccordionTitle.js
index dd7c05619c..f9e8a7cbfe 100644
--- a/src/modules/Accordion/AccordionTitle.js
+++ b/src/modules/Accordion/AccordionTitle.js
@@ -4,14 +4,13 @@ import PropTypes from 'prop-types'
import React, { Component } from 'react'
import {
+ createShorthandFactory,
customPropTypes,
getElementType,
getUnhandledProps,
META,
useKeyOnly,
- createShorthandFactory,
} from '../../lib'
-
import Icon from '../../elements/Icon'
/**
@@ -34,6 +33,9 @@ export default class AccordionTitle extends Component {
/** Shorthand for primary content. */
content: customPropTypes.contentShorthand,
+ /** AccordionTitle index inside Accordion. */
+ index: PropTypes.number,
+
/**
* Called on click.
*
@@ -49,11 +51,7 @@ export default class AccordionTitle extends Component {
parent: 'Accordion',
}
- handleClick = (e) => {
- const { onClick } = this.props
-
- if (onClick) onClick(e, this.props)
- }
+ handleClick = e => _.invoke(this.props, 'onClick', e, this.props)
render() {
const {
@@ -72,11 +70,7 @@ export default class AccordionTitle extends Component {
const ElementType = getElementType(AccordionTitle, this.props)
if (_.isNil(content)) {
- return (
-
- {children}
-
- )
+ return {children}
}
return (
diff --git a/test/specs/modules/Accordion/Accordion-test.js b/test/specs/modules/Accordion/Accordion-test.js
index c494e6a2f7..267bfeef41 100644
--- a/test/specs/modules/Accordion/Accordion-test.js
+++ b/test/specs/modules/Accordion/Accordion-test.js
@@ -1,388 +1,22 @@
-import faker from 'faker'
import React from 'react'
import Accordion from 'src/modules/Accordion/Accordion'
+import AccordionAccordion from 'src/modules/Accordion/AccordionAccordion'
import AccordionContent from 'src/modules/Accordion/AccordionContent'
import AccordionTitle from 'src/modules/Accordion/AccordionTitle'
import * as common from 'test/specs/commonTests'
-import { consoleUtil, sandbox } from 'test/utils'
describe('Accordion', () => {
common.isConformant(Accordion)
+ common.hasSubComponents(Accordion, [AccordionAccordion, AccordionContent, AccordionTitle])
common.hasUIClassName(Accordion)
- common.rendersChildren(Accordion)
- common.hasSubComponents(Accordion, [AccordionContent, AccordionTitle])
+
common.propKeyOnlyToClassName(Accordion, 'fluid')
common.propKeyOnlyToClassName(Accordion, 'inverted')
common.propKeyOnlyToClassName(Accordion, 'styled')
- describe('activeIndex', () => {
- it('defaults to -1', () => {
- shallow()
- .should.have.state('activeIndex', -1)
- })
- it('can be overridden with "active" on Title/Content', () => {
- const wrapper = mount(
-
-
-
-
-
- ,
- )
- const titles = wrapper.find('AccordionTitle')
- const contents = wrapper.find('AccordionContent')
-
- titles.at(0).should.not.have.className('active')
- contents.at(0).should.not.have.className('active')
-
- titles.at(1).should.have.className('active')
- contents.at(1).should.have.className('active')
- })
- it('makes Accordion.Content at activeIndex - 1 "active"', () => {
- const contents = shallow(
-
-
-
-
-
- ,
- )
- .find('AccordionContent')
-
- contents.at(0).should.have.prop('active', true)
- contents.at(1).should.have.prop('active', false)
- })
- it('is toggled to -1 when clicking Title a second time', () => {
- const wrapper = mount(
-
-
-
- ,
- )
-
- // open panel (activeIndex 0)
- wrapper
- .find('AccordionTitle')
- .simulate('click')
- .should.have.className('active')
- wrapper
- .should.have.state('activeIndex', 0)
-
- // close panel (activeIndex -1)
- wrapper
- .find('AccordionTitle')
- .simulate('click')
- .should.not.have.className('active')
- wrapper
- .should.have.state('activeIndex', -1)
- })
- it('sets the correct pair of title/content active', () => {
- const wrapper = shallow(
-
-
-
-
-
-
-
- ,
- )
- wrapper.setProps({ activeIndex: 0 })
- wrapper.childAt(0).should.have.prop('active', true)
- wrapper.childAt(1).should.have.prop('active', true)
-
- wrapper.setProps({ activeIndex: 1 })
- wrapper.childAt(2).should.have.prop('active', true)
- wrapper.childAt(3).should.have.prop('active', true)
-
- wrapper.setProps({ activeIndex: 2 })
- wrapper.childAt(4).should.have.prop('active', true)
- wrapper.childAt(5).should.have.prop('active', true)
- })
-
- it('can be an array', () => {
- const wrapper = shallow(
-
-
-
-
-
-
-
- ,
- )
- wrapper.setProps({ activeIndex: [0, 1] })
- wrapper.childAt(0).should.have.prop('active', true)
- wrapper.childAt(1).should.have.prop('active', true)
- wrapper.childAt(2).should.have.prop('active', true)
- wrapper.childAt(3).should.have.prop('active', true)
-
- wrapper.setProps({ activeIndex: [1, 2] })
- wrapper.childAt(2).should.have.prop('active', true)
- wrapper.childAt(3).should.have.prop('active', true)
- wrapper.childAt(4).should.have.prop('active', true)
- wrapper.childAt(5).should.have.prop('active', true)
- })
-
- it('can be inclusive and makes Accordion.Content at activeIndex - 1 "active"', () => {
- const contents = shallow(
-
-
-
-
-
- ,
- )
- .find('AccordionTitle')
-
- contents.at(0).should.have.prop('active', true)
- contents.at(1).should.have.prop('active', false)
- })
-
- it('can be inclusive and allows multiple open', () => {
- const contents = shallow(
-
-
-
-
-
- ,
- )
- .find('AccordionTitle')
-
- contents.at(0).should.have.prop('active', true)
- contents.at(1).should.have.prop('active', true)
- })
-
- it('can be inclusive and can open multiple panels by clicking', () => {
- const wrapper = mount(
-
-
-
-
-
- ,
- )
- const titles = wrapper.find('AccordionTitle')
- const contents = wrapper.find('AccordionContent')
-
- titles
- .at(0)
- .simulate('click')
- .should.have.prop('active', true)
- titles
- .at(1)
- .simulate('click')
- .should.have.prop('active', true)
- contents.at(0).should.have.prop('active', true)
- contents.at(1).should.have.prop('active', true)
- })
-
- it('can be inclusive and close multiple panels by clicking', () => {
- const wrapper = mount(
-
-
-
-
-
- ,
- )
- const titles = wrapper.find('AccordionTitle')
- const contents = wrapper.find('AccordionContent')
-
- titles
- .at(0)
- .simulate('click')
- .should.have.prop('active', false)
- titles
- .at(1)
- .simulate('click')
- .should.have.prop('active', false)
- contents.at(0).should.have.prop('active', false)
- contents.at(1).should.have.prop('active', false)
- })
- })
-
- describe('defaultActiveIndex', () => {
- it('sets the initial activeIndex state', () => {
- shallow()
- .should.have.state('activeIndex', 123)
- })
- })
-
- describe('onTitleClick', () => {
- it('is called with (event, index)', () => {
- const spy = sandbox.spy()
- const event = { foo: 'bar' }
- const titles = mount(
-
-
-
- ,
- )
- .find('AccordionTitle')
-
- titles.at(0).simulate('click', event)
- spy.should.have.been.calledWithMatch(event, 0)
-
- titles.at(1).simulate('click', event)
- spy.should.have.been.calledWithMatch(event, 1)
- })
- })
-
- describe('panels', () => {
- it('does not render children', () => {
- consoleUtil.disableOnce()
- shallow(
-
-
- ,
- )
- .should.not.have.descendants('#do-not-find-me')
- })
-
- it('adds text title and text content sibling children', () => {
- const panels = [{
- title: faker.lorem.sentence(),
- content: faker.lorem.paragraph(),
- }]
- const wrapper = mount()
-
- wrapper
- .childAt(0)
- .should.have.className('title')
- .and.contain.text(panels[0].title)
-
- expect(wrapper.childAt(0).key()).to.equal(`${panels[0].title}-title`)
-
- wrapper
- .childAt(1)
- .should.have.className('content')
- .and.contain.text(panels[0].content)
-
- expect(wrapper.childAt(1).key()).to.equal(`${panels[0].title}-content`)
- })
-
- it('adds custom element title and custom element content sibling children', () => {
- const panels = [{
- key: 'panel-1',
- title: (