windctrl 0.2.0 → 0.2.2
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/README.md +55 -0
- package/dist/dynamic.d.ts +22 -0
- package/dist/dynamic.js +41 -0
- package/dist/index.d.ts +5 -22
- package/dist/index.js +12 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -257,6 +257,61 @@ const button = windctrl({
|
|
|
257
257
|
|
|
258
258
|
The scope classes are automatically prefixed with `group-data-[windctrl-scope=...]/windctrl-scope:` to target the parent's data attribute.
|
|
259
259
|
|
|
260
|
+
## Merging External `className` Safely (`wcn()`)
|
|
261
|
+
|
|
262
|
+
WindCtrl resolves Tailwind class conflicts **inside** `windctrl()` using `tailwind-merge`.
|
|
263
|
+
However, in real applications you often need to merge **additional `className` values** at the component boundary.
|
|
264
|
+
|
|
265
|
+
A simple string concat can reintroduce conflicts:
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
// ⚠️ Can cause subtle Tailwind conflicts (e.g. p-2 vs p-4)
|
|
269
|
+
className={`${result.className} ${className}`}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
WindCtrl exports a small helper for this use case:
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
import { wcn } from "windctrl";
|
|
276
|
+
|
|
277
|
+
// ✅ Conflict-safe merge
|
|
278
|
+
className={wcn(result.className, className)}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`wcn()` is equivalent to `twMerge(clsx(...))` and matches WindCtrl’s internal conflict resolution behavior.
|
|
282
|
+
This keeps the “last one wins” behavior consistent across both generated and user-supplied classes.
|
|
283
|
+
|
|
284
|
+
## Type Helpers (`StyleProps`)
|
|
285
|
+
|
|
286
|
+
When building reusable components, you often want to expose the exact style-related props inferred from a `windctrl()` definition.
|
|
287
|
+
|
|
288
|
+
WindCtrl exports a small type helper for this purpose:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import type { StyleProps } from "windctrl";
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`StyleProps<typeof styles>` extracts all variant, trait, and dynamic props from a WindCtrl instance — similar to `VariantProps` in cva.
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
const button = windctrl({ ... });
|
|
298
|
+
|
|
299
|
+
type ButtonProps<T extends ElementType = "button"> = {
|
|
300
|
+
as?: T;
|
|
301
|
+
} & Omit<ComponentPropsWithoutRef<T>, keyof StyleProps<typeof button>>
|
|
302
|
+
& StyleProps<typeof button>;
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
This lets you:
|
|
306
|
+
|
|
307
|
+
- Avoid manually duplicating variant/trait prop definitions
|
|
308
|
+
- Keep component props automatically in sync with styling config
|
|
309
|
+
- Refactor styles without touching component typings
|
|
310
|
+
|
|
311
|
+
> `StyleProps` is optional - you can always define props manually if you prefer.
|
|
312
|
+
|
|
313
|
+
> `wcProps` is provided as an alias of `StyleProps` for convenience.
|
|
314
|
+
|
|
260
315
|
## Gotchas
|
|
261
316
|
|
|
262
317
|
- **Tailwind JIT:** Tailwind only generates CSS for class names it can statically detect. Avoid constructing class strings dynamically unless you safelist them.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type CSSProperties = Record<string, string | number>;
|
|
2
|
+
export type DynamicResolverResult = string | {
|
|
3
|
+
className?: string;
|
|
4
|
+
style?: CSSProperties;
|
|
5
|
+
};
|
|
6
|
+
export type DynamicResolver<T = any> = (value: T) => DynamicResolverResult;
|
|
7
|
+
type PxProp = "width" | "height" | "minWidth" | "maxWidth" | "minHeight" | "maxHeight" | "top" | "right" | "bottom" | "left";
|
|
8
|
+
type NumProp = "zIndex" | "flexGrow" | "flexShrink" | "order";
|
|
9
|
+
type VarUnit = "px" | "%" | "deg" | "ms";
|
|
10
|
+
declare function px(prop: PxProp): DynamicResolver<number | string>;
|
|
11
|
+
declare function num(prop: NumProp): DynamicResolver<number | string>;
|
|
12
|
+
declare function opacity(): DynamicResolver<number | string>;
|
|
13
|
+
declare function cssVar(name: `--${string}`, options?: {
|
|
14
|
+
unit?: VarUnit;
|
|
15
|
+
}): DynamicResolver<number | string>;
|
|
16
|
+
export declare const dynamic: {
|
|
17
|
+
px: typeof px;
|
|
18
|
+
num: typeof num;
|
|
19
|
+
opacity: typeof opacity;
|
|
20
|
+
var: typeof cssVar;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
package/dist/dynamic.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function px(prop) {
|
|
2
|
+
return (value) => {
|
|
3
|
+
if (typeof value === "number") {
|
|
4
|
+
return { style: { [prop]: `${value}px` } };
|
|
5
|
+
}
|
|
6
|
+
return value;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function num(prop) {
|
|
10
|
+
return (value) => {
|
|
11
|
+
if (typeof value === "number") {
|
|
12
|
+
return { style: { [prop]: value } };
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function opacity() {
|
|
18
|
+
return (value) => {
|
|
19
|
+
if (typeof value === "number") {
|
|
20
|
+
return { style: { opacity: value } };
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function cssVar(name, options) {
|
|
26
|
+
return (value) => {
|
|
27
|
+
if (typeof value === "number") {
|
|
28
|
+
if (options?.unit) {
|
|
29
|
+
return { style: { [name]: `${value}${options.unit}` } };
|
|
30
|
+
}
|
|
31
|
+
return { style: { [name]: String(value) } };
|
|
32
|
+
}
|
|
33
|
+
return { style: { [name]: value } };
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export const dynamic = {
|
|
37
|
+
px,
|
|
38
|
+
num,
|
|
39
|
+
opacity,
|
|
40
|
+
var: cssVar,
|
|
41
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
import { type ClassValue } from "clsx";
|
|
2
|
-
type CSSProperties
|
|
3
|
-
type
|
|
4
|
-
className?: string;
|
|
5
|
-
style?: CSSProperties;
|
|
6
|
-
};
|
|
7
|
-
type DynamicResolver<T = any> = (value: T) => DynamicResolverResult;
|
|
8
|
-
type PxProp = "width" | "height" | "minWidth" | "maxWidth" | "minHeight" | "maxHeight" | "top" | "right" | "bottom" | "left";
|
|
9
|
-
type NumProp = "zIndex" | "flexGrow" | "flexShrink" | "order";
|
|
10
|
-
type VarUnit = "px" | "%" | "deg" | "ms";
|
|
11
|
-
declare function px(prop: PxProp): DynamicResolver<number | string>;
|
|
12
|
-
declare function num(prop: NumProp): DynamicResolver<number | string>;
|
|
13
|
-
declare function opacity(): DynamicResolver<number | string>;
|
|
14
|
-
declare function cssVar(name: `--${string}`, options?: {
|
|
15
|
-
unit?: VarUnit;
|
|
16
|
-
}): DynamicResolver<number | string>;
|
|
17
|
-
export declare const dynamic: {
|
|
18
|
-
px: typeof px;
|
|
19
|
-
num: typeof num;
|
|
20
|
-
opacity: typeof opacity;
|
|
21
|
-
var: typeof cssVar;
|
|
22
|
-
};
|
|
2
|
+
import type { CSSProperties, DynamicResolver } from "./dynamic";
|
|
3
|
+
export { dynamic, type CSSProperties, type DynamicResolver } from "./dynamic";
|
|
23
4
|
type SlotAwareObject = {
|
|
24
5
|
root?: ClassValue;
|
|
25
6
|
slots?: Record<string, ClassValue>;
|
|
@@ -55,4 +36,6 @@ type Result<TSlotKeys extends string = never> = {
|
|
|
55
36
|
};
|
|
56
37
|
export declare function windctrl<TVariants extends Record<string, Record<string, SlotAwareValue>> = {}, TTraits extends Record<string, SlotAwareValue> = {}, TDynamic extends Record<string, DynamicResolver> = {}, TScopes extends Record<string, ClassValue> = {}>(config: Config<TVariants, TTraits, TDynamic, TScopes>): (props?: Props<TVariants, TTraits, TDynamic>) => Result<SlotKeys<typeof config.base, TVariants, TTraits>>;
|
|
57
38
|
export declare const wc: typeof windctrl;
|
|
58
|
-
export
|
|
39
|
+
export declare function wcn(...inputs: ClassValue[]): string;
|
|
40
|
+
export type StyleProps<T> = T extends (props?: infer P) => any ? P : never;
|
|
41
|
+
export type wcProps<T> = StyleProps<T>;
|
package/dist/index.js
CHANGED
|
@@ -1,46 +1,6 @@
|
|
|
1
1
|
import { clsx } from "clsx";
|
|
2
2
|
import { twMerge } from "tailwind-merge";
|
|
3
|
-
|
|
4
|
-
return (value) => {
|
|
5
|
-
if (typeof value === "number") {
|
|
6
|
-
return { style: { [prop]: `${value}px` } };
|
|
7
|
-
}
|
|
8
|
-
return value;
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
function num(prop) {
|
|
12
|
-
return (value) => {
|
|
13
|
-
if (typeof value === "number") {
|
|
14
|
-
return { style: { [prop]: value } };
|
|
15
|
-
}
|
|
16
|
-
return value;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
function opacity() {
|
|
20
|
-
return (value) => {
|
|
21
|
-
if (typeof value === "number") {
|
|
22
|
-
return { style: { opacity: value } };
|
|
23
|
-
}
|
|
24
|
-
return value;
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
function cssVar(name, options) {
|
|
28
|
-
return (value) => {
|
|
29
|
-
if (typeof value === "number") {
|
|
30
|
-
if (options?.unit) {
|
|
31
|
-
return { style: { [name]: `${value}${options.unit}` } };
|
|
32
|
-
}
|
|
33
|
-
return { style: { [name]: String(value) } };
|
|
34
|
-
}
|
|
35
|
-
return { style: { [name]: value } };
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
export const dynamic = {
|
|
39
|
-
px,
|
|
40
|
-
num,
|
|
41
|
-
opacity,
|
|
42
|
-
var: cssVar,
|
|
43
|
-
};
|
|
3
|
+
export { dynamic } from "./dynamic";
|
|
44
4
|
function mergeStyles(...styles) {
|
|
45
5
|
return Object.assign({}, ...styles.filter(Boolean));
|
|
46
6
|
}
|
|
@@ -193,15 +153,18 @@ export function windctrl(config) {
|
|
|
193
153
|
let finalSlots;
|
|
194
154
|
const slotNames = Object.keys(slotParts);
|
|
195
155
|
if (slotNames.length > 0) {
|
|
196
|
-
|
|
156
|
+
const out = {};
|
|
197
157
|
for (const slotName of slotNames) {
|
|
198
|
-
const
|
|
158
|
+
const parts = slotParts[slotName];
|
|
159
|
+
if (!parts)
|
|
160
|
+
continue;
|
|
161
|
+
const merged = twMerge(clsx(parts));
|
|
199
162
|
if (merged) {
|
|
200
|
-
|
|
163
|
+
out[slotName] = merged;
|
|
201
164
|
}
|
|
202
165
|
}
|
|
203
|
-
if (Object.keys(
|
|
204
|
-
finalSlots =
|
|
166
|
+
if (Object.keys(out).length > 0) {
|
|
167
|
+
finalSlots = out;
|
|
205
168
|
}
|
|
206
169
|
}
|
|
207
170
|
return {
|
|
@@ -212,3 +175,6 @@ export function windctrl(config) {
|
|
|
212
175
|
};
|
|
213
176
|
}
|
|
214
177
|
export const wc = windctrl;
|
|
178
|
+
export function wcn(...inputs) {
|
|
179
|
+
return twMerge(clsx(inputs));
|
|
180
|
+
}
|
package/package.json
CHANGED