Skip to content

Commit

Permalink
Overhaul v8 icon doc text and add search for SVG icons (#21211)
Browse files Browse the repository at this point in the history
  • Loading branch information
ecraig12345 committed Jan 12, 2022
1 parent 7d40205 commit d2fbf6b
Show file tree
Hide file tree
Showing 18 changed files with 234 additions and 89 deletions.
2 changes: 2 additions & 0 deletions apps/public-docsite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"@fluentui/react-examples": "^8.34.4",
"@fluentui/react-experiments": "^8.4.16",
"@fluentui/react-file-type-icons": "^8.5.8",
"@fluentui/react-icons-mdl2": "^1.2.11",
"@fluentui/react-icons-mdl2-branded": "^1.1.20",
"@fluentui/set-version": "^8.1.5",
"@fluentui/theme": "^2.4.6",
"@fluentui/theme-samples": "^8.2.63",
Expand Down
12 changes: 10 additions & 2 deletions apps/public-docsite/src/components/IconGrid/IconGrid.module.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
@import '../../styles/common';

.root {
min-height: 200px;
}

.loading {
margin: 20px 0;
}

.grid {
display: flex;
flex-wrap: wrap;
Expand All @@ -17,7 +25,7 @@
width: 80px;
overflow: hidden;

span {
.iconName {
color: $ms-color-neutralPrimary;
font-size: $ms-font-size-xs;
opacity: 0;
Expand All @@ -27,7 +35,7 @@
&:hover {
overflow: visible;

span {
.iconName {
opacity: 1;
}
}
Expand Down
154 changes: 115 additions & 39 deletions apps/public-docsite/src/components/IconGrid/IconGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,89 +1,165 @@
import * as React from 'react';
import { Icon, SearchBox } from '@fluentui/react';
import { FontIcon, ISearchBoxProps, SearchBox, Spinner, SpinnerSize } from '@fluentui/react';
import * as stylesImport from './IconGrid.module.scss';
const styles: any = stylesImport;

export interface IIconGridProps {
export interface IFontIconGridProps {
/**
* An array of icons.
* An array of font-based icon names.
*/
icons: { name: string }[];

/**
* If we should render using `Icon` from Fabric
* Type of icons:
* - `core-font` is Fabric Core font icons, rendered in an `<i>` tag with classes
* - `react-font` is Fluent UI React font icons, rendered using `<FontIcon>`
* - `react-svg` is SVG icon components, rendered directly
*/
useFabricIcons?: boolean;
iconType: 'core-font' | 'react-font';
}

export interface IIconGridState {
export interface ISvgIconGridProps {
/**
* A promise loading a package of SVG icon components.
*/
icons: Promise<{ [exportName: string]: any }>;

iconType: 'react-svg';
}

export type IIconGridProps = IFontIconGridProps | ISvgIconGridProps;

interface IFontIconDefinition {
name: string;
ref: React.RefObject<HTMLElement>;
iconType: 'core-font' | 'react-font';
}

interface ISvgIconDefinition {
name: string;
component: React.ComponentType;
iconType: 'react-svg';
}

type IIconDefinition = IFontIconDefinition | ISvgIconDefinition;

interface IIconGridState {
/**
* The text we are filtering the icons by.
*/
searchQuery: string;

/**
* The actual icons to show. This is generated immediately for a static list of font-based icons,
* or generated on load for a list of SVG icon components.
*/
resolvedIcons?: IIconDefinition[];
}

export class IconGrid extends React.Component<IIconGridProps, IIconGridState> {
private _iconRefs: { [iconName: string]: React.RefObject<HTMLElement> };

constructor(props: IIconGridProps) {
super(props);

this.state = {
const state: IIconGridState = {
searchQuery: '',
};

this._iconRefs = {};
for (const icon of props.icons) {
this._iconRefs[icon.name] = React.createRef();
if (props.iconType !== 'react-svg') {
state.resolvedIcons = props.icons
// filter out any json meta properties or whatever
.filter(icon => !!icon?.name)
.map(icon => ({
name: icon.name,
ref: React.createRef(),
// this is duplicated in each icon as a type discriminant
iconType: props.iconType,
}));
}

this.state = state;
}

public render(): JSX.Element {
let { searchQuery } = this.state;
public componentDidMount() {
if (this.props.iconType === 'react-svg') {
this.props.icons.then(iconExports => {
this.setState({
resolvedIcons: Object.keys(iconExports)
// Remove any exports that aren't react components
.filter(exportName => !!iconExports[exportName]?.displayName)
.map(exportName => ({
component: iconExports[exportName],
name: exportName.replace(/Icon$/, ''),
iconType: 'react-svg',
})),
});
});
}
}

public render(): JSX.Element {
const areIconsLoaded = !!this.state.resolvedIcons;
const icons = this._getItems();

return (
<div>
<SearchBox
placeholder="Search icons"
value={searchQuery}
onChange={this._onSearchQueryChanged}
className={styles.searchBox}
/>
<ul className={styles.grid}>{icons.map(this._renderIcon)}</ul>
<div className={styles.root}>
{areIconsLoaded ? (
<>
<SearchBox
placeholder="Search icons"
value={this.state.searchQuery}
onChange={this._onSearchQueryChanged}
className={styles.searchBox}
/>
{icons.length ? <ul className={styles.grid}>{icons.map(this._renderIcon)}</ul> : <div>No results</div>}
</>
) : (
<Spinner label="Loading icons..." className={styles.loading} size={SpinnerSize.large} />
)}
</div>
);
}

private _getItems = (): { name: string }[] => {
const { icons } = this.props;
const { searchQuery } = this.state;
private _getItems = (): IIconDefinition[] => {
const { searchQuery, resolvedIcons } = this.state;

if (!resolvedIcons) {
return [];
}
if (!searchQuery) {
return resolvedIcons;
}

return icons.filter(icon => icon && icon.name && icon.name.toLowerCase().indexOf(searchQuery.toLowerCase()) !== -1);
return resolvedIcons.filter(icon => icon.name.toLowerCase().indexOf(searchQuery.toLowerCase()) !== -1);
};

private _renderIcon = (icon: { name: string }, index?: number): JSX.Element => {
const { useFabricIcons } = this.props;
let iconClassName = `ms-Icon ms-Icon--${icon.name}`;
const iconRef = this._iconRefs[icon.name];
if (iconRef.current && iconRef.current.offsetWidth > 80) {
iconClassName += ' hoverIcon';
private _renderIcon = (icon: IIconDefinition, index?: number): JSX.Element => {
let renderedIcon: JSX.Element;
switch (icon.iconType) {
case 'core-font':
let iconClassName = `ms-Icon ms-Icon--${icon.name}`;
if (icon.ref.current && icon.ref.current.offsetWidth > 80) {
iconClassName += ' hoverIcon';
}
renderedIcon = <i ref={icon.ref} className={iconClassName} title={icon.name} aria-hidden="true" />;
break;
case 'react-font':
renderedIcon = <FontIcon iconName={icon.name} />;
break;
case 'react-svg':
const IconComponent = (icon as ISvgIconDefinition).component;
renderedIcon = <IconComponent />;
break;
}

return (
<li key={icon.name + index} aria-label={icon.name + ' icon'}>
{useFabricIcons ? (
<Icon iconName={icon.name} />
) : (
<i ref={iconRef} className={iconClassName} title={icon.name} aria-hidden="true" />
)}
<span>{icon.name}</span>
{renderedIcon}
<span className={styles.iconName}>{icon.name}</span>
</li>
);
};

private _onSearchQueryChanged = (ev, newValue: string): void => {
private _onSearchQueryChanged: ISearchBoxProps['onChange'] = (ev, newValue) => {
this.setState({
searchQuery: newValue,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,53 @@ export const FabricIconsPage: React.FunctionComponent<IStylesPageProps> = props
};

function _otherSections(platform: Platforms): IPageSectionProps<Platforms>[] {
const [selectedItem, setSelectedItem] = React.useState('react-font');
switch (platform) {
case 'web':
return [
{
sectionName: 'Usage',
sectionName: 'Usage: Font icons',
editUrl: `${baseUrl}/web/FabricIconsUsage.md`,
content: require('!raw-loader?esModule=false!@fluentui/public-docsite/src/pages/Styles/FabricIconsPage/docs/web/FabricIconsUsage.md') as string,
jumpLinks: [
// prettier-ignore
{ text: enDash + ' Fluent UI React (font)', url: 'fluent-ui-react-font-based-icons' },
{ text: enDash + ' Fluent UI React (SVG)', url: 'fluent-ui-react-svg-based-icons' },
{ text: enDash + ' Fluent UI React', url: 'fluent-ui-react' },
{ text: enDash + ' Fabric Core', url: 'fabric-core' },
{ text: enDash + ' Fluent UI Icons tool', url: 'fluent-ui-icons-tool' },
],
},

{
sectionName: 'Usage: SVG icons',
editUrl: `${baseUrl}/web/FabricIconsSvgUsage.md`,
content: require('!raw-loader?esModule=false!@fluentui/public-docsite/src/pages/Styles/FabricIconsPage/docs/web/FabricIconsSvgUsage.md') as string,
},

{
sectionName: 'Available icons',
content: (
<Pivot>
<PivotItem headerText="Fluent UI React (font-based)" className={styles.iconGrid}>
<IconGrid icons={fabricReactIcons} useFabricIcons={true} />
<Pivot
onLinkClick={item => {
setSelectedItem(item.props.itemKey);
}}
>
<PivotItem headerText="Fluent UI React (font)" itemKey="react-font" className={styles.iconGrid}>
<IconGrid icons={fabricReactIcons} iconType="react-font" />
</PivotItem>
<PivotItem headerText="Fabric Core" itemKey="core-font" className={styles.iconGrid}>
<IconGrid icons={fabricCoreIcons} iconType="core-font" />
</PivotItem>
<PivotItem headerText="SVG icons" itemKey="svg" className={styles.iconGrid}>
{
// The icon components are a large download and slow to render, so wait until the tab is clicked
selectedItem === 'svg' && (
<IconGrid icons={import('@fluentui/react-icons-mdl2')} iconType="react-svg" />
)
}
</PivotItem>
<PivotItem headerText="Fabric Core" className={styles.iconGrid}>
<IconGrid icons={fabricCoreIcons} />
<PivotItem headerText="SVG icons (branded)" itemKey="svg-branded" className={styles.iconGrid}>
{selectedItem === 'svg-branded' && (
<IconGrid icons={import('@fluentui/react-icons-mdl2-branded')} iconType="react-svg" />
)}
</PivotItem>
</Pivot>
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Fluent UI uses a custom font for its iconography. This font contains glyphs that you can scale, color, and style in any way. There are also mirrored icons available for right-to-left localization. You can also download and install the icon font to use it with various design apps like Sketch, Figma, or Adobe XD.
Fluent UI primarily uses a custom font for its iconography, released under the [Microsoft Fabric Assets License](https://aka.ms/fluentui-assets-license). As of Fluent UI React version 8, an SVG-based version of the same icon set is available under the MIT license.

**This page is about the general-use monoline icons. See the [brand icons page](#/styles/web/office-brand-icons) for multi-color product icons and the [file type icons page](#/styles/web/file-type-icons) for document icons.**

### When should I use Fluent UI icons?

It is recommended to use Fluent UI icons for command bars, navigation or status indicators. If you need icons to represent Office apps, see the [Office brand icons page](#/styles/web/office-brand-icons) . If you are representing files or digital content, see the [file type icons page](#/styles/web/file-type-icons).
It is recommended to use Fluent UI icons for command bars, navigation or status indicators. If you need icons to represent Office apps, see the [Office brand icons page](#/styles/web/office-brand-icons). If you are representing files or digital content, see the [file type icons page](#/styles/web/file-type-icons).
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
An SVG-based version of Fluent UI's icon set is available from `@fluentui/react-icons-mdl2` and is released under the MIT license. This is the same MDL2 icon set used in the font icons, excluding any branded icons.

Branded SVG icons are available from `@fluentui/react-icons-mdl2-branded` and are still subject to the [Microsoft Fabric Assets License](https://aka.ms/fluentui-assets-license).

Both packages contain SVG icons wrapped in React components. This allows you to import and bundle only the icons you need, resulting in smaller download sizes compared to the font-based approach with `initializeIcons`, which downloads all icons by default.

The SVG icon components are primarily intended to be used directly, rather than registering them globally (via `registerIcons` or `initializeIcons`) and referencing them by name in Fluent UI React's `Icon` component or via `iconProps.name`. For example:

```tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { ChevronDownIcon } from '@fluentui/react-icons-mdl2';

ReactDOM.render(<ChevronDownIcon />, document.body.firstChild);
```

If you do need to make SVG icons available to reference by name (for example, in Fluent UI React components which take `iconProps`), this can be done using `registerIcons` as follows:

```tsx
import { registerIcons } from '@fluentui/react/lib/Styling';
// Note: This approach works with any SVG icon set, not just @fluentui/react-icons-mdl2
import { ChevronDownIcon } from '@fluentui/react-icons-mdl2';

registerIcons({
icons: {
ChevronDown: <ChevronDownIcon />,
},
});
```

0 comments on commit d2fbf6b

Please sign in to comment.