'Build a complex type from Union type

I'm trying to build a type with two layers from a flat union type

Here's my code:

type TextVariants =
  | {
      size: 'tiny'
      // available variants for this size
      variants:
        | 'regularNormal'
    }
  | {
      size: 'super'
      // available variants for this size
      variants:
        | 'regularNone'
    }

type TextSizesProps = {
  [k in TextVariants['size']]: {
    css: Object
    variants: {
      [v in TextVariants['variants']]: Object
    }
  }
}

const textSizes: TextSizesProps = {
  tiny: {
    css: {
      fontSize: '$tiny',
    },
    variants: {
      // Here TS is complaining about missing "regularNone" but shouldn't
      regularNormal: {
        lineHeight: '$normal',
        fontWeight: '$regular',
      },
    },
  },
  super: {
    css: {
      fontSize: '$super',
    },
    variants: {
      // same
      regularNone: {
        lineHeight: '$none',
        fontWeight: '$regular',
      },
      },
    },
  },
}

I have different names for each text size, and I need it to be recognized

Here's a link for the typescript playground

Any help is appreciated



Solution 1:[1]

You should try it like this:

type TextSizesProps = {
  [V in TextVariants as V["size"]]: {
    css: Object
    variants: {
      [v in V["variants"]]: Object
    }
  }
}

Creating a mapped type out of TextVariants['size'] and then again out of TextVariants['variants'] will lead to a union which allows all variants for all sizes.

But if you map over the different TextVariants you can store each variant inside V. V now represents a specific variant and can be used to access the variants variant and size properties.

Playground

Solution 2:[2]

If you're open to it, you can change the definition of the TextVariants type to be an object type so you can iterate the keys in the TextSizesProps type.

For example:

type TextVariants = {
  tiny:
    | "regularNormal";
  super:
    | "regularNone";
}

type TextSizesProps = {
  [K in keyof TextVariants]: {
    css: Object;
    variants: {
      [V in TextVariants[K]]: Object;
    }
  }
}

or to allow for adding more fields in the future:

type TextVariants = {
  tiny: {
    variants: 
      | "regularNormal";
  };
  super: {
    variants:
      | "regularNone";
  };
}

type TextSizesProps = {
  [K in keyof TextVariants]: {
    css: Object;
    variants: {
      [V in TextVariants[K]["variants"]]: Object;
    }
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tobias S.
Solution 2 Henry Woody