{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-} module Web.View.Types where import Data.Kind (Type) import Data.Map (Map) import Data.String (IsString (..)) import Data.Text (Text, pack, unpack) import Data.Text qualified as T import GHC.Generics (Generic) import Numeric (showFFloat) import Text.Casing (kebab) data Content = Node Element | Text Text | -- | Raw embedded HTML or SVG. See 'Web.View.Element.raw' Raw Text deriving (Show, Eq) -- | A single HTML tag. Note that the class attribute is stored separately from the rest of the attributes to make adding styles easier data Element = Element { inline :: Bool , name :: Name , attributes :: Attributes () , children :: [Content] } deriving (Show, Eq) -- | Construct an Element element :: Name -> Attributes c -> [Content] -> Element element n atts = Element False n (stripContext atts) where stripContext :: Attributes c -> Attributes () stripContext (Attributes cls other) = Attributes cls other -- | The Attributes for an 'Element'. Classes are merged and managed separately from the other attributes. data Attributes c = Attributes { classes :: CSS , other :: Map Name AttValue } deriving (Show, Eq) instance Semigroup (Attributes c) where a1 <> a2 = Attributes (a1.classes <> a2.classes) (a1.other <> a2.other) instance Monoid (Attributes c) where mempty = Attributes mempty mempty type Attribute = (Name, AttValue) type Name = Text type AttValue = Text -- * Attribute Modifiers {- | Element functions expect a modifier function as their first argument. These can add attributes and classes. Combine multiple `Mod`s with (`.`) > userEmail :: User -> View c () > userEmail user = input (fontSize 16 . active) (text user.email) > where > active = isActive user then bold else id If you don't want to specify any attributes, you can use `id` > plainView :: View c () > plainView = el id "No styles" -} type Mod (context :: Type) = Attributes context -> Attributes context -- * Atomic CSS -- TODO: document atomic CSS here? -- | All the atomic classes used in a 'Web.View.View' type CSS = Map Selector Class -- | Atomic classes include a selector and the corresponding styles data Class = Class { selector :: Selector , properties :: Styles } deriving (Show, Eq) -- | The styles to apply for a given atomic 'Class' type Styles = Map Name StyleValue -- | A parent selector limits the selector to only apply when a descendent of the parent in question type Ancestor = Text -- | A child selector limits data ChildCombinator = AllChildren | ChildWithName Text deriving (Show, Eq, Ord) instance IsString ChildCombinator where fromString s = ChildWithName (fromString s) -- | The selector to use for the given atomic 'Class' data Selector = Selector { media :: Maybe Media , ancestor :: Maybe Ancestor , child :: Maybe ChildCombinator , pseudo :: Maybe Pseudo , className :: ClassName } deriving (Eq, Ord, Show) instance IsString Selector where fromString s = selector (fromString s) -- | Create a 'Selector' given only a 'ClassName' selector :: ClassName -> Selector selector c = Selector { pseudo = Nothing , ancestor = Nothing , child = Nothing , media = Nothing , className = c } -- | A class name newtype ClassName = ClassName { text :: Text } deriving newtype (Eq, Ord, Show, Monoid, Semigroup) instance IsString ClassName where fromString s = ClassName $ pack s -- | Create a class name, escaping special characters className :: Text -> ClassName className = ClassName . T.toLower . T.map noDot where noDot '.' = '-' noDot c = c -- | Convert a type into a className segment to generate unique compound style names based on the value class ToClassName a where toClassName :: a -> ClassName default toClassName :: (Show a) => a -> ClassName toClassName = className . T.pack . show instance ToClassName Int instance ToClassName Text where toClassName = className instance ToClassName Float where toClassName f = className $ pack $ showFFloat (Just 3) f "" instance ToClassName () where toClassName _ = "" {- | Psuedos allow for specifying styles that only apply in certain conditions. See `Web.View.Style.hover` etc > el (color Primary . hover (color White)) "hello" -} data Pseudo = Hover | Active | Even | Odd deriving (Show, Eq, Ord) -- | The value of a css style property newtype StyleValue = StyleValue String deriving newtype (IsString, Show, Eq, Monoid, Semigroup) -- | Use a type as a css style property value class ToStyleValue a where toStyleValue :: a -> StyleValue default toStyleValue :: (Show a) => a -> StyleValue toStyleValue = StyleValue . kebab . show instance ToStyleValue String where toStyleValue = StyleValue instance ToStyleValue Text where toStyleValue = StyleValue . unpack instance ToStyleValue Int instance ToStyleValue Float where -- this does not convert to a percent, just a ratio toStyleValue n = StyleValue $ showFFloat (Just 2) n "" instance ToStyleValue StyleValue where toStyleValue = id data Length = PxRem PxRem | Pct Float deriving (Show) instance ToClassName Length where toClassName (PxRem p) = toClassName p toClassName (Pct p) = toClassName p -- | Px, converted to Rem. Allows for the user to change the document font size and have the app scale accordingly. But allows the programmer to code in pixels to match a design newtype PxRem = PxRem' Int deriving newtype (Show, ToClassName, Num, Eq, Integral, Real, Ord, Enum) instance Num Length where -- only support numeric literals a + _ = a a * _ = a abs (PxRem a) = PxRem (abs a) abs (Pct a) = Pct (abs a) signum (PxRem a) = PxRem (signum a) signum (Pct a) = Pct (signum a) negate (PxRem a) = PxRem (negate a) negate (Pct a) = Pct (negate a) fromInteger n = PxRem (fromInteger n) instance ToStyleValue PxRem where toStyleValue (PxRem' 0) = "0px" toStyleValue (PxRem' 1) = "1px" toStyleValue (PxRem' n) = StyleValue $ show ((fromIntegral n :: Float) / 16.0) <> "rem" instance ToStyleValue Length where toStyleValue (PxRem p) = toStyleValue p toStyleValue (Pct n) = StyleValue $ showFFloat (Just 1) (n * 100) "" <> "%" -- | Milliseconds, used for transitions newtype Ms = Ms Int deriving (Show) deriving newtype (Num, ToClassName) instance ToStyleValue Ms where toStyleValue (Ms n) = StyleValue $ show n <> "ms" -- | Media allows for responsive designs that change based on characteristics of the window. See [Layout Example](https://github.com/seanhess/web-view/blob/master/example/Example/Layout.hs) data Media = MinWidth Int | MaxWidth Int deriving (Eq, Ord, Show) {- | Options for styles that support specifying various sides. This has a "fake" Num instance to support literals > border 5 > border (X 2) > border (TRBL 0 5 0 0) -} data Sides a = All a | TRBL a a a a | X a | Y a | XY a a -- Num instance is just to support literals instance (Num a) => Num (Sides a) where a + _ = a a * _ = a abs a = a negate a = a signum a = a fromInteger n = All (fromInteger n) -- | Element's attributes do not include class, which is separated. FlatAttributes generate the class attribute and include it newtype FlatAttributes = FlatAttributes {attributes :: Map Name AttValue} deriving (Generic) -- ** Colors {- | ToColor allows you to create a type containing your application's colors: > data AppColor > = White > | Primary > | Dark > > instance ToColor AppColor where > colorValue White = "#FFF" > colorValue Dark = "#333" > colorValue Primary = "#00F" > > hello :: View c () > hello = el (bg Primary . color White) "Hello" -} class ToColor a where colorValue :: a -> HexColor colorName :: a -> Text default colorName :: (Show a) => a -> Text colorName = T.toLower . pack . show -- | Hexidecimal Color. Can be specified with or without the leading '#'. Recommended to use an AppColor type instead of manually using hex colors. See 'Web.View.Types.ToColor' newtype HexColor = HexColor Text deriving (Show) instance ToColor HexColor where colorValue c = c colorName (HexColor a) = T.dropWhile (== '#') a instance ToStyleValue HexColor where toStyleValue (HexColor s) = StyleValue $ "#" <> unpack (T.dropWhile (== '#') s) instance IsString HexColor where fromString = HexColor . T.dropWhile (== '#') . T.pack instance ToClassName HexColor where toClassName = className . colorName data Align = Center deriving (Show, ToClassName, ToStyleValue) data None = None deriving (Show, ToClassName, ToStyleValue) -- data Size -- = Sm -- | Md -- | Lg -- | Xl -- | Xl2 -- deriving (Show, ToClassName)