Skip to content

Commit 831a122

Browse files
committed
refactor(theme): ♻️ update theme & utils code
1 parent 4b73832 commit 831a122

22 files changed

Lines changed: 573 additions & 432 deletions

package.json

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,30 +39,30 @@
3939
"dist"
4040
],
4141
"scripts": {
42+
"postinstall": "concurrently \"husky install\" \"patch-package\"",
4243
"boot": "concurrently \"yarn keys\" \"yarn previews\"",
44+
"keys": "node scripts/generateKeys",
45+
"previews": "node scripts/create-previews.js",
46+
"storybook": "start-storybook -p 6006",
47+
"test": "jest --config ./jest.config.ts --no-cache",
48+
"lint": "eslint --color --ext .js,.jsx,.ts,.tsx .",
49+
"lint:fix": "eslint --color --ext .js,.jsx,.ts,.tsx . --fix",
50+
"format": "prettier -wl \"./**/*.{html,css,js,jsx,ts,tsx,md,json}\"",
51+
"format:package": "sort-package-json package.json",
4352
"prebuild": "rimraf dist",
4453
"build": "yarn keys && concurrently yarn:build:*",
4554
"build:cjs": "cross-env BABEL_ENV=cjs babel src --extensions .ts,.tsx -d dist/cjs --source-maps",
4655
"build:esm": "cross-env BABEL_ENV=esm babel src --extensions .ts,.tsx -d dist/esm --source-maps",
4756
"build:types": "tsc --emitDeclarationOnly --project tsconfig.prod.json",
4857
"check-types": "yarn build:types && tsd",
49-
"commit": "gacp",
50-
"contributors:add": "all-contributors add",
51-
"contributors:generate": "all-contributors generate",
52-
"format": "prettier -wl \"./**/*.{html,css,js,jsx,ts,tsx,md,json}\"",
53-
"format:package": "sort-package-json package.json",
54-
"postinstall": "concurrently \"husky install\" \"patch-package\"",
55-
"keys": "node scripts/generateKeys",
56-
"lint": "eslint --color --ext .js,.jsx,.ts,.tsx .",
57-
"lint:fix": "eslint --color --ext .js,.jsx,.ts,.tsx . --fix",
58+
"storybook-build": "yarn keys && yarn previews && build-storybook",
5859
"prepublishOnly": "pinst --disable",
59-
"previews": "node scripts/create-previews.js",
60-
"postpublish": "pinst --enable",
6160
"release": "release-it",
61+
"postpublish": "pinst --enable",
6262
"size": "yarn build && size-limit",
63-
"storybook": "start-storybook -p 6006",
64-
"storybook-build": "yarn keys && yarn previews && build-storybook",
65-
"test": "jest --config ./jest.config.ts --no-cache"
63+
"commit": "gacp",
64+
"contributors:add": "all-contributors add",
65+
"contributors:generate": "all-contributors generate"
6666
},
6767
"commitlint": {
6868
"extends": [
@@ -75,9 +75,6 @@
7575
],
7676
"**/*.{html,css,js,jsx,ts,tsx,md,json}": [
7777
"yarn format"
78-
],
79-
"package.json": [
80-
"yarn format:package"
8178
]
8279
},
8380
"browserslist": {

renderlesskit.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { extendTheme } from "./src/theme/extendTheme";
1+
import { extendTheme } from "./src";
22

33
export const theme = extendTheme({
44
// This only affected the Storybook, doesn't go or merge when used this config as preset

src/global-types.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { DefaultTheme } from "./theme";
2+
3+
// https://stackoverflow.com/questions/60795256/typescript-type-merging
4+
// https://dev.to/svehla/typescript-how-to-deep-merge-170c
5+
6+
/**
7+
* Take two objects T and U and create the new one with uniq keys for T a U objectI
8+
* helper generic for `DeepMergeTwoTypes`
9+
*/
10+
type GetObjDifferentKeys<T, U> = Omit<T, keyof U> & Omit<U, keyof T>;
11+
12+
/**
13+
* Take two objects T and U and create the new one with the same objects keys
14+
* helper generic for `DeepMergeTwoTypes`
15+
*/
16+
type GetObjSameKeys<T, U> = Omit<T | U, keyof GetObjDifferentKeys<T, U>>;
17+
18+
type Merge<T, U> =
19+
// non shared keys are optional
20+
Partial<GetObjDifferentKeys<T, U>> & {
21+
// shared keys are recursively resolved by `DeepMergeTwoTypes<...>`
22+
[K in keyof GetObjSameKeys<T, U>]: DeepMerge<T[K], U[K]>;
23+
};
24+
25+
// it merge 2 static types and try to avoid of unnecessary options (`'`)
26+
/**
27+
* @template T source object
28+
* @template U target object
29+
*
30+
* @description Deep merge two theme objects
31+
*/
32+
export type DeepMerge<T, U> =
33+
// check if generic types are arrays and unwrap it and do the recursion
34+
[T, U] extends [(infer TItem)[], (infer UItem)[]]
35+
? DeepMerge<TItem, UItem>[]
36+
: // check if generic types are objects
37+
[T, U] extends [{ [key: string]: unknown }, { [key: string]: unknown }]
38+
? Merge<T, U>
39+
: [T, U] extends [
40+
{ [key: string]: unknown } | undefined,
41+
{ [key: string]: unknown } | undefined,
42+
]
43+
? Merge<NonNullable<T>, NonNullable<U>> | undefined
44+
: T | U;
45+
46+
interface _ComponentDefaultTheme {
47+
components: DefaultTheme;
48+
}
49+
50+
declare const _brand: unique symbol;
51+
52+
declare global {
53+
namespace Renderlesskit {
54+
export interface Theme extends _ComponentDefaultTheme {}
55+
/**
56+
* @template T default theme
57+
* @template U user theme
58+
*
59+
* @description Safely Deep merges default theme with user theme
60+
*/
61+
export type MergeTheme<T, U> = DeepMerge<
62+
T,
63+
U extends { [x: string]: any } ? U : {}
64+
>;
65+
66+
type Brand<Type, Name = "ThemeKey"> = Type & { [_brand]: Name };
67+
68+
type Comps = Renderlesskit.Theme["components"];
69+
70+
/**
71+
* @template C component name
72+
* @template K theme key
73+
* @template L theme key
74+
* @template M theme key
75+
* @template N theme key
76+
*/
77+
export type GetThemeValue<
78+
C extends keyof Comps,
79+
K extends keyof Comps[C] = Brand<keyof Comps[C]>,
80+
L extends keyof Comps[C][K] = Brand<keyof Comps[C][K]>,
81+
M extends keyof Comps[C][K][L] = Brand<keyof Comps[C][K][L]>,
82+
N extends keyof Comps[C][K][L][M] = Brand<keyof Comps[C][K][L][M]>,
83+
> = [C, K, L, M, N] extends [string, Brand<K>, Brand<L>, Brand<M>, Brand<N>]
84+
? Comps[C]
85+
: [C, K, L, M, N] extends [string, string, Brand<L>, Brand<M>, Brand<N>]
86+
? Comps[C][K]
87+
: [C, K, L, M, N] extends [string, string, string, Brand<M>, Brand<N>]
88+
? Comps[C][K][L]
89+
: [C, K, L, M, N] extends [string, string, string, string, Brand<N>]
90+
? Comps[C][K][L][M]
91+
: [C, K, L, M, N] extends [string, string, string, string, string]
92+
? Comps[C][K][L][M][N]
93+
: never;
94+
}
95+
}

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from "./circular-progress";
99
export * from "./create-icon";
1010
export * from "./datepicker";
1111
export * from "./form-field";
12+
export * from "./global-types";
1213
export * from "./hooks";
1314
export * from "./icons";
1415
export * from "./input";
@@ -21,7 +22,6 @@ export * from "./switch";
2122
export * from "./tag";
2223
export * from "./textarea";
2324
export * from "./theme";
24-
export * from "./theme/types";
2525
export * from "./toast";
2626
export * from "./tooltip";
2727
export * from "./utils";
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mergeExtensions, mergeThemes } from "./mergeThemes";
1+
import { mergeExtensions, mergeThemes } from "../utils";
22

33
describe("mergeThemes", () => {
44
test("merges two empty themes together", () => {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expectAssignable, expectType } from "tsd";
22

3-
import theme from "./defaultTheme";
3+
import theme from "../defaultTheme";
44

55
// TODO: Test global renderlesskit namespace
66

src/theme/extendTheme.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/theme/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from "./provider";
2+
export * from "./types";
3+
export * from "./utils";
Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import * as React from "react";
22

3-
import { DeepDictionary, DeepPartial } from "../utils";
4-
53
import defaultTheme from "./defaultTheme";
6-
import { mergeExtensions, mergeThemes } from "./mergeThemes";
7-
8-
export type DefaultTheme = typeof defaultTheme;
9-
10-
export type ThemeContextType = DeepDictionary<DefaultTheme>;
11-
12-
const ThemeContext = React.createContext<ThemeContextType | undefined>(
4+
import {
5+
DefaultTheme,
6+
ExtendableDefaultTheme,
7+
ThemeContextType,
8+
ThemeKeys,
9+
} from "./types";
10+
import { mergeExtensions, mergeThemes } from "./utils";
11+
12+
export const ThemeContext = React.createContext<ThemeContextType | undefined>(
1313
undefined,
1414
);
1515

16-
type ThemeKeys = keyof DefaultTheme;
1716
export function useTheme(): DefaultTheme;
1817
export function useTheme<T extends ThemeKeys>(component?: T): DefaultTheme[T];
1918
export function useTheme<T extends ThemeKeys>(component?: T) {
@@ -31,11 +30,6 @@ export function useTheme<T extends ThemeKeys>(component?: T) {
3130
return context;
3231
}
3332

34-
export type PartialDefaultTheme = DeepPartial<DefaultTheme>;
35-
export type ExtendableDefaultTheme = PartialDefaultTheme & {
36-
extend?: PartialDefaultTheme;
37-
};
38-
3933
export type RenderlesskitProviderProps = {
4034
theme?: DefaultTheme;
4135
extend?: ExtendableDefaultTheme;
@@ -57,5 +51,3 @@ export const RenderlesskitProvider: React.FC<RenderlesskitProviderProps> =
5751
</ThemeContext.Provider>
5852
);
5953
};
60-
61-
export * from "./extendTheme";

src/theme/types.ts

Lines changed: 9 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,15 @@
1-
import { DefaultTheme } from "../index";
1+
import { DeepDictionary, DeepPartial } from "../utils";
22

3-
// https://stackoverflow.com/questions/60795256/typescript-type-merging
4-
// https://dev.to/svehla/typescript-how-to-deep-merge-170c
3+
import defaultTheme from "./defaultTheme";
54

6-
/**
7-
* Take two objects T and U and create the new one with uniq keys for T a U objectI
8-
* helper generic for `DeepMergeTwoTypes`
9-
*/
10-
type GetObjDifferentKeys<T, U> = Omit<T, keyof U> & Omit<U, keyof T>;
5+
export type DefaultTheme = typeof defaultTheme;
116

12-
/**
13-
* Take two objects T and U and create the new one with the same objects keys
14-
* helper generic for `DeepMergeTwoTypes`
15-
*/
16-
type GetObjSameKeys<T, U> = Omit<T | U, keyof GetObjDifferentKeys<T, U>>;
7+
export type ThemeKeys = keyof DefaultTheme;
178

18-
type Merge<T, U> =
19-
// non shared keys are optional
20-
Partial<GetObjDifferentKeys<T, U>> & {
21-
// shared keys are recursively resolved by `DeepMergeTwoTypes<...>`
22-
[K in keyof GetObjSameKeys<T, U>]: DeepMerge<T[K], U[K]>;
23-
};
9+
export type ThemeContextType = DeepDictionary<DefaultTheme>;
2410

25-
// it merge 2 static types and try to avoid of unnecessary options (`'`)
26-
/**
27-
* @template T source object
28-
* @template U target object
29-
*
30-
* @description Deep merge two theme objects
31-
*/
32-
export type DeepMerge<T, U> =
33-
// check if generic types are arrays and unwrap it and do the recursion
34-
[T, U] extends [(infer TItem)[], (infer UItem)[]]
35-
? DeepMerge<TItem, UItem>[]
36-
: // check if generic types are objects
37-
[T, U] extends [{ [key: string]: unknown }, { [key: string]: unknown }]
38-
? Merge<T, U>
39-
: [T, U] extends [
40-
{ [key: string]: unknown } | undefined,
41-
{ [key: string]: unknown } | undefined,
42-
]
43-
? Merge<NonNullable<T>, NonNullable<U>> | undefined
44-
: T | U;
11+
export type PartialDefaultTheme = DeepPartial<DefaultTheme>;
4512

46-
interface _ComponentDefaultTheme {
47-
components: DefaultTheme;
48-
}
49-
50-
declare const _brand: unique symbol;
51-
52-
declare global {
53-
namespace Renderlesskit {
54-
export interface Theme extends _ComponentDefaultTheme {}
55-
/**
56-
* @template T default theme
57-
* @template U user theme
58-
*
59-
* @description Safely Deep merges default theme with user theme
60-
*/
61-
export type MergeTheme<T, U> = DeepMerge<
62-
T,
63-
U extends { [x: string]: any } ? U : {}
64-
>;
65-
66-
type Brand<Type, Name = "ThemeKey"> = Type & { [_brand]: Name };
67-
68-
type Comps = Renderlesskit.Theme["components"];
69-
70-
/**
71-
* @template C component name
72-
* @template K theme key
73-
* @template L theme key
74-
* @template M theme key
75-
* @template N theme key
76-
*/
77-
export type GetThemeValue<
78-
C extends keyof Comps,
79-
K extends keyof Comps[C] = Brand<keyof Comps[C]>,
80-
L extends keyof Comps[C][K] = Brand<keyof Comps[C][K]>,
81-
M extends keyof Comps[C][K][L] = Brand<keyof Comps[C][K][L]>,
82-
N extends keyof Comps[C][K][L][M] = Brand<keyof Comps[C][K][L][M]>,
83-
> = [C, K, L, M, N] extends [string, Brand<K>, Brand<L>, Brand<M>, Brand<N>]
84-
? Comps[C]
85-
: [C, K, L, M, N] extends [string, string, Brand<L>, Brand<M>, Brand<N>]
86-
? Comps[C][K]
87-
: [C, K, L, M, N] extends [string, string, string, Brand<M>, Brand<N>]
88-
? Comps[C][K][L]
89-
: [C, K, L, M, N] extends [string, string, string, string, Brand<N>]
90-
? Comps[C][K][L][M]
91-
: [C, K, L, M, N] extends [string, string, string, string, string]
92-
? Comps[C][K][L][M][N]
93-
: never;
94-
}
95-
}
13+
export type ExtendableDefaultTheme = PartialDefaultTheme & {
14+
extend?: PartialDefaultTheme;
15+
};

0 commit comments

Comments
 (0)