diff --git a/package-lock.json b/package-lock.json index 19bc76e9c70..3b32f7c8476 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43772,6 +43772,9 @@ "terra-theme-context": "^1.0.0", "uuid": "3.4.0" }, + "devDependencies": { + "terra-icon": "^3.60.0" + }, "peerDependencies": { "react": "^16.8.5", "react-dom": "^16.8.5", diff --git a/packages/terra-core-docs/src/terra-dev-site/doc/dropdown-button/SplitButton.2.doc.mdx b/packages/terra-core-docs/src/terra-dev-site/doc/dropdown-button/SplitButton.2.doc.mdx index 5a1d726faed..04539ebfcf5 100644 --- a/packages/terra-core-docs/src/terra-dev-site/doc/dropdown-button/SplitButton.2.doc.mdx +++ b/packages/terra-core-docs/src/terra-dev-site/doc/dropdown-button/SplitButton.2.doc.mdx @@ -4,6 +4,7 @@ import DefaultSplitButton from './example/DefaultSplitButton?dev-site-example'; import GhostSplitButton from './example/GhostSplitButton?dev-site-example'; import DisabledSplitButton from './example/DisabledSplitButton?dev-site-example'; import BlockSplitButton from './example/BlockSplitButton?dev-site-example'; +import IconSplitButton from './example/IconSplitButton?dev-site-example'; import SplitButtonPropsTable from 'terra-dropdown-button/lib/SplitButton?dev-site-props-table'; import ItemPropsTable from 'terra-dropdown-button/lib/Item?dev-site-props-table'; @@ -46,6 +47,7 @@ import { Item, SplitButton } from 'terra-dropdown-button'; + ## Split Button Props diff --git a/packages/terra-core-docs/src/terra-dev-site/doc/dropdown-button/example/IconSplitButton.jsx b/packages/terra-core-docs/src/terra-dev-site/doc/dropdown-button/example/IconSplitButton.jsx new file mode 100644 index 00000000000..ab8db70cd3e --- /dev/null +++ b/packages/terra-core-docs/src/terra-dev-site/doc/dropdown-button/example/IconSplitButton.jsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Item, SplitButton } from 'terra-dropdown-button'; +import { IconFeaturedOutlineYellow } from 'terra-icon'; + +import classNames from 'classnames/bind'; +import styles from './IconSplitButton.module.scss'; + +const cx = classNames.bind(styles); + +const Example = () => { + return ( + <> + } + onSelect={() => setMessage('Reply clicked')} + buttonAttrs={{ + 'aria-label': 'icon split', + }} + className={cx('icon-button')} + > + setMessage('Reply All clicked')} /> + setMessage('Forward clicked')} /> + setMessage('Reply in 10 minutes clicked')} /> + setMessage('Selective Reply clicked')} /> + + } + isReversed + onSelect={() => setMessage('Reply clicked')} + buttonAttrs={{ + 'aria-label': 'reverse icon split', + }} + className={cx('icon-button')} + > + setMessage('Reply All clicked')} /> + setMessage('Forward clicked')} /> + setMessage('Reply in 10 minutes clicked')} /> + setMessage('Selective Reply clicked')} /> + + } + isIconOnly + onSelect={() => setMessage('Reply clicked')} + buttonAttrs={{ + 'aria-label': 'icon only split', + }} + className={cx('icon-button')} + > + setMessage('Reply All clicked')} /> + setMessage('Forward clicked')} /> + setMessage('Reply in 10 minutes clicked')} /> + setMessage('Selective Reply clicked')} /> + + + ); +}; + +export default Example; diff --git a/packages/terra-core-docs/src/terra-dev-site/doc/dropdown-button/example/IconSplitButton.module.scss b/packages/terra-core-docs/src/terra-dev-site/doc/dropdown-button/example/IconSplitButton.module.scss new file mode 100644 index 00000000000..c83f805dbbf --- /dev/null +++ b/packages/terra-core-docs/src/terra-dev-site/doc/dropdown-button/example/IconSplitButton.module.scss @@ -0,0 +1,5 @@ +:local { + .icon-button { + margin: 5px; + } +} diff --git a/packages/terra-core-docs/src/terra-dev-site/test/dropdown-button/IconOnlySplitButton.test.jsx b/packages/terra-core-docs/src/terra-dev-site/test/dropdown-button/IconOnlySplitButton.test.jsx new file mode 100644 index 00000000000..f92a4566961 --- /dev/null +++ b/packages/terra-core-docs/src/terra-dev-site/test/dropdown-button/IconOnlySplitButton.test.jsx @@ -0,0 +1,34 @@ +import React, { useState } from 'react'; +import classnames from 'classnames/bind'; +import { SplitButton, Item } from 'terra-dropdown-button'; +import { IconFeaturedOutlineYellow } from 'terra-icon'; +import styles from './ExtraSpacing.module.scss'; + +const cx = classnames.bind(styles); + +const IconOnlySplitButton = () => { + const [message, setMessage] = useState(' No option clicked'); + + return ( +
+ } + isIconOnly + metaData={{ key: 'primary-button' }} + onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} + id="split" + > + { setMessage(` ${metaData.key}`); }} /> + { setMessage(` ${metaData.key}`); }} /> + { setMessage(` ${metaData.key}`); }} /> + +

+ MetaData of : + {message} +

+
+ ); +}; + +export default IconOnlySplitButton; diff --git a/packages/terra-core-docs/src/terra-dev-site/test/dropdown-button/LeftIconSplitButton.test.jsx b/packages/terra-core-docs/src/terra-dev-site/test/dropdown-button/LeftIconSplitButton.test.jsx new file mode 100644 index 00000000000..c29d9866497 --- /dev/null +++ b/packages/terra-core-docs/src/terra-dev-site/test/dropdown-button/LeftIconSplitButton.test.jsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react'; +import classnames from 'classnames/bind'; +import { SplitButton, Item } from 'terra-dropdown-button'; +import { IconFeaturedOutlineYellow } from 'terra-icon'; +import styles from './ExtraSpacing.module.scss'; + +const cx = classnames.bind(styles); + +const LeftIconSplitButton = () => { + const [message, setMessage] = useState(' No option clicked'); + + return ( +
+ } + metaData={{ key: 'primary-button' }} + onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} + id="split" + > + { setMessage(` ${metaData.key}`); }} /> + { setMessage(` ${metaData.key}`); }} /> + { setMessage(` ${metaData.key}`); }} /> + +

+ MetaData of : + {message} +

+
+ ); +}; + +export default LeftIconSplitButton; diff --git a/packages/terra-core-docs/src/terra-dev-site/test/dropdown-button/RightIconSplitButton.test.jsx b/packages/terra-core-docs/src/terra-dev-site/test/dropdown-button/RightIconSplitButton.test.jsx new file mode 100644 index 00000000000..6d3b07a7b0f --- /dev/null +++ b/packages/terra-core-docs/src/terra-dev-site/test/dropdown-button/RightIconSplitButton.test.jsx @@ -0,0 +1,34 @@ +import React, { useState } from 'react'; +import classnames from 'classnames/bind'; +import { SplitButton, Item } from 'terra-dropdown-button'; +import { IconFeaturedOutlineYellow } from 'terra-icon'; +import styles from './ExtraSpacing.module.scss'; + +const cx = classnames.bind(styles); + +const RightIconSplitButton = () => { + const [message, setMessage] = useState(' No option clicked'); + + return ( +
+ } + isReversed + metaData={{ key: 'primary-button' }} + onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} + id="split" + > + { setMessage(` ${metaData.key}`); }} /> + { setMessage(` ${metaData.key}`); }} /> + { setMessage(` ${metaData.key}`); }} /> + +

+ MetaData of : + {message} +

+
+ ); +}; + +export default RightIconSplitButton; diff --git a/packages/terra-dropdown-button/package.json b/packages/terra-dropdown-button/package.json index d33ed711611..741e3474557 100644 --- a/packages/terra-dropdown-button/package.json +++ b/packages/terra-dropdown-button/package.json @@ -57,5 +57,8 @@ "LICENSE", "NOTICE", "README.md" - ] + ], + "devDependencies": { + "terra-icon": "^3.60.0" + } } diff --git a/packages/terra-dropdown-button/src/SplitButton.jsx b/packages/terra-dropdown-button/src/SplitButton.jsx index b47ece58a8a..69923fb5422 100644 --- a/packages/terra-dropdown-button/src/SplitButton.jsx +++ b/packages/terra-dropdown-button/src/SplitButton.jsx @@ -23,6 +23,10 @@ const propTypes = { * The options to display in the dropdown. Should be comprised of the subcomponent `Item`. */ children: PropTypes.node.isRequired, + /** + * An optional icon. Nested inline with the text when provided. + */ + icon: PropTypes.element, /** * Determines whether the component should have block styles applied. The dropdown will match the component's width. */ @@ -35,6 +39,14 @@ const propTypes = { * Determines whether the primary button and expanding the dropdown should be disabled. */ isDisabled: PropTypes.bool, + /** + * Whether or not the button should only display as an icon. + */ + isIconOnly: PropTypes.bool, + /** + * Reverses the position of the icon and text. + */ + isReversed: PropTypes.bool, /** * Sets the text that will be shown on the primary button which is outside the dropdown. */ @@ -190,9 +202,12 @@ class SplitButton extends React.Component { render() { const { children, + isReversed, + icon, isBlock, isCompact, isDisabled, + isIconOnly, primaryOptionLabel, onSelect, variant, @@ -235,6 +250,30 @@ class SplitButton extends React.Component { theme.className, ); + const buttonTextClassnames = cx([ + { 'text-first': icon && isReversed }, + ]); + + const iconClassnames = cx([ + { 'icon-first': (!isIconOnly) && !isReversed }, + ]); + + const buttonText = !isIconOnly ? {primaryOptionLabel} : null; + + let buttonIcon = null; + if (icon) { + const iconSvgClasses = icon.props.className ? `${icon.props.className} ${cx('icon-svg')}` : cx('icon-svg'); + const cloneIcon = React.cloneElement(icon, { className: iconSvgClasses }); + buttonIcon = {cloneIcon}; + } + + const buttonLabel = ( + <> + {isReversed ? buttonText : buttonIcon} + {isReversed ? buttonIcon : buttonText} + + ); + let buttonAriaLabel = ''; const modifiedButtonAttrs = { ...buttonAttrs }; if (modifiedButtonAttrs && modifiedButtonAttrs['aria-label']) { @@ -270,8 +309,9 @@ class SplitButton extends React.Component { disabled={isDisabled} tabIndex={isDisabled ? '-1' : undefined} aria-disabled={isDisabled} + aria-label={isIconOnly ? primaryOptionLabel : undefined} > - {primaryOptionLabel} + {buttonLabel}