|
1 | 1 | import { clsx } from "clsx"; |
| 2 | +import type { ClassMultipleInput, ClassMultipleResult } from "./types.js"; |
2 | 3 |
|
3 | 4 | /** |
4 | 5 | * Conditionally join class names into a single string |
@@ -26,7 +27,23 @@ import { clsx } from "clsx"; |
26 | 27 | * cx('foo', [1 && 'bar', { baz: false }], ['hello', ['world']], 'cya'); |
27 | 28 | * // => 'foo bar hello world cya' |
28 | 29 | */ |
29 | | -export const cx = clsx; |
| 30 | +export const cx = Object.assign(clsx, { |
| 31 | + multiple: cxMultiple |
| 32 | +}); |
| 33 | + |
| 34 | +function cxMultiple<T extends ClassMultipleInput>( |
| 35 | + map: T |
| 36 | +): ClassMultipleResult<T> { |
| 37 | + const result = {} as ClassMultipleResult<T>; |
| 38 | + |
| 39 | + for (const key in map) { |
| 40 | + if (Object.prototype.hasOwnProperty.call(map, key)) { |
| 41 | + result[key] = clsx(map[key]); |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + return result; |
| 46 | +} |
30 | 47 |
|
31 | 48 | // == Tests ==================================================================== |
32 | 49 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment |
@@ -98,6 +115,12 @@ if (import.meta.vitest) { |
98 | 115 | expect(cx("foo", 42, "bar")).toBe("foo 42 bar"); |
99 | 116 | }); |
100 | 117 |
|
| 118 | + it.skip("handles bigint inputs", () => { |
| 119 | + // NOTE: CLSX currently does not support BigInt, so these tests are skipped |
| 120 | + expect(cx(BigInt(123))).toBe("123"); |
| 121 | + expect(cx("foo", BigInt(42), "bar")).toBe("foo 42 bar"); |
| 122 | + }); |
| 123 | + |
101 | 124 | it("filters falsy values correctly", () => { |
102 | 125 | expect(cx(null)).toBe(""); |
103 | 126 | expect(cx(undefined)).toBe(""); |
@@ -127,4 +150,55 @@ if (import.meta.vitest) { |
127 | 150 | assertType<string>(cx(123)); |
128 | 151 | }); |
129 | 152 | }); |
| 153 | + |
| 154 | + describe.concurrent("cx.multiple()", () => { |
| 155 | + it("processes a map of class values", () => { |
| 156 | + const result = cx.multiple({ |
| 157 | + primary: ["bg-blue-500", "text-white"], |
| 158 | + secondary: ["bg-gray-500", "text-black"] |
| 159 | + }); |
| 160 | + |
| 161 | + expect(result.primary).toBe("bg-blue-500 text-white"); |
| 162 | + expect(result.secondary).toBe("bg-gray-500 text-black"); |
| 163 | + }); |
| 164 | + |
| 165 | + it("handles mixed input types in map", () => { |
| 166 | + const isHidden = false; |
| 167 | + const result = cx.multiple({ |
| 168 | + strings: "foo bar", |
| 169 | + array: ["baz", "qux"], |
| 170 | + object: { enabled: true, disabled: false }, |
| 171 | + conditional: ["base", isHidden && "hidden"] |
| 172 | + }); |
| 173 | + |
| 174 | + expect(result.strings).toBe("foo bar"); |
| 175 | + expect(result.array).toBe("baz qux"); |
| 176 | + expect(result.object).toBe("enabled"); |
| 177 | + expect(result.conditional).toBe("base"); |
| 178 | + }); |
| 179 | + |
| 180 | + it("handles empty map", () => { |
| 181 | + const result = cx.multiple({}); |
| 182 | + expect(result).toEqual({}); |
| 183 | + }); |
| 184 | + |
| 185 | + it("preserves keys with empty values", () => { |
| 186 | + const result = cx.multiple({ |
| 187 | + empty: [], |
| 188 | + falsy: null |
| 189 | + }); |
| 190 | + |
| 191 | + expect(result.empty).toBe(""); |
| 192 | + expect(result.falsy).toBe(""); |
| 193 | + }); |
| 194 | + |
| 195 | + it("cx.multiple returns correct type", () => { |
| 196 | + const result = cx.multiple({ |
| 197 | + a: "foo", |
| 198 | + b: ["bar"] |
| 199 | + }); |
| 200 | + |
| 201 | + assertType<{ a: string; b: string }>(result); |
| 202 | + }); |
| 203 | + }); |
130 | 204 | } |
0 commit comments