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/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 = (context: {
73
- defaultValue: unknown;
74
- variants: Readonly<Record<string, unknown>>;
75
- }) => unknown;
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
- context: DefaultVariantContext<V, K>,
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: ({ defaultValue, variants }) =>
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: ({ defaultValue, variants }) =>
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: ({ defaultValue, variants }) =>
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: ({ defaultValue, variants }) =>
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: ({ defaultValue, variants }) =>
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: ({ defaultValue, variants }) =>
443
+ color: (defaultValue, variants) =>
444
444
  variants.done ? "blue" : defaultValue,
445
445
  },
446
446
  refine: ({ variants, setVariants }) => {
@@ -206,7 +206,7 @@ for (const config of Object.values(CONFIGS)) {
206
206
  },
207
207
  defaultVariants: {
208
208
  size: "lg",
209
- color: ({ variants }) => (variants.size === "lg" ? "blue" : "red"),
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 tempDir = mkdtempSync(join(workspaceDir, ".tmp-language-service-"));
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(