packages feed

atomic-css-0.2.0: src/Web/Atomic/Types/Styleable.hs

module Web.Atomic.Types.Styleable where

import Web.Atomic.Types.ClassName
import Web.Atomic.Types.Rule as Rule
import Web.Atomic.Types.Selector
import Web.Atomic.Types.Style


class Styleable h where
  -- | Apply a CSS utility to some html
  --
  -- > el ~ bold . border 1 $ "styled"
  -- > el "styled" ~ bold . border 1
  -- > el "not styled"
  (~) :: h -> (CSS h -> CSS h) -> h
  h ~ f =
    flip modCSS h $ \rs ->
      let CSS new = f $ CSS rs
       in uniqueRules new


  modCSS :: ([Rule] -> [Rule]) -> h -> h


infixl 5 ~


instance {-# OVERLAPPABLE #-} (Styleable b) => Styleable (a -> b) where
  (~) :: (a -> b) -> (CSS (a -> b) -> CSS (a -> b)) -> (a -> b)
  hh ~ f = \content ->
    hh content ~ \(CSS m) ->
      let CSS m2 = f $ CSS m
       in CSS m2


  modCSS r hh content =
    modCSS r $ hh content


instance Styleable [Rule] where
  modCSS f = f


instance Styleable (CSS h) where
  modCSS f (CSS rs) = CSS $ f rs


newtype CSS h = CSS {rules :: [Rule]}
  deriving newtype (Monoid, Semigroup)


mapRules :: (Rule -> Rule) -> CSS a -> CSS a
mapRules f (CSS rs) = CSS $ fmap f rs


{- | Create an atomic CSS utility. These are classes that set a single property, allowing you to compose styles like functions

@
bold :: 'Styleable' h => 'CSS' h -> 'CSS' h
bold = utility "bold" ["font-weight" :. "bold"]

pad :: 'Styleable' h => 'PxRem' -> 'CSS' h -> 'CSS' h
pad px = utility ("pad" -. px) ["padding" :. 'style' px]

example = el ~ bold . pad 10 $ "Padded and bold"
@
-}
utility :: (Styleable h) => ClassName -> [Declaration] -> CSS h -> CSS h
utility cn ds (CSS rs) =
  CSS $ rule cn ds : rs


{- | Apply a class name with no styles. Useful for external CSS

> el ~ cls "parent" $ do
>   el ~ cls "item" $ "one"
>   el ~ cls "item" $ "two"
-}
cls :: (Styleable h) => ClassName -> CSS h -> CSS h
cls cn (CSS rs) =
  CSS $ Rule.fromClass cn : rs


{- | Embed CSS with a custom selector and apply it to an element. Modifiers like 'hover' will ignore this

> listItems =
>   css
>     "list"
>     ".list > .item"
>     [ "display" :. "list-item"
>     , "list-style" :. "square"
>     ]
>
> example = do
>   el ~ listItems $ do
>     el ~ cls "item" $ "one"
>     el ~ cls "item" $ "two"
>     el ~ cls "item" $ "three"
-}
css :: (Styleable h) => ClassName -> Selector -> [Declaration] -> CSS h -> CSS h
css cn sel ds (CSS rs) =
  CSS $ Rule cn (CustomRule sel) mempty ds : rs


-- | Get all the rules for combined utilities
rules :: (CSS [Rule] -> CSS [Rule]) -> [Rule]
rules f =
  let CSS rs = f mempty
   in rs


-- | Get all the declarations for a utility or combination of them
declarations :: (CSS [Rule] -> CSS [Rule]) -> [Declaration]
declarations f =
  mconcat $ fmap (.properties) (rules f)