Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typing for '!Important' #160

Open
sergeyzenchenko opened this issue May 26, 2022 · 3 comments
Open

Typing for '!Important' #160

sergeyzenchenko opened this issue May 26, 2022 · 3 comments

Comments

@sergeyzenchenko
Copy link

Hey guys, we've been using csstype as part of vanilla-extract.
We needed to add support of !important in some cases. I've been able to implement it using following TS mapped type magic.

function withImportant(css:ImportantCSS<CSSProperties>): CSSProperties {
  return css as CSSProperties;
}

type WithImportant<T extends string> = T | `${T} !important`;
type WithImportantArrays<T> = T extends string ? WithImportant<T> : T extends Array<infer R> ? R extends string ? WithImportant<R>[] : T : T;

type ImportantCSS<T> = {
  [Property in keyof T]: WithImportantArrays<T[Property]>
}

globalStyle(`h2`, withImportant({
  textAlign: 'center !important',
  flexDirection: "row",
  border: 22
}));

It even provides code completion

image

What do you think about this approach and do you see any problems with it?

@sergeyzenchenko
Copy link
Author

hey @frenic

@frenic
Copy link
Owner

frenic commented Jun 1, 2022

I think it looks good. However, in the future I plan to implement string templates at some extent and I know there's a technical limit with those. It means you will receive a type error if you exceed the amount of variations. With your addition it may cause a type error at your end, but not at our end. But I don't think it will be that advanced so I would say you're pretty safe anyway.

@mikeybinns
Copy link

I'm just chiming in with an alternate workaround, this is how I solved it. I've noticed just before posting that it's kinda similar to OPs solution, however I still feel like it's different enough to share.

// This function requires TS 5.0, see below for alternative
function allowImportant<
  const InputProperty extends unknown,
  ReturnedPropertyType = InputProperty extends `${infer PropertyWithoutImportant} !important`
    ? PropertyWithoutImportant
    : InputProperty,
>(property: InputProperty) {
  return property as unknown as ReturnedPropertyType;
}

const autoTest = allowImportant("auto !important"); // infers as "auto"
const numberTest = allowImportant(0); // infers as 0

This function is purely a type helper, so it just returns the original value while typing it differently.
If you provide it a string which ends with " !important", it infers the type as whatever is before " !important" in that string.
If you provide it anything else, it will infer it as the input type.

If you remove const from before InputProperty, the function becomes compatible with TS versions earlier than TS 5.0, but the typings are looser:

function allowImportant<
  InputProperty extends unknown,
  ReturnedPropertyType = InputProperty extends `${infer PropertyWithoutImportant} !important`
    ? PropertyWithoutImportant
    : InputProperty,
>(property: InputProperty) {
  return property as unknown as ReturnedPropertyType;
}

const autoTest = allowImportant("auto !important"); // infers as string
const numberTest = allowImportant(0); // infers as number

This still worked for me with Vanilla Extract but your mileage may vary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants