packages feed

reflex-classhss (empty) → 0.1.0.0

raw patch · 8 files changed

+813/−0 lines, 8 filesdep +ClasshSSdep +basedep +containers

Dependencies added: ClasshSS, base, containers, data-default, lens, reflex-dom-core, template-haskell, text

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for ClashSS++## 0.1.0.0 -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2024 Galen Sprout++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ reflex-classhss.cabal view
@@ -0,0 +1,60 @@+cabal-version:      2.4+name:               reflex-classhss+version:            0.1.0.0+synopsis:+        ClasshSS defined element builders for Reflex+description:+        Using ClasshSS this module provides a number of interfaces to rapidly build UIs,+        in particular there is a focus on text and layouts++homepage:           https://github.com/augyg/ClasshSS+license:            MIT+license-file:       LICENSE+author:             Galen Sprout+maintainer:         galen.sprout@gmail.com+-- copyright:+category:           Web+build-type:         Simple+extra-doc-files:    CHANGELOG.md+-- extra-source-files:++source-repository head+  type:                git+  location:            https://github.com/augyg/reflex-classhss+   +common warnings+    ghc-options: -Wall++library+    import:           warnings+    exposed-modules:  Classh.Reflex+                      Classh.Reflex.El+                      Classh.Reflex.Layout+                      Classh.Reflex.Text+                      Classh.Reflex.Place+    build-depends: ClasshSS >= 0.2.0 && < 0.3+                 , base >= 4.20.2 && < 4.21+                 , containers >= 0.7 && < 0.8+                 , data-default >= 0.8.0 && < 0.9+                 , lens >= 5.3.6 && < 5.4+                 , reflex-dom-core >= 0.8.1 && < 0.9+                 , template-haskell >= 2.22.0 && < 2.23+                 , text >= 2.1.3 && < 2.2+++    build-depends:    base+                      --^>=4.15.1.0+                    , containers+                    , data-default+                      -->= 0.7.1 && < 0.8+                    , lens+                      -->= 5.2.3 && < 5.3+                    , template-haskell+                    , reflex-dom-core+                    , ClasshSS+                      -->= 2.17.0 && < 2.18+                    , text+                      -->= 1.2.5 && < 1.3+    hs-source-dirs:   src+    ghc-options: -Wall -threaded -fno-show-valid-hole-fits+    default-language: Haskell2010
+ src/Classh/Reflex.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RankNTypes #-}++--------------------------------------------------------------------------------+-- |+--  Module      :  Classh.Reflex.Layout+--  Copyright   :  (c) 2024, Galen Sprout+--  License     :  BSD-style (see end of this file)+--+--  Maintainer  :  Galen Sprout <galen.sprout@gmail.com>+--  Stability   :  provisional+--  Portability :  portable+--+--  A collection of patterns that have emerged from usage of ClasshSS in order to reduce the amount of+--  code necessary to build beautiful UIs that will be easier to maintain.+--+--  For example, lets use grids to show 3 components (just text here) that on mobile and+--  tablet(sm) will take up the full row, and at larger sizes take up 3\/12 width, 4\/12 width, and 5\/12 width+--  respectively.+--+--  Let's also pretend this is a landing page and we want all rows evenly spaced, we can make this obvious+--  with 'row'+-- +-- @+--   {-# LANGUAGE OverloadedStrings #-}+--   {-# LANGUAGE FlexibleContexts #-}+--   module Main where+--+--   import Reflex.Dom.Core+--   import Classh+--   +--   mySimpleResponsivePage :: DomBuilder t m => m ()+--   mySimpleResponsivePage = do +--     row [y .~~ TWSize 10] $ do +--       gridCol Col12 $ do+--         col [12,12,3] $ normalText "hey"+--         col [12,12,4] $ normalText "hello"+--         col [12,12,5] $ normalText "howdy"+--     where+--        normalText = textS $(classh' [text_size .|~ [LG, XL, XL2, XL3] ] )+-- @+--+-- +-------------------------------------------------------------------------------+++module Classh.Reflex ( module X) where++import Classh.Reflex.El as X+import Classh.Reflex.Text as X+import Classh.Reflex.Place as X+import Classh.Reflex.Layout as X+++++-- | Not real: just ideating+-- | How can we model a discrete set of consistent brand options?+--+-- data Mytextsize = Heading | Subtext | Normal+-- domain = Map.fromList [ (Heading , XL7) , (Normal , XL3) , (Subtext , XS) ]+--+-- brandF :: k -> WhenTW k+-- brandF (domainType :: Ord k => k) =+--   let baseSize = fromJust $ Map.lookup domainType (domain :: Map (k :: Ord k => k) (v :: ShowTW x => x))+--   in+--     autoScale baseSize+--+-- then this above code suggests that we should think of our domain as mobile first+-- it is important to remember this is not meant to represent 100% of CSS but rather ~80%+-- and should be a way to communicate branding concisely such that a stoopid cud impl. it to be+-- consistent+--+-- Now, it wont always be super complex: for example, color+--+-- def = TW.def & text_color .~~ aceBlue+--+-- But then again, a brand may have multiple colors+--+-- brandColors = Map.fromList [ (AceBlue, hex _), (AceNavyBlue, hex _), (AceBabyBlue, hex _) ]+-- and also:+       -- What about dark mode? \+       --+-- and note: lets say 90% of titles should have Bold as their weight, we can set this and+++++++++-- | TODO: styledParagraphs :: [( BottomPadding, [(tw,T.Text)])] -> m ()+-- then apply to bannerFor in Landing.*+++-- buttonC :: Map.Map T.Text T.Text -> m a -> m (Event t a)+-- buttonC attrs inner = do+--   (e,x) <- elAttr' "button" attrs inner+--   pure $ x <$ domEvent Click e++++-- textTW' :: (DomBuilder t m, ShowTW tw) => tw -> T.Text -> m ()+-- textTW' = styledText'++-- styledText' :: (DomBuilder t m, ShowTW tw) => tw -> T.Text -> m ()+-- styledText' cfg txt = elClass "span" (showTW cfg) $ text txt++-- dynTextTW' :: (DomBuilder t m, PostBuild t m, ShowTW tw) => tw -> Dynamic t T.Text -> m ()+-- dynTextTW' cfg txt = elClass "span" (showTW cfg) $ dynText txt++-- textDynTW' :: (DomBuilder t m, PostBuild t m, ShowTW tw) => Dynamic t tw -> T.Text -> m ()+-- textDynTW' dynCfg txt = elDynClass "span" ( (\cfg -> defaultClasses <> " " <> showTW cfg) <$> dynCfg ) $ text txt++-- styledTexts' :: (ShowTW tw, DomBuilder t m) => [(tw, T.Text)] -> m ()+-- styledTexts' styTexs = el "div" $ forM_ styTexs $ \(cfg, txt) -> styledText' cfg txt++
+ src/Classh/Reflex/El.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+--  Module      :  Classh.Reflex.Text+--  Copyright   :  (c) 2024, Galen Sprout+--  License     :  BSD-style (see end of this file)+--+--  Maintainer  :  Galen Sprout <galen.sprout@gmail.com>+--  Stability   :  provisional+--  Portability :  portable+--+--  A collection of Classh-typed versions of elClass and similar funcs using classhUnsafe+--+-------------------------------------------------------------------------------+++module Classh.Reflex.El where++import Reflex.Dom.Core+import Classh+import Control.Monad (void)+import qualified Data.Map as Map+import qualified Data.Text as T+++++type Shell m a = m a -> m a++-- | is elClass except that it takes a BoxConfig instead of a string for classes+elTW :: DomBuilder t m => T.Text -> BoxConfig -> m a -> m a+elTW etag cfg ma = elClass etag (defaultClasses <> " " <> showTW cfg) ma++-- | is elClass' except that it takes a BoxConfig instead of a string for classes.+elTW' :: DomBuilder t m => T.Text -> BoxConfig -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)+elTW' etag cfg ma = elClass' etag (defaultClasses <> " " <> showTW cfg) ma++-- | is elDynClass except that it takes a BoxConfig instead of a string for classes+elDynTW :: (PostBuild t m, DomBuilder t m) => T.Text -> Dynamic t BoxConfig -> m a -> m a+elDynTW etag cfgDyn ma = elDynClass etag ( (\cfg -> defaultClasses <> " " <> showTW cfg) <$> cfgDyn ) ma++-- | is elDynClass' except that it takes a BoxConfig instead of a string for classes+elDynTW' :: (PostBuild t m, DomBuilder t m) => T.Text -> Dynamic t BoxConfig -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)+elDynTW' etag cfgDyn ma = elDynClass' etag ( (\cfg -> defaultClasses <> " " <> showTW cfg) <$> cfgDyn ) ma++div_ :: DomBuilder t m => m a -> m a+div_ = el "div"++-- | When we want to do absolutely nothing but create an area devoid of influence by outside+-- | Most common case is for differing displays like inline-block vs grid+-- |+-- | In practice this can also be used to solve a common problem where+-- | helper functions unnecessarily restrict size of children.+-- |+-- | TODO: we need to probably use this everywhere+in_ :: DomBuilder t m => m a -> m a+in_ = divClass $(classh' [h .~~ pct 100, w .~~ pct 100])++-- | Example Use Case:+-- | divClass $(classh' [pos .~~ centered]) $ do+-- |   inH_ leaf+inH_ :: DomBuilder t m => m a -> m a+inH_ = divClass $(classh' [h .~~ pct 100])+++type PlaceHolder = T.Text+inputEl :: DomBuilder t m => T.Text -> T.Text -> PlaceHolder -> m ()+inputEl elClasses textClasses placeholder = void $ inputElement $ def & inputElementConfig_elementConfig . initialAttributes .~+  ("class" =: (elClasses <&> textClasses)+  <> "placeholder" =: placeholder+  )++type TextAreaRows = Int+textAreaEl :: DomBuilder t m => TextAreaRows -> T.Text -> T.Text -> PlaceHolder -> m ()+textAreaEl rows elClasses textClasses placeholder =+  void $ textAreaElement $ def & textAreaElementConfig_elementConfig . initialAttributes .~+  ("class" =: (elClasses <&> textClasses)+  <> "rows" =: tshow rows+  <> "placeholder" =: placeholder+  )++-- <img+--   src="dog-600.jpg"+--   srcset="+--     dog-300.jpg 300w,+--     dog-450.jpg 450w,+--     dog-600.jpg 600w,+--     dog-900.jpg 900w,+--     dog-1200.jpg 1200w,+--     dog-1600.jpg 1600w+--   "+--   sizes="(max-width: 600px) 100vw, (max-width: 900px) 50vw, 33vw"+--   alt="Dog running through the field" />+type ScreenWidth = DimensionConstraint+imgResponsive+  :: DomBuilder t m+  => [ (StaticImagePath+       , (CSSSize, ScreenWidth)+       )+     ]+  -> CompiledS+  -> m ()+imgResponsive imageInfo classes = elAttr "img" (imgSrcSet imageInfo classes) blank++-- example = imgResponsive (imgSrcSet [] "")++--imgSrcSet+type StaticImagePath = T.Text+type Pixels = Int++imgSrcSet :: [(StaticImagePath, (CSSSize, DimensionConstraint))] -> CompiledS -> Map.Map T.Text T.Text+imgSrcSet [] _ = error "No images given to Classh.Reflex.imgSrcSet" -- we know this must be the start, there is no recursion+imgSrcSet xs classes =+  let+    mkPair = \(src',(w_,_)) -> src' <&> (renderCSS w_) <> "w"+    pairs = mkPair <$> xs++    showMW :: DimensionConstraint -> T.Text+    showMW m_ = "(max-width: " <> showTW m_ <> ")"++    mkFuckingAlgebraicSizes :: [(StaticImagePath, (CSSSize, DimensionConstraint))] -> T.Text+    mkFuckingAlgebraicSizes [] = error "mkFuckingAlgebraicSizes received no input"+    mkFuckingAlgebraicSizes ((_,(w_, maxW__)):xs_) =+      if null xs_+      then renderCSS w_+      else showMW maxW__ <> renderCSS w_ <> "," <> mkFuckingAlgebraicSizes xs_+  in+    "srcset" =: (T.intercalate "," pairs)+    <> "sizes" =: (mkFuckingAlgebraicSizes xs)+    <> "class" =: classes++divClassh :: BoxConfig -> [BoxConfig -> BoxConfig] -> Compiled Expression+divClassh base muts = case compileS $ foldl (\acc f -> f acc) base muts of+  Left e -> fail $ T.unpack e+  Right styleString -> [| divClass styleString |]++divClassh' :: [BoxConfig -> BoxConfig] -> Compiled Expression+divClassh' muts = case compileS $ foldl (\acc f -> f acc) def muts of+  Left e -> fail $ T.unpack e+  Right styleString -> [| divClass styleString |]++textClassh :: TextConfigTW -> [TextConfigTW -> TextConfigTW] -> Compiled Expression+textClassh base muts = case compileS $ foldl (\acc f -> f acc) base muts of+  Left e -> fail $ T.unpack e+  Right styleString -> [| textS styleString |]++textClassh' :: [TextConfigTW -> TextConfigTW] -> Compiled Expression+textClassh' muts = case compileS $ foldl (\acc f -> f acc) def muts of+  Left e -> fail $ T.unpack e+  Right styleString -> [| textS styleString |]++textPos' :: [TextPosition -> TextPosition] -> Compiled Expression+textPos' muts = case compileS $ foldl (\acc f -> f acc) def muts of+  Left e -> fail $ T.unpack e+  Right styleString -> [| textPosition styleString |]++textPos :: TextPosition -> [TextPosition -> TextPosition] -> Compiled Expression+textPos base muts = case compileS $ foldl (\acc f -> f acc) base muts of+  Left e -> fail $ T.unpack e+  Right styleString -> [| textPosition styleString |]
+ src/Classh/Reflex/Layout.hs view
@@ -0,0 +1,262 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE OverloadedStrings #-}+++--------------------------------------------------------------------------------+-- |+--  Module      :  Classh.Reflex.Layout+--  Copyright   :  (c) 2024, Galen Sprout+--  License     :  BSD-style (see end of this file)+--+--  Maintainer  :  Galen Sprout <galen.sprout@gmail.com>+--  Stability   :  provisional+--  Portability :  portable+--+--  A collection of functions to quickly divide up a Dom into rows and columns.+-- +--  Note that these functions are really just meant to be reduced Boxes that visually instruct+--  other developers what the UI is doing.+--+--  For example, lets use grids to show 3 components (just text here) that on mobile and+--  tablet(sm) will take up the full row, and at larger sizes take up 3/12 cols, 4/12 cols, and 5/12 cols+--  respectively.+--+--  Let's also pretend this is a landing page and we want all rows evenly spaced, we can make this obvious+--  with 'row'+-- +-- @+--   row [y .~~ TWSize 10] $ do +--     gridCol Col12 $ do+--       col [12,12,3] $ text "hey"+--       col [12,12,4] $ text "hello"+--       col [12,12,5] $ text "howdy"+-- @+--+-- +-------------------------------------------------------------------------------++++              +module Classh.Reflex.Layout where+++import Classh.Reflex.Place+import Classh+import Reflex.Dom.Core+import Control.Monad+import qualified Data.Text as T+++type Mutation a = a -> a++type MinWidth = DimensionConstraint+type MaxWidth = DimensionConstraint+type TargetWidth = TWSizeOrFraction++-- | Centering relative to full sized containers, as constants+centeredFullH :: [Mutation BoxConfig]+centeredFullH = [h .~~ pct 100, pos .~~ centered]+centeredFullW :: [Mutation BoxConfig]+centeredFullW = [w .~~ pct 100, pos .~~ centered]+centeredFull :: [Mutation BoxConfig]+centeredFull = [h .~~ pct 100, w .~~ pct 100, pos .~~ centered]++inline :: DomBuilder t m => m a -> m a+inline ma = divClass (classhUnsafe [box_custom .~ "inline-block"]) ma++inlines :: DomBuilder t m => [m a] -> m ()+inlines [] = pure ()+inlines (ma:mas) = inline ma >> inlines mas+++-- | Call as infix: topDOM `overtopOf` bottomDOM+overtopOf :: DomBuilder t m => [TWSizeOrFraction] -> m () -> m () -> m ()+overtopOf heights topDOM bottomDOM = do+  -- TODO: throw compile error if its a non-static height+  when (null heights) $ do+    error "`overtopOf` received null list"+  el "div" $ do+    divClass shell_relative $ do+      divClass shell_top $ do+        topDOM+      divClass shell_bottom $ do+        bottomDOM+  where+    shell_relative = (classhUnsafe [ custom .~ "relative", h .|~ heights])+    shell_top = (classhUnsafe [ custom .~ "absolute z-30", w .~~ TWSize_Full, h .|~ heights])+    shell_bottom = (classhUnsafe [ custom .~ "absolute z-20", w .~~ TWSize_Full, h .|~ heights])++++-- | A generic interface to creating a centered row of some width+width constraints+rowW :: DomBuilder t m => [(MinWidth,TargetWidth,MaxWidth)] -> (Justify, Align) -> m a -> m a +rowW widthCfg pos_ = centerSimple . divClass (classhUnsafe [minW .|~ minWidths, maxW .|~ maxWidths, w .|~ targetWidths, pos .~~ pos_ ])+  where+    (minWidths, targetWidths, maxWidths) = unzip3 widthCfg ++-- | A handy way to visually (to the dev and to the user) divide up a section vertically+--  > row [ y .~~ TWSize 10, l .~~ pix 5 ] $ text "hey"+row :: DomBuilder t m => [Mutation BoxPadding] -> m a -> m a+row paddingF = elClass "div" ( "" <&> showTW (applyFs def paddingF) )++-- | Divide a row into a number of columns +-- | Purposefully designed to take no other data besides ColInt in order to be very self-contained+--  > gridCol Col12 $ do { col [6] $ text "hey"; col [6] $ text "hi" }+gridCol :: DomBuilder t m => ColInt -> m a -> m a+gridCol cInt ma = elClass "div" ("grid grid-cols-" <> showTW cInt) ma++-- | Divide a row into a number of columns, with other arbitrary classes  +--  > gridColW Col12 "bg-black" $ do { col [6] $ text "hey"; col [6] $ text "hi" }+gridCol' :: DomBuilder t m => ColInt -> T.Text -> m a -> m a+gridCol' cInt customCs ma = elClass "div" ("grid grid-cols-" <> showTW cInt <&> customCs) ma++-- | Divide a row into a number of columns, and take up a fraction of parent row +--  > gridColW Col12 "bg-black" $ do { col [6] $ text "hey"; col [6] $ text "hi" }+gridColW :: DomBuilder t m => ColInt -> TWSizeOrFraction -> m a -> m a+gridColW cInt width_ ma = elClass "div" ("grid grid-cols-" <> showTW cInt <&> ("w-" <> showTW width_)) ma++-- TODO: this should have the ability to add rows somehow+--  and how we can think of this is that if we start at a large->Col12 and scale down to+--  lets say a 'sm' then Col12 / n where n is from config. So lets say n=2 then sm->Col6 which will show up as+--  2 rows of 6; similarly n=6 => 6 rows of 2 ; n=12 -> 12 rows of 1; n=3 => 3 rows of 4 ; n=4 => 4 rows of 3+--+-- | Divide a row into a number of columns where columns is based on screen size +-- | Purposefully designed to take no other data besides ColInt in order to be very self-contained+--  > gridColWhen [Col6, Col12] $ do { col [6] $ text "hey"; col [6] $ text "hi" } +gridColWhen :: DomBuilder t m => WhenTW ColInt -> m a -> m a+gridColWhen cInts' ma = elClass "div" (showCInts cInts') ma+  where+    showCInts [] = ""+    showCInts (("def",cInt):cInts) = "grid grid-cols-" <> showTW cInt <&> showCInts cInts+    showCInts ((case_,cInt):cInts) = case_ <> ":" <> "grid" <&> case_ <> ":" <> "grid-cols-" <> showTW cInt+                                 <&> showCInts cInts++-- Shouldn't it be assumed that its responsive? that is the whole point of Classh really+--+-- | Takes a list of values for how many columns should be taken up at a given screen size+-- | using 'zipScreens'+col :: DomBuilder t m => [Int] -> m a -> m a+col = responsiveRowCol++-- | Takes a list of values for how many columns should be taken up at a given screen size+-- | using 'zipScreens', as well as where this should start (if you want to skip columns)+--  > do { colFrom [(2,2)] $ text "hey" ; colFrom [(9,2)] $ text "hi" }+colFrom :: DomBuilder t m => [(Int,Int)] -> m a -> m a+colFrom colsCfg ma =+  let+    (colStarts, colSpans) = unzip colsCfg+    spandex = renderWhenTW (zipScreens colSpans) ((<>) "col-span-" . tshow)+    startdex = renderWhenTW (zipScreens colStarts) ((<>) "col-start-" . tshow)+  in elClass "div" (spandex <&> startdex) ma++-- | Like col except that the list of cols to take up, is Dynamic. An example use is+-- | an element that expands and contracts based on a click and that we also want to be responsive+-- | to screen sizes, like a side navbar+colDyn :: forall a t m.+  ( PostBuild t m+  , DomBuilder t m+  )+  => Dynamic t [Int]+  -> m a+  -> m a+colDyn dynCols ma = +  let+    spandex = ffor dynCols $ \(colSpans) ->      +      renderWhenTWRank2+      (zipScreens colSpans)+      (\condition c ->+         if c == 0+         then twWhenText condition ("col-span-" <> tshow c) <&> twWhenText condition "hidden"+         else twWhenText condition ("col-span-" <> tshow c) <&> twWhenText condition "block" -- negate hidden if shown on larger size+         +      )+  in elDynClass "div" spandex ma++-- | Like col except that its height is 100% of container+-- | +-- | If we look at Width and height of CSS Boxes as Categories then there are 2 main ones on either end+-- | 1) Assume Height == HeightOfContents+-- | 2) Assume Height == HeightOfContainer+-- | and this is true for width as well, but this is handled by the argument which is logical since+-- | responsiveness has a far bigger bearing on width+col' :: DomBuilder t m => [Int] -> m a -> m a+col' colSpans ma = +  let+    spandex = renderWhenTW (zipScreens colSpans) ((<>) "col-span-" . tshow)+    heightCol = $(classh' [h .~~ TWSize_Full])+  in elClass "div" (heightCol <&> spandex) ma+  +-- | Takes a list of values for how many columns should be taken up at a given screen size+-- | using 'zipScreens'+responsiveCol :: DomBuilder t m => [Int] -> m a -> m a +responsiveCol = responsiveRowCol++--  TODO: should we have a variant which controls for padding needs when being a full row? What should that look like? Do we even need that??+-- +-- | Takes a list of values for how many columns should be taken up at a given screen size+-- | using 'zipScreens'+responsiveRowCol :: DomBuilder t m => [Int] -> m a -> m a+responsiveRowCol colSpans ma =+  let spandex = renderWhenTW (zipScreens colSpans) ((<>) "col-span-" . tshow)+  in elClass "div" spandex ma++-- | A type to model how two elements should rotate+-- | as the screen size gets smaller and they are forced into one column+-- | ie. should the element on the right be on top or bottom? +data Rotation a b = CounterClockwise a b | Clockwise a b++-- | An interface to control how two elements should rotate+-- | as the screen size gets smaller and they are forced into one column+-- | ie. should the element on the right be on top or bottom? +rotatingBox :: DomBuilder t m => TWSizeOrFraction -> Rotation (m ()) (m ()) -> m ()+rotatingBox widthA rotation = do+  case rotation of +    CounterClockwise domA domB -> do+      --  A | B ||   (A : 100% width)+      --        ||   (B : 100% width)+      nonMobile widthA domA domB +      flip mobile domA domB+    Clockwise domA domB -> do+      --  A | B ||   (B : 100% width)  +      --        ||   (A : 100% width)+      nonMobile widthA domA domB +      mobile domA domB+  where+    boxMobileOnly = col [12] . divClass "block md:hidden w-[100%]" +    boxNonMobileLeft width_ = col [7] . divClass (classhUnsafe [w .~~ width_,  custom .~ "hidden md:block"])+    boxNonMobileRight = col [5] . divClass "hidden md:block" ++    nonMobile width_ domX domY = do+      _ <- boxNonMobileLeft width_ $ do+        domX+      boxNonMobileRight $ do+        domY+    mobile domX domY = do+      _ <- boxMobileOnly $ do+        domX+      boxMobileOnly $ do+        domY++  -- in case rotation of +  --   CounterClockwise domA domB -> do+  --     --  A | B ||   (A : 100% width)+  --     --        ||   (B : 100% width)+  --     nonMobile+  --     flip mobile domA domB+  --     -- boxMobileOnly $ do+  --     --   domB+  --     -- boxMobileOnly $ do+  --     --   domA+  --   Clockwise domA domB -> do+  --     --  A | B ||   B  +  --     --        ||   A+  --     nonMobile+  --     mobile domA domB+  --     -- boxMobileOnly $ do+  --     --   domA+  --     -- boxMobileOnly $ do+  --     --   domB+    
+ src/Classh/Reflex/Place.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- |+--  Module      :  Classh.Reflex.Place+--  Copyright   :  (c) 2024, Galen Sprout+--  License     :  BSD-style (see end of this file)+--+--  Maintainer  :  Galen Sprout <galen.sprout@gmail.com>+--  Stability   :  provisional+--  Portability :  portable+--+--  A collection of ways to quickly place an element within a parent+--+-------------------------------------------------------------------------------++++module Classh.Reflex.Place where++--import Classh.Reflex.Layout+import Reflex.Dom.Core+import Classh++-- | A row of some target width to place a component/container+placeCenterWidth :: DomBuilder t m => WhenTW TWSizeOrFraction -> m a -> m a  +placeCenterWidth widthResponsive = centerSimple . divClass (classhUnsafe [ w .~ widthResponsive ])++-- | A collection of classes to center horizontally and vertically+centerXY :: ClassCollection BoxConfig+centerXY = ClassCollection [ position .~~ centered, w .~~ pct 100, h .~~ pct 100 ]++-- | A collection of classes to center horizontally +centerX :: ClassCollection BoxConfig+centerX = ClassCollection [ position .~~ centered, w .~~ pct 100 ]++-- | A collection of classes to center vertically+centerY :: ClassCollection BoxConfig+centerY = ClassCollection [ position .~~ centered, h .~~ pct 100 ]++-- | A utility to easily place the element given as an arg in the center (vertically,horizontally) of its parent +centerSimple :: DomBuilder t m => m a -> m a +centerSimple = divClass $(classh' [ position .~~ centered, w .~~ pct 100, h .~~ pct 100 ])++-- | A utility to easily place the element given as an arg in the center (horizontally) of its parent +centerHSimple :: DomBuilder t m => m a -> m a +centerHSimple = divClass $(classh' [ position .~~ centered, w .~~ pct 100 ])++-- | A utility to easily place the element given as an arg in the center (vertically) of its parent +centerVSimple :: DomBuilder t m => m a -> m a +centerVSimple = divClass $(classh' [ position .~~ centered, h .~~ pct 100 ])++-- | A utility to easily place the element-arg centered between some padding +responsiveXPaddedRegion :: DomBuilder t m => [TWSizeOrFraction] -> m a -> m a+responsiveXPaddedRegion opts = centerHSimple . divClass (classhUnsafe [w .|~ opts])++-- | A utility to easily place the element-arg centered between some padding, with pre-chosen values+-- | this is really useful for prototyping+responsiveXPaddedRegion' :: DomBuilder t m => m a -> m a +responsiveXPaddedRegion' = responsiveXPaddedRegion [pct 100, pct 80, pct 80, pct 80, pct 80, pct 70]+++  
+ src/Classh/Reflex/Text.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}++--------------------------------------------------------------------------------+-- |+--  Module      :  Classh.Reflex.Text+--  Copyright   :  (c) 2024, Galen Sprout+--  License     :  BSD-style (see end of this file)+--+--  Maintainer  :  Galen Sprout <galen.sprout@gmail.com>+--  Stability   :  provisional+--  Portability :  portable+--+--  A collection of interfaces to handle text from a single word to multiple paragraphs +--+-------------------------------------------------------------------------------+++module Classh.Reflex.Text where++import Classh.Reflex.Layout (row)+import Reflex.Dom.Core+import Classh++import qualified Data.Text as T+++-- TODO TEXT CONTAINER WITH STATIC SIZING ++--  TODO: variants which do not get checked/compiled++-- | Text wrapped in a span to ensure classes work properly regardless of context +textS :: DomBuilder t m => CompiledS -> T.Text -> m ()+textS s txt = elClass "span" s $ text txt++-- | Text wrapped in a \'u\' tag  to ensure classes work properly regardless of context +textU :: DomBuilder t m => CompiledS -> T.Text -> m ()+textU s txt = elClass "u" s $ text txt++-- | Like textS except that the classes are Dynamic so they may change in response to user events+textDynS+  :: ( PostBuild t m+     , DomBuilder t m+     )+  => Dynamic t CompiledS+  -> T.Text+  -> m ()+textDynS s txt = elDynClass "span" s $ text txt++-- | Like textS except that the Text is Dynamic so they may change in response to user events+-- | This is just the classhified version of dynText +dynTextS+  :: ( PostBuild t m+     , DomBuilder t m+     )+  => CompiledS+  -> Dynamic t T.Text+  -> m ()+dynTextS s txt = elClass "span" s $ dynText txt++-- | Like textS except that the Text and class attribute is Dynamic so they may change in response to user events+dynTextDynS+  :: ( PostBuild t m+     , DomBuilder t m+     )+  => Dynamic t CompiledS+  -> Dynamic t T.Text+  -> m ()+dynTextDynS s txt = elDynClass "span" s $ dynText txt++-- | For creating generic Templates which dont deeply depend on the text or text config given, or allow+-- | for multiple 'TextConfigTW's across a single text.+-- | A simple example is a sentence with highlighted or different color words for emphasis+-- | The one caveat is you do need to manually set the TextSize.+--  > intercalate (only XL2) " " [ textS $(classh' [text_size .~~ XL2, text_color .~~ Black]) "Hey"+--  >                            , dynTextS $(classh' [text_size .~~ XL2, text_color .~~ White]) (user :: Dynamic t String)+--  >                            ]+intercalate+  :: DomBuilder t m+  => WhenTW TextSize+  -> T.Text+  -> [m ()]+  -> m ()+intercalate _ _ [] = pure () +intercalate textSize inter (txt':txts') = do+  txt' >> prependAll textSize inter txts'+  where+    prependAll _ _ [] = pure ()+    prependAll s i (txt:txts) = elClass "span" (renderWhenTW s showTW ) (text i) >> txt >> prependAll s i txts++-- | Works by setting top padding by one consistent value. Does not add padding for first element++-- | Display a number of paragraphs (where a paragraph is 1 or more sentences, with whatever crazy styling, like intercalate)+-- | with even spacing between them, defined by the first arg of TWSize +paragraphs+  :: DomBuilder t m+  => WhenTW TWSize+  -> [m ()]+  -> m ()+paragraphs _ [] = pure ()+paragraphs spacing (para:paras) = do+  row [] $ para+  withTopPadding spacing paras+  where+    withTopPadding _ [] = pure ()+    withTopPadding p_ (r_:rs_) = row [t .~ p_] r_ >> withTopPadding p_ rs_++-- | Display a number of paragraphs (where a paragraph is 1 or more sentences, with whatever crazy styling, like intercalate)+-- | with variable spacing between them, defined by the first element of the tuple, of type TWSize +paragraphs'+  :: DomBuilder t m+  => [(WhenTW TWSize, m ())]+  -> m ()+paragraphs' [] = pure ()+paragraphs' ((topPadding,para):rows) = row [t .~ topPadding] para >> paragraphs' rows++++textPosition :: DomBuilder t m => CompiledS -> m a -> m a+textPosition = divClass