module Layout exposing ( twoBlocks , tab, sideIconBar , iconAsElement, iconAsIcon , containedButton, outlinedButton, textButton , textInput, passwordInput , header, stdText , itemWithSubtext , sideList, radioButtons , loadingIndicator, svg ) {-| # Layout The layout module exposes some boilerplate functions that have produce a beautiful Material design Elm webpage. ## Screen layout @docs twoBlocks ## Elements @docs tab, sideIconBar ## Icons @docs iconAsElement, iconAsIcon ## Buttons @docs containedButton, outlinedButton, textButton ## Text fields @docs textInput, passwordInput ## Text @docs header, stdText ## Items in a list @docs itemWithSubtext ## Lists @docs sideList, radioButtons ## Other elements @docs loadingIndicator, svg -} import Color exposing (Color) import Element exposing (Element) import Element.Background import Element.Events import Element.Font import Element.Input import Html.Attributes import Material.Icons.Types import Svg exposing (Svg) import Svg.Attributes import Theme import Widget import Widget.Customize as Customize import Widget.Icon exposing (Icon) import Widget.Material as Material import Widget.Material.Typography {-| A contained button representing the most important action of a group. -} containedButton : { buttonColor : Color , clickColor : Color , icon : Icon msg , onPress : Maybe msg , text : String } -> Element msg containedButton data = Widget.button ({ primary = data.buttonColor, onPrimary = data.clickColor } |> singlePalette |> Material.containedButton |> Customize.elementButton [ Element.width Element.fill ] ) { text = data.text, icon = data.icon, onPress = data.onPress } header : String -> Element msg header = Element.text >> Element.el Widget.Material.Typography.h1 >> List.singleton >> Element.paragraph [] iconAsElement : { color : Color , height : Int , icon : Material.Icons.Types.Icon msg , width : Int } -> Element msg iconAsElement data = data.icon |> iconAsIcon |> (|>) { size = Basics.min data.height data.width, color = data.color } |> Element.el [ Element.centerX, Element.centerY ] |> Element.el [ Element.height (Element.px data.height) , Element.width (Element.px data.width) ] iconAsIcon : Material.Icons.Types.Icon msg -> Widget.Icon.Icon msg iconAsIcon = Widget.Icon.elmMaterialIcons Material.Icons.Types.Color {-| Multiline item -} itemWithSubtext : { color : Color , leftIcon : Widget.Icon.Icon msg , onPress : Maybe msg , rightIcon : Widget.Icon.Icon msg , text : String , title : String } -> Widget.Item msg itemWithSubtext data = Widget.multiLineItem ({ primary = data.color, onPrimary = data.color } |> singlePalette |> Material.multiLineItem ) { content = data.rightIcon , icon = data.leftIcon , onPress = data.onPress , title = data.title , text = data.text } {-| Circular loading bar indicator -} loadingIndicator : { color : Color } -> Element msg loadingIndicator data = Widget.circularProgressIndicator ({ primary = data.color, onPrimary = data.color } |> singlePalette |> Material.progressIndicator ) Nothing {-| An outlined button representing an important action within a group. -} outlinedButton : { color : Color , icon : Icon msg , onPress : Maybe msg , text : String } -> Element msg outlinedButton data = Widget.button ({ primary = data.color, onPrimary = data.color } |> singlePalette |> Material.outlinedButton ) { text = data.text, icon = data.icon, onPress = data.onPress } {-| Show a password field -} passwordInput : { color : Color , label : String , onChange : String -> msg , placeholder : Maybe String , show : Bool , text : String } -> Element msg passwordInput data = Widget.currentPasswordInputV2 ({ primary = data.color, onPrimary = data.color } |> singlePalette |> Material.passwordInput |> Customize.elementRow [ Element.width Element.fill ] ) { label = data.label , onChange = data.onChange , placeholder = data.placeholder |> Maybe.map Element.text |> Maybe.map (Element.Input.placeholder []) , show = data.show , text = data.text } {-| Redio buttons are side-by-side buttons that only allowed up to one to be selected. -} radioButtons : { color : Color , items : List ( Bool, a ) , toIcon : a -> Icon msg , toString : a -> String , onChange : a -> msg } -> Element msg radioButtons data = data.items |> List.map (Tuple.mapSecond (\item -> { text = data.toString item , icon = \{ size, color } -> Element.text (data.toString item) , onPress = Just (data.onChange item) } ) ) |> Widget.toggleRow { elementRow = Material.toggleRow , content = { primary = data.color, onPrimary = data.color } |> singlePalette |> Material.toggleButton } {-| Create a simple palette. -} singlePalette : { primary : Color, onPrimary : Color } -> Material.Palette singlePalette { primary, onPrimary } = { primary = primary , secondary = primary , background = primary , surface = primary , error = primary , on = { primary = onPrimary , secondary = onPrimary , background = onPrimary , surface = onPrimary , error = onPrimary } } sideIconBar : { colorBackground : Color , colorText : Color , height : Int , items : List { icon : Widget.Icon.Icon msg, onPress : msg, text : String } , width : Int } -> Element msg sideIconBar data = let buttonHeight = round (toFloat data.width * 1.618) fontSize = data.width // 6 iconSize = data.width * 3 // 5 in data.items |> List.map (\item -> [ item.icon { size = iconSize, color = data.colorText } |> Element.el [ Element.centerX ] , Element.paragraph [] [ Element.text item.text ] ] |> Element.column [ Element.centerX , Element.centerY , Element.Font.bold , Element.Font.center , Element.Font.size fontSize , Element.htmlAttribute (Html.Attributes.style "cursor" "pointer") ] |> Element.el [ Element.centerY , Element.Events.onClick item.onPress , Element.height (Element.px data.width) , Element.width (Element.px data.width) ] |> Element.el [ Element.height (Element.px buttonHeight) ] ) |> Element.column [ Element.Background.color (Theme.toElmUiColor data.colorBackground) , Element.height (Element.px data.height) , Element.scrollbarY , Element.width (Element.px data.width) ] sideList : { color : Color, items : List (Widget.Item msg), width : Int } -> Element msg sideList data = let width px = Element.width (Element.px px) in Widget.itemList ({ primary = data.color, onPrimary = data.color } |> singlePalette |> Material.sideSheet ) data.items |> Element.el [ Element.centerX, width (Basics.min 360 data.width) ] |> Element.el [ width data.width ] stdText : String -> Element msg stdText = Element.text >> List.singleton >> Element.paragraph [] svg : { aspectRatio : Float , height : Int , svg : Svg msg , width : Int , viewMinX : Float , viewMaxX : Float , viewMinY : Float , viewMaxY : Float } -> Element msg svg data = let givenWidth = toFloat data.width givenHeight = toFloat data.height scaleFactorWidth = givenHeight / givenWidth innerWidth = if scaleFactorWidth > data.aspectRatio then givenWidth else givenHeight / data.aspectRatio innerHeight = if scaleFactorWidth > data.aspectRatio then givenWidth * data.aspectRatio else givenHeight in Svg.svg [ [ data.viewMinX, data.viewMinY, data.viewMaxX - data.viewMinX, data.viewMaxY - data.viewMinY ] |> List.map String.fromFloat |> String.join " " |> Svg.Attributes.viewBox , Svg.Attributes.width (String.fromFloat innerWidth) , Svg.Attributes.height (String.fromFloat innerHeight) ] [ data.svg ] |> Element.html |> Element.el [ Element.centerX, Element.centerY ] |> Element.el [ Element.height (Element.px data.height) , Element.width (Element.px data.width) ] {-| A tab selector that always has an item selected. -} tab : { color : Color , content : Int -> Element msg , items : List { text : String, icon : Icon msg } , onSelect : Int -> msg , selected : Int } -> Element msg tab data = Widget.tab ({ primary = data.color, onPrimary = data.color } |> singlePalette |> Material.tab ) { tabs = { onSelect = data.onSelect >> Just , options = data.items , selected = Just data.selected } , content = \_ -> data.content data.selected } {-| A text button representing an important action within a group. -} textButton : { icon : Icon msg , onPress : Maybe msg , text : String , color : Color } -> Element msg textButton data = Widget.button ({ primary = data.color, onPrimary = data.color } |> singlePalette |> Material.textButton ) { text = data.text, icon = data.icon, onPress = data.onPress } {-| Text input element. -} textInput : { color : Color , label : String , onChange : String -> msg , placeholder : Maybe String , text : String } -> Element msg textInput data = Widget.textInput ({ primary = data.color, onPrimary = data.color } |> singlePalette |> Material.textInput |> Customize.elementRow [ Element.width Element.fill ] ) { chips = [] , text = data.text , placeholder = data.placeholder |> Maybe.map Element.text |> Maybe.map (Element.Input.placeholder []) , label = data.label , onChange = data.onChange } {-| Two blocks either next to each other or below each other, depending on the screen shape. -} twoBlocks : { height : Int , el1 : { height : Int, width : Int } -> Element msg , el2 : { height : Int, width : Int } -> Element msg , width : Int } -> Element msg twoBlocks data = let goesVertical = 2 * data.width <= 3 * data.height direction = if goesVertical then Element.column else Element.row width = if goesVertical then data.width else data.width // 2 height = if goesVertical then data.height // 2 else data.height in direction [ Element.height (Element.px data.height) , Element.width (Element.px data.width) ] [ data.el1 { height = height, width = width } , data.el2 { height = height, width = width } ]