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 CHANGED
@@ -1,5 +1,47 @@
1
1
  # clava
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Computed default variant parameters
6
+
7
+ **BREAKING** if you're using computed `defaultVariants` functions in [`cv`](https://clava.style/docs/reference/cv).
8
+
9
+ Computed `defaultVariants` functions now receive `defaultValue` and `variants` as separate parameters instead of a context object.
10
+
11
+ Before:
12
+
13
+ ```ts
14
+ const button = cv({
15
+ variants: {
16
+ size: { sm: "sm", lg: "lg" },
17
+ intent: { neutral: "neutral", brand: "brand" },
18
+ },
19
+ defaultVariants: {
20
+ intent: ({ defaultValue, variants }) =>
21
+ variants.size === "lg" ? "neutral" : defaultValue,
22
+ },
23
+ });
24
+ ```
25
+
26
+ After:
27
+
28
+ ```ts
29
+ const button = cv({
30
+ variants: {
31
+ size: { sm: "sm", lg: "lg" },
32
+ intent: { neutral: "neutral", brand: "brand" },
33
+ },
34
+ defaultVariants: {
35
+ intent: (defaultValue, variants) =>
36
+ variants.size === "lg" ? "neutral" : defaultValue,
37
+ },
38
+ });
39
+ ```
40
+
41
+ ### Other updates
42
+
43
+ - Documented Clava's public TypeScript helper declarations with inline examples.
44
+
3
45
  ## 0.5.0
4
46
 
5
47
  ### Computed default variants
@@ -18,7 +60,7 @@ const button = cv({
18
60
  size: { sm: "sm", lg: "lg" },
19
61
  intent: { neutral: "neutral", brand: "brand" },
20
62
  },
21
- refine: ({ variants, setDefaultVariants }) => {
63
+ refine({ variants, setDefaultVariants }) {
22
64
  if (variants.size === "lg") {
23
65
  setDefaultVariants({ intent: "neutral" });
24
66
  }
@@ -111,7 +153,7 @@ Before:
111
153
  ```ts
112
154
  const button = cv({
113
155
  variants: { size: { sm: "sm", lg: "lg" } },
114
- computed: ({ variants, addClass }) => {
156
+ computed({ variants, addClass }) {
115
157
  if (variants.size === "lg") {
116
158
  addClass("is-large");
117
159
  }
@@ -124,7 +166,7 @@ After:
124
166
  ```ts
125
167
  const button = cv({
126
168
  variants: { size: { sm: "sm", lg: "lg" } },
127
- refine: ({ variants, addClass }) => {
169
+ refine({ variants, addClass }) {
128
170
  if (variants.size === "lg") {
129
171
  addClass("is-large");
130
172
  }
package/README.md CHANGED
@@ -24,6 +24,7 @@ import type { Variant, VariantProps } from "clava";
24
24
  - [Solid](#solid)
25
25
  - [`create()` And `cx()`](#create-and-cx)
26
26
  - [Type Helpers](#type-helpers)
27
+ - [Comparison](#comparison)
27
28
  - [API Summary](#api-summary)
28
29
 
29
30
  ## Install
@@ -230,17 +231,19 @@ Variant values can be class values, arrays, `{ class, style }` objects, or [func
230
231
 
231
232
  ## Function Variants
232
233
 
233
- Add a function as a variant value when the prop should generate class/style output dynamically. The function's parameter type defines the prop type.
234
+ Add a function as a variant value when the prop should generate class/style output dynamically. The function's parameter type defines the prop type. Method syntax works well for multi-line callbacks; compact one-line callbacks can stay as arrow-function property values.
234
235
 
235
236
  ```ts
236
237
  const grid = cv({
237
238
  class: "grid",
238
239
  variants: {
239
- columns: (value: number) => ({
240
- class: `grid-cols-${value}`,
241
- style: { "--grid-columns": `${value}` },
242
- }),
243
- color: (value: string | null) => {
240
+ columns(value: number) {
241
+ return {
242
+ class: `grid-cols-${value}`,
243
+ style: { "--grid-columns": `${value}` },
244
+ };
245
+ },
246
+ color(value: string | null) {
244
247
  return value ? `text-${value}` : "text-current";
245
248
  },
246
249
  },
@@ -316,7 +319,7 @@ You can extend any component mode, including `baseButton.jsx`, `baseButton.html`
316
319
 
317
320
  ## Refine
318
321
 
319
- Use computed `defaultVariants` for dependent defaults. A function entry receives the current default value for that key plus the resolved variants snapshot, and returns the next default value.
322
+ Use computed `defaultVariants` for dependent defaults. A function entry receives the current default value for that key as the first parameter and the resolved variants snapshot as the second parameter, then returns the next default value.
320
323
 
321
324
  Use `refine` for final variant overrides and class/style adjustments. It receives the resolved variant values for the component and can return class/style output.
322
325
 
@@ -331,10 +334,10 @@ const toolbarButton = cv({
331
334
  loading: "toolbar-button-loading",
332
335
  },
333
336
  defaultVariants: {
334
- intent: ({ defaultValue, variants }) =>
337
+ intent: (defaultValue, variants) =>
335
338
  variants.size === "lg" ? "neutral" : defaultValue,
336
339
  },
337
- refine: ({ variants, setVariants, addClass, addStyle }) => {
340
+ refine({ variants, setVariants, addClass, addStyle }) {
338
341
  if (variants.loading) {
339
342
  setVariants({ pressed: false });
340
343
  }
@@ -384,17 +387,18 @@ const button = cv({
384
387
  lg: "button-lg",
385
388
  },
386
389
  },
387
- }).jsx;
390
+ });
388
391
 
389
- type ButtonProps = ComponentProps<"button"> & VariantProps<typeof button>;
392
+ interface ButtonProps
393
+ extends ComponentProps<"button">, VariantProps<typeof button> {}
390
394
 
391
395
  function Button(props: ButtonProps) {
392
396
  const [variantProps, buttonProps] = splitProps(props, button);
393
- return <button {...buttonProps} {...button(variantProps)} />;
397
+ return <button {...buttonProps} {...button.jsx(variantProps)} />;
394
398
  }
395
399
  ```
396
400
 
397
- The first component source claims variant props plus styling props (`class`, `className`, and `style`, depending on the mode). Later component sources receive only their variant props. Array sources receive exactly the listed keys and do not claim styling props.
401
+ The first component source claims variant props plus the styling props exposed by that component's `propKeys`. The base component claims `class`, `className`, and `style`; mode-specific components claim their own styling props. Later component sources receive only their variant props. Array sources receive exactly the listed keys and do not claim styling props.
398
402
 
399
403
  ```ts
400
404
  const [buttonProps, fieldProps, rest] = splitProps(props, button, field);
@@ -431,14 +435,14 @@ const button = cv({
431
435
  md: "button-md",
432
436
  },
433
437
  },
434
- }).jsx;
438
+ });
435
439
 
436
440
  interface ButtonProps
437
441
  extends ComponentProps<"button">, VariantProps<typeof button> {}
438
442
 
439
443
  function Button(props: ButtonProps) {
440
444
  const [variantProps, buttonProps] = splitProps(props, button);
441
- return <button {...buttonProps} {...button(variantProps)} />;
445
+ return <button {...buttonProps} {...button.jsx(variantProps)} />;
442
446
  }
443
447
  ```
444
448
 
@@ -459,13 +463,14 @@ const button = cv({
459
463
  md: "button-md",
460
464
  },
461
465
  },
462
- }).htmlObj;
466
+ });
463
467
 
464
- type ButtonProps = ComponentProps<"button"> & VariantProps<typeof button>;
468
+ interface ButtonProps
469
+ extends ComponentProps<"button">, VariantProps<typeof button> {}
465
470
 
466
471
  function Button(props: ButtonProps) {
467
472
  const [variantProps, buttonProps] = splitProps(props, button);
468
- return <button {...buttonProps} {...button(variantProps)} />;
473
+ return <button {...buttonProps} {...button.htmlObj(variantProps)} />;
469
474
  }
470
475
  ```
471
476
 
@@ -504,7 +509,8 @@ Use `VariantProps<typeof component>` to add a Clava component's variant props to
504
509
  import type { ComponentProps } from "react";
505
510
  import type { VariantProps } from "clava";
506
511
 
507
- type ButtonProps = ComponentProps<"button"> & VariantProps<typeof button>;
512
+ interface ButtonProps
513
+ extends ComponentProps<"button">, VariantProps<typeof button> {}
508
514
  ```
509
515
 
510
516
  Use `Variant<typeof component, "key">` to constrain a new variant map to the same values as another component's variant.
@@ -534,6 +540,36 @@ const icon = cv({
534
540
 
535
541
  The package also exports `ClassValue`, `StyleValue`, `StyleClassProps`, `StyleClassValue`, `JSXProps`, `HTMLProps`, `HTMLObjProps`, `CVComponent`, and `CVConfig`.
536
542
 
543
+ ## Comparison
544
+
545
+ This table compares Clava with the packages used by the repository's alternative benchmark: `class-variance-authority@0.7.1`, `cva@1.0.0-beta.4`, `tailwind-variants/lite@3.2.2`, and `tailwind-variants@3.2.2`.
546
+
547
+ | Feature | Clava | CVA v0 | CVA v1 beta | TV Lite | TV |
548
+ | ---------------------------------------------------- | -------------- | ------------------ | ------------------ | ------------------ | ------------------ |
549
+ | Typed variants and default variants | Yes | Yes | Yes | Yes | Yes |
550
+ | Boolean shorthand variants | Yes | No | No | No | No |
551
+ | Component extension or composition | `extend` | No helper | `compose()` | `extend` | `extend` |
552
+ | Cross-variant conditions | `refine()` | `compoundVariants` | `compoundVariants` | `compoundVariants` | `compoundVariants` |
553
+ | Function variant values | Yes | No | No | No | No |
554
+ | Computed default variants | Yes | No | No | No | No |
555
+ | Class and style prop output | Yes | Classes only | Classes only | Classes only | Classes only |
556
+ | JSX, HTML string, and hyphenated-object output modes | Yes | No | No | No | No |
557
+ | Built-in prop splitting for framework props | `splitProps()` | No | No | No | No |
558
+ | Dedicated slots API | No | No | No | Yes | Yes |
559
+ | Built-in Tailwind conflict merging | No | No | No | No | Yes |
560
+
561
+ The `pnpm perf-alternatives` benchmark resolves a composed Tailwind-style button with inherited variants, defaults, and cross-variant conditions. On Node v24.14.1, Vitest reported these results, where higher ops/sec is better:
562
+
563
+ | Package | Ops/sec | Relative to Clava |
564
+ | -------------------------------- | --------: | ----------------: |
565
+ | `clava@0.5.0` | 1,040,314 | 1.00x |
566
+ | `class-variance-authority@0.7.1` | 426,132 | 2.44x slower |
567
+ | `tailwind-variants/lite@3.2.2` | 369,732 | 2.81x slower |
568
+ | `tailwind-variants@3.2.2` | 273,503 | 3.80x slower |
569
+ | `cva@1.0.0-beta.4` | 211,474 | 4.92x slower |
570
+
571
+ Benchmark results vary by runtime and hardware, so treat them as a reproducible snapshot of this repository's composed-variant case rather than a universal ranking.
572
+
537
573
  ## API Summary
538
574
 
539
575
  `cv(config?)` creates a typed Clava component. Supported config keys are `extend`, `class`, `style`, `variants`, `defaultVariants`, and `refine`.
package/dist/index.d.ts CHANGED
@@ -2,22 +2,84 @@ import { ClassValue as ClassValue$1 } from "clsx";
2
2
  import * as CSS from "csstype";
3
3
 
4
4
  //#region src/types.d.ts
5
+ /**
6
+ * A class value accepted by Clava. It supports strings, numbers, booleans,
7
+ * nullish values, and nested arrays.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import type { ClassValue } from "clava";
12
+ *
13
+ * const className: ClassValue = [
14
+ * "button",
15
+ * false && "button-hidden",
16
+ * ["button-primary"],
17
+ * ];
18
+ * ```
19
+ */
5
20
  type ClassValue = string | number | boolean | null | undefined | void | ClassValue[];
6
21
  type JSXCSSProperties = CSS.Properties<string | number>;
7
22
  type HTMLCSSProperties = CSS.PropertiesHyphen<string | number>;
8
23
  type StyleProperty = JSXCSSProperties | HTMLCSSProperties | string;
24
+ /**
25
+ * The prop object returned by a component's `.jsx()` mode.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { type JSXProps, cv } from "clava";
30
+ *
31
+ * const button = cv({ class: "button" });
32
+ * const props: JSXProps = button.jsx();
33
+ * ```
34
+ */
9
35
  interface JSXProps {
10
36
  className: string;
11
37
  style: JSXCSSProperties;
12
38
  }
39
+ /**
40
+ * The prop object returned by a component's `.html()` mode. The `style` value
41
+ * is serialized as an HTML style string.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * import { type HTMLProps, cv } from "clava";
46
+ *
47
+ * const button = cv({ style: { color: "red" } });
48
+ * const props: HTMLProps = button.html();
49
+ * ```
50
+ */
13
51
  interface HTMLProps {
14
52
  class: string;
15
53
  style: string;
16
54
  }
55
+ /**
56
+ * The prop object returned by a component's `.htmlObj()` mode. The `style`
57
+ * value uses hyphenated CSS property names.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import { type HTMLObjProps, cv } from "clava";
62
+ *
63
+ * const button = cv({ style: { fontSize: "16px" } });
64
+ * const props: HTMLObjProps = button.htmlObj();
65
+ * ```
66
+ */
17
67
  interface HTMLObjProps {
18
68
  class: string;
19
69
  style: HTMLCSSProperties;
20
70
  }
71
+ /**
72
+ * The default prop object returned by a Clava component. It uses `class`
73
+ * rather than `className` and keeps styles as a normalized object.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * import { type StyleClassProps, cv } from "clava";
78
+ *
79
+ * const button = cv({ class: "button" });
80
+ * const props: StyleClassProps = button();
81
+ * ```
82
+ */
21
83
  interface StyleClassProps {
22
84
  class: string;
23
85
  style: StyleValue;
@@ -61,6 +123,25 @@ interface ModalComponent<V, R extends ComponentResult> {
61
123
  variantKeys: (keyof V)[];
62
124
  propKeys: (keyof V | ComponentPropKey<R>)[];
63
125
  }
126
+ /**
127
+ * A callable Clava component returned by `cv()`. It includes the default
128
+ * output mode plus `.jsx()`, `.html()`, and `.htmlObj()` mode helpers.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * import { type CVComponent, cv } from "clava";
133
+ *
134
+ * const button: CVComponent<{
135
+ * size: { sm: string; lg: string };
136
+ * }> = cv({
137
+ * variants: {
138
+ * size: { sm: "button-sm", lg: "button-lg" },
139
+ * },
140
+ * });
141
+ *
142
+ * button.jsx({ size: "lg" });
143
+ * ```
144
+ */
64
145
  interface CVComponent<V extends Variants = {}, E extends AnyComponent[] = [], R extends ComponentResult = StyleClassProps> extends ModalComponent<MergeVariants<V, E>, R> {
65
146
  jsx: ModalComponent<MergeVariants<V, E>, JSXProps>;
66
147
  html: ModalComponent<MergeVariants<V, E>, HTMLProps>;
@@ -77,17 +158,41 @@ type VariantValue = ClassValue | StyleClassValue;
77
158
  type NonNullKeys<T> = { [K in keyof T]: T[K] extends null ? never : K }[keyof T];
78
159
  type ExtractVariantValue<T> = T extends null ? never : T extends ((value: infer V) => any) ? V : T extends readonly unknown[] ? boolean : T extends Record<string, any> ? StringToBoolean<NonNullKeys<T>> : T extends ClassValue ? boolean : never;
79
160
  type VariantValues<V> = { [K in keyof V]?: ExtractVariantValue<V[K]> | undefined };
80
- interface DefaultVariantContext<V, K extends keyof V> {
81
- defaultValue: ExtractVariantValue<V[K]> | undefined;
82
- variants: Readonly<VariantValues<V>>;
83
- }
84
- type ComputedDefaultVariant<V, K extends keyof V> = (context: DefaultVariantContext<V, K>) => ExtractVariantValue<V[K]> | undefined;
161
+ type ComputedDefaultVariant<V, K extends keyof V> = (defaultValue: ExtractVariantValue<V[K]> | undefined, variants: Readonly<VariantValues<V>>) => ExtractVariantValue<V[K]> | undefined;
85
162
  type NonFunctionVariantValue<T> = Exclude<T, (...args: any[]) => any>;
86
163
  type DefaultVariantValue<V, K extends keyof V> = [NonFunctionVariantValue<ExtractVariantValue<V[K]>>] extends [never] ? ComputedDefaultVariant<V, K> : NonFunctionVariantValue<ExtractVariantValue<V[K]>> | ComputedDefaultVariant<V, K>;
87
164
  type DefaultVariants<V> = { [K in keyof V]?: DefaultVariantValue<V, K> | undefined };
165
+ /**
166
+ * A normalized style object accepted by Clava config, variant, and refine
167
+ * style entries. CSS custom properties are supported with string values.
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * import type { StyleValue } from "clava";
172
+ *
173
+ * const style: StyleValue = {
174
+ * color: "red",
175
+ * "--button-accent": "oklch(62% 0.2 250)",
176
+ * };
177
+ * ```
178
+ */
88
179
  type StyleValue = CSS.Properties & {
89
180
  [key: `--${string}`]: string;
90
181
  };
182
+ /**
183
+ * A value that contributes both class and style output from a base config,
184
+ * variant value, function variant, or refine callback.
185
+ *
186
+ * @example
187
+ * ```ts
188
+ * import type { StyleClassValue } from "clava";
189
+ *
190
+ * const tone: StyleClassValue = {
191
+ * class: "button-primary",
192
+ * style: { color: "white" },
193
+ * };
194
+ * ```
195
+ */
91
196
  interface StyleClassValue {
92
197
  style?: StyleValue;
93
198
  class?: ClassValue;
@@ -105,9 +210,87 @@ type NullablePartial<T> = T extends Record<string, any> ? { [K in keyof T]?: T[K
105
210
  type ExtendableVariants<V extends Variants, E extends AnyComponent[]> = V & { [K in keyof MergeExtendedVariants<E>]?: NullablePartial<MergeExtendedVariants<E>[K]> | Variant$1 };
106
211
  //#endregion
107
212
  //#region src/index.d.ts
213
+ /**
214
+ * Extracts the variant props inferred for a Clava component. Use it to add a
215
+ * component's variant props to framework component props.
216
+ *
217
+ * @example
218
+ * ```ts
219
+ * import { type VariantProps, cv } from "clava";
220
+ * import type { ComponentProps } from "react";
221
+ *
222
+ * const button = cv({
223
+ * variants: {
224
+ * size: { sm: "button-sm", lg: "button-lg" },
225
+ * disabled: { true: "button-disabled", false: "" },
226
+ * },
227
+ * });
228
+ *
229
+ * interface ButtonProps
230
+ * extends ComponentProps<"button">,
231
+ * VariantProps<typeof button> {}
232
+ *
233
+ * const props: ButtonProps = {
234
+ * size: "lg",
235
+ * disabled: true,
236
+ * };
237
+ * ```
238
+ */
108
239
  type VariantProps<T extends Pick<AnyComponent, "getVariants">> = ReturnType<T["getVariants"]>;
109
240
  type VariantKey<T> = T extends boolean ? "true" | "false" : Extract<T, string>;
241
+ /**
242
+ * Constrains a variant map to the same value keys as a variant on another
243
+ * component. Boolean variants are represented with `"true"` and `"false"`
244
+ * object keys.
245
+ *
246
+ * @example
247
+ * ```ts
248
+ * import { type Variant, cv } from "clava";
249
+ *
250
+ * const button = cv({
251
+ * variants: {
252
+ * size: { sm: "button-sm", lg: "button-lg" },
253
+ * },
254
+ * });
255
+ *
256
+ * const icon = cv({
257
+ * extend: [button],
258
+ * variants: {
259
+ * size: {
260
+ * sm: "icon-sm",
261
+ * lg: "icon-lg",
262
+ * } satisfies Variant<typeof button, "size">,
263
+ * },
264
+ * });
265
+ * ```
266
+ */
110
267
  type Variant<T extends Pick<AnyComponent, "getVariants">, K extends keyof VariantProps<T>> = Record<VariantKey<NonNullable<VariantProps<T>[K]>>, ClassValue | StyleClassValue>;
268
+ /**
269
+ * The configuration object accepted by `cv()`. It defines base class/style
270
+ * output, variants, default variants, component extensions, and refinement
271
+ * logic.
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * import { type CVConfig, cv } from "clava";
276
+ *
277
+ * const config: CVConfig<{
278
+ * tone: { info: string; danger: string };
279
+ * }> = {
280
+ * variants: {
281
+ * tone: {
282
+ * info: "alert-info",
283
+ * danger: "alert-danger",
284
+ * },
285
+ * },
286
+ * defaultVariants: {
287
+ * tone: "info",
288
+ * },
289
+ * };
290
+ *
291
+ * const alert = cv(config);
292
+ * ```
293
+ */
111
294
  interface CVConfig<V extends Variants = {}, E extends AnyComponent[] = []> {
112
295
  extend?: E;
113
296
  class?: ClassValue;
package/dist/index.js CHANGED
@@ -816,10 +816,7 @@ function create({ transformClass = (className) => className } = {}) {
816
816
  if (protectedVariantKeys?.has(key)) continue;
817
817
  const variantSnapshot = getOwnVariants();
818
818
  const defaultValue = inheritedComputedDefaultKeys.has(key) ? variantSnapshot[key] : defaultResolved[key];
819
- const value = computedDefaultFns[i]({
820
- defaultValue,
821
- variants: variantSnapshot
822
- });
819
+ const value = computedDefaultFns[i](defaultValue, variantSnapshot);
823
820
  if (hasAnyDisabled) {
824
821
  if (disabledVariantKeys.has(key)) continue;
825
822
  const valueKey = getVariantValueKey(value);