diff --git a/apps/public-docsite/package.json b/apps/public-docsite/package.json index 5ed4dc1b3d4d3..4c4b9c1beba36 100644 --- a/apps/public-docsite/package.json +++ b/apps/public-docsite/package.json @@ -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", diff --git a/apps/public-docsite/src/components/IconGrid/IconGrid.module.scss b/apps/public-docsite/src/components/IconGrid/IconGrid.module.scss index 5416c6b20b7c2..1bae40db7534a 100644 --- a/apps/public-docsite/src/components/IconGrid/IconGrid.module.scss +++ b/apps/public-docsite/src/components/IconGrid/IconGrid.module.scss @@ -1,5 +1,13 @@ @import '../../styles/common'; +.root { + min-height: 200px; +} + +.loading { + margin: 20px 0; +} + .grid { display: flex; flex-wrap: wrap; @@ -17,7 +25,7 @@ width: 80px; overflow: hidden; - span { + .iconName { color: $ms-color-neutralPrimary; font-size: $ms-font-size-xs; opacity: 0; @@ -27,7 +35,7 @@ &:hover { overflow: visible; - span { + .iconName { opacity: 1; } } diff --git a/apps/public-docsite/src/components/IconGrid/IconGrid.tsx b/apps/public-docsite/src/components/IconGrid/IconGrid.tsx index 79d5142936880..fcd3304a39949 100644 --- a/apps/public-docsite/src/components/IconGrid/IconGrid.tsx +++ b/apps/public-docsite/src/components/IconGrid/IconGrid.tsx @@ -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 `` tag with classes + * - `react-font` is Fluent UI React font icons, rendered using `` + * - `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; + 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 { - private _iconRefs: { [iconName: string]: React.RefObject }; - 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 ( -
- -
    {icons.map(this._renderIcon)}
+
+ {areIconsLoaded ? ( + <> + + {icons.length ?
    {icons.map(this._renderIcon)}
:
No results
} + + ) : ( + + )}
); } - 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 =