Skip to content

Commit 2063132

Browse files
Merge className correctly when it’s a function (#2412)
* Add className tests for `render` Fix snapshots * Merge `className` correctly when it’s a function * Update changelog
1 parent c92a847 commit 2063132

File tree

3 files changed

+66
-3
lines changed

3 files changed

+66
-3
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Add `FocusTrap` event listeners once document has loaded ([#2389](https://github.com/tailwindlabs/headlessui/pull/2389))
1515
- Fix `className` hydration for `<Transition appear>` ([#2390](https://github.com/tailwindlabs/headlessui/pull/2390))
1616
- Improve `Combobox` types to improve false positives ([#2411](https://github.com/tailwindlabs/headlessui/pull/2411))
17+
- Merge `className` correctly when it’s a function ([#2412](https://github.com/tailwindlabs/headlessui/pull/2412))
1718

1819
### Added
1920

packages/@headlessui-react/src/utils/render.test.tsx

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { suppressConsoleLogs } from '../test-utils/suppress-console-logs'
55
import { render, Features, PropsForFeatures } from './render'
66
import { Props, Expand } from '../types'
77

8-
function contents() {
9-
return prettyDOM(getByTestId(document.body, 'wrapper'), undefined, {
8+
function contents(id = 'wrapper') {
9+
return prettyDOM(getByTestId(document.body, id), undefined, {
1010
highlight: false,
1111
})
1212
}
@@ -29,6 +29,22 @@ describe('Default functionality', () => {
2929
)
3030
}
3131

32+
function DummyWithClassName<TTag extends ElementType = 'div'>(
33+
props: Props<TTag> & Partial<{ className: string | (() => string) }>
34+
) {
35+
return (
36+
<div data-testid="wrapper-with-class">
37+
{render({
38+
ourProps: {},
39+
theirProps: props,
40+
slot,
41+
defaultTag: 'div',
42+
name: 'Dummy',
43+
})}
44+
</div>
45+
)
46+
}
47+
3248
it('should be possible to render a dummy component', () => {
3349
testRender(<Dummy />)
3450

@@ -41,6 +57,46 @@ describe('Default functionality', () => {
4157
`)
4258
})
4359

60+
it('should be possible to merge classes when rendering', () => {
61+
testRender(
62+
<DummyWithClassName as={Fragment} className="test-outer">
63+
<div className="test-inner"></div>
64+
</DummyWithClassName>
65+
)
66+
67+
expect(contents('wrapper-with-class')).toMatchInlineSnapshot(`
68+
"<div
69+
data-testid=\\"wrapper-with-class\\"
70+
>
71+
<div
72+
class=\\"test-inner test-outer\\"
73+
/>
74+
</div>"
75+
`)
76+
})
77+
78+
it('should be possible to merge class fns when rendering', () => {
79+
testRender(
80+
<DummyWithClassName as={Fragment} className="test-outer">
81+
<Dummy className={() => 'test-inner'}></Dummy>
82+
</DummyWithClassName>
83+
)
84+
85+
expect(contents('wrapper-with-class')).toMatchInlineSnapshot(`
86+
"<div
87+
data-testid=\\"wrapper-with-class\\"
88+
>
89+
<div
90+
data-testid=\\"wrapper\\"
91+
>
92+
<div
93+
class=\\"test-inner test-outer\\"
94+
/>
95+
</div>
96+
</div>"
97+
`)
98+
})
99+
44100
it('should be possible to render a dummy component with some children as a callback', () => {
45101
expect.assertions(2)
46102

packages/@headlessui-react/src/utils/render.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,13 @@ function _render<TTag extends ElementType, TSlot>(
174174

175175
// Merge class name prop in SSR
176176
// @ts-ignore We know that the props may not have className. It'll be undefined then which is fine.
177-
let newClassName = classNames(resolvedChildren.props?.className, rest.className)
177+
let childProps = resolvedChildren.props as { className: string | (() => string) } | null
178+
179+
let newClassName =
180+
typeof childProps?.className === 'function'
181+
? (...args: any[]) => classNames(childProps?.className(...args), rest.className)
182+
: classNames(childProps?.className, rest.className)
183+
178184
let classNameProps = newClassName ? { className: newClassName } : {}
179185

180186
return cloneElement(

0 commit comments

Comments
 (0)