Skip to content

Commit

Permalink
feat(button): allow icons to be placed left and right of button (#866)
Browse files Browse the repository at this point in the history
* feat(button): allow icons to be placed left and right of button
  • Loading branch information
ogermain-kronos authored May 1, 2024
1 parent 2249458 commit 43850e3
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 1 deletion.
43 changes: 43 additions & 0 deletions packages/react/src/components/buttons/button.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { shallow } from 'enzyme';
import { doNothing } from '../../test-utils/callbacks';
import { mountWithProviders, renderWithProviders } from '../../test-utils/renderer';
import { Button } from './button';
import { getByTestId } from '../../test-utils/enzyme-selectors';

describe('Button', () => {
test('onClick callback is called when clicked', () => {
Expand Down Expand Up @@ -176,4 +178,45 @@ describe('Button', () => {
modifier: ':focus',
});
});

test('icons can be placed left and right of the button', () => {
const wrapper = shallow(
<Button
buttonType="primary"
label="Primary Button"
leftIconName="chevronLeft"
rightIconName="chevronRight"
/>,
);

expect(getByTestId(wrapper, 'left-icon')).toHaveLength(1);
expect(getByTestId(wrapper, 'right-icon')).toHaveLength(1);
});

test('icons can be placed on one side of the button', () => {
const wrapper = shallow(
<Button
buttonType="primary"
label="Primary Button"
rightIconName="chevronRight"
/>,
);

expect(getByTestId(wrapper, 'left-icon')).toHaveLength(0);
expect(getByTestId(wrapper, 'right-icon')).toHaveLength(1);
});

test('has left and right icons', () => {
const tree = renderWithProviders(
<Button
buttonType="primary"
label="Primary Button"
leftIconName="chevronLeft"
rightIconName="chevronRight"

/>,
);

expect(tree).toMatchSnapshot();
});
});
122 changes: 122 additions & 0 deletions packages/react/src/components/buttons/button.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,128 @@ exports[`Button has destructive-secondary styles 1`] = `
</button>
`;

exports[`Button has left and right icons 1`] = `
.c0 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: inherit;
border: 1px solid;
border-radius: 1.5rem;
box-sizing: border-box;
color: inherit;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
font-family: inherit;
font-size: 0.75rem;
font-weight: var(--font-bold);
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-letter-spacing: 0.025rem;
-moz-letter-spacing: 0.025rem;
-ms-letter-spacing: 0.025rem;
letter-spacing: 0.025rem;
line-height: 1rem;
min-height: var(--size-2x);
min-width: 2rem;
outline: none;
padding: 0 var(--spacing-2x);
text-transform: uppercase;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.c0 {
outline: 2px solid transparent;
outline-offset: -2px;
}
.c0:focus {
box-shadow: 0 0 0 2px #006296;
outline: 2px solid #84C6EA;
outline-offset: -2px;
}
.c0 > svg {
color: inherit;
height: var(--size-1x);
width: var(--size-1x);
}
.c2 {
margin-right: var(--spacing-1x);
}
.c3 {
margin-left: var(--spacing-1x);
}
.c1 {
background-color: #006296;
border-color: #006296;
color: #FFFFFF;
}
.c1 {
outline: 2px solid transparent;
outline-offset: -2px;
}
.c1:focus {
box-shadow: 0 0 0 2px #006296;
outline: 2px solid #84C6EA;
outline-offset: -2px;
}
.c1:hover,
.c1[aria-expanded='true'] {
background-color: #003A5A;
border-color: #003A5A;
color: #FFFFFF;
}
.c1:disabled {
background-color: #84C6EA;
border-color: #84C6EA;
color: #FFFFFF;
}
<button
class="c0 c1"
type="button"
>
<svg
aria-hidden="true"
class="c2"
color="currentColor"
data-testid="left-icon"
focusable="false"
height="24"
width="24"
/>
Primary Button
<svg
aria-hidden="true"
class="c3"
color="currentColor"
data-testid="right-icon"
focusable="false"
height="24"
width="24"
/>
</button>
`;

exports[`Button has mobile styles 1`] = `
.c0 {
-webkit-align-items: center;
Expand Down
44 changes: 43 additions & 1 deletion packages/react/src/components/buttons/button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { FocusEventHandler, forwardRef, KeyboardEvent, MouseEvent, PropsWithChildren, ReactElement, Ref } from 'react';
import {
FocusEventHandler,
forwardRef,
KeyboardEvent,
MouseEvent,
PropsWithChildren,
ReactElement,
Ref,
} from 'react';
import styled from 'styled-components';
import {
Icon,
IconName,
} from '../icon/icon';
import { ResolvedTheme } from '../../themes/theme';
import { useDeviceContext } from '../device-context-provider/device-context-provider';
import { AbstractButton, ButtonType, getButtonTypeStyles } from './abstract-button';
Expand Down Expand Up @@ -32,12 +44,23 @@ export interface ButtonProps {
title?: string;
type?: Type;

leftIconName?: IconName;
rightIconName?: IconName;

onClick?(event: MouseEvent<HTMLButtonElement>): void;
onFocus?: FocusEventHandler<HTMLButtonElement>;
onBlur?: FocusEventHandler<HTMLButtonElement>;
onKeyDown?(event: KeyboardEvent<HTMLButtonElement>): void;
}

const LeftIcon = styled(Icon)`
margin-right: var(--spacing-1x);
`;

const RightIcon = styled(Icon)`
margin-left: var(--spacing-1x);
`;

const StyledButton = styled(AbstractButton)<{ theme: ResolvedTheme } & ButtonProps>`
${getButtonTypeStyles}
`;
Expand All @@ -55,10 +78,13 @@ export const Button = forwardRef<HTMLButtonElement, PropsWithChildren<ButtonProp
onBlur,
onKeyDown,
title,
leftIconName,
rightIconName,
type = 'button',
...props
}: PropsWithChildren<ButtonProps>, ref: Ref<HTMLButtonElement>): ReactElement => {
const { isMobile } = useDeviceContext();
const iconSize = props?.size === 'small' && !isMobile ? '16' : '24';

return (
<StyledButton
Expand All @@ -78,7 +104,23 @@ export const Button = forwardRef<HTMLButtonElement, PropsWithChildren<ButtonProp
{...props /* eslint-disable-line react/jsx-props-no-spreading *//* To spread aria-* and data-* */}
>
{children}
{leftIconName && (
<LeftIcon
aria-hidden="true"
data-testid="left-icon"
name={leftIconName}
size={iconSize}
/>
)}
{label}
{rightIconName && (
<RightIcon
aria-hidden="true"
data-testid="right-icon"
name={rightIconName}
size={iconSize}
/>
)}
</StyledButton>
);
});
Expand Down
13 changes: 13 additions & 0 deletions packages/storybook/stories/button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,16 @@ export const PrimaryWithIcon: Story = {
</Button>
),
};

export const PrimaryWithIconsOnBothSides: Story = {
args: {
buttonType: 'primary',
label: 'Icons on both sides',
size: 'medium',
leftIconName: 'chevronLeft',
rightIconName: 'chevronRight',
},
render: (args) => (
<Button {...args /* eslint-disable-line react/jsx-props-no-spreading */} />
),
};

0 comments on commit 43850e3

Please sign in to comment.