@vitus-labs/rocketstyle 2.0.0-alpha.8 → 2.0.0-beta.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/README.md +4 -4
- package/lib/index.d.ts +15 -7
- package/lib/index.js +82 -37
- package/package.json +12 -10
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Multi-dimensional styling system for React.
|
|
|
5
5
|
[](https://www.npmjs.com/package/@vitus-labs/rocketstyle)
|
|
6
6
|
[](https://github.com/vitus-labs/ui-system/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
-
Organize component styles by dimensions — states, sizes, variants — instead of flat props. Chain theme values, attach
|
|
8
|
+
Organize component styles by dimensions — states, sizes, variants — instead of flat props. Chain theme values, attach CSS via the active connector, and get full TypeScript inference. Built-in pseudo-state handling, light/dark mode, and provider/consumer patterns for parent-child state propagation.
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
@@ -21,7 +21,7 @@ Organize component styles by dimensions — states, sizes, variants — instead
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npm install @vitus-labs/rocketstyle @vitus-labs/core
|
|
24
|
+
npm install @vitus-labs/rocketstyle @vitus-labs/core
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Quick Start
|
|
@@ -110,7 +110,7 @@ Pseudo-state keys nest directly in the theme object:
|
|
|
110
110
|
|
|
111
111
|
### Styles Function
|
|
112
112
|
|
|
113
|
-
The `.styles()` method defines the
|
|
113
|
+
The `.styles()` method defines the CSS template that receives the computed theme:
|
|
114
114
|
|
|
115
115
|
```tsx
|
|
116
116
|
.styles((css) => css`
|
|
@@ -200,7 +200,7 @@ Button.states((theme, mode, css) => ({
|
|
|
200
200
|
|
|
201
201
|
### .styles(callback)
|
|
202
202
|
|
|
203
|
-
Define the
|
|
203
|
+
Define the CSS template using the active connector's `css` tagged template.
|
|
204
204
|
|
|
205
205
|
```tsx
|
|
206
206
|
Button.styles((css) => css`
|
package/lib/index.d.ts
CHANGED
|
@@ -35,6 +35,11 @@ declare const DEFAULT_DIMENSIONS: {
|
|
|
35
35
|
readonly propName: "multiple";
|
|
36
36
|
readonly multi: true;
|
|
37
37
|
};
|
|
38
|
+
readonly modifiers: {
|
|
39
|
+
readonly propName: "modifier";
|
|
40
|
+
readonly multi: true;
|
|
41
|
+
readonly transform: true;
|
|
42
|
+
};
|
|
38
43
|
};
|
|
39
44
|
type DefaultDimensions = typeof DEFAULT_DIMENSIONS;
|
|
40
45
|
//#endregion
|
|
@@ -82,7 +87,7 @@ type ExtractProps<TComponentOrTProps> = TComponentOrTProps extends ComponentType
|
|
|
82
87
|
//#endregion
|
|
83
88
|
//#region src/types/styles.d.ts
|
|
84
89
|
interface StylesDefault {}
|
|
85
|
-
type Styles<S
|
|
90
|
+
type Styles<S = unknown> = StylesDefault;
|
|
86
91
|
type Css = typeof config.css;
|
|
87
92
|
/**
|
|
88
93
|
* Props available inside `.styles()` interpolation functions.
|
|
@@ -129,15 +134,17 @@ type ExtractDimensionMulti<T extends DimensionValue> = T extends DimensionValueO
|
|
|
129
134
|
type DimensionValuePrimitive = string;
|
|
130
135
|
type DimensionValueObj = {
|
|
131
136
|
propName: string;
|
|
132
|
-
multi?: boolean;
|
|
137
|
+
multi?: boolean; /** When true, this dimension is evaluated last and its values receive the accumulated theme as argument. */
|
|
138
|
+
transform?: boolean;
|
|
133
139
|
};
|
|
134
140
|
type DimensionValue = DimensionValuePrimitive | DimensionValueObj;
|
|
135
141
|
type Dimensions = Record<string, DimensionValue>;
|
|
136
142
|
type MultiKeys<T extends Dimensions = Dimensions> = Partial<Record<ExtractDimensionKey<T[keyof T]>, true>>;
|
|
137
|
-
type
|
|
138
|
-
type
|
|
139
|
-
type
|
|
140
|
-
type
|
|
143
|
+
type DeepPartial<T> = { [K in keyof T]?: T[K] extends ((...args: any[]) => any) ? T[K] : NonNullable<T[K]> extends Record<string, any> ? DeepPartial<NonNullable<T[K]>> | Extract<T[K], null> : T[K] };
|
|
144
|
+
type DimensionResult<CT, T = any> = Record<string, boolean | null | DeepPartial<CT> | ((theme: CT, appTheme: T, mode: ThemeModeCallback, css: Css) => DeepPartial<CT>)>;
|
|
145
|
+
type DimensionObj<CT, T = any> = DimensionResult<CT, T>;
|
|
146
|
+
type DimensionCb<T, CT> = (theme: T, mode: ThemeModeCallback, css: Css) => DimensionResult<CT, T>;
|
|
147
|
+
type DimensionCallbackParam<T, CT> = DimensionObj<CT, T> | DimensionCb<T, CT>;
|
|
141
148
|
type TDKP = Record<ExtractDimensionKey<Dimensions[keyof Dimensions]>, Record<string, boolean | never | Record<string, boolean>> | unknown>;
|
|
142
149
|
type DimensionProps<K extends DimensionValue, D extends Dimensions, P extends CallBackParam, DKP extends TDKP> = { [I in ExtractDimensionKey<D[keyof D]>]: I extends ExtractDimensionKey<K> ? ExtractNullableDimensionKeys<Spread<[DKP[I], NullableKeys<ReturnCbParam<P>>]>> : DKP[I] };
|
|
143
150
|
type DimensionTypesHelper<DKP extends TDKP> = { [I in keyof DKP]: keyof DKP[I] };
|
|
@@ -180,6 +187,7 @@ type InitConfiguration<C, D> = {
|
|
|
180
187
|
dimensionKeys: ArrayOfKeys<D>;
|
|
181
188
|
dimensionValues: ArrayOfValues<D>;
|
|
182
189
|
multiKeys: MultiKeys;
|
|
190
|
+
transformKeys: Partial<Record<string, true>>;
|
|
183
191
|
};
|
|
184
192
|
type Configuration<C = ElementType | unknown, D extends Dimensions = Dimensions> = InitConfiguration<C, D> & {
|
|
185
193
|
provider?: boolean;
|
|
@@ -365,7 +373,7 @@ interface IRocketStyleComponent<OA extends TObj = {}, EA extends TObj = {}, T ex
|
|
|
365
373
|
* }))
|
|
366
374
|
* ```
|
|
367
375
|
*/
|
|
368
|
-
theme: <P extends TObj = {}>(param: Partial<P> | ThemeCb<P, Theme<T>>) => RocketStyleComponent<OA, EA, T, MergeTypes<[CSS, P]>, S, HOC, D, UB, DKP>;
|
|
376
|
+
theme: <P extends TObj = {}>(param: Partial<P> | Partial<Styles<CSS>> | ThemeCb<P, Theme<T>>) => RocketStyleComponent<OA, EA, T, MergeTypes<[CSS, P]>, S, HOC, D, UB, DKP>;
|
|
369
377
|
/**
|
|
370
378
|
* A chaining method to define default rendered styles
|
|
371
379
|
* @param param Callback of styled-components `css` function
|
package/lib/index.js
CHANGED
|
@@ -85,6 +85,11 @@ const DEFAULT_DIMENSIONS = {
|
|
|
85
85
|
multiple: {
|
|
86
86
|
propName: "multiple",
|
|
87
87
|
multi: true
|
|
88
|
+
},
|
|
89
|
+
modifiers: {
|
|
90
|
+
propName: "modifier",
|
|
91
|
+
multi: true,
|
|
92
|
+
transform: true
|
|
88
93
|
}
|
|
89
94
|
};
|
|
90
95
|
|
|
@@ -197,12 +202,18 @@ const useThemeAttrs = ({ inversed }) => {
|
|
|
197
202
|
const { theme = {}, mode: ctxMode = "light", isDark: ctxDark } = useContext(context) || {};
|
|
198
203
|
const mode = inversed ? THEME_MODES_INVERSED[ctxMode] : ctxMode;
|
|
199
204
|
const isDark = inversed ? !ctxDark : ctxDark;
|
|
200
|
-
|
|
205
|
+
const isLight = !isDark;
|
|
206
|
+
return useMemo(() => ({
|
|
201
207
|
theme,
|
|
202
208
|
mode,
|
|
203
209
|
isDark,
|
|
204
|
-
isLight
|
|
205
|
-
}
|
|
210
|
+
isLight
|
|
211
|
+
}), [
|
|
212
|
+
theme,
|
|
213
|
+
mode,
|
|
214
|
+
isDark,
|
|
215
|
+
isLight
|
|
216
|
+
]);
|
|
206
217
|
};
|
|
207
218
|
|
|
208
219
|
//#endregion
|
|
@@ -213,13 +224,16 @@ const useThemeAttrs = ({ inversed }) => {
|
|
|
213
224
|
* to its rocketstyle children.
|
|
214
225
|
*/
|
|
215
226
|
const context$1 = createContext({});
|
|
227
|
+
const EMPTY_CTX = { pseudo: {} };
|
|
216
228
|
const useLocalContext = (consumer) => {
|
|
217
229
|
const ctx = useContext(context$1);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
230
|
+
return useMemo(() => {
|
|
231
|
+
if (!consumer) return EMPTY_CTX;
|
|
232
|
+
return {
|
|
233
|
+
pseudo: {},
|
|
234
|
+
...consumer((callback) => callback(ctx))
|
|
235
|
+
};
|
|
236
|
+
}, [consumer, ctx]);
|
|
223
237
|
};
|
|
224
238
|
const LocalProvider = context$1.Provider;
|
|
225
239
|
|
|
@@ -259,14 +273,11 @@ const RocketStyleProviderComponent = (WrappedComponent) => forwardRef(({ onMouse
|
|
|
259
273
|
|
|
260
274
|
//#endregion
|
|
261
275
|
//#region src/utils/attrs.ts
|
|
262
|
-
const removeUndefinedProps = (props) =>
|
|
263
|
-
const
|
|
264
|
-
if (
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
};
|
|
268
|
-
return acc;
|
|
269
|
-
}, {});
|
|
276
|
+
const removeUndefinedProps = (props) => {
|
|
277
|
+
const result = {};
|
|
278
|
+
for (const key in props) if (props[key] !== void 0) result[key] = props[key];
|
|
279
|
+
return result;
|
|
280
|
+
};
|
|
270
281
|
const pickStyledAttrs = (props, keywords) => Object.keys(props).reduce((acc, key) => {
|
|
271
282
|
if (keywords[key] && props[key]) acc[key] = props[key];
|
|
272
283
|
return acc;
|
|
@@ -280,23 +291,26 @@ const calculateStylingAttrs = ({ useBooleans, multiKeys }) => ({ props, dimensio
|
|
|
280
291
|
const result = {};
|
|
281
292
|
Object.keys(dimensions).forEach((item) => {
|
|
282
293
|
const pickedProp = props[item];
|
|
283
|
-
const
|
|
294
|
+
const t = typeof pickedProp;
|
|
284
295
|
if (multiKeys?.[item] && Array.isArray(pickedProp)) result[item] = pickedProp;
|
|
285
|
-
else if (
|
|
296
|
+
else if (t === "string" || t === "number") result[item] = pickedProp;
|
|
286
297
|
else result[item] = void 0;
|
|
287
298
|
});
|
|
288
299
|
if (useBooleans) {
|
|
289
|
-
const propsKeys = Object.keys(props)
|
|
300
|
+
const propsKeys = Object.keys(props);
|
|
290
301
|
Object.entries(result).forEach(([key, value]) => {
|
|
291
302
|
const isMultiKey = multiKeys[key];
|
|
292
303
|
if (!value) {
|
|
293
304
|
let newDimensionValue;
|
|
294
|
-
const
|
|
295
|
-
if (isMultiKey) newDimensionValue = propsKeys.filter((key) =>
|
|
296
|
-
else
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
305
|
+
const keywordSet = new Set(Object.keys(dimensions[key]));
|
|
306
|
+
if (isMultiKey) newDimensionValue = propsKeys.filter((key) => keywordSet.has(key));
|
|
307
|
+
else for (let i = propsKeys.length - 1; i >= 0; i--) {
|
|
308
|
+
const k = propsKeys[i];
|
|
309
|
+
if (keywordSet.has(k) && props[k]) {
|
|
310
|
+
newDimensionValue = k;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
300
314
|
result[key] = newDimensionValue;
|
|
301
315
|
}
|
|
302
316
|
});
|
|
@@ -396,6 +410,12 @@ const getMultipleDimensions = (obj) => getValues(obj).reduce((accumulator, value
|
|
|
396
410
|
}
|
|
397
411
|
return accumulator;
|
|
398
412
|
}, {});
|
|
413
|
+
const getTransformDimensions = (obj) => getValues(obj).reduce((accumulator, value) => {
|
|
414
|
+
if (typeof value === "object") {
|
|
415
|
+
if (value.transform === true) accumulator[value.propName] = true;
|
|
416
|
+
}
|
|
417
|
+
return accumulator;
|
|
418
|
+
}, {});
|
|
399
419
|
|
|
400
420
|
//#endregion
|
|
401
421
|
//#region src/utils/statics.ts
|
|
@@ -424,12 +444,17 @@ const removeNullableValues = (obj) => Object.entries(obj).filter(([, v]) => v !=
|
|
|
424
444
|
|
|
425
445
|
//#endregion
|
|
426
446
|
//#region src/utils/theme.ts
|
|
447
|
+
const MODE_CALLBACK_BRAND = Symbol.for("vl.themeModeCallback");
|
|
427
448
|
/** Creates a mode-switching function that returns the light or dark value based on the active mode. */
|
|
428
|
-
const themeModeCallback = (light, dark) =>
|
|
429
|
-
|
|
430
|
-
|
|
449
|
+
const themeModeCallback = (light, dark) => {
|
|
450
|
+
const fn = (mode) => {
|
|
451
|
+
if (!mode || mode === "light") return light;
|
|
452
|
+
return dark;
|
|
453
|
+
};
|
|
454
|
+
fn.__brand = MODE_CALLBACK_BRAND;
|
|
455
|
+
return fn;
|
|
431
456
|
};
|
|
432
|
-
const isModeCallback = (value) => typeof value === "function" && value.
|
|
457
|
+
const isModeCallback = (value) => typeof value === "function" && value.__brand === MODE_CALLBACK_BRAND;
|
|
433
458
|
const getThemeFromChain = (options, theme) => {
|
|
434
459
|
const result = {};
|
|
435
460
|
if (!options || isEmpty(options)) return result;
|
|
@@ -445,15 +470,21 @@ const getDimensionThemes = (theme, options) => {
|
|
|
445
470
|
return acc;
|
|
446
471
|
}, result);
|
|
447
472
|
};
|
|
448
|
-
const getTheme = ({ rocketstate, themes, baseTheme }) => {
|
|
473
|
+
const getTheme = ({ rocketstate, themes, baseTheme, transformKeys, appTheme }) => {
|
|
449
474
|
let finalTheme = { ...baseTheme };
|
|
475
|
+
const deferredTransforms = [];
|
|
450
476
|
Object.entries(rocketstate).forEach(([key, value]) => {
|
|
451
477
|
const keyTheme = themes[key];
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
478
|
+
const isTransform = transformKeys?.[key];
|
|
479
|
+
const mergeValue = (item) => {
|
|
480
|
+
const val = keyTheme[item];
|
|
481
|
+
if (isTransform && typeof val === "function") deferredTransforms.push(val);
|
|
482
|
+
else finalTheme = merge({}, finalTheme, val);
|
|
483
|
+
};
|
|
484
|
+
if (Array.isArray(value)) value.forEach(mergeValue);
|
|
485
|
+
else mergeValue(value);
|
|
456
486
|
});
|
|
487
|
+
for (const transform of deferredTransforms) finalTheme = merge({}, finalTheme, transform(finalTheme, appTheme ?? {}, themeModeCallback, config.css));
|
|
457
488
|
return finalTheme;
|
|
458
489
|
};
|
|
459
490
|
const getThemeByMode = (object, mode) => Object.keys(object).reduce((acc, key) => {
|
|
@@ -550,11 +581,24 @@ const rocketComponent = (options) => {
|
|
|
550
581
|
...rocketstate,
|
|
551
582
|
pseudo: pseudoRocketstate
|
|
552
583
|
};
|
|
553
|
-
|
|
584
|
+
let rsKey = "";
|
|
585
|
+
for (const k in rocketstate) {
|
|
586
|
+
const v = rocketstate[k];
|
|
587
|
+
rsKey += `${k}=`;
|
|
588
|
+
rsKey += `${Array.isArray(v) ? v.join(",") : v}|`;
|
|
589
|
+
}
|
|
590
|
+
const rocketstyle = useMemo(() => getTheme({
|
|
554
591
|
rocketstate,
|
|
555
592
|
themes: currentModeThemes,
|
|
556
|
-
baseTheme: currentModeBaseTheme
|
|
557
|
-
|
|
593
|
+
baseTheme: currentModeBaseTheme,
|
|
594
|
+
transformKeys: options.transformKeys,
|
|
595
|
+
appTheme: theme
|
|
596
|
+
}), [
|
|
597
|
+
rsKey,
|
|
598
|
+
currentModeThemes,
|
|
599
|
+
currentModeBaseTheme,
|
|
600
|
+
theme
|
|
601
|
+
]);
|
|
558
602
|
const finalProps = {
|
|
559
603
|
...omit(mergeProps, [
|
|
560
604
|
...RESERVED_STYLING_PROPS_KEYS,
|
|
@@ -665,6 +709,7 @@ const rocketstyle = ({ dimensions = DEFAULT_DIMENSIONS, useBooleans = true } = {
|
|
|
665
709
|
dimensionKeys: getKeys(dimensions),
|
|
666
710
|
dimensionValues: getDimensionsValues(dimensions),
|
|
667
711
|
multiKeys: getMultipleDimensions(dimensions),
|
|
712
|
+
transformKeys: getTransformDimensions(dimensions),
|
|
668
713
|
styled: true
|
|
669
714
|
});
|
|
670
715
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitus-labs/rocketstyle",
|
|
3
|
-
"version": "2.0.0-
|
|
3
|
+
"version": "2.0.0-beta.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Vit Bokisch <vit@bokisch.cz>",
|
|
6
6
|
"maintainers": [
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"types": "./lib/index.d.ts"
|
|
15
15
|
},
|
|
16
16
|
"types": "./lib/index.d.ts",
|
|
17
|
+
"main": "./lib/index.js",
|
|
17
18
|
"files": [
|
|
18
19
|
"lib",
|
|
19
20
|
"!lib/**/*.map",
|
|
@@ -60,19 +61,20 @@
|
|
|
60
61
|
"test:coverage": "vitest run --coverage",
|
|
61
62
|
"test:watch": "vitest",
|
|
62
63
|
"cover": "coveralls < .coverage/lcov.info",
|
|
63
|
-
"typecheck": "tsc --noEmit"
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"version": "node ../../scripts/sync-peer-deps.mjs"
|
|
64
66
|
},
|
|
65
67
|
"peerDependencies": {
|
|
66
|
-
"@vitus-labs/core": "
|
|
68
|
+
"@vitus-labs/core": "2.0.0-alpha.27",
|
|
67
69
|
"react": ">= 19"
|
|
68
70
|
},
|
|
69
71
|
"devDependencies": {
|
|
70
|
-
"@vitus-labs/core": "2.0.0-
|
|
71
|
-
"@vitus-labs/elements": "2.0.0-
|
|
72
|
-
"@vitus-labs/tools-rolldown": "
|
|
73
|
-
"@vitus-labs/tools-storybook": "
|
|
74
|
-
"@vitus-labs/tools-typescript": "
|
|
75
|
-
"@vitus-labs/unistyle": "2.0.0-
|
|
72
|
+
"@vitus-labs/core": "2.0.0-beta.0",
|
|
73
|
+
"@vitus-labs/elements": "2.0.0-beta.0",
|
|
74
|
+
"@vitus-labs/tools-rolldown": "1.10.0",
|
|
75
|
+
"@vitus-labs/tools-storybook": "1.10.0",
|
|
76
|
+
"@vitus-labs/tools-typescript": "1.10.0",
|
|
77
|
+
"@vitus-labs/unistyle": "2.0.0-beta.0"
|
|
76
78
|
},
|
|
77
|
-
"gitHead": "
|
|
79
|
+
"gitHead": "dd8b9f356086ecd8bfb69c87fcad1e8bfa9ab1f4"
|
|
78
80
|
}
|