Skip to content

Commit 9c6f874

Browse files
committed
set type VariantsConfig for config argument in styled function + fix typings
1 parent 8dda492 commit 9c6f874

File tree

4 files changed

+73
-61
lines changed

4 files changed

+73
-61
lines changed

.changeset/rich-eyes-kiss.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-tailwind-variants': major
3+
---
4+
5+
BREAKING CHANGE: set type `VariantsConfig` for `config` argument in `styled` function + fix typings

README.md

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ npm install tailwind-merge react-tailwind-variants
2828
- [Boolean variants](#boolean-variants)
2929
- [Compound variants](#compound-variants)
3030
- [Default variants](#default-variants)
31-
- [Components without variants](#components-without-variants)
3231
- [Polymorphic components](#polymorphic-components)
3332
- [Composing components](#composing-components)
3433
- [Utilities](#utilities)
@@ -216,18 +215,6 @@ const Button = styled('button', {
216215

217216
---
218217

219-
### Components without variants
220-
221-
If the component does not have any options, you can specify a string with classes instead of a configuration object
222-
223-
```tsx
224-
import { styled } from 'react-tailwind-variants';
225-
226-
const Button = styled('button', 'bg-blue-500 text-white px-4 py-2');
227-
```
228-
229-
---
230-
231218
### Polymorphic components
232219

233220
If you want to keep all the variants you have defined for a component but want to render a different HTML tag or a different custom component, you can use the `"asChild"` prop to do so:

src/react.ts

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,43 @@ import {
1414
type VariantsSchema,
1515
type VariantOptions,
1616
variants,
17-
Simplify,
1817
} from './variants';
1918

2019
const StyledComponentConfigKey = '$$tailwindVariantsConfig';
2120

22-
type StyledComponentConfigProp<Config extends VariantsConfig> = {
23-
readonly [StyledComponentConfigKey]: Config;
21+
type StyledComponentConfigProp<
22+
C extends VariantsConfig<V>,
23+
V extends VariantsSchema = NonNullable<C['variants']>
24+
> = {
25+
readonly [StyledComponentConfigKey]: C;
2426
};
2527

2628
export type StyledComponent<
2729
ForwardRefComponent extends ForwardRefExoticComponent<any>,
28-
Config extends VariantsConfig
29-
> = ForwardRefComponent & StyledComponentConfigProp<Config>;
30+
C extends VariantsConfig<V>,
31+
V extends VariantsSchema = NonNullable<C['variants']>
32+
> = ForwardRefComponent & StyledComponentConfigProp<C>;
3033

3134
export function variantProps<
3235
C extends VariantsConfig<V>,
33-
V extends VariantsSchema = C['variants']
36+
V extends VariantsSchema = NonNullable<C['variants']>
3437
>(config: C) {
3538
const variantsHandler = variants(config);
3639

3740
return function <
38-
P extends VariantOptions<C> & {
41+
P extends VariantOptions<typeof config> & {
3942
className?: string;
4043
}
4144
>(props: P) {
4245
const result: any = {};
4346

4447
// Pass-through all unrelated props
4548
for (let prop in props) {
46-
if (!config.variants || !(prop in config.variants)) {
49+
if (
50+
!('variants' in config) ||
51+
!config.variants ||
52+
!(prop in config.variants)
53+
) {
4754
result[prop] = props[prop];
4855
}
4956
}
@@ -67,14 +74,9 @@ type SlottableProps<
6774
export function styled<
6875
T extends ElementType,
6976
C extends VariantsConfig<V>,
70-
V extends VariantsSchema = C extends VariantsConfig ? C['variants'] : {}
71-
>(baseType: T, config: string | Simplify<C>) {
72-
const preparedConfig =
73-
typeof config === 'string'
74-
? ({ base: config, variants: {} } as Simplify<C>)
75-
: config;
76-
77-
const propsHandler = variantProps(preparedConfig);
77+
V extends VariantsSchema = NonNullable<C['variants']>
78+
>(baseType: T, config: C) {
79+
const propsHandler = variantProps(config);
7880

7981
type ConfigVariants = VariantOptions<C>;
8082
type Props = SlottableProps<
@@ -100,23 +102,25 @@ export function styled<
100102
});
101103

102104
return Object.assign(component, {
103-
[StyledComponentConfigKey]: preparedConfig,
104-
}) as StyledComponent<typeof component, typeof preparedConfig>;
105+
[StyledComponentConfigKey]: config,
106+
}) as StyledComponent<typeof component, typeof config>;
105107
}
106108

107109
export type VariantsConfigOf<
108-
Component extends StyledComponent<ForwardRefExoticComponent<any>, Config>,
109-
Config extends VariantsConfig = Component[typeof StyledComponentConfigKey]
110-
> = Config;
110+
Component extends StyledComponent<ForwardRefExoticComponent<any>, C>,
111+
C extends VariantsConfig<V> = Component[typeof StyledComponentConfigKey],
112+
V extends VariantsSchema = NonNullable<C['variants']>
113+
> = C;
111114

112115
export type VariantPropsOf<
113-
Component extends StyledComponent<ForwardRefExoticComponent<any>, Config>,
114-
Config extends VariantsConfig = Component[typeof StyledComponentConfigKey]
115-
> = VariantOptions<Config>;
116+
Component extends StyledComponent<ForwardRefExoticComponent<any>, C>,
117+
C extends VariantsConfig<V> = Component[typeof StyledComponentConfigKey],
118+
V extends VariantsSchema = NonNullable<C['variants']>
119+
> = VariantOptions<C>;
116120

117121
export function extractVariantsConfig<
118-
Component extends ForwardRefExoticComponent<any>,
119-
Config extends VariantsConfig
120-
>(styledComponent: StyledComponent<Component, Config>) {
122+
C extends VariantsConfig<V>,
123+
V extends VariantsSchema = NonNullable<C['variants']>
124+
>(styledComponent: StyledComponent<ForwardRefExoticComponent<any>, C>) {
121125
return styledComponent[StyledComponentConfigKey];
122126
}

test/react.spec.tsx

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@ const StyledComponent = styled('button', {
3131

3232
// ============================================
3333

34-
const StyledComponentWithStringConfig = styled(
35-
'div',
36-
'bg-white p-4 border-2 rounded-lg'
37-
);
34+
const StyledComponentWithoutVariants = styled('div', {
35+
base: 'bg-white p-4 border-2 rounded-lg',
36+
});
3837

3938
// ============================================
4039

@@ -123,11 +122,35 @@ styled('div', {
123122
},
124123
compoundVariants: [
125124
{
126-
//@ts-expect-error
127-
variants: { color: 'foobar' },
125+
variants: {
126+
// @ts-expect-error
127+
color: 'foobar',
128+
},
128129
className: '',
129130
},
130131
],
132+
});
133+
134+
styled('div', {
135+
variants: {
136+
color: {
137+
neutral: 'grey',
138+
accent: 'hotpink',
139+
},
140+
},
141+
defaultVariants: {
142+
// @ts-expect-error
143+
color: 'foobar',
144+
},
145+
});
146+
147+
styled('div', {
148+
variants: {
149+
color: {
150+
neutral: 'grey',
151+
accent: 'hotpink',
152+
},
153+
},
131154
defaultVariants: {
132155
// @ts-expect-error
133156
outlined: true,
@@ -235,10 +258,9 @@ describe('extractVariantsConfig', () => {
235258
});
236259
});
237260

238-
it('extracts variants config from styled component with string configuration', () => {
239-
expect(extractVariantsConfig(StyledComponentWithStringConfig)).toEqual({
261+
it('extracts variants config from styled component without variants', () => {
262+
expect(extractVariantsConfig(StyledComponentWithoutVariants)).toEqual({
240263
base: 'bg-white p-4 border-2 rounded-lg',
241-
variants: {},
242264
});
243265
});
244266
});
@@ -323,12 +345,10 @@ describe('styled', () => {
323345
});
324346
});
325347

326-
describe('styled component with string configuration', () => {
348+
describe('styled component without variants', () => {
327349
test('render without props', () => {
328350
render(
329-
<StyledComponentWithStringConfig>
330-
Button
331-
</StyledComponentWithStringConfig>
351+
<StyledComponentWithoutVariants>Button</StyledComponentWithoutVariants>
332352
);
333353

334354
expect(screen.getByText('Button')).toMatchInlineSnapshot(`
@@ -342,21 +362,19 @@ describe('styled', () => {
342362

343363
test('render with unrelated props', () => {
344364
render(
345-
<StyledComponentWithStringConfig
346-
color="neutral"
365+
<StyledComponentWithoutVariants
347366
data-foo="bar"
348367
className="py-6 bg-gray-700"
349368
>
350369
Button
351-
</StyledComponentWithStringConfig>
370+
</StyledComponentWithoutVariants>
352371
);
353372

354373
const button = screen.getByText('Button');
355374

356375
expect(button).toMatchInlineSnapshot(`
357376
<div
358377
class="p-4 border-2 rounded-lg py-6 bg-gray-700"
359-
color="neutral"
360378
data-foo="bar"
361379
>
362380
Button
@@ -366,24 +384,22 @@ describe('styled', () => {
366384

367385
test('render as child with unrelated props', () => {
368386
render(
369-
<StyledComponentWithStringConfig
387+
<StyledComponentWithoutVariants
370388
asChild
371-
color="neutral"
372389
data-foo="bar"
373390
className="py-6 bg-gray-700"
374391
>
375392
<a href="/some/link" className="py-1 text-gray-200">
376393
Link
377394
</a>
378-
</StyledComponentWithStringConfig>
395+
</StyledComponentWithoutVariants>
379396
);
380397

381398
const link = screen.getByText('Link');
382399

383400
expect(link).toMatchInlineSnapshot(`
384401
<a
385402
class="p-4 border-2 rounded-lg py-6 bg-gray-700 py-1 text-gray-200"
386-
color="neutral"
387403
data-foo="bar"
388404
href="/some/link"
389405
>

0 commit comments

Comments
 (0)