import { css as gooberCss } from 'goober'; import { PropertiesHyphenFallback } from 'csstype'; interface CssString { // TODO stricter type checking toString(): string } export class CssStringImpl implements CssString { private css: string constructor(s: TemplateStringsArray) { this.css = s.raw[0] .trim() .replace(/[ ][ ]+/g, ' ') .replace(/:[ ]/g, ':') .replace(/\n/g, '') .replace(/;[ ]/g, ';'); } toString() { return this.css } } interface CssIf { (b: boolean): (...a: CssString[]) => CssIfString } interface CssElse { (...c: CssString[]): CssString if: CssIf } interface CssIfString extends CssString { else: CssElse } const cssIf: CssIf = (b: boolean) => (...a: CssString[]) => new CssIfStringImpl(b, ...a); export class CssIfStringImpl implements CssIfString { readonly else: CssElse private readonly cssStrings: CssString[] constructor(private readonly condition: boolean, ...cssStrings: CssString[]) { this.cssStrings = cssStrings const elseFn = (...c: CssString[]) => { if (this.condition) { return { toString: () => { return this.cssStrings.map((c2) => c2.toString()).join(''); } } } return { toString: () => { return c.map((cc) => cc.toString()).join('') } }; } elseFn.if = cssIf this.else = elseFn } toString() { if (this.condition) { return this.cssStrings.map((c2) => c2.toString()).join(''); } return ''; } } interface CssNest { (selector: string): (...a: CssString[]) => CssString } const cssNest: CssNest = (selector) => (...a) => { return { toString: () => `${selector}{${a.map(aa => aa.toString()).join('')}}` } } interface CssDynamic { (a: PropertiesHyphenFallback): CssString } const cssDynamic: CssDynamic = (a: PropertiesHyphenFallback) => { return { toString(): string { return Object .entries(a) .map(([key, value]) => `${key}:${value.toString()};`) .join('') } }; }; interface CssMedia { (raw: string): any } const cssMedia: CssMedia = (arg1: string) => { return (...body: CssString[]) => { return { toString(): string { return `@media ${arg1}{${body.map(b => b.toString()).join('')}}` } } } } const cssCompile = (...strings: CssString[]) => { return strings .filter((s) => ['string', 'object'].includes(typeof s)) .map((s) => { if (typeof s === 'object') { return gooberCss`${s.toString()}` } return s }) .join(' ') } interface Css { (s: TemplateStringsArray): CssString if: CssIf nest: CssNest dynamic: CssDynamic media: CssMedia cx(...strings: CssString[]): string } const _css: Partial = (s: TemplateStringsArray) => new CssStringImpl(s); _css.if = cssIf; _css.nest = cssNest; _css.dynamic = cssDynamic; _css.media = cssMedia; _css.cx = cssCompile; export const css = _css as Css;