gregberge/twc

Customize `className` with props from underlying component `className` render function.

izznat opened this issue ยท 8 comments

izznat commented

๐Ÿš€ Feature Proposal

Some components from react-aria-components accept a function as className prop value. The function will receive the render props. So, it basically a render function for className. react-twc supports customizing className via props, might as well merge className render props from such components with the transient props. I use this pattern a lot while wrapping react-aria-components.

Motivation

To properly customize the produced className using the underlying className render props.

Example

Without react-twc:

import * as React from 'react'
import * as RAC from 'react-aria-components'
import { twMerge as tw } from 'tailwind-merge'

const Button = React.forwardRef(function Button({ className, ...props }, ref) {
    return ( 
        <RAC.Button 
            {...props} 
            className={(renderProps) => {
                return tw('flex px-2', typeof className === 'function' ? className(renderProps) : className)
            }
        } />
    )
})

With react-twc:

import * as RAC from 'react-aria-components'
import { twc } from 'react-twc'

const Button = twc(RAC.Button)(
    (props) => ['flex px-2', props.isHovered ? 'bg-blue-700' : 'bg-blue-600'], { asFunction: true }
)

Pitch

By supporting classname render function, react-twc will be fully compatible with component library such as react-aria-components and headlessui.

The problem is how react-twc can decide to pass in either a function or a string to the underlying component. We might need to add an option for that'

Hello @izznatsir, good idea, I think we can make something to be usable with these libraries. I could take a look, feel free to submit a PR anyway. Keep it minds that it is not the main use-case and the library should be as a light as possible.

I've updated the description with the proposed API. I'll try to submit a PR later.

FYI, there's a function in React Aria Components for this. Haven't had time to add to the docs yet (was added very last minute).

import {composeRenderProps} from 'react-aria-components';

<RACButton className={composeRenderProps(props.className, className => twMerge('...', className))} />

@devongovett do you have suggestion of an API to fully support react-aria-components in twc?

As I understand it should be possible to return a function as a className, so this should work right?

const Button = twc(RACButton )(props => renderProps => ['flex px-2', renderProps.isHovered ? 'bg-blue-700' : 'bg-blue-600']);

As I understand it should be possible to return a function as a className, so this should work right?

const Button = twc(RACButton )(props => renderProps => ['flex px-2', renderProps.isHovered ? 'bg-blue-700' : 'bg-blue-600']);

Actually, it won't work. Because, the renderProps is received at component render time instead of template creation time. renderProps => ['flex px-2', renderProps.isHovered ? 'bg-blue-700' : 'bg-blue-600']) will be passed to the compose function instead of the component className props, which don't have access to the renderProps.

To support this pattern, the className evaluation needs to executed at component render time by passing in a render function to the className prop instead of template creation time.

But, I realized that react-aria-components also exposes data-[...] attributes. So, it can be easily accessed via data-[...]: variant or using tailwindcss-react-aria-components plugin.

So, this feature is not required to works with headless ui lib such as react-aria-components. But, the end user of the component cannot provides a render function as the className prop.

@izznatsir I just submitted a PR #20, could you take a look? ๐Ÿ‘€