Skip to content
Open
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
80 changes: 60 additions & 20 deletions packages/core/src/components/icon/Icon.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { storybookLayoutDecorator, StoryLabel } from "@storybook-common";

import { Buggy } from "@blueprintjs/icons";
import { Flex } from "@blueprintjs/labs";

import { Intent } from "../../common";
import { Colors, Intent } from "../../common";

import { Icon, IconSize } from "./icon";

Expand All @@ -17,27 +18,37 @@ const meta: Meta<typeof Icon> = {
decorators: [storybookLayoutDecorator],
args: {
icon: "buggy",
intent: Intent.NONE,
size: IconSize.STANDARD,
intent: "none",
color: undefined,
title: undefined,
tagName: "span",
autoLoad: true,
},
argTypes: {
icon: {
control: "text",
},
intent: {
control: "select",
options: Object.values(Intent),
},
size: {
control: "select",
options: [IconSize.STANDARD, IconSize.LARGE],
control: "number",
},
icon: {
color: {
control: "color",
},
title: {
control: "text",
},
color: {
tagName: {
control: "text",
},
autoLoad: {
control: "boolean",
},
className: { table: { disable: true } },
autoLoad: { table: { disable: true } },
svgProps: { table: { disable: true } },
},
} satisfies Meta<typeof Icon>;
Expand All @@ -48,11 +59,7 @@ type Story = StoryObj<typeof meta>;
/**
* A basic icon with default styling.
*/
export const Default: Story = {
args: {
icon: "buggy",
},
};
export const Default: Story = {};

/**
* Use the `intent` prop to apply a semantic color that conveys the purpose or status of the icon.
Expand Down Expand Up @@ -80,15 +87,48 @@ export const SizeExample: Story = {
size: { table: { disable: true } },
},
render: args => (
<Flex flexDirection="column" gap={10}>
<Flex flexDirection="column" gap={2} alignItems="center">
<StoryLabel title="standard size - 16px" />
<Icon {...args} size={IconSize.STANDARD} />
</Flex>
<Flex gap={5}>
<Icon {...args} size={IconSize.STANDARD} />
<Icon {...args} size={IconSize.LARGE} />
<Icon {...args} size={48} />
</Flex>
),
};

<Flex flexDirection="column" gap={2} alignItems="center">
<StoryLabel title="large size - 20px" />
<Icon {...args} size={IconSize.LARGE} />
/**
* Use the `color` prop to override the icon fill with a custom CSS color.
* This takes precedence over `intent`.
*/
export const ColorExample: Story = {
name: "Color",
render: args => (
<Flex gap={4} alignItems="center">
{[Colors.BLUE3, Colors.FOREST3, Colors.GOLD3, Colors.RED3, Colors.INDIGO4].map(color => (
<Flex key={color} flexDirection="column" gap={1} alignItems="center">
<Icon {...args} color={color} />
<StoryLabel title={color} />
</Flex>
))}
</Flex>
),
};

/**
* The `icon` prop accepts either a string icon name or a React element (typically an icon
* component from `@blueprintjs/icons`). When an element is provided, `<Icon>` clones it and
* merges the parent-provided `className` and intent class onto its root.
*/
export const ElementIcon: Story = {
name: "Element icon",
render: args => (
<Flex gap={4} alignItems="center">
<Flex flexDirection="column" gap={1} alignItems="center">
<Icon {...args} icon="buggy" />
<StoryLabel title='icon="buggy"' />
</Flex>
<Flex flexDirection="column" gap={1} alignItems="center">
<Icon {...args} icon={<Buggy size={args.size} />} />
<StoryLabel title="icon={<Buggy />}" />
</Flex>
</Flex>
),
Expand Down
12 changes: 9 additions & 3 deletions packages/core/src/components/icon/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import classNames from "classnames";
import { createElement, forwardRef, useEffect, useState } from "react";
import { cloneElement, createElement, forwardRef, isValidElement, useEffect, useState } from "react";

import {
type DefaultSVGIconProps,
Expand Down Expand Up @@ -56,8 +56,9 @@ export interface IconOwnProps {
* - If given an `IconName` (a string literal union of all icon names), that
* icon will be rendered as an `<svg>` with `<path>` tags. Unknown strings
* will render a blank icon to occupy space.
* - If given a `React.JSX.Element`, that element will be rendered and _all other
* props on this component are ignored._ This type is supported to
* - If given a `React.JSX.Element`, that element will be rendered with the
* parent-provided `className` and intent class merged onto its root. All
* other props on this component are ignored. This type is supported to
* simplify icon support in other Blueprint components. As a consumer, you
* should avoid using `<Icon icon={<Element />}` directly; simply render
* `<Element />` instead.
Expand Down Expand Up @@ -157,6 +158,11 @@ export const Icon: IconComponent = forwardRef(<T extends Element>(props: IconPro
if (icon == null || typeof icon === "boolean") {
return null;
} else if (typeof icon !== "string") {
if (isValidElement<{ className?: string }>(icon)) {
return cloneElement(icon, {
className: classNames(icon.props.className, className, Classes.intentClass(intent)),
});
}
return icon;
}

Expand Down