From 1215851e2a1952c1f0d9e5cdb2dc29cdb6d4c1ec Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Fri, 20 Feb 2026 14:26:40 -0500 Subject: [PATCH 01/14] refactor: parameterize colors and use ClasshSS transform support Eliminate hardcoded colors throughout template components by adding Color parameters to all template functions. All functions now have parameterized versions (suffix ') with default wrappers maintaining backward compatibility. Components updated: - Buttons: navyBlueButton', primaryButton', primaryButtonImage', etc. - Convert custom CSS to proper ClasshSS properties (outline, ring, shadow) - Use transform field for scale animations on hover/active - Lists: Add 5 color parameters to ListItemConfig - hoverColor, unreadColor, borderColor, textColor, subtextColor - Use ClasshSS border properties instead of custom CSS - Dropdown: Add focusColor, bgColor, borderColor parameters - Use proper ClasshSS border and outline properties - Searchbar: Add bgColor and textColor parameters - Replace Shadow_Button with Shadow_Md - Containers: Use ClasshSS transform field for rotations - Replace custom "rotate-180" CSS with proper transform . rotate - Add smooth transitions with withTransition and withTiming - Invitebar: Add bgColor, borderColor, textColor parameters - Use ClasshSS border and text color properties - Shapes: Fix circle function to use TWSizeOrFraction instead of string Technical improvements: - Fix lens operators: use . for composition, .~ for setters (not C.. or C..~) - Wrap transitionable values with noTransition or withTransition - Update cabal version to support default-extensions - Maintain backward compatibility with default color wrappers All call sites remain unchanged due to wrapper functions with sensible defaults. --- src/Templates/Partials/Buttons.hs | 180 +++++++++++------- src/Templates/Partials/Containers.hs | 45 +++-- src/Templates/Partials/Containers/Dropdown.hs | 64 +++++-- src/Templates/Partials/Invitebar.hs | 14 +- src/Templates/Partials/Lists.hs | 51 +++-- src/Templates/Partials/Searchbar.hs | 59 +++--- src/Templates/Partials/Shapes.hs | 44 ++--- templates.cabal | 34 ++-- 8 files changed, 305 insertions(+), 186 deletions(-) diff --git a/src/Templates/Partials/Buttons.hs b/src/Templates/Partials/Buttons.hs index 046f814..e1d5d6d 100644 --- a/src/Templates/Partials/Buttons.hs +++ b/src/Templates/Partials/Buttons.hs @@ -7,7 +7,7 @@ import Classh as C import Classh.Reflex as C import Data.Text as T import Reflex.Dom.Core -import Control.Lens ((%~)) +import Control.Lens ((%~), (.~)) import Data.Proxy import Data.Char (isAlphaNum, isSpace) @@ -28,8 +28,8 @@ iconButton' enabled icon = do -primaryButton' :: (DomBuilder t m, PostBuild t m) => Dynamic t Bool -> Text -> m (Event t ()) -primaryButton' enabled buttonText = do +primaryButtonDyn :: (DomBuilder t m, PostBuild t m) => Dynamic t Bool -> Text -> m (Event t ()) +primaryButtonDyn enabled buttonText = do let attrs = ffor enabled $ \b -> if b then "class" =: classes else "class" =: classes <> "disabled" =: "1" (e, _) <- elDynAttr' "button" attrs $ text buttonText pure $ domEvent Click e @@ -68,21 +68,31 @@ sendButton = iconButton "send" navyBlueButton :: DomBuilder t m => Text -> m (Event t ()) -navyBlueButton buttonText = do - (e, _) <- elClass' "button" other $ C.textS textCfg' buttonText +navyBlueButton = navyBlueButton' (C.hex "2E3A59") C.White + +navyBlueButton' :: DomBuilder t m => C.Color -> C.Color -> Text -> m (Event t ()) +navyBlueButton' bgCol txtCol buttonText = do + (e, _) <- elClass' "button" boxCfg $ C.textS textCfg buttonText pure $ domEvent Click e where - textCfg' = $(C.classh' [ C.text_color C..~~ C.White - , C.text_font C..~~ C.Font_Custom "Sarabun" - , C.text_weight C..~~ C.Bold - ]) - other = - "focus:outline-none w-full p-4 shadow-button bg-[#2E3A59] \ - \ rounded-3xl hover:bg-primary-rich active:bg-primary-desaturated \ - \ focus:ring-4 ring-primary ring-opacity-50 \ - \ transition-all duration-300 ease-in-out \ - \ transform hover:scale-105 active:scale-95 \ - \ hover:shadow-md active:shadow-lg" + textCfg = C.classhUnsafe [ C.text_color .~~ txtCol + , C.text_font .~~ C.Font_Custom "Sarabun" + , C.text_weight .~~ C.Bold + ] + boxCfg = C.classhUnsafe [ C.w .~~ C.TWSize_Full + , C.p .~~ C.TWSize 4 + , C.bgColor .~~ bgCol + , C.br .~~ C.R_3Xl + , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) + , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) + ] + , C.border . C.outline .~ [("focus", C.Outline_None)] + , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] + , C.border . C.ring . C.ringOpacity .~~ 50 + , C.custom .~ "ring-primary \ + \ hover:bg-primary-rich active:bg-primary-desaturated \ + \ transform hover:scale-105 active:scale-95" + ] @@ -97,85 +107,123 @@ primaryButtonImageDyn -> Height -> Width -> m (Event t ()) -primaryButtonImageDyn dynImageHref height width = do - (e, _) <- elClass' "button" classes $ - elDynAttr "img" imgAttrs blank +primaryButtonImageDyn = primaryButtonImageDyn' (C.hex "00B9DA") C.White + +primaryButtonImageDyn' + :: ( PostBuild t m + , DomBuilder t m + ) + => C.Color + -> C.Color + -> Dynamic t Text + -> Height + -> Width + -> m (Event t ()) +primaryButtonImageDyn' bgCol txtCol dynImageHref height width = do + (e, _) <- elClass' "button" boxCfg $ + elDynAttr "img" imgAttrs blank pure $ domEvent Click e where otherImgAttrs = "height" =: height <> "width" =: width <> "class" =: "block mx-auto" imgAttrs = (\src -> otherImgAttrs <> "src" =: src) <$> dynImageHref - classes = - "focus:outline-none w-full p-4 mt-16 shadow-button bg-[#00B9DA] \ - \ font-[Sarabun] font-bold text-white text-body text-center rounded-xl \ - \ hover:bg-primary-rich active:bg-primary-desaturated \ - \ focus:ring-4 ring-primary ring-opacity-50 \ - \ transition-all duration-300 ease-in-out \ - \ transform hover:scale-105 active:scale-95 \ - \ hover:shadow-md active:shadow-lg" + boxCfg = C.classhUnsafe [ C.w .~~ C.TWSize_Full + , C.p .~~ C.TWSize 4 + , C.mt .~~ C.TWSize 16 + , C.bgColor .~~ bgCol + , C.br .~~ C.R_Xl + , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) + , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) + ] + , C.border . C.outline .~ [("focus", C.Outline_None)] + , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] + , C.border . C.ring . C.ringOpacity .~~ 50 + , C.custom .~ "font-[Sarabun] font-bold text-white text-body text-center \ + \ ring-primary \ + \ hover:bg-primary-rich active:bg-primary-desaturated \ + \ transform hover:scale-105 active:scale-95" + ] type Height = Text type Width = Text primaryButtonImage :: DomBuilder t m => Text -> Height -> Width -> m (Event t ()) -primaryButtonImage imageHref height width = do - (e, _) <- elClass' "button" classes $ +primaryButtonImage = primaryButtonImage' (C.hex "00B9DA") C.White + +primaryButtonImage' :: DomBuilder t m => C.Color -> C.Color -> Text -> Height -> Width -> m (Event t ()) +primaryButtonImage' bgCol _ imageHref height width = do + (e, _) <- elClass' "button" (primaryButtonBoxCfg bgCol) $ elAttr "img" ("src" =: imageHref <> "height" =: height <> "width" =: width <> "class" =: "block mx-auto") blank pure $ domEvent Click e - where - classes = - "focus:outline-none w-full p-4 mt-16 shadow-button bg-[#00B9DA] \ - \ font-[Sarabun] font-bold text-white text-body text-center rounded-xl \ - \ hover:bg-primary-rich active:bg-primary-desaturated \ - \ focus:ring-4 ring-primary ring-opacity-50 \ - \ transition-all duration-300 ease-in-out \ - \ transform hover:scale-105 active:scale-95 \ - \ hover:shadow-md active:shadow-lg" primaryButtonImageText :: DomBuilder t m => Text -> Height -> Width -> Text -> m (Event t ()) -primaryButtonImageText imageHref height width bottomText = do - (e, _) <- elClass' "button" classes $ do +primaryButtonImageText = primaryButtonImageText' (C.hex "00B9DA") C.White + +primaryButtonImageText' :: DomBuilder t m => C.Color -> C.Color -> Text -> Height -> Width -> Text -> m (Event t ()) +primaryButtonImageText' bgCol _ imageHref height width bottomText = do + (e, _) <- elClass' "button" (primaryButtonBoxCfg bgCol) $ do elAttr "img" ("src" =: imageHref <> "height" =: height <> "width" =: width <> "class" =: "block mx-auto") blank - text bottomText -- TODO: replace with styledText + text bottomText pure $ domEvent Click e - where - classes = - "focus:outline-none w-full p-4 mt-16 shadow-button bg-[#00B9DA] \ - \ font-[Sarabun] font-bold text-white text-body text-center rounded-xl \ - \ hover:bg-primary-rich active:bg-primary-desaturated \ - \ focus:ring-4 ring-primary ring-opacity-50 \ - \ transition-all duration-300 ease-in-out \ - \ transform hover:scale-105 active:scale-95 \ - \ hover:shadow-md active:shadow-lg" + +primaryButtonBoxCfg :: C.Color -> Text +primaryButtonBoxCfg bgCol = C.classhUnsafe [ C.w .~~ C.TWSize_Full + , C.p .~~ C.TWSize 4 + , C.mt .~~ C.TWSize 16 + , C.bgColor .~~ bgCol + , C.br .~~ C.R_Xl + , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) + , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) + ] + , C.border . C.outline .~ [("focus", C.Outline_None)] + , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] + , C.border . C.ring . C.ringOpacity .~~ 50 + , C.custom .~ "font-[Sarabun] font-bold text-white text-body text-center \ + \ ring-primary \ + \ hover:bg-primary-rich active:bg-primary-desaturated \ + \ transform hover:scale-105 active:scale-95" + ] primaryButtonSized :: DomBuilder t m => TWSize -> TWSize -> Text -> m (Event t ()) -primaryButtonSized height width buttonText = do - (e, _) <- elAttr' "button" ("class" =: classTW <> "name" =: name) $ text buttonText +primaryButtonSized = primaryButtonSized' (C.hex "00B9DA") C.White + +primaryButtonSized' :: DomBuilder t m => C.Color -> C.Color -> TWSize -> TWSize -> Text -> m (Event t ()) +primaryButtonSized' bgCol _ height width buttonText = do + (e, _) <- elAttr' "button" ("class" =: classCfg <> "name" =: name) $ text buttonText pure $ domEvent Click e where - classTW = primaryClass <&> ("py-" <> showTW height) <&> ("px-" <> showTW width) - -- for testing / selenium mainly + classCfg = primaryClass bgCol <&> C.classhUnsafe [C.py .~~ height, C.px .~~ width] name = T.filter (\c -> isAlphaNum c || isSpace c ) buttonText primaryButton :: DomBuilder t m => Text -> m (Event t ()) -primaryButton buttonText = do - (e, _) <- elAttr' "button" ("class" =: (primaryClass <&> "py-4 px-8") <> "name" =: name) $ text buttonText +primaryButton = primaryButton' (C.hex "00B9DA") C.White + +primaryButton' :: DomBuilder t m => C.Color -> C.Color -> Text -> m (Event t ()) +primaryButton' bgCol _ buttonText = do + (e, _) <- elAttr' "button" ("class" =: classCfg <> "name" =: name) $ text buttonText pure $ domEvent Click e where - -- for testing / selenium mainly + classCfg = primaryClass bgCol <&> C.classhUnsafe [C.py .~~ C.TWSize 4, C.px .~~ C.TWSize 8] name = T.filter (\c -> isAlphaNum c || isSpace c ) buttonText -primaryClass = - "focus:outline-none shadow-button bg-[#00B9DA] \ - \ font-[Sarabun] font-bold text-white text-body text-center rounded-xl \ - \ hover:bg-primary-rich active:bg-primary-desaturated \ - \ focus:ring-4 ring-primary ring-opacity-50 \ - \ transition-all duration-300 ease-in-out \ - \ transform hover:scale-105 active:scale-95 \ - \ whitespace-nowrap inline-block \ - \ hover:shadow-md active:shadow-lg min-[0px]:text-xs md:text-lg" +primaryClass :: C.Color -> Text +primaryClass bgCol = C.classhUnsafe [ C.bgColor .~~ bgCol + , C.br .~~ C.R_Xl + , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) + , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) + ] + , C.border . C.outline .~ [("focus", C.Outline_None)] + , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] + , C.border . C.ring . C.ringOpacity .~~ 50 + , C.custom .~ "font-[Sarabun] font-bold text-white text-body text-center \ + \ ring-primary \ + \ hover:bg-primary-rich active:bg-primary-desaturated \ + \ transform hover:scale-105 active:scale-95 \ + \ whitespace-nowrap inline-block \ + \ min-[0px]:text-xs md:text-lg" + ] example :: Template t m => m (Event t ()) example = buttonToggleBody "" True $ \case diff --git a/src/Templates/Partials/Containers.hs b/src/Templates/Partials/Containers.hs index ad9c8ce..2b978f1 100644 --- a/src/Templates/Partials/Containers.hs +++ b/src/Templates/Partials/Containers.hs @@ -17,13 +17,19 @@ screenContainer :: (DomBuilder t m) => m a -> m a screenContainer = elClass "div" $(classh' [w .~~ TWSize_Screen, h .~~ TWSize_Screen, custom .~ "flex flex-col overflow-hidden"]) toggleButton :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => Text -> m (Dynamic t Bool) -toggleButton label = do +toggleButton = toggleButton' White + +-- | Parameterized version with custom text color +toggleButton' :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => Color -> Text -> m (Dynamic t Bool) +toggleButton' txtCol label = do let classes :: Bool -> Text - classes shown = - "text-icon font-icon transform select-none " -- Icon display classes - <> "transition-transform duration-300 ease-in-out " -- Animation settings classes - <> bool "rotate-180" mempty shown -- Do a little twirl! + classes shown = classhUnsafe $ + [ custom .~ "text-icon font-icon select-none" ] + <> (if shown + then [ transform . rotate .~^ [("def", Rotate_0 `withTransition` Duration_300 `withTiming` Ease_InOut)] ] + else [ transform . rotate .~^ [("def", Rotate_180 `withTransition` Duration_300 `withTiming` Ease_InOut)] ] + ) -- The icon for a rendered container is a triangle pointing up, and -- the icon for an unrendered container is that same triangle, rotated @@ -31,10 +37,11 @@ toggleButton label = do -- be animated; Having the "neutral" position be "no transform" means -- the icon won't do a twirl on page load. rec - (labelEl, _) <- elClass' "div" $(classh' [ mt .~~ TWSize 8 - , custom .~ "cursor-pointer flex flex-row justify-between" - ]) $ do - textS $(classh' [ text_color .~~ White ]) label + (labelEl, _) <- elClass' "div" (classhUnsafe [ mt .~~ TWSize 8 + , cursor .~~ Cursor_Pointer + , custom .~ "flex flex-row justify-between" + ]) $ do + textS (classhUnsafe [ text_color .~~ txtCol ]) label elDynClass' "span" (classes <$> toggled) $ text "expand_less" let toggleEv = domEvent Click labelEl toggled <- holdUniqDyn =<< toggle True toggleEv @@ -79,18 +86,20 @@ collapsibleContainerWithImage imgSrc label body = do bodyClasses :: Bool -> Text bodyClasses shown = bool "hidden" mempty shown - toggled <- toggleButton' imgSrc label + toggled <- toggleButtonWithImage imgSrc label elDynClass "div" (bodyClasses <$> toggled) body -toggleButton' :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => Text -> Text -> m (Dynamic t Bool) -toggleButton' imgSrc label = do +toggleButtonWithImage :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => Text -> Text -> m (Dynamic t Bool) +toggleButtonWithImage imgSrc label = do let classes :: Bool -> Text - classes shown = - "text-icon font-icon transform select-none " -- Icon display classes - <> "transition-transform duration-300 ease-in-out " -- Animation settings classes - <> bool "rotate-180" mempty shown -- Do a little twirl! + classes shown = classhUnsafe $ + [ custom .~ "text-icon font-icon select-none" ] + <> (if shown + then [ transform . rotate .~^ [("def", Rotate_0 `withTransition` Duration_300 `withTiming` Ease_InOut)] ] + else [ transform . rotate .~^ [("def", Rotate_180 `withTransition` Duration_300 `withTiming` Ease_InOut)] ] + ) -- The icon for a rendered container is a triangle pointing up, and -- the icon for an unrendered container is that same triangle, rotated @@ -116,10 +125,10 @@ openCloseButton imgSrc tColor name = do buttonToggleBody (constDyn "pl-10") True $ \case True -> do --gridCol Col12 $ do - col [6] $ imgClass imgSrc $(classh' [custom .~ "rotate-180 inline-block", position .~~ centered ]) + col [6] $ imgClass imgSrc $(classh' [transform . rotate .~^ [("def", noTransition Rotate_180)], custom .~ "inline-block", position .~~ centered ]) divClass $(classh' [colSpan .|~ [6], position .~~ centered, custom .~ "inline-block"]) $ do textS (classhUnsafe [text_color .~~ tColor]) $ "close" <&> name False -> do - col [6] $ imgClass imgSrc $(classh' [custom .~ "rotate-180 inline-block", position .~~ centered ]) + col [6] $ imgClass imgSrc $(classh' [transform . rotate .~^ [("def", noTransition Rotate_180)], custom .~ "inline-block", position .~~ centered ]) divClass $(classh' [colSpan .|~ [6], position .~~ centered, custom .~ "inline-block"]) $ do textS (classhUnsafe [text_color .~~ tColor]) $ "open" <&> name diff --git a/src/Templates/Partials/Containers/Dropdown.hs b/src/Templates/Partials/Containers/Dropdown.hs index d7e0287..aa3153a 100644 --- a/src/Templates/Partials/Containers/Dropdown.hs +++ b/src/Templates/Partials/Containers/Dropdown.hs @@ -20,8 +20,31 @@ dropdown' => Map.Map a T.Text -> SelectElementConfig er t (DomBuilderSpace m) -> m (Dynamic t a) -dropdown' options cfg' = mdo - let class' = "w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:border-[#00B9DA] font-[Sarabun] text-lg mb-5 bg-white" +dropdown' = dropdown'' (hex "00B9DA") White (Gray C300) + +-- | Parameterized version with custom colors +dropdown'' + :: ( MonadFix m + , DomBuilder t m + ) + => Color -- ^ Focus border color + -> Color -- ^ Background color + -> Color -- ^ Default border color + -> Map.Map a T.Text + -> SelectElementConfig er t (DomBuilderSpace m) + -> m (Dynamic t a) +dropdown'' focusCol bgCol borderCol options cfg' = mdo + let class' = classhUnsafe [ w .~~ TWSize_Full + , px .~~ TWSize 4 + , py .~~ TWSize 3 + , bw .~~ B1 + , bc .~^ [("def", noTransition borderCol), ("focus", noTransition focusCol)] + , br .~~ R_Lg + , border . outline .~ [("focus", Outline_None)] + , mb .~~ TWSize 5 + , bgColor .~~ bgCol + , custom .~ "font-[Sarabun] text-lg" + ] let safeInitial = (snd . head $ Map.toList options) let cfg = cfg' & selectElementConfig_initialValue .~ safeInitial @@ -46,16 +69,31 @@ dropdownWithDefault -> T.Text -> SelectElementConfig er t (DomBuilderSpace m) -> m (Dynamic t a) -dropdownWithDefault options start cfg' = mdo - let class' = $(classh' [ w .~~ TWSize_Full, px .~~ TWSize 4, py .~~ TWSize 3 - , bw .~~ B1 - , bc .~^ [("def", noTransition (Gray C300)), ("focus", noTransition (hex "00B9DA"))] - , br .~~ R_Lg - , border . bStyle .~ [("focus",BNone)] - , custom .~ "focus:outline-none focus:border-" - , mb .~~ TWSize 5 - , bgColor .~~ White - ]) +dropdownWithDefault = dropdownWithDefault' (hex "00B9DA") White (Gray C300) + +-- | Parameterized version with custom colors +dropdownWithDefault' + :: ( MonadFix m + , DomBuilder t m + ) + => Color -- ^ Focus border color + -> Color -- ^ Background color + -> Color -- ^ Default border color + -> Map.Map a T.Text + -> T.Text + -> SelectElementConfig er t (DomBuilderSpace m) + -> m (Dynamic t a) +dropdownWithDefault' focusCol bgCol borderCol options start cfg' = mdo + let class' = classhUnsafe [ w .~~ TWSize_Full + , px .~~ TWSize 4 + , py .~~ TWSize 3 + , bw .~~ B1 + , bc .~^ [("def", noTransition borderCol), ("focus", noTransition focusCol)] + , br .~~ R_Lg + , border . outline .~ [("focus", Outline_None)] + , mb .~~ TWSize 5 + , bgColor .~~ bgCol + ] let safeInitial = start let cfg = cfg' & selectElementConfig_initialValue .~ safeInitial @@ -69,5 +107,5 @@ dropdownWithDefault options start cfg' = mdo where flipTup (a_,b_) = (b_,a_) makeOpt optText = do - (e, _) <- elAttr' "option" ("value" =: optText) $ textS $(classh' [text_font .~~ Font_Custom "Sarabun", text_size .~~ LG]) optText + (e, _) <- elAttr' "option" ("value" =: optText) $ textS (classhUnsafe [text_font .~~ Font_Custom "Sarabun", text_size .~~ LG]) optText pure $ optText <$ domEvent Click e diff --git a/src/Templates/Partials/Invitebar.hs b/src/Templates/Partials/Invitebar.hs index 489ada2..a33667c 100644 --- a/src/Templates/Partials/Invitebar.hs +++ b/src/Templates/Partials/Invitebar.hs @@ -22,20 +22,24 @@ emailParse _ = Right True -- | Builds an input bar for emails, it returns both the input and -- the button that sends it. invitebar :: (PostBuild t m, DomBuilder t m, MonadHold t m, MonadFix m) => Text -> m (InputEl t m, Event t ()) -invitebar placeholder = do +invitebar = invitebar' White (hex "E11D48") (hex "E11D48") + +-- | Parameterized version with custom colors +invitebar' :: (PostBuild t m, DomBuilder t m, MonadHold t m, MonadFix m) => Color -> Color -> Color -> Text -> m (InputEl t m, Event t ()) +invitebar' bgCol borderCol textCol placeholder = do rec let emailClass e = ffor (emailParse <$> e) $ \case - Right True -> "border-rose-600 text-rose-600" - _ -> "border-rose-600 text-rose-600" + Right True -> classhUnsafe [border . bc .~~ borderCol, text_color .~~ textCol] + _ -> classhUnsafe [border . bc .~~ borderCol, text_color .~~ textCol] emailText e = case emailParse e of Right True -> "Invitation sent to " <> e -- TODO: remove Right False -> "Email must be @aceinterviewprep.io" Left _ -> "Invalid email format" - invbar@(invInput, invButton) <- elClass "div" $(classh' [my .~~ TWSize 2 + invbar@(invInput, invButton) <- elClass "div" (classhUnsafe [my .~~ TWSize 2 , w .~~ TWSize_Full , custom .~ "shadow-button flex flex-row" - , bgColor .~~ White + , bgColor .~~ bgCol , br .~~ R_Normal ]) $ do elClass "button" $(classh' [ pl .~~ TWSize 2 ]) $ textS "font-icon text-icon" "email" diff --git a/src/Templates/Partials/Lists.hs b/src/Templates/Partials/Lists.hs index e6f50ad..6f32799 100644 --- a/src/Templates/Partials/Lists.hs +++ b/src/Templates/Partials/Lists.hs @@ -19,17 +19,32 @@ data ListItemConfig t = ListItemConfig , _listItemConfig_icon :: Dynamic t (Maybe Text) , _listItemConfig_highlight :: Dynamic t (Maybe Text) , _listItemConfig_unread :: Dynamic t (Maybe Int) + , _listItemConfig_hoverColor :: Color + , _listItemConfig_unreadColor :: Color + , _listItemConfig_borderColor :: Color + , _listItemConfig_textColor :: Color + , _listItemConfig_subtextColor :: Color } -- | The default configuration for list items is to have no subtext, no -- icon, no highlighting information, and to not be clickable. +-- Uses default dark theme colors. defListItemConfig :: Applicative (Dynamic t) => ListItemConfig t -defListItemConfig = ListItemConfig +defListItemConfig = defListItemConfig' (hex "2E3A59") (hex "FF6D31") (hex "E5E7EB") White (hex "D1D5DB") + +-- | Configurable version with custom colors +defListItemConfig' :: Applicative (Dynamic t) => Color -> Color -> Color -> Color -> Color -> ListItemConfig t +defListItemConfig' hoverCol unreadCol borderCol textCol subtextCol = ListItemConfig { _listItemConfig_clickable = pure False , _listItemConfig_subtext = pure Nothing , _listItemConfig_icon = pure Nothing , _listItemConfig_highlight = pure Nothing - , _listItemConfig_unread = pure Nothing + , _listItemConfig_unread = pure Nothing + , _listItemConfig_hoverColor = hoverCol + , _listItemConfig_unreadColor = unreadCol + , _listItemConfig_borderColor = borderCol + , _listItemConfig_textColor = textCol + , _listItemConfig_subtextColor = subtextCol } instance Reflex t => Default (ListItemConfig t) where @@ -53,27 +68,37 @@ listItem ) => ListItemConfig t -- ^ Visual configuration for the list item -> Dynamic t Text -- ^ The label - -> m (Event t ()) + -> m (Event t ()) -- ^ If the configuration has '_listItemConfig_clickable' set, then -- this is the 'Click' event for the list item. Otherwise, it's -- 'never'. listItem cfg label = do - let + let topClass :: (Bool, Maybe Int) -> Text - topClass (click, unread) = T.intercalate " " $ - [ "flex flex-col py-2 border-b border-metaline" ] + topClass (click, unread) = classhUnsafe $ + [ py .~~ TWSize 2 + , border . bStyle . b .~~ BSolid + , border . bw_b .~~ B1 + , border . bc_b .~~ _listItemConfig_borderColor cfg + ] <> case click of - True -> ["cursor-pointer hover:bg-[#2E3A59]"] + True -> [ cursor .~~ CursorPointer + , bgColor .~^ [("def", noTransition Transparent) + , ("hover", noTransition $ _listItemConfig_hoverColor cfg)] + ] False -> [] <> case unread of - Just _ -> [ "bg-[#FF6D31]" ] + Just _ -> [ bgColor .~~ _listItemConfig_unreadColor cfg ] Nothing -> [] let topClassDyn = topClass <$> ((,) <$> _listItemConfig_clickable cfg <*> _listItemConfig_unread cfg) - + (e, _) <- elDynClass' "div" topClassDyn $ do - elClass "div" "leading-none font-facit text-body text-xl text-white flex flex-row items-center gap-2 overflow-hidden" $ do + let labelClass = classhUnsafe [ custom .~ "leading-none font-facit text-body text-xl flex flex-row items-center gap-2 overflow-hidden" + , text_color .~~ _listItemConfig_textColor cfg + ] + elClass "div" labelClass $ do dyn_ $ ffor (_listItemConfig_icon cfg) $ \case Nothing -> blank Just icon -> elClass "span" "font-icon text-icon select-none" $ text icon @@ -81,10 +106,12 @@ listItem cfg label = do el "span" $ dyn_ $ renderHighlight cfg <$> label dyn_ $ ffor (_listItemConfig_subtext cfg) $ \case - Just subtext -> elClass "div" "mt-1 leading-none font-facit text-label text-light" $ text subtext + Just subtext -> textS (classhUnsafe [ custom .~ "mt-1 leading-none font-facit text-label" + , text_color .~~ _listItemConfig_subtextColor cfg + ]) subtext Nothing -> blank - + pure $ domEvent Click e diff --git a/src/Templates/Partials/Searchbar.hs b/src/Templates/Partials/Searchbar.hs index cfb3fb0..285c6d0 100644 --- a/src/Templates/Partials/Searchbar.hs +++ b/src/Templates/Partials/Searchbar.hs @@ -16,30 +16,41 @@ searchbar => Text -> Event t a -> m (InputEl t m) -searchbar placeholder clearEvent = do - elClass "div" $(classh' [mt .~~ TWSize 0, w .~~ TWSize_Full, bgColor .~~ White, br .~~ R_Normal, custom .~ "flex flex-row"]) $ do - elClass "button" $(classh' [ px .~~ TWSize 3 - , br .~~ R_Normal - , custom .~ "leading-none shadow-button focus:outline-none font-icon"]) $ text "search" +searchbar = searchbar' White Black + +-- | Parameterized version with custom colors +searchbar' + :: DomBuilder t m + => Color -- ^ Background color + -> Color -- ^ Text color + -> Text + -> Event t a + -> m (InputEl t m) +searchbar' bgCol txtCol placeholder clearEvent = do + elClass "div" (classhUnsafe [mt .~~ TWSize 0, w .~~ TWSize_Full, bgColor .~~ bgCol, br .~~ R_Normal, custom .~ "flex flex-row"]) $ do + elClass "button" (classhUnsafe [ px .~~ TWSize 3 + , br .~~ R_Normal + , shadow .~~ Shadow_Md + , border . outline .~ [("focus", Outline_None)] + , custom .~ "leading-none font-icon" + ]) $ text "search" + let inputClass = classhUnsafe [ w .~~ (pix 96) + , h .~~ TWSize_Full + , bgColor .~~ Transparent + , pl .~~ TWSize 2 + , py .~~ TWSize 1 + , pr .~~ TWSize 3 + , border . outline .~ [("focus", Outline_None)] + , custom .~ "flex-grow placeholder-light" + ] + <> classhUnsafe [ text_color .~~ txtCol + , text_weight .~~ Light + , text_size .~~ XL + , custom .~ "text-icon" + ] inputElement $ def - & initialAttributes .~ - ("class" =: ($(classh' [ w .~~ (pix 96) - , h .~~ TWSize_Full - , bgColor .~~ Transparent - , pl .~~ TWSize 2 - , py .~~ TWSize 1 - , pr .~~ TWSize 3 - , custom .~ "focus:outline-none flex-grow placeholder-light" - ] - ) <> $(classh' [ text_color .~~ Black - , text_weight .~~ Light - , text_size .~~ XL - , custom .~ "text-icon" - ] - ) - ) - <> "placeholder" =: placeholder - <> "type" =: "text" - ) + & initialAttributes .~ ("class" =: inputClass + <> "placeholder" =: placeholder + <> "type" =: "text") & inputElementConfig_setValue .~ (mempty <$ clearEvent) diff --git a/src/Templates/Partials/Shapes.hs b/src/Templates/Partials/Shapes.hs index 3ce558f..7fabf4f 100644 --- a/src/Templates/Partials/Shapes.hs +++ b/src/Templates/Partials/Shapes.hs @@ -6,44 +6,28 @@ import Classh import Classh.Reflex import Templates.Types import Reflex.Dom.Core -import qualified Data.Text as T --- | Simple circle with specified radius and color -circle :: DomBuilder t m => T.Text -> Color -> m () -circle rad c = circle' (parseRadius rad) c - where - -- Parse "10px" to 5.0 (radius is half diameter) - parseRadius r = case T.stripSuffix "px" r of - Just numStr -> case reads (T.unpack numStr) :: [(Float, String)] of - [(n, "")] -> n / 2 - _ -> 5.0 -- default - Nothing -> 5.0 +type Radius = Float --- | Circle with specified radius and color using ClasshSS +-- | Circle with specified diameter and color +-- Example: circle (twSize' 10) aceAccent creates a 10-unit diameter circle +circle :: DomBuilder t m => TWSizeOrFraction -> Color -> m () +circle diameter c = + elClass "div" (classhUnsafe [w .~~ diameter, h .~~ diameter, bgColor .~~ c, br .~~ R_Full]) $ do + textS (classhUnsafe [text_color .~~ c]) "." + +-- | Circle with specified radius and color (for backwards compatibility) circle' :: DomBuilder t m => Radius -> Color -> m () -circle' rad c = elClass "div" (classhUnsafe [w .~~ twSize' (rad * 2), h .~~ twSize' (rad * 2), bgColor .~~ c, br .~~ R_Full]) $ do - textS (classhUnsafe [text_color .~~ c]) "." +circle' rad c = circle (twSize' (rad * 2)) c -- | Circle with dynamic color -circleDynColor' :: Template t m => Radius -> Dynamic t Color -> m () -circleDynColor' rad dynC = elDynClass "div" (mkBoxStyle <$> dynC) $ do +circleDynColor :: Template t m => TWSizeOrFraction -> Dynamic t Color -> m () +circleDynColor diameter dynC = elDynClass "div" (mkBoxStyle <$> dynC) $ do textDynS (mkTextStyle <$> dynC) "." where mkTextStyle c' = (classhUnsafe [text_color .~~ c']) - mkBoxStyle c' = classhUnsafe [ w .~~ twSize' (rad * 2) - , h .~~ twSize' (rad * 2) + mkBoxStyle c' = classhUnsafe [ w .~~ diameter + , h .~~ diameter , bgColor .~~ c' , br .~~ R_Full ] - --- | Legacy dynamic color circle (uses raw HTML) -circleDynColor :: (PostBuild t m, DomBuilder t m) => T.Text -> Dynamic t T.Text -> m () -circleDynColor rad dynColor = - elDynAttr "span" attrs blank - where - attrs = ffor dynColor $ \color -> - ( "style" =: ("border-radius:50%; width: " <> rad <> "; height: " <> rad <> "; display: inline-block;") - <> "class" =: color - ) - -type Radius = Float diff --git a/templates.cabal b/templates.cabal index 37a22a2..6d61534 100644 --- a/templates.cabal +++ b/templates.cabal @@ -1,6 +1,6 @@ name: templates version: 0.1.0.0 -cabal-version: >= 1.8 +cabal-version: >= 1.10 build-type: Simple @@ -21,27 +21,25 @@ library Templates.Types Templates.DomExtras + default-extensions: + ConstraintKinds + FlexibleContexts + FlexibleInstances + FunctionalDependencies + GADTs + LambdaCase + MultiParamTypeClasses + RankNTypes + RecursiveDo + ScopedTypeVariables + TemplateHaskell - default-extensions: - ConstraintKinds - FlexibleContexts - FlexibleInstances - FunctionalDependencies - GADTs - LambdaCase - MultiParamTypeClasses - -- OverloadedStrings - RankNTypes - RecursiveDo - ScopedTypeVariables - TemplateHaskell - - build-depends: base + build-depends: base , ClasshSS , containers , data-default , filepath , lens , reflex-classhss - , reflex-dom - , text \ No newline at end of file + , reflex-dom-core + , text From 04b592849d9eb389fb8d7c1da000e9ce9437ba3b Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Fri, 20 Feb 2026 14:29:12 -0500 Subject: [PATCH 02/14] chore: add nix build configuration and gitignore Add nix-shell development environment for local builds with ClasshSS and reflex-classhss dependencies. Changes: - Add shell.nix with local package overrides for ClasshSS and reflex-classhss - Add default.nix for package definition - Add .gitignore to exclude dist-newstyle build artifacts --- .gitignore | 1 + default.nix | 14 ++++++++++++++ shell.nix | 25 +++++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 .gitignore create mode 100644 default.nix create mode 100644 shell.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c33954f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dist-newstyle/ diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..156f2b3 --- /dev/null +++ b/default.nix @@ -0,0 +1,14 @@ +{ mkDerivation, base, ClasshSS, containers, data-default, filepath +, lens, lib, reflex-classhss, reflex-dom-core, text +}: +mkDerivation { + pname = "templates"; + version = "0.1.0.0"; + src = ./.; + libraryHaskellDepends = [ + base ClasshSS containers data-default filepath lens reflex-classhss + reflex-dom-core text + ]; + description = "Ace templates library"; + license = lib.licenses.bsd3; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..810d9cc --- /dev/null +++ b/shell.nix @@ -0,0 +1,25 @@ +{ nixpkgs ? import {}, compiler ? "default", doBenchmark ? false }: + +let + inherit (nixpkgs) pkgs; + haskellPackages = if compiler == "default" + then pkgs.haskellPackages + else pkgs.haskell.packages.${compiler}; + + variant = if doBenchmark then pkgs.haskell.lib.doBenchmark else pkgs.lib.id; + + # Override haskellPackages to use local versions + haskellPackages' = haskellPackages.override { + overrides = self: super: { + ClasshSS = self.callCabal2nix "ClasshSS" ../ClasshSS-dev {}; + reflex-classhss = self.callCabal2nix "reflex-classhss" ../reflex-classh {}; + }; + }; + + templates = import ./default.nix; + drv = variant (haskellPackages'.callPackage templates {}); +in +pkgs.mkShell { + buildInputs = [ pkgs.cabal-install ]; + inputsFrom = [ (if pkgs.lib.inNixShell then drv.env else drv) ]; +} From 17d04ea82539d904b6223eb56439e413720ff51a Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Fri, 20 Feb 2026 17:19:59 -0500 Subject: [PATCH 03/14] fix: correct border lens usage and cursor constructor - Use shorthand lenses bw_b and bc_b directly instead of border . bw . b - Fix bStyle to not use per-side lenses (applies to all borders) - Change Cursor_Pointer to CursorPointer (correct constructor) - Fix dropdown to use pattern matching instead of partial head function - Remove unused emailClass function from invitebar - Use showTW for color rendering in custom text color class --- src/Templates/Partials/Containers.hs | 2 +- src/Templates/Partials/Containers/Dropdown.hs | 5 ++++- src/Templates/Partials/Invitebar.hs | 11 ++++------- src/Templates/Partials/Lists.hs | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Templates/Partials/Containers.hs b/src/Templates/Partials/Containers.hs index 2b978f1..a0c8961 100644 --- a/src/Templates/Partials/Containers.hs +++ b/src/Templates/Partials/Containers.hs @@ -38,7 +38,7 @@ toggleButton' txtCol label = do -- the icon won't do a twirl on page load. rec (labelEl, _) <- elClass' "div" (classhUnsafe [ mt .~~ TWSize 8 - , cursor .~~ Cursor_Pointer + , cursor .~~ CursorPointer , custom .~ "flex flex-row justify-between" ]) $ do textS (classhUnsafe [ text_color .~~ txtCol ]) label diff --git a/src/Templates/Partials/Containers/Dropdown.hs b/src/Templates/Partials/Containers/Dropdown.hs index aa3153a..4e046dd 100644 --- a/src/Templates/Partials/Containers/Dropdown.hs +++ b/src/Templates/Partials/Containers/Dropdown.hs @@ -8,6 +8,7 @@ import Reflex.Dom.Core import Control.Monad.Fix import Data.Maybe import qualified Data.Map as Map +import Data.Map ((!)) import qualified Data.Text as T @@ -45,7 +46,9 @@ dropdown'' focusCol bgCol borderCol options cfg' = mdo , bgColor .~~ bgCol , custom .~ "font-[Sarabun] text-lg" ] - let safeInitial = (snd . head $ Map.toList options) + let safeInitial = case Map.toList options of + [] -> error "dropdown' requires non-empty options map" + (k,v):_ -> v let cfg = cfg' & selectElementConfig_initialValue .~ safeInitial & selectElementConfig_setValue .~ optionsEvent diff --git a/src/Templates/Partials/Invitebar.hs b/src/Templates/Partials/Invitebar.hs index a33667c..abb1994 100644 --- a/src/Templates/Partials/Invitebar.hs +++ b/src/Templates/Partials/Invitebar.hs @@ -28,10 +28,7 @@ invitebar = invitebar' White (hex "E11D48") (hex "E11D48") invitebar' :: (PostBuild t m, DomBuilder t m, MonadHold t m, MonadFix m) => Color -> Color -> Color -> Text -> m (InputEl t m, Event t ()) invitebar' bgCol borderCol textCol placeholder = do rec - let emailClass e = ffor (emailParse <$> e) $ \case - Right True -> classhUnsafe [border . bc .~~ borderCol, text_color .~~ textCol] - _ -> classhUnsafe [border . bc .~~ borderCol, text_color .~~ textCol] - emailText e = case emailParse e of + let emailText e = case emailParse e of Right True -> "Invitation sent to " <> e -- TODO: remove Right False -> "Email must be @aceinterviewprep.io" @@ -79,10 +76,10 @@ invitebar' bgCol borderCol textCol placeholder = do invButton let - dynClass = - fmap ( (<>) $(classh' [pl .~~ TWSize 1, pt .~~ TWSize 0, pb .~~ TWSize 1, pr .~~ TWSize 3, custom .~ "text-center"])) + dynClass = + fmap ( (<>) $(classh' [pl .~~ TWSize 1, pt .~~ TWSize 0, pb .~~ TWSize 1, pr .~~ TWSize 3, custom .~ "text-center"]) + . (<>) (" text-[" <> showTW textCol <> "]")) $ toHide - <> (emailClass $ _inputElement_value invInput) elDynClass "div" dynClass $ dynText feedback diff --git a/src/Templates/Partials/Lists.hs b/src/Templates/Partials/Lists.hs index 6f32799..15102f6 100644 --- a/src/Templates/Partials/Lists.hs +++ b/src/Templates/Partials/Lists.hs @@ -77,9 +77,9 @@ listItem cfg label = do topClass :: (Bool, Maybe Int) -> Text topClass (click, unread) = classhUnsafe $ [ py .~~ TWSize 2 - , border . bStyle . b .~~ BSolid - , border . bw_b .~~ B1 - , border . bc_b .~~ _listItemConfig_borderColor cfg + , border . bStyle .~~ BSolid + , bw_b .~~ B1 + , bc_b .~~ _listItemConfig_borderColor cfg ] <> case click of True -> [ cursor .~~ CursorPointer From 64b8b774addb85c67bbb8c87d90da62fff2a0151 Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Sun, 22 Feb 2026 20:39:37 -0500 Subject: [PATCH 04/14] refactor: use WhenTW types for all color parameters - Update bgColor params to WhenTW (WithTransition GradientColor) - Update border color params to WhenTW (WithTransition Color) - Update text color params to WhenTW Color - Wrapper functions provide sensible defaults - Primed functions accept full WhenTW types for user control --- src/Templates/Partials/Buttons.hs | 52 +++++++++++------- src/Templates/Partials/Containers/Dropdown.hs | 34 +++++++----- src/Templates/Partials/Invitebar.hs | 52 +++++++++++------- src/Templates/Partials/Lists.hs | 53 ++++++++++--------- src/Templates/Partials/Searchbar.hs | 18 ++++--- src/Templates/Partials/Shapes.hs | 22 ++++---- 6 files changed, 135 insertions(+), 96 deletions(-) diff --git a/src/Templates/Partials/Buttons.hs b/src/Templates/Partials/Buttons.hs index e1d5d6d..6b77827 100644 --- a/src/Templates/Partials/Buttons.hs +++ b/src/Templates/Partials/Buttons.hs @@ -68,20 +68,22 @@ sendButton = iconButton "send" navyBlueButton :: DomBuilder t m => Text -> m (Event t ()) -navyBlueButton = navyBlueButton' (C.hex "2E3A59") C.White +navyBlueButton = navyBlueButton' + (C.only (C.noTransition (C.solidColor (C.hex "2E3A59")))) + (C.only C.White) -navyBlueButton' :: DomBuilder t m => C.Color -> C.Color -> Text -> m (Event t ()) +navyBlueButton' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.Color -> Text -> m (Event t ()) navyBlueButton' bgCol txtCol buttonText = do (e, _) <- elClass' "button" boxCfg $ C.textS textCfg buttonText pure $ domEvent Click e where - textCfg = C.classhUnsafe [ C.text_color .~~ txtCol + textCfg = C.classhUnsafe [ C.text_color .~ txtCol , C.text_font .~~ C.Font_Custom "Sarabun" , C.text_weight .~~ C.Bold ] boxCfg = C.classhUnsafe [ C.w .~~ C.TWSize_Full , C.p .~~ C.TWSize 4 - , C.bgColor .~~ bgCol + , C.bgColor .~ bgCol , C.br .~~ C.R_3Xl , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) @@ -107,14 +109,16 @@ primaryButtonImageDyn -> Height -> Width -> m (Event t ()) -primaryButtonImageDyn = primaryButtonImageDyn' (C.hex "00B9DA") C.White +primaryButtonImageDyn = primaryButtonImageDyn' + (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) + (C.only C.White) primaryButtonImageDyn' :: ( PostBuild t m , DomBuilder t m ) - => C.Color - -> C.Color + => C.WhenTW (C.WithTransition C.GradientColor) + -> C.WhenTW C.Color -> Dynamic t Text -> Height -> Width @@ -129,7 +133,7 @@ primaryButtonImageDyn' bgCol txtCol dynImageHref height width = do boxCfg = C.classhUnsafe [ C.w .~~ C.TWSize_Full , C.p .~~ C.TWSize 4 , C.mt .~~ C.TWSize 16 - , C.bgColor .~~ bgCol + , C.bgColor .~ bgCol , C.br .~~ C.R_Xl , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) @@ -148,29 +152,33 @@ primaryButtonImageDyn' bgCol txtCol dynImageHref height width = do type Height = Text type Width = Text primaryButtonImage :: DomBuilder t m => Text -> Height -> Width -> m (Event t ()) -primaryButtonImage = primaryButtonImage' (C.hex "00B9DA") C.White +primaryButtonImage = primaryButtonImage' + (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) + (C.only C.White) -primaryButtonImage' :: DomBuilder t m => C.Color -> C.Color -> Text -> Height -> Width -> m (Event t ()) +primaryButtonImage' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.Color -> Text -> Height -> Width -> m (Event t ()) primaryButtonImage' bgCol _ imageHref height width = do (e, _) <- elClass' "button" (primaryButtonBoxCfg bgCol) $ elAttr "img" ("src" =: imageHref <> "height" =: height <> "width" =: width <> "class" =: "block mx-auto") blank pure $ domEvent Click e primaryButtonImageText :: DomBuilder t m => Text -> Height -> Width -> Text -> m (Event t ()) -primaryButtonImageText = primaryButtonImageText' (C.hex "00B9DA") C.White +primaryButtonImageText = primaryButtonImageText' + (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) + (C.only C.White) -primaryButtonImageText' :: DomBuilder t m => C.Color -> C.Color -> Text -> Height -> Width -> Text -> m (Event t ()) +primaryButtonImageText' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.Color -> Text -> Height -> Width -> Text -> m (Event t ()) primaryButtonImageText' bgCol _ imageHref height width bottomText = do (e, _) <- elClass' "button" (primaryButtonBoxCfg bgCol) $ do elAttr "img" ("src" =: imageHref <> "height" =: height <> "width" =: width <> "class" =: "block mx-auto") blank text bottomText pure $ domEvent Click e -primaryButtonBoxCfg :: C.Color -> Text +primaryButtonBoxCfg :: C.WhenTW (C.WithTransition C.GradientColor) -> Text primaryButtonBoxCfg bgCol = C.classhUnsafe [ C.w .~~ C.TWSize_Full , C.p .~~ C.TWSize 4 , C.mt .~~ C.TWSize 16 - , C.bgColor .~~ bgCol + , C.bgColor .~ bgCol , C.br .~~ C.R_Xl , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) @@ -186,9 +194,11 @@ primaryButtonBoxCfg bgCol = C.classhUnsafe [ C.w .~~ C.TWSize_Full primaryButtonSized :: DomBuilder t m => TWSize -> TWSize -> Text -> m (Event t ()) -primaryButtonSized = primaryButtonSized' (C.hex "00B9DA") C.White +primaryButtonSized = primaryButtonSized' + (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) + (C.only C.White) -primaryButtonSized' :: DomBuilder t m => C.Color -> C.Color -> TWSize -> TWSize -> Text -> m (Event t ()) +primaryButtonSized' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.Color -> TWSize -> TWSize -> Text -> m (Event t ()) primaryButtonSized' bgCol _ height width buttonText = do (e, _) <- elAttr' "button" ("class" =: classCfg <> "name" =: name) $ text buttonText pure $ domEvent Click e @@ -198,9 +208,11 @@ primaryButtonSized' bgCol _ height width buttonText = do primaryButton :: DomBuilder t m => Text -> m (Event t ()) -primaryButton = primaryButton' (C.hex "00B9DA") C.White +primaryButton = primaryButton' + (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) + (C.only C.White) -primaryButton' :: DomBuilder t m => C.Color -> C.Color -> Text -> m (Event t ()) +primaryButton' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.Color -> Text -> m (Event t ()) primaryButton' bgCol _ buttonText = do (e, _) <- elAttr' "button" ("class" =: classCfg <> "name" =: name) $ text buttonText pure $ domEvent Click e @@ -208,8 +220,8 @@ primaryButton' bgCol _ buttonText = do classCfg = primaryClass bgCol <&> C.classhUnsafe [C.py .~~ C.TWSize 4, C.px .~~ C.TWSize 8] name = T.filter (\c -> isAlphaNum c || isSpace c ) buttonText -primaryClass :: C.Color -> Text -primaryClass bgCol = C.classhUnsafe [ C.bgColor .~~ bgCol +primaryClass :: C.WhenTW (C.WithTransition C.GradientColor) -> Text +primaryClass bgCol = C.classhUnsafe [ C.bgColor .~ bgCol , C.br .~~ C.R_Xl , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) diff --git a/src/Templates/Partials/Containers/Dropdown.hs b/src/Templates/Partials/Containers/Dropdown.hs index 4e046dd..51e904a 100644 --- a/src/Templates/Partials/Containers/Dropdown.hs +++ b/src/Templates/Partials/Containers/Dropdown.hs @@ -21,29 +21,32 @@ dropdown' => Map.Map a T.Text -> SelectElementConfig er t (DomBuilderSpace m) -> m (Dynamic t a) -dropdown' = dropdown'' (hex "00B9DA") White (Gray C300) +dropdown' = dropdown'' + (only (noTransition (solidColor White))) + [ ("def", noTransition (Gray C300)) + , ("focus", noTransition (hex "00B9DA")) + ] -- | Parameterized version with custom colors dropdown'' :: ( MonadFix m , DomBuilder t m ) - => Color -- ^ Focus border color - -> Color -- ^ Background color - -> Color -- ^ Default border color + => WhenTW (WithTransition GradientColor) -- ^ Background color + -> WhenTW (WithTransition Color) -- ^ Border color (including focus state) -> Map.Map a T.Text -> SelectElementConfig er t (DomBuilderSpace m) -> m (Dynamic t a) -dropdown'' focusCol bgCol borderCol options cfg' = mdo +dropdown'' bgCol borderCol options cfg' = mdo let class' = classhUnsafe [ w .~~ TWSize_Full , px .~~ TWSize 4 , py .~~ TWSize 3 , bw .~~ B1 - , bc .~^ [("def", noTransition borderCol), ("focus", noTransition focusCol)] + , bc .~ borderCol , br .~~ R_Lg , border . outline .~ [("focus", Outline_None)] , mb .~~ TWSize 5 - , bgColor .~~ bgCol + , bgColor .~ bgCol , custom .~ "font-[Sarabun] text-lg" ] let safeInitial = case Map.toList options of @@ -72,30 +75,33 @@ dropdownWithDefault -> T.Text -> SelectElementConfig er t (DomBuilderSpace m) -> m (Dynamic t a) -dropdownWithDefault = dropdownWithDefault' (hex "00B9DA") White (Gray C300) +dropdownWithDefault = dropdownWithDefault' + (only (noTransition (solidColor White))) + [ ("def", noTransition (Gray C300)) + , ("focus", noTransition (hex "00B9DA")) + ] -- | Parameterized version with custom colors dropdownWithDefault' :: ( MonadFix m , DomBuilder t m ) - => Color -- ^ Focus border color - -> Color -- ^ Background color - -> Color -- ^ Default border color + => WhenTW (WithTransition GradientColor) -- ^ Background color + -> WhenTW (WithTransition Color) -- ^ Border color (including focus state) -> Map.Map a T.Text -> T.Text -> SelectElementConfig er t (DomBuilderSpace m) -> m (Dynamic t a) -dropdownWithDefault' focusCol bgCol borderCol options start cfg' = mdo +dropdownWithDefault' bgCol borderCol options start cfg' = mdo let class' = classhUnsafe [ w .~~ TWSize_Full , px .~~ TWSize 4 , py .~~ TWSize 3 , bw .~~ B1 - , bc .~^ [("def", noTransition borderCol), ("focus", noTransition focusCol)] + , bc .~ borderCol , br .~~ R_Lg , border . outline .~ [("focus", Outline_None)] , mb .~~ TWSize 5 - , bgColor .~~ bgCol + , bgColor .~ bgCol ] let safeInitial = start let cfg = cfg' diff --git a/src/Templates/Partials/Invitebar.hs b/src/Templates/Partials/Invitebar.hs index abb1994..059353f 100644 --- a/src/Templates/Partials/Invitebar.hs +++ b/src/Templates/Partials/Invitebar.hs @@ -22,11 +22,22 @@ emailParse _ = Right True -- | Builds an input bar for emails, it returns both the input and -- the button that sends it. invitebar :: (PostBuild t m, DomBuilder t m, MonadHold t m, MonadFix m) => Text -> m (InputEl t m, Event t ()) -invitebar = invitebar' White (hex "E11D48") (hex "E11D48") +invitebar = invitebar' + (only (noTransition (solidColor White))) + (only (noTransition (solidColor Transparent))) + (only (noTransition (hex "E11D48"))) + (only (hex "E11D48")) -- | Parameterized version with custom colors -invitebar' :: (PostBuild t m, DomBuilder t m, MonadHold t m, MonadFix m) => Color -> Color -> Color -> Text -> m (InputEl t m, Event t ()) -invitebar' bgCol borderCol textCol placeholder = do +invitebar' + :: (PostBuild t m, DomBuilder t m, MonadHold t m, MonadFix m) + => WhenTW (WithTransition GradientColor) -- ^ Container background color + -> WhenTW (WithTransition GradientColor) -- ^ Input background color + -> WhenTW (WithTransition Color) -- ^ Border color + -> WhenTW Color -- ^ Feedback text color + -> Text + -> m (InputEl t m, Event t ()) +invitebar' bgCol inputBgCol borderCol textCol placeholder = do rec let emailText e = case emailParse e of Right True -> "Invitation sent to " <> e @@ -36,23 +47,23 @@ invitebar' bgCol borderCol textCol placeholder = do invbar@(invInput, invButton) <- elClass "div" (classhUnsafe [my .~~ TWSize 2 , w .~~ TWSize_Full , custom .~ "shadow-button flex flex-row" - , bgColor .~~ bgCol + , bgColor .~ bgCol , br .~~ R_Normal ]) $ do elClass "button" $(classh' [ pl .~~ TWSize 2 ]) $ textS "font-icon text-icon" "email" invInput' <- inputElement $ def & initialAttributes .~ ("class" =: - $(classh' [ w .~~ TWSize_Full - , h .~~ TWSize_Full - , bgColor .~~ Transparent - , px .~~ TWSize 1 - , pt .~~ TWSize 3 - , pb .~~ TWSize 3 - , pr .~~ TWSize 2 - , custom .~ "focus:outline-none flex-grow placeholder-light font-label" - ] - ) + (classhUnsafe [ w .~~ TWSize_Full + , h .~~ TWSize_Full + , bgColor .~ inputBgCol + , px .~~ TWSize 1 + , pt .~~ TWSize 3 + , pb .~~ TWSize 3 + , pr .~~ TWSize 2 + , custom .~ "focus:outline-none flex-grow placeholder-light font-label" + ] + ) <> "placeholder" =: placeholder <> "type" =: "email" ) @@ -76,10 +87,15 @@ invitebar' bgCol borderCol textCol placeholder = do invButton let - dynClass = - fmap ( (<>) $(classh' [pl .~~ TWSize 1, pt .~~ TWSize 0, pb .~~ TWSize 1, pr .~~ TWSize 3, custom .~ "text-center"]) - . (<>) (" text-[" <> showTW textCol <> "]")) - $ toHide + boxClass = classhUnsafe [ pl .~~ TWSize 1 + , pt .~~ TWSize 0 + , pb .~~ TWSize 1 + , pr .~~ TWSize 3 + , custom .~ "text-center" + ] + textClass = classhUnsafe [text_color .~ textCol] + baseClass = boxClass <> " " <> textClass + dynClass = fmap (<> baseClass) toHide elDynClass "div" dynClass $ dynText feedback diff --git a/src/Templates/Partials/Lists.hs b/src/Templates/Partials/Lists.hs index 15102f6..8d5c429 100644 --- a/src/Templates/Partials/Lists.hs +++ b/src/Templates/Partials/Lists.hs @@ -13,35 +13,44 @@ import qualified Data.Text as T import Reflex.Dom.Core -- | Configuration for a list item. +-- All color fields use WhenTW for full user control over states and transitions. data ListItemConfig t = ListItemConfig { _listItemConfig_clickable :: Dynamic t Bool , _listItemConfig_subtext :: Dynamic t (Maybe Text) , _listItemConfig_icon :: Dynamic t (Maybe Text) , _listItemConfig_highlight :: Dynamic t (Maybe Text) , _listItemConfig_unread :: Dynamic t (Maybe Int) - , _listItemConfig_hoverColor :: Color - , _listItemConfig_unreadColor :: Color - , _listItemConfig_borderColor :: Color - , _listItemConfig_textColor :: Color - , _listItemConfig_subtextColor :: Color + , _listItemConfig_bgColor :: WhenTW (WithTransition GradientColor) + , _listItemConfig_borderColor :: WhenTW (WithTransition Color) + , _listItemConfig_textColor :: WhenTW Color + , _listItemConfig_subtextColor :: WhenTW Color } -- | The default configuration for list items is to have no subtext, no -- icon, no highlighting information, and to not be clickable. -- Uses default dark theme colors. defListItemConfig :: Applicative (Dynamic t) => ListItemConfig t -defListItemConfig = defListItemConfig' (hex "2E3A59") (hex "FF6D31") (hex "E5E7EB") White (hex "D1D5DB") +defListItemConfig = defListItemConfig' + (only (noTransition (solidColor Transparent))) + (only (noTransition (hex "E5E7EB"))) + (only White) + (only (hex "D1D5DB")) -- | Configurable version with custom colors -defListItemConfig' :: Applicative (Dynamic t) => Color -> Color -> Color -> Color -> Color -> ListItemConfig t -defListItemConfig' hoverCol unreadCol borderCol textCol subtextCol = ListItemConfig +defListItemConfig' + :: Applicative (Dynamic t) + => WhenTW (WithTransition GradientColor) -- ^ Background color (user controls states/transitions) + -> WhenTW (WithTransition Color) -- ^ Border color + -> WhenTW Color -- ^ Text color + -> WhenTW Color -- ^ Subtext color + -> ListItemConfig t +defListItemConfig' bgCol borderCol textCol subtextCol = ListItemConfig { _listItemConfig_clickable = pure False , _listItemConfig_subtext = pure Nothing , _listItemConfig_icon = pure Nothing , _listItemConfig_highlight = pure Nothing , _listItemConfig_unread = pure Nothing - , _listItemConfig_hoverColor = hoverCol - , _listItemConfig_unreadColor = unreadCol + , _listItemConfig_bgColor = bgCol , _listItemConfig_borderColor = borderCol , _listItemConfig_textColor = textCol , _listItemConfig_subtextColor = subtextCol @@ -74,29 +83,21 @@ listItem -- 'never'. listItem cfg label = do let - topClass :: (Bool, Maybe Int) -> Text - topClass (click, unread) = classhUnsafe $ + topClass :: Bool -> Text + topClass click = classhUnsafe $ [ py .~~ TWSize 2 , border . bStyle .~~ BSolid , bw_b .~~ B1 - , bc_b .~~ _listItemConfig_borderColor cfg + , bc_b .~ _listItemConfig_borderColor cfg + , bgColor .~ _listItemConfig_bgColor cfg ] - <> case click of - True -> [ cursor .~~ CursorPointer - , bgColor .~^ [("def", noTransition Transparent) - , ("hover", noTransition $ _listItemConfig_hoverColor cfg)] - ] - False -> [] - <> case unread of - Just _ -> [ bgColor .~~ _listItemConfig_unreadColor cfg ] - Nothing -> [] - let topClassDyn = topClass <$> ((,) <$> _listItemConfig_clickable cfg <*> _listItemConfig_unread cfg) - + <> if click then [ cursor .~~ CursorPointer ] else [] + let topClassDyn = topClass <$> _listItemConfig_clickable cfg (e, _) <- elDynClass' "div" topClassDyn $ do let labelClass = classhUnsafe [ custom .~ "leading-none font-facit text-body text-xl flex flex-row items-center gap-2 overflow-hidden" - , text_color .~~ _listItemConfig_textColor cfg + , text_color .~ _listItemConfig_textColor cfg ] elClass "div" labelClass $ do dyn_ $ ffor (_listItemConfig_icon cfg) $ \case @@ -107,7 +108,7 @@ listItem cfg label = do dyn_ $ ffor (_listItemConfig_subtext cfg) $ \case Just subtext -> textS (classhUnsafe [ custom .~ "mt-1 leading-none font-facit text-label" - , text_color .~~ _listItemConfig_subtextColor cfg + , text_color .~ _listItemConfig_subtextColor cfg ]) subtext Nothing -> blank diff --git a/src/Templates/Partials/Searchbar.hs b/src/Templates/Partials/Searchbar.hs index 285c6d0..075833e 100644 --- a/src/Templates/Partials/Searchbar.hs +++ b/src/Templates/Partials/Searchbar.hs @@ -16,18 +16,22 @@ searchbar => Text -> Event t a -> m (InputEl t m) -searchbar = searchbar' White Black +searchbar = searchbar' + (only (noTransition (solidColor White))) + (only (noTransition (solidColor Transparent))) + (only Black) -- | Parameterized version with custom colors searchbar' :: DomBuilder t m - => Color -- ^ Background color - -> Color -- ^ Text color + => WhenTW (WithTransition GradientColor) -- ^ Container background color + -> WhenTW (WithTransition GradientColor) -- ^ Input background color + -> WhenTW Color -- ^ Text color -> Text -> Event t a -> m (InputEl t m) -searchbar' bgCol txtCol placeholder clearEvent = do - elClass "div" (classhUnsafe [mt .~~ TWSize 0, w .~~ TWSize_Full, bgColor .~~ bgCol, br .~~ R_Normal, custom .~ "flex flex-row"]) $ do +searchbar' bgCol inputBgCol txtCol placeholder clearEvent = do + elClass "div" (classhUnsafe [mt .~~ TWSize 0, w .~~ TWSize_Full, bgColor .~ bgCol, br .~~ R_Normal, custom .~ "flex flex-row"]) $ do elClass "button" (classhUnsafe [ px .~~ TWSize 3 , br .~~ R_Normal , shadow .~~ Shadow_Md @@ -36,14 +40,14 @@ searchbar' bgCol txtCol placeholder clearEvent = do ]) $ text "search" let inputClass = classhUnsafe [ w .~~ (pix 96) , h .~~ TWSize_Full - , bgColor .~~ Transparent + , bgColor .~ inputBgCol , pl .~~ TWSize 2 , py .~~ TWSize 1 , pr .~~ TWSize 3 , border . outline .~ [("focus", Outline_None)] , custom .~ "flex-grow placeholder-light" ] - <> classhUnsafe [ text_color .~~ txtCol + <> classhUnsafe [ text_color .~ txtCol , text_weight .~~ Light , text_size .~~ XL , custom .~ "text-icon" diff --git a/src/Templates/Partials/Shapes.hs b/src/Templates/Partials/Shapes.hs index 7fabf4f..3e5922c 100644 --- a/src/Templates/Partials/Shapes.hs +++ b/src/Templates/Partials/Shapes.hs @@ -9,21 +9,21 @@ import Reflex.Dom.Core type Radius = Float --- | Circle with specified diameter and color --- Example: circle (twSize' 10) aceAccent creates a 10-unit diameter circle -circle :: DomBuilder t m => TWSizeOrFraction -> Color -> m () -circle diameter c = - elClass "div" (classhUnsafe [w .~~ diameter, h .~~ diameter, bgColor .~~ c, br .~~ R_Full]) $ do - textS (classhUnsafe [text_color .~~ c]) "." +-- | Circle with specified diameter and background/text colors +-- Example: circle (twSize' 10) (only (noTransition (solidColor aceAccent))) (only White) +circle :: DomBuilder t m => TWSizeOrFraction -> WhenTW (WithTransition GradientColor) -> WhenTW Color -> m () +circle diameter bgCol txtCol = + elClass "div" (classhUnsafe [w .~~ diameter, h .~~ diameter, bgColor .~ bgCol, br .~~ R_Full]) $ do + textS (classhUnsafe [text_color .~ txtCol]) "." -- | Circle with specified radius and color (for backwards compatibility) -circle' :: DomBuilder t m => Radius -> Color -> m () -circle' rad c = circle (twSize' (rad * 2)) c +circle' :: DomBuilder t m => Radius -> WhenTW (WithTransition GradientColor) -> WhenTW Color -> m () +circle' rad bgCol txtCol = circle (twSize' (rad * 2)) bgCol txtCol -- | Circle with dynamic color -circleDynColor :: Template t m => TWSizeOrFraction -> Dynamic t Color -> m () -circleDynColor diameter dynC = elDynClass "div" (mkBoxStyle <$> dynC) $ do - textDynS (mkTextStyle <$> dynC) "." +circleDynColor :: Template t m => TWSizeOrFraction -> Dynamic t GradientColor -> Dynamic t Color -> m () +circleDynColor diameter dynBgC dynTxtC = elDynClass "div" (mkBoxStyle <$> dynBgC) $ do + textDynS (mkTextStyle <$> dynTxtC) "." where mkTextStyle c' = (classhUnsafe [text_color .~~ c']) mkBoxStyle c' = classhUnsafe [ w .~~ diameter From 0bcef63e122adaafa13c2d6a08a310aa7891f4c9 Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Mon, 23 Feb 2026 11:03:47 -0500 Subject: [PATCH 05/14] Update templates for ColorWithOpacity API - Update type signatures from Color to ColorWithOpacity - Update border color types from WithTransition Color to WithTransition ColorWithOpacity - Wrap bare Color values with 'color' helper function --- src/Templates/Partials/Buttons.hs | 24 +++++++++---------- src/Templates/Partials/Containers.hs | 6 ++--- src/Templates/Partials/Containers/Dropdown.hs | 12 +++++----- src/Templates/Partials/Invitebar.hs | 8 +++---- src/Templates/Partials/Lists.hs | 18 +++++++------- src/Templates/Partials/Searchbar.hs | 4 ++-- src/Templates/Partials/Shapes.hs | 8 +++---- 7 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/Templates/Partials/Buttons.hs b/src/Templates/Partials/Buttons.hs index 6b77827..af63b5d 100644 --- a/src/Templates/Partials/Buttons.hs +++ b/src/Templates/Partials/Buttons.hs @@ -70,9 +70,9 @@ sendButton = iconButton "send" navyBlueButton :: DomBuilder t m => Text -> m (Event t ()) navyBlueButton = navyBlueButton' (C.only (C.noTransition (C.solidColor (C.hex "2E3A59")))) - (C.only C.White) + (C.only (C.color C.White)) -navyBlueButton' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.Color -> Text -> m (Event t ()) +navyBlueButton' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.ColorWithOpacity -> Text -> m (Event t ()) navyBlueButton' bgCol txtCol buttonText = do (e, _) <- elClass' "button" boxCfg $ C.textS textCfg buttonText pure $ domEvent Click e @@ -111,14 +111,14 @@ primaryButtonImageDyn -> m (Event t ()) primaryButtonImageDyn = primaryButtonImageDyn' (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) - (C.only C.White) + (C.only (C.color C.White)) primaryButtonImageDyn' :: ( PostBuild t m , DomBuilder t m ) => C.WhenTW (C.WithTransition C.GradientColor) - -> C.WhenTW C.Color + -> C.WhenTW C.ColorWithOpacity -> Dynamic t Text -> Height -> Width @@ -154,9 +154,9 @@ type Width = Text primaryButtonImage :: DomBuilder t m => Text -> Height -> Width -> m (Event t ()) primaryButtonImage = primaryButtonImage' (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) - (C.only C.White) + (C.only (C.color C.White)) -primaryButtonImage' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.Color -> Text -> Height -> Width -> m (Event t ()) +primaryButtonImage' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.ColorWithOpacity -> Text -> Height -> Width -> m (Event t ()) primaryButtonImage' bgCol _ imageHref height width = do (e, _) <- elClass' "button" (primaryButtonBoxCfg bgCol) $ elAttr "img" ("src" =: imageHref <> "height" =: height <> "width" =: width <> "class" =: "block mx-auto") blank @@ -165,9 +165,9 @@ primaryButtonImage' bgCol _ imageHref height width = do primaryButtonImageText :: DomBuilder t m => Text -> Height -> Width -> Text -> m (Event t ()) primaryButtonImageText = primaryButtonImageText' (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) - (C.only C.White) + (C.only (C.color C.White)) -primaryButtonImageText' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.Color -> Text -> Height -> Width -> Text -> m (Event t ()) +primaryButtonImageText' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.ColorWithOpacity -> Text -> Height -> Width -> Text -> m (Event t ()) primaryButtonImageText' bgCol _ imageHref height width bottomText = do (e, _) <- elClass' "button" (primaryButtonBoxCfg bgCol) $ do elAttr "img" ("src" =: imageHref <> "height" =: height <> "width" =: width <> "class" =: "block mx-auto") blank @@ -196,9 +196,9 @@ primaryButtonBoxCfg bgCol = C.classhUnsafe [ C.w .~~ C.TWSize_Full primaryButtonSized :: DomBuilder t m => TWSize -> TWSize -> Text -> m (Event t ()) primaryButtonSized = primaryButtonSized' (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) - (C.only C.White) + (C.only (C.color C.White)) -primaryButtonSized' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.Color -> TWSize -> TWSize -> Text -> m (Event t ()) +primaryButtonSized' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.ColorWithOpacity -> TWSize -> TWSize -> Text -> m (Event t ()) primaryButtonSized' bgCol _ height width buttonText = do (e, _) <- elAttr' "button" ("class" =: classCfg <> "name" =: name) $ text buttonText pure $ domEvent Click e @@ -210,9 +210,9 @@ primaryButtonSized' bgCol _ height width buttonText = do primaryButton :: DomBuilder t m => Text -> m (Event t ()) primaryButton = primaryButton' (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) - (C.only C.White) + (C.only (C.color C.White)) -primaryButton' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.Color -> Text -> m (Event t ()) +primaryButton' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.ColorWithOpacity -> Text -> m (Event t ()) primaryButton' bgCol _ buttonText = do (e, _) <- elAttr' "button" ("class" =: classCfg <> "name" =: name) $ text buttonText pure $ domEvent Click e diff --git a/src/Templates/Partials/Containers.hs b/src/Templates/Partials/Containers.hs index a0c8961..bc997fb 100644 --- a/src/Templates/Partials/Containers.hs +++ b/src/Templates/Partials/Containers.hs @@ -17,10 +17,10 @@ screenContainer :: (DomBuilder t m) => m a -> m a screenContainer = elClass "div" $(classh' [w .~~ TWSize_Screen, h .~~ TWSize_Screen, custom .~ "flex flex-col overflow-hidden"]) toggleButton :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => Text -> m (Dynamic t Bool) -toggleButton = toggleButton' White +toggleButton = toggleButton' (color White) -- | Parameterized version with custom text color -toggleButton' :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => Color -> Text -> m (Dynamic t Bool) +toggleButton' :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => ColorWithOpacity -> Text -> m (Dynamic t Bool) toggleButton' txtCol label = do let classes :: Bool -> Text @@ -120,7 +120,7 @@ toggleButtonWithImage imgSrc label = do -openCloseButton :: Template t m => ImgSrc -> Color -> T.Text -> m (Event t ()) +openCloseButton :: Template t m => ImgSrc -> ColorWithOpacity -> T.Text -> m (Event t ()) openCloseButton imgSrc tColor name = do buttonToggleBody (constDyn "pl-10") True $ \case True -> do diff --git a/src/Templates/Partials/Containers/Dropdown.hs b/src/Templates/Partials/Containers/Dropdown.hs index 51e904a..fec9414 100644 --- a/src/Templates/Partials/Containers/Dropdown.hs +++ b/src/Templates/Partials/Containers/Dropdown.hs @@ -23,8 +23,8 @@ dropdown' -> m (Dynamic t a) dropdown' = dropdown'' (only (noTransition (solidColor White))) - [ ("def", noTransition (Gray C300)) - , ("focus", noTransition (hex "00B9DA")) + [ ("def", noTransition (color (Gray C300))) + , ("focus", noTransition (color (hex "00B9DA"))) ] -- | Parameterized version with custom colors @@ -33,7 +33,7 @@ dropdown'' , DomBuilder t m ) => WhenTW (WithTransition GradientColor) -- ^ Background color - -> WhenTW (WithTransition Color) -- ^ Border color (including focus state) + -> WhenTW (WithTransition ColorWithOpacity) -- ^ Border color (including focus state) -> Map.Map a T.Text -> SelectElementConfig er t (DomBuilderSpace m) -> m (Dynamic t a) @@ -77,8 +77,8 @@ dropdownWithDefault -> m (Dynamic t a) dropdownWithDefault = dropdownWithDefault' (only (noTransition (solidColor White))) - [ ("def", noTransition (Gray C300)) - , ("focus", noTransition (hex "00B9DA")) + [ ("def", noTransition (color (Gray C300))) + , ("focus", noTransition (color (hex "00B9DA"))) ] -- | Parameterized version with custom colors @@ -87,7 +87,7 @@ dropdownWithDefault' , DomBuilder t m ) => WhenTW (WithTransition GradientColor) -- ^ Background color - -> WhenTW (WithTransition Color) -- ^ Border color (including focus state) + -> WhenTW (WithTransition ColorWithOpacity) -- ^ Border color (including focus state) -> Map.Map a T.Text -> T.Text -> SelectElementConfig er t (DomBuilderSpace m) diff --git a/src/Templates/Partials/Invitebar.hs b/src/Templates/Partials/Invitebar.hs index 059353f..1f7cd70 100644 --- a/src/Templates/Partials/Invitebar.hs +++ b/src/Templates/Partials/Invitebar.hs @@ -25,16 +25,16 @@ invitebar :: (PostBuild t m, DomBuilder t m, MonadHold t m, MonadFix m) => Text invitebar = invitebar' (only (noTransition (solidColor White))) (only (noTransition (solidColor Transparent))) - (only (noTransition (hex "E11D48"))) - (only (hex "E11D48")) + (only (noTransition (color (hex "E11D48")))) + (only (color (hex "E11D48"))) -- | Parameterized version with custom colors invitebar' :: (PostBuild t m, DomBuilder t m, MonadHold t m, MonadFix m) => WhenTW (WithTransition GradientColor) -- ^ Container background color -> WhenTW (WithTransition GradientColor) -- ^ Input background color - -> WhenTW (WithTransition Color) -- ^ Border color - -> WhenTW Color -- ^ Feedback text color + -> WhenTW (WithTransition ColorWithOpacity) -- ^ Border color + -> WhenTW ColorWithOpacity -- ^ Feedback text color -> Text -> m (InputEl t m, Event t ()) invitebar' bgCol inputBgCol borderCol textCol placeholder = do diff --git a/src/Templates/Partials/Lists.hs b/src/Templates/Partials/Lists.hs index 8d5c429..5d7eec6 100644 --- a/src/Templates/Partials/Lists.hs +++ b/src/Templates/Partials/Lists.hs @@ -21,9 +21,9 @@ data ListItemConfig t = ListItemConfig , _listItemConfig_highlight :: Dynamic t (Maybe Text) , _listItemConfig_unread :: Dynamic t (Maybe Int) , _listItemConfig_bgColor :: WhenTW (WithTransition GradientColor) - , _listItemConfig_borderColor :: WhenTW (WithTransition Color) - , _listItemConfig_textColor :: WhenTW Color - , _listItemConfig_subtextColor :: WhenTW Color + , _listItemConfig_borderColor :: WhenTW (WithTransition ColorWithOpacity) + , _listItemConfig_textColor :: WhenTW ColorWithOpacity + , _listItemConfig_subtextColor :: WhenTW ColorWithOpacity } -- | The default configuration for list items is to have no subtext, no @@ -32,17 +32,17 @@ data ListItemConfig t = ListItemConfig defListItemConfig :: Applicative (Dynamic t) => ListItemConfig t defListItemConfig = defListItemConfig' (only (noTransition (solidColor Transparent))) - (only (noTransition (hex "E5E7EB"))) - (only White) - (only (hex "D1D5DB")) + (only (noTransition (color (hex "E5E7EB")))) + (only (color White)) + (only (color (hex "D1D5DB"))) -- | Configurable version with custom colors defListItemConfig' :: Applicative (Dynamic t) => WhenTW (WithTransition GradientColor) -- ^ Background color (user controls states/transitions) - -> WhenTW (WithTransition Color) -- ^ Border color - -> WhenTW Color -- ^ Text color - -> WhenTW Color -- ^ Subtext color + -> WhenTW (WithTransition ColorWithOpacity) -- ^ Border color + -> WhenTW ColorWithOpacity -- ^ Text color + -> WhenTW ColorWithOpacity -- ^ Subtext color -> ListItemConfig t defListItemConfig' bgCol borderCol textCol subtextCol = ListItemConfig { _listItemConfig_clickable = pure False diff --git a/src/Templates/Partials/Searchbar.hs b/src/Templates/Partials/Searchbar.hs index 075833e..376f614 100644 --- a/src/Templates/Partials/Searchbar.hs +++ b/src/Templates/Partials/Searchbar.hs @@ -19,14 +19,14 @@ searchbar searchbar = searchbar' (only (noTransition (solidColor White))) (only (noTransition (solidColor Transparent))) - (only Black) + (only (color Black)) -- | Parameterized version with custom colors searchbar' :: DomBuilder t m => WhenTW (WithTransition GradientColor) -- ^ Container background color -> WhenTW (WithTransition GradientColor) -- ^ Input background color - -> WhenTW Color -- ^ Text color + -> WhenTW ColorWithOpacity -- ^ Text color -> Text -> Event t a -> m (InputEl t m) diff --git a/src/Templates/Partials/Shapes.hs b/src/Templates/Partials/Shapes.hs index 3e5922c..8501ced 100644 --- a/src/Templates/Partials/Shapes.hs +++ b/src/Templates/Partials/Shapes.hs @@ -10,18 +10,18 @@ import Reflex.Dom.Core type Radius = Float -- | Circle with specified diameter and background/text colors --- Example: circle (twSize' 10) (only (noTransition (solidColor aceAccent))) (only White) -circle :: DomBuilder t m => TWSizeOrFraction -> WhenTW (WithTransition GradientColor) -> WhenTW Color -> m () +-- Example: circle (twSize' 10) (only (noTransition (solidColor aceAccent))) (only (color White)) +circle :: DomBuilder t m => TWSizeOrFraction -> WhenTW (WithTransition GradientColor) -> WhenTW ColorWithOpacity -> m () circle diameter bgCol txtCol = elClass "div" (classhUnsafe [w .~~ diameter, h .~~ diameter, bgColor .~ bgCol, br .~~ R_Full]) $ do textS (classhUnsafe [text_color .~ txtCol]) "." -- | Circle with specified radius and color (for backwards compatibility) -circle' :: DomBuilder t m => Radius -> WhenTW (WithTransition GradientColor) -> WhenTW Color -> m () +circle' :: DomBuilder t m => Radius -> WhenTW (WithTransition GradientColor) -> WhenTW ColorWithOpacity -> m () circle' rad bgCol txtCol = circle (twSize' (rad * 2)) bgCol txtCol -- | Circle with dynamic color -circleDynColor :: Template t m => TWSizeOrFraction -> Dynamic t GradientColor -> Dynamic t Color -> m () +circleDynColor :: Template t m => TWSizeOrFraction -> Dynamic t GradientColor -> Dynamic t ColorWithOpacity -> m () circleDynColor diameter dynBgC dynTxtC = elDynClass "div" (mkBoxStyle <$> dynBgC) $ do textDynS (mkTextStyle <$> dynTxtC) "." where From 40134b13300b0e821a12d1bdc5dad021f89a45c2 Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Sun, 1 Mar 2026 18:29:18 -0500 Subject: [PATCH 06/14] refactor(buttons): convert iconButton to Classh with parameterized colors - Convert iconButton and iconButton' to use Classh instead of raw Tailwind - Add iconButton' with bgCol and txtCol parameters for customization - Rename old iconButton' to iconButtonEnabled for enabled/disabled state - Use bright lavender (#A78BFA) default for better contrast on dark backgrounds - Update Invitebar to use iconButtonEnabled --- src/Templates/Partials/Buttons.hs | 54 ++++++++++++++++++++++++----- src/Templates/Partials/Invitebar.hs | 2 +- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/Templates/Partials/Buttons.hs b/src/Templates/Partials/Buttons.hs index af63b5d..49f849e 100644 --- a/src/Templates/Partials/Buttons.hs +++ b/src/Templates/Partials/Buttons.hs @@ -12,19 +12,55 @@ import Data.Proxy import Data.Char (isAlphaNum, isSpace) iconButton :: DomBuilder t m => Text -> m (Event t ()) -iconButton icon = do - (e, _) <- elClass' "button" classes $ text icon +iconButton = iconButton' + (C.only (C.noTransition (C.solidColor (C.hex "A78BFA")))) -- Default: bright lavender + (C.only (C.color C.White)) + +iconButton' + :: DomBuilder t m + => C.WhenTW (C.WithTransition C.GradientColor) -- ^ Background color (with states) + -> C.WhenTW C.ColorWithOpacity -- ^ Text/icon color + -> Text -- ^ Icon name + -> m (Event t ()) +iconButton' bgCol txtCol icon = do + (e, _) <- elClass' "button" classes $ C.textS textCfg icon pure $ domEvent Click e where - classes = "focus:outline-none bg-primary rounded p-2.5 w-[50%] font-icon text-icon text-white leading-none shadow-button h-[95%]" - -iconButton' :: (DomBuilder t m, PostBuild t m) => Dynamic t Bool -> Text -> m (Event t ()) -iconButton' enabled icon = do - let attrs = ffor enabled $ \b -> if b then "class" =: classes else "class" =: classes <> "disabled" =: "1" - (e, _) <- elDynAttr' "button" attrs $ text icon + textCfg = C.classhUnsafe + [ C.text_color .~ txtCol + , C.custom .~ "font-icon text-icon leading-none" + ] + classes = C.classhUnsafe + [ C.bgColor .~ bgCol + , C.br .~~ C.R_Normal + , C.p .~~ C.TWSize 2.5 + , C.w .~~ C.pct 50 + , C.h .~~ C.pct 95 + , C.shadow .~~ C.Shadow_Md + , C.border . C.outline .~ [("focus", C.Outline_None)] + ] + +iconButtonEnabled :: (DomBuilder t m, PostBuild t m) => Dynamic t Bool -> Text -> m (Event t ()) +iconButtonEnabled enabled icon = do + let attrs = ffor enabled $ \b -> + if b + then "class" =: classes + else "class" =: classes <> "disabled" =: "1" + (e, _) <- elDynAttr' "button" attrs $ C.textS textCfg icon pure $ domEvent Click e where - classes = "focus:outline-none flex-shrink-0 bg-primary rounded p-2.5 font-icon text-icon text-white leading-none shadow-button" + textCfg = C.classhUnsafe + [ C.text_color .~~ C.color C.White + , C.custom .~ "font-icon text-icon leading-none" + ] + classes = C.classhUnsafe + [ C.bgColor .~~ C.solidColor (C.hex "A78BFA") + , C.br .~~ C.R_Normal + , C.p .~~ C.TWSize 2.5 + , C.shadow .~~ C.Shadow_Md + , C.border . C.outline .~ [("focus", C.Outline_None)] + , C.custom .~ "flex-shrink-0" + ] diff --git a/src/Templates/Partials/Invitebar.hs b/src/Templates/Partials/Invitebar.hs index 1f7cd70..a8c2e33 100644 --- a/src/Templates/Partials/Invitebar.hs +++ b/src/Templates/Partials/Invitebar.hs @@ -67,7 +67,7 @@ invitebar' bgCol inputBgCol borderCol textCol placeholder = do <> "placeholder" =: placeholder <> "type" =: "email" ) - invButton' <- iconButton' toEnable "send" + invButton' <- iconButtonEnabled toEnable "send" return (invInput', invButton') From ef3c34875696ff39935fb74703512a83ea859e20 Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Sun, 1 Mar 2026 18:41:55 -0500 Subject: [PATCH 07/14] fix(containers): add text color to toggle icon for visibility on dark Apply text color to expand/collapse icon so it's visible on dark backgrounds --- src/Templates/Partials/Containers.hs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Templates/Partials/Containers.hs b/src/Templates/Partials/Containers.hs index bc997fb..a9709af 100644 --- a/src/Templates/Partials/Containers.hs +++ b/src/Templates/Partials/Containers.hs @@ -23,13 +23,14 @@ toggleButton = toggleButton' (color White) toggleButton' :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => ColorWithOpacity -> Text -> m (Dynamic t Bool) toggleButton' txtCol label = do let - classes :: Bool -> Text - classes shown = classhUnsafe $ + boxClasses :: Bool -> Text + boxClasses shown = classhUnsafe $ [ custom .~ "text-icon font-icon select-none" ] <> (if shown then [ transform . rotate .~^ [("def", Rotate_0 `withTransition` Duration_300 `withTiming` Ease_InOut)] ] else [ transform . rotate .~^ [("def", Rotate_180 `withTransition` Duration_300 `withTiming` Ease_InOut)] ] ) + textStyle = classhUnsafe [ text_color .~~ txtCol ] -- The icon for a rendered container is a triangle pointing up, and -- the icon for an unrendered container is that same triangle, rotated @@ -42,7 +43,7 @@ toggleButton' txtCol label = do , custom .~ "flex flex-row justify-between" ]) $ do textS (classhUnsafe [ text_color .~~ txtCol ]) label - elDynClass' "span" (classes <$> toggled) $ text "expand_less" + elDynClass "span" (boxClasses <$> toggled) $ textS textStyle "expand_less" let toggleEv = domEvent Click labelEl toggled <- holdUniqDyn =<< toggle True toggleEv From 34059cad39602275601fc5db073733e99aa6e1f8 Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Sun, 1 Mar 2026 19:25:09 -0500 Subject: [PATCH 08/14] refactor: convert all hardcoded colors to Classh with parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Buttons.hs: Convert hex colors (00B9DA → Cyan C500), remove raw Tailwind from custom fields (hover:bg-primary-rich, ring-primary), add type-safe transform/scale setters, remove unused wrapper functions (secondaryIconButton, secondaryButton, primaryButtonImageDyn, primaryButtonImageText) - Modal.hs: Convert #00004D → Slate C900, text-rose-500 → Rose C500, add parameterized modal' with overlay/content/border/text colors - Dropdown.hs: Convert hex "00B9DA" → Cyan C500 for focus border - Errors.hs: Convert text-opacity-70 → withOpacity (Rose C500) 70, add parameterized errorMessage' - Invitebar.hs: Convert hex "E11D48" → Rose C600 - Lists.hs: Convert hex colors to Gray C200/C300 --- src/Templates/Partials/Buttons.hs | 357 ++++++++++++------ src/Templates/Partials/Containers/Dropdown.hs | 4 +- src/Templates/Partials/Errors.hs | 9 +- src/Templates/Partials/Invitebar.hs | 4 +- src/Templates/Partials/Lists.hs | 10 +- src/Templates/Partials/Modal.hs | 55 ++- 6 files changed, 311 insertions(+), 128 deletions(-) diff --git a/src/Templates/Partials/Buttons.hs b/src/Templates/Partials/Buttons.hs index 49f849e..005846d 100644 --- a/src/Templates/Partials/Buttons.hs +++ b/src/Templates/Partials/Buttons.hs @@ -41,7 +41,18 @@ iconButton' bgCol txtCol icon = do ] iconButtonEnabled :: (DomBuilder t m, PostBuild t m) => Dynamic t Bool -> Text -> m (Event t ()) -iconButtonEnabled enabled icon = do +iconButtonEnabled = iconButtonEnabled' + (C.solidColor (C.Violet C.C400)) -- bright violet + (C.color C.White) + +iconButtonEnabled' + :: (DomBuilder t m, PostBuild t m) + => C.GradientColor -- ^ Background color + -> C.ColorWithOpacity -- ^ Icon color + -> Dynamic t Bool + -> Text + -> m (Event t ()) +iconButtonEnabled' bgCol iconCol enabled icon = do let attrs = ffor enabled $ \b -> if b then "class" =: classes @@ -50,11 +61,11 @@ iconButtonEnabled enabled icon = do pure $ domEvent Click e where textCfg = C.classhUnsafe - [ C.text_color .~~ C.color C.White + [ C.text_color .~~ iconCol , C.custom .~ "font-icon text-icon leading-none" ] classes = C.classhUnsafe - [ C.bgColor .~~ C.solidColor (C.hex "A78BFA") + [ C.bgColor .~~ bgCol , C.br .~~ C.R_Normal , C.p .~~ C.TWSize 2.5 , C.shadow .~~ C.Shadow_Md @@ -65,39 +76,111 @@ iconButtonEnabled enabled icon = do primaryButtonDyn :: (DomBuilder t m, PostBuild t m) => Dynamic t Bool -> Text -> m (Event t ()) -primaryButtonDyn enabled buttonText = do +primaryButtonDyn = primaryButtonDyn' + (C.only (C.noTransition (C.solidColor (C.Violet C.C600)))) -- bg + [("hover", C.solidColor (C.Violet C.C700) `C.withTransition` C.Duration_200) + ,("active", C.solidColor (C.Violet C.C500) `C.withTransition` C.Duration_100)] -- bg states + (C.only (C.color C.White)) -- text + C.Shadow_Md -- shadow + [("focus", C.noTransition C.Ring_4)] -- ring + (C.color (C.Violet C.C500)) -- ring color + +primaryButtonDyn' + :: (DomBuilder t m, PostBuild t m) + => C.WhenTW (C.WithTransition C.GradientColor) -- ^ Background color + -> [(C.TWCondition, C.WithTransition C.GradientColor)] -- ^ Background hover/active states + -> C.WhenTW C.ColorWithOpacity -- ^ Text color + -> C.BoxShadow -- ^ Shadow + -> [(C.TWCondition, C.WithTransition C.RingWidth)] -- ^ Ring width states + -> C.ColorWithOpacity -- ^ Ring color + -> Dynamic t Bool + -> Text + -> m (Event t ()) +primaryButtonDyn' bgCol bgStates txtCol shadowVal ringStates ringCol enabled buttonText = do let attrs = ffor enabled $ \b -> if b then "class" =: classes else "class" =: classes <> "disabled" =: "1" - (e, _) <- elDynAttr' "button" attrs $ text buttonText + (e, _) <- elDynAttr' "button" attrs $ C.textS textCfg buttonText pure $ domEvent Click e where - classes = - "focus:outline-none w-full p-4 mt-12 shadow-button bg-primary \ - \ font-facit font-bold text-white text-body text-center rounded \ - \ hover:bg-primary-rich active:bg-primary-desaturated \ - \ focus:ring-4 ring-primary ring-opacity-50" - -secondaryIconButton :: DomBuilder t m => Text -> Text -> m (Event t ()) -secondaryIconButton cs icon = do + textCfg = C.classhUnsafe + [ C.text_color .~ txtCol + , C.text_weight .~~ C.Bold + , C.custom .~ "font-facit text-body text-center" + ] + classes = C.classhUnsafe + [ C.bgColor .~ bgCol + , C.bgColor .~^ bgStates + , C.w .~~ C.TWSize_Full + , C.p .~~ C.TWSize 4 + , C.mt .~~ C.TWSize 12 + , C.shadow .~~ shadowVal + , C.br .~~ C.R_Normal + , C.border . C.outline .~ [("focus", C.Outline_None)] + , C.border . C.ring . C.ringWidth .~ ringStates + , C.border . C.ring . C.ringColor .~~ ringCol + , C.border . C.ring . C.ringOpacity .~~ 50 + ] + +secondaryIconButton + :: DomBuilder t m + => C.GradientColor -- ^ Background color + -> C.ColorWithOpacity -- ^ Icon color + -> C.ColorWithOpacity -- ^ Border color + -> C.ColorWithOpacity -- ^ Ring color + -> Text -- ^ Extra classes + -> Text -- ^ Icon name + -> m (Event t ()) +secondaryIconButton bgCol iconCol borderCol ringCol cs icon = do (e, _) <- elClass' "button" classes $ - elClass "div" "font-icon leading-none text-icon text-primary-dark" $ text icon + elClass "div" iconClasses $ C.textS iconTextCfg icon pure $ domEvent Click e where - classes = - "focus:outline-none rounded border border-metaline \ - \ focus:ring-4 ring-primary ring-opacity-50 \ - \ p-2.5 flex-shrink-0 bg-primary-light " <> cs + iconTextCfg = C.classhUnsafe [ C.text_color .~~ iconCol ] + iconClasses = C.classhUnsafe [ C.box_custom .~ "font-icon leading-none text-icon" ] + classes = C.classhUnsafe + [ C.bgColor .~~ bgCol + , C.bc .~~ borderCol + , C.bw .~~ C.B1 + , C.br .~~ C.R_Normal + , C.p .~~ C.TWSize 2.5 + , C.border . C.outline .~ [("focus", C.Outline_None)] + , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] + , C.border . C.ring . C.ringColor .~~ ringCol + , C.border . C.ring . C.ringOpacity .~~ 50 + , C.custom .~ ("flex-shrink-0 " <> cs) + ] -secondaryButton :: DomBuilder t m => Text -> Text -> m (Event t ()) -secondaryButton cs label = do - (e, _) <- elClass' "button" classes $ - text label +secondaryButton + :: DomBuilder t m + => C.GradientColor -- ^ Background color + -> C.ColorWithOpacity -- ^ Text color + -> C.ColorWithOpacity -- ^ Border color + -> C.ColorWithOpacity -- ^ Ring color + -> Text -- ^ Extra classes + -> Text -- ^ Label + -> m (Event t ()) +secondaryButton bgCol txtCol borderCol ringCol cs label = do + (e, _) <- elClass' "button" classes $ C.textS textCfg label pure $ domEvent Click e where - classes = - "w-full p-2.5 leading-none text-center rounded border border-metaline \ - \ bg-primary-light text-primary-darker font-bold font-facit focus:outline-none \ - \ focus:ring-4 ring-primary ring-opacity-50 " <> cs + textCfg = C.classhUnsafe + [ C.text_color .~~ txtCol + , C.text_weight .~~ C.Bold + , C.custom .~ "font-facit leading-none text-center" + ] + classes = C.classhUnsafe + [ C.bgColor .~~ bgCol + , C.bc .~~ borderCol + , C.bw .~~ C.B1 + , C.br .~~ C.R_Normal + , C.w .~~ C.TWSize_Full + , C.p .~~ C.TWSize 2.5 + , C.border . C.outline .~ [("focus", C.Outline_None)] + , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] + , C.border . C.ring . C.ringColor .~~ ringCol + , C.border . C.ring . C.ringOpacity .~~ 50 + , C.custom .~ cs + ] sendButton :: DomBuilder t m => m (Event t ()) sendButton = iconButton "send" @@ -105,11 +188,21 @@ sendButton = iconButton "send" navyBlueButton :: DomBuilder t m => Text -> m (Event t ()) navyBlueButton = navyBlueButton' - (C.only (C.noTransition (C.solidColor (C.hex "2E3A59")))) + (C.only (C.noTransition (C.solidColor (C.Slate C.C800)))) -- navy blue + [("hover", C.solidColor (C.Slate C.C700) `C.withTransition` C.Duration_200) + ,("active", C.solidColor (C.Slate C.C600) `C.withTransition` C.Duration_100)] (C.only (C.color C.White)) + (C.color (C.Violet C.C500)) -- ring color -navyBlueButton' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.ColorWithOpacity -> Text -> m (Event t ()) -navyBlueButton' bgCol txtCol buttonText = do +navyBlueButton' + :: DomBuilder t m + => C.WhenTW (C.WithTransition C.GradientColor) -- ^ Background color + -> [(C.TWCondition, C.WithTransition C.GradientColor)] -- ^ Background hover/active states + -> C.WhenTW C.ColorWithOpacity -- ^ Text color + -> C.ColorWithOpacity -- ^ Ring color + -> Text + -> m (Event t ()) +navyBlueButton' bgCol bgStates txtCol ringCol buttonText = do (e, _) <- elClass' "button" boxCfg $ C.textS textCfg buttonText pure $ domEvent Click e where @@ -120,16 +213,17 @@ navyBlueButton' bgCol txtCol buttonText = do boxCfg = C.classhUnsafe [ C.w .~~ C.TWSize_Full , C.p .~~ C.TWSize 4 , C.bgColor .~ bgCol + , C.bgColor .~^ bgStates , C.br .~~ C.R_3Xl , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) ] , C.border . C.outline .~ [("focus", C.Outline_None)] , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] + , C.border . C.ring . C.ringColor .~~ ringCol , C.border . C.ring . C.ringOpacity .~~ 50 - , C.custom .~ "ring-primary \ - \ hover:bg-primary-rich active:bg-primary-desaturated \ - \ transform hover:scale-105 active:scale-95" + , C.transform . C.scale .~^ [("hover", C.Scale_105 `C.withTransition` C.Duration_200) + ,("active", C.Scale_95 `C.withTransition` C.Duration_100)] ] @@ -141,25 +235,14 @@ primaryButtonImageDyn :: ( PostBuild t m , DomBuilder t m ) - => Dynamic t Text - -> Height - -> Width - -> m (Event t ()) -primaryButtonImageDyn = primaryButtonImageDyn' - (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) - (C.only (C.color C.White)) - -primaryButtonImageDyn' - :: ( PostBuild t m - , DomBuilder t m - ) - => C.WhenTW (C.WithTransition C.GradientColor) - -> C.WhenTW C.ColorWithOpacity + => C.WhenTW (C.WithTransition C.GradientColor) -- ^ Background color + -> [(C.TWCondition, C.WithTransition C.GradientColor)] -- ^ Background hover/active states + -> C.ColorWithOpacity -- ^ Ring color -> Dynamic t Text -> Height -> Width -> m (Event t ()) -primaryButtonImageDyn' bgCol txtCol dynImageHref height width = do +primaryButtonImageDyn bgCol bgStates ringCol dynImageHref height width = do (e, _) <- elClass' "button" boxCfg $ elDynAttr "img" imgAttrs blank pure $ domEvent Click e @@ -170,17 +253,18 @@ primaryButtonImageDyn' bgCol txtCol dynImageHref height width = do , C.p .~~ C.TWSize 4 , C.mt .~~ C.TWSize 16 , C.bgColor .~ bgCol + , C.bgColor .~^ bgStates , C.br .~~ C.R_Xl , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) ] , C.border . C.outline .~ [("focus", C.Outline_None)] , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] + , C.border . C.ring . C.ringColor .~~ ringCol , C.border . C.ring . C.ringOpacity .~~ 50 - , C.custom .~ "font-[Sarabun] font-bold text-white text-body text-center \ - \ ring-primary \ - \ hover:bg-primary-rich active:bg-primary-desaturated \ - \ transform hover:scale-105 active:scale-95" + , C.transform . C.scale .~^ [("hover", C.Scale_105 `C.withTransition` C.Duration_200) + ,("active", C.Scale_95 `C.withTransition` C.Duration_100)] + , C.custom .~ "font-[Sarabun] font-bold text-white text-body text-center" ] @@ -189,89 +273,142 @@ type Height = Text type Width = Text primaryButtonImage :: DomBuilder t m => Text -> Height -> Width -> m (Event t ()) primaryButtonImage = primaryButtonImage' - (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) - (C.only (C.color C.White)) + (C.only (C.noTransition (C.solidColor (C.Cyan C.C500)))) + [("hover", C.solidColor (C.Cyan C.C600) `C.withTransition` C.Duration_200) + ,("active", C.solidColor (C.Cyan C.C400) `C.withTransition` C.Duration_100)] + (C.color (C.Cyan C.C500)) -primaryButtonImage' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.ColorWithOpacity -> Text -> Height -> Width -> m (Event t ()) -primaryButtonImage' bgCol _ imageHref height width = do - (e, _) <- elClass' "button" (primaryButtonBoxCfg bgCol) $ +primaryButtonImage' + :: DomBuilder t m + => C.WhenTW (C.WithTransition C.GradientColor) -- ^ Background color + -> [(C.TWCondition, C.WithTransition C.GradientColor)] -- ^ Background hover/active states + -> C.ColorWithOpacity -- ^ Ring color + -> Text -> Height -> Width -> m (Event t ()) +primaryButtonImage' bgCol bgStates ringCol imageHref height width = do + (e, _) <- elClass' "button" (primaryButtonBoxCfg bgCol bgStates ringCol) $ elAttr "img" ("src" =: imageHref <> "height" =: height <> "width" =: width <> "class" =: "block mx-auto") blank pure $ domEvent Click e -primaryButtonImageText :: DomBuilder t m => Text -> Height -> Width -> Text -> m (Event t ()) -primaryButtonImageText = primaryButtonImageText' - (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) - (C.only (C.color C.White)) - -primaryButtonImageText' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.ColorWithOpacity -> Text -> Height -> Width -> Text -> m (Event t ()) -primaryButtonImageText' bgCol _ imageHref height width bottomText = do - (e, _) <- elClass' "button" (primaryButtonBoxCfg bgCol) $ do +primaryButtonImageText + :: DomBuilder t m + => C.WhenTW (C.WithTransition C.GradientColor) -- ^ Background color + -> [(C.TWCondition, C.WithTransition C.GradientColor)] -- ^ Background hover/active states + -> C.ColorWithOpacity -- ^ Text color + -> C.ColorWithOpacity -- ^ Ring color + -> Text -> Height -> Width -> Text -> m (Event t ()) +primaryButtonImageText bgCol bgStates txtCol ringCol imageHref height width bottomText = do + (e, _) <- elClass' "button" (primaryButtonBoxCfg bgCol bgStates ringCol) $ do elAttr "img" ("src" =: imageHref <> "height" =: height <> "width" =: width <> "class" =: "block mx-auto") blank - text bottomText + C.textS textCfg bottomText pure $ domEvent Click e + where + textCfg = C.classhUnsafe [ C.text_color .~~ txtCol + , C.text_font .~~ C.Font_Custom "Sarabun" + , C.text_weight .~~ C.Bold + ] -primaryButtonBoxCfg :: C.WhenTW (C.WithTransition C.GradientColor) -> Text -primaryButtonBoxCfg bgCol = C.classhUnsafe [ C.w .~~ C.TWSize_Full - , C.p .~~ C.TWSize 4 - , C.mt .~~ C.TWSize 16 - , C.bgColor .~ bgCol - , C.br .~~ C.R_Xl - , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) - , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) - ] - , C.border . C.outline .~ [("focus", C.Outline_None)] - , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] - , C.border . C.ring . C.ringOpacity .~~ 50 - , C.custom .~ "font-[Sarabun] font-bold text-white text-body text-center \ - \ ring-primary \ - \ hover:bg-primary-rich active:bg-primary-desaturated \ - \ transform hover:scale-105 active:scale-95" - ] +primaryButtonBoxCfg + :: C.WhenTW (C.WithTransition C.GradientColor) -- ^ Background color + -> [(C.TWCondition, C.WithTransition C.GradientColor)] -- ^ Background hover/active states + -> C.ColorWithOpacity -- ^ Ring color + -> Text +primaryButtonBoxCfg bgCol bgStates ringCol = C.classhUnsafe + [ C.w .~~ C.TWSize_Full + , C.p .~~ C.TWSize 4 + , C.mt .~~ C.TWSize 16 + , C.bgColor .~ bgCol + , C.bgColor .~^ bgStates + , C.br .~~ C.R_Xl + , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) + , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) + ] + , C.border . C.outline .~ [("focus", C.Outline_None)] + , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] + , C.border . C.ring . C.ringColor .~~ ringCol + , C.border . C.ring . C.ringOpacity .~~ 50 + , C.transform . C.scale .~^ [("hover", C.Scale_105 `C.withTransition` C.Duration_200) + ,("active", C.Scale_95 `C.withTransition` C.Duration_100)] + , C.custom .~ "text-center" + ] primaryButtonSized :: DomBuilder t m => TWSize -> TWSize -> Text -> m (Event t ()) primaryButtonSized = primaryButtonSized' - (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) - (C.only (C.color C.White)) + (C.only (C.noTransition (C.solidColor (C.Cyan C.C500)))) + [("hover", C.solidColor (C.Cyan C.C600) `C.withTransition` C.Duration_200) + ,("active", C.solidColor (C.Cyan C.C400) `C.withTransition` C.Duration_100)] + (C.color C.White) + (C.color (C.Cyan C.C500)) -primaryButtonSized' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.ColorWithOpacity -> TWSize -> TWSize -> Text -> m (Event t ()) -primaryButtonSized' bgCol _ height width buttonText = do - (e, _) <- elAttr' "button" ("class" =: classCfg <> "name" =: name) $ text buttonText +primaryButtonSized' + :: DomBuilder t m + => C.WhenTW (C.WithTransition C.GradientColor) -- ^ Background color + -> [(C.TWCondition, C.WithTransition C.GradientColor)] -- ^ Background hover/active states + -> C.ColorWithOpacity -- ^ Text color + -> C.ColorWithOpacity -- ^ Ring color + -> TWSize -> TWSize -> Text -> m (Event t ()) +primaryButtonSized' bgCol bgStates txtCol ringCol height width buttonText = do + (e, _) <- elAttr' "button" ("class" =: classCfg <> "name" =: name) $ + C.textS textCfg buttonText pure $ domEvent Click e where - classCfg = primaryClass bgCol <&> C.classhUnsafe [C.py .~~ height, C.px .~~ width] + textCfg = C.classhUnsafe [ C.text_color .~~ txtCol + , C.text_font .~~ C.Font_Custom "Sarabun" + , C.text_weight .~~ C.Bold + , C.text_size .|~ [C.XS, C.XS, C.LG] -- text-xs on mobile, text-lg on md+ + ] + classCfg = primaryClass bgCol bgStates ringCol <&> C.classhUnsafe [C.py .~~ height, C.px .~~ width] name = T.filter (\c -> isAlphaNum c || isSpace c ) buttonText primaryButton :: DomBuilder t m => Text -> m (Event t ()) primaryButton = primaryButton' - (C.only (C.noTransition (C.solidColor (C.hex "00B9DA")))) - (C.only (C.color C.White)) + (C.only (C.noTransition (C.solidColor (C.Cyan C.C500)))) + [("hover", C.solidColor (C.Cyan C.C600) `C.withTransition` C.Duration_200) + ,("active", C.solidColor (C.Cyan C.C400) `C.withTransition` C.Duration_100)] + (C.color C.White) + (C.color (C.Cyan C.C500)) -primaryButton' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -> C.WhenTW C.ColorWithOpacity -> Text -> m (Event t ()) -primaryButton' bgCol _ buttonText = do - (e, _) <- elAttr' "button" ("class" =: classCfg <> "name" =: name) $ text buttonText +primaryButton' + :: DomBuilder t m + => C.WhenTW (C.WithTransition C.GradientColor) -- ^ Background color + -> [(C.TWCondition, C.WithTransition C.GradientColor)] -- ^ Background hover/active states + -> C.ColorWithOpacity -- ^ Text color + -> C.ColorWithOpacity -- ^ Ring color + -> Text -> m (Event t ()) +primaryButton' bgCol bgStates txtCol ringCol buttonText = do + (e, _) <- elAttr' "button" ("class" =: classCfg <> "name" =: name) $ + C.textS textCfg buttonText pure $ domEvent Click e where - classCfg = primaryClass bgCol <&> C.classhUnsafe [C.py .~~ C.TWSize 4, C.px .~~ C.TWSize 8] + textCfg = C.classhUnsafe [ C.text_color .~~ txtCol + , C.text_font .~~ C.Font_Custom "Sarabun" + , C.text_weight .~~ C.Bold + , C.text_size .|~ [C.XS, C.XS, C.LG] -- text-xs on mobile, text-lg on md+ + ] + classCfg = primaryClass bgCol bgStates ringCol <&> C.classhUnsafe [C.py .~~ C.TWSize 4, C.px .~~ C.TWSize 8] name = T.filter (\c -> isAlphaNum c || isSpace c ) buttonText -primaryClass :: C.WhenTW (C.WithTransition C.GradientColor) -> Text -primaryClass bgCol = C.classhUnsafe [ C.bgColor .~ bgCol - , C.br .~~ C.R_Xl - , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) - , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) - ] - , C.border . C.outline .~ [("focus", C.Outline_None)] - , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] - , C.border . C.ring . C.ringOpacity .~~ 50 - , C.custom .~ "font-[Sarabun] font-bold text-white text-body text-center \ - \ ring-primary \ - \ hover:bg-primary-rich active:bg-primary-desaturated \ - \ transform hover:scale-105 active:scale-95 \ - \ whitespace-nowrap inline-block \ - \ min-[0px]:text-xs md:text-lg" - ] +primaryClass + :: C.WhenTW (C.WithTransition C.GradientColor) -- ^ Background color + -> [(C.TWCondition, C.WithTransition C.GradientColor)] -- ^ Background hover/active states + -> C.ColorWithOpacity -- ^ Ring color + -> Text +primaryClass bgCol bgStates ringCol = C.classhUnsafe + [ C.bgColor .~ bgCol + , C.bgColor .~^ bgStates + , C.br .~~ C.R_Xl + , C.shadow .~^ [("def", C.noTransition C.Shadow_Md) + , ("hover", C.Shadow_Md `C.withTransition` C.Duration_300) + ] + , C.border . C.outline .~ [("focus", C.Outline_None)] + , C.border . C.ring . C.ringWidth .~ [("focus", C.noTransition C.Ring_4)] + , C.border . C.ring . C.ringColor .~~ ringCol + , C.border . C.ring . C.ringOpacity .~~ 50 + , C.transform . C.scale .~^ [("hover", C.Scale_105 `C.withTransition` C.Duration_200) + ,("active", C.Scale_95 `C.withTransition` C.Duration_100)] + , C.custom .~ "text-center whitespace-nowrap inline-block" + ] example :: Template t m => m (Event t ()) example = buttonToggleBody "" True $ \case diff --git a/src/Templates/Partials/Containers/Dropdown.hs b/src/Templates/Partials/Containers/Dropdown.hs index fec9414..68a0b59 100644 --- a/src/Templates/Partials/Containers/Dropdown.hs +++ b/src/Templates/Partials/Containers/Dropdown.hs @@ -24,7 +24,7 @@ dropdown' dropdown' = dropdown'' (only (noTransition (solidColor White))) [ ("def", noTransition (color (Gray C300))) - , ("focus", noTransition (color (hex "00B9DA"))) + , ("focus", noTransition (color (Cyan C500))) ] -- | Parameterized version with custom colors @@ -78,7 +78,7 @@ dropdownWithDefault dropdownWithDefault = dropdownWithDefault' (only (noTransition (solidColor White))) [ ("def", noTransition (color (Gray C300))) - , ("focus", noTransition (color (hex "00B9DA"))) + , ("focus", noTransition (color (Cyan C500))) ] -- | Parameterized version with custom colors diff --git a/src/Templates/Partials/Errors.hs b/src/Templates/Partials/Errors.hs index 09ebfe7..ef54f77 100644 --- a/src/Templates/Partials/Errors.hs +++ b/src/Templates/Partials/Errors.hs @@ -35,5 +35,10 @@ displayOn template ev = maybeDisplay template =<< holdDyn Nothing (Just <$> ev) -- | Render an error message. errorMessage :: Template t m => Text -> m () -errorMessage t = - elClass "div" $(classh' [mt .~~ TWSize 1, h .~~ twSize' 4]) $ textS "text-opacity-70" t +errorMessage = errorMessage' (withOpacity (Rose C500) 70) + +-- | Render an error message with custom text color. +errorMessage' :: Template t m => ColorWithOpacity -> Text -> m () +errorMessage' txtCol t = + elClass "div" $(classh' [mt .~~ TWSize 1, h .~~ twSize' 4]) $ + textS (classhUnsafe [text_color .~~ txtCol]) t diff --git a/src/Templates/Partials/Invitebar.hs b/src/Templates/Partials/Invitebar.hs index a8c2e33..edc69e0 100644 --- a/src/Templates/Partials/Invitebar.hs +++ b/src/Templates/Partials/Invitebar.hs @@ -25,8 +25,8 @@ invitebar :: (PostBuild t m, DomBuilder t m, MonadHold t m, MonadFix m) => Text invitebar = invitebar' (only (noTransition (solidColor White))) (only (noTransition (solidColor Transparent))) - (only (noTransition (color (hex "E11D48")))) - (only (color (hex "E11D48"))) + (only (noTransition (color (Rose C600)))) + (only (color (Rose C600))) -- | Parameterized version with custom colors invitebar' diff --git a/src/Templates/Partials/Lists.hs b/src/Templates/Partials/Lists.hs index 5d7eec6..fbbd489 100644 --- a/src/Templates/Partials/Lists.hs +++ b/src/Templates/Partials/Lists.hs @@ -28,13 +28,13 @@ data ListItemConfig t = ListItemConfig -- | The default configuration for list items is to have no subtext, no -- icon, no highlighting information, and to not be clickable. --- Uses default dark theme colors. +-- Uses default light theme colors (for dark backgrounds, use defListItemConfig' with custom colors). defListItemConfig :: Applicative (Dynamic t) => ListItemConfig t defListItemConfig = defListItemConfig' - (only (noTransition (solidColor Transparent))) - (only (noTransition (color (hex "E5E7EB")))) - (only (color White)) - (only (color (hex "D1D5DB"))) + (only (noTransition (solidColor Transparent))) -- bg: transparent + (only (noTransition (color (Gray C200)))) -- border: gray-200 + (only (color White)) -- text: white + (only (color (Gray C300))) -- | Configurable version with custom colors defListItemConfig' diff --git a/src/Templates/Partials/Modal.hs b/src/Templates/Partials/Modal.hs index 45d78ab..a41aa16 100644 --- a/src/Templates/Partials/Modal.hs +++ b/src/Templates/Partials/Modal.hs @@ -2,6 +2,8 @@ module Templates.Partials.Modal where +import Classh as C +import Classh.Reflex as C import Templates.Partials.Image import Templates.Types import Reflex.Dom.Core @@ -18,16 +20,55 @@ modal :: ( DomBuilder t m -> Event t () -> m a -> m (Event t a) -modal xButtonImgSrc open modalDom = mdo - let styleBase = "position:fixed;z-index:20;padding-top:100px;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4);" - hideModal = ("style" =: ("display:none;" <> styleBase)) - showModal = ("style" =: ("display:block;" <> styleBase)) +modal = modal' + (C.solidColorOpacity C.Black 40) -- overlay background + (C.solidColor (C.Slate C.C900)) -- content background (dark navy) + (C.color C.Black) -- border color + (C.color C.White) -- text color + (C.color (C.Rose C.C500)) -- close button color + +modal' + :: ( DomBuilder t m + , MonadFix m + , MonadHold t m + , PostBuild t m + ) + => C.GradientColor -- ^ Overlay background color (with opacity) + -> C.GradientColor -- ^ Content background color + -> C.ColorWithOpacity -- ^ Border color + -> C.ColorWithOpacity -- ^ Text color + -> C.ColorWithOpacity -- ^ Close button icon color + -> ImgSrc + -> Event t () + -> m a + -> m (Event t a) +modal' overlayBg contentBg borderCol txtCol closeBtnCol xButtonImgSrc open modalDom = mdo + -- Overlay positioning must remain as inline style (no Classh for position:fixed, z-index, etc.) + let styleBase = "position:fixed;z-index:20;padding-top:100px;left:0;top:0;width:100%;height:100%;overflow:auto;" + hideModal = ("style" =: ("display:none;" <> styleBase) <> "class" =: overlayClass) + showModal = ("style" =: ("display:block;" <> styleBase) <> "class" =: overlayClass) modalAttrs <- holdDyn hideModal $ mergeWith const [showModal <$ open, hideModal <$ close] close <- elDynAttr "div" modalAttrs $ do - elAttr "div" ("style" =: "background-color:#00004D;margin:auto;padding:20px;width:80%;color:white;" - <> "class" =: "border-double rounded-md border-8 border-black text-base") $ do - e <- fmap fst $ elClass' "span" "pl-5 ml-5 pb-4 text-rose-500 grid justify-items-end" $ do + elAttr "div" ("style" =: "margin:auto;width:80%;" <> "class" =: contentClass) $ do + e <- fmap fst $ elClass' "span" closeSpanClass $ do imgAttr xButtonImgSrc ("height" =: "30px" <> "width" =: "30px") x' <- modalDom pure $ x' <$ (domEvent Click e) pure close + where + overlayClass = C.classhUnsafe [ C.bgColor .~~ overlayBg ] + contentClass = C.classhUnsafe + [ C.bgColor .~~ contentBg + , C.p .~~ C.TWSize 5 -- padding:20px ≈ p-5 + , C.border . C.bStyle .~~ C.BDouble + , C.br .~~ C.R_Md + , C.bw .~~ C.B8 + , C.bc .~~ borderCol + , C.custom .~ "text-base" -- text-base for font sizing + ] <> " " <> C.classhUnsafe [ C.text_color .~~ txtCol ] + closeSpanClass = C.classhUnsafe + [ C.pl .~~ C.TWSize 5 + , C.ml .~~ C.TWSize 5 + , C.pb .~~ C.TWSize 4 + , C.custom .~ "grid justify-items-end" + ] <> " " <> C.classhUnsafe [ C.text_color .~~ closeBtnCol ] From f2578bf7e0e296ce8658438a70d4d8946636254d Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Sun, 1 Mar 2026 20:01:20 -0500 Subject: [PATCH 09/14] remove hardcoded variants --- src/Templates/Partials/Buttons.hs | 81 +++++++++---------- src/Templates/Partials/Containers.hs | 4 +- src/Templates/Partials/Containers/Dropdown.hs | 50 ++++++------ src/Templates/Partials/Errors.hs | 6 +- src/Templates/Partials/Invitebar.hs | 17 ++-- src/Templates/Partials/Lists.hs | 12 +-- src/Templates/Partials/Modal.hs | 30 +++---- src/Templates/Partials/Searchbar.hs | 18 ++--- 8 files changed, 105 insertions(+), 113 deletions(-) diff --git a/src/Templates/Partials/Buttons.hs b/src/Templates/Partials/Buttons.hs index 005846d..864d225 100644 --- a/src/Templates/Partials/Buttons.hs +++ b/src/Templates/Partials/Buttons.hs @@ -11,10 +11,6 @@ import Control.Lens ((%~), (.~)) import Data.Proxy import Data.Char (isAlphaNum, isSpace) -iconButton :: DomBuilder t m => Text -> m (Event t ()) -iconButton = iconButton' - (C.only (C.noTransition (C.solidColor (C.hex "A78BFA")))) -- Default: bright lavender - (C.only (C.color C.White)) iconButton' :: DomBuilder t m @@ -40,11 +36,6 @@ iconButton' bgCol txtCol icon = do , C.border . C.outline .~ [("focus", C.Outline_None)] ] -iconButtonEnabled :: (DomBuilder t m, PostBuild t m) => Dynamic t Bool -> Text -> m (Event t ()) -iconButtonEnabled = iconButtonEnabled' - (C.solidColor (C.Violet C.C400)) -- bright violet - (C.color C.White) - iconButtonEnabled' :: (DomBuilder t m, PostBuild t m) => C.GradientColor -- ^ Background color @@ -75,15 +66,15 @@ iconButtonEnabled' bgCol iconCol enabled icon = do -primaryButtonDyn :: (DomBuilder t m, PostBuild t m) => Dynamic t Bool -> Text -> m (Event t ()) -primaryButtonDyn = primaryButtonDyn' - (C.only (C.noTransition (C.solidColor (C.Violet C.C600)))) -- bg - [("hover", C.solidColor (C.Violet C.C700) `C.withTransition` C.Duration_200) - ,("active", C.solidColor (C.Violet C.C500) `C.withTransition` C.Duration_100)] -- bg states - (C.only (C.color C.White)) -- text - C.Shadow_Md -- shadow - [("focus", C.noTransition C.Ring_4)] -- ring - (C.color (C.Violet C.C500)) -- ring color +-- primaryButtonDyn :: (DomBuilder t m, PostBuild t m) => Dynamic t Bool -> Text -> m (Event t ()) +-- primaryButtonDyn = primaryButtonDyn' +-- (C.only (C.noTransition (C.solidColor (C.Violet C.C600)))) -- bg +-- [("hover", C.solidColor (C.Violet C.C700) `C.withTransition` C.Duration_200) +-- ,("active", C.solidColor (C.Violet C.C500) `C.withTransition` C.Duration_100)] -- bg states +-- (C.only (C.color C.White)) -- text +-- C.Shadow_Md -- shadow +-- [("focus", C.noTransition C.Ring_4)] -- ring +-- (C.color (C.Violet C.C500)) -- ring color primaryButtonDyn' :: (DomBuilder t m, PostBuild t m) @@ -186,13 +177,13 @@ sendButton :: DomBuilder t m => m (Event t ()) sendButton = iconButton "send" -navyBlueButton :: DomBuilder t m => Text -> m (Event t ()) -navyBlueButton = navyBlueButton' - (C.only (C.noTransition (C.solidColor (C.Slate C.C800)))) -- navy blue - [("hover", C.solidColor (C.Slate C.C700) `C.withTransition` C.Duration_200) - ,("active", C.solidColor (C.Slate C.C600) `C.withTransition` C.Duration_100)] - (C.only (C.color C.White)) - (C.color (C.Violet C.C500)) -- ring color +-- navyBlueButton :: DomBuilder t m => Text -> m (Event t ()) +-- navyBlueButton = navyBlueButton' +-- (C.only (C.noTransition (C.solidColor (C.Violet C.C600)))) -- violet for visibility on dark bg +-- [("hover", C.solidColor (C.Violet C.C500) `C.withTransition` C.Duration_200) +-- ,("active", C.solidColor (C.Violet C.C400) `C.withTransition` C.Duration_100)] +-- (C.only (C.color C.White)) +-- (C.color (C.Violet C.C300)) -- ring color navyBlueButton' :: DomBuilder t m @@ -271,12 +262,12 @@ primaryButtonImageDyn bgCol bgStates ringCol dynImageHref height width = do type Height = Text type Width = Text -primaryButtonImage :: DomBuilder t m => Text -> Height -> Width -> m (Event t ()) -primaryButtonImage = primaryButtonImage' - (C.only (C.noTransition (C.solidColor (C.Cyan C.C500)))) - [("hover", C.solidColor (C.Cyan C.C600) `C.withTransition` C.Duration_200) - ,("active", C.solidColor (C.Cyan C.C400) `C.withTransition` C.Duration_100)] - (C.color (C.Cyan C.C500)) +-- primaryButtonImage :: DomBuilder t m => Text -> Height -> Width -> m (Event t ()) +-- primaryButtonImage = primaryButtonImage' +-- (C.only (C.noTransition (C.solidColor (C.Cyan C.C500)))) +-- [("hover", C.solidColor (C.Cyan C.C600) `C.withTransition` C.Duration_200) +-- ,("active", C.solidColor (C.Cyan C.C400) `C.withTransition` C.Duration_100)] +-- (C.color (C.Cyan C.C500)) primaryButtonImage' :: DomBuilder t m @@ -332,13 +323,13 @@ primaryButtonBoxCfg bgCol bgStates ringCol = C.classhUnsafe ] -primaryButtonSized :: DomBuilder t m => TWSize -> TWSize -> Text -> m (Event t ()) -primaryButtonSized = primaryButtonSized' - (C.only (C.noTransition (C.solidColor (C.Cyan C.C500)))) - [("hover", C.solidColor (C.Cyan C.C600) `C.withTransition` C.Duration_200) - ,("active", C.solidColor (C.Cyan C.C400) `C.withTransition` C.Duration_100)] - (C.color C.White) - (C.color (C.Cyan C.C500)) +-- primaryButtonSized :: DomBuilder t m => TWSize -> TWSize -> Text -> m (Event t ()) +-- primaryButtonSized = primaryButtonSized' +-- (C.only (C.noTransition (C.solidColor (C.Cyan C.C500)))) +-- [("hover", C.solidColor (C.Cyan C.C600) `C.withTransition` C.Duration_200) +-- ,("active", C.solidColor (C.Cyan C.C400) `C.withTransition` C.Duration_100)] +-- (C.color C.White) +-- (C.color (C.Cyan C.C500)) primaryButtonSized' :: DomBuilder t m @@ -361,13 +352,13 @@ primaryButtonSized' bgCol bgStates txtCol ringCol height width buttonText = do name = T.filter (\c -> isAlphaNum c || isSpace c ) buttonText -primaryButton :: DomBuilder t m => Text -> m (Event t ()) -primaryButton = primaryButton' - (C.only (C.noTransition (C.solidColor (C.Cyan C.C500)))) - [("hover", C.solidColor (C.Cyan C.C600) `C.withTransition` C.Duration_200) - ,("active", C.solidColor (C.Cyan C.C400) `C.withTransition` C.Duration_100)] - (C.color C.White) - (C.color (C.Cyan C.C500)) +-- primaryButton :: DomBuilder t m => Text -> m (Event t ()) +-- primaryButton = primaryButton' +-- (C.only (C.noTransition (C.solidColor (C.Cyan C.C500)))) +-- [("hover", C.solidColor (C.Cyan C.C600) `C.withTransition` C.Duration_200) +-- ,("active", C.solidColor (C.Cyan C.C400) `C.withTransition` C.Duration_100)] +-- (C.color C.White) +-- (C.color (C.Cyan C.C500)) primaryButton' :: DomBuilder t m diff --git a/src/Templates/Partials/Containers.hs b/src/Templates/Partials/Containers.hs index a9709af..7bf3e38 100644 --- a/src/Templates/Partials/Containers.hs +++ b/src/Templates/Partials/Containers.hs @@ -16,8 +16,8 @@ import Reflex.Dom.Core screenContainer :: (DomBuilder t m) => m a -> m a screenContainer = elClass "div" $(classh' [w .~~ TWSize_Screen, h .~~ TWSize_Screen, custom .~ "flex flex-col overflow-hidden"]) -toggleButton :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => Text -> m (Dynamic t Bool) -toggleButton = toggleButton' (color White) +-- toggleButton :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => Text -> m (Dynamic t Bool) +-- toggleButton = toggleButton' (color White) -- | Parameterized version with custom text color toggleButton' :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => ColorWithOpacity -> Text -> m (Dynamic t Bool) diff --git a/src/Templates/Partials/Containers/Dropdown.hs b/src/Templates/Partials/Containers/Dropdown.hs index 68a0b59..0ab2f1e 100644 --- a/src/Templates/Partials/Containers/Dropdown.hs +++ b/src/Templates/Partials/Containers/Dropdown.hs @@ -14,18 +14,18 @@ import qualified Data.Text as T -- | TODO: take an element config for the options elements as well -- | TODO: clean up and upstream to reflex-dom-contrib -dropdown' - :: ( MonadFix m - , DomBuilder t m - ) - => Map.Map a T.Text - -> SelectElementConfig er t (DomBuilderSpace m) - -> m (Dynamic t a) -dropdown' = dropdown'' - (only (noTransition (solidColor White))) - [ ("def", noTransition (color (Gray C300))) - , ("focus", noTransition (color (Cyan C500))) - ] +-- dropdown' +-- :: ( MonadFix m +-- , DomBuilder t m +-- ) +-- => Map.Map a T.Text +-- -> SelectElementConfig er t (DomBuilderSpace m) +-- -> m (Dynamic t a) +-- dropdown' = dropdown'' +-- (only (noTransition (solidColor White))) +-- [ ("def", noTransition (color (Gray C300))) +-- , ("focus", noTransition (color (Cyan C500))) +-- ] -- | Parameterized version with custom colors dropdown'' @@ -67,19 +67,19 @@ dropdown'' bgCol borderCol options cfg' = mdo (e, _) <- elAttr' "option" ("value" =: optText) $ text optText pure $ optText <$ domEvent Click e -dropdownWithDefault - :: ( MonadFix m - , DomBuilder t m - ) - => Map.Map a T.Text - -> T.Text - -> SelectElementConfig er t (DomBuilderSpace m) - -> m (Dynamic t a) -dropdownWithDefault = dropdownWithDefault' - (only (noTransition (solidColor White))) - [ ("def", noTransition (color (Gray C300))) - , ("focus", noTransition (color (Cyan C500))) - ] +-- dropdownWithDefault +-- :: ( MonadFix m +-- , DomBuilder t m +-- ) +-- => Map.Map a T.Text +-- -> T.Text +-- -> SelectElementConfig er t (DomBuilderSpace m) +-- -> m (Dynamic t a) +-- dropdownWithDefault = dropdownWithDefault' +-- (only (noTransition (solidColor White))) +-- [ ("def", noTransition (color (Gray C300))) +-- , ("focus", noTransition (color (Cyan C500))) +-- ] -- | Parameterized version with custom colors dropdownWithDefault' diff --git a/src/Templates/Partials/Errors.hs b/src/Templates/Partials/Errors.hs index ef54f77..d002c87 100644 --- a/src/Templates/Partials/Errors.hs +++ b/src/Templates/Partials/Errors.hs @@ -33,9 +33,9 @@ maybeDisplay template val = dyn_ $ ffor val $ \case displayOn :: Template t m => (a -> m ()) -> Event t a -> m () displayOn template ev = maybeDisplay template =<< holdDyn Nothing (Just <$> ev) --- | Render an error message. -errorMessage :: Template t m => Text -> m () -errorMessage = errorMessage' (withOpacity (Rose C500) 70) +-- -- | Render an error message. +-- errorMessage :: Template t m => Text -> m () +-- errorMessage = errorMessage' (withOpacity (Rose C500) 70) -- | Render an error message with custom text color. errorMessage' :: Template t m => ColorWithOpacity -> Text -> m () diff --git a/src/Templates/Partials/Invitebar.hs b/src/Templates/Partials/Invitebar.hs index edc69e0..203aa0f 100644 --- a/src/Templates/Partials/Invitebar.hs +++ b/src/Templates/Partials/Invitebar.hs @@ -19,14 +19,14 @@ import Templates.Partials.Buttons emailParse :: a -> Either T.Text Bool emailParse _ = Right True --- | Builds an input bar for emails, it returns both the input and --- the button that sends it. -invitebar :: (PostBuild t m, DomBuilder t m, MonadHold t m, MonadFix m) => Text -> m (InputEl t m, Event t ()) -invitebar = invitebar' - (only (noTransition (solidColor White))) - (only (noTransition (solidColor Transparent))) - (only (noTransition (color (Rose C600)))) - (only (color (Rose C600))) +-- -- | Builds an input bar for emails, it returns both the input and +-- -- the button that sends it. +-- invitebar :: (PostBuild t m, DomBuilder t m, MonadHold t m, MonadFix m) => Text -> m (InputEl t m, Event t ()) +-- invitebar = invitebar' +-- (only (noTransition (solidColor White))) +-- (only (noTransition (solidColor Transparent))) +-- (only (noTransition (color (Rose C600)))) +-- (only (color (Rose C600))) -- | Parameterized version with custom colors invitebar' @@ -101,3 +101,4 @@ invitebar' bgCol inputBgCol borderCol textCol placeholder = do $ dynText feedback return invbar + diff --git a/src/Templates/Partials/Lists.hs b/src/Templates/Partials/Lists.hs index fbbd489..357e9e5 100644 --- a/src/Templates/Partials/Lists.hs +++ b/src/Templates/Partials/Lists.hs @@ -29,12 +29,12 @@ data ListItemConfig t = ListItemConfig -- | The default configuration for list items is to have no subtext, no -- icon, no highlighting information, and to not be clickable. -- Uses default light theme colors (for dark backgrounds, use defListItemConfig' with custom colors). -defListItemConfig :: Applicative (Dynamic t) => ListItemConfig t -defListItemConfig = defListItemConfig' - (only (noTransition (solidColor Transparent))) -- bg: transparent - (only (noTransition (color (Gray C200)))) -- border: gray-200 - (only (color White)) -- text: white - (only (color (Gray C300))) +-- defListItemConfig :: Applicative (Dynamic t) => ListItemConfig t +-- defListItemConfig = defListItemConfig' +-- (only (noTransition (solidColor Transparent))) -- bg: transparent +-- (only (noTransition (color (Gray C200)))) -- border: gray-200 +-- (only (color White)) -- text: white +-- (only (color (Gray C300))) -- | Configurable version with custom colors defListItemConfig' diff --git a/src/Templates/Partials/Modal.hs b/src/Templates/Partials/Modal.hs index a41aa16..2678642 100644 --- a/src/Templates/Partials/Modal.hs +++ b/src/Templates/Partials/Modal.hs @@ -11,21 +11,21 @@ import Control.Monad.Fix import Data.Text as T -modal :: ( DomBuilder t m - , MonadFix m - , MonadHold t m - , PostBuild t m - ) - => ImgSrc - -> Event t () - -> m a - -> m (Event t a) -modal = modal' - (C.solidColorOpacity C.Black 40) -- overlay background - (C.solidColor (C.Slate C.C900)) -- content background (dark navy) - (C.color C.Black) -- border color - (C.color C.White) -- text color - (C.color (C.Rose C.C500)) -- close button color +-- modal :: ( DomBuilder t m +-- , MonadFix m +-- , MonadHold t m +-- , PostBuild t m +-- ) +-- => ImgSrc +-- -> Event t () +-- -> m a +-- -> m (Event t a) +-- modal = modal' +-- (C.solidColorOpacity C.Black 40) -- overlay background +-- (C.solidColor (C.Slate C.C900)) -- content background (dark navy) +-- (C.color C.Black) -- border color +-- (C.color C.White) -- text color +-- (C.color (C.Rose C.C500)) -- close button color modal' :: ( DomBuilder t m diff --git a/src/Templates/Partials/Searchbar.hs b/src/Templates/Partials/Searchbar.hs index 376f614..54a4d55 100644 --- a/src/Templates/Partials/Searchbar.hs +++ b/src/Templates/Partials/Searchbar.hs @@ -11,15 +11,15 @@ import Data.Text (Text) import Reflex.Dom.Core import Templates.Types -searchbar - :: DomBuilder t m - => Text - -> Event t a - -> m (InputEl t m) -searchbar = searchbar' - (only (noTransition (solidColor White))) - (only (noTransition (solidColor Transparent))) - (only (color Black)) +-- searchbar +-- :: DomBuilder t m +-- => Text +-- -> Event t a +-- -> m (InputEl t m) +-- searchbar = searchbar' +-- (only (noTransition (solidColor White))) +-- (only (noTransition (solidColor Transparent))) +-- (only (color Black)) -- | Parameterized version with custom colors searchbar' From 39fd532eef82c58eac2c265d3242f58d110425d5 Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Sun, 1 Mar 2026 20:14:14 -0500 Subject: [PATCH 10/14] remove hardcoded variants --- src/Templates/Partials/Buttons.hs | 6 +++--- src/Templates/Partials/Containers.hs | 18 ++++++++++++------ src/Templates/Partials/Invitebar.hs | 6 ++++-- src/Templates/Partials/Lists.hs | 4 ++-- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Templates/Partials/Buttons.hs b/src/Templates/Partials/Buttons.hs index 864d225..39b0b08 100644 --- a/src/Templates/Partials/Buttons.hs +++ b/src/Templates/Partials/Buttons.hs @@ -12,6 +12,9 @@ import Data.Proxy import Data.Char (isAlphaNum, isSpace) +-- sendButton :: DomBuilder t m => m (Event t ()) +-- sendButton = iconButton "send" + iconButton' :: DomBuilder t m => C.WhenTW (C.WithTransition C.GradientColor) -- ^ Background color (with states) @@ -173,9 +176,6 @@ secondaryButton bgCol txtCol borderCol ringCol cs label = do , C.custom .~ cs ] -sendButton :: DomBuilder t m => m (Event t ()) -sendButton = iconButton "send" - -- navyBlueButton :: DomBuilder t m => Text -> m (Event t ()) -- navyBlueButton = navyBlueButton' diff --git a/src/Templates/Partials/Containers.hs b/src/Templates/Partials/Containers.hs index 7bf3e38..abe604a 100644 --- a/src/Templates/Partials/Containers.hs +++ b/src/Templates/Partials/Containers.hs @@ -55,21 +55,27 @@ toggleButton' txtCol label = do -- | Wrap the body contents in a container that can be collapsed by -- clicking on its header. As an optimisation, when the container is -- collapsed, the inner tree is not rendered. -collapsibleContainer_ :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => Text -> m a -> m () -collapsibleContainer_ label body = do - toggled <- toggleButton label +collapsibleContainer_ + :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) + => ColorWithOpacity -- ^ Text color + -> Text -> m a -> m () +collapsibleContainer_ txtCol label body = do + toggled <- toggleButton' txtCol label dyn_ (bool blank (void body) <$> toggled) -- | Wrap the body contents in a container that can be collapsed by -- clicking on its header. -collapsibleContainer :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => Text -> m a -> m a -collapsibleContainer label body = do +collapsibleContainer + :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) + => ColorWithOpacity -- ^ Text color + -> Text -> m a -> m a +collapsibleContainer txtCol label body = do let bodyClasses :: Bool -> Text bodyClasses shown = bool "hidden" mempty shown - toggled <- toggleButton label + toggled <- toggleButton' txtCol label elDynClass "div" (bodyClasses <$> toggled) body diff --git a/src/Templates/Partials/Invitebar.hs b/src/Templates/Partials/Invitebar.hs index 203aa0f..f48e6a3 100644 --- a/src/Templates/Partials/Invitebar.hs +++ b/src/Templates/Partials/Invitebar.hs @@ -35,9 +35,11 @@ invitebar' -> WhenTW (WithTransition GradientColor) -- ^ Input background color -> WhenTW (WithTransition ColorWithOpacity) -- ^ Border color -> WhenTW ColorWithOpacity -- ^ Feedback text color + -> GradientColor -- ^ Button background color + -> ColorWithOpacity -- ^ Button icon color -> Text -> m (InputEl t m, Event t ()) -invitebar' bgCol inputBgCol borderCol textCol placeholder = do +invitebar' bgCol inputBgCol borderCol textCol btnBgCol btnIconCol placeholder = do rec let emailText e = case emailParse e of Right True -> "Invitation sent to " <> e @@ -67,7 +69,7 @@ invitebar' bgCol inputBgCol borderCol textCol placeholder = do <> "placeholder" =: placeholder <> "type" =: "email" ) - invButton' <- iconButtonEnabled toEnable "send" + invButton' <- iconButtonEnabled' btnBgCol btnIconCol toEnable "send" return (invInput', invButton') diff --git a/src/Templates/Partials/Lists.hs b/src/Templates/Partials/Lists.hs index 357e9e5..ed0979d 100644 --- a/src/Templates/Partials/Lists.hs +++ b/src/Templates/Partials/Lists.hs @@ -56,8 +56,8 @@ defListItemConfig' bgCol borderCol textCol subtextCol = ListItemConfig , _listItemConfig_subtextColor = subtextCol } -instance Reflex t => Default (ListItemConfig t) where - def = defListItemConfig +-- instance Reflex t => Default (ListItemConfig t) where +-- def = defListItemConfig -- | Render the given 'Text' with highlighting given by the -- 'ListItemConfig'. From 966a7a36d2fddee9e7156903b8de24f50f78a287 Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Tue, 3 Mar 2026 13:36:50 -0500 Subject: [PATCH 11/14] feat(containers): add CollapsibleState type for initial open/closed state Add CollapsibleState sum type (Open | Closed) to collapsibleContainerWithImage allowing callers to specify whether the container starts expanded or collapsed. --- src/Templates/Partials/Containers.hs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Templates/Partials/Containers.hs b/src/Templates/Partials/Containers.hs index abe604a..fbd3e6d 100644 --- a/src/Templates/Partials/Containers.hs +++ b/src/Templates/Partials/Containers.hs @@ -13,6 +13,14 @@ import Control.Monad.Fix import Control.Monad import Reflex.Dom.Core +-- | Initial state for collapsible containers +data CollapsibleState = Open | Closed + deriving (Eq, Show) + +isOpen :: CollapsibleState -> Bool +isOpen Open = True +isOpen Closed = False + screenContainer :: (DomBuilder t m) => m a -> m a screenContainer = elClass "div" $(classh' [w .~~ TWSize_Screen, h .~~ TWSize_Screen, custom .~ "flex flex-col overflow-hidden"]) @@ -84,21 +92,22 @@ collapsibleContainer txtCol label body = do -- clicking on its header. collapsibleContainerWithImage :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) - => Text + => CollapsibleState -- ^ Initial state + -> Text -> Text -> m a -> m a -collapsibleContainerWithImage imgSrc label body = do +collapsibleContainerWithImage initialState imgSrc label body = do let bodyClasses :: Bool -> Text bodyClasses shown = bool "hidden" mempty shown - toggled <- toggleButtonWithImage imgSrc label + toggled <- toggleButtonWithImage initialState imgSrc label elDynClass "div" (bodyClasses <$> toggled) body -toggleButtonWithImage :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => Text -> Text -> m (Dynamic t Bool) -toggleButtonWithImage imgSrc label = do +toggleButtonWithImage :: (MonadFix m, DomBuilder t m, PostBuild t m, MonadHold t m) => CollapsibleState -> Text -> Text -> m (Dynamic t Bool) +toggleButtonWithImage initialState imgSrc label = do let classes :: Bool -> Text classes shown = classhUnsafe $ @@ -121,7 +130,7 @@ toggleButtonWithImage imgSrc label = do dynText "expand_less" let toggleEv = domEvent Click labelEl - toggled <- holdUniqDyn =<< toggle True toggleEv + toggled <- holdUniqDyn =<< toggle (isOpen initialState) toggleEv pure toggled From 45cecfd734286d2ba38e00687a86f3020c9787cf Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Mon, 16 Mar 2026 17:31:03 -0400 Subject: [PATCH 12/14] overridable message input --- src/Templates/Partials/Inputs.hs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Templates/Partials/Inputs.hs b/src/Templates/Partials/Inputs.hs index a40b5c1..2826393 100644 --- a/src/Templates/Partials/Inputs.hs +++ b/src/Templates/Partials/Inputs.hs @@ -16,28 +16,30 @@ commonClassesText = $(classh' [text_size .|~ [XL2, XL4], custom .~ "font-label" messageInput' :: Template t m - => Event t (Map AttributeName Text) + => T.Text + -> Event t (Map AttributeName Text) -> Event t () -> m (InputEl t m) -messageInput' newAttributes clearEvent = inputElement $ def - & initialAttributes .~ messageInputAttrs +messageInput' textClass newAttributes clearEvent = inputElement $ def + & initialAttributes .~ messageInputAttrs textClass & inputElementConfig_setValue .~ ("" <$ clearEvent) & (inputElementConfig_elementConfig . elementConfig_modifyAttributes) .~ ((fmap.fmap) Just newAttributes) messageInput :: Template t m - => Event t (Map AttributeName Text) + => T.Text + -> Event t (Map AttributeName Text) -> Event t () -> m (TextAreaElement EventResult (DomBuilderSpace m) t) -messageInput newAttributes clearEvent = textAreaElement $ def - & initialAttributes .~ messageInputAttrs +messageInput textClass newAttributes clearEvent = textAreaElement $ def + & initialAttributes .~ messageInputAttrs textClass & textAreaElementConfig_setValue .~ ("" <$ clearEvent) & (textAreaElementConfig_elementConfig . elementConfig_modifyAttributes) .~ ((fmap.fmap) Just newAttributes) -messageInputAttrs :: Map.Map AttributeName T.Text -messageInputAttrs = - "class" =: $(classh' [w .~~ TWSize_Full, p .~~ TWSize 4, custom .~ "text-2xl"] ) +messageInputAttrs :: T.Text -> Map.Map AttributeName T.Text +messageInputAttrs textClass = + "class" =: ($(classh' [w .~~ TWSize_Full, p .~~ TWSize 4]) <> " " <> textClass) <> "placeholder" =: "Type your message" <> "type" =: "text" <> "rows" =: "2" From 637729efc164feb18a3d553f5a299e887a404042 Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Mon, 16 Mar 2026 21:01:08 -0400 Subject: [PATCH 13/14] Style message input with ClasshSS focus/bg/border --- src/Templates/Partials/Inputs.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Templates/Partials/Inputs.hs b/src/Templates/Partials/Inputs.hs index 2826393..0d98178 100644 --- a/src/Templates/Partials/Inputs.hs +++ b/src/Templates/Partials/Inputs.hs @@ -39,7 +39,7 @@ messageInput textClass newAttributes clearEvent = textAreaElement $ def messageInputAttrs :: T.Text -> Map.Map AttributeName T.Text messageInputAttrs textClass = - "class" =: ($(classh' [w .~~ TWSize_Full, p .~~ TWSize 4]) <> " " <> textClass) + "class" =: ($(classh' [custom .~ "focus:outline-none", bgColor .~~ solidColor (hex "2D2644"), br .~~ R_Lg, w .~~ TWSize_Full, p .~~ TWSize 4]) <> " " <> textClass) <> "placeholder" =: "Type your message" <> "type" =: "text" <> "rows" =: "2" From f806cc1f8194d9d7cb53b8e697ace79cbe071aa2 Mon Sep 17 00:00:00 2001 From: lazyLambda Date: Thu, 19 Mar 2026 17:02:31 -0400 Subject: [PATCH 14/14] Convert margin TWSize to twSize' for TWSizeOrFraction compatibility ClasshSS BoxMargin fields changed from TWSize to TWSizeOrFraction. All margin setters (mt, mb, ml, mr, mx, my) now use twSize' N instead of TWSize N. --- src/Templates/Partials/Buttons.hs | 6 +++--- src/Templates/Partials/Containers.hs | 2 +- src/Templates/Partials/Containers/Dropdown.hs | 4 ++-- src/Templates/Partials/Errors.hs | 2 +- src/Templates/Partials/Inputs.hs | 2 +- src/Templates/Partials/Invitebar.hs | 2 +- src/Templates/Partials/Modal.hs | 8 ++++---- src/Templates/Partials/Searchbar.hs | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Templates/Partials/Buttons.hs b/src/Templates/Partials/Buttons.hs index 39b0b08..f37f27d 100644 --- a/src/Templates/Partials/Buttons.hs +++ b/src/Templates/Partials/Buttons.hs @@ -105,7 +105,7 @@ primaryButtonDyn' bgCol bgStates txtCol shadowVal ringStates ringCol enabled but , C.bgColor .~^ bgStates , C.w .~~ C.TWSize_Full , C.p .~~ C.TWSize 4 - , C.mt .~~ C.TWSize 12 + , C.mt .~~ C.twSize' 12 , C.shadow .~~ shadowVal , C.br .~~ C.R_Normal , C.border . C.outline .~ [("focus", C.Outline_None)] @@ -242,7 +242,7 @@ primaryButtonImageDyn bgCol bgStates ringCol dynImageHref height width = do imgAttrs = (\src -> otherImgAttrs <> "src" =: src) <$> dynImageHref boxCfg = C.classhUnsafe [ C.w .~~ C.TWSize_Full , C.p .~~ C.TWSize 4 - , C.mt .~~ C.TWSize 16 + , C.mt .~~ C.twSize' 16 , C.bgColor .~ bgCol , C.bgColor .~^ bgStates , C.br .~~ C.R_Xl @@ -306,7 +306,7 @@ primaryButtonBoxCfg primaryButtonBoxCfg bgCol bgStates ringCol = C.classhUnsafe [ C.w .~~ C.TWSize_Full , C.p .~~ C.TWSize 4 - , C.mt .~~ C.TWSize 16 + , C.mt .~~ C.twSize' 16 , C.bgColor .~ bgCol , C.bgColor .~^ bgStates , C.br .~~ C.R_Xl diff --git a/src/Templates/Partials/Containers.hs b/src/Templates/Partials/Containers.hs index fbd3e6d..86340f0 100644 --- a/src/Templates/Partials/Containers.hs +++ b/src/Templates/Partials/Containers.hs @@ -46,7 +46,7 @@ toggleButton' txtCol label = do -- be animated; Having the "neutral" position be "no transform" means -- the icon won't do a twirl on page load. rec - (labelEl, _) <- elClass' "div" (classhUnsafe [ mt .~~ TWSize 8 + (labelEl, _) <- elClass' "div" (classhUnsafe [ mt .~~ twSize' 8 , cursor .~~ CursorPointer , custom .~ "flex flex-row justify-between" ]) $ do diff --git a/src/Templates/Partials/Containers/Dropdown.hs b/src/Templates/Partials/Containers/Dropdown.hs index 0ab2f1e..4ee8c10 100644 --- a/src/Templates/Partials/Containers/Dropdown.hs +++ b/src/Templates/Partials/Containers/Dropdown.hs @@ -45,7 +45,7 @@ dropdown'' bgCol borderCol options cfg' = mdo , bc .~ borderCol , br .~~ R_Lg , border . outline .~ [("focus", Outline_None)] - , mb .~~ TWSize 5 + , mb .~~ twSize' 5 , bgColor .~ bgCol , custom .~ "font-[Sarabun] text-lg" ] @@ -100,7 +100,7 @@ dropdownWithDefault' bgCol borderCol options start cfg' = mdo , bc .~ borderCol , br .~~ R_Lg , border . outline .~ [("focus", Outline_None)] - , mb .~~ TWSize 5 + , mb .~~ twSize' 5 , bgColor .~ bgCol ] let safeInitial = start diff --git a/src/Templates/Partials/Errors.hs b/src/Templates/Partials/Errors.hs index d002c87..843222f 100644 --- a/src/Templates/Partials/Errors.hs +++ b/src/Templates/Partials/Errors.hs @@ -40,5 +40,5 @@ displayOn template ev = maybeDisplay template =<< holdDyn Nothing (Just <$> ev) -- | Render an error message with custom text color. errorMessage' :: Template t m => ColorWithOpacity -> Text -> m () errorMessage' txtCol t = - elClass "div" $(classh' [mt .~~ TWSize 1, h .~~ twSize' 4]) $ + elClass "div" $(classh' [mt .~~ twSize' 1, h .~~ twSize' 4]) $ textS (classhUnsafe [text_color .~~ txtCol]) t diff --git a/src/Templates/Partials/Inputs.hs b/src/Templates/Partials/Inputs.hs index 0d98178..e676485 100644 --- a/src/Templates/Partials/Inputs.hs +++ b/src/Templates/Partials/Inputs.hs @@ -9,7 +9,7 @@ import Data.Map as Map import Classh as C commonClassesBox :: T.Text -commonClassesBox = $(classh' [mx .~~ TWSize 1, custom .~ "focus:outline-none bg-inset flex-grow" ]) +commonClassesBox = $(classh' [mx .~~ twSize' 1, custom .~ "focus:outline-none bg-inset flex-grow" ]) commonClassesText :: T.Text commonClassesText = $(classh' [text_size .|~ [XL2, XL4], custom .~ "font-label" ]) diff --git a/src/Templates/Partials/Invitebar.hs b/src/Templates/Partials/Invitebar.hs index f48e6a3..c352b22 100644 --- a/src/Templates/Partials/Invitebar.hs +++ b/src/Templates/Partials/Invitebar.hs @@ -46,7 +46,7 @@ invitebar' bgCol inputBgCol borderCol textCol btnBgCol btnIconCol placeholder = -- TODO: remove Right False -> "Email must be @aceinterviewprep.io" Left _ -> "Invalid email format" - invbar@(invInput, invButton) <- elClass "div" (classhUnsafe [my .~~ TWSize 2 + invbar@(invInput, invButton) <- elClass "div" (classhUnsafe [my .~~ twSize' 2 , w .~~ TWSize_Full , custom .~ "shadow-button flex flex-row" , bgColor .~ bgCol diff --git a/src/Templates/Partials/Modal.hs b/src/Templates/Partials/Modal.hs index 2678642..4a60ab6 100644 --- a/src/Templates/Partials/Modal.hs +++ b/src/Templates/Partials/Modal.hs @@ -44,9 +44,9 @@ modal' -> m (Event t a) modal' overlayBg contentBg borderCol txtCol closeBtnCol xButtonImgSrc open modalDom = mdo -- Overlay positioning must remain as inline style (no Classh for position:fixed, z-index, etc.) - let styleBase = "position:fixed;z-index:20;padding-top:100px;left:0;top:0;width:100%;height:100%;overflow:auto;" - hideModal = ("style" =: ("display:none;" <> styleBase) <> "class" =: overlayClass) - showModal = ("style" =: ("display:block;" <> styleBase) <> "class" =: overlayClass) + let styleBase = "position:fixed;z-index:9999;padding-top:100px;left:0;top:0;width:100%;height:100%;overflow:auto;" + hideModal = ("style" =: ("display:none;visibility:hidden;" <> styleBase) <> "class" =: overlayClass) + showModal = ("style" =: ("display:block;visibility:visible;" <> styleBase) <> "class" =: overlayClass) modalAttrs <- holdDyn hideModal $ mergeWith const [showModal <$ open, hideModal <$ close] close <- elDynAttr "div" modalAttrs $ do elAttr "div" ("style" =: "margin:auto;width:80%;" <> "class" =: contentClass) $ do @@ -68,7 +68,7 @@ modal' overlayBg contentBg borderCol txtCol closeBtnCol xButtonImgSrc open modal ] <> " " <> C.classhUnsafe [ C.text_color .~~ txtCol ] closeSpanClass = C.classhUnsafe [ C.pl .~~ C.TWSize 5 - , C.ml .~~ C.TWSize 5 + , C.ml .~~ C.twSize' 5 , C.pb .~~ C.TWSize 4 , C.custom .~ "grid justify-items-end" ] <> " " <> C.classhUnsafe [ C.text_color .~~ closeBtnCol ] diff --git a/src/Templates/Partials/Searchbar.hs b/src/Templates/Partials/Searchbar.hs index 54a4d55..05b850e 100644 --- a/src/Templates/Partials/Searchbar.hs +++ b/src/Templates/Partials/Searchbar.hs @@ -31,7 +31,7 @@ searchbar' -> Event t a -> m (InputEl t m) searchbar' bgCol inputBgCol txtCol placeholder clearEvent = do - elClass "div" (classhUnsafe [mt .~~ TWSize 0, w .~~ TWSize_Full, bgColor .~ bgCol, br .~~ R_Normal, custom .~ "flex flex-row"]) $ do + elClass "div" (classhUnsafe [mt .~~ twSize' 0, w .~~ TWSize_Full, bgColor .~ bgCol, br .~~ R_Normal, custom .~ "flex flex-row"]) $ do elClass "button" (classhUnsafe [ px .~~ TWSize 3 , br .~~ R_Normal , shadow .~~ Shadow_Md