UNPKG

@twstyled/core

Version:

twstyled -- the full-featured Tailwind CSS + CSS in JS solution with blazing fast build times and no runtime overhead

105 lines (104 loc) 4.74 kB
/** * This file contains an runtime version of `styled` component. Responsibilities of the component are: * - returns ReactElement based on HTML tag used with `styled` or custom React Component * - injects classNames for the returned component * - injects CSS variables used to define dynamic styles based on props */ import { createElement, forwardRef } from 'react'; import validAttr from '@emotion/is-prop-valid'; import cx from 'clsx'; // Workaround for rest operator const restOp = (obj, keysToExclude) => Object.keys(obj) .filter((prop) => !keysToExclude.includes(prop)) .reduce((acc, curr) => Object.assign(acc, { [curr]: obj[curr] }), {}); // rest operator workaround const warnIfInvalid = (value, componentName) => { if (process.env.NODE_ENV !== 'production') { if (typeof value === 'string' || // eslint-disable-next-line no-self-compare (typeof value === 'number' && isFinite(value))) { return; } const stringified = typeof value === 'object' ? JSON.stringify(value) : String(value); // eslint-disable-next-line no-console console.warn(`An interpolation evaluated to '${stringified}' in the component '${componentName}', which is probably a mistake. You should explicitly cast or transform the value to a string.`); } }; function styled(tag) { return (options) => { if (process.env.NODE_ENV !== 'production') { if (Array.isArray(options)) { // We received a strings array since it's used as a tag throw new Error('Using the "styled" tag in runtime is not supported. Make sure you have set up the Babel plugin correctly. See https://github.com/callstack/linaria#setup'); } } const render = (props, ref) => { const { as: component = tag, class: className } = props; const rest = restOp(props, ['as', 'class']); let filteredProps; // Check if it's an HTML tag and not a custom element if (typeof component === 'string' && component.indexOf('-') === -1) { filteredProps = {}; // eslint-disable-next-line guard-for-in for (const key in rest) { if (key === 'as' || validAttr(key)) { // Don't pass through invalid attributes to HTML elements filteredProps[key] = rest[key]; } } } else { filteredProps = rest; } filteredProps.ref = ref; const { vars, twvars } = options; let { class: classnames } = options; if (vars) { const style = {}; // eslint-disable-next-line guard-for-in for (const name in vars) { const variable = vars[name]; const result = variable[0]; const value = typeof result === 'function' ? result(props) : result; warnIfInvalid(value, options.name); if (twvars && twvars.indexOf(name) !== -1) { classnames += ` ${value}`; } else { const unit = variable[1] || ''; style[`--${name}`] = `${value}${unit}`; } } filteredProps.style = Object.assign(style, filteredProps.style); } filteredProps.className = cx(filteredProps.className || className, classnames); if (tag.__linaria && tag !== component) { // If the underlying tag is a styled component, forward the `as` prop // Otherwise the styles from the underlying component will be ignored filteredProps.as = component; return createElement(tag, filteredProps); } return createElement(component, filteredProps); }; const Result = forwardRef ? forwardRef(render) : // React.forwardRef won't available on older React versions and in Preact // Fallback to a innerRef prop in that case (props) => { const rest = restOp(props, ['innerRef']); return render(rest, props.innerRef); }; Result.displayName = options.name; Result.__linaria = { className: options.class, extends: tag }; return Result; }; } export default (process.env.NODE_ENV !== 'production' ? new Proxy(styled, { get(o, prop) { return o(prop); } }) : styled);