Provide A Custom TS-safe Custom Variant

by ADMIN 40 views

Environment

  • Operating System: Darwin
  • Node Version: v22.12.0
  • Nuxt Version: 3.17.1
  • CLI Version: 3.25.0
  • Nitro Version: 2.11.11
  • Package Manager: pnpm@10.6.2
  • Builder: -
  • User Config: modules, devtools, app, css, site, content, runtimeConfig, build, future, experimental, compatibilityDate, typescript, cookieControl, eslint, i18n, icon, image, ogImage, sitemap, turnstile
  • Runtime Modules: @vueuse/nuxt@13.1.0, @nuxt/eslint@1.3.0, @nuxt/ui-pro@3.1.0, @nuxtjs/sitemap@7.2.10, @nuxtjs/robots@5.2.10, @nuxt/content@3.5.1, nuxt-og-image@5.1.3, @nuxt/image@1.10.0, @nuxtjs/turnstile@1.0.0, @dargmuesli/nuxt-cookie-control@9.0.2, @nuxtjs/robots@5.2.10, nuxt-auth-utils@0.5.20, @nuxtjs/i18n@9.5.3
  • Build Modules: -

Version

v3.1.0

Reproduction

Not needed for this situation

Description

You're trying to create a custom variant for a button using TypeScript, but you're encountering a TypeScript error when trying to apply it at the component level. You've tried adding the variant using the variants object and also using compoundVariants, but you're still getting the error.

Code Snippet

button: {
  variants: {
    variant: {
      gradient: 'shadow-sm shadow-emerald-300/5 border border-emerald-300/10 hover:shadow hover:border-emerald-300 hover:shadow-emerald-300 transition-all duration-200 text-white bg-gradient-to-br from-logo-a via-logo-a-dark to-logo-a focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-{color}-500 dark:focus-visible:outline-{color}-400',
    },
  },
  defaultVariants: {
    color: 'neutral',
    variant: 'subtle',
  },
  default: {
    color: 'white',
    loadingIcon: 'i-mdi-loading',
  },
},

Error Message

Type '"gradient"' is not assignable to type '"solid" | "outline" | "soft" | "subtle" | "ghost" | "link" | undefined'.ts-plugin(2322)

Image

Image

Additional Context

No response

Logs


Solution

The issue here is that the variants object is expecting a specific type, which is a union of string literals. However, you're trying to assign a string value that contains a dynamic value ({color}).

To fix this, you can create a custom type that extends the existing type, and then use that custom type in your variants object.

type CustomVariant = 'gradient' | 'solid' | 'outline' | 'soft' | 'subtle' | 'ghost' | 'link';

button: {
  variants: {
    variant: {
      [key in CustomVariant]: string;
    },
  },
  defaultVariants: {
    color: 'neutral',
    variant: 'subtle',
  },
  default: {
    color: 'white',
    loadingIcon: 'i-mdi-loading',
  },
},

In this code, we've created a custom type CustomVariant that extends the existing type. We've then used this custom type in our variants object.

By doing this, we're telling TypeScript that the gradient property is of type CustomVariant, which is a union of string literals. This should fix the TypeScript error you're encountering.

Conclusion

Q: What is the issue with the custom variant I'm trying to create?

A: The issue is that the variants object is expecting a specific type, which is a union of string literals. However, you're trying to assign a string value that contains a dynamic value ({color}).

Q: How can I fix this issue?

A: You can create a custom type that extends the existing type, and then use that custom type in your variants object.

Q: What is the custom type I need to create?

A: You need to create a custom type that extends the existing type. For example, if the existing type is CustomVariant, you can create a custom type like this:

type CustomVariant = 'gradient' | 'solid' | 'outline' | 'soft' | 'subtle' | 'ghost' | 'link';

Q: How do I use the custom type in my variants object?

A: You can use the custom type in your variants object like this:

button: {
  variants: {
    variant: {
      [key in CustomVariant]: string;
    },
  },
  defaultVariants: {
    color: 'neutral',
    variant: 'subtle',
  },
  default: {
    color: 'white',
    loadingIcon: 'i-mdi-loading',
  },
},

Q: What is the [key in CustomVariant] syntax?

A: The [key in CustomVariant] syntax is called a mapped type. It's a way to create a new type that is based on an existing type. In this case, we're creating a new type that is based on the CustomVariant type.

Q: Why do I need to use a mapped type?

A: You need to use a mapped type because the variants object is expecting a specific type, which is a union of string literals. By using a mapped type, we're telling TypeScript that the gradient property is of type CustomVariant, which is a union of string literals.

Q: Can I use a mapped type with other types?

A: Yes, you can use a mapped type with other types. For example, if you have a type Color that is a union of string literals, you can use a mapped type like this:

type Color = 'red' | 'green' | 'blue';

type CustomColor = {
  [key in Color]: string;
};

Q: What is the benefit of using a mapped type?

A: The benefit of using a mapped type is that it allows you to create a new type that is based on an existing type. This can be useful when you need to create a type that is similar to an existing type, but with some modifications.

Q: Can I use a mapped type with interfaces?

A: Yes, you can use a mapped type with interfaces. For example, if you have an interface IColor that has a property color, you can use a mapped type like this:

interface IColor {
  color: string}

type CustomColor = {
  [key in keyof IColor]: string;
};

Q: What is the keyof syntax?

A: The keyof syntax is called a type query. It's a way to get the type of a property or a method. In this case, we're using keyof to get the type of the color property.

Q: Can I use a mapped type with classes?

A: Yes, you can use a mapped type with classes. For example, if you have a class Color that has a property color, you can use a mapped type like this:

class Color {
  color: string;
}

type CustomColor = {
  [key in keyof Color]: string;
};

Q: What is the benefit of using a mapped type with classes?

A: The benefit of using a mapped type with classes is that it allows you to create a new type that is based on an existing class. This can be useful when you need to create a type that is similar to an existing class, but with some modifications.