nextui-org / tailwind-variants

🦄 Tailwindcss first-class variant API
https://tailwind-variants.org
MIT License
2.42k stars 68 forks source link

Tailwind Variants Transform Failed: Widths is not defined #61

Closed sebpowell closed 7 months ago

sebpowell commented 1 year ago

Describe the bug

First of all, thank you for the library – enjoying using it so far, it's helped a lot in structuring Tailwind-based projects.

Using Next.js 13 – I have a file that looks like this:

import { tv } from "tailwind-variants";

type WidthScale = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;

export const Widths: { [key in WidthScale]: string } = {
  0: "w-0",
  1: "w-1",
  2: "w-2",
  3: "w-3",
  4: "w-4",
  5: "w-5",
  6: "w-6",
  7: "w-7",
  8: "w-8",
  9: "w-9",
  10: "w-10",
};

export const useWidth = tv(
  {
    variants: {
      width: {
        full: "w-full",
        ...Widths,
      },
    },
  },
  { responsiveVariants: true }
);

When I start the app, I get the following message:

Tailwind Variants Transform Failed: Widths is not defined
If you think this is an issue, please submit it at https://github.com/nextui-org/tailwind-variants/issues/new/choose

If I move the widths directly into the function, the problem goes away – i.e:

export const useWidth = tv(
  {
    variants: {
      width: {
        full: "w-full",
        0: "w-0",
        1: "w-1",
        2: "w-2",
        3: "w-3",
        4: "w-4",
        5: "w-5",
        6: "w-6",
        7: "w-7",
        8: "w-8",
        9: "w-9",
        10: "w-10",
      },
    },
  },
  { responsiveVariants: true }
);

To Reproduce Create a file with the above, and reference it inside a component.

Desktop (please complete the following information):

justinmetros commented 1 year ago

Exact same issue in a Remix project with @latest dependencies. Happens when the responsive variants object is present.

mskelton commented 7 months ago

@sebpowell This is a limitation of the responsive variants compiler which evaluates the code within tv without knowledge of variables being imported or used. Without full semantic analysis (which even that would fall short in many scenarios) your use case is not something that is supported.

I'm going to close this as not planned.

ellisio commented 5 months ago

Edit: Seems importing from a static file is also generating the same error (derp after re-reading what you posted above). This also doesn't work, even with the utility function, as the responsive classes are dropped from the final bundle after running next build. Looks like copy-pasta it is, for now... 🤞

@mskelton I'm running into this error as well, using a utility function called spacing(), so I can define things like gap, padding, etc. on my components. The purpose of this is to expand the defaultTheme object and create all the necessary key/value items for the defined prop. For example, the following:

variants: {
  gap: spacing("gap"),
  padding: spacing("padding"),
},
{
  responsiveVariants: true,
},

Would be like doing the following:

variants: {
  gap: {
    0: "gap-0",
    1: "gap-1",
    2: "gap-2",
    // ...
  },
  padding: {
    0: "p-0",
    1: "p-1",
    2: "p-2",
    // ...
  },
},
{
  responsiveVariants: true,
},

This is a DX thing, so we don't have to copy pasta this all over the place, and it can dynamically change if defaultTheme is changed (or we decide to point to a custom configuration that is not defaultTheme). Is there a better way to go about this, so we can avoid this error?

The only other option I am thinking about, is having a "predev/prebuild" step that generates these objects, writes them to a file, and we import that instead. It's doable, but kind of stinks to bloat out the dev/build steps like that. This keeps our developers happy, as they get the type safety for these props (CSS-in-JS devs, so we're trying to meet in the middle here). I've tried telling them to use className and define this stuff manually, if they want... but they want component props.

Also, for reference, this is how we've put together our spacing() helper (just demoing gap and padding atm, we have many more mapped).

import { spacing as twSpacing } from "tailwindcss/defaultTheme";

type CastToNumber<T> = T extends `${infer N extends number}` ? N : never;

type Spacing = CastToNumber<keyof typeof twSpacing>;

const SPACING_TYPES = {
  gap: "gap",
  padding: "p",
};

export const spacing = (type: keyof typeof SPACING_TYPES) => {
  return Object.keys(twSpacing).reduce(
    (acc, key) => {
      acc[Number(key) as Spacing] = `${SPACING_TYPES[type]}-${key}`;
      return acc;
    },
    {} as Record<Spacing, string>,
  );
};
Nicotu commented 4 months ago

This won''t work regardless of responsive variants. Spread operator syntax will always end up triggering that error :(