@pmwcs/icon
Version:
PMWCS icon component
160 lines (133 loc) • 4.66 kB
JavaScript
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import { h, cloneElement, isValidElement } from 'preact';
import { useProviderContext } from '@pmwcs/provider';
import { Tag, classNames, getDisplayName } from '@pmwcs/base';
/**
* Given content, tries to figure out an appropriate strategy for it
*/
const processAutoStrategy = function (content, prefix) {
// check for URLS
if (typeof content === 'string' && content.includes('/')) {
return 'url';
} // handle prefixed
if (typeof content === 'string' && prefix) {
return 'className';
} // handle JSX components
if (isValidElement(content)) {
return 'component';
} // we don't know what it is, default to ligature for compat with material icons
return 'ligature';
};
/**
* Get the actual icon strategy to use
*/
export const getIconStrategy = function (content, strategy, providerStrategy, prefix) {
strategy = strategy || providerStrategy || 'auto';
if (strategy === 'auto') {
return processAutoStrategy(content, prefix);
}
return strategy;
};
const renderLigature = ({
content,
...rest
}) => h(Tag, _extends({
tag: "i"
}, rest), content);
const renderClassName = ({
content,
...rest
}) => h(Tag, _extends({
tag: "i"
}, rest));
const renderUrl = ({
content,
...rest
}) => h(Tag, _extends({
tag: "i"
}, rest, {
style: { ...rest.style,
backgroundImage: `url(${content})`
}
}));
const renderComponent = ({
content,
...rest
}) => {
return h(Tag, _extends({
tag: "i"
}, rest), content);
};
const iconRenderMap = {
ligature: renderLigature,
className: renderClassName,
url: renderUrl,
component: renderComponent,
auto: undefined
};
const buildIconOptions = icon => isValidElement(icon) || icon && typeof icon !== 'object' ? {
icon
} : icon;
/** An Icon component. Most of these options can be set once globally, read the documentation on Provider for more info. */
export function Icon({
icon,
size: _size,
prefix: _prefix,
basename: _basename,
children,
outlined,
rounded,
twoTone,
sharp,
...rest
}) {
const providerContext = useProviderContext(); // Build icon options object
const {
ref,
icon: content,
strategy,
prefix = _prefix,
basename = _basename,
size = _size,
render,
...optionsRest
} = { ...buildIconOptions(icon || children)
}; // Get provider options
const {
basename: providerBasename = null,
prefix: providerPrefix = null,
strategy: providerStrategy = null,
render: providerRender = null
} = providerContext.icon || {};
const contentToUse = content;
const strategyToUse = getIconStrategy(contentToUse, strategy || null, providerStrategy || null, prefix);
const prefixToUse = prefix || providerPrefix;
const materialVariant = outlined ? 'material-icons-outlined' : rounded ? 'material-icons-round' : sharp ? 'material-icons-sharp' : twoTone ? 'material-icons-two-tone' : providerBasename;
const basenameToUse = basename !== undefined ? basename : providerBasename === 'material-icons' ? materialVariant : providerBasename;
const iconClassName = strategyToUse === 'className' && typeof content === 'string' ? `${String(prefixToUse)}${content}` : null;
const rendererFromMap = !!strategyToUse && iconRenderMap[strategyToUse]; // For some reason TS thinks the render method will return undefined...
const renderToUse = strategyToUse === 'custom' ? render || providerRender : rendererFromMap || null;
if (!renderToUse) {
console.error(`Icon: rendering not implemented for ${String(strategyToUse)}.`);
return null;
}
const rendered = renderToUse({ ...rest,
...optionsRest,
ref,
content: contentToUse,
className: classNames('pmwc-icon', `pmwc-icon--${strategyToUse}`, basenameToUse, rest.className, optionsRest.className, iconClassName, {
[`pmwc-icon--size-${size || ''}`]: !!size
})
});
const childDisplayName = getDisplayName(rendered.props.children);
if (childDisplayName.includes('Avatar') || childDisplayName.includes('Icon')) {
return cloneElement(rendered.props.children, { ...rendered.props.children.props,
...rendered.props,
ref,
// prevents an infinite loop
children: rendered.props.children.props.children,
className: classNames(rendered.props.className, rendered.props.children.props.className)
});
}
return rendered;
}