Styled Components Possible?

reasonreact

#1

We use styled components at my work. The reason we like it above other solutions (at least speaking for myself) is:

You don’t need to provide class names, it just generates a component

  const Button = styled.a`
      width: 11rem;
      background: transparent;
      color: white;
  `
 // later

 const myComponent = () => (<div><Button /></div>);

Also, you can make it dynamic very easily

  const Button = styled.a`
      width: 11rem;
      background: ${{color} => color ? color : 'transperent' };
      color: white;
  `

This desugars to:

  const Button = styled.a([
       "width: 11rem; background:",
      "; color: white;"
    ], 
    ({color}) => color ? color : 'transperent' 
 )

Also, you can reference other components like so:

  const Link = styled.a`
    background: papayawhip;
    color: palevioletred;
  `;

  const Icon = styled.svg`
    ${Link}:hover & {
      fill: rebeccapurple;
    }
  `;

This seems like it’ll be really hard to type, I’m not sure if I can use Functors because components are usually modules in ReasonReact. I’m just wondering if anyone has any other ideas on how to make this kind of API in a sane way in Reasons’ type system.

Also, I’m assuming there’s not really tagged template literals in Reason as well…


#2

bss-css might interest you.
It is binding for glamor (not styled-component, but looking like)


#3

This is pretty darn cool. I think I might be able to convince people to use it, however when you introduce a DSL (instead of using CSS pretty directly as StyledComponents does) it can make it harder for people to adopt. The other thing I dislike is that it doesn’t create the component for you. There’s a lot less visual noise when you can do <Button> rather than <div className={Styles.button} />.

I guess to have more directed question, Is there way to generate ReasonReact components using a function call?


#4

The benefit of DSL is that statically typed.

Yes it possible to generate a ReasonReact components using bs-css
But I don’t recommend it, because reason, there is no property spread, and it might be verbose or not safe.

Example:

module CreateComponent = (Config: {
  let tag: string;
  let style: Css.t;
}) => {
  let component = ReasonReact.statelessComponent("createcomponent");
  let make = (~onClick, ~className="", children) => {
    ...component,
    render: (_) =>
      ReasonReact.createDomElement(
        Config.tag,
        ~props={"className": Css.style(Config.style) ++ " " ++ className, "onClick": onClick},
        children,
      ),
  };
};

module MyButton = CreateComponent({
  	let tag = "button";
	let style = Css.([margin(px(10))]);
});

<MyButton className="extra" />

You should list all available html properties in props of CreateComponent


#5

I’d personally stray away from injecting vanilla CSS in there, since you lose the ability to use the language features and safety to build up your styles.

What you’d want instead is a nicely typed DSL that interprets down to the objects React would use to compile to CSS.

Then CSS becomes an implementation detail and migrating your app to becomes switching that interpreter.


#6

@maarekj Wow that code example is pretty darn awesome. I think there’s a way that we could basically steal the type signature of a div and use that in that functor. That’s sweet.

@ostera: I actually would love to (long term) make it so that I can make a PPX transformation like the graphql one that would basically transform CSS into Reason objects at compile time then (fingers crossed) have statically typed CSS, while typing real css. However even @maarekj’s solution is pretty darn good.


#7

The solution that I proposed is incomplete and presents some cons.
I will detail it tomorrow


#8

Yeah I would be interested in that. I’ve often wondered if making Comonents at the module level is a good idea :thinking:. Maybe the problem is not related to that though.


#9

Here is a better snippet:

module CreateComponent =
       (
         Config: {
           let tag: string;
           type params;
           let defaultParams: params;
           let style: params => list(Css.rule);
         },
       ) => {
  external propsToObj : ReactDOMRe.reactDOMProps => Js.t({..}) = "%identity";
  let mergeProps = (a: ReactDOMRe.reactDOMProps, b: ReactDOMRe.reactDOMProps) =>
    ReactDOMRe.objToDOMProps(Js.Obj.assign(propsToObj(a), propsToObj(b)));
  let component = ReasonReact.statelessComponent("createcomponent");
  let make =
      (
        ~tag=Config.tag,
        ~params=Config.defaultParams,
        ~className="",
        ~props: ReactDOMRe.reactDOMProps=ReactDOMRe.props(),
        children,
      ) => {
    ...component,
    render: (_) =>
      ReasonReact.createDomElement(
        tag,
        ~props=
          propsToObj(
            mergeProps(
              props,
              ReactDOMRe.props(
                ~className=Css.style(Config.style(params)) ++ className,
                (),
              ),
            ),
          ),
        children,
      ),
  };
};

type params = {
  variant: [ | `primary | `secondary],
  size: [ | `sm | `md | `lg],
};

module Button =
  CreateComponent(
    {
      let tag = "button";
      type nonrec params = params;
      let defaultParams = {variant: `primary, size: `md};
      let variantRules =
        fun
        | `primary => [Css.(background(hex("00FF00")))]
        | `secondary => [Css.(background(hex("0000FF")))];
      let sizeRules =
        fun
        | `sm => Css.[margin(px(10))]
        | `md => Css.[margin(px(12))]
        | `lg => Css.[margin(px(15)), padding(px(20))];
      let style = params =>
        Css.merge([sizeRules(params.size), variantRules(params.variant)]);
    },
  );

let primarySm =
  <Button
    params={variant: `primary, size: `sm}
    props=(ReactDOMRe.props(~onClick=event => Js.log(event), ()))
  />;

let secondaryLarge =
  <Button
    params={variant: `secondary, size: `lg}
    props=(ReactDOMRe.props(~onClick=event => Js.log(event), ()))
  />;

let secondaryLargeInput =
  <Button
    tag="input"
    params={variant: `secondary, size: `lg}
    props=(ReactDOMRe.props(~_type="button", ~onClick=event => Js.log(event), ()))
  />;

#10

@justgage How do you feel about the following?

module Button = {
  let button = [%raw
    {|
      styled.a`
        width: 11rem;
        background: transparent;
        color: white; 10px;
        }
      `
    |}
  ];

  [@react.component]
  let make = (~children) => {
    React.createElementVariadic(button, makeProps(~children, ()), [||]);
  };
};

// later

[@react.component]
let make = () => <div> <Button /> </div>;

More realistic examples can be found here: https://github.com/persianturtle/reason-app-shell-starter-kit




It’s still experimental, but I’d love to know your thoughts.


#11

It’s an old post, but I wanted to share it here for further viewers.

We are working on a solution to mimic styled components/emotion in Reason and OCaml:

Feel free to try it and give some feedback since it’s still experimental and will evolve.

Thanks!


#12

One thing that worries me in all those embedded syntax solutions is IDE support. In JS/TS syntax highlighting, validation, and autocompletion for styled-components works, but is palpably slow. In Reason, I’m not sure it’ll work at all any time soon.

Whereas bs-css, being vanilla Caml, autocompletes and validates just fine.


#13

I really like bs-css too!

The only thing I would say is, that it needs a little more work regarding consistency (in naming and labelled vs positional arguments). It just feels a little bit clunky to write at the moment.


#14

Totally, a worth it worry.

As @Ewert pointed, it’s very hard to adjust consistency and 0 learning curve into bs-css, which styled-ppx tries to achieve.

But I’m very aware that there are very well tested extensions for styled-components that works out of the box, which includes autocomplete/syntax/etc, having support for reason shoudn’t be crazy (and we will provide a much better errors, since we understand the CSS).

It might take a bit longer to create the tooling around, but once it’s created it’s best to port a react with styled components app and to write CSS straight.

Thanks for the feedback @hoichi