Skip to content

Commit

Permalink
WIP: theme() utility function code completion
Browse files Browse the repository at this point in the history
This will give you code completion in the `theme()` function. The reason
it is still a WIP is that this only works with the default config right
now and not 100% sure if it is possible to define generics in JSDoc.

The idea would be to:
- Provide types from the default config
- Provide types from the custom config (e.g.: 3rd party plugin)
- Override default config types with the overrides of the user's config

Right now this only provides types for the defaultConfig which might
result in dropping all of this in favor of a much simpler:

```ts
theme<D = any>(path: string, defaultValue: D) => D
```

But this sadly doesn't give you "nice" auto completion. However, the
default might be good enough if we don't error on for example
`theme('customPlugin')` which is currently not the case.
  • Loading branch information
RobinMalfait committed Mar 23, 2022
1 parent be95f7a commit 8d611e0
Showing 1 changed file with 45 additions and 1 deletion.
46 changes: 45 additions & 1 deletion types/config.d.ts
Expand Up @@ -13,9 +13,53 @@ interface RecursiveKeyValuePair<K extends keyof any = string, V = string> {
}
type ResolvableTo<T> = T | ((utils: PluginUtils) => T)

type DotNotation<T, K extends keyof T = keyof T> = K extends string
? T[K] extends Record<string, any>
? T[K] extends any[]
? K | `${K}.${DotNotation<T[K], Exclude<keyof T[K], keyof any[]>>}`
: K | `${K}.${DotNotation<T[K], keyof T[K]>}`
: K
: never
type Path<T> = DotNotation<T> | keyof T
type PathValue<T, P extends Path<T>> = P extends `${infer K}.${infer Rest}`
? K extends keyof T
? Rest extends Path<T[K]>
? PathValue<T[K], Rest>
: never
: never
: P extends keyof T
? T[P]
: never

// Removes arbitrary values like: { [key: string]: any }
type WithoutStringKey<T> = { [K in keyof T as string extends K ? never : K]: T[K] }

type Unpack<T> = T extends ResolvableTo<infer R>
? { [K in keyof R]: Unpack<R[K]> }
: T extends object
? { [K in keyof T]: Unpack<T[K]> }
: T

// This will remove all the callbacks and simplify it to the actual object
// it uses or the object it returns. It will also remove the `extend`
// section because at runtime that's gone anyway.
type UnpackedTheme = Omit<WithoutStringKey<Unpack<Config['theme']>>, 'extend'>

// This will add additional information purely for code completion. E.g.:
type AugmentedTheme = Expand<Omit<UnpackedTheme, 'colors'> & { colors: DefaultColors }>

interface PluginUtils {
colors: DefaultColors
theme(path: string, defaultValue: unknown): keyof ThemeConfig

// Dynamic based on (default) theme config
theme<P extends Path<AugmentedTheme>, TDefaultValue>(
path: P,
defaultValue?: TDefaultValue
): PathValue<AugmentedTheme, P>
// Path is just a string, useful for third party plugins where we don't
// know the resulting type without generics.
theme<TDefaultValue = any>(path: string, defaultValue?: TDefaultValue): TDefaultValue

breakpoints<I = Record<string, unknown>, O = I>(arg: I): O
rgb(arg: string): (arg: Partial<{ opacityVariable: string; opacityValue: number }>) => string
hsl(arg: string): (arg: Partial<{ opacityVariable: string; opacityValue: number }>) => string
Expand Down

0 comments on commit 8d611e0

Please sign in to comment.