mdgriffith / style-elements-design-discussion

BSD 3-Clause "New" or "Revised" License
8 stars 0 forks source link

High Level Style Organization #15

Open mdgriffith opened 7 years ago

mdgriffith commented 7 years ago

After some discussion, it's become apparent that addressing high level style organization of styles s pretty important for this library.

Mixins

Mixins are fascinating because they've become standard to css preprocessors. On the one side they allow you to capture commonly grouped style properties. On the flip side, reliance on them leads to an extra level of indirection.

A style class composed of multiple mixins is harder to debug because you have to go to many places to figure out where the bad interaction is happening. Changing a mixin is also challenging because you'll have to verify that the change you're making works with every class that uses that mixin.

The power of mixins relies on the insight of the CSS creator. This library is focused on not relying on developer expertise to create a good stylesheet. We want that baked in, just like Elm bakes in so many best practices.

Option 1: Remove Mixins

This is a real option. However, I believe both the positives and negatives of mixins are valid. This eliminates both, so I'm not sure if it's the way to go.

Option 2: Semantic Mixins

One of the root causes of mixins being a pain is that you have no idea what properties you're bringing in. Whenever you declare a mixin, you can bring in literally any set of css properties, including child selectors.

Semantically grouping style properties provides a potential solution. What if the only mixins that are allowed are based on these groups? Here's an example of a semantic mixin:


primary : Style.Color.Palette
primary =
    Style.Color.palette
        { border = Color.blue
        , text = Color.red
        , background = Color.white
        }

myStyle =
    class MyStyleClass
        [ Font.current
            |> Font.bold
            |> Font.size 20
            |> Font.lineHeight 1
        , Color.mix
            primary
        ]

Why is this more powerful?

The class using the mixin knows exactly the type of properties coming in.

The actual mixin has all the information needed to avoid bad interactions. Color only cares about other colors. This does rely on getting the grouping correctly, which is covered/discussed here.

Merging Smaller Stylesheets

It's fairly common to want a few different small stylesheets instead of one large one.

As long as two stylesheets are based on the same union types, they can be easily merged if the library provides a function like Style.merge which would allow you to embed a stylesheet in another one.

Some sort of check/warning when you forget to embed a stylesheet would be really nice. Not 100% on how that would work though.

Something likeStyle.merge might also create an opportunity for "auto-embedding". I don't know exactly how this would work, but the gist would be to pass styling information along with the Html you're rendering, and then have the root element merge all stylesheets into one. I've played around with this before and the issues I ran into were in implementing something like Html.Keyed and Html.Lazy. However I'm willing to revisit!

Dremora commented 7 years ago

As long as two stylesheets are based on the same union types, they can be easily merged

I would expect each stylesheet to come with its own union type. When I'm working on a module, I only want to work with styles related to it directly. If I'm defining 10 classes in a module, but there's 50 classes defined elsewhere (based on the same union type), how I can be sure which parts of this union type actually belong to the current module and which parts elsewhere?

I was thinking it would be possible to merge any two stylesheets, this would allow building a stylesheet tree which would reflect a module hierarchy (or simply a list). stylesheet.class, however, will stop working with this approach. Perhaps we need two data types, e.g. Stylesheet and StylesheetGroup?

Alternatively, we could just define classes independently from each other, e.g.:

title = class "title" [
      width (px 300)
    , height auto
    ]

use them in DOM like this:

div [ title.class ] [ ... ]

group into stylesheets like this:

stylesheet = Style.render [ title, main, ... ]

then merging becomes as simple as:

mergedStylesheet = Stylesheet.merge [ stylesheet1, stylesheet2, ...]

and rendering stylesheets stays the same.


In regards to mixins, I'd like to see some examples of common mixins that people use. From my experience, it has always been possible to avoid mixins altogether:

mdgriffith commented 7 years ago

For organizing the master union type, you could easily break it up into sections.

type MyClasses
     = Nav
     | Button
     | MyOtherStyles OtherStyles

-- in MyModule.elm
type OtherStyles
    = Widget
    | CatPortrait

You could support that with a special merge function as well. Something like:

Style.merge MyOtherStyles myOtherStylesheet, which would just map MyOtherStyles to all the classes in the sub-stylesheet.

I'm also playing around with being able to generate/maintain this Union type via the elm-style CLI. We'll see if that makes sense at all.

So, the advantage of the independent function based approach that you suggest is that it avoids having to maintain a union type. However, it makes something like hash guards mandatory and requires you to manually add each style definition to the stylesheet.


I'll see if I can put together some examples using the semantic grouping method I'm proposing.

wizza-smile commented 6 years ago

How did this work out? Did you get around to creating those examples?