mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-12-23 20:03:31 +03:00
1692 lines
43 KiB
Elm
1692 lines
43 KiB
Elm
|
module Element exposing
|
||
|
( Element, none, text, el
|
||
|
, row, wrappedRow, column
|
||
|
, paragraph, textColumn
|
||
|
, Column, table, IndexedColumn, indexedTable
|
||
|
, Attribute, width, height, Length, px, shrink, fill, fillPortion, maximum, minimum
|
||
|
, explain
|
||
|
, padding, paddingXY, paddingEach
|
||
|
, spacing, spacingXY, spaceEvenly
|
||
|
, centerX, centerY, alignLeft, alignRight, alignTop, alignBottom
|
||
|
, transparent, alpha, pointer
|
||
|
, moveUp, moveDown, moveRight, moveLeft, rotate, scale
|
||
|
, clip, clipX, clipY
|
||
|
, scrollbars, scrollbarX, scrollbarY
|
||
|
, layout, layoutWith, Option, noStaticStyleSheet, forceHover, noHover, focusStyle, FocusStyle
|
||
|
, link, newTabLink, download, downloadAs
|
||
|
, image
|
||
|
, Color, rgba, rgb, rgb255, rgba255, fromRgb, fromRgb255, toRgb
|
||
|
, above, below, onRight, onLeft, inFront, behindContent
|
||
|
, Attr, Decoration, mouseOver, mouseDown, focused
|
||
|
, Device, DeviceClass(..), Orientation(..), classifyDevice
|
||
|
, modular
|
||
|
, map, mapAttribute
|
||
|
, html, htmlAttribute
|
||
|
)
|
||
|
|
||
|
{-|
|
||
|
|
||
|
|
||
|
# Basic Elements
|
||
|
|
||
|
@docs Element, none, text, el
|
||
|
|
||
|
|
||
|
# Rows and Columns
|
||
|
|
||
|
When we want more than one child on an element, we want to be _specific_ about how they will be laid out.
|
||
|
|
||
|
So, the common ways to do that would be `row` and `column`.
|
||
|
|
||
|
@docs row, wrappedRow, column
|
||
|
|
||
|
|
||
|
# Text Layout
|
||
|
|
||
|
Text layout needs some specific considerations.
|
||
|
|
||
|
@docs paragraph, textColumn
|
||
|
|
||
|
|
||
|
# Data Table
|
||
|
|
||
|
@docs Column, table, IndexedColumn, indexedTable
|
||
|
|
||
|
|
||
|
# Size
|
||
|
|
||
|
@docs Attribute, width, height, Length, px, shrink, fill, fillPortion, maximum, minimum
|
||
|
|
||
|
|
||
|
# Debugging
|
||
|
|
||
|
@docs explain
|
||
|
|
||
|
|
||
|
# Padding and Spacing
|
||
|
|
||
|
There's no concept of margin in `elm-ui`, instead we have padding and spacing.
|
||
|
|
||
|
Padding is the distance between the outer edge and the content, and spacing is the space between children.
|
||
|
|
||
|
So, if we have the following row, with some padding and spacing.
|
||
|
|
||
|
Element.row [ padding 10, spacing 7 ]
|
||
|
[ Element.el [] none
|
||
|
, Element.el [] none
|
||
|
, Element.el [] none
|
||
|
]
|
||
|
|
||
|
Here's what we can expect:
|
||
|
|
||
|
![Three boxes spaced 7 pixels apart. There's a 10 pixel distance from the edge of the parent to the boxes.](https://mdgriffith.gitbooks.io/style-elements/content/assets/spacing-400.png)
|
||
|
|
||
|
**Note** `spacing` set on a `paragraph`, will set the pixel spacing between lines.
|
||
|
|
||
|
@docs padding, paddingXY, paddingEach
|
||
|
|
||
|
@docs spacing, spacingXY, spaceEvenly
|
||
|
|
||
|
|
||
|
# Alignment
|
||
|
|
||
|
Alignment can be used to align an `Element` within another `Element`.
|
||
|
|
||
|
Element.el [ centerX, alignTop ] (text "I'm centered and aligned top!")
|
||
|
|
||
|
If alignment is set on elements in a layout such as `row`, then the element will push the other elements in that direction. Here's an example.
|
||
|
|
||
|
Element.row []
|
||
|
[ Element.el [] Element.none
|
||
|
, Element.el [ alignLeft ] Element.none
|
||
|
, Element.el [ centerX ] Element.none
|
||
|
, Element.el [ alignRight ] Element.none
|
||
|
]
|
||
|
|
||
|
will result in a layout like
|
||
|
|
||
|
|-|-| |-| |-|
|
||
|
|
||
|
Where there are two elements on the left, one on the right, and one in the center of the space between the elements on the left and right.
|
||
|
|
||
|
**Note** For text alignment, check out `Element.Font`!
|
||
|
|
||
|
@docs centerX, centerY, alignLeft, alignRight, alignTop, alignBottom
|
||
|
|
||
|
|
||
|
# Transparency
|
||
|
|
||
|
@docs transparent, alpha, pointer
|
||
|
|
||
|
|
||
|
# Adjustment
|
||
|
|
||
|
@docs moveUp, moveDown, moveRight, moveLeft, rotate, scale
|
||
|
|
||
|
|
||
|
# Clipping and Scrollbars
|
||
|
|
||
|
Clip the content if it overflows.
|
||
|
|
||
|
@docs clip, clipX, clipY
|
||
|
|
||
|
Add a scrollbar if the content is larger than the element.
|
||
|
|
||
|
@docs scrollbars, scrollbarX, scrollbarY
|
||
|
|
||
|
|
||
|
# Rendering
|
||
|
|
||
|
@docs layout, layoutWith, Option, noStaticStyleSheet, forceHover, noHover, focusStyle, FocusStyle
|
||
|
|
||
|
|
||
|
# Links
|
||
|
|
||
|
@docs link, newTabLink, download, downloadAs
|
||
|
|
||
|
|
||
|
# Images
|
||
|
|
||
|
@docs image
|
||
|
|
||
|
|
||
|
# Color
|
||
|
|
||
|
In order to use attributes like `Font.color` and `Background.color`, you'll need to make some colors!
|
||
|
|
||
|
@docs Color, rgba, rgb, rgb255, rgba255, fromRgb, fromRgb255, toRgb
|
||
|
|
||
|
|
||
|
# Nearby Elements
|
||
|
|
||
|
Let's say we want a dropdown menu. Essentially we want to say: _put this element below this other element, but don't affect the layout when you do_.
|
||
|
|
||
|
Element.row []
|
||
|
[ Element.el
|
||
|
[ Element.below (Element.text "I'm below!")
|
||
|
]
|
||
|
(Element.text "I'm normal!")
|
||
|
]
|
||
|
|
||
|
This will result in
|
||
|
|
||
|
|- I'm normal! -|
|
||
|
I'm below
|
||
|
|
||
|
Where `"I'm Below"` doesn't change the size of `Element.row`.
|
||
|
|
||
|
This is very useful for things like dropdown menus or tooltips.
|
||
|
|
||
|
@docs above, below, onRight, onLeft, inFront, behindContent
|
||
|
|
||
|
|
||
|
# Temporary Styling
|
||
|
|
||
|
@docs Attr, Decoration, mouseOver, mouseDown, focused
|
||
|
|
||
|
|
||
|
# Responsiveness
|
||
|
|
||
|
The main technique for responsiveness is to store window size information in your model.
|
||
|
|
||
|
Install the `Browser` package, and set up a subscription for [`Browser.Events.onResize`](https://package.elm-lang.org/packages/elm/browser/latest/Browser-Events#onResize).
|
||
|
|
||
|
You'll also need to retrieve the initial window size. You can either use [`Browser.Dom.getViewport`](https://package.elm-lang.org/packages/elm/browser/latest/Browser-Dom#getViewport) or pass in `window.innerWidth` and `window.innerHeight` as flags to your program, which is the preferred way. This requires minor setup on the JS side, but allows you to avoid the state where you don't have window info.
|
||
|
|
||
|
@docs Device, DeviceClass, Orientation, classifyDevice
|
||
|
|
||
|
|
||
|
# Scaling
|
||
|
|
||
|
@docs modular
|
||
|
|
||
|
|
||
|
## Mapping
|
||
|
|
||
|
@docs map, mapAttribute
|
||
|
|
||
|
|
||
|
## Compatibility
|
||
|
|
||
|
@docs html, htmlAttribute
|
||
|
|
||
|
-}
|
||
|
|
||
|
import Html exposing (Html)
|
||
|
import Html.Attributes
|
||
|
import Internal.Flag as Flag exposing (Flag)
|
||
|
import Internal.Model as Internal
|
||
|
import Internal.Style exposing (classes)
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
type alias Color =
|
||
|
Internal.Color
|
||
|
|
||
|
|
||
|
{-| Provide the red, green, and blue channels for the color.
|
||
|
|
||
|
Each channel takes a value between 0 and 1.
|
||
|
|
||
|
-}
|
||
|
rgb : Float -> Float -> Float -> Color
|
||
|
rgb r g b =
|
||
|
Internal.Rgba r g b 1
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
rgba : Float -> Float -> Float -> Float -> Color
|
||
|
rgba =
|
||
|
Internal.Rgba
|
||
|
|
||
|
|
||
|
{-| Provide the red, green, and blue channels for the color.
|
||
|
|
||
|
Each channel takes a value between 0 and 255.
|
||
|
|
||
|
-}
|
||
|
rgb255 : Int -> Int -> Int -> Color
|
||
|
rgb255 red green blue =
|
||
|
Internal.Rgba
|
||
|
(toFloat red / 255)
|
||
|
(toFloat green / 255)
|
||
|
(toFloat blue / 255)
|
||
|
1
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
rgba255 : Int -> Int -> Int -> Float -> Color
|
||
|
rgba255 red green blue a =
|
||
|
Internal.Rgba
|
||
|
(toFloat red / 255)
|
||
|
(toFloat green / 255)
|
||
|
(toFloat blue / 255)
|
||
|
a
|
||
|
|
||
|
|
||
|
{-| Create a color from an RGB record.
|
||
|
-}
|
||
|
fromRgb :
|
||
|
{ red : Float
|
||
|
, green : Float
|
||
|
, blue : Float
|
||
|
, alpha : Float
|
||
|
}
|
||
|
-> Color
|
||
|
fromRgb clr =
|
||
|
Internal.Rgba
|
||
|
clr.red
|
||
|
clr.green
|
||
|
clr.blue
|
||
|
clr.alpha
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
fromRgb255 :
|
||
|
{ red : Int
|
||
|
, green : Int
|
||
|
, blue : Int
|
||
|
, alpha : Float
|
||
|
}
|
||
|
-> Color
|
||
|
fromRgb255 clr =
|
||
|
Internal.Rgba
|
||
|
(toFloat clr.red / 255)
|
||
|
(toFloat clr.green / 255)
|
||
|
(toFloat clr.blue / 255)
|
||
|
clr.alpha
|
||
|
|
||
|
|
||
|
{-| Deconstruct a `Color` into its rgb channels.
|
||
|
-}
|
||
|
toRgb :
|
||
|
Color
|
||
|
->
|
||
|
{ red : Float
|
||
|
, green : Float
|
||
|
, blue : Float
|
||
|
, alpha : Float
|
||
|
}
|
||
|
toRgb (Internal.Rgba r g b a) =
|
||
|
{ red = r
|
||
|
, green = g
|
||
|
, blue = b
|
||
|
, alpha = a
|
||
|
}
|
||
|
|
||
|
|
||
|
{-| The basic building block of your layout.
|
||
|
|
||
|
howdy : Element msg
|
||
|
howdy =
|
||
|
Element.el [] (Element.text "Howdy!")
|
||
|
|
||
|
-}
|
||
|
type alias Element msg =
|
||
|
Internal.Element msg
|
||
|
|
||
|
|
||
|
{-| An attribute that can be attached to an `Element`
|
||
|
-}
|
||
|
type alias Attribute msg =
|
||
|
Internal.Attribute () msg
|
||
|
|
||
|
|
||
|
{-| This is a special attribute that counts as both a `Attribute msg` and a `Decoration`.
|
||
|
-}
|
||
|
type alias Attr decorative msg =
|
||
|
Internal.Attribute decorative msg
|
||
|
|
||
|
|
||
|
{-| Only decorations
|
||
|
-}
|
||
|
type alias Decoration =
|
||
|
Internal.Attribute Never Never
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
html : Html msg -> Element msg
|
||
|
html =
|
||
|
Internal.unstyled
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
htmlAttribute : Html.Attribute msg -> Attribute msg
|
||
|
htmlAttribute =
|
||
|
Internal.Attr
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
map : (msg -> msg1) -> Element msg -> Element msg1
|
||
|
map =
|
||
|
Internal.map
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
mapAttribute : (msg -> msg1) -> Attribute msg -> Attribute msg1
|
||
|
mapAttribute =
|
||
|
Internal.mapAttr
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
type alias Length =
|
||
|
Internal.Length
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
px : Int -> Length
|
||
|
px =
|
||
|
Internal.Px
|
||
|
|
||
|
|
||
|
{-| Shrink an element to fit its contents.
|
||
|
-}
|
||
|
shrink : Length
|
||
|
shrink =
|
||
|
Internal.Content
|
||
|
|
||
|
|
||
|
{-| Fill the available space. The available space will be split evenly between elements that have `width fill`.
|
||
|
-}
|
||
|
fill : Length
|
||
|
fill =
|
||
|
Internal.Fill 1
|
||
|
|
||
|
|
||
|
{-| Similarly you can set a minimum boundary.
|
||
|
|
||
|
el
|
||
|
[ height
|
||
|
(fill
|
||
|
|> maximum 300
|
||
|
|> minimum 30
|
||
|
)
|
||
|
|
||
|
]
|
||
|
(text "I will stop at 300px")
|
||
|
|
||
|
-}
|
||
|
minimum : Int -> Length -> Length
|
||
|
minimum i l =
|
||
|
Internal.Min i l
|
||
|
|
||
|
|
||
|
{-| Add a maximum to a length.
|
||
|
|
||
|
el
|
||
|
[ height
|
||
|
(fill
|
||
|
|> maximum 300
|
||
|
)
|
||
|
]
|
||
|
(text "I will stop at 300px")
|
||
|
|
||
|
-}
|
||
|
maximum : Int -> Length -> Length
|
||
|
maximum i l =
|
||
|
Internal.Max i l
|
||
|
|
||
|
|
||
|
{-| Sometimes you may not want to split available space evenly. In this case you can use `fillPortion` to define which elements should have what portion of the available space.
|
||
|
|
||
|
So, two elements, one with `width (fillPortion 2)` and one with `width (fillPortion 3)`. The first would get 2 portions of the available space, while the second would get 3.
|
||
|
|
||
|
**Also:** `fill == fillPortion 1`
|
||
|
|
||
|
-}
|
||
|
fillPortion : Int -> Length
|
||
|
fillPortion =
|
||
|
Internal.Fill
|
||
|
|
||
|
|
||
|
{-| This is your top level node where you can turn `Element` into `Html`.
|
||
|
-}
|
||
|
layout : List (Attribute msg) -> Element msg -> Html msg
|
||
|
layout =
|
||
|
layoutWith { options = [] }
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
layoutWith : { options : List Option } -> List (Attribute msg) -> Element msg -> Html msg
|
||
|
layoutWith { options } attrs child =
|
||
|
Internal.renderRoot options
|
||
|
(Internal.htmlClass
|
||
|
(String.join " "
|
||
|
[ classes.root
|
||
|
, classes.any
|
||
|
, classes.single
|
||
|
]
|
||
|
)
|
||
|
:: (Internal.rootStyle ++ attrs)
|
||
|
)
|
||
|
child
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
type alias Option =
|
||
|
Internal.Option
|
||
|
|
||
|
|
||
|
{-| Elm UI embeds two StyleSheets, one that is constant, and one that changes dynamically based on styles collected from the elements being rendered.
|
||
|
|
||
|
This option will stop the static/constant stylesheet from rendering.
|
||
|
|
||
|
If you're embedding multiple elm-ui `layout` elements, you need to guarantee that only one is rendering the static style sheet and that it's above all the others in the DOM tree.
|
||
|
|
||
|
-}
|
||
|
noStaticStyleSheet : Option
|
||
|
noStaticStyleSheet =
|
||
|
Internal.RenderModeOption Internal.NoStaticStyleSheet
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
defaultFocus :
|
||
|
{ borderColor : Maybe Color
|
||
|
, backgroundColor : Maybe Color
|
||
|
, shadow :
|
||
|
Maybe
|
||
|
{ color : Color
|
||
|
, offset : ( Int, Int )
|
||
|
, blur : Int
|
||
|
, size : Int
|
||
|
}
|
||
|
}
|
||
|
defaultFocus =
|
||
|
Internal.focusDefaultStyle
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
type alias FocusStyle =
|
||
|
{ borderColor : Maybe Color
|
||
|
, backgroundColor : Maybe Color
|
||
|
, shadow :
|
||
|
Maybe
|
||
|
{ color : Color
|
||
|
, offset : ( Int, Int )
|
||
|
, blur : Int
|
||
|
, size : Int
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
focusStyle : FocusStyle -> Option
|
||
|
focusStyle =
|
||
|
Internal.FocusStyleOption
|
||
|
|
||
|
|
||
|
{-| Disable all `mouseOver` styles.
|
||
|
-}
|
||
|
noHover : Option
|
||
|
noHover =
|
||
|
Internal.HoverOption Internal.NoHover
|
||
|
|
||
|
|
||
|
{-| Any `hover` styles, aka attributes with `mouseOver` in the name, will be always turned on.
|
||
|
|
||
|
This is useful for when you're targeting a platform that has no mouse, such as mobile.
|
||
|
|
||
|
-}
|
||
|
forceHover : Option
|
||
|
forceHover =
|
||
|
Internal.HoverOption Internal.ForceHover
|
||
|
|
||
|
|
||
|
{-| When you want to render exactly nothing.
|
||
|
-}
|
||
|
none : Element msg
|
||
|
none =
|
||
|
Internal.Empty
|
||
|
|
||
|
|
||
|
{-| Create some plain text.
|
||
|
|
||
|
text "Hello, you stylish developer!"
|
||
|
|
||
|
**Note** text does not wrap by default. In order to get text to wrap, check out `paragraph`!
|
||
|
|
||
|
-}
|
||
|
text : String -> Element msg
|
||
|
text content =
|
||
|
Internal.Text content
|
||
|
|
||
|
|
||
|
{-| The basic building block of your layout.
|
||
|
|
||
|
You can think of an `el` as a `div`, but it can only have one child.
|
||
|
|
||
|
If you want multiple children, you'll need to use something like `row` or `column`
|
||
|
|
||
|
import Element exposing (Element, rgb)
|
||
|
import Element.Background as Background
|
||
|
import Element.Border as Border
|
||
|
|
||
|
myElement : Element msg
|
||
|
myElement =
|
||
|
Element.el
|
||
|
[ Background.color (rgb 0 0.5 0)
|
||
|
, Border.color (rgb 0 0.7 0)
|
||
|
]
|
||
|
(Element.text "You've made a stylish element!")
|
||
|
|
||
|
-}
|
||
|
el : List (Attribute msg) -> Element msg -> Element msg
|
||
|
el attrs child =
|
||
|
Internal.element
|
||
|
Internal.asEl
|
||
|
Internal.div
|
||
|
(width shrink
|
||
|
:: height shrink
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed [ child ])
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
row : List (Attribute msg) -> List (Element msg) -> Element msg
|
||
|
row attrs children =
|
||
|
Internal.element
|
||
|
Internal.asRow
|
||
|
Internal.div
|
||
|
(Internal.htmlClass (classes.contentLeft ++ " " ++ classes.contentCenterY)
|
||
|
:: width shrink
|
||
|
:: height shrink
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed children)
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
column : List (Attribute msg) -> List (Element msg) -> Element msg
|
||
|
column attrs children =
|
||
|
Internal.element
|
||
|
Internal.asColumn
|
||
|
Internal.div
|
||
|
(Internal.htmlClass
|
||
|
(classes.contentTop
|
||
|
++ " "
|
||
|
++ classes.contentLeft
|
||
|
)
|
||
|
:: height shrink
|
||
|
:: width shrink
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed children)
|
||
|
|
||
|
|
||
|
{-| Same as `row`, but will wrap if it takes up too much horizontal space.
|
||
|
-}
|
||
|
wrappedRow : List (Attribute msg) -> List (Element msg) -> Element msg
|
||
|
wrappedRow attrs children =
|
||
|
let
|
||
|
( padded, spaced ) =
|
||
|
Internal.extractSpacingAndPadding attrs
|
||
|
in
|
||
|
case spaced of
|
||
|
Nothing ->
|
||
|
Internal.element
|
||
|
Internal.asRow
|
||
|
Internal.div
|
||
|
(Internal.htmlClass
|
||
|
(classes.contentLeft
|
||
|
++ " "
|
||
|
++ classes.contentCenterY
|
||
|
++ " "
|
||
|
++ classes.wrapped
|
||
|
)
|
||
|
:: width shrink
|
||
|
:: height shrink
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed children)
|
||
|
|
||
|
Just (Internal.Spaced spaceName x y) ->
|
||
|
let
|
||
|
newPadding =
|
||
|
case padded of
|
||
|
Just (Internal.Padding name t r b l) ->
|
||
|
if r >= (toFloat x / 2) && b >= (toFloat y / 2) then
|
||
|
let
|
||
|
newTop =
|
||
|
t - (toFloat y / 2)
|
||
|
|
||
|
newRight =
|
||
|
r - (toFloat x / 2)
|
||
|
|
||
|
newBottom =
|
||
|
b - (toFloat y / 2)
|
||
|
|
||
|
newLeft =
|
||
|
l - (toFloat x / 2)
|
||
|
in
|
||
|
Just <|
|
||
|
Internal.StyleClass Flag.padding
|
||
|
(Internal.PaddingStyle
|
||
|
(Internal.paddingNameFloat
|
||
|
newTop
|
||
|
newRight
|
||
|
newBottom
|
||
|
newLeft
|
||
|
)
|
||
|
newTop
|
||
|
newRight
|
||
|
newBottom
|
||
|
newLeft
|
||
|
)
|
||
|
|
||
|
else
|
||
|
Nothing
|
||
|
|
||
|
Nothing ->
|
||
|
Nothing
|
||
|
in
|
||
|
case newPadding of
|
||
|
Just pad ->
|
||
|
Internal.element
|
||
|
Internal.asRow
|
||
|
Internal.div
|
||
|
(Internal.htmlClass
|
||
|
(classes.contentLeft
|
||
|
++ " "
|
||
|
++ classes.contentCenterY
|
||
|
++ " "
|
||
|
++ classes.wrapped
|
||
|
)
|
||
|
:: width shrink
|
||
|
:: height shrink
|
||
|
:: attrs
|
||
|
++ [ pad ]
|
||
|
)
|
||
|
(Internal.Unkeyed children)
|
||
|
|
||
|
Nothing ->
|
||
|
-- Not enough space in padding to compensate for spacing
|
||
|
let
|
||
|
halfX =
|
||
|
negate (toFloat x / 2)
|
||
|
|
||
|
halfY =
|
||
|
negate (toFloat y / 2)
|
||
|
in
|
||
|
Internal.element
|
||
|
Internal.asEl
|
||
|
Internal.div
|
||
|
attrs
|
||
|
(Internal.Unkeyed
|
||
|
[ Internal.element
|
||
|
Internal.asRow
|
||
|
Internal.div
|
||
|
(Internal.htmlClass
|
||
|
(classes.contentLeft
|
||
|
++ " "
|
||
|
++ classes.contentCenterY
|
||
|
++ " "
|
||
|
++ classes.wrapped
|
||
|
)
|
||
|
:: Internal.Attr
|
||
|
(Html.Attributes.style "margin"
|
||
|
(String.fromFloat halfY
|
||
|
++ "px"
|
||
|
++ " "
|
||
|
++ String.fromFloat halfX
|
||
|
++ "px"
|
||
|
)
|
||
|
)
|
||
|
:: Internal.Attr
|
||
|
(Html.Attributes.style "width"
|
||
|
("calc(100% + "
|
||
|
++ String.fromInt x
|
||
|
++ "px)"
|
||
|
)
|
||
|
)
|
||
|
:: Internal.Attr
|
||
|
(Html.Attributes.style "height"
|
||
|
("calc(100% + "
|
||
|
++ String.fromInt y
|
||
|
++ "px)"
|
||
|
)
|
||
|
)
|
||
|
:: Internal.StyleClass Flag.spacing (Internal.SpacingStyle spaceName x y)
|
||
|
:: []
|
||
|
)
|
||
|
(Internal.Unkeyed children)
|
||
|
]
|
||
|
)
|
||
|
|
||
|
|
||
|
{-| This is just an alias for `Debug.todo`
|
||
|
-}
|
||
|
type alias Todo =
|
||
|
String -> Never
|
||
|
|
||
|
|
||
|
{-| Highlight the borders of an element and it's children below. This can really help if you're running into some issue with your layout!
|
||
|
|
||
|
**Note** This attribute needs to be handed `Debug.todo` in order to work, even though it won't do anything with it. This is a safety measure so you don't accidently ship code with `explain` in it, as Elm won't compile with `--optimize` if you still have a `Debug` statement in your code.
|
||
|
|
||
|
el
|
||
|
[ Element.explain Debug.todo
|
||
|
]
|
||
|
(text "Help, I'm being debugged!")
|
||
|
|
||
|
-}
|
||
|
explain : Todo -> Attribute msg
|
||
|
explain _ =
|
||
|
Internal.htmlClass "explain"
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
type alias Column record msg =
|
||
|
{ header : Element msg
|
||
|
, width : Length
|
||
|
, view : record -> Element msg
|
||
|
}
|
||
|
|
||
|
|
||
|
{-| Show some tabular data.
|
||
|
|
||
|
Start with a list of records and specify how each column should be rendered.
|
||
|
|
||
|
So, if we have a list of `persons`:
|
||
|
|
||
|
type alias Person =
|
||
|
{ firstName : String
|
||
|
, lastName : String
|
||
|
}
|
||
|
|
||
|
persons : List Person
|
||
|
persons =
|
||
|
[ { firstName = "David"
|
||
|
, lastName = "Bowie"
|
||
|
}
|
||
|
, { firstName = "Florence"
|
||
|
, lastName = "Welch"
|
||
|
}
|
||
|
]
|
||
|
|
||
|
We could render it using
|
||
|
|
||
|
Element.table []
|
||
|
{ data = persons
|
||
|
, columns =
|
||
|
[ { header = Element.text "First Name"
|
||
|
, width = fill
|
||
|
, view =
|
||
|
\person ->
|
||
|
Element.text person.firstName
|
||
|
}
|
||
|
, { header = Element.text "Last Name"
|
||
|
, width = fill
|
||
|
, view =
|
||
|
\person ->
|
||
|
Element.text person.lastName
|
||
|
}
|
||
|
]
|
||
|
}
|
||
|
|
||
|
**Note:** Sometimes you might not have a list of records directly in your model. In this case it can be really nice to write a function that transforms some part of your model into a list of records before feeding it into `Element.table`.
|
||
|
|
||
|
-}
|
||
|
table :
|
||
|
List (Attribute msg)
|
||
|
->
|
||
|
{ data : List records
|
||
|
, columns : List (Column records msg)
|
||
|
}
|
||
|
-> Element msg
|
||
|
table attrs config =
|
||
|
tableHelper attrs
|
||
|
{ data = config.data
|
||
|
, columns =
|
||
|
List.map InternalColumn config.columns
|
||
|
}
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
type alias IndexedColumn record msg =
|
||
|
{ header : Element msg
|
||
|
, width : Length
|
||
|
, view : Int -> record -> Element msg
|
||
|
}
|
||
|
|
||
|
|
||
|
{-| Same as `Element.table` except the `view` for each column will also receive the row index as well as the record.
|
||
|
-}
|
||
|
indexedTable :
|
||
|
List (Attribute msg)
|
||
|
->
|
||
|
{ data : List records
|
||
|
, columns : List (IndexedColumn records msg)
|
||
|
}
|
||
|
-> Element msg
|
||
|
indexedTable attrs config =
|
||
|
tableHelper attrs
|
||
|
{ data = config.data
|
||
|
, columns =
|
||
|
List.map InternalIndexedColumn config.columns
|
||
|
}
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
type alias InternalTable records msg =
|
||
|
{ data : List records
|
||
|
, columns : List (InternalTableColumn records msg)
|
||
|
}
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
type InternalTableColumn record msg
|
||
|
= InternalIndexedColumn (IndexedColumn record msg)
|
||
|
| InternalColumn (Column record msg)
|
||
|
|
||
|
|
||
|
tableHelper : List (Attribute msg) -> InternalTable data msg -> Element msg
|
||
|
tableHelper attrs config =
|
||
|
let
|
||
|
( sX, sY ) =
|
||
|
Internal.getSpacing attrs ( 0, 0 )
|
||
|
|
||
|
columnHeader col =
|
||
|
case col of
|
||
|
InternalIndexedColumn colConfig ->
|
||
|
colConfig.header
|
||
|
|
||
|
InternalColumn colConfig ->
|
||
|
colConfig.header
|
||
|
|
||
|
columnWidth col =
|
||
|
case col of
|
||
|
InternalIndexedColumn colConfig ->
|
||
|
colConfig.width
|
||
|
|
||
|
InternalColumn colConfig ->
|
||
|
colConfig.width
|
||
|
|
||
|
maybeHeaders =
|
||
|
List.map columnHeader config.columns
|
||
|
|> (\headers ->
|
||
|
if List.all ((==) Internal.Empty) headers then
|
||
|
Nothing
|
||
|
|
||
|
else
|
||
|
Just (List.indexedMap (\col header -> onGrid 1 (col + 1) header) headers)
|
||
|
)
|
||
|
|
||
|
template =
|
||
|
Internal.StyleClass Flag.gridTemplate <|
|
||
|
Internal.GridTemplateStyle
|
||
|
{ spacing = ( px sX, px sY )
|
||
|
, columns = List.map columnWidth config.columns
|
||
|
, rows = List.repeat (List.length config.data) Internal.Content
|
||
|
}
|
||
|
|
||
|
onGrid rowLevel columnLevel elem =
|
||
|
Internal.element
|
||
|
Internal.asEl
|
||
|
Internal.div
|
||
|
[ Internal.StyleClass Flag.gridPosition
|
||
|
(Internal.GridPosition
|
||
|
{ row = rowLevel
|
||
|
, col = columnLevel
|
||
|
, width = 1
|
||
|
, height = 1
|
||
|
}
|
||
|
)
|
||
|
]
|
||
|
(Internal.Unkeyed [ elem ])
|
||
|
|
||
|
add cell columnConfig cursor =
|
||
|
case columnConfig of
|
||
|
InternalIndexedColumn col ->
|
||
|
{ cursor
|
||
|
| elements =
|
||
|
onGrid cursor.row
|
||
|
cursor.column
|
||
|
(col.view
|
||
|
(if maybeHeaders == Nothing then
|
||
|
cursor.row - 1
|
||
|
|
||
|
else
|
||
|
cursor.row - 2
|
||
|
)
|
||
|
cell
|
||
|
)
|
||
|
:: cursor.elements
|
||
|
, column = cursor.column + 1
|
||
|
}
|
||
|
|
||
|
InternalColumn col ->
|
||
|
{ elements =
|
||
|
onGrid cursor.row cursor.column (col.view cell)
|
||
|
:: cursor.elements
|
||
|
, column = cursor.column + 1
|
||
|
, row = cursor.row
|
||
|
}
|
||
|
|
||
|
build columns rowData cursor =
|
||
|
let
|
||
|
newCursor =
|
||
|
List.foldl (add rowData)
|
||
|
cursor
|
||
|
columns
|
||
|
in
|
||
|
{ elements = newCursor.elements
|
||
|
, row = cursor.row + 1
|
||
|
, column = 1
|
||
|
}
|
||
|
|
||
|
children =
|
||
|
List.foldl (build config.columns)
|
||
|
{ elements = []
|
||
|
, row =
|
||
|
if maybeHeaders == Nothing then
|
||
|
1
|
||
|
|
||
|
else
|
||
|
2
|
||
|
, column = 1
|
||
|
}
|
||
|
config.data
|
||
|
in
|
||
|
Internal.element
|
||
|
Internal.asGrid
|
||
|
Internal.div
|
||
|
(width fill
|
||
|
:: template
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed
|
||
|
(case maybeHeaders of
|
||
|
Nothing ->
|
||
|
children.elements
|
||
|
|
||
|
Just renderedHeaders ->
|
||
|
renderedHeaders ++ List.reverse children.elements
|
||
|
)
|
||
|
)
|
||
|
|
||
|
|
||
|
{-| A paragraph will layout all children as wrapped, inline elements.
|
||
|
|
||
|
import Element exposing (el, paragraph, text)
|
||
|
import Element.Font as Font
|
||
|
|
||
|
view =
|
||
|
paragraph []
|
||
|
[ text "lots of text ...."
|
||
|
, el [ Font.bold ] (text "this is bold")
|
||
|
, text "lots of text ...."
|
||
|
]
|
||
|
|
||
|
This is really useful when you want to markup text by having some parts be bold, or some be links, or whatever you so desire.
|
||
|
|
||
|
Also, if a child element has `alignLeft` or `alignRight`, then it will be moved to that side and the text will flow around it, (ah yes, `float` behavior).
|
||
|
|
||
|
This makes it particularly easy to do something like a [dropped capital](https://en.wikipedia.org/wiki/Initial).
|
||
|
|
||
|
import Element exposing (alignLeft, el, padding, paragraph, text)
|
||
|
import Element.Font as Font
|
||
|
|
||
|
view =
|
||
|
paragraph []
|
||
|
[ el
|
||
|
[ alignLeft
|
||
|
, padding 5
|
||
|
]
|
||
|
(text "S")
|
||
|
, text "o much text ...."
|
||
|
]
|
||
|
|
||
|
Which will look something like
|
||
|
|
||
|
![A paragraph where the first letter is twice the height of the others](https://mdgriffith.gitbooks.io/style-elements/content/assets/Screen%20Shot%202017-08-25%20at%209.41.52%20PM.png)
|
||
|
|
||
|
**Note** `spacing` on a paragraph will set the pixel spacing between lines.
|
||
|
|
||
|
-}
|
||
|
paragraph : List (Attribute msg) -> List (Element msg) -> Element msg
|
||
|
paragraph attrs children =
|
||
|
Internal.element
|
||
|
Internal.asParagraph
|
||
|
Internal.div
|
||
|
(Internal.Describe Internal.Paragraph
|
||
|
:: width fill
|
||
|
:: spacing 5
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed children)
|
||
|
|
||
|
|
||
|
{-| Now that we have a paragraph, we need some way to attach a bunch of paragraph's together.
|
||
|
|
||
|
To do that we can use a `textColumn`.
|
||
|
|
||
|
The main difference between a `column` and a `textColumn` is that `textColumn` will flow the text around elements that have `alignRight` or `alignLeft`, just like we just saw with paragraph.
|
||
|
|
||
|
In the following example, we have a `textColumn` where one child has `alignLeft`.
|
||
|
|
||
|
Element.textColumn [ spacing 10, padding 10 ]
|
||
|
[ paragraph [] [ text "lots of text ...." ]
|
||
|
, el [ alignLeft ] none
|
||
|
, paragraph [] [ text "lots of text ...." ]
|
||
|
]
|
||
|
|
||
|
Which will result in something like:
|
||
|
|
||
|
![A text layout where an image is on the left.](https://mdgriffith.gitbooks.io/style-elements/content/assets/Screen%20Shot%202017-08-25%20at%208.42.39%20PM.png)
|
||
|
|
||
|
-}
|
||
|
textColumn : List (Attribute msg) -> List (Element msg) -> Element msg
|
||
|
textColumn attrs children =
|
||
|
Internal.element
|
||
|
Internal.asTextColumn
|
||
|
Internal.div
|
||
|
(width
|
||
|
(fill
|
||
|
|> minimum 500
|
||
|
|> maximum 750
|
||
|
)
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed children)
|
||
|
|
||
|
|
||
|
{-| Both a source and a description are required for images.
|
||
|
|
||
|
The description is used for people using screen readers.
|
||
|
|
||
|
Leaving the description blank will cause the image to be ignored by assistive technology. This can make sense for images that are purely decorative and add no additional information.
|
||
|
|
||
|
So, take a moment to describe your image as you would to someone who has a harder time seeing.
|
||
|
|
||
|
-}
|
||
|
image : List (Attribute msg) -> { src : String, description : String } -> Element msg
|
||
|
image attrs { src, description } =
|
||
|
let
|
||
|
imageAttributes =
|
||
|
attrs
|
||
|
|> List.filter
|
||
|
(\a ->
|
||
|
case a of
|
||
|
Internal.Width _ ->
|
||
|
True
|
||
|
|
||
|
Internal.Height _ ->
|
||
|
True
|
||
|
|
||
|
_ ->
|
||
|
False
|
||
|
)
|
||
|
in
|
||
|
Internal.element
|
||
|
Internal.asEl
|
||
|
Internal.div
|
||
|
(Internal.htmlClass classes.imageContainer
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed
|
||
|
[ Internal.element
|
||
|
Internal.asEl
|
||
|
(Internal.NodeName "img")
|
||
|
([ Internal.Attr <| Html.Attributes.src src
|
||
|
, Internal.Attr <| Html.Attributes.alt description
|
||
|
]
|
||
|
++ imageAttributes
|
||
|
)
|
||
|
(Internal.Unkeyed [])
|
||
|
]
|
||
|
)
|
||
|
|
||
|
|
||
|
{-|
|
||
|
|
||
|
link []
|
||
|
{ url = "http://fruits.com"
|
||
|
, label = text "A link to my favorite fruit provider."
|
||
|
}
|
||
|
|
||
|
-}
|
||
|
link :
|
||
|
List (Attribute msg)
|
||
|
->
|
||
|
{ url : String
|
||
|
, label : Element msg
|
||
|
}
|
||
|
-> Element msg
|
||
|
link attrs { url, label } =
|
||
|
Internal.element
|
||
|
Internal.asEl
|
||
|
(Internal.NodeName "a")
|
||
|
(Internal.Attr (Html.Attributes.href url)
|
||
|
:: Internal.Attr (Html.Attributes.rel "noopener noreferrer")
|
||
|
:: width shrink
|
||
|
:: height shrink
|
||
|
:: Internal.htmlClass
|
||
|
(classes.contentCenterX
|
||
|
++ " "
|
||
|
++ classes.contentCenterY
|
||
|
++ " "
|
||
|
++ classes.link
|
||
|
)
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed [ label ])
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
newTabLink :
|
||
|
List (Attribute msg)
|
||
|
->
|
||
|
{ url : String
|
||
|
, label : Element msg
|
||
|
}
|
||
|
-> Element msg
|
||
|
newTabLink attrs { url, label } =
|
||
|
Internal.element
|
||
|
Internal.asEl
|
||
|
(Internal.NodeName "a")
|
||
|
(Internal.Attr (Html.Attributes.href url)
|
||
|
:: Internal.Attr (Html.Attributes.rel "noopener noreferrer")
|
||
|
:: Internal.Attr (Html.Attributes.target "_blank")
|
||
|
:: width shrink
|
||
|
:: height shrink
|
||
|
:: Internal.htmlClass
|
||
|
(classes.contentCenterX
|
||
|
++ " "
|
||
|
++ classes.contentCenterY
|
||
|
++ " "
|
||
|
++ classes.link
|
||
|
)
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed [ label ])
|
||
|
|
||
|
|
||
|
{-| A link to download a file.
|
||
|
|
||
|
**Note** If you're using `Browser.application`, then this won't be enough to actually trigger a file download due to how `Browser.Navigation` works.
|
||
|
|
||
|
[Here's a description of what needs to happen](https://github.com/elm/html/issues/175).
|
||
|
|
||
|
-}
|
||
|
download :
|
||
|
List (Attribute msg)
|
||
|
->
|
||
|
{ url : String
|
||
|
, label : Element msg
|
||
|
}
|
||
|
-> Element msg
|
||
|
download attrs { url, label } =
|
||
|
Internal.element
|
||
|
Internal.asEl
|
||
|
(Internal.NodeName "a")
|
||
|
(Internal.Attr (Html.Attributes.href url)
|
||
|
:: Internal.Attr (Html.Attributes.download "")
|
||
|
:: width shrink
|
||
|
:: height shrink
|
||
|
:: Internal.htmlClass classes.contentCenterX
|
||
|
:: Internal.htmlClass classes.contentCenterY
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed [ label ])
|
||
|
|
||
|
|
||
|
{-| A link to download a file, but you can specify the filename.
|
||
|
-}
|
||
|
downloadAs :
|
||
|
List (Attribute msg)
|
||
|
->
|
||
|
{ label : Element msg
|
||
|
, filename : String
|
||
|
, url : String
|
||
|
}
|
||
|
-> Element msg
|
||
|
downloadAs attrs { url, filename, label } =
|
||
|
Internal.element
|
||
|
Internal.asEl
|
||
|
(Internal.NodeName "a")
|
||
|
(Internal.Attr (Html.Attributes.href url)
|
||
|
:: Internal.Attr (Html.Attributes.download filename)
|
||
|
:: width shrink
|
||
|
:: height shrink
|
||
|
:: Internal.htmlClass classes.contentCenterX
|
||
|
:: Internal.htmlClass classes.contentCenterY
|
||
|
:: attrs
|
||
|
)
|
||
|
(Internal.Unkeyed [ label ])
|
||
|
|
||
|
|
||
|
|
||
|
{- NEARBYS -}
|
||
|
|
||
|
|
||
|
createNearby : Internal.Location -> Element msg -> Attribute msg
|
||
|
createNearby loc element =
|
||
|
case element of
|
||
|
Internal.Empty ->
|
||
|
Internal.NoAttribute
|
||
|
|
||
|
_ ->
|
||
|
Internal.Nearby loc element
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
below : Element msg -> Attribute msg
|
||
|
below element =
|
||
|
createNearby Internal.Below element
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
above : Element msg -> Attribute msg
|
||
|
above element =
|
||
|
createNearby Internal.Above element
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
onRight : Element msg -> Attribute msg
|
||
|
onRight element =
|
||
|
createNearby Internal.OnRight element
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
onLeft : Element msg -> Attribute msg
|
||
|
onLeft element =
|
||
|
createNearby Internal.OnLeft element
|
||
|
|
||
|
|
||
|
{-| This will place an element in front of another.
|
||
|
|
||
|
**Note:** If you use this on a `layout` element, it will place the element as fixed to the viewport which can be useful for modals and overlays.
|
||
|
|
||
|
-}
|
||
|
inFront : Element msg -> Attribute msg
|
||
|
inFront element =
|
||
|
createNearby Internal.InFront element
|
||
|
|
||
|
|
||
|
{-| This will place an element between the background and the content of an element.
|
||
|
-}
|
||
|
behindContent : Element msg -> Attribute msg
|
||
|
behindContent element =
|
||
|
createNearby Internal.Behind element
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
width : Length -> Attribute msg
|
||
|
width =
|
||
|
Internal.Width
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
height : Length -> Attribute msg
|
||
|
height =
|
||
|
Internal.Height
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
scale : Float -> Attr decorative msg
|
||
|
scale n =
|
||
|
Internal.TransformComponent Flag.scale (Internal.Scale ( n, n, 1 ))
|
||
|
|
||
|
|
||
|
{-| Angle is given in radians. [Here are some conversion functions if you want to use another unit.](https://package.elm-lang.org/packages/elm/core/latest/Basics#degrees)
|
||
|
-}
|
||
|
rotate : Float -> Attr decorative msg
|
||
|
rotate angle =
|
||
|
Internal.TransformComponent Flag.rotate (Internal.Rotate ( 0, 0, 1 ) angle)
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
moveUp : Float -> Attr decorative msg
|
||
|
moveUp y =
|
||
|
Internal.TransformComponent Flag.moveY (Internal.MoveY (negate y))
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
moveDown : Float -> Attr decorative msg
|
||
|
moveDown y =
|
||
|
Internal.TransformComponent Flag.moveY (Internal.MoveY y)
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
moveRight : Float -> Attr decorative msg
|
||
|
moveRight x =
|
||
|
Internal.TransformComponent Flag.moveX (Internal.MoveX x)
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
moveLeft : Float -> Attr decorative msg
|
||
|
moveLeft x =
|
||
|
Internal.TransformComponent Flag.moveX (Internal.MoveX (negate x))
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
padding : Int -> Attribute msg
|
||
|
padding x =
|
||
|
let
|
||
|
f =
|
||
|
toFloat x
|
||
|
in
|
||
|
Internal.StyleClass Flag.padding (Internal.PaddingStyle ("p-" ++ String.fromInt x) f f f f)
|
||
|
|
||
|
|
||
|
{-| Set horizontal and vertical padding.
|
||
|
-}
|
||
|
paddingXY : Int -> Int -> Attribute msg
|
||
|
paddingXY x y =
|
||
|
if x == y then
|
||
|
let
|
||
|
f =
|
||
|
toFloat x
|
||
|
in
|
||
|
Internal.StyleClass Flag.padding (Internal.PaddingStyle ("p-" ++ String.fromInt x) f f f f)
|
||
|
|
||
|
else
|
||
|
let
|
||
|
xFloat =
|
||
|
toFloat x
|
||
|
|
||
|
yFloat =
|
||
|
toFloat y
|
||
|
in
|
||
|
Internal.StyleClass Flag.padding
|
||
|
(Internal.PaddingStyle
|
||
|
("p-" ++ String.fromInt x ++ "-" ++ String.fromInt y)
|
||
|
yFloat
|
||
|
xFloat
|
||
|
yFloat
|
||
|
xFloat
|
||
|
)
|
||
|
|
||
|
|
||
|
{-| If you find yourself defining unique paddings all the time, you might consider defining
|
||
|
|
||
|
edges =
|
||
|
{ top = 0
|
||
|
, right = 0
|
||
|
, bottom = 0
|
||
|
, left = 0
|
||
|
}
|
||
|
|
||
|
And then just do
|
||
|
|
||
|
paddingEach { edges | right = 5 }
|
||
|
|
||
|
-}
|
||
|
paddingEach : { top : Int, right : Int, bottom : Int, left : Int } -> Attribute msg
|
||
|
paddingEach { top, right, bottom, left } =
|
||
|
if top == right && top == bottom && top == left then
|
||
|
let
|
||
|
topFloat =
|
||
|
toFloat top
|
||
|
in
|
||
|
Internal.StyleClass Flag.padding
|
||
|
(Internal.PaddingStyle ("p-" ++ String.fromInt top)
|
||
|
topFloat
|
||
|
topFloat
|
||
|
topFloat
|
||
|
topFloat
|
||
|
)
|
||
|
|
||
|
else
|
||
|
Internal.StyleClass Flag.padding
|
||
|
(Internal.PaddingStyle
|
||
|
(Internal.paddingName top right bottom left)
|
||
|
(toFloat top)
|
||
|
(toFloat right)
|
||
|
(toFloat bottom)
|
||
|
(toFloat left)
|
||
|
)
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
centerX : Attribute msg
|
||
|
centerX =
|
||
|
Internal.AlignX Internal.CenterX
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
centerY : Attribute msg
|
||
|
centerY =
|
||
|
Internal.AlignY Internal.CenterY
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
alignTop : Attribute msg
|
||
|
alignTop =
|
||
|
Internal.AlignY Internal.Top
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
alignBottom : Attribute msg
|
||
|
alignBottom =
|
||
|
Internal.AlignY Internal.Bottom
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
alignLeft : Attribute msg
|
||
|
alignLeft =
|
||
|
Internal.AlignX Internal.Left
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
alignRight : Attribute msg
|
||
|
alignRight =
|
||
|
Internal.AlignX Internal.Right
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
spaceEvenly : Attribute msg
|
||
|
spaceEvenly =
|
||
|
Internal.Class Flag.spacing Internal.Style.classes.spaceEvenly
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
spacing : Int -> Attribute msg
|
||
|
spacing x =
|
||
|
Internal.StyleClass Flag.spacing (Internal.SpacingStyle (Internal.spacingName x x) x x)
|
||
|
|
||
|
|
||
|
{-| In the majority of cases you'll just need to use `spacing`, which will work as intended.
|
||
|
|
||
|
However for some layouts, like `textColumn`, you may want to set a different spacing for the x axis compared to the y axis.
|
||
|
|
||
|
-}
|
||
|
spacingXY : Int -> Int -> Attribute msg
|
||
|
spacingXY x y =
|
||
|
Internal.StyleClass Flag.spacing (Internal.SpacingStyle (Internal.spacingName x y) x y)
|
||
|
|
||
|
|
||
|
{-| Make an element transparent and have it ignore any mouse or touch events, though it will stil take up space.
|
||
|
-}
|
||
|
transparent : Bool -> Attr decorative msg
|
||
|
transparent on =
|
||
|
if on then
|
||
|
Internal.StyleClass Flag.transparency (Internal.Transparency "transparent" 1.0)
|
||
|
|
||
|
else
|
||
|
Internal.StyleClass Flag.transparency (Internal.Transparency "visible" 0.0)
|
||
|
|
||
|
|
||
|
{-| A capped value between 0.0 and 1.0, where 0.0 is transparent and 1.0 is fully opaque.
|
||
|
|
||
|
Semantically equivalent to html opacity.
|
||
|
|
||
|
-}
|
||
|
alpha : Float -> Attr decorative msg
|
||
|
alpha o =
|
||
|
let
|
||
|
transparency =
|
||
|
o
|
||
|
|> max 0.0
|
||
|
|> min 1.0
|
||
|
|> (\x -> 1 - x)
|
||
|
in
|
||
|
Internal.StyleClass Flag.transparency <| Internal.Transparency ("transparency-" ++ Internal.floatClass transparency) transparency
|
||
|
|
||
|
|
||
|
|
||
|
-- {-| -}
|
||
|
-- hidden : Bool -> Attribute msg
|
||
|
-- hidden on =
|
||
|
-- if on then
|
||
|
-- Internal.class "hidden"
|
||
|
-- else
|
||
|
-- Internal.NoAttribute
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
scrollbars : Attribute msg
|
||
|
scrollbars =
|
||
|
Internal.Class Flag.overflow classes.scrollbars
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
scrollbarY : Attribute msg
|
||
|
scrollbarY =
|
||
|
Internal.Class Flag.overflow classes.scrollbarsY
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
scrollbarX : Attribute msg
|
||
|
scrollbarX =
|
||
|
Internal.Class Flag.overflow classes.scrollbarsX
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
clip : Attribute msg
|
||
|
clip =
|
||
|
Internal.Class Flag.overflow classes.clip
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
clipY : Attribute msg
|
||
|
clipY =
|
||
|
Internal.Class Flag.overflow classes.clipY
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
clipX : Attribute msg
|
||
|
clipX =
|
||
|
Internal.Class Flag.overflow classes.clipX
|
||
|
|
||
|
|
||
|
{-| Set the cursor to be a pointing hand when it's hovering over this element.
|
||
|
-}
|
||
|
pointer : Attribute msg
|
||
|
pointer =
|
||
|
Internal.Class Flag.cursor classes.cursorPointer
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
type alias Device =
|
||
|
{ class : DeviceClass
|
||
|
, orientation : Orientation
|
||
|
}
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
type DeviceClass
|
||
|
= Phone
|
||
|
| Tablet
|
||
|
| Desktop
|
||
|
| BigDesktop
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
type Orientation
|
||
|
= Portrait
|
||
|
| Landscape
|
||
|
|
||
|
|
||
|
{-| Takes in a Window.Size and returns a device profile which can be used for responsiveness.
|
||
|
|
||
|
If you have more detailed concerns around responsiveness, it probably makes sense to copy this function into your codebase and modify as needed.
|
||
|
|
||
|
-}
|
||
|
classifyDevice : { window | height : Int, width : Int } -> Device
|
||
|
classifyDevice window =
|
||
|
-- Tested in this ellie:
|
||
|
-- https://ellie-app.com/68QM7wLW8b9a1
|
||
|
{ class =
|
||
|
let
|
||
|
longSide =
|
||
|
max window.width window.height
|
||
|
|
||
|
shortSide =
|
||
|
min window.width window.height
|
||
|
in
|
||
|
if shortSide < 600 then
|
||
|
Phone
|
||
|
|
||
|
else if longSide <= 1200 then
|
||
|
Tablet
|
||
|
|
||
|
else if longSide > 1200 && longSide <= 1920 then
|
||
|
Desktop
|
||
|
|
||
|
else
|
||
|
BigDesktop
|
||
|
, orientation =
|
||
|
if window.width < window.height then
|
||
|
Portrait
|
||
|
|
||
|
else
|
||
|
Landscape
|
||
|
}
|
||
|
|
||
|
|
||
|
{-| When designing it's nice to use a modular scale to set spacial rythms.
|
||
|
|
||
|
scaled =
|
||
|
Element.modular 16 1.25
|
||
|
|
||
|
A modular scale starts with a number, and multiplies it by a ratio a number of times.
|
||
|
Then, when setting font sizes you can use:
|
||
|
|
||
|
Font.size (scaled 1) -- results in 16
|
||
|
|
||
|
Font.size (scaled 2) -- 16 * 1.25 results in 20
|
||
|
|
||
|
Font.size (scaled 4) -- 16 * 1.25 ^ (4 - 1) results in 31.25
|
||
|
|
||
|
We can also provide negative numbers to scale below 16px.
|
||
|
|
||
|
Font.size (scaled -1) -- 16 * 1.25 ^ (-1) results in 12.8
|
||
|
|
||
|
-}
|
||
|
modular : Float -> Float -> Int -> Float
|
||
|
modular normal ratio rescale =
|
||
|
if rescale == 0 then
|
||
|
normal
|
||
|
|
||
|
else if rescale < 0 then
|
||
|
normal * ratio ^ toFloat rescale
|
||
|
|
||
|
else
|
||
|
normal * ratio ^ (toFloat rescale - 1)
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
mouseOver : List Decoration -> Attribute msg
|
||
|
mouseOver decs =
|
||
|
Internal.StyleClass Flag.hover <|
|
||
|
Internal.PseudoSelector Internal.Hover
|
||
|
(Internal.unwrapDecorations decs)
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
mouseDown : List Decoration -> Attribute msg
|
||
|
mouseDown decs =
|
||
|
Internal.StyleClass Flag.active <|
|
||
|
Internal.PseudoSelector Internal.Active
|
||
|
(Internal.unwrapDecorations decs)
|
||
|
|
||
|
|
||
|
{-| -}
|
||
|
focused : List Decoration -> Attribute msg
|
||
|
focused decs =
|
||
|
Internal.StyleClass Flag.focus <|
|
||
|
Internal.PseudoSelector Internal.Focus
|
||
|
(Internal.unwrapDecorations decs)
|