Proposal: stricter / app-specific CSSNestedProperties
silviogutierrez opened this issue · comments
First off: huge fan of this library. I can't praise it enough! So simple and really makes styling an app a pleasure.
TypeStyle already has a way to add properties that are missing, as documented here:
But it lacks a way to further restrict properties. Why restrict them? Two use cases:
- Forbid using specific properties
- Only allow certain colors, fonts, weights, etc based on a style guide.
A lot of the above can be enforced procedurally through mixins, code review, etc. But why not do it at the compiler level?
So I actually got this to work with a few hacks, and it's 90% of the way there. But with a few tweaks it can be built-in to TypeStyle. And far simpler.
In the example below, as a POC, I wanted to limit font weights and font families only to what I provide.
// Put this in a file like client/style.ts
import * as typeCSSTips from "csstips";
import {
media as typeMedia,
style as typeStyle,
types,
} from "typestyle";
type CSSTipNames = Extract<keyof typeof typeCSSTips, string>;
type HandleCSSTipMember<T> = T extends (... args: infer S) => types.NestedCSSProperties ? (...args: S) => JoyNestedCSSProperties : JoyNestedCSSProperties;
type JoyCSSTips = {
[P in Exclude<CSSTipNames, 'padding' |' margin' | 'border'>]: HandleCSSTipMember<typeof typeCSSTips[P]>;
} & {
[P in Extract<CSSTipNames, 'padding' |' margin' | 'border'>]: typeof typeCSSTips[P];
};
export const csstips = typeCSSTips as JoyCSSTips;
export interface JoyNestedCSSProperties
extends Omit<types.NestedCSSProperties, "fontFamily" | "fontWeight"> {
fontFamily?: "Montserrat";
fontWeight?: 200 | 300 | 500 | 600;
// this could be tighter but I just wanted to demonstrate
$nest?: {
[selector: string]: JoyNestedCSSProperties | undefined;
};
}
export function style(...objects: (JoyNestedCSSProperties | undefined)[]): string;
export function style(
...objects: (JoyNestedCSSProperties | null | false | undefined)[]
): string;
export function style() {
return typeStyle.apply(undefined, arguments);
}
export const media = (
mediaQuery: types.MediaQuery,
...objects: (JoyNestedCSSProperties | undefined | null | false)[]
): JoyNestedCSSProperties => {
return typeMedia(mediaQuery, ...objects) as JoyNestedCSSProperties;
};
The main style function was super easy. But it could be even easier: just make typestyle.createTypeStyle();
take in a generic and pass it to a class. Like so:
declare function createTypeStyle<T = types.NestedCSSProperties>()
It still defaults for those who don't care.
We'd also need to move media
under that factory and any related functions. But the above covered everything I used in my code base.
CSS Tips
Unfortunately, CSS tips were a little harder, since it's a collection of functions and objects that return CSSNestedProperties
. Boxed types are a particularly troublesome one because argument inference in TS makes all of these required, which isn't true in the real source.
But again, if we make these into a factory and simply provide a default instance just like TypeStyle, it should be no problem.
Thanks again for a great library!