Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compose styles #128

Closed
gaku-sei opened this issue Jun 11, 2019 · 5 comments
Closed

Compose styles #128

gaku-sei opened this issue Jun 11, 2019 · 5 comments
Assignees
Labels
language Language feature question Further information is requested

Comments

@gaku-sei
Copy link

gaku-sei commented Jun 11, 2019

First of all, let me tell you that I really like this project, and hope it will reach v1 and be supported by a bigger community soon!

I have a question regarding the styles. Apparently, there is no way to use more than one style for an HTML node (<div::foo::bar is not allowed), and there is also no way to "inject" several css rules in a row (style my-style { width: 20px; {moreStyle} } throws a parse error), which leaves us with this "computed values" pattern:

style my-style {
  color: {color}
}

get color : String { ... }

Which is, in my humble opinion, pretty nice. Except when you need more than one or two computed values! The code gets longer and longer, and might become quite unsafe as well.

I tried to return a record, but it's still quite cumbersome, since anonymous records are apparently not supported in Mint, and therefore requires you to declare the record type additionally to the returned value.

So my question is; is there a better way to handle this use case? If not, is anything planned in the future?

The roadmap says there will be some type checking for the css, meaning there will probably be some Css or Style type one day, making this: get computedStyle : Css { ... } possible.

@gdotdesign
Copy link
Member

Thank you for opening this issue!

A little inside on how things work in Mint:
CSS declarations in Mint can be in three forms:

  • static background: red; - these are compiled as is into a selector
  • semi-dynamic background: {some expression}; - these are compiled to use CSS Variables background: var(--a);
  • dynamic - currently, in the daily build you can set the style attribute of an element to either a String or a Map(String, String) like this:
    <div::style style={Map.set("background", "red", Map.empty())}></div>
    
    and they are applied to the style attribute of the element.

The combining styles issue came up before as well: #99 Personally I had only a few times that I felt that the language needs some way of combining styles however reading the description I had an idea on how it can be solved property.

What do you think about something like this:

style base {
  background: red;
  color: white;

  if (loading) {
    background: white;
    color: blue;
  }
}

Basically if statements would be allowed in style blocks (with or without an else branch) and can only contain CSS declarations. This would reduce the boilerplate as well as make the code more readable, and I think it can be implemented.

Any property in any ifs would be semi-dynamic and the order would be the priority.

As for the type checking of the CSS: since most declarations are static and checked at compile time, there will not be any actual type for it, the idea here is to check the values of properties against the CSS spec and throw compile time errors.

@gdotdesign gdotdesign added language Language feature question Further information is requested labels Jun 11, 2019
@manveru
Copy link
Contributor

manveru commented Jun 11, 2019

While I really like this, I often looked for a concise way to write this:

record Item {
  active : Bool
}

component Main {
  style item-active {
    background: #f00;
    width: 100%;
    height: 1em;
  }

  style item-inactive {
    background: #000;
    width: 100%;
    height: 1em;
  }

  style container {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
  }

  get items : Array(Item) {
    [
      { active = true },
      { active = false }
    ]
  }

  fun render : Html {
    <div::container>
      for (item of items) {
        if (item.active) {
          <div::item-active/>
        } else {
          <div::item-inactive/>
        }
      }
    </div>
  }
}

Given your proposal I'd instead write it like this:

component Ui.Item {
  property item : Item = {active = false}

  style item {
    width: 100%;
    height: 1em;
    if (item.active) {
      background: #f00;
    } else {
      background: #000;
    }
  }
  
  render : Html {
    <div::item><{ children }></div>
  }
}

But what really would make this simple would be allowing arguments to style. So it'd look like this:

component Main {
  style item(active : Bool) {
    if (active) {
      background: #f00;
    } else {
      background: #000;
    }
    width: 100%;
    height: 1em;
  }

  style container {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
  }

  get items : Array(Item) {
    [
      { active = true },
      { active = false }
    ]
  }

  fun render : Html {
    <div::container>
      for (item of items) {
        <div::item(item.active)/>
      }
    </div>
  }
}

Maybe that can be a thing in future (not hoping for feature parity with something like SCSS, but they do have some neat stuff).

@gdotdesign
Copy link
Member

I've never thought about giving parameters to style blocks but it makes sense, I don't know how to implement them yet but I don't think it's impossible.

@gaku-sei
Copy link
Author

I like the style with argument and the conditional inside the style body! The case expression could be nice to have too, for consistency:

style my-style(enum : SomeEnum) {
  color: red;
  {case (enum) { ... }}
}

Also, what I had in mind was a way to actually merge styles:

component X {
  style style-1 {
    color: red;
  }

  style style-2 use style-1 {
    background-color: blue;
  }

  fun render : Html {
    <div>
      <{ if (something) { <div::style-1 /> } else { <div::style-2 /> } }>
    </div>
  }
}

This would take the conditionals outside the style, but should provide enough flexibility to handle most of use cases.

So this:

record Item {
  active : Bool
}

component Main {
  style item-active {
    background: #f00;
    width: 100%;
    height: 1em;
  }

  style item-inactive {
    background: #000;
    width: 100%;
    height: 1em;
  }

  style container {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
  }

  get items : Array(Item) {
    [
      { active = true },
      { active = false }
    ]
  }

  fun render : Html {
    <div::container>
      for (item of items) {
        if (item.active) {
          <div::item-active/>
        } else {
          <div::item-inactive/>
        }
      }
    </div>
  }
}

Would be:

record Item {
  active : Bool
}

component Main {
  style item-active {
    background: #f00;
    width: 100%;
    height: 1em;
  }

  style item-inactive use item-active {
    background: #000;
  }

  style container {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
  }

  get items : Array(Item) {
    [
      { active = true },
      { active = false }
    ]
  }

  fun render : Html {
    <div::container>
      for (item of items) {
        if (item.active) {
          <div::item-active/>
        } else {
          <div::item-inactive/>
        }
      }
    </div>
  }
}

No need to copy/paste the style, and the style blocks remain simple.

That being said, we might need something more powerful one day, like the ability to return a style object usable anywhere:

fun buildStyle(color : String) : Style {
  { color = color }
}

@gdotdesign
Copy link
Member

This is no resolved with the #140 PR 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
language Language feature question Further information is requested
Development

No branches or pull requests

3 participants