-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtailwind.tsx
101 lines (90 loc) · 3.09 KB
/
tailwind.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import clsx, { type ClassValue } from "clsx";
import type { ComponentType, FunctionComponent, JSX } from "react";
import type {
HTMLAttributesForTagName,
HTMLElementTagName,
} from "./html-types.js";
type HTMLElementOrComponent =
| HTMLElementTagName
| ComponentType<WithClassname<unknown>>;
type WithClassname<Props> = Props & {
className?: string;
};
/**
* Whenever `createElementWithClassname` is called with a component, the return
* type should be a React function component with the same signature, but with
* the prop `className` added to the props.
*
* Whenever `createElementWithClassname` is called with an HTML element tag
* name, the return type should be a React function component with the props of
* the HTML element, as if you'd render the HTML element with JSX directly.
*/
type ReturnType<Component extends HTMLElementOrComponent> = FunctionComponent<
PropsForHTMLElementOrComponent<Component>
>;
/**
* Given the input component for `createElementWithClassname`, returns the
* props for that component.
*/
type PropsForHTMLElementOrComponent<Component extends HTMLElementOrComponent> =
Component extends ComponentType<infer Props>
? WithClassname<Props>
: // TypeScript isn't smart enough to understand that `Component` is an HTML
// element tag name at this point.
Component extends HTMLElementTagName
? HTMLAttributesForTagName<Component>
: never;
function createElementWithClassname<Component extends HTMLElementOrComponent>(
component: Component,
...inputs: ClassValue[]
): ReturnType<Component> {
type Props = PropsForHTMLElementOrComponent<Component>;
function ComponentWithClasses(props: Props): JSX.Element {
const { className, ...htmlProps } = props;
const Component = component;
const classes = clsx(...inputs, className);
// @ts-expect-error TypeScript doesn't seem to understand that only adding
// `className` to the props is valid.
return <Component className={classes} {...htmlProps} />;
}
return ComponentWithClasses;
}
type TailwindFactory = typeof createElementWithClassname & {
[Tag in HTMLElementTagName]: (...inputs: ClassValue[]) => ReturnType<Tag>;
};
/**
* A utility for quickly creating components with Tailwind classes, using the
* styled components syntax. It simply takes all the classes and passes it to
* {@link clsx} to generate a `className` string.
*
* @example
*
* Creating a wrapper div and rendering it.
*
* ```tsx
* const Wrapper = tailwind.div("w-64 flex", "border");
*
* function Main() {
* return <Wrapper className="text-red-500">Contents</Wrapper>
* }
* ```
*
* @example
*
* Extending a custom component.
*
* ```tsx
* import { CustomComponent } from "./custom.component.jsx";
*
* const CustomWithClasses = tailwind(CustomComponent, "w-64 flex", "border");
*
* function Main() {
* return <CustomWithClasses className="text-red-500">Contents</Wrapper>
* }
* ```
*/
export const tailwind = new Proxy(createElementWithClassname, {
get(target, name: HTMLElementTagName) {
return (...inputs: ClassValue[]) => target(name, ...inputs);
},
}) as TailwindFactory;