diff --git a/playgrounds/nuxt/app/pages/components/button.vue b/playgrounds/nuxt/app/pages/components/button.vue index a8d25610eb..ab64fdcd68 100644 --- a/playgrounds/nuxt/app/pages/components/button.vue +++ b/playgrounds/nuxt/app/pages/components/button.vue @@ -24,8 +24,12 @@ function onClick() { - - + + + + + + diff --git a/src/runtime/components/Button.vue b/src/runtime/components/Button.vue index b1fd3f2006..822b627932 100644 --- a/src/runtime/components/Button.vue +++ b/src/runtime/components/Button.vue @@ -48,6 +48,7 @@ import { defu } from 'defu' import { useForwardProps } from 'reka-ui' import { useAppConfig } from '#imports' import { useComponentUI } from '../composables/useComponentUI' +import { useComponentVariant } from '../composables/useComponentVariant' import { useComponentIcons } from '../composables/useComponentIcons' import { useFieldGroup } from '../composables/useFieldGroup' import { formLoadingInjectionKey } from '../composables/useFormField' @@ -64,6 +65,8 @@ const slots = defineSlots() const appConfig = useAppConfig() as Button['AppConfig'] const uiProp = useComponentUI('button', props) +const variantProp = useComponentVariant('button', props) + const { orientation, size: buttonSize } = useFieldGroup(props) const linkProps = useForwardProps(pickLinkProps(props)) @@ -104,12 +107,12 @@ const ui = computed(() => tv({ } }, appConfig.ui?.button || {}) })({ - color: props.color, - variant: props.variant, + color: variantProp.value.color, + variant: variantProp.value.variant, size: buttonSize.value, loading: isLoading.value, - block: props.block, - square: props.square || (!slots.default && !props.label), + block: variantProp.value.block, + square: variantProp.value.square || (!slots.default && !props.label), leading: isLeading.value, trailing: isTrailing.value, fieldGroup: orientation.value diff --git a/src/runtime/components/Theme.vue b/src/runtime/components/Theme.vue index ba6fb90ebf..6cf9a03039 100644 --- a/src/runtime/components/Theme.vue +++ b/src/runtime/components/Theme.vue @@ -2,9 +2,11 @@ import { computed } from 'vue' import { provideThemeContext } from '../composables/useComponentUI' import type { ThemeUI } from '../composables/useComponentUI' +import { provideVariantContext, type VariantUI } from '../composables/useComponentVariant' export interface ThemeProps { - ui: ThemeUI + ui?: ThemeUI + variants?: VariantUI } export interface ThemeSlots { @@ -16,7 +18,11 @@ export interface ThemeSlots { const props = defineProps() provideThemeContext({ - ui: computed(() => props.ui) + ui: computed(() => props.ui ?? {}), +}) + +provideVariantContext({ + variant: computed(() => props.variants ?? {}) }) diff --git a/src/runtime/composables/useComponentUI.ts b/src/runtime/composables/useComponentUI.ts index 43140438c1..715b60eea0 100644 --- a/src/runtime/composables/useComponentUI.ts +++ b/src/runtime/composables/useComponentUI.ts @@ -22,11 +22,11 @@ export type ThemeUI = { [K in keyof typeof ui]?: ThemeSlotOverrides<(typeof ui)[K]> } -export type ThemeRootContext = { +export type ThemeUIContext = { ui: ComputedRef } -const [injectThemeContext, provideThemeContext] = createContext('UTheme', 'RootContext') +const [injectThemeContext, provideThemeContext] = createContext('UThemeUI', 'RootContext') export { provideThemeContext } diff --git a/src/runtime/composables/useComponentVariant.ts b/src/runtime/composables/useComponentVariant.ts new file mode 100644 index 0000000000..a1c227c037 --- /dev/null +++ b/src/runtime/composables/useComponentVariant.ts @@ -0,0 +1,40 @@ +import type { ComputedRef } from 'vue' +import { computed } from 'vue' +import defu from 'defu' +import { createContext } from 'reka-ui' +import type { ComponentConfig, TVConfig } from '../types/tv' +import type * as ui from '#build/ui' +import { get } from '../utils' +import type { AppConfig } from '@nuxt/schema' + +type UIConfig = TVConfig +type ComponentVariants = ComponentConfig<(typeof ui)[C], AppConfig, C>['variants'] + +type VariantValue = [V] extends ['true' | 'false'] ? boolean : V +type ThemeVariantOverrides = { + [K in keyof T]?: VariantValue +}; + +export type ThemeVariantContext = { + variant: ComputedRef +} + +export type VariantUI = { + [K in keyof UIConfig]?: ThemeVariantOverrides> +} + +const [injectVariantContext, provideVariantContext] = createContext('UThemeVariant', 'RootContext') + +export { provideVariantContext } + +type ComponentVariantProps = ThemeVariantOverrides> + +export function useComponentVariant(name: C, props: ComponentVariantProps): ComputedRef>> { + const { variant } = injectVariantContext({ variant: computed(() => ({})) }) + + return computed(() => { + const themeOverrides = (get(variant.value, name as string) || {}) + + return defu(props.variants ?? {}, themeOverrides) + }) +}