Skip to content

Commit

Permalink
Use template literal types for unit values
Browse files Browse the repository at this point in the history
  • Loading branch information
tpict committed Sep 13, 2020
1 parent c01326f commit f40e7c3
Show file tree
Hide file tree
Showing 26 changed files with 849 additions and 456 deletions.
15 changes: 2 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const styles = tacky(_ => [
_.fontSize(_.rem(fontSize)),
_.fontFamily("Times New Roman", "serif"),
_.display(display),
_.boxShadow(_.rem(2), _.rem(2), _.rem(2), _.rem(2), _.rgba(0, 0, 0, 0.5)),
_.media([_.media.screen(_.media.minWidth(_.rem(30)))],
_.boxShadow("2rem", "2rem", "2rem", "2rem", _.rgba(0, 0, 0, 0.5)),
_.media([_.media.screen(_.media.minWidth("300px"))],
_.color(_.rgb(0, 255, 0)),
),
]);
Expand Down Expand Up @@ -72,17 +72,6 @@ Tacky is a library inspired by
approach in order to provide safety that can't be guaranteed by a `Record`
interface alone.

### Why are values specified unit-first?

Standard object styles allow any string where scalar CSS values e.g. `"2rem"`,
`"5px"` are expected. It's not feasible to enumerate every possibility
of those values, and TypeScript's type system has no ability to interpolate
strings.

By expressing these as a "unit function" that receives magnitude as a
number, we can be much more strict about what values are allowed for a given
property.

### Why write styles as a list of function calls?

Representing complex CSS values safely in TypeScript warrants the use of a
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@
"@types/lodash-es": "^4.17.3",
"@types/react": "^16.9.46",
"@types/react-dom": "^16.9.8",
"@typescript-eslint/eslint-plugin": "^3.10.0",
"@typescript-eslint/parser": "^3.10.0",
"@typescript-eslint/eslint-plugin": "^4.1.0",
"@typescript-eslint/parser": "^4.1.0",
"babel-jest": "^26.3.0",
"babel-loader": "^8.1.0",
"babel-plugin-macros": "^2.8.0",
"clean-webpack-plugin": "^3.0.0",
"csstype": "^3.0.3",
"eslint": "^7.7.0",
"eslint": "^7.9.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.6",
Expand All @@ -50,7 +50,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"tsconfig-paths-webpack-plugin": "^3.3.0",
"typescript": "^4.0.2",
"typescript": "^4.1.0-dev.20200911",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
Expand Down
2 changes: 1 addition & 1 deletion packages/tacky-css/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
"babel-plugin-macros": "^2.8.0",
"csstype": "^3.0.3",
"nanoid": "^3.1.12",
"typescript": "^4.0.2"
"typescript": "^4.1.0-dev.20200911"
}
}
153 changes: 152 additions & 1 deletion packages/tacky-css/src/color.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,156 @@
import { TackyVariant } from "./types";

type NamedColor =
| "aliceblue"
| "antiquewhite"
| "aqua"
| "aquamarine"
| "azure"
| "beige"
| "bisque"
| "black"
| "blanchedalmond"
| "blue"
| "blueviolet"
| "brown"
| "burlywood"
| "cadetblue"
| "chartreuse"
| "chocolate"
| "coral"
| "cornflowerblue"
| "cornsilk"
| "crimson"
| "cyan"
| "darkblue"
| "darkcyan"
| "darkgoldenrod"
| "darkgray"
| "darkgreen"
| "darkgrey"
| "darkkhaki"
| "darkmagenta"
| "darkolivegreen"
| "darkorange"
| "darkorchid"
| "darkred"
| "darksalmon"
| "darkseagreen"
| "darkslateblue"
| "darkslategray"
| "darkslategrey"
| "darkturquoise"
| "darkviolet"
| "deeppink"
| "deepskyblue"
| "dimgray"
| "dimgrey"
| "dodgerblue"
| "firebrick"
| "floralwhite"
| "forestgreen"
| "fuchsia"
| "gainsboro"
| "ghostwhite"
| "gold"
| "goldenrod"
| "gray"
| "green"
| "greenyellow"
| "grey"
| "honeydew"
| "hotpink"
| "indianred"
| "indigo"
| "ivory"
| "khaki"
| "lavender"
| "lavenderblush"
| "lawngreen"
| "lemonchiffon"
| "lightblue"
| "lightcoral"
| "lightcyan"
| "lightgoldenrodyellow"
| "lightgray"
| "lightgreen"
| "lightgrey"
| "lightpink"
| "lightsalmon"
| "lightseagreen"
| "lightskyblue"
| "lightslategray"
| "lightslategrey"
| "lightsteelblue"
| "lightyellow"
| "lime"
| "limegreen"
| "linen"
| "magenta"
| "maroon"
| "mediumaquamarine"
| "mediumblue"
| "mediumorchid"
| "mediumpurple"
| "mediumseagreen"
| "mediumslateblue"
| "mediumspringgreen"
| "mediumturquoise"
| "mediumvioletred"
| "midnightblue"
| "mintcream"
| "mistyrose"
| "moccasin"
| "navajowhite"
| "navy"
| "oldlace"
| "olive"
| "olivedrab"
| "orange"
| "orangered"
| "orchid"
| "palegoldenrod"
| "palegreen"
| "paleturquoise"
| "palevioletred"
| "papayawhip"
| "peachpuff"
| "peru"
| "pink"
| "plum"
| "powderblue"
| "purple"
| "rebeccapurple"
| "red"
| "rosybrown"
| "royalblue"
| "saddlebrown"
| "salmon"
| "sandybrown"
| "seagreen"
| "seashell"
| "sienna"
| "silver"
| "skyblue"
| "slateblue"
| "slategray"
| "slategrey"
| "snow"
| "springgreen"
| "steelblue"
| "tan"
| "teal"
| "thistle"
| "tomato"
| "transparent"
| "turquoise"
| "violet"
| "wheat"
| "white"
| "whitesmoke"
| "yellow"
| "yellowgreen";

export type Rgb = TackyVariant<"rgb">;
export const rgb = (red: number, green: number, blue: number): Rgb =>
`rgb(${red}, ${green}, ${blue})` as Rgb;
Expand All @@ -12,4 +163,4 @@ export const rgba = (
alpha: number
): Rgba => `rgba(${red}, ${green}, ${blue}, ${alpha})` as Rgba;

export type CSSColor = Rgb | Rgba | "currentcolor" | "transparent";
export type CSSColor = Rgb | Rgba | "currentcolor" | NamedColor;
5 changes: 3 additions & 2 deletions packages/tacky-css/src/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export type CSSURL = TackyVariant<"url">;
export const url = (url: URL): CSSURL => `url(${url})` as CSSURL;

export type FitContent = TackyVariant<"fitContent">;
export const fitContent = (arg: CSSLengthPercentage): FitContent =>
`fitContent(${arg})` as FitContent;
export const fitContent = <T extends string>(
arg: CSSLengthPercentage<T>
): FitContent => `fitContent(${arg})` as FitContent;
28 changes: 16 additions & 12 deletions packages/tacky-css/src/image.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BackgroundPositionArgs } from "./property";
import { CSSColor } from "./color";
import { CSSLengthPercentage, Percent, CSSAngle } from "./unit";
import { CSSLengthPercentage, CSSPercentage, CSSAngle } from "./unit";
import { TackyVariant } from "./types";
import { CSSURL } from "./function";

Expand All @@ -18,15 +18,17 @@ type SideOrCorner =
| "to right top"
| "to right bottom";

type InitialLinearColorStop = [
type InitialLinearColorStop<T extends string> = [
color: CSSColor,
stopStart?: CSSLengthPercentage,
stopEnd?: CSSLengthPercentage
stopStart?: CSSLengthPercentage<T>,
stopEnd?: CSSLengthPercentage<T>
];

type LinearColorStop = InitialLinearColorStop;
type ColorHint = Percent;
type LinearColorStopOrHint = LinearColorStop | [ColorHint, ...LinearColorStop];
type LinearColorStop<T extends string> = InitialLinearColorStop<T>;
type ColorHint<T extends string> = CSSPercentage<T>;
type LinearColorStopOrHint<T extends string> =
| LinearColorStop<T>
| [ColorHint<T>, ...LinearColorStop<T>];

// Ideally <color-hint> would be expressed in a way that matches the CSS syntax
// more closely, i.e.
Expand Down Expand Up @@ -58,11 +60,12 @@ type LinearColorStopOrHint = LinearColorStop | [ColorHint, ...LinearColorStop];
export interface LinearGradientFunction<Return> {
<
T extends [CSSAngle | SideOrCorner] | [],
V extends [LinearColorStopOrHint, ...LinearColorStopOrHint[]]
U extends string,
V extends [LinearColorStopOrHint<U>, ...LinearColorStopOrHint<U>[]]
>(
...args: [
...angle: T,
colorStop: InitialLinearColorStop,
colorStop: InitialLinearColorStop<U>,
...colorStopOrHint: V
]
): Return;
Expand Down Expand Up @@ -95,17 +98,18 @@ type RadialGradientExtentKeyword =
| "farthest-corner";
export interface RadialGradientFunction<Return> {
<
U extends string,
T extends
| [
RadialGradientShape | RadialGradientExtentKeyword,
...([] | ["at", ...BackgroundPositionArgs])
...([] | ["at", ...BackgroundPositionArgs<U>])
]
| [],
V extends [LinearColorStopOrHint, ...LinearColorStopOrHint[]]
V extends [LinearColorStopOrHint<U>, ...LinearColorStopOrHint<U>[]]
>(
...args: [
...shape: T,
colorStop: InitialLinearColorStop,
colorStop: InitialLinearColorStop<U>,
...colorStopOrHint: V
]
): Return;
Expand Down
2 changes: 1 addition & 1 deletion packages/tacky-css/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type TackyArg = typeof tackyArg;

export const compile = (styles: TypedCSSArray): CSSObject =>
styles.reduce((acc, [key, value]) => {
// Investigate TS2590 without this cast
// TODO: Investigate TS2590 without this cast
acc[key as string] = Array.isArray(value) ? compile(value) : value;
return acc;
}, {} as CSSObject);
Expand Down
42 changes: 25 additions & 17 deletions packages/tacky-css/src/media/mediaFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,48 @@ export const color = (bits?: number): MediaExpression =>
export const colorGamut = (gamut: "srgb" | "p3" | "rec2020"): MediaExpression =>
`(colorGamut: ${gamut})` as MediaExpression;

export const height = (value: CSSLength): MediaExpression =>
`(height: ${value})` as MediaExpression;
export const height = <T extends string>(
value: CSSLength<T>
): MediaExpression => `(height: ${value})` as MediaExpression;

export const hover = (support: "none" | "hover"): MediaExpression =>
`(hover: ${support})` as MediaExpression;

export const maxColor = (bits: number): MediaExpression =>
`(max-color: ${bits})` as MediaExpression;

export const maxHeight = (value: CSSLength): MediaExpression =>
`(max-height: ${value})` as MediaExpression;
export const maxHeight = <T extends string>(
value: CSSLength<T>
): MediaExpression => `(max-height: ${value})` as MediaExpression;

export const maxMonochrome = (bits: number): MediaExpression =>
`(max-monochrome: ${bits})` as MediaExpression;

export const maxResolution = (value: CSSResolution): MediaExpression =>
`(max-resolution: ${value})` as MediaExpression;
export const maxResolution = <T extends string>(
value: CSSResolution<T>
): MediaExpression => `(max-resolution: ${value})` as MediaExpression;

export const maxWidth = (value: CSSLength): MediaExpression =>
`(max-width: ${value})` as MediaExpression;
export const maxWidth = <T extends string>(
value: CSSLength<T>
): MediaExpression => `(max-width: ${value})` as MediaExpression;

export const minColor = (bits: number): MediaExpression =>
`(min-color: ${bits})` as MediaExpression;

export const minHeight = (value: CSSLength): MediaExpression =>
`(min-height: ${value})` as MediaExpression;
export const minHeight = <T extends string>(
value: CSSLength<T>
): MediaExpression => `(min-height: ${value})` as MediaExpression;

export const minMonochrome = (bits: number): MediaExpression =>
`(min-monochrome: ${bits})` as MediaExpression;

export const minResolution = (value: CSSResolution): MediaExpression =>
`(min-resolution: ${value})` as MediaExpression;
export const minResolution = <T extends string>(
value: CSSResolution<T>
): MediaExpression => `(min-resolution: ${value})` as MediaExpression;

export const minWidth = (value: CSSLength): MediaExpression =>
`(min-width: ${value})` as MediaExpression;
export const minWidth = <T extends string>(
value: CSSLength<T>
): MediaExpression => `(min-width: ${value})` as MediaExpression;

export const monochrome = (bits?: number): MediaExpression =>
`(monochrome${bits ? `: ${bits}` : ""})` as MediaExpression;
Expand All @@ -63,8 +70,9 @@ export const pointer = (
accuracy: "none" | "coarse" | "fine"
): MediaExpression => `(pointer: ${accuracy})` as MediaExpression;

export const resolution = (value: CSSResolution): MediaExpression =>
`(resolution: ${value})` as MediaExpression;
export const resolution = <T extends string>(
value: CSSResolution<T>
): MediaExpression => `(resolution: ${value})` as MediaExpression;

export const width = (value: CSSLength): MediaExpression =>
export const width = <T extends string>(value: CSSLength<T>): MediaExpression =>
`(width: ${value})` as MediaExpression;
Loading

0 comments on commit f40e7c3

Please sign in to comment.