Skip to content

Commit f5abd29

Browse files
authored
feat: allow to forward asChild property (#54)
Allow to forward `asChild` property due to `shouldForwardAsChild` flag in config Closes #53
1 parent 7abe7af commit f5abd29

2 files changed

Lines changed: 44 additions & 2 deletions

File tree

src/index.test.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, expect, test, beforeEach } from "vitest";
1+
import { describe, expect, test, beforeEach, vi } from "vitest";
22
import { render, screen, cleanup } from "@testing-library/react";
33
import { cva, VariantProps } from "class-variance-authority";
44
import { twc, createTwc, TwcComponentProps } from "./index";
@@ -9,6 +9,7 @@ import {
99
ButtonProps as AriaButtonProps,
1010
} from "react-aria-components";
1111
import * as AccordionPrimitive from "@radix-ui/react-accordion";
12+
import { Slot } from "@radix-ui/react-slot";
1213

1314
describe("twc", () => {
1415
beforeEach(cleanup);
@@ -228,6 +229,38 @@ describe("twc", () => {
228229
expect(title.classList.contains("text-xl")).toBe(true);
229230
});
230231

232+
test('forwards "asChild" property to custom component when "shouldForwardAsChild" is true', () => {
233+
const twx = createTwc({
234+
shouldForwardAsChild: true,
235+
});
236+
237+
type PrimitiveProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
238+
asChild?: boolean;
239+
};
240+
241+
const handleClick = vi.fn<[React.MouseEvent<HTMLButtonElement>], void>()
242+
const Primitive = ({ asChild, ...props }: PrimitiveProps) => {
243+
const Comp = asChild ? Slot : 'button'
244+
return <Comp onClick={handleClick} {...props}/>;
245+
};
246+
247+
const StyledPrimitive = twx(Primitive)`text-xl`
248+
249+
render(
250+
<StyledPrimitive asChild>
251+
<a>Click me!</a>
252+
</StyledPrimitive>
253+
)
254+
255+
const element = screen.getByText('Click me!')
256+
element.click()
257+
258+
expect(element.tagName).toBeDefined()
259+
expect(element.tagName).toBe('A')
260+
expect(element.classList.contains('text-xl')).toBe(true)
261+
expect(handleClick).toBeCalled()
262+
});
263+
231264
test("works with tailwind-merge", () => {
232265
const twx = createTwc({
233266
compose: twMerge,

src/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ export type Config<TCompose extends AbstractCompose> = {
9595
* underlying component. Defaults to `prop => prop[0] !== "$"`.
9696
*/
9797
shouldForwardProp?: (prop: string) => boolean;
98+
99+
/**
100+
* The flag responsible for behavior of asChild prop. When true asChild
101+
* prop would be forwarded to the underlying component. Defaults to `false`
102+
*/
103+
shouldForwardAsChild?: boolean;
98104
};
99105

100106
function filterProps(
@@ -137,7 +143,9 @@ export const createTwc = <TCompose extends AbstractCompose = typeof clsx>(
137143
const rp =
138144
typeof attrs === "function" ? attrs(p) : attrs ? attrs : {};
139145
const fp = filterProps({ ...rp, ...rest }, shouldForwardProp);
140-
const Comp = asChild ? Slot : Component;
146+
const shouldForwardAsChild =
147+
config.shouldForwardAsChild && typeof Component !== "string";
148+
const Comp = !shouldForwardAsChild && asChild ? Slot : Component;
141149
const resClassName = isClassFn ? stringsOrFn(p) : tplClassName;
142150
return (
143151
<Comp
@@ -153,6 +161,7 @@ export const createTwc = <TCompose extends AbstractCompose = typeof clsx>(
153161
)
154162
: compose(resClassName, classNameProp)
155163
}
164+
asChild={shouldForwardAsChild ? asChild : undefined}
156165
{...fp}
157166
/>
158167
);

0 commit comments

Comments
 (0)