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

[v5] [icons] fix static icon component size, remove built-in webpack loaders #6222

Merged
merged 9 commits into from
Jun 14, 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
1 change: 1 addition & 0 deletions packages/colors/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
*/

export { Colors } from "./colors";
// eslint-disable-next-line deprecation/deprecation
export { LegacyColors } from "./legacyColors";
6 changes: 2 additions & 4 deletions packages/core/src/components/button/buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@
import classNames from "classnames";
import * as React from "react";

import { IconSize } from "@blueprintjs/icons";

import { Classes, Utils } from "../../common";
import { DISPLAYNAME_PREFIX, removeNonHTMLProps } from "../../common/props";
import { mergeRefs } from "../../common/refs";
import { Icon } from "../icon/icon";
import { Spinner } from "../spinner/spinner";
import { Spinner, SpinnerSize } from "../spinner/spinner";
import { AnchorButtonProps, ButtonProps } from "./buttonProps";

/**
Expand Down Expand Up @@ -158,7 +156,7 @@ function renderButtonContents<E extends HTMLAnchorElement | HTMLButtonElement>(
const hasTextContent = !Utils.isReactNodeEmpty(text) || !Utils.isReactNodeEmpty(children);
return (
<>
{loading && <Spinner key="loading" className={Classes.BUTTON_SPINNER} size={IconSize.LARGE} />}
{loading && <Spinner key="loading" className={Classes.BUTTON_SPINNER} size={SpinnerSize.SMALL} />}
<Icon key="leftIcon" icon={icon} />
{hasTextContent && (
<span key="text" className={Classes.BUTTON_TEXT}>
Expand Down
22 changes: 20 additions & 2 deletions packages/core/src/components/icon/icon.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ the name as a string, these components render `<Icon icon="..." />` under the ho
Use the `<Icon>` component to easily render __SVG icons__ in React. The `icon`
prop is typed such that editors can offer autocomplete for known icon names. The
optional `size` prop determines the exact width and height of the icon
image; the icon element itself can be sized separately using CSS.
image; the icon element itself can be also be sized using CSS.

<div class="@ns-callout @ns-intent-primary @ns-icon-info-sign">

Icons may be configured to load in various ways, see ["Loading icons"](#icons/loading-icons).

</div>

The HTML element rendered by `<Icon>` can be customized with the `tagName` prop
(defaults to `span`), and additional props are passed to this element.
Expand Down Expand Up @@ -68,7 +74,19 @@ Custom sizes are supported. The following React component:

@interface IconProps

@## CSS
@## Static components

The `<Icon>` component loads icon paths via dynamic module imports by default. An alternative API
is available in the __@blueprintjs/icons__ package which provides static imports of each icon as
a React component. The example below uses the `<Calendar>` component.

Note that some `<Icon>` props are not yet supported for these components, such as `intent`.

@reactExample IconGeneratedComponentExample

@interface SVGIconProps

@## CSS API

The CSS-only icons API uses the __icon fonts__ from the __@blueprintjs/icons__ package.
Note that _none of Blueprint's React components use the icon font_; it is only provided
Expand Down
4 changes: 0 additions & 4 deletions packages/docs-app/src/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,9 @@ import * as React from "react";

import { Classes, H3, InputGroup, NonIdealState } from "@blueprintjs/core";
import { smartSearch } from "@blueprintjs/docs-theme";
import { Icons as IconLoader } from "@blueprintjs/icons";

import { DocsIcon, DocsIconProps as Icon } from "./docsIcon";

// this compiles all the icon modules into this chunk, so async Icon.load() calls don't block later
IconLoader.loadAll({ loader: "webpack-eager" });

const ICONS_PER_ROW = 5;

export interface IconsState {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2023 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as React from "react";

import { H5, Label, Slider } from "@blueprintjs/core";
import { Example, ExampleProps } from "@blueprintjs/docs-theme";
import { Calendar, IconSize } from "@blueprintjs/icons";

interface ExampleState {
iconSize: number;
}

export class IconGeneratedComponentExample extends React.PureComponent<ExampleProps, ExampleState> {
public state: ExampleState = {
iconSize: IconSize.STANDARD,
};

private handleIconSizeChange = (iconSize: number) => this.setState({ iconSize });

private iconSizeLabelId = "icon-size-label";

public render() {
const { iconSize } = this.state;

const options = (
<>
<H5>Props</H5>
<Label id={this.iconSizeLabelId}>Icon size</Label>
<Slider
labelStepSize={MAX_ICON_SIZE / 5}
min={0}
max={MAX_ICON_SIZE}
showTrackFill={false}
value={iconSize}
onChange={this.handleIconSizeChange}
handleHtmlProps={{ "aria-labelledby": this.iconSizeLabelId }}
/>
</>
);

return (
<Example options={options} {...this.props}>
<Calendar size={iconSize} />
</Example>
);
}
}

const MAX_ICON_SIZE = 100;
3 changes: 2 additions & 1 deletion packages/docs-app/src/examples/core-examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export { HotkeyPiano } from "./hotkeyPiano";
export { HotkeyTesterExample } from "./hotkeyTesterExample";
export { HotkeysTarget2Example } from "./hotkeysTarget2Example";
export { HTMLSelectExample } from "./htmlSelectExample";
export * from "./iconExample";
export { IconExample } from "./iconExample";
export { IconGeneratedComponentExample } from "./iconGeneratedComponentExample";
export * from "./menuExample";
export { MenuItemExample } from "./menuItemExample";
export * from "./multiSliderExample";
Expand Down
4 changes: 4 additions & 0 deletions packages/docs-app/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ import * as ReactDOM from "react-dom";

import { docsData } from "@blueprintjs/docs-data";
import { createDefaultRenderers, ReactDocsTagRenderer, ReactExampleTagRenderer } from "@blueprintjs/docs-theme";
import { Icons } from "@blueprintjs/icons";

import { BlueprintDocs } from "./components/blueprintDocs";
import * as ReactDocs from "./tags/reactDocs";
import { reactExamples } from "./tags/reactExamples";

// load all icons up front so that they do not experience a flash of unstyled content (but we don't need to block on this promise)
Icons.loadAll();

const reactDocs = new ReactDocsTagRenderer(ReactDocs as any);
const reactExample = new ReactExampleTagRenderer(reactExamples);

Expand Down
3 changes: 3 additions & 0 deletions packages/icons/scripts/generate-icon-components.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ for (const [iconName, icon16pxPath] of Object.entries(iconPaths[16])) {
}
writeFileSync(
join(generatedSrcDir, `components/${iconName}.tsx`),
// Notes on icon component template implementation:
// - path "translation" transform must use "viewbox" dimensions, not "size", in order to avoid issues
// like https://github.com/palantir/blueprint/issues/6220
iconComponentTemplate({
iconName,
icon16pxPath,
Expand Down
10 changes: 4 additions & 6 deletions packages/icons/scripts/iconComponent.tsx.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ import { IconSize } from "../../iconTypes";
import { SVGIconContainer } from "../../svgIconContainer";

export const {{pascalCase iconName}}: React.FC<SVGIconProps> = React.forwardRef<any, SVGIconProps>((props, ref) => {
const translation = `${-1 * props.size! / {{pathScaleFactor}} / 2}`;
const isLarge = props.size! >= IconSize.LARGE;
const pixelGridSize = isLarge ? IconSize.LARGE : IconSize.STANDARD;
const translation = `${-1 * pixelGridSize / {{pathScaleFactor}} / 2}`;
return (
<SVGIconContainer iconName="{{iconName}}" ref={ref} {...props}>
<path
d={
props.size! < IconSize.LARGE
? "{{icon16pxPath}}"
: "{{icon20pxPath}}"
}
d={isLarge ? "{{icon20pxPath}}" : "{{icon16pxPath}}"}
fillRule="evenodd"
transform-origin="center"
transform={`scale({{pathScaleFactor}}, -{{pathScaleFactor}}) translate(${translation}, ${translation})`}
Expand Down
File renamed without changes.
37 changes: 16 additions & 21 deletions packages/icons/src/iconLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import { type IconName, IconNames } from "./iconNames";
import { type IconPaths, IconSize } from "./iconTypes";
import { wrapWithTimer } from "./loaderUtils";
import { webpackEagerPathsLoader, webpackLazyOncePathsLoader, webpackLazyPathsLoader } from "./webpackIconLoaders";

/** Given an icon name and size, loads the icon paths that define it. */
export type IconPathsLoader = (iconName: IconName, iconSize: IconSize) => Promise<IconPaths>;
Expand All @@ -27,17 +26,29 @@ export interface IconLoaderOptions {
* The id of a built-in loader, or a custom loader function.
*
* @see https://blueprintjs.com/docs/versions/5/#icons/loading-icons
* @default "webpack-lazy-once"
* @default undefined (equivalent to "split-by-size")
*/
loader?: "webpack-lazy-once" | "webpack-lazy" | "webpack-eager" | IconPathsLoader;
loader?: "split-by-size" | "all" | IconPathsLoader;
}

async function getLoaderFn(options: IconLoaderOptions): Promise<IconPathsLoader> {
const { loader = singleton.defaultLoader } = options;

if (typeof loader === "function") {
return loader;
} else if (loader === "all") {
return (await import("./paths-loaders/allPathsLoader")).allPathsLoader;
} else {
return (await import("./paths-loaders/splitPathsBySizeLoader")).splitPathsBySizeLoader;
}
}

/**
* Blueprint icons loader.
*/
export class Icons {
/** @internal */
public defaultLoader: Required<IconLoaderOptions>["loader"] = "webpack-lazy-once";
public defaultLoader: IconLoaderOptions["loader"] = "split-by-size";

/** @internal */
public loadedIconPaths16: Map<IconName, IconPaths> = new Map();
Expand Down Expand Up @@ -112,23 +123,7 @@ export class Icons {
return;
}

const { loader = singleton.defaultLoader } = options;

const loaderFn =
typeof loader == "function"
? loader
: loader === "webpack-eager"
? webpackEagerPathsLoader
: loader === "webpack-lazy"
? webpackLazyPathsLoader
: loader === "webpack-lazy-once"
? webpackLazyOncePathsLoader
: undefined;

if (loaderFn === undefined) {
console.error(`[Blueprint] Unknown icon loader: ${loader}`);
return;
}
const loaderFn = await getLoaderFn(options);

try {
const supportedSize = size < IconSize.LARGE ? IconSize.STANDARD : IconSize.LARGE;
Expand Down
2 changes: 1 addition & 1 deletion packages/icons/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

// N.B. these named imports will trigger bundlers to statically loads all icon path modules
export { IconSvgPaths16, IconSvgPaths20, getIconPaths } from "./iconPaths";
export { IconSvgPaths16, IconSvgPaths20, getIconPaths } from "./allPaths";

export { Icons, IconLoaderOptions, IconPathsLoader } from "./iconLoader";
export { SVGIconProps } from "./svgIconProps";
Expand Down
Loading