Skip to content

Commit d1cafe3

Browse files
authored
Merge pull request #163 from danpacho/162-bug-should-override-variants-base-when-compose
[bug + feature] override variants base when `compose`, add multiple class literal for `class`
2 parents f170b52 + 46f04fe commit d1cafe3

File tree

16 files changed

+204
-113
lines changed

16 files changed

+204
-113
lines changed

packages/tailwindest/src/create_tailwindest.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ export interface TailwindestInterface {
8888
* Enables arbitrary strings as valid style properties if `true`.
8989
*/
9090
useArbitrary?: true | false
91+
/**
92+
* Use typed class literal strings for extra classes
93+
*/
94+
useTypedClassLiteral?: true | false
9195
}
9296

9397
/**
@@ -109,7 +113,8 @@ export interface TailwindestInterface {
109113
* tailwind: Tailwind
110114
* tailwindNestGroups: TailwindNestGroups
111115
* useArbitrary: true
112-
* groupPrefix: "#"
116+
* useTypedClassLiteral: true
117+
* groupPrefix: "#" // optional
113118
* }>
114119
* ```
115120
*/

packages/tailwindest/src/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { createTools, type GetVariants } from "./tools"
22

3-
// Merger public interfaces
4-
export type { Merger as TailwindestMerger } from "./tools/merger_interface"
5-
export type { ClassList as TailwindestClassList } from "./tools/to_class"
6-
73
// V2 + tailwindcss < 4.0
84
export type { Tailwindest } from "./legacy/tailwindest"
95
export type { ShortTailwindest } from "./legacy/tailwindest.short"
106

117
// V3 + tailwindcss >= 4.0
8+
9+
// Merger public interfaces
10+
export type { Merger as TailwindestMerger } from "./tools/merger_interface"
11+
export type { ClassList as TailwindestClassList } from "./tools/to_class"
12+
13+
// Create tailwindest typeset
1214
export type {
1315
CreateTailwindest,
1416
CreateTailwindLiteral,

packages/tailwindest/src/tools/__tests__/create_tools.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ describe("PrimitiveStyler", () => {
2626
it("concatenates with extra class name", () => {
2727
const style: TestStyle = { color: "green" }
2828
const styler = new PrimitiveStyler<TestStyle>(style)
29-
expect(styler.class("extra")).toBe("green extra")
29+
expect(
30+
styler.class(
31+
"extra1 extra2 extra3",
32+
["extra4 extra5", "extra6"],
33+
"extra7"
34+
)
35+
).toBe("green extra1 extra2 extra3 extra4 extra5 extra6 extra7")
3036
})
3137
})
3238

@@ -144,7 +150,7 @@ describe("RotaryStyler", () => {
144150

145151
describe("compose method", () => {
146152
it("creates new styler with composed base", () => {
147-
const base: TestStyle = { color: "gray" }
153+
const base: TestStyle = { color: "gray", fontSize: "14" }
148154
const variants: Record<VariantKey, TestStyle> = {
149155
primary: { color: "blue" },
150156
secondary: { color: "green" },
@@ -153,14 +159,14 @@ describe("RotaryStyler", () => {
153159
base,
154160
variants,
155161
})
156-
const newStyler = styler.compose({ fontSize: "12", color: "gray" })
162+
const newStyler = styler.compose({ color: "gray", fontSize: "200" })
157163
expect(newStyler.style("base")).toEqual({
158164
color: "gray",
159-
fontSize: "12",
165+
fontSize: "200",
160166
})
161167
expect(newStyler.style("primary")).toEqual({
162168
color: "blue",
163-
fontSize: "12",
169+
fontSize: "200",
164170
})
165171
})
166172
})

packages/tailwindest/src/tools/__tests__/get_variants.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ describe("GetVariants - rotary", () => {
8787
describe("GetVariants - style", () => {
8888
test("infer never", () => {
8989
const baseWind = tw.style({})
90-
expectType<TypeEqual<GetVariants<typeof baseWind>, string | undefined>>(
90+
expectType<TypeEqual<GetVariants<typeof baseWind>, string | string[]>>(
9191
true
9292
)
9393
})
Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { describe, expect, it } from "vitest"
2-
import { twMerge } from "tailwind-merge"
3-
import { Merger } from "../merger_interface"
42
import { createTools } from "../create_tools"
5-
import { CreateTailwindest } from "../../create_tailwindest"
6-
import { ClassList } from "../to_class"
3+
import type { Merger } from "../merger_interface"
4+
import type { CreateTailwindest } from "../../create_tailwindest"
5+
6+
import { twMerge } from "tailwind-merge"
77

88
describe("Merger interface", () => {
99
it("1. tailwind-merge", () => {
@@ -25,25 +25,23 @@ describe("Merger interface", () => {
2525
["bg-red-100", "bg-red-200", "p-2"],
2626
{
2727
backgroundColor: "bg-red-300",
28+
border: ["border-t-4"],
29+
},
30+
{
31+
border: "border-2",
2832
}
2933
)
30-
expect(argumentOrderHasPriority).toBe("p-2 bg-red-300")
34+
expect(argumentOrderHasPriority).toBe("p-2 bg-red-300 border-2")
35+
const style = t.style({
36+
backgroundColor: "bg-red-100",
37+
})
38+
39+
expect(style.class("bg-red-200", "p-2")).toBe("bg-red-200 p-2")
3140
})
3241
it("2. custom-merger", () => {
33-
const customMerger: Merger<ClassList> = (...args) => {
34-
const res = args
35-
.map((e) => {
36-
if (typeof e === "string") {
37-
return e
38-
}
39-
if (Array.isArray(e)) {
40-
return customMerger(...e)
41-
}
42-
throw new Error("Invalid merging value")
43-
})
44-
.filter(Boolean)
45-
.join(" with ")
46-
return res
42+
const customMerger: Merger = (...args) => {
43+
const uniqueToken = Array.from(new Set(args))
44+
return uniqueToken.join(" with ")
4745
}
4846

4947
const t = createTools<{
@@ -59,11 +57,14 @@ describe("Merger interface", () => {
5957
const style1 = t.def([["bg-red-100", "bg-red-200"], "p-2"])
6058
expect(style1).toBe("bg-red-100 with bg-red-200 with p-2")
6159

62-
const style2 = t.def(["bg-red-100", "bg-red-200", "p-2"], {
63-
backgroundColor: "bg-red-300",
64-
})
60+
const style2 = t.def(
61+
["bg-red-100", "ONCE", "ONCE", "ONCE", "bg-red-200", "p-2"],
62+
{
63+
backgroundColor: "bg-red-300",
64+
}
65+
)
6566
expect(style2).toBe(
66-
"bg-red-100 with bg-red-200 with p-2 with bg-red-300"
67+
"bg-red-100 with ONCE with bg-red-200 with p-2 with bg-red-300"
6768
)
6869
})
6970
})

packages/tailwindest/src/tools/__tests__/styler.test.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { describe, it, expect, vi, beforeEach } from "vitest"
22
import { Styler } from "../styler" // Adjust path as needed
3-
import type { Merger } from "../merger_interface"
3+
import type { AdditionalClassTokens, Merger } from "../merger_interface"
44

55
// Mock implementation of Styler for testing abstract methods
66
class TestStyler extends Styler<string, Record<string, unknown>> {
7-
public class(key: string, extraClassName?: string): string {
8-
return this.merger(key, extraClassName || "")
7+
public class(
8+
key: string,
9+
...extraClassNames: AdditionalClassTokens<string>
10+
): string {
11+
return this.merge(key, ...extraClassNames)
912
}
1013

1114
public style(key: string, extraStyle?: Record<string, unknown>): unknown {
@@ -21,7 +24,9 @@ describe("Styler", () => {
2124
describe("merger", () => {
2225
it("uses default merger when none set", () => {
2326
const styler = new TestStyler()
24-
expect(styler.merger("a", "b")).toBe("a b")
27+
expect(styler.merge("a", "b", ["d e f", "g", "h"], "i")).toBe(
28+
"a b d e f g h i"
29+
)
2530
})
2631

2732
it("uses custom merger when set", () => {
@@ -30,7 +35,7 @@ describe("Styler", () => {
3035
classes.join("-")
3136
)
3237
styler.setMerger(customMerger)
33-
expect(styler.merger("a", "b")).toBe("a-b")
38+
expect(styler.merge("a", "b")).toBe("a-b")
3439
expect(customMerger).toHaveBeenCalledWith("a", "b")
3540
})
3641
})

packages/tailwindest/src/tools/create_tools.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ interface ToolOptions {
5454
* tailwindest: Tailwindest
5555
* tailwindLiteral: TailwindLiteral
5656
* useArbitrary: true // enable arbitrary strings
57+
* useTypedClassLiteral: true // enable typed class literal
5758
* >({
5859
* merger: twMerge // set tailwind-merge as merger, [optional]
5960
* })
@@ -66,25 +67,35 @@ export const createTools = <Type extends TailwindestInterface>({
6667
type ClassLiteral = Type["useArbitrary"] extends true
6768
? Type["tailwindLiteral"] | (`${string}` & {})
6869
: Type["tailwindLiteral"]
70+
type StyleLiteral = Type["useTypedClassLiteral"] extends true
71+
? ClassLiteral
72+
: string
6973

7074
const style = (stylesheet: StyleType) =>
71-
new PrimitiveStyler<StyleType>(stylesheet).setMerger(merger)
75+
new PrimitiveStyler<StyleType, StyleLiteral>(stylesheet).setMerger(
76+
merger
77+
)
7278

7379
const toggle = (toggleVariants: ToggleVariants<StyleType>) =>
74-
new ToggleStyler<StyleType>(toggleVariants).setMerger(merger)
80+
new ToggleStyler<StyleType, StyleLiteral>(toggleVariants).setMerger(
81+
merger
82+
)
7583

7684
const rotary = <VRecord extends Record<string, StyleType>>(params: {
7785
base?: StyleType
7886
variants: VRecord
7987
}) =>
80-
new RotaryStyler<StyleType, Stringify<keyof VRecord>>(params).setMerger(
81-
merger
82-
)
88+
new RotaryStyler<StyleType, Stringify<keyof VRecord>, StyleLiteral>(
89+
params
90+
).setMerger(merger)
8391

8492
const variants = <VMap extends VariantsRecord<StyleType>>(params: {
8593
base?: StyleType
8694
variants: VMap
87-
}) => new VariantsStyler<StyleType, VMap>(params).setMerger(merger)
95+
}) =>
96+
new VariantsStyler<StyleType, VMap, StyleLiteral>(params).setMerger(
97+
merger
98+
)
8899

89100
const mergeRecord = (...overrideRecord: Array<StyleType>): StyleType =>
90101
overrideRecord.reduce<StyleType>(
@@ -100,8 +111,17 @@ export const createTools = <Type extends TailwindestInterface>({
100111
return res
101112
}
102113

103-
const join = (...classList: ClassList<ClassLiteral>): string =>
104-
merger ? toClass(merger(...classList)) : toClass(classList)
114+
const join = (...classList: ClassList<ClassLiteral>): string => {
115+
const base = toClass(...classList)
116+
if (merger) {
117+
const tokens = base
118+
.split(" ")
119+
.filter((e) => e.length > 0)
120+
.map((e) => e.trim())
121+
return merger(...tokens)
122+
}
123+
return base
124+
}
105125

106126
const def = (
107127
classList: ClassList<ClassLiteral>,
@@ -111,6 +131,9 @@ export const createTools = <Type extends TailwindestInterface>({
111131
return {
112132
/**
113133
* Define style
134+
*
135+
* `styleList` has higher priority than `classList`
136+
*
114137
* @see {@link https://github.com/lukeed/clsx#readme clsx}
115138
* @param classList join target styles
116139
* @param styleList define styles in a record structure way

packages/tailwindest/src/tools/get_variants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import type { Styler } from "./styler"
33
/**
44
* Get variants
55
*/
6-
export type GetVariants<StylerInstance extends Styler<any, any>> =
7-
StylerInstance extends Styler<infer Arg, any>
6+
export type GetVariants<StylerInstance extends Styler<any, any, any>> =
7+
StylerInstance extends Styler<infer Arg, any, any>
88
? Arg extends never
99
? never
1010
: Exclude<Parameters<StylerInstance["class"]>[0], "base">
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
/**
2+
* @interface
3+
* Additional class tokens that can be merged into a single className string.
4+
*/
5+
export type AdditionalClassTokens<Literal extends string> = Array<
6+
Literal | Array<Literal>
7+
>
18
/**
29
* @interface
310
* Merge arbitrary class list into one valid style classname string
411
*/
5-
export type Merger<ClassList extends Array<any> = any[]> = (
6-
...classList: ClassList
12+
export type Merger<Literal extends string = string> = (
13+
...classList: AdditionalClassTokens<Literal>
714
) => string

packages/tailwindest/src/tools/primitive.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import type { AdditionalClassTokens } from "./merger_interface"
12
import { Styler } from "./styler"
23

3-
export class PrimitiveStyler<StyleType> extends Styler<never, StyleType> {
4+
export class PrimitiveStyler<
5+
StyleType,
6+
StyleLiteral extends string = string,
7+
> extends Styler<never, StyleType, StyleLiteral> {
48
private _class: string
59
private _style: StyleType
610

@@ -14,20 +18,22 @@ export class PrimitiveStyler<StyleType> extends Styler<never, StyleType> {
1418
* Get classname
1519
* @param extraClassName extra classnames, if merger is provided it uses merger or just concat.
1620
*/
17-
public class(extraClassName?: string): string {
21+
public class(
22+
...extraClassList: AdditionalClassTokens<StyleLiteral>
23+
): string {
1824
const inquired = this._class
19-
if (!extraClassName) return inquired
20-
return this.merger(inquired, extraClassName)
25+
if (extraClassList.length === 0) return inquired
26+
return this.merge(inquired as StyleLiteral, ...extraClassList)
2127
}
2228

2329
/**
2430
* Get stylesheet
2531
* @param extraStyle extra stylesheet
2632
*/
27-
public style(extraStyle?: StyleType): StyleType {
33+
public style(...extraStyles: Array<StyleType>): StyleType {
2834
const inquired = this._style
29-
if (!extraStyle) return inquired
30-
return Styler.deepMerge(inquired, extraStyle)
35+
if (extraStyles.length === 0) return inquired
36+
return Styler.deepMerge(inquired, ...extraStyles)
3137
}
3238

3339
public compose(...styles: Array<StyleType>): PrimitiveStyler<StyleType> {

0 commit comments

Comments
 (0)