import type { ComponentProps } from 'react' import { useLayoutEffect, useReducer } from 'react' import { isDefined } from '../common/utils' import { generateDataSet } from '../components/web/generateDataSet' import { useUniwindContext } from '../core/context' import { Logger } from '../core/logger' import { CSSListener, formatColor, getWebStyles } from '../core/web' import type { AnyObject, Component, OptionMapping, WithUniwind } from './types' import { classToColor, classToStyle, isClassProperty, isColorClassProperty, isStyleProperty } from './withUniwindUtils' let warnedOnce = false export const withUniwind: WithUniwind = < TComponent extends Component, TOptions extends Record, OptionMapping>, >( Component: TComponent, options?: TOptions, ) => options ? withManualUniwind(Component, options) : withAutoUniwind(Component) const withAutoUniwind = (Component: Component) => (props: AnyObject) => { const uniwindContext = useUniwindContext() const { classNames, generatedProps } = Object.entries(props).reduce((acc, [propName, propValue]) => { if (isColorClassProperty(propName)) { const colorProp = classToColor(propName) if (props[colorProp] !== undefined) { return acc } const className = propValue const color = getWebStyles(className, props, uniwindContext).accentColor if (__DEV__ && !warnedOnce && isDefined(className) && className.trim() !== '' && color === undefined) { warnedOnce = true Logger.warn( `className '${className}' was provided to extract accentColor but no color was found. Make sure the className includes a color utility (e.g., 'accent-red-500', 'accent-blue-600'). See https://docs.uniwind.dev/class-names#the-accent-prefix`, ) } acc.generatedProps[colorProp] = color !== undefined ? formatColor(color) : undefined acc.classNames += `${className} ` return acc } if (isClassProperty(propName)) { const styleProp = classToStyle(propName) acc.generatedProps[styleProp] ??= [] acc.generatedProps[styleProp][0] = { $$css: true, tailwind: propValue } return acc } if (isStyleProperty(propName)) { acc.generatedProps[propName] ??= [] acc.generatedProps[propName][1] = propValue return acc } return acc }, { generatedProps: {} as AnyObject, classNames: '' }) const [, rerender] = useReducer(() => ({}), {}) useLayoutEffect(() => { const dispose = CSSListener.subscribeToClassName(classNames, rerender) return dispose }, [classNames]) return ( ) } const withManualUniwind = (Component: Component, options: Record) => (props: AnyObject) => { const uniwindContext = useUniwindContext() const { generatedProps, classNames } = Object.entries(options).reduce((acc, [propName, option]) => { const className = props[option.fromClassName] if (className === undefined) { return acc } if (option.styleProperty !== undefined) { // If the prop is already defined, we don't want to override it if (props[propName] !== undefined) { return acc } const value = getWebStyles(className, props, uniwindContext)[option.styleProperty] const transformedValue = value !== undefined && option.styleProperty.toLowerCase().includes('color') ? formatColor(value as string) : value acc.classNames += `${className} ` acc.generatedProps[propName] = transformedValue return acc } acc.generatedProps[propName] = [{ $$css: true, tailwind: className }, props[propName]] return acc }, { generatedProps: {} as AnyObject, classNames: '' }) const [, rerender] = useReducer(() => ({}), {}) useLayoutEffect(() => { const dispose = CSSListener.subscribeToClassName(classNames, rerender) return dispose }, [classNames]) return ( ) }