GithubHelp home page GithubHelp logo

bs-css's Introduction

bs-css

Statically typed DSL for writing css in reason and rescript.

The bs-css library contains type css core definitions, it has different implementations:

  • bs-css-emotion is a typed interface to Emotion
  • bs-css-fela is a typed interface to Fela (react)
  • bs-css-dom is a typed interface to ReactDOMRe.Style.t (reason-react)

If you know another implementation that can be added, send a link in an issue or create a PR.

Installation

npm install --save bs-css bs-css-emotion
or
yarn add bs-css bs-css-emotion

In your bsconfig.json, include "bs-css" and "bs-css-emotion" in the bs-dependencies.

You can replace bs-css-emotion with bs-css-dom in the above instructions if you prefer to use React styles, or bs-css-fela for a different runtime.

Usage for bs-css-emotion

module Theme = {
  let basePadding = CssJs.px(5)
  let textColor = CssJs.black
}

module Styles = {
  /*
  Open the Css module, so we can access the style properties below without prefixing them with Css.
  You can use either Css or CssJs: Css module is using lists, CssJs is using arrays.
  If you're targeting js and/or using Rescript, prefer CssJs
 */
  open CssJs

  let card = style(. [
    display(flexBox),
    flexDirection(column),
    alignItems(stretch),
    backgroundColor(white),
    boxShadow(Shadow.box(~y=px(3), ~blur=px(5), rgba(0, 0, 0, #num(0.3)))),
    // You can add non-standard and other unsafe style declarations using the `unsafe` function, with strings as the two arguments
    unsafe("-webkit-overflow-scrolling", "touch"),
    // You can place all your theme styles in Theme.re and access as normal module
    padding(Theme.basePadding),
  ])

  let title = style(. [
    fontSize(rem(1.5)),
    lineHeight(#abs(1.25)),
    color(Theme.textColor),
    marginBottom(Theme.basePadding),
  ])

  let actionButton = disabled =>
    style(. [
      background(disabled ? darkgray : white),
      color(black),
      border(px(1), solid, black),
      borderRadius(px(3)),
    ])
}

@react.component
let make = () =>
  <div className=Styles.card>
    <h1 className=Styles.title> {React.string("Hello")} </h1>
    <button className={Styles.actionButton(false)} />
  </div>

Global css

You can define global css rules with global

open CssJs

global(. "body", [margin(px(0))])
global(. "h1, h2, h3", [color(rgb(33, 33, 33))])

Keyframes

Define animation keyframes;

open CssJs

let bounce = keyframes(. [
  (0, [transform(scale(0.1, 0.1)), opacity(0.0)]),
  (60, [transform(scale(1.2, 1.2)), opacity(1.0)]),
  (100, [transform(scale(1.0, 1.0)), opacity(1.0)]),
])

let styles = style(. [
  animationName(bounce),
  animationDuration(2000),
  width(px(50)),
  height(px(50)),
  backgroundColor(rgb(255, 0, 0)),
])

// ...
<div className=styles> {React.string("bounce!")} </div>

Css attributes

For some CSS parameters (like setting padding on an input field), one needs to use CSS attributes like so:

input[type="text"] {
   padding:20px;
}

The selector function can be used:

open CssJs
let styles = style(. [selector("input[type='text']", [padding(px(20))])])

Merging styles

You should avoid trying to merge styles in the same list of rules or by concatinating lists. A list of rules is converted into a JS object before being passed to Emotion where every property becomes a key in the object. This means you lose any earlier rule if you have another rule with the same property later in the list. This is especially noticable when writing sub-selectors and media queries

Trying to merge styles by just using concatenation can result in unexpected results.

This example:

open CssJs

let base = [
  padding(px(0)),
  fontSize(px(1))
]
let overrides = [
  padding(px(20)),
  fontSize(px(24)),
  color(blue)
]
let media1 = [
  media("(max-width: 768px)", [
    padding(px(10))
  ])
]
let media2 = [
  media("(max-width: 768px)", [
    fontSize(px(16)),
    color(red)
  ])
]
let mergedStyles = style(. Belt.Array.concatMany([base, overrides, media1, media2]))

generates the following:

.css-1nuk4bg {
  padding: 20px;
  font-size: 24px;
  color: #0000ff;
}
@media (max-width: 768px) {
  .css-1nuk4bg {
    font-size: 16px;
    color: #ff0000;
  }
}

As you can see both properties from base are overwritten (as opposed to overridden in css) and the media query in media1 is also lost because the media query from media2 overwrites it.

The merge method

merge safely merges styles by name. Uses Emotion’s cx method.

open CssJs

let mergedStyles = merge(. [
  style(. [padding(px(0)), fontSize(px(1))]),
  style(. [padding(px(20)), fontSize(px(24)), color(blue)]),
  style(. [media("(max-width: 768px)", [padding(px(10))])]),
  style(. [media("(max-width: 768px)", [fontSize(px(16)), color(red)])]),
])

Generates the following:

.css-q0lkhz {
  padding: 0px;
  font-size: 1px;
  padding: 20px;
  font-size: 24px;
  color: #0000ff;
}
@media (max-width: 768px) {
  .css-q0lkhz {
    padding: 10px;
  }
}
@media (max-width: 768px) {
  .css-q0lkhz {
    font-size: 16px;
    color: #ff0000;
  }
}

Nothing is lost and everything ends up in the final stylesheet where normal overrides apply.

Usage for bs-css-fela

First you need to use a provider in your Jsx:

let renderer = createRenderer()

switch ReactDOM.querySelector("#app") {
| None => ()
| Some(dom) =>
  ReactDOM.render(<CssReact.RendererProvider renderer> ... </CssReact.RendererProvider>, dom)
}

Then, you need to use the useFela hook in your Jsx:

module Styles = {
  /*
   Open the Css module, so we can access the style properties below without prefixing them with Css.
   You can use either Css or CssJs: Css module is using lists, CssJs is using arrays.
   If you're targeting js and/or using Rescript, prefer CssJs
  */
  open CssJs

  let card = style(. [
    display(flexBox),
    flexDirection(column),
    alignItems(stretch),
    backgroundColor(white),
    boxShadow(Shadow.box(~y=px(3), ~blur=px(5), rgba(0, 0, 0, #num(0.3)))),
    // You can add non-standard and other unsafe style declarations using the `unsafe` function, with strings as the two arguments
    unsafe("-webkit-overflow-scrolling", "touch"),
    // You can place all your theme styles in Theme.re and access as normal Reason module
    padding(Theme.basePadding),
  ])

  let title = style(. [
    fontSize(rem(1.5)),
    lineHeight(#abs(1.25)),
    color(Theme.textColor),
    marginBottom(Theme.basePadding),
  ])

  let actionButton = disabled =>
    style(. [
      background(disabled ? darkgray : white),
      color(black),
      border(px(1), solid, black),
      borderRadius(px(3)),
    ])
}

@react.component
let make = () => {
  let {css, _} = CssReact.useFela()

  <div className={css(. Styles.card)}>
    <h1 className={css(. Styles.title)}> {React.string("Hello")} </h1>
    <button className={css(. Styles.actionButton(false))} />
  </div>
}

Global css

You can define global css rules with global

open CssJs
let renderer = createRenderer()

renderGlobal(. renderer, "body", [margin(px(0))])
renderGlobal(. renderer, "h1, h2, h3", [color(rgb(33, 33, 33))])

Usage for bs-css-dom

Use style instead of classname, for example:

module Styles = {
  // Open the Css module, so we can access the style properties below without prefixing them with Css
  open CssJs

  let card = style(. [
    display(flexBox),
    flexDirection(column),
    alignItems(stretch),
    backgroundColor(white),
    boxShadow(Shadow.box(~y=px(3), ~blur=px(5), rgba(0, 0, 0, #num(0.3)))),
    // You can add non-standard and other unsafe style declarations using the `unsafe` function, with strings as the two arguments
    unsafe("-webkit-overflow-scrolling", "touch"),
    // You can place all your theme styles in Theme.re and access as normal Reason module
    padding(Theme.basePadding),
  ])

  let title = style(. [fontSize(rem(1.5)), color(Theme.textColor), marginBottom(Theme.basePadding)])

  let actionButton = disabled =>
    style(. [
      background(disabled ? darkgray : white),
      color(black),
      border(px(1), solid, black),
      borderRadius(px(3)),
    ])
}

@react.component
let make = () =>
  <div style=Styles.card>
    <h1 style=Styles.title> {React.string("Hello")} </h1>
    <button style={Styles.actionButton(false)} />
  </div>

Where is the documentation?

You can check out Css_Js_Core.rei and Css_Legacy_Core.rei.

Thanks

Thanks to emotion which is doing all the heavy lifting.

Thanks to bs-glamor which this repo was forked from.

Thanks to elm-css for dsl design inspiration.

bs-css's People

Contributors

giraud avatar cullophid avatar poeschko avatar davesnx avatar wegry avatar glennsl avatar dependabot-preview[bot] avatar lucasweng avatar mobily avatar rusty-key avatar jakubmarkiewicz avatar dependabot[bot] avatar ruslangrigoryev avatar fhammerschmidt avatar logason avatar tomis avatar steinararnason avatar bloodyowl avatar knowbody avatar jeong-sik avatar bobzhang avatar erykpiast avatar cdebotton avatar chenglou avatar baldurh avatar amirmoh10 avatar arnarthor avatar arecvlohe avatar bwarni avatar happylinks avatar

Stargazers

Antoine Karcher avatar Niranjan Anandkumar avatar João Paulo Hotequil avatar rizzrark avatar Ananda Umamil avatar Alex Strand avatar min avatar Asher avatar 伊欧 avatar  avatar pm bhatiya avatar Tamim Arafat avatar  avatar Andreas Thoelke avatar Emile Rolley ⏚ avatar M Arie Syukron avatar  avatar Silas Medeiros avatar  avatar liuyanghejerry avatar Bruno Romero avatar YONGJAE LEE(이용재) avatar  avatar  avatar  avatar Ben avatar changhui lee avatar  avatar Travis Kaufman avatar MaxSvargal avatar Charleno Pires avatar Eugene avatar  avatar Kcicnnhi avatar Pedro Lisboa avatar  avatar Adan Alvarado avatar Benjamin Schandera avatar Victor Landim avatar Christian Rotzoll avatar Jeff avatar Augusto Calaca avatar Matheus Henrique avatar andrew ananta avatar Zack Corr avatar  avatar Masanori Ogino avatar Hiroki Noda avatar Hisayuki Mima avatar App Service avatar Shin, SJ avatar gabrielle oliveira avatar Lorenzo Palmes avatar Aleksander Valle Grunnvoll avatar Fahad Hossain avatar Brad Dunn avatar Gcode avatar Simone avatar Fabien Bourgeois avatar Carlos A avatar José Eduardo Monteiro avatar Tomé Vardasca avatar Pierre Bertet avatar Aycan Doruklu avatar Ian Klein avatar Hao "Johnny" Jiang avatar Mateusz Przywarty avatar isensei avatar Dima Paloskin avatar Luke Frisken avatar Max Sh. avatar Amirali Esmaeili avatar  avatar Leon Feng avatar Joel Jeddeloh avatar Russell avatar  avatar Vinicius Sales avatar Tadanobu Ohata avatar  avatar Pomin Wu avatar Chance Snow avatar Davi Spindola avatar Adhy Wiranata P avatar Ilya Suzdalnitskiy avatar yuxiaoqing avatar Oskar avatar Connor Skees avatar Pedro Valentim avatar Artyom avatar Bernardo Gurgel avatar  avatar Jeremy Bae avatar Shine Sutheeravet avatar Leonard Souza avatar @musurca avatar Nicholas Van Doorn avatar Dmitriy Kovalenko avatar Machou avatar Peter Piekarczyk avatar

Watchers

Haukur Kristinsson avatar  avatar  avatar Pedro Lisboa avatar  avatar

bs-css's Issues

Fraction unit (fr) for grid

I can't use fr unit for gridTemplateColumns/gridTemplateRows.
e.g.

gridTemplateColumns([fr(1), fr(2)]),   // <-- error

Error output

This has type:
    [> `fr(float) ]
  But somewhere wanted:
    [ `auto
    | `calc([ `add | `sub ], Css.length, Css.length)
    | `ch(float)
    | `cm(float)
    | `em(float)
    | `ex(float)
    | `mm(float)
    | `percent(float)
    | `pt(int)
    | `px(int)
    | `rem(float)
    | `vh(float)
    | `vmax(float)
    | `vmin(float)
    | `vw(float)
    | `zero ]
    at <anonymous>nt type does not allow tag(s) `fr

Is fr maybe missing from function string_of_dimension?

DSL macro expansion

Hi guys, thanks for creating such a beginner-friendly CSS tool for Reason beginners like myself.

Are there any plans to improve the verbosity of the DSL through something like a macro expansion extension to Reason? https://github.com/cyrus-/relit

To a beginner, it feels like the holy grail of css-in-Reason is to write in plain (or nearly plain) css. Something like:

module Styles = {
  open Css_notation;

  let card = $css `
    display: flexbox;
    flex-direction: column;
    align-items: stretch;
    background-color: white;
    box-shadow: 3px 5px rgba(0, 0, 0, 0.3);
    padding: ${Theme.basePadding};
  `;
};

I'm not sure what's technologically feasible here, so I'd be really interested to know whether this is worth looking into or just a total dead end. Thanks.

Losing styles when merging nested selectors

We're building components by composing different styles. However, if we use the same sub-selector in two places, only one will be compiled. Eg:

let buttonStyles = [
  padding(px(5)),
  media("(min-width: 768px)", [
    padding(px(10)),
  ]),
]

let typographyStyles = [
  fontSize(px(14)),
  media("(min-width: 768px)", [
    fontSize(px(16)),
  ]),
]

let Button = style(merge([
  buttonStyles,
  typographyStyles,
]))

In this case, only the buttonStyles media query works. This happens for any sub-selector, eg &:hover, &:focus, [dir=rtl] &.

Looking through the implementation, it seems like this issue is quite baked in, with how bs-css abstracts glamor objects as reason lists.

The example above may be arbitrary, but IMO a big draw for CSS-in-JS, and glamor especially, is composability. This bug and abstraction break it quite severely.

Rule list order should be preserved after Emotion.makeDict

On current master, the order of properties is backwards from how it ultimately ends up in CSS.

  Css.[
    background(red),
    borderBottom(px(5), solid, black),
    width(px(50)),
    height(px(50)),
    margin(px(10)),
];
margin: 10px;
height: 50px;
width: 50px;
border-bottom: 5px solid #000000;
background: #FF0000;

Presumably this is because here we reverse the rule list before passing it into the JS object creator.

I noticed this because of the interaction between properties like border and border left. Order matters in this case.

Changing the order to match existing CSS was part of #92, but was stripped out with the merge changes I'd added. I've been using my own fork because of this, but I'd like to have it patched upstream. What better than a major release?

boxSizing needs cascading values

Of course a lot of other properties need this as well, but right now I need it for boxSizing! 😄
I’m writing some base css for my app and want this rule:
*, *::before, *::after { box-sizing: inherit; }

I want to be able to do it cleanly like so:
global("*, *::before, *::after", [boxSizing(`inherit)])

But right now I have to do:
insertRule("*, *::before, *::after { box-sizing: inherit; }")

Support for nesting children?

I've looked through the code and don't see this option, but I may have missed something. I think it would be pretty useful to be able to target any html tag ("a", "button", ".some-classname", etc) within the component using bs-css.

backgroundPosition(align, [align])

The current implementation of backgroundPosition looks like this:

let backgroundPosition = (x, y) =>
  d("backgroundPosition", string_of_length(x) ++ " " ++ string_of_length(y));

Unfortunately, this prevents things like backgroundPosition: center;. Is it possible to support string_of_align() here as well?

Possible to use multiple background layers?

I'm trying to an image url on top of a linear gradient as a background, but it seems like the type system is limiting me to one or the other. Is there a way to use multiple background layers with bs-css?

E.g. generate something like this:

background: url("../img/bg-pattern.png"), linear-gradient(to left, #7b4397, #dc2430);

Update README with v8 changes

Changes:

  • Emotion is now the backing library. If you're using tools like browserslist, you should see less unnecessary autoprefixing. #94
  • merge no longer merges a list(list(rule)) and instead only merges list(string) into a string. If you need the old list behavior, List.concat has you covered. #97
  • Rule lists are applied in the order they are provided instead of reversed like they were in v7. #96
  • Font weight now requires the use of the num polymorphic variant instead of just ints. #98

inherit/unset values

I'm trying to add these values to the different properties, but it's a very long work to add it to every css prop.

  1. I'd like to know what are the most important properties to start with, and focus on these first.
  2. I also have a problem when using inherit because it's a keyword. I'm using inherit_ right now, but I'd like to know if someone has a better suggestion.

thanks.

What is the way of generating (min-width: 960px) with bs-css

Hi folks,

I was using Css.media(string, Css.rule) and wanted to compute (min-width: 960px) with bs-css.

Initially my thought was that was possible because internally we have string_of_length, we also have minWidth.
After trying a bit I did not found a way to do that.

Thank you for wrapping Glamor.

This is my first css in js (reason actually) experience.

Expose `string_of` functions

It might be a good idea to expose the string_of_length and similar functions as sometimes they are needed. Normally when you're down to the definition level and you have constants defined elsewhere as length. At the moment they are hidden from the interface and I literally grabbed them from the source. But this way if bs-css changes my forked functions will get out of sync.
These functions are also useful when you have a theme and you need to add some inline styles.
I would just alias the functions within a Utils submodule. I can make a pull request if you think it's a good idea

SSR?

With this support server-side rendering?

Looking for (co)maintainers

First of all Thanks to everyone who are using and or contributing to bs-css!
I am sorry that it often takes a while before I get to answer issues, or review pull requests.
Between Baby and startup I dont have much time on my hands.

If any of you are using bs-css and would like to help maintain the project (answer issues, review/merge PR's) I would really appreciate it.
I will ofc still be here as much as i can.

lineHeight unit

What unit to use for lineHeight? I'm trying to pass 1.3, but px requires whole numbers, what unit should be used? Not even sure if it should have been px as in css we don't add px to the end of the value.

Support elm-css's property function

elm-css supports letting users embed any CSS declaration with an api like such:

property "grid-template-areas" """
  "header header header"
  "sidebar content content"
  "footer footer footer"
"""

property "grid-area" "content"

It's a nice escape hatch for when properties or values don't have type definitions so users can sacrifice a little type-safety to not be blocked by lack of existing definitions in the library.

I looked at the source and it seems the d function already does this, but isn't exposed in Css.rei--I could maybe create a PR, but I'm not quite sure how to test it all.

The elm-css implementation:

https://github.com/rtfeldman/elm-css/blob/8d0e7c1a1affb500f014c010efd71b91356be9ac/src/Css.elm#L7243-L7246

CSS grid not defined

Hello!

grid is a valid value for display, but it does not seem to be defined.

Would you accept a PR adding this?

Thanks,
Louis

Add repeat to gridLength

Maybe an approach similar to this would be helpful. But I'm bogged down in polymorphic variants trying to patch this up myself.

 The implementation src/Css.mlast
       does not match the interface src/css.cmi:
       Values do not match:
         let gridTemplateColumns:
           list(([< `auto
                  | `calc([< `add | `sub ],
                         ([< `calc([< `add | `sub ], 'b, 'b) &
                               ([< `add | `sub ], 'b, 'b)
                           | `ch(float)
                           | `cm(float)
                           | `em(float)
                           | `ex(float)
                           | `mm(float)
                           | `percent(float)
                           | `pt(int)
                           | `px(int)
                           | `pxFloat(float)
                           | `rem(float)
                           | `vh(float)
                           | `vmax(float)
                           | `vmin(float)
                           | `vw(float)
                           | `zero ]
                          as 'b),
                         ([< `calc([< `add | `sub ], 'c, 'c) &
                               ([< `add | `sub ], 'c, 'c)
                           | `ch(float)
                           | `cm(float)
                           | `em(float)
                           | `ex(float)
                           | `mm(float)
                           | `percent(float)
                           | `pt(int)
                           | `px(int)
                           | `pxFloat(float)
                           | `rem(float)
                           | `vh(float)
                           | `vmax(float)
                           | `vmin(float)
                           | `vw(float)
                           | `zero ]
                          as 'c))
                  | `ch(float)
                  | `cm(float)
                  | `em(float)
                  | `ex(float)
                  | `fr(float)
                  | `maxContent
                  | `minContent
                  | `mm(float)
                  | `percent(float)
                  | `pt(int)
                  | `px(int)
                  | `pxFloat(float)
                  | `rem(float)
                  | `repeat([< `autoFill | `autoFit | `n(int) ], 'a)
                  | `vh(float)
                  | `vmax(float)
                  | `vmin(float)
                  | `vw(float)
                  | `zero ]
                 as 'a)) =>
           [> `declaration(string, string) ]
       is not included in
         let gridTemplateColumns:
           list([ `auto
                | `calc([ `add | `sub ], length, length)
                | `ch(float)
                | `cm(float)
                | `em(float)
                | `ex(float)
                | `fr(float)
                | `maxContent
                | `minContent
                | `mm(float)
                | `percent(float)
                | `pt(int)
                | `px(int)
                | `pxFloat(float)
                | `rem(float)
                | `repeat(repeatValue, gridLength)
                | `vh(float)
                | `vmax(float)
                | `vmin(float)
                | `vw(float)
                | `zero ]) =>
           rule

from

diff --git a/src/Css.re b/src/Css.re
index 1963f48..e3e4067 100644
--- a/src/Css.re
+++ b/src/Css.re
@@ -3,8 +3,7 @@ include Css_Colors;
 module Emotion = {
   type css = string;
   [@bs.module "emotion"] external _make: Js.Json.t => css = "css";
-  [@bs.module "emotion"]
-  external injectGlobal: Js.Json.t => unit = "";
+  [@bs.module "emotion"] external injectGlobal: Js.Json.t => unit = "";
   [@bs.module "emotion"]
   external rawInjectGlobal: string => unit = "injectGlobal";
   [@bs.module "emotion"]
@@ -291,9 +290,12 @@ type selector = [ | `selector(string, list(rule))];
 let empty = [];
 
 let merge = List.concat;
-let global = (selector, rules: list(rule)) => {
-  Emotion.injectGlobal([(selector, Emotion.makeDict(rules))]->Js.Dict.fromList->Js.Json.object_);
-}
+let global = (selector, rules: list(rule)) =>
+  Emotion.injectGlobal(
+    [(selector, Emotion.makeDict(rules))]
+    ->Js.Dict.fromList
+    ->Js.Json.object_,
+  );
 
 let insertRule = raw => Emotion.rawInjectGlobal(raw);
 
@@ -391,7 +393,21 @@ type length = [
   | `zero
 ];
 
-type gridLength = [ length | `fr(float) | `minContent | `maxContent];
+type repeatValue = [ | `autoFill | `autoFit | `n(int)];
+
+let repeatValueToJs =
+  fun
+  | `autoFill => "auto-fill"
+  | `autoFit => "auto-fit"
+  | `n(x) => x->string_of_int;
+
+type gridLength = [
+  length
+  | `fr(float)
+  | `minContent
+  | `maxContent
+  | `repeat(repeatValue, gridLength)
+];
 
 let string_of_length_cascading =
   fun
@@ -757,7 +773,7 @@ let paddingRight = x => d("paddingRight", string_of_length(x));
 let paddingTop = x => d("paddingTop", string_of_length(x));
 let paddingBottom = x => d("paddingBottom", string_of_length(x));
 
-let string_of_dimension =
+let rec string_of_dimension =
   fun
   | `auto => "auto"
   | `calc(`add, a, b) =>
@@ -781,7 +797,9 @@ let string_of_dimension =
   | `fr(x) => string_of_float(x) ++ "fr"
   | `zero => "0"
   | `minContent => "min-content"
-  | `maxContent => "max-content";
+  | `maxContent => "max-content"
+  | `repeat(n, x) =>
+    "repeat(" ++ repeatValueToJs(n) ++ ", " ++ string_of_dimension(x) ++ ")";
 
 let width = x => d("width", string_of_dimension(x));
 let maxWidth = x => d("maxWidth", string_of_dimension(x));
@@ -815,8 +833,8 @@ let gridAutoFlow = direction =>
 let string_of_dimensions = dimensions =>
   dimensions |> List.map(string_of_dimension) |> String.concat(" ");
 
-let gridTemplateColumns = dimensions =>
-  d("gridTemplateColumns", string_of_dimensions(dimensions));
+let gridTemplateColumns =
+  dimensions => d("gridTemplateColumns", string_of_dimensions(dimensions));
 
 let gridTemplateRows = dimensions =>
   d("gridTemplateRows", string_of_dimensions(dimensions));

Need to be able to set pixels as floating point values

We’re creating a fluid grid system which requires floating point pixels since spacing and sizes are calculated values. What would be the best option? Changing `px to accept float would be a breaking change. So maybe adding `pxFloat?

Can't use it as a library

[email protected] is dependent on bs-platform@^3.0.0 but it is published with js files compiled with the 2.2.2 version.
The published js files are still using Pervasives.string_of_int that doesn't exist anymore in bs 3.3.0.
This is breaking my code when I use it in a transitive dependency.

lineheight unit

Today, lineHeight has the type float => rule
But lineHeight is supposed to be able to receive pixel, rem, em etc., in addition to the float.

It would be better if lineHeight had the following type: [ lenght | `abs(float) ]

Media queries

Can't seem to get them working, are there any examples of how to use it?

Compose

Is there a good way to compose multiple styles?

Selectors resolving to [object Object]

v6.1.0, though I believe this affected v5 as well.

I'm having trouble using selectors:

  let table = style([
    selector("span", [
      margin(unit(1.0))
    ])
  ]);
<div className=Styles.table>

This results in:

screen shot 2018-02-01 at 4 05 42 pm

Any ideas on how I can troubleshoot?

Thanks for a great project!

fontSize does not support absolute and relative sizes

According to the formal syntax for font-size, it's supposed to support absolute and relative sizes like large, larger, etc.

Would you agree that bs-css needs types for absolute and relative sizes so that they can be supported by fontSize, or have you already thought through this?

I think typed css in reason has lots of potential, and that this project would flourish with more contributors and users. I'm still learning the language but I'll try and dedicate time to this.

Wrong value for minWidth/maxWidth

We have

let minWidth: [ length | `auto] => rule;
let maxWidth: [ length | `auto] => rule;

"auto" is a valid keyword value for "min-width", but invalid or "max-width".
"none" is a missing keyword value for "max-width".
So we should have

let minWidth: [ length | `auto] => rule;
let maxWidth: [ length | `none] => rule;

Support for `inherit` value

Inherit is a useful value with properties like borderRadius, borderColor, fontSize, etc...

If there is a way to do this already, would you mind pointing me in the right direction?

Colon-like infix operator

I know most people don't like operators, but since it's DSL, I wanted to share idea.
We have this fat and ugly operator ( @@ ), that is perfect for reducing number of parentheses on function call, so instead of this:

let card = style([
  display(flexBox),
  flexDirection(column), 
  alignItems(stretch),
  backgroundColor(white),
]); 

we get this:

let card = style([
  display @@ flexBox,
  flexDirection @@ column, 
  alignItems @@ stretch,
  backgroundColor @@ white,
]); 

Which looks a bit nicer, but far from perfect.
We can define a better operator, that behaves exactly like ( @@ ), but looks more CSS-like and lightweight:

let card = style([
  display^: flexBox,
  flexDirection^: column, 
  alignItems^: stretch,
  backgroundColor^: white,
]); 

We can define it like this:

let ( ^: ) = ( @@ );

Or like this:

external ( ^: ) : ('a => 'b) => 'a => 'b = "%apply";

polymorphic variant in rei

should we use [< | `some_tag | ... ] instead of [ | `some_tag | ... ] whenever possible?

https://github.com/SentiaAnalytics/bs-css/blob/master/src/Css.rei#L499 like here,
I've got a [<| `color | `url(string)], but when I pass it into background, it could not compiles:

  This has type:
    [< `currentColor
     | `hex(string)
     | `hsl(int, int, int)
     | `hsla(int, int, int, int)
     | `rgb(int, int, int)
     | `rgba(int, int, int, int)
     | `transparent
     | `url(string)
     > `hex ]
  But somewhere wanted:
    [ `currentColor
    | `hex(string)
    | `hsl(int, int, int)
    | `hsla(int, int, int, float)
    | `linearGradient(Css.angle, list((int, Css.color)))
    | `none
    | `radialGradient(list((int, Css.color)))
    | `repeatingLinearGradient(Css.angle, list((int, Css.color)))
    | `repeatingRadialGradient(list((int, Css.color)))
    | `rgb(int, int, int)
    | `rgba(int, int, int, float)
    | `transparent
    | `url(string) ]

CSS grid support

Hey, I'm going to submit a PR with some CSS grid values that are currently not supported.

One minor problem that needs to be decided on before I do so is if it should be a breaking change or not, but as a consequence not give compile time errors for the difference between flex and grid.

The problem is that both Flex and CSS grid use the align-items key to tell how to align items within a grid or flex container.

For flex box the valid values for this property are:

normal; 
stretch; 
center;
start;
end;
flex-start;
flex-end
self-start; 
self-end; 
baseline; 
first baseline; 
last baseline;
safe center; 
unsafe center; 
inherit; 
initial; 
unset;

But for grid they are just a subset of these

auto
normal
start
end
center
stretch
baseline
first baseline
last baseline

Should grid just not have the same compile time guarantees and just add the missing fields for that. Or should the user specify that its using Flex by doing something like alignItems(`flex(`flexEnd))?

I'm leaning towards non compile guarantees since the CSS grid properties are just a subset of flex (apart from auto), but at the same time it breaks the contract of typesafe CSS in the library.

All thoughts greatly appreciated. I'm really torn here 😄

Should we add generated js files to .gitignore?

@giraud what do you think about removing these files from git?

The benefits, I think, are

  • Smaller diffs and PRs
  • Easier time merging

But I can see that adding the compiled JS means bs-css's users can

  • try the example/index page without building
  • get a feel for how heavy the library code is without node_module dependencies

Proposal: A better type system for values

I've been toying a bit with the type system to get value types to compose better. This will make declarations more concise, but will also solve some issues that I don't think can be solved with the current approach. AND I actually think the errors this produces is better.

The problem

CSS is typed as lots of nested unions. These unions are currently modeled in one of two ways:

  1. As string-backed abstract types, e.g. cssunit, color. These are efficient and concise, but do not nest and need to be uniquely named.
  2. As ordinary vairants, e.g. background. These nest and do not need to be uniquely named, but are inefficient and verbose.

A common problem for both approaches is that they need to enumerate every single possible value of that type, which for 1. is practically impossible due to the need for unique names, and for 2. makes the implementation very verbose and repetitive.

More specifically, the practical problem is with implementing these CSS features:

  1. The universal values, inherit, initial and unset, which should be appllicable to every css property.
  2. Nested unions can become pretty verbose, either when used or when implemented (or a bit of both, as it is now). For example, a DRY implementation of background would have usage look like background(Image(Url(url("http://...")))). Alternatively it can be "flattened" to justbackground(Url("http://...")), but at the cost of needing to explicitly enumerate and convert all possible values in the implementation of each property.

Proposal

By using a phantom polymorphic variant type, we can model this more precisely.

This is what it could look like in use:

Css.(style([
  background(white),
  background(url("http://...")),
  background(linearGradient(...)),
  color(unset),
]));

Compared to today:

Css.(style([
  background(Color(white)),
  background(Image("http://...")),

  /* gradients currently aren't valid images, so including them would require adding another
   * level (which would also make the url declaration above more verbose), or alternatively
   * another variant constructor if flattened
   */
  background(Image(Gradient(linearGradient(...)))),

  /* not implemented today, but could be hacked together to look like this and be somewhat
   * type-safe
   */
  unset(color),
]));

Error would look like this:
2017-12-18-120530_312x148_scrot

Implementation

Time for magic. Let's first look at the interface:

  /* The type of all css values */
  type value('a);
  
  /* [`color] is the type of a polymorphic variant with a single constructor, `color */
  type color = value([`color]);
  let white: color;
  
  /* The aliases aren't strictly necessary, but gives nicer error messages */
  type url = value([`url]);
  let url: string => url;
  
  /* [< `color ...] means we'll accept any polymorphic variant type containing a subset of
   * these constructors. So value([`color]) is good, as would value([`color | `url]) be,
   * but not value([`color | `length])
   */
  type background('a) = value([< `color | `url | `gradient] as 'a);
  let background: background('a) => rule;

To implement it, we'll define a couple of helpers to avoid blind casting magic and ensure what we put in and get out are actually strings, as expected:

  external makeValue : string => value(_) = "%identity";
  external getValue : value(_) => string = "%identity";

We'll also represent the rule as just a tuple for simplicity:

type rule = (string, string);

And then the meat of the implementation:

  type value('a);
  
  type color = value([`color]);
  let white = makeValue("white");
  
  type url = value([`url]);  
  let url = url => makeValue({j|url($url)|j});
  
  type background('a) = value([< `color | `url | `gradient] as 'a);
  let background = value => ("background", getValue(value));

A playground with this code, as well as an example of how to define a universal value

Conclusion

More testing is of course needed to ensure there aren't any problems when things get more complex, or if CSS does something super weird that doesn't fit this model either. But I think this approach is worth exploring further. If everything works out it's looking to be more concise, flexible, efficient and give better errors, but at the cost of making the implementation a bit harder to understand.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.