There are 2 problems to solve:
Problem 1: Mapped type with extra properties
You need to use intersection type:
type IconType = 'file' | 'emoji' | 'external'
type IconFile<U extends IconType & string> = { type: U } & { [P in U]: string }
const x: IconFile<'file'> = {
type: 'file',
file: 'string'
}
Note the extra generic argument on IconFile of type string.
Problem 2: Map union type to another union type
We'd like to get a type equal to IconFile<'file'> | IconFile<'emoji'> | IconFile<'external'>
You can use conditional type for distributing over the members of the union type
type DistributeToIconFile<U extends IconType & string> = U extends string
? IconFile<U>
: never;
type IconFileAsUnion = DistributeToIconFile<IconType>
// ^?
// type IconFileAsUnion = IconFile<'file'> | IconFile<'emoji'> | IconFile<'external'>
const a: IconFileAsUnion = {
type: 'file',
file: 'myfile'
};
See TypeScript: Map union type to another union type
Note that simple IconFile<IconType>
does not distribute the union members
const nodDistributedBadApproach: IconFile<IconType> = {
type: 'file',
file: 'string1',
emoji: 'string2',
external: 'string3'
}
Playground