From 591d59fe89f1d9bf016c121bf9160f73fe0a290d Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Fri, 2 May 2025 17:06:20 +0200 Subject: [PATCH] fix(theme): improve app config types for `ui` object Resolves #3579 --- src/runtime/types/tv.ts | 63 +++++++++++++++++++++++++++++++ src/runtime/types/utils.ts | 77 +++++--------------------------------- src/templates.ts | 4 +- src/unplugin.ts | 6 +-- 4 files changed, 77 insertions(+), 73 deletions(-) create mode 100644 src/runtime/types/tv.ts diff --git a/src/runtime/types/tv.ts b/src/runtime/types/tv.ts new file mode 100644 index 00000000..0300fa93 --- /dev/null +++ b/src/runtime/types/tv.ts @@ -0,0 +1,63 @@ +import type { ClassValue, TVVariants, TVCompoundVariants, TVDefaultVariants } from 'tailwind-variants' + +/** + * Defines the AppConfig object based on the tailwind-variants configuration. + */ +export type TVConfig> = { + [P in keyof T]?: { + [K in keyof T[P]as K extends 'base' | 'slots' | 'variants' | 'compoundVariants' | 'defaultVariants' ? K : never]?: K extends 'base' ? ClassValue + : K extends 'slots' ? { + [S in keyof T[P]['slots']]?: ClassValue + } + : K extends 'variants' ? TVVariants + : K extends 'compoundVariants' ? TVCompoundVariants + : K extends 'defaultVariants' ? TVDefaultVariants + : never + } +} + +/** + * Utility type to flatten intersection types for better IDE hover information. + * @template T The type to flatten. + */ +type Id = {} & { [P in keyof T]: T[P] } + +type ComponentVariants> }> = { + [K in keyof T['variants']]: keyof T['variants'][K] +} + +type ComponentSlots }> = Id<{ + [K in keyof T['slots']]?: ClassValue +}> + +type GetComponentAppConfig = + A extends Record> ? A[U][K] : {} + +type ComponentAppConfig< + T, + A extends Record, + K extends string, + U extends string = 'ui' | 'uiPro' | 'uiPro.prose' +> = A & ( + U extends 'uiPro.prose' + ? { uiPro?: { prose?: { [k in K]?: Partial } } } + : { [key in Exclude]?: { [k in K]?: Partial } } +) + +/** + * Defines the configuration shape expected for a component. + * @template T The component's theme imported from `#build/ui/*`. + * @template A The base AppConfig type from `@nuxt/schema`. + * @template K The key identifying the component (e.g., 'badge'). + * @template U The top-level key in AppConfig ('ui' or 'uiPro'). + */ +export type ComponentConfig< + T extends Record, + A extends Record, + K extends string, + U extends 'ui' | 'uiPro' | 'uiPro.prose' = 'ui' +> = { + AppConfig: ComponentAppConfig + variants: ComponentVariants> + slots: ComponentSlots +} diff --git a/src/runtime/types/utils.ts b/src/runtime/types/utils.ts index 23d2ab44..6d2dc68d 100644 --- a/src/runtime/types/utils.ts +++ b/src/runtime/types/utils.ts @@ -1,20 +1,5 @@ -import type { AcceptableValue as _AcceptableValue } from 'reka-ui' -import type { ClassValue } from 'tailwind-variants' import type { VNode } from 'vue' - -export interface TightMap { - [key: string]: TightMap | O -} - -export type DeepPartial = { - [P in keyof T]?: T[P] extends Array - ? string - : T[P] extends object - ? DeepPartial - : T[P]; -} & { - [key: string]: O | TightMap -} +import type { AcceptableValue as _AcceptableValue } from 'reka-ui' export type DynamicSlotsKeys = ( Name extends string @@ -56,13 +41,13 @@ export type MergeTypes = { export type GetItemKeys = keyof Extract, object> export type GetItemValue | undefined, T extends NestedItem = NestedItem> = -T extends object - ? VK extends undefined - ? T - : VK extends keyof T - ? T[VK] - : never - : T + T extends object + ? VK extends undefined + ? T + : VK extends keyof T + ? T[VK] + : never + : T export type GetModelValue< T, @@ -92,48 +77,4 @@ export type EmitsToProps = { : never } -/** - * Utility type to flatten intersection types for better IDE hover information. - * @template T The type to flatten. - */ -type Id = {} & { [P in keyof T]: T[P] } - -type ComponentVariants> }> = { - [K in keyof T['variants']]: keyof T['variants'][K] -} - -type ComponentSlots }> = Id<{ - [K in keyof T['slots']]?: ClassValue -}> - -type GetComponentAppConfig = - A extends Record> ? A[U][K] : {} - -type ComponentAppConfig< - T, - A extends Record, - K extends string, - U extends string = 'ui' | 'uiPro' | 'uiPro.prose' -> = A & ( - U extends 'uiPro.prose' - ? { uiPro?: { prose?: { [k in K]?: Partial } } } - : { [key in Exclude]?: { [k in K]?: Partial } } -) - -/** - * Defines the configuration shape expected for a component. - * @template T The component's theme imported from `#build/ui/*`. - * @template A The base AppConfig type from `@nuxt/schema`. - * @template K The key identifying the component (e.g., 'badge'). - * @template U The top-level key in AppConfig ('ui' or 'uiPro'). - */ -export type ComponentConfig< - T extends Record, - A extends Record, - K extends string, - U extends 'ui' | 'uiPro' | 'uiPro.prose' = 'ui' -> = { - AppConfig: ComponentAppConfig - variants: ComponentVariants> - slots: ComponentSlots -} +export * from './tv' diff --git a/src/templates.ts b/src/templates.ts index a130f083..9ab720c0 100644 --- a/src/templates.ts +++ b/src/templates.ts @@ -149,7 +149,7 @@ export function getTemplates(options: ModuleOptions, uiConfig: Record `import * as ui from '#build/ui' -import type { DeepPartial } from '@nuxt/ui' +import type { TVConfig } from '@nuxt/ui' import type { defaultConfig } from 'tailwind-variants' import colors from 'tailwindcss/colors' @@ -165,7 +165,7 @@ type AppConfigUI = { } icons?: Partial tv?: typeof defaultConfig -} & DeepPartial +} & TVConfig declare module '@nuxt/schema' { interface AppConfigInput { diff --git a/src/unplugin.ts b/src/unplugin.ts index 783d84b4..1e26ef5a 100644 --- a/src/unplugin.ts +++ b/src/unplugin.ts @@ -20,10 +20,10 @@ import PluginsPlugin from './plugins/plugins' import AppConfigPlugin from './plugins/app-config' import ComponentImportPlugin from './plugins/components' import NuxtEnvironmentPlugin from './plugins/nuxt-environment' - -import type { DeepPartial } from './runtime/types/utils' import AutoImportPlugin from './plugins/auto-import' +import type { TVConfig } from './runtime/types/tv' + type NeutralColor = 'slate' | 'gray' | 'zinc' | 'neutral' | 'stone' type Color = Exclude | (string & {}) @@ -31,7 +31,7 @@ type AppConfigUI = { // TODO: add type hinting for colors from `options.theme.colors` colors?: Record & { neutral?: NeutralColor } icons?: Partial -} & DeepPartial +} & TVConfig export interface NuxtUIOptions extends Omit { /** Whether to generate declaration files for auto-imported components. */