Skip to content
This repository has been archived by the owner on May 24, 2024. It is now read-only.

[terra-dropdown-button] Add support for icons in split button #4080

Merged
merged 15 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/terra-core-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Added
* Added examples and tests for `terra-dropdown-button` `SplitButton` with icons.

## 1.69.0 - (March 27, 2024)

* Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -46,6 +47,7 @@ import { Item, SplitButton } from 'terra-dropdown-button';
<GhostSplitButton />
<DisabledSplitButton />
<BlockSplitButton />
<IconSplitButton />

## Split Button Props
<SplitButtonPropsTable />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import { Item, SplitButton } from 'terra-dropdown-button';
import { IconReply } from 'terra-icon';

import classNames from 'classnames/bind';
import styles from './IconSplitButton.module.scss';

const cx = classNames.bind(styles);

const Example = () => (
<>
<SplitButton
primaryOptionLabel="Reply"
icon={<IconReply />}
onSelect={() => {}}
buttonAttrs={{
'aria-label': 'icon split',
}}
className={cx('icon-button')}
>
<Item label="Reply All" onSelect={() => {}} />
<Item label="Forward" onSelect={() => {}} />
<Item label="Reply in 10 minutes" onSelect={() => {}} />
<Item label="Selective Reply" onSelect={() => {}} />
</SplitButton>
<SplitButton
primaryOptionLabel="Reply"
icon={<IconReply />}
isReversed
onSelect={() => {}}
buttonAttrs={{
'aria-label': 'reverse icon split',
}}
className={cx('icon-button')}
>
<Item label="Reply All" onSelect={() => {}} />
<Item label="Forward" onSelect={() => {}} />
<Item label="Reply in 10 minutes" onSelect={() => {}} />
<Item label="Selective Reply" onSelect={() => {}} />
</SplitButton>
<SplitButton
primaryOptionLabel="Reply"
icon={<IconReply />}
isIconOnly
onSelect={() => {}}
buttonAttrs={{
'aria-label': 'icon only split',
}}
className={cx('icon-button')}
>
<Item label="Reply All" onSelect={() => {}} />
<Item label="Forward" onSelect={() => {}} />
<Item label="Reply in 10 minutes" onSelect={() => {}} />
<Item label="Selective Reply" onSelect={() => {}} />
</SplitButton>
</>
);

export default Example;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:local {
.icon-button {
margin: 5px;
}
}
Original file line number Diff line number Diff line change
@@ -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 (
<div className={cx('container-spacing-wrapper')}>
<SplitButton
primaryOptionLabel="Split"
icon={<IconFeaturedOutlineYellow />}
isIconOnly
metaData={{ key: 'primary-button' }}
onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }}
id="split"
>
<Item id="opt1" label="1st" metaData={{ key: '1st Option' }} onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} />
<Item id="opt2" label="2nd" metaData={{ key: '2nd Option' }} onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} />
<Item id="opt3" label="3rd" metaData={{ key: '3rd Option' }} onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} />
</SplitButton>
<p>
MetaData of :
{message}
</p>
</div>
);
};

export default IconOnlySplitButton;
Original file line number Diff line number Diff line change
@@ -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 (
<div className={cx('container-spacing-wrapper')}>
<SplitButton
primaryOptionLabel="Split"
icon={<IconFeaturedOutlineYellow />}
metaData={{ key: 'primary-button' }}
onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }}
id="split"
>
<Item id="opt1" label="1st" metaData={{ key: '1st Option' }} onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} />
<Item id="opt2" label="2nd" metaData={{ key: '2nd Option' }} onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} />
<Item id="opt3" label="3rd" metaData={{ key: '3rd Option' }} onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} />
</SplitButton>
<p>
MetaData of :
{message}
</p>
</div>
);
};

export default LeftIconSplitButton;
Original file line number Diff line number Diff line change
@@ -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 (
<div className={cx('container-spacing-wrapper')}>
<SplitButton
primaryOptionLabel="Split"
icon={<IconFeaturedOutlineYellow />}
isReversed
metaData={{ key: 'primary-button' }}
onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }}
id="split"
>
<Item id="opt1" label="1st" metaData={{ key: '1st Option' }} onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} />
<Item id="opt2" label="2nd" metaData={{ key: '2nd Option' }} onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} />
<Item id="opt3" label="3rd" metaData={{ key: '3rd Option' }} onSelect={(event, metaData) => { setMessage(` ${metaData.key}`); }} />
</SplitButton>
<p>
MetaData of :
{message}
</p>
</div>
);
};

export default RightIconSplitButton;
3 changes: 3 additions & 0 deletions packages/terra-dropdown-button/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Added
* Added support for icons in `SplitButton`.

## 1.40.0 - (February 15, 2024)

* Changed
Expand Down
5 changes: 4 additions & 1 deletion packages/terra-dropdown-button/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,8 @@
"LICENSE",
"NOTICE",
"README.md"
]
],
"devDependencies": {
"terra-icon": "^3.60.0"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this as a devDependency for use in Jest tests

}
}
42 changes: 41 additions & 1 deletion packages/terra-dropdown-button/src/SplitButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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.
*/
Expand Down Expand Up @@ -190,9 +202,12 @@ class SplitButton extends React.Component {
render() {
const {
children,
isReversed,
icon,
isBlock,
isCompact,
isDisabled,
isIconOnly,
primaryOptionLabel,
onSelect,
variant,
Expand Down Expand Up @@ -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 ? <span className={buttonTextClassnames}>{primaryOptionLabel}</span> : 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 = <span className={iconClassnames}>{cloneIcon}</span>;
}

const buttonLabel = (
<>
{isReversed ? buttonText : buttonIcon}
{isReversed ? buttonIcon : buttonText}
</>
);

let buttonAriaLabel = '';
const modifiedButtonAttrs = { ...buttonAttrs };
if (modifiedButtonAttrs && modifiedButtonAttrs['aria-label']) {
Expand Down Expand Up @@ -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}
</button>
<button
{...modifiedButtonAttrs}
Expand Down
13 changes: 13 additions & 0 deletions packages/terra-dropdown-button/src/SplitButton.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,17 @@
top: var(--terra-dropdown-button-split-type-caret-top, 0.04em);
width: var(--terra-dropdown-button-split-type-caret-width, 1em);
}

.text-first {
margin-right: var(--terra-dropdown-button-split-type-text-first-margin-right, 0.3571rem);
}

.icon-first {
margin-right: var(--terra-dropdown-button-split-type-icon-first-margin-right, 0.3571rem);
}

.icon-svg {
height: var(--terra-dropdown-button-split-type-icon-height, 1rem);
width: var(--terra-dropdown-button-split-type-icon-width, 1rem);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
--terra-dropdown-button-split-type-caret-focus-outline-ghost: 2px dashed #b2b5b6;
--terra-dropdown-button-split-type-caret-hover-box-shadow-ghost: none;

--terra-dropdown-button-split-type-text-first-margin-right: 0.3571rem;
--terra-dropdown-button-split-type-icon-first-margin-right: 0.3571rem;
--terra-dropdown-button-split-type-icon-height: 1rem;
--terra-dropdown-button-split-type-icon-width: 1rem;

/* Icons */
@include terra-inline-svg-var('--terra-dropdown-button-caret-active-background-image-neutral','<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class="is-bidi"><path fill="#b2b5b6" d="M48 12L24 36 0 12h48z"/></svg>');
@include terra-inline-svg-var('--terra-dropdown-button-caret-background-image-neutral', '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class="is-bidi"><path fill="#b2b5b6" d="M48 12L24 36 0 12h48z"/></svg>');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
--terra-dropdown-button-split-type-caret-focus-outline-ghost: 2px dashed #000;
--terra-dropdown-button-split-type-caret-hover-box-shadow-ghost: none;

--terra-dropdown-button-split-type-text-first-margin-right: 0.41667rem;
--terra-dropdown-button-split-type-icon-first-margin-right: 0.41667rem;
--terra-dropdown-button-split-type-icon-height: 1rem;
--terra-dropdown-button-split-type-icon-width: 1rem;

/* Icons */
@include terra-inline-svg-var('--terra-dropdown-button-caret-background-image-neutral', '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class="is-bidi"><path fill="currentColor" d="M24,37.7,0,14.2l3.8-3.9L24,30,44.2,10.3,48,14.2Z"/></svg>');
@include terra-inline-svg-var('--terra-dropdown-button-caret-active-background-image-neutral','<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class="is-bidi"><path fill="currentColor" d="M24,37.7,0,14.2l3.8-3.9L24,30,44.2,10.3,48,14.2Z"/></svg>');
Expand Down
Loading
Loading