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)