├── .gitignore ├── README.md ├── elm-package.json ├── examples ├── HolyGrail.elm ├── Positioning.elm └── elm-package.json └── src └── Flex.elm /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff/ 2 | *.js 3 | *.html -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flex-html 2 | This library contains several affordances for laying out Html with elm-html using flexbox. 3 | 4 | To illustrate how it works, here is an example of how to do the holy grail layout using flex-html 5 | 6 | ### Holy Grail Layout 7 | 8 | ```elm 9 | background : Float -> String -> Html 10 | background grow color = 11 | let 12 | backgroundStyles = 13 | ("background-color", color) 14 | :: Flex.grow grow 15 | in 16 | div 17 | [ style backgroundStyles ] 18 | [] 19 | 20 | 21 | holyGrail : Html 22 | holyGrail = 23 | let 24 | topSection = background 1 "red" 25 | bottomSection = background 1 "black" 26 | leftSection = background 1 "blue" 27 | rightSection = background 1 "yellow" 28 | centerSection = background 4 "green" 29 | 30 | styleList = 31 | Flex.direction Flex.Horizontal 32 | ++ Flex.grow 8 33 | ++ Flex.display 34 | 35 | mainSection = 36 | div 37 | [ style styleList ] 38 | [ leftSection 39 | , centerSection 40 | , rightSection 41 | ] 42 | 43 | mainStyleList = 44 | [ ("width", "100vw") 45 | , ("height", "100vh") 46 | ] 47 | ++ Flex.display 48 | ++ Flex.direction Flex.Vertical 49 | 50 | in 51 | div 52 | [ style mainStyleList ] 53 | [ topSection 54 | , mainSection 55 | , bottomSection 56 | ] 57 | 58 | 59 | main : Html 60 | main = 61 | holyGrail 62 | ``` 63 | 64 | We populate the page with distinctly colored backgrounds (hence, the use of a `background` function). 65 | 66 | The holy grail layout has 5 sections: a top row section, a bottom row section, a left column section, a right column section, and a center content section. 67 | 68 | Each section is created as a background with a unique color to distinguish them: 69 | 70 | ```elm 71 | topSection = background 1 "red" 72 | bottomSection = background 1 "black" 73 | leftSection = background 1 "blue" 74 | rightSection = background 1 "yellow" 75 | centerSection = background 4 "green" 76 | ``` 77 | 78 | An then we simply lay them out. We first consider the vertically flowing sections. So we lay the top section atop a main section atop a bottom section as follows: 79 | 80 | ```elm 81 | mainStyleList = 82 | [ ("width", "100vw") 83 | , ("height", "100vh") 84 | ] 85 | ++ Flex.display 86 | ++ Flex.direction Flex.Vertical 87 | 88 | {-| ... -} 89 | 90 | div 91 | [ style mainStyleList ] 92 | [ topSection 93 | , mainSection 94 | , bottomSection 95 | ] 96 | ``` 97 | 98 | Where the main section is a horizontal layout of the left section, the center section, and the right section (from left to right) and is defined as follows: 99 | 100 | ```elm 101 | styleList = 102 | Flex.direction Flex.Horizontal 103 | ++ Flex.grow 8 104 | ++ Flex.display 105 | 106 | mainSection = 107 | div 108 | [ style styleList ] 109 | [ leftSection 110 | , centerSection 111 | , rightSection 112 | ] 113 | ``` 114 | 115 | And that's how one would implement the holy grail with flex-html. 116 | 117 | ### Advanced layout example 118 | 119 | flex-html is implemented as a collection of mixins. Each mixin allows you to define a flex property for a parent or child. 120 | 121 | To illustrate how you can use these mixins, here is an example of positioning labels in a manner which would otherwise be insanely difficult: 122 | 123 | ```elm 124 | label : String -> Html 125 | label value = 126 | let 127 | labelStyle = 128 | [ ("background-color", "red") 129 | , ("color", "white") 130 | , ("padding", "5px") 131 | , ("font-weight", "bold") 132 | ] 133 | in 134 | div 135 | [ style labelStyle ] 136 | [ text value ] 137 | 138 | 139 | main : Html 140 | main = 141 | let 142 | containerStyle = 143 | ("width", "100vw") 144 | :: ("height", "100vh") 145 | :: Flex.justifyContent Flex.Surround 146 | ++ Flex.alignItems Flex.Center 147 | ++ Flex.wrap Flex.NoWrap 148 | ++ Flex.display 149 | 150 | innerContainerStyle = 151 | ("height", "100%") 152 | :: Flex.flow Flex.Vertical Flex.NoWrap 153 | ++ Flex.justifyContent Flex.Surround 154 | ++ Flex.alignItems Flex.Center 155 | ++ Flex.display 156 | in 157 | div 158 | [ style containerStyle ] 159 | [ label "I am on the left" 160 | , div 161 | [ style innerContainerStyle ] 162 | [ label "I am on top" 163 | , label "I am absolutely centered" 164 | , label "I am down below" 165 | ] 166 | , label "I am on the right" 167 | ] 168 | ``` 169 | 170 | So, as you may see, we have a helper function called `label` to create our labels. 171 | 172 | We then layout the left label, the three central labels and the right label horizontally with: 173 | 174 | ```elm 175 | containerStyle = 176 | ("width", "100vw") 177 | :: ("height", "100vh") 178 | :: Flex.justifyContent Flex.Surround 179 | ++ Flex.alignItems Flex.Center 180 | ++ Flex.wrap Flex.NoWrap 181 | ++ Flex.display 182 | ``` 183 | where: 184 | * `justifyContent Surround` implies that the children will be equally spaced along the main axis (in this case, the horizontal axis) 185 | * `wrap NoWrap` implies that the children will not wrap if there isn't enough space to flex (not a concern in this example) 186 | * `alignItems Center` makes sure to center the items 187 | * `display` applies flex display on the div 188 | * `horizontal` is the default direction and thus main axis 189 | 190 | And the central section (containing the top, centered, and bottom labels) are laid out using: 191 | 192 | ```elm 193 | innerContainerStyle = 194 | ("height", "100%") 195 | :: Flex.flow Flex.Vertical Flex.NoWrap 196 | ++ Flex.justifyContent Flex.Surround 197 | ++ Flex.alignItems Flex.Center 198 | ++ Flex.display 199 | ``` 200 | where: 201 | * `flow Vertical NoWrap` sets Vertical as the direction, making that the main axis and disable wrapping as seen previously 202 | 203 | There are more mixins available. You can learn about these in the documentation as a reader exercise. 204 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.1", 3 | "summary": "Flexbox layout for elm-html", 4 | "repository": "https://github.com/TheSeamau5/flex-html.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "Flex" 11 | ], 12 | "dependencies": { 13 | "coreytrampe/elm-vendor": "1.0.0 <= v < 2.0.0", 14 | "elm-lang/core": "2.0.0 <= v < 3.0.0" 15 | }, 16 | "elm-version": "0.15.0 <= v < 0.16.0" 17 | } -------------------------------------------------------------------------------- /examples/HolyGrail.elm: -------------------------------------------------------------------------------- 1 | import Html exposing (Html, div, text) 2 | import Html.Attributes exposing (style) 3 | 4 | import Flex 5 | 6 | ---------------------------- 7 | -- HOLY GRAIL LAYOUT EXAMPLE 8 | ---------------------------- 9 | 10 | background : Float -> String -> Html 11 | background grow color = 12 | let 13 | backgroundStyles = 14 | ("background-color", color) 15 | :: Flex.grow grow 16 | in 17 | div 18 | [ style backgroundStyles ] 19 | [] 20 | 21 | 22 | holyGrail : Html 23 | holyGrail = 24 | let 25 | topSection = background 1 "red" 26 | bottomSection = background 1 "black" 27 | leftSection = background 1 "blue" 28 | rightSection = background 1 "yellow" 29 | centerSection = background 4 "green" 30 | 31 | styleList = 32 | Flex.direction Flex.Horizontal 33 | ++ Flex.grow 8 34 | ++ Flex.display 35 | 36 | mainSection = 37 | div 38 | [ style styleList ] 39 | [ leftSection 40 | , centerSection 41 | , rightSection 42 | ] 43 | 44 | mainStyleList = 45 | [ ("width", "100vw") 46 | , ("height", "100vh") 47 | ] 48 | ++ Flex.display 49 | ++ Flex.direction Flex.Vertical 50 | 51 | in 52 | div 53 | [ style mainStyleList ] 54 | [ topSection 55 | , mainSection 56 | , bottomSection 57 | ] 58 | 59 | 60 | main : Html 61 | main = 62 | holyGrail 63 | -------------------------------------------------------------------------------- /examples/Positioning.elm: -------------------------------------------------------------------------------- 1 | import Html exposing (Html, div, text) 2 | import Html.Attributes exposing (style) 3 | 4 | import Flex 5 | 6 | ---------------------- 7 | -- POSITIONING EXAMPLE 8 | ---------------------- 9 | 10 | label : String -> Html 11 | label value = 12 | let 13 | labelStyle = 14 | [ ("background-color", "red") 15 | , ("color", "white") 16 | , ("padding", "5px") 17 | , ("font-weight", "bold") 18 | ] 19 | in 20 | div 21 | [ style labelStyle ] 22 | [ text value ] 23 | 24 | 25 | main : Html 26 | main = 27 | let 28 | containerStyle = 29 | ("width", "100vw") 30 | :: ("height", "100vh") 31 | :: Flex.justifyContent Flex.Surround 32 | ++ Flex.alignItems Flex.Center 33 | ++ Flex.wrap Flex.NoWrap 34 | ++ Flex.display 35 | 36 | innerContainerStyle = 37 | ("height", "100%") 38 | :: Flex.flow Flex.Vertical Flex.NoWrap 39 | ++ Flex.justifyContent Flex.Surround 40 | ++ Flex.alignItems Flex.Center 41 | ++ Flex.display 42 | in 43 | div 44 | [ style containerStyle ] 45 | [ label "I am on the left" 46 | , div 47 | [ style innerContainerStyle ] 48 | [ label "I am on top" 49 | , label "I am absolutely centered" 50 | , label "I am down below" 51 | ] 52 | , label "I am on the right" 53 | ] 54 | -------------------------------------------------------------------------------- /examples/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Examples for flex-html.", 4 | "repository": "https://github.com/TheSeamau5/flex-html.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | ".", 8 | "../src" 9 | ], 10 | "exposed-modules": [], 11 | "dependencies": { 12 | "elm-lang/core": "2.0.0 <= v < 3.0.0", 13 | "evancz/elm-html": "3.0.0 <= v < 4.0.0", 14 | "coreytrampe/elm-vendor": "1.0.0 <= v < 2.0.0" 15 | }, 16 | "elm-version": "0.15.0 <= v < 0.16.0" 17 | } 18 | -------------------------------------------------------------------------------- /src/Flex.elm: -------------------------------------------------------------------------------- 1 | module Flex where 2 | 3 | {-| Companion library to elm-html. Helps with flexbox layout 4 | 5 | # Flex Types 6 | @docs Direction, Alignment, WrapValue 7 | 8 | # Flex Mixins 9 | Mixins can be used alone or as a combinator to specify flex-related styles. 10 | @docs display, flow, direction, wrap, alignItems, justifyContent, grow, shrink, basis, flex, order, alignSelf 11 | 12 | -} 13 | 14 | import Vendor 15 | 16 | 17 | {-| The `Direction` type specifies all the direction values possible for the 18 | `flexDirection` mixin. 19 | 20 | - Horizontal: Default value. The flexible items are displayed horizontally, as a row. 21 | - Vertical: The flexible items are displayed vertically, as a column. 22 | - HorizontalReverse: Same as `Horizontal`, but in reverse order. 23 | - verticalReverse: Same as `Vertical`, but in reverse order. 24 | -} 25 | type Direction 26 | = Horizontal 27 | | Vertical 28 | | HorizontalReverse 29 | | VerticalReverse 30 | 31 | 32 | {-| The `Alignment` type specifies all the values possible for the `alignItems` 33 | and `justifyConteent` mixins. 34 | 35 | - Start: Content is left-aligned. 36 | - Center: Content is center-aligned. 37 | - End: Content is right-aligned. 38 | - Stretch: Content-width is stretched to fill up the space. 39 | - Surround: Extra space is devided into equal spaces around the content. 40 | -} 41 | type Alignment 42 | = Start 43 | | Center 44 | | End 45 | | Stretch 46 | | Surround 47 | 48 | 49 | {-| The `WrapValue` type specifies all the wrapping values possible for the 50 | `wrap` mixin. 51 | 52 | - Wrap: Specifies that the flexible items will wrap if necessary. 53 | - NoWrap: Default value. Specifies that the flexible items will not wrap. 54 | - WrapReverse: Specifies that the flexible items will wrap, if necessary, in reverse order. 55 | -} 56 | type WrapValue 57 | = Wrap 58 | | NoWrap 59 | | WrapReverse 60 | 61 | 62 | {-| Displays an element as an block-level flex container. -} 63 | display : List (String, String) 64 | display = 65 | let displayValue = 66 | if Vendor.prefix == Vendor.Webkit 67 | then "-webkit-flex" 68 | else "flex" 69 | in 70 | [ ("display", displayValue) ] 71 | 72 | 73 | {-| The `flow` mixin specifies how much the item will grow relative to the rest 74 | of the flexible items inside the same container. 75 | -} 76 | flow: Direction -> WrapValue -> List (String, String) 77 | flow directionValue wrapValue = 78 | direction directionValue 79 | ++ wrap wrapValue 80 | 81 | 82 | {-| The `direction` mixin specifies the direction of the flexible items. 83 | -} 84 | direction : Direction -> List (String, String) 85 | direction directionValue = 86 | let (boxDirection, boxOrientation, value) = 87 | case directionValue of 88 | Horizontal -> 89 | ("normal", "horizontal", "row") 90 | 91 | Vertical -> 92 | ("normal", "vertical", "column") 93 | 94 | HorizontalReverse -> 95 | ("reverse", "horizontal", "row-reverse") 96 | 97 | VerticalReverse -> 98 | ("reverse", "vertical", "column-reverse") 99 | in 100 | [ ("-webkit-box-direction", boxDirection) 101 | , ("-webkit-box-orient", boxOrientation) 102 | , ("-webkit-flex-direction", value) 103 | , ("-ms-flex-direction", value) 104 | , ("flex-direction", value) 105 | ] 106 | 107 | 108 | {-| The `wrap` mixin specifies whether the flexible items should wrap or not. 109 | -} 110 | wrap : WrapValue -> List (String, String) 111 | wrap wrapValue = 112 | let (vendorValue, value) = 113 | case wrapValue of 114 | Wrap -> 115 | ("wrap", "wrap") 116 | 117 | NoWrap -> 118 | ("none", "nowrap") 119 | 120 | WrapReverse -> 121 | ("wrap-reverse", "wrap-reverse") 122 | in 123 | [ ("-webkit-flex-wrap", value) 124 | , ("-ms-flex-wrap", vendorValue) 125 | , ("flex-wrap", value) 126 | ] 127 | 128 | 129 | {-| The `alignItems` mixin specifies the default alignment for items inside 130 | the flexible container. 131 | -} 132 | alignItems : Alignment -> List (String, String) 133 | alignItems alignment = 134 | let (vendorValue, value) = 135 | case alignment of 136 | Start -> 137 | ("start", "flex-start") 138 | 139 | Center -> 140 | ("center", "center") 141 | 142 | End -> 143 | ("end", "flex-end") 144 | 145 | Stretch -> 146 | ("stretch", "stretch") 147 | 148 | Surround -> 149 | ("baseline", "baseline") 150 | in 151 | [ ("-webkit-box-align", vendorValue) 152 | , ("-webkit-align-items", value) 153 | , ("-ms-flex-align", vendorValue) 154 | , ("align-items", value) 155 | ] 156 | 157 | 158 | {-| The `justifyContent` mixin aligns the flexible container's items when the 159 | items do not use all available space on the main-axis. 160 | -} 161 | justifyContent : Alignment -> List (String, String) 162 | justifyContent alignment = 163 | let (webkitValue, msValue, value) = 164 | case alignment of 165 | Start -> 166 | ("start", "start", "flex-start") 167 | 168 | Center -> 169 | ("center", "center", "center") 170 | 171 | End -> 172 | ("end", "end", "flex-end") 173 | 174 | Stretch -> 175 | ("justify", "justify", "space-between") 176 | 177 | Surround -> 178 | ("none", "distribute", "space-around") 179 | in 180 | [ ("-webkit-box-pack", webkitValue) 181 | , ("-webkit-justify-content", value) 182 | , ("-ms-flex-pack", msValue) 183 | , ("justify-content", value) 184 | ] 185 | 186 | 187 | {-| The `grow` mixin specifies how much the item will grow relative to the rest 188 | of the flexible items inside the same container. 189 | -} 190 | grow : Float -> List (String, String) 191 | grow growValue = 192 | let 193 | string = 194 | toString growValue 195 | in 196 | [ ("-webkit-box-flex", string) 197 | , ("-webkit-flex-grow", string) 198 | , ("-ms-flex-positive", string) 199 | , ("flex-grow", string) 200 | ] 201 | 202 | 203 | {-| The `shrink` mixin specifies how the item will shrink relative to the rest 204 | of the flexible items inside the same container. 205 | -} 206 | shrink : Float -> List (String, String) 207 | shrink shrinkValue = 208 | let 209 | string = 210 | toString shrinkValue 211 | in 212 | [ ("-webkit-flex-shrink", string) 213 | , ("-ms-flex-negative", string) 214 | , ("flex-shrink", string) 215 | ] 216 | 217 | 218 | {-| The `basis` mixin specifies the initial length of a flexible item. 219 | -} 220 | basis : String -> List (String, String) 221 | basis basisValue = 222 | [ ("-webkit-flex-basis", basisValue) 223 | , ("-ms-flex-preferred-size", basisValue) 224 | , ("flex-basis", basisValue) 225 | ] 226 | 227 | 228 | {-| The `flex` mixin specifies the length of the item, relative to the rest of 229 | the flexible items inside the same container. 230 | 231 | It's a style shorthand for flexGrow, flexShrink and flexBasis 232 | -} 233 | flex : Float -> Float -> String -> List (String, String) 234 | flex grow shrink basis = 235 | let 236 | growString = 237 | toString grow 238 | 239 | shrinkString = 240 | toString shrink 241 | 242 | value = 243 | growString ++ " " ++ shrinkString ++ " " ++ basis 244 | in 245 | [ ("-webkit-box-flex", growString) 246 | , ("-webkit-flex", value) 247 | , ("-ms-flex", value) 248 | , ("flex", value) 249 | ] 250 | 251 | 252 | {-| The `order` mixin specifies the order of a flexible item relative to the 253 | rest of the flexible items inside the same container. 254 | -} 255 | order : Int -> List (String, String) 256 | order value = 257 | let string = 258 | toString value 259 | in 260 | [ ("-webkit-box-ordinal-group", string) 261 | , ("-webkit-order", string) 262 | , ("-ms-flex-order", string) 263 | , ("-order", string) 264 | ] 265 | 266 | 267 | {-| The `alignSelf` mixin specifies the alignment for the selected item inside 268 | the flexible container. 269 | -} 270 | alignSelf : Alignment -> List (String, String) 271 | alignSelf alignment = 272 | let value = 273 | case alignment of 274 | Start -> 275 | "flex-start" 276 | 277 | Center -> 278 | "center" 279 | 280 | End -> 281 | "flex-end" 282 | 283 | Stretch -> 284 | "stretch" 285 | 286 | Surround -> 287 | "baseline" 288 | in 289 | [ ("-webkit-align-self", value) 290 | , ("-ms-flex-item-align", value) 291 | , ("align-self", value) 292 | ] 293 | --------------------------------------------------------------------------------