Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] fix(Callout): load intent icons statically #6254

Merged
merged 3 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
30 changes: 17 additions & 13 deletions packages/core/src/components/callout/callout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import classNames from "classnames";
import * as React from "react";

import { IconName } from "@blueprintjs/icons";
import { Error, IconName, InfoSign, Tick, WarningSign } from "@blueprintjs/icons";

import {
AbstractPureComponent,
Expand Down Expand Up @@ -73,42 +73,46 @@ export class Callout extends AbstractPureComponent<CalloutProps> {

public render() {
const { className, children, icon, intent, title, ...htmlProps } = this.props;
const iconName = this.getIconName(icon, intent);
const iconElement = this.renderIcon(icon, intent);
const classes = classNames(
Classes.CALLOUT,
Classes.intentClass(intent),
{ [Classes.CALLOUT_ICON]: iconName != null },
{ [Classes.CALLOUT_ICON]: iconElement != null },
className,
);

return (
<div className={classes} {...htmlProps}>
{iconName && <Icon icon={iconName} aria-hidden={true} tabIndex={-1} />}
{iconElement}
{title && <H5>{title}</H5>}
{children}
</div>
);
}

private getIconName(icon?: CalloutProps["icon"], intent?: Intent): IconName | MaybeElement {
private renderIcon(icon?: CalloutProps["icon"], intent?: Intent): IconName | MaybeElement {
// 1. no icon
if (icon === null) {
if (icon === null || icon === false) {
return undefined;
}
// 2. defined iconName prop

const iconProps = { "aria-hidden": true, tabIndex: -1 };

// 2. icon specified by name or as a custom SVG element
if (icon !== undefined) {
return icon;
return <Icon icon={icon} {...iconProps} />;
}
// 3. default intent icon

// 3. icon specified by intent prop
switch (intent) {
case Intent.DANGER:
return "error";
return <Error {...iconProps} />;
case Intent.PRIMARY:
return "info-sign";
return <InfoSign {...iconProps} />;
case Intent.WARNING:
return "warning-sign";
return <WarningSign {...iconProps} />;
case Intent.SUCCESS:
return "tick";
return <Tick {...iconProps} />;
default:
return undefined;
}
Expand Down
45 changes: 29 additions & 16 deletions packages/core/test/callout/calloutTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,56 @@
*/

import { assert } from "chai";
import { shallow } from "enzyme";
import { mount } from "enzyme";
import * as React from "react";

import { Callout, Classes, H5, Icon, Intent } from "../../src";
import { IconNames } from "@blueprintjs/icons";

import { Callout, Classes, H5, Intent } from "../../src";

describe("<Callout>", () => {
let containerElement: HTMLElement | undefined;

beforeEach(() => {
containerElement = document.createElement("div");
document.body.appendChild(containerElement);
});
afterEach(() => {
containerElement?.remove();
});

it("supports className", () => {
const wrapper = shallow(<Callout className="foo" />);
const wrapper = mount(<Callout className="foo" />, { attachTo: containerElement });
assert.isFalse(wrapper.find(H5).exists(), "expected no H5");
assert.isTrue(wrapper.hasClass(Classes.CALLOUT));
assert.isTrue(wrapper.hasClass("foo"));
assert.isTrue(wrapper.find(`.${Classes.CALLOUT}`).hostNodes().exists());
assert.isTrue(wrapper.find(`.foo`).hostNodes().exists());
});

it("supports icon", () => {
const wrapper = shallow(<Callout icon="graph" />);
assert.isTrue(wrapper.find(Icon).exists());
const wrapper = mount(<Callout icon={IconNames.GRAPH} />, { attachTo: containerElement });
assert.isTrue(wrapper.find(`[data-icon="${IconNames.GRAPH}"]`).exists());
});

it("supports intent", () => {
const wrapper = shallow(<Callout intent={Intent.DANGER} />);
assert.isTrue(wrapper.hasClass(Classes.INTENT_DANGER));
const wrapper = mount(<Callout intent={Intent.DANGER} />, { attachTo: containerElement });
assert.isTrue(wrapper.find(`.${Classes.INTENT_DANGER}`).hostNodes().exists());
});

it("intent renders default icon", () => {
const wrapper = shallow(<Callout intent={Intent.PRIMARY} />);
assert.isTrue(wrapper.find(Icon).exists());
it("intent='primary' renders the associated default icon", () => {
const wrapper = mount(<Callout intent={Intent.PRIMARY} />, { attachTo: containerElement });
assert.isTrue(wrapper.find(`[data-icon="${IconNames.INFO_SIGN}"]`).exists());
});

it("icon=null removes intent icon", () => {
const wrapper = shallow(<Callout icon={null} intent={Intent.PRIMARY} />);
assert.isFalse(wrapper.find(Icon).exists());
const wrapper = mount(<Callout icon={null} intent={Intent.PRIMARY} />, { attachTo: containerElement });
assert.isFalse(wrapper.find(`[data-icon]`).exists());
});

it("renders optional title element", () => {
const wrapper = shallow(<Callout title="title" />);
const wrapper = mount(<Callout title="title" />, { attachTo: containerElement });
assert.isTrue(wrapper.find(H5).exists());
// NOTE: JSX cannot be passed through `title` prop due to conflict with HTML props
// shallow(<Callout title={<em>typings fail</em>} />);
// @ts-expect-error
mount(<Callout title={<em>typings fail</em>} />);
});
});