Skip to content

Instantly share code, notes, and snippets.

@gcanti
Created December 6, 2016 11:31
Show Gist options
  • Select an option

  • Save gcanti/f7ccecc3cd813ba12aeb2a95f5bb2560 to your computer and use it in GitHub Desktop.

Select an option

Save gcanti/f7ccecc3cd813ba12aeb2a95f5bb2560 to your computer and use it in GitHub Desktop.

Revisions

  1. gcanti created this gist Dec 6, 2016.
    102 changes: 102 additions & 0 deletions typed-styles.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,102 @@
    // @flow

    //
    // library agnostic types and helpers
    //

    // keep private
    class Unit<A> {}
    class IsMedia {}

    export type Px = string & Unit<'pixel'>;
    export type Percentage = string & Unit<'percentage'>;
    export type Media = string & IsMedia;

    export const px = (x: number): Px => ((`${x}px`: any): Px)
    export const percentage = (x: number): Percentage => ((`${x}%`: any): Percentage)
    // other unit factories here...
    export const media = (options: { minHeight?: Px }): Media => {
    const constraints = []
    if (options.minHeight) {
    constraints.push(`minHeight: ${options.minHeight}`)
    }
    // other constraints here...
    return ((`@media (${constraints.join(' and ')})`: any): Media)
    }

    // the official $Exact doesn't play well with Pseudo type
    type Exact<A> = A & $Shape<A>;

    export type Own = Exact<{
    fontSize?: Px,
    lineHeight?: Px | number
    // other rules here...
    }>;

    export type Pseudo = Exact<{
    ':hover'?: Own
    // other pseudos here...
    }>;

    // I need an array here because computed properties are bugged
    // https://github.com/facebook/flow/issues/2928
    // when fixed we could define type Medias = { [key: Media]: Exact<{ style?: Own, pseudo?: Pseudo }> }
    export type Medias = Array<[Media, Exact<{
    style?: Own,
    pseudo?: Pseudo
    }>]>;

    //
    // adapter example
    //

    // this function is library specific, one for styled-components, one for fela, etc...
    // and returns a library specific domain model, perhaps a string or an
    // internal representation for styled-components, an object for fela, etc...
    function css(style: Own, pseudos: Pseudo, medias: Medias) {
    return Object.assign({}, style, pseudos, getMedias(medias))
    }

    function getMedias(medias?: Medias): ?Object {
    if (medias) {
    const o = {}
    medias.forEach(([m, s]) => o[m] = s)
    return o
    }
    return null
    }

    //
    // usage
    //

    const style = css({
    fontSize: px(30)
    // fontSize: 'a' // <= error
    // fontSize: 30 // <= error
    // fontsize: 1 / <= error
    }, {
    ':hover': {
    fontSize: px(50)
    }
    }, [
    [media({ minHeight: px(300) }), {
    style: {
    fontSize: px(60)
    }
    }]
    ])

    console.log(JSON.stringify(style, null, 2))
    /*
    Output:
    {
    "fontSize": "30px",
    ":hover": {
    "fontSize": "50px"
    },
    "@media (minHeight: 300px)": {
    "fontSize": "60px"
    }
    }
    */