Skip to content

Commit

Permalink
Merge className correctly when it’s a function (#2412)
Browse files Browse the repository at this point in the history
* Add className tests for `render`

Fix snapshots

* Merge `className` correctly when it’s a function

* Update changelog
  • Loading branch information
thecrypticace committed Apr 3, 2023
1 parent c92a847 commit 2063132
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `FocusTrap` event listeners once document has loaded ([#2389](https://github.com/tailwindlabs/headlessui/pull/2389))
- Fix `className` hydration for `<Transition appear>` ([#2390](https://github.com/tailwindlabs/headlessui/pull/2390))
- Improve `Combobox` types to improve false positives ([#2411](https://github.com/tailwindlabs/headlessui/pull/2411))
- Merge `className` correctly when it’s a function ([#2412](https://github.com/tailwindlabs/headlessui/pull/2412))

### Added

Expand Down
60 changes: 58 additions & 2 deletions packages/@headlessui-react/src/utils/render.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { suppressConsoleLogs } from '../test-utils/suppress-console-logs'
import { render, Features, PropsForFeatures } from './render'
import { Props, Expand } from '../types'

function contents() {
return prettyDOM(getByTestId(document.body, 'wrapper'), undefined, {
function contents(id = 'wrapper') {
return prettyDOM(getByTestId(document.body, id), undefined, {
highlight: false,
})
}
Expand All @@ -29,6 +29,22 @@ describe('Default functionality', () => {
)
}

function DummyWithClassName<TTag extends ElementType = 'div'>(
props: Props<TTag> & Partial<{ className: string | (() => string) }>
) {
return (
<div data-testid="wrapper-with-class">
{render({
ourProps: {},
theirProps: props,
slot,
defaultTag: 'div',
name: 'Dummy',
})}
</div>
)
}

it('should be possible to render a dummy component', () => {
testRender(<Dummy />)

Expand All @@ -41,6 +57,46 @@ describe('Default functionality', () => {
`)
})

it('should be possible to merge classes when rendering', () => {
testRender(
<DummyWithClassName as={Fragment} className="test-outer">
<div className="test-inner"></div>
</DummyWithClassName>
)

expect(contents('wrapper-with-class')).toMatchInlineSnapshot(`
"<div
data-testid=\\"wrapper-with-class\\"
>
<div
class=\\"test-inner test-outer\\"
/>
</div>"
`)
})

it('should be possible to merge class fns when rendering', () => {
testRender(
<DummyWithClassName as={Fragment} className="test-outer">
<Dummy className={() => 'test-inner'}></Dummy>
</DummyWithClassName>
)

expect(contents('wrapper-with-class')).toMatchInlineSnapshot(`
"<div
data-testid=\\"wrapper-with-class\\"
>
<div
data-testid=\\"wrapper\\"
>
<div
class=\\"test-inner test-outer\\"
/>
</div>
</div>"
`)
})

it('should be possible to render a dummy component with some children as a callback', () => {
expect.assertions(2)

Expand Down
8 changes: 7 additions & 1 deletion packages/@headlessui-react/src/utils/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,13 @@ function _render<TTag extends ElementType, TSlot>(

// Merge class name prop in SSR
// @ts-ignore We know that the props may not have className. It'll be undefined then which is fine.
let newClassName = classNames(resolvedChildren.props?.className, rest.className)
let childProps = resolvedChildren.props as { className: string | (() => string) } | null

let newClassName =
typeof childProps?.className === 'function'
? (...args: any[]) => classNames(childProps?.className(...args), rest.className)
: classNames(childProps?.className, rest.className)

let classNameProps = newClassName ? { className: newClassName } : {}

return cloneElement(
Expand Down

0 comments on commit 2063132

Please sign in to comment.