tiwi
Version:
React library to create components with Tailwind styles baked in.
99 lines (98 loc) • 3.84 kB
JavaScript
import { forwardRef, useMemo, } from "react";
import { twMerge } from "tailwind-merge";
import { allIntrinsicElements } from "./elements.js";
import { tiwiComponentSymbol, } from "./types.js";
//
// Variants helpers.
//
function variantsToArray(variants) {
if (!variants) {
return [];
}
if (typeof variants === "string") {
return [variants];
}
if (Array.isArray(variants)) {
return variants.filter(isString);
}
return Object.keys(variants).filter(k => variants[k] === true);
}
//
// Component factory.
//
function isTiwiComponent(Element) {
return Element && Element[tiwiComponentSymbol] === true;
}
function isString(value) {
return typeof value === "string";
}
function buildTiwiBase(createElement) {
return (Element) => {
return (classNames, ...variantDefinitions) => {
const AnyElement = Element;
const isTiwi = isTiwiComponent(Element);
const component = forwardRef((props, ref) => {
const { className, variants, ...otherProps } = props;
const flatVariants = variantsToArray(variants);
const flatVariantsString = flatVariants.join("::");
const mergedClassName = useMemo(() => {
const allClassNames = classNames.reduce((list, current, index) => {
const variantDefinition = variantDefinitions[index - 1];
if (variantDefinition) {
Object.keys(variantDefinition)
.filter(v => flatVariants.includes(v))
.forEach(v => {
const variantValue = variantDefinition[v];
if (!variantValue) {
return;
}
list.push(variantValue);
});
}
list.push(current);
return list;
}, []);
return twMerge([...allClassNames, className]) || undefined;
// Refresh on parent variables to make fast-refresh work in RN.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [classNames, variantDefinitions, className, flatVariantsString]);
if (isTiwi) {
return createElement(AnyElement, {
...otherProps,
ref: ref,
className: mergedClassName,
variants: variants,
});
}
return createElement(AnyElement, {
...otherProps,
ref: ref,
className: mergedClassName,
});
});
component[tiwiComponentSymbol] = true;
component.displayName = (() => {
if (typeof Element === "string") {
return (component.displayName = `tiwi.${Element}`);
}
const name = Element.displayName || Element.name || "";
return (component.displayName = `tiwi(${name})`);
})();
return component;
};
};
}
//
// Add intrinsic components to function.
//
export function buildTiwi(createElement) {
const tiwiBase = buildTiwiBase(createElement);
if (typeof navigator === "object" && navigator.product === "ReactNative") {
return tiwiBase;
}
const intrinsicElementsFunctions = allIntrinsicElements.reduce((result, DomElement) => {
result[DomElement] = tiwiBase(DomElement);
return result;
}, {});
return Object.assign(tiwiBase, intrinsicElementsFunctions);
}