clava 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -3
- package/README.md +55 -19
- package/dist/index.d.ts +188 -5
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +83 -8
- package/src/types.ts +111 -6
- package/tests/component-api.test.ts +6 -6
- package/tests/extend.test.ts +1 -1
- package/tests/language-service.test.ts +12 -1
- package/tests/refine.test.ts +135 -179
- package/tests/variants-inference.test.ts +69 -1
package/src/index.ts
CHANGED
|
@@ -69,10 +69,10 @@ type ResolveRefineFn = (
|
|
|
69
69
|
defaultResolved?: Record<string, unknown>,
|
|
70
70
|
) => Record<string, unknown>;
|
|
71
71
|
|
|
72
|
-
type ComputedDefaultVariantFn = (
|
|
73
|
-
defaultValue: unknown
|
|
74
|
-
variants: Readonly<Record<string, unknown
|
|
75
|
-
|
|
72
|
+
type ComputedDefaultVariantFn = (
|
|
73
|
+
defaultValue: unknown,
|
|
74
|
+
variants: Readonly<Record<string, unknown>>,
|
|
75
|
+
) => unknown;
|
|
76
76
|
|
|
77
77
|
// Internal metadata stored on components but hidden from public types.
|
|
78
78
|
interface ComponentMeta {
|
|
@@ -218,12 +218,64 @@ export type {
|
|
|
218
218
|
CVComponent,
|
|
219
219
|
};
|
|
220
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Extracts the variant props inferred for a Clava component. Use it to add a
|
|
223
|
+
* component's variant props to framework component props.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```ts
|
|
227
|
+
* import { type VariantProps, cv } from "clava";
|
|
228
|
+
* import type { ComponentProps } from "react";
|
|
229
|
+
*
|
|
230
|
+
* const button = cv({
|
|
231
|
+
* variants: {
|
|
232
|
+
* size: { sm: "button-sm", lg: "button-lg" },
|
|
233
|
+
* disabled: { true: "button-disabled", false: "" },
|
|
234
|
+
* },
|
|
235
|
+
* });
|
|
236
|
+
*
|
|
237
|
+
* interface ButtonProps
|
|
238
|
+
* extends ComponentProps<"button">,
|
|
239
|
+
* VariantProps<typeof button> {}
|
|
240
|
+
*
|
|
241
|
+
* const props: ButtonProps = {
|
|
242
|
+
* size: "lg",
|
|
243
|
+
* disabled: true,
|
|
244
|
+
* };
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
221
247
|
export type VariantProps<T extends Pick<AnyComponent, "getVariants">> =
|
|
222
248
|
ReturnType<T["getVariants"]>;
|
|
223
249
|
|
|
224
250
|
// Variant props expose booleans, but variant object keys are always strings.
|
|
225
251
|
type VariantKey<T> = T extends boolean ? "true" | "false" : Extract<T, string>;
|
|
226
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Constrains a variant map to the same value keys as a variant on another
|
|
255
|
+
* component. Boolean variants are represented with `"true"` and `"false"`
|
|
256
|
+
* object keys.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
260
|
+
* import { type Variant, cv } from "clava";
|
|
261
|
+
*
|
|
262
|
+
* const button = cv({
|
|
263
|
+
* variants: {
|
|
264
|
+
* size: { sm: "button-sm", lg: "button-lg" },
|
|
265
|
+
* },
|
|
266
|
+
* });
|
|
267
|
+
*
|
|
268
|
+
* const icon = cv({
|
|
269
|
+
* extend: [button],
|
|
270
|
+
* variants: {
|
|
271
|
+
* size: {
|
|
272
|
+
* sm: "icon-sm",
|
|
273
|
+
* lg: "icon-lg",
|
|
274
|
+
* } satisfies Variant<typeof button, "size">,
|
|
275
|
+
* },
|
|
276
|
+
* });
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
227
279
|
export type Variant<
|
|
228
280
|
T extends Pick<AnyComponent, "getVariants">,
|
|
229
281
|
K extends keyof VariantProps<T>,
|
|
@@ -232,6 +284,32 @@ export type Variant<
|
|
|
232
284
|
ClassValue | StyleClassValue
|
|
233
285
|
>;
|
|
234
286
|
|
|
287
|
+
/**
|
|
288
|
+
* The configuration object accepted by `cv()`. It defines base class/style
|
|
289
|
+
* output, variants, default variants, component extensions, and refinement
|
|
290
|
+
* logic.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* import { type CVConfig, cv } from "clava";
|
|
295
|
+
*
|
|
296
|
+
* const config: CVConfig<{
|
|
297
|
+
* tone: { info: string; danger: string };
|
|
298
|
+
* }> = {
|
|
299
|
+
* variants: {
|
|
300
|
+
* tone: {
|
|
301
|
+
* info: "alert-info",
|
|
302
|
+
* danger: "alert-danger",
|
|
303
|
+
* },
|
|
304
|
+
* },
|
|
305
|
+
* defaultVariants: {
|
|
306
|
+
* tone: "info",
|
|
307
|
+
* },
|
|
308
|
+
* };
|
|
309
|
+
*
|
|
310
|
+
* const alert = cv(config);
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
235
313
|
export interface CVConfig<
|
|
236
314
|
V extends Variants = {},
|
|
237
315
|
E extends AnyComponent[] = [],
|
|
@@ -1015,10 +1093,7 @@ export function create({
|
|
|
1015
1093
|
const defaultValue = inheritedComputedDefaultKeys.has(key)
|
|
1016
1094
|
? variantSnapshot[key]
|
|
1017
1095
|
: defaultResolved[key];
|
|
1018
|
-
const value = computedDefaultFns[i](
|
|
1019
|
-
defaultValue,
|
|
1020
|
-
variants: variantSnapshot,
|
|
1021
|
-
});
|
|
1096
|
+
const value = computedDefaultFns[i](defaultValue, variantSnapshot);
|
|
1022
1097
|
if (hasAnyDisabled) {
|
|
1023
1098
|
if (disabledVariantKeys.has(key)) continue;
|
|
1024
1099
|
const valueKey = getVariantValueKey(value);
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import type * as CSS from "csstype";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A class value accepted by Clava. It supports strings, numbers, booleans,
|
|
5
|
+
* nullish values, and nested arrays.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import type { ClassValue } from "clava";
|
|
10
|
+
*
|
|
11
|
+
* const className: ClassValue = [
|
|
12
|
+
* "button",
|
|
13
|
+
* false && "button-hidden",
|
|
14
|
+
* ["button-primary"],
|
|
15
|
+
* ];
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
3
18
|
export type ClassValue =
|
|
4
19
|
| string
|
|
5
20
|
| number
|
|
@@ -17,21 +32,68 @@ export type CSSProperties = CSS.Properties;
|
|
|
17
32
|
|
|
18
33
|
export type StyleProperty = JSXCSSProperties | HTMLCSSProperties | string;
|
|
19
34
|
|
|
35
|
+
/**
|
|
36
|
+
* The prop object returned by a component's `.jsx()` mode.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { type JSXProps, cv } from "clava";
|
|
41
|
+
*
|
|
42
|
+
* const button = cv({ class: "button" });
|
|
43
|
+
* const props: JSXProps = button.jsx();
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
20
46
|
export interface JSXProps {
|
|
21
47
|
className: string;
|
|
22
48
|
style: JSXCSSProperties;
|
|
23
49
|
}
|
|
24
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The prop object returned by a component's `.html()` mode. The `style` value
|
|
53
|
+
* is serialized as an HTML style string.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { type HTMLProps, cv } from "clava";
|
|
58
|
+
*
|
|
59
|
+
* const button = cv({ style: { color: "red" } });
|
|
60
|
+
* const props: HTMLProps = button.html();
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
25
63
|
export interface HTMLProps {
|
|
26
64
|
class: string;
|
|
27
65
|
style: string;
|
|
28
66
|
}
|
|
29
67
|
|
|
68
|
+
/**
|
|
69
|
+
* The prop object returned by a component's `.htmlObj()` mode. The `style`
|
|
70
|
+
* value uses hyphenated CSS property names.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { type HTMLObjProps, cv } from "clava";
|
|
75
|
+
*
|
|
76
|
+
* const button = cv({ style: { fontSize: "16px" } });
|
|
77
|
+
* const props: HTMLObjProps = button.htmlObj();
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
30
80
|
export interface HTMLObjProps {
|
|
31
81
|
class: string;
|
|
32
82
|
style: HTMLCSSProperties;
|
|
33
83
|
}
|
|
34
84
|
|
|
85
|
+
/**
|
|
86
|
+
* The default prop object returned by a Clava component. It uses `class`
|
|
87
|
+
* rather than `className` and keeps styles as a normalized object.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { type StyleClassProps, cv } from "clava";
|
|
92
|
+
*
|
|
93
|
+
* const button = cv({ class: "button" });
|
|
94
|
+
* const props: StyleClassProps = button();
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
35
97
|
export interface StyleClassProps {
|
|
36
98
|
class: string;
|
|
37
99
|
style: StyleValue;
|
|
@@ -177,6 +239,25 @@ export interface ModalComponent<V, R extends ComponentResult> {
|
|
|
177
239
|
propKeys: (keyof V | ComponentPropKey<R>)[];
|
|
178
240
|
}
|
|
179
241
|
|
|
242
|
+
/**
|
|
243
|
+
* A callable Clava component returned by `cv()`. It includes the default
|
|
244
|
+
* output mode plus `.jsx()`, `.html()`, and `.htmlObj()` mode helpers.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```ts
|
|
248
|
+
* import { type CVComponent, cv } from "clava";
|
|
249
|
+
*
|
|
250
|
+
* const button: CVComponent<{
|
|
251
|
+
* size: { sm: string; lg: string };
|
|
252
|
+
* }> = cv({
|
|
253
|
+
* variants: {
|
|
254
|
+
* size: { sm: "button-sm", lg: "button-lg" },
|
|
255
|
+
* },
|
|
256
|
+
* });
|
|
257
|
+
*
|
|
258
|
+
* button.jsx({ size: "lg" });
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
180
261
|
export interface CVComponent<
|
|
181
262
|
V extends Variants = {},
|
|
182
263
|
E extends AnyComponent[] = [],
|
|
@@ -257,13 +338,9 @@ export type VariantValues<V> = {
|
|
|
257
338
|
[K in keyof V]?: ExtractVariantValue<V[K]> | undefined;
|
|
258
339
|
};
|
|
259
340
|
|
|
260
|
-
interface DefaultVariantContext<V, K extends keyof V> {
|
|
261
|
-
defaultValue: ExtractVariantValue<V[K]> | undefined;
|
|
262
|
-
variants: Readonly<VariantValues<V>>;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
341
|
type ComputedDefaultVariant<V, K extends keyof V> = (
|
|
266
|
-
|
|
342
|
+
defaultValue: ExtractVariantValue<V[K]> | undefined,
|
|
343
|
+
variants: Readonly<VariantValues<V>>,
|
|
267
344
|
) => ExtractVariantValue<V[K]> | undefined;
|
|
268
345
|
|
|
269
346
|
type NonFunctionVariantValue<T> = Exclude<T, (...args: any[]) => any>;
|
|
@@ -280,10 +357,38 @@ export type DefaultVariants<V> = {
|
|
|
280
357
|
[K in keyof V]?: DefaultVariantValue<V, K> | undefined;
|
|
281
358
|
};
|
|
282
359
|
|
|
360
|
+
/**
|
|
361
|
+
* A normalized style object accepted by Clava config, variant, and refine
|
|
362
|
+
* style entries. CSS custom properties are supported with string values.
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```ts
|
|
366
|
+
* import type { StyleValue } from "clava";
|
|
367
|
+
*
|
|
368
|
+
* const style: StyleValue = {
|
|
369
|
+
* color: "red",
|
|
370
|
+
* "--button-accent": "oklch(62% 0.2 250)",
|
|
371
|
+
* };
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
283
374
|
export type StyleValue = CSS.Properties & {
|
|
284
375
|
[key: `--${string}`]: string;
|
|
285
376
|
};
|
|
286
377
|
|
|
378
|
+
/**
|
|
379
|
+
* A value that contributes both class and style output from a base config,
|
|
380
|
+
* variant value, function variant, or refine callback.
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ```ts
|
|
384
|
+
* import type { StyleClassValue } from "clava";
|
|
385
|
+
*
|
|
386
|
+
* const tone: StyleClassValue = {
|
|
387
|
+
* class: "button-primary",
|
|
388
|
+
* style: { color: "white" },
|
|
389
|
+
* };
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
287
392
|
export interface StyleClassValue {
|
|
288
393
|
style?: StyleValue;
|
|
289
394
|
class?: ClassValue;
|
|
@@ -137,7 +137,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
137
137
|
},
|
|
138
138
|
defaultVariants: {
|
|
139
139
|
color: () => "red" as const,
|
|
140
|
-
size: (
|
|
140
|
+
size: (defaultValue, variants) =>
|
|
141
141
|
variants.color === "red" ? "lg" : defaultValue,
|
|
142
142
|
},
|
|
143
143
|
}),
|
|
@@ -155,7 +155,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
155
155
|
color: { red: "red", blue: "blue" },
|
|
156
156
|
},
|
|
157
157
|
defaultVariants: {
|
|
158
|
-
color: (
|
|
158
|
+
color: (defaultValue, variants) =>
|
|
159
159
|
variants.size === "lg" ? "blue" : defaultValue,
|
|
160
160
|
},
|
|
161
161
|
}),
|
|
@@ -271,7 +271,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
271
271
|
mode: { on: "on" },
|
|
272
272
|
},
|
|
273
273
|
defaultVariants: {
|
|
274
|
-
size: (
|
|
274
|
+
size: (defaultValue, variants) =>
|
|
275
275
|
variants.mode === "on" ? "lg" : defaultValue,
|
|
276
276
|
},
|
|
277
277
|
refine: ({ variants, setVariants }) => {
|
|
@@ -356,7 +356,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
356
356
|
mode: { on: "on" },
|
|
357
357
|
},
|
|
358
358
|
defaultVariants: {
|
|
359
|
-
size: (
|
|
359
|
+
size: (defaultValue, variants) =>
|
|
360
360
|
variants.mode === "on" ? "lg" : defaultValue,
|
|
361
361
|
},
|
|
362
362
|
refine: ({ variants, setVariants }) => {
|
|
@@ -422,7 +422,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
422
422
|
extend: [base],
|
|
423
423
|
variants: { color: { red: "child-red", blue: "child-blue" } },
|
|
424
424
|
defaultVariants: {
|
|
425
|
-
color: (
|
|
425
|
+
color: (defaultValue, variants) =>
|
|
426
426
|
variants.color === "red" ? "blue" : defaultValue,
|
|
427
427
|
},
|
|
428
428
|
}),
|
|
@@ -440,7 +440,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
440
440
|
done: "",
|
|
441
441
|
},
|
|
442
442
|
defaultVariants: {
|
|
443
|
-
color: (
|
|
443
|
+
color: (defaultValue, variants) =>
|
|
444
444
|
variants.done ? "blue" : defaultValue,
|
|
445
445
|
},
|
|
446
446
|
refine: ({ variants, setVariants }) => {
|
package/tests/extend.test.ts
CHANGED
|
@@ -206,7 +206,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
206
206
|
},
|
|
207
207
|
defaultVariants: {
|
|
208
208
|
size: "lg",
|
|
209
|
-
color: (
|
|
209
|
+
color: (_, variants) => (variants.size === "lg" ? "blue" : "red"),
|
|
210
210
|
},
|
|
211
211
|
}),
|
|
212
212
|
);
|
|
@@ -80,7 +80,10 @@ function createLanguageServiceHost(
|
|
|
80
80
|
function createLanguageServiceFixture(
|
|
81
81
|
consumerSource: string,
|
|
82
82
|
): LanguageServiceFixture {
|
|
83
|
-
const
|
|
83
|
+
const tempRoot = join(workspaceDir, ".tmp");
|
|
84
|
+
mkdirSync(tempRoot, { recursive: true });
|
|
85
|
+
|
|
86
|
+
const tempDir = mkdtempSync(join(tempRoot, "language-service-"));
|
|
84
87
|
tempDirs.push(tempDir);
|
|
85
88
|
|
|
86
89
|
const fixtureSourceDir = join(tempDir, "src");
|
|
@@ -134,6 +137,14 @@ afterEach(() => {
|
|
|
134
137
|
});
|
|
135
138
|
|
|
136
139
|
describe("TypeScript language service", () => {
|
|
140
|
+
test("creates fixtures inside the workspace .tmp directory", () => {
|
|
141
|
+
const fixture = createLanguageServiceFixture(getVariantFixtureSource());
|
|
142
|
+
const fixtureDir = dirname(fixture.consumerFile);
|
|
143
|
+
const fixturePrefix = join(workspaceDir, ".tmp", "language-service-");
|
|
144
|
+
|
|
145
|
+
expect(fixtureDir.startsWith(fixturePrefix)).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
|
|
137
148
|
test("goes to the local variant definition from a variant prop usage", () => {
|
|
138
149
|
const fixture = createLanguageServiceFixture(getVariantFixtureSource());
|
|
139
150
|
const definitionStart = getFixturePosition(
|