Skip to content

Instantly share code, notes, and snippets.

@jhesgodi
Created September 16, 2022 09:01
Show Gist options
  • Save jhesgodi/ba0039008fa0d24eef32f93b467d8d9c to your computer and use it in GitHub Desktop.
Save jhesgodi/ba0039008fa0d24eef32f93b467d8d9c to your computer and use it in GitHub Desktop.
Variant class names typing
// Playground Link
// https://www.typescriptlang.org/play?#code/C4TwDgpgBAaghgJwJZwHbAM4HkBmAeAFQBooAFKAXigAkB7WgawzywFslgDwI8AlCAI4BXJAggATQgD4SAMSGoAxsCS1UMqABIAggBtdUqZSgBvAFBRLUANoMoSVFAYQQtHGQC6AfgBcZWx5QEAAewBCo4hhQAJIRIVBephZWKdZI9o6kAd5+8MhomLh4dIzMbBx4pCQMhkYAPlAYwMioAOYA3MmWAL5QDU0trVB+WQyBIWERUQQIQsAAFiAJUNoYAEL0uhBoeSjoeAMOQw27Bdj4JUws7MCV1bVGfqgQAG4QCJ3dZmagkFAzc0WxgARptto4GrRgQArCDKPpQeRKFRqb6-aCxcTxKiHNoIjAgVig3QI1BCInvTo-bgrdZgnaIPa3UhGKgmKAAcmaQggHJGUF6DXZHJwcF0GF5-MFjWaRzRNJ0+mM7M0Yt0flxrRIqt0AHc4CAMBrZXivtS-poACKiKJURUkgBkUAAohx5u88CrUGoIMbBgKSF6kOqZYNtd7gKhg36jgKpPK-pdmMQoJbjFabaz-lAnZaE9BypxuIQSCzlV0bHYHE4XG5s3BpgEgqFwpEyMtnm8EMMnB4-AQAmYzeioFhULoQCWyFn2bYMjXXO4CH3-gF2gKc6ZK-PnIvPL4oJ3KQLOiPXQsPSmy1QxxOp2WGrfJ1V-lIqWYAPQfqAAOVoYT8aIoEUWghF0cRUC5KBWDgZwoAvKBxFEOEVDeKJgWgaEhCaUMjhIDBaHsYAoFcIR4IQJZgEImC4IvVgoDQKAoVheEQNYMBg3eKBdTUYi2LAXQ4AcEgFloCVP2-DhuN4picBwLi4DmQj+K2FFHAgN13gAOigABlQj0nEGSJWgKSGwYqBgSQIYsTFLTvm+L9vwAWlc5z-ggJpYzc1yHJHXSkAAL2gKgACJggwUKEVCjBWCihpQtYcR4qgULdFaFLwt0UKqRHNY5io1BSAQWgwFtJJLCQcQD01TpLHEOBgDgAhPOAaJqpjNo6uAwSMAwH84FYCAapNDpvksEDdFoBAD1C4FdB5TKxGS6L5rgRQGBy5IXkZApZrAZAYIozKJRAiJEBATKwgQFQLsy4RGveVA7uihQmhALZku6jAguGvwAuC7r5jEHARsGb6HuWg9QVoLY0G6pCMDgeaJGh+lUG6nAwN0AB1KqFjR2HwW6xQ4A4prdF+jrLPR7q1AAYUpjaDwACl9KAAFlQIlZ03nQABKSgjAUBhvV1DHkjULnsIgZ10HeVn2elnm+eAQWKGF1BRdocXuqa4FMRCA8yQpD5khKrZZuBAq1Eyymta2yxdoAGWRiBdDBo5ut24rPIlamYbhiXLBUYBLc6sbLCmuAkLaQmg8+Kkzpwnb8nQI1YF29Oiny4BCuK0qMBnCsdXVCtQpwXjnKR1Aokr9Bq4gdhiXEci0B+tTEKEBBGtUVBnIAdgAVigEqFCxcRq-ogoUEphsJDbjajmc3UqugOHY9aZzvWeGngmc0EECxbsHHtiBnJwLZgiIpuMGcxRwmuqAsK8nAQHvx+uIwMB1vP-eACZQpEAcpYH6wU-DmBSBFPwiUHDOXmIPeCExnIRSgGAd+ABGNBACgEVlijA9g-cEEAE4kGhBQVEdBzl-7YOcgAZlwSkJKBC4EIIwQABjIcAChaD37-y0iPMA+8GHAJSOlFhRDnIYJoWEchqCqF0NoSIiswQQywMkRggALFwg+89eHOW0UIgxjCAzjUaJDCQECKzcnZpAlIjRfpWPsVYaBqUEED24oPExzj8FuOcqQ3U-jvH2OYX4jhniOHBLEa0GBbCaGBOkVElxai2HaISZopJ3RRFWCyckXJ20s7ACcVYA6SAjogGKSkUuMDZHcN1PMDg0BD7HySYjZGn1Kn2NFOKWxFZnGTWmp05xlh5o8hgc094B8FpNK3qM6A8xaBdh8MCWZ0z6HsPYUk5xy1xnTWPs5Zalkt6HIWUslZByJDrM2dk4ZVg1obV2UfSZrQe7v2IRso5zkXkGn8R8057xllb2+e-AeGytk5JufY3JtybHjK3m0lGrdSrrQ4O-TRnDanOQRZ9TJkL8kpFOmoBqFEhlaDVI8lpkL6pICRoi0llhukSnpVYapbjFkAsxfUxp4KJqw0GRVW5dzpkUsmXMnRYr-kIEBVMxaVLhk7NShMhAFzW6YpOeyqV5zlo8qFetBgIrlXAt+Ri5BRr3mcMldKs1YK5UpGhcM+1zjYWKr2c815VymI-0UKigx1y+mmLtcA6x7xbokoFSkbFljw1dLFEy6N-S+VSvjcMuZNTkESo1T4TFcyrk6ssAq0KaqF6WqLZPOhNr-XOPufq1KmLrUWszXW91oK-WCoDQ6oNMLZjs0Laa915bOHIu9aAX1uKKz4qsA9a6z0w12IjTS9pUa50xp6cy3lU0k3Ltuay0KRqAAcFa20jOFWypZ2bpl5tHlG0KJbkHattfY6tsTG19p+eanVjqoWduGc63t5CjUDs9Sikd6Kx2BrMZYTQUYQxbrJfoA1NMnmGvdf-D5Ta32HvsZG8QzLGU9stUqmV8zM3nIzWe5BXKwjgt-UOn1oG5WfvtRO6Om8YFgFoA4a6zlXiPzvjvGZWKF2Ip0dhxD+z+O4OSFjfQeNxALE6b+wJ0nsrZInaTcmYoqYKe7TAoQYBIAIFJhKExE7sPabGalRQ3cCLKojM5NUOsJDBLwzAqzCAbPOXY5x94JngFmiAA
type VariantsOf<T, P = Hooks<OmitType<Required<T>, Function>, $All>> = {
[k in keyof P]?: P[k] extends Index ? {
[i in P[k]]?: VariantsOf<Hooks<Omit<P, k>>> | string;
} | string : P[k] extends Truthy ? AsBooleanVariant<string | VariantsOf<Hooks<Omit<P, k>>>> : never;
}
type Truthy = boolean | object | Function
type Index = string | symbol | number;
type AsBooleanVariant<P> = { 'true': P } | { 'false': P } | string
type $All = { $all: string, $always: string }
type $Dirs = $All & Either<{ $none: string }, { $nil: string, $notnil: string }>
type Hooks<T, D = $Dirs> = T & D
type OmitType<T, P> = {
[k in keyof T as T[k] extends P ? never : k]: T[k]
}
type Only<T, P> = { [k in keyof T]: T[k]; } & { [k in keyof P]?: never; };
type Either<T, P> = Only<T, P> | Only<P, T>;
// Note: I couldn't make the directives be just string, so it you try to make them an object compiler wont complain, those
// it wont offer auto completion either. So i dont see it as a big deal.
/// ---- Testing -----
type Size = "xs" | "sm" | "md" | "lg" | "xl";
type ButtonProps = {
id?: string;
dataTestId?: string;
className?: string;
color?: "blue" | "red" | "black";
variant?: "primary" | "secondary" | "tertiary" | "quaternary" | "unstyled";
size?: Size;
href?: string;
squared?: boolean;
disabled?: boolean;
fullWidth?: boolean;
capitalized?: boolean;
onClick?: (e: MouseEvent) => unknown;
onMouseEnter?: (e: MouseEvent) => unknown;
tabIndex?: number;
role?: "button" | "link";
ariaLabel?: string;
ariaPressed?: boolean;
title?: string;
loading?: boolean;
};
const variants: VariantsOf<ButtonProps> = {
$all:
"font-sans font-semibold transition duration-75 rounded-sm antialiased tracking-wide leading-none box-border inline-flex items-center justify-center space-x-2",
size: {
xs: "min-h-7 text-xs py-1 px-2",
sm: "min-h-9 text-xs py-2 px-3",
md: "min-h-10 text-xs py-2.5 px-3",
lg: "min-h-12 text-xs py-3 px-3",
xl: "min-h-14 text-base py-4 px-4",
},
squared: {
true: {
size: {
xs: "h-7 w-7",
sm: "h-9 w-9",
md: "h-10 w-10",
lg: "h-12 w-12",
xl: "h-14 w-14",
},
},
},
variant: {
primary: {
$all: "text-white border",
disabled: {
false: {
color: {
blue: "border-blue bg-blue hover:bg-blue-300",
red: "border-red bg-red hover:bg-red-300",
black: "border-gray-900 bg-gray-900 hover:bg-gray-700",
},
},
true: "bg-disabled opacity-40 text-disabled",
},
},
secondary: {
$all: "border",
disabled: {
false: {
$all: "hover:text-white",
color: {
blue: "border-blue text-blue hover:bg-blue",
red: "border-red text-red hover:bg-red",
black: "border-gray-900 text-gray-900 hover:bg-gray-900",
},
},
true: "border-gray-300 opacity-40",
},
},
tertiary: {
disabled: {
false: {
color: {
blue: "text-blue hover:text-blue-300",
red: "text-red hover:text-red-300",
black: "text-gray-900 hover:text-gray-700",
},
},
true: "text-gray-300 opacity-40",
},
},
quaternary: {
disabled: {
false: {
color: {
$all: "gray-800",
blue: "hover:text-blue",
red: "hover:text-red",
black: "hover:text-gray-900",
},
},
true: "text-gray-300 opacity-40",
},
},
$nil: {
$all: "border border-gray-200 text-gray-900",
disabled: {
false: "hover:border-blue hover:bg-blue hover:text-white",
true: "opacity-40",
},
},
},
loading: "pointer-events-none bg-disabled text-disabled border-none",
fullWidth: {
true: "w-full",
},
capitalized: {
true: "uppercase",
},
disabled: {
true: "cursor-not-allowed",
false: "cursor-pointer",
},
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment