Skip to content

Commit

Permalink
Add endIcon prop and change existing icon prop to startIcon
Browse files Browse the repository at this point in the history
  • Loading branch information
nishasy committed Oct 13, 2023
1 parent 3f06c47 commit 8e3a7c7
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/rich-jars-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-blocks-button": minor
---

Add `endIcon` prop and change existing `icon` prop to `startIcon`
2 changes: 1 addition & 1 deletion .storybook/components/component-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const ComponentInfo: React.FC<Props> = (
href={`https://github.com/Khan/wonder-blocks/tree/main/packages/${packageFolder}`}
target="_blank"
style={{color: "black"}}
icon={githubLogo}
startIcon={githubLogo}
>
Source code
</Button>
Expand Down
18 changes: 16 additions & 2 deletions __docs__/wonder-blocks-button/button.argtypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,22 @@ export default {
description: "Text to appear on the button.",
type: {name: "string", required: true},
},
icon: {
description: "A Phosphor icon asset (imported as a static SVG file).",
startIcon: {
description: `A Phosphor icon asset (imported as a static SVG file)
that will appear at the start of the button (left for LTR, right
for RTL).`,
type: {name: "other", value: "PhosphorIconAsset", required: false},
control: {type: "select"},
options: IconMappings,
table: {
category: "Layout",
type: {summary: "PhosphorIconAsset"},
},
},
endIcon: {
description: `A Phosphor icon asset (imported as a static SVG file)
that will appear at the end of the button (right for LTR, left
for RTL).`,
type: {name: "other", value: "PhosphorIconAsset", required: false},
control: {type: "select"},
options: IconMappings,
Expand Down
74 changes: 69 additions & 5 deletions __docs__/wonder-blocks-button/button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Color from "@khanacademy/wonder-blocks-color";
import {View} from "@khanacademy/wonder-blocks-core";
import {Strut} from "@khanacademy/wonder-blocks-layout";
import Spacing from "@khanacademy/wonder-blocks-spacing";
import {LabelMedium} from "@khanacademy/wonder-blocks-typography";
import {LabelMedium, LabelLarge} from "@khanacademy/wonder-blocks-typography";

import Button from "@khanacademy/wonder-blocks-button";
import packageConfig from "../../packages/wonder-blocks-button/package.json";
Expand Down Expand Up @@ -143,6 +143,10 @@ export const styles: StyleDeclaration = StyleSheet.create({
background: Color.offWhite,
padding: Spacing.medium_16,
},
label: {
marginTop: Spacing.large_24,
marginBottom: Spacing.xSmall_8,
},
});

export const Variants: StoryComponentType = () => (
Expand Down Expand Up @@ -314,11 +318,67 @@ const kinds = ["primary", "secondary", "tertiary"] as const;

const IconExample = () => (
<View>
<LabelLarge style={styles.label}>Using `startIcon` prop</LabelLarge>
<View style={styles.row}>
{kinds.map((kind, idx) => (
<Button
kind={kind}
startIcon={pencilSimple}
style={styles.button}
key={idx}
>
{kind}
</Button>
))}
</View>
<View style={styles.row}>
{kinds.map((kind, idx) => (
<Button
kind={kind}
startIcon={pencilSimpleBold}
style={styles.button}
key={idx}
size="small"
>
{`${kind} small`}
</Button>
))}
</View>
<LabelLarge style={styles.label}>Using `endIcon` prop</LabelLarge>
<View style={styles.row}>
{kinds.map((kind, idx) => (
<Button
kind={kind}
endIcon={pencilSimple}
style={styles.button}
key={idx}
>
{kind}
</Button>
))}
</View>
<View style={styles.row}>
{kinds.map((kind, idx) => (
<Button
kind={kind}
endIcon={pencilSimpleBold}
style={styles.button}
key={idx}
size="small"
>
{`${kind} small`}
</Button>
))}
</View>
<LabelLarge style={styles.label}>
Using both `startIcon` and `endIcon` props
</LabelLarge>
<View style={styles.row}>
{kinds.map((kind, idx) => (
<Button
kind={kind}
icon={pencilSimple}
startIcon={pencilSimple}
endIcon={plus}
style={styles.button}
key={idx}
>
Expand All @@ -330,7 +390,8 @@ const IconExample = () => (
{kinds.map((kind, idx) => (
<Button
kind={kind}
icon={pencilSimpleBold}
startIcon={pencilSimpleBold}
endIcon={plus}
style={styles.button}
key={idx}
size="small"
Expand All @@ -343,7 +404,10 @@ const IconExample = () => (
);

/**
* Buttons can have an icon on it's left side.
* Buttons can have a start icon or an end icon. The `startIcon` prop
* results in the icon appearing before the label (left for LTR, right for RTL)
* and the `endIcon` prop results in the icon appearing after the label (right
* for LTR, left for RTL).
*
* __NOTE:__ Icons are available from the [Phosphor
* Icons](https://phosphoricons.com/) library.
Expand Down Expand Up @@ -487,7 +551,7 @@ export const TruncatingLabels: StoryComponentType = {
label too long for the parent container
</Button>
<Strut size={16} />
<Button onClick={() => {}} style={{maxWidth: 200}} icon={plus}>
<Button onClick={() => {}} style={{maxWidth: 200}} startIcon={plus}>
label too long for the parent container
</Button>
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -830,32 +830,86 @@ describe("Button", () => {
});

describe("button with icon", () => {
test("icon is displayed when button contains icon", () => {
test("icon is displayed when button contains startIcon", () => {
// Arrange
render(
<Button testId={"button-focus-test"} icon={plus}>
<Button testId={"button-focus-test"} startIcon={plus}>
Label
</Button>,
);

// Act
const icon = screen.getByTestId("button-focus-test-icon");
const icon = screen.getByTestId("button-focus-test-start-icon");

// Assert
expect(icon).toBeInTheDocument();
expect(icon).toHaveAttribute("aria-hidden", "true");
});

test("icon should be hidden from Screen Readers", () => {
test("icon is displayed when button contains endIcon", () => {
// Arrange
render(
<Button testId={"button-focus-test"} icon={plus}>
<Button testId={"button-focus-test"} endIcon={plus}>
Label
</Button>,
);

// Act
const icon = screen.getByTestId("button-focus-test-icon");
const icon = screen.getByTestId("button-focus-test-end-icon");

// Assert
expect(icon).toBeInTheDocument();
expect(icon).toHaveAttribute("aria-hidden", "true");
});

test("both icons are displayed when button contains startIcon and endIcon", () => {
// Arrange
render(
<Button
testId={"button-focus-test"}
startIcon={plus}
endIcon={plus}
>
Label
</Button>,
);

// Act
const startIcon = screen.getByTestId(
"button-focus-test-start-icon",
);
const endIcon = screen.getByTestId("button-focus-test-end-icon");

// Assert
expect(startIcon).toBeInTheDocument();
expect(endIcon).toBeInTheDocument();
});

test("start icon should be hidden from Screen Readers", () => {
// Arrange
render(
<Button testId={"button-focus-test"} startIcon={plus}>
Label
</Button>,
);

// Act
const icon = screen.getByTestId("button-focus-test-start-icon");

// Assert
expect(icon).toHaveAttribute("aria-hidden", "true");
});

test("end icon should be hidden from Screen Readers", () => {
// Arrange
render(
<Button testId={"button-focus-test"} endIcon={plus}>
Label
</Button>,
);

// Act
const icon = screen.getByTestId("button-focus-test-end-icon");

// Assert
expect(icon).toHaveAttribute("aria-hidden", "true");
Expand Down
38 changes: 22 additions & 16 deletions packages/wonder-blocks-button/src/components/button-core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,17 @@ const ButtonCore: React.ForwardRefExoticComponent<
testId,
type = undefined,
spinner,
icon,
startIcon,
endIcon,
id,
waiting: _,
...restProps
} = props;

const iconWidth = icon
? size === "small"
? theme.size.width.medium
: theme.size.width.large
: 0;
const buttonStyles = _generateStyles(
color,
kind,
light,
iconWidth,
size,
theme,
themeName,
Expand All @@ -80,7 +75,8 @@ const ButtonCore: React.ForwardRefExoticComponent<
const defaultStyle = [
sharedStyles.shared,
disabled && sharedStyles.disabled,
icon && sharedStyles.withIcon,
startIcon && sharedStyles.withStartIcon,
endIcon && sharedStyles.withEndIcon,
buttonStyles.default,
disabled && buttonStyles.disabled,
// apply focus effect only to default and secondary buttons
Expand Down Expand Up @@ -141,12 +137,12 @@ const ButtonCore: React.ForwardRefExoticComponent<

const contents = (
<React.Fragment>
{icon && (
{startIcon && (
<ButtonIcon
size={iconSize}
icon={icon}
style={sharedStyles.icon}
testId={testId ? `${testId}-icon` : undefined}
icon={startIcon}
style={sharedStyles.startIcon}
testId={testId ? `${testId}-start-icon` : undefined}
/>
)}
{label}
Expand All @@ -158,6 +154,14 @@ const ButtonCore: React.ForwardRefExoticComponent<
testId={`${testId || "button"}-spinner`}
/>
)}
{endIcon && (
<ButtonIcon
size={iconSize}
icon={endIcon}
style={sharedStyles.endIcon}
testId={testId ? `${testId}-end-icon` : undefined}
/>
)}
</React.Fragment>
);

Expand Down Expand Up @@ -265,8 +269,11 @@ const themedSharedStyles: ThemedStylesFn<ButtonThemeContract> = (theme) => ({
spinner: {
position: "absolute",
},
icon: {
marginRight: theme.padding.small,
startIcon: {
marginInlineEnd: theme.padding.small,
},
endIcon: {
marginInlineStart: theme.padding.small,
},
});

Expand All @@ -276,7 +283,6 @@ const _generateStyles = (
buttonColor = "default",
kind: "primary" | "secondary" | "tertiary",
light: boolean,
iconWidth: number,
size: "large" | "medium" | "small",
theme: ButtonThemeContract,
themeName: string,
Expand All @@ -286,7 +292,7 @@ const _generateStyles = (
? theme.color.bg.critical.default
: theme.color.bg.action.default;

const buttonType = `${color}-${kind}-${light}-${iconWidth}-${size}-${themeName}`;
const buttonType = `${color}-${kind}-${light}-${size}-${themeName}`;

if (styles[buttonType]) {
return styles[buttonType];
Expand Down
10 changes: 8 additions & 2 deletions packages/wonder-blocks-button/src/components/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ export type SharedProps =
*/
children: string;
/**
* A Phosphor icon asset (imported as a static SVG file).
* A Phosphor icon asset (imported as a static SVG file) that
* will appear at the start of the button (left for LTR, right for RTL).
*/
icon?: PhosphorIconAsset;
startIcon?: PhosphorIconAsset;
/**
* A Phosphor icon asset (imported as a static SVG file) that
* will appear at the end of the button (right for LTR, left for RTL).
*/
endIcon?: PhosphorIconAsset;
/**
* If true, replaces the contents with a spinner.
*
Expand Down

0 comments on commit 8e3a7c7

Please sign in to comment.