sebastiaanvisser / clay

A CSS preprocessor as embedded Haskell.
Other
357 stars 72 forks source link

Type safe class names? #220

Open ddssff opened 3 years ago

ddssff commented 3 years ago

One big win from using Haskell to generate CSS is that you can use typed values to ensure that the class names you use in your HTML correspond with the ones you use in your CSS. We have implemented this in a somewhat ad-hoc way, I wonder if there is a case for adding type safe class names (and maybe other things like animation names) to Clay?

turion commented 3 years ago

What exactly do you mean by type safe class names? What error would you like to rule out? Maybe you have an example?

ddssff commented 3 years ago

For example, we have a CssClass class with a cssClass method that converts instances to class names. The code for an open/close button looks like this:

data ToggleCss
  = ToggleThing
  | ToggleButton
  deriving (Eq, Ord, Show)

instance CssClass ToggleCss where
  cssClass = show

#if SERVER
instance CssStyle ToggleCss () where
  cssStyle () = do
    star # byClass' ToggleThing ? do
      star # byClass' ToggleButton ? do
        -- parentUpperRight
        color grey
        background transparent
#endif

#if CLIENT
toggleButton :: (Renderer' h m, HasCallStack) => Lens' (Sh h) Bool -> m () -> m () -> m ()
toggleButton lns expandedGlyph compressedGlyph = do
  expanded <- view lns
  buttonA (\_ -> lns %= not >> pure N) (bool compressedGlyph expandedGlyph expanded)
    `with` [classes_ [cssClass ToggleButton]]
....

byClass' is a version of byClass that takes a CssClass instead of a string. When we used strings instead of calling cssClass the names in the client code would get out of sync with those in the server code, or disappear altogether when design changes were made in one place or another. I'm also adding a withLocationHash function to append a uniquifying string to every class name based on the location of the CssClass instance (though the use of the Int type in the Hashable class is causing incompatibilities due to the 56 bit ints in ghcjs.)

ddssff commented 3 years ago

Oops I commented and closed this thinking it was another issue for another package.

turion commented 3 years ago

I can't quite follow. What's CssStyle? What exactly is your proposal here?

ddssff commented 3 years ago

I don't have a concrete proposal yet, just a question about how to guarantee that the class names in your HTML match the ones in your CSS. It may be that my web development tools are sufficiently different from those of other people that they don't have this issue.

CssStyle is just a trick to scan all the instances for to collect and render all the Css values using template haskell.

ddssff commented 2 years ago

Update: I'm now using symbol names for css class names, via the template haskell quote mechanism:

import Language.Haskell.TH.Syntax (Name(..), ModName(..), NameFlavour(..), NameSpace(..), OccName(..))

instance CssClass Name where
  cssClass (Name (OccName o) (NameG space _ (ModName m))) =
    protectCSSIdentifier (pack (m <> "-" <> o <>
                               case space of
                                 TcClsName -> "_T"
                                 DataName -> "_D"
                                 VarName -> "_V"))
  cssClass (Name (OccName o) (NameQ (ModName m))) = protectCSSIdentifier (pack (m <> "-" <> o))
  cssClass (Name o f) = error ("cssClass (Name " <> show o <> " " <> show f <> ")")

This way I can immediately identify what module a css class name is coming from.