module Layouts.Navbar exposing (Model, Msg, Props, layout) import Config.Data.Identity exposing (pageNames) import Config.Helpers.Format exposing ( paragraphFontSize , paragraphSpacing ) import Config.Style.Colour.Helpers exposing (colourTheme) import Config.Style.Fonts exposing (spartanFont) import Config.Style.Glow exposing (glowDeepDarkGreyNavbar) import Config.Style.Icons.Icons exposing ( circleDots , circleX , contact , debate , discord , donate , gitlab , home , hyperBlog , interviews , leaving , line , lock , mastodon , nutriDex , services , twitter , upRootLarge , upRootMedium , upRootSmall , video ) import Config.Style.Icons.Types as TySvg exposing (..) import Config.Style.Transitions exposing ( hoverFontLightOrange , specialNavbarTransition , transitionStyleFast , transitionStyleMedium , transitionStyleSlow ) import Effect exposing (Effect) import Element as E exposing (..) import Element.Background as B import Element.Border as D import Element.Events as Events import Element.Font as F import Element.Region exposing (description) import Html exposing (Html) import Html.Attributes as H exposing ( class , style ) import Layout exposing (Layout) import Maybe.Extra import Route exposing (Route) import Route.Path as Path import Shared import Shared.Msg import Svg.Attributes as SvgAttr import View exposing (View) type alias Props = {} layout : Props -> Shared.Model -> Route () -> Layout () Model Msg contentMsg layout _ s r = Layout.new { init = init s , update = update , view = view r s , subscriptions = \_ -> Sub.none } -- MODEL type alias Model = { isNavbarExpanded : Bool } init : Shared.Model -> () -> ( Model, Effect.Effect Msg ) init shared _ = ( { isNavbarExpanded = shared.isNavbarExpanded } , Effect.none ) -- UPDATE type Msg = ReplaceMe | ToggleNavbarExpansion | ToggleLanguage update : Msg -> Model -> ( Model, Effect.Effect Msg ) update msg model = case msg of ReplaceMe -> ( model , Effect.none ) ToggleLanguage -> ( model , Effect.toggleLanguage ) ToggleNavbarExpansion -> ( { model | isNavbarExpanded = not model.isNavbarExpanded } , Effect.toggleNavbarExpansion ) subscriptions : Model -> Sub Msg subscriptions model = Sub.none -- VIEW view : Route () -> Shared.Model -> { toContentMsg : Msg -> contentMsg, content : View.View contentMsg, model : Model } -> View.View contentMsg view route shared { toContentMsg, content, model } = { title = "uRN :: " ++ content.title , attributes = [ B.color colourTheme.backgroundDarkGrey , F.color colourTheme.textLightGrey , F.family [ spartanFont ] , height fill ] , element = navbarContainer { route = route , sharedModel = shared , model = model , contentMessage = toContentMsg ToggleNavbarExpansion , languageSelectorMessage = toContentMsg ToggleLanguage , content = content } (case shared.device.class of Phone -> topbar Tablet -> topbar _ -> sidebar ) } languageSelector : contentMsg -> Element contentMsg languageSelector m = el [ alignRight , Events.onClick m ] <| none navbarContainer : NavbarInput contentMsg -> NavbarMaker contentMsg -> Element contentMsg navbarContainer input maker = el [ height fill , inFront <| maker input , inFront <| languageSelector input.languageSelectorMessage , paddingEach <| (\( top, left ) -> { top = top , right = 0 , bottom = 0 , left = left } ) (case input.sharedModel.device.class of Phone -> ( barReservedRegion.topbar, 0 ) Tablet -> ( barReservedRegion.topbar, 0 ) _ -> ( 0, barReservedRegion.sidebar ) ) , width fill ] <| el [ height fill , width fill , scrollbarY , E.htmlAttribute (H.id "scroll-container") ] input.content.element barReservedRegion : { sidebar : number, topbar : number } barReservedRegion = { sidebar = 70 , topbar = 60 } bar : NavbarInput contentMsg -> NavbarLogoMaker contentMsg -> List (Attribute contentMsg) -> Element contentMsg bar input logo attr = column ([ B.color colourTheme.backgroundDarkGrey , transitionStyleMedium ] ++ attr ) <| [ logo input , items input , footerItems input ] topbar : NavbarInput contentMsg -> Element contentMsg topbar input = bar input topbarLogo [ height <| px <| if input.model.isNavbarExpanded then input.sharedModel.height else barReservedRegion.topbar , width fill , D.widthEach { bottom = 1 , top = 0 , left = 0 , right = 0 } , D.color colourTheme.textDarkOrange ] topbarLogo : NavbarInput contentMsg -> Element contentMsg topbarLogo input = let svgFormat = { elementAttributes = [ centerX , centerY , Events.onClick input.contentMessage , transitionStyleSlow ] , sharedModel = input.sharedModel , svgAttributes = [ SvgAttr.width "30" ] } in row ([ height <| px barReservedRegion.topbar , transitionStyleMedium , width fill ] ++ (if input.model.isNavbarExpanded then [ B.color colourTheme.backgroundDarkGrey ] else [] ) ) <| [ link [ pointer ] { url = Path.toString Path.Home_ , label = el [ paddingXY 10 0, width <| px 250 ] <| html upRootLarge } , el [ height <| px 50 , width <| px 40 , alignRight , centerY , moveUp 5 ] <| -- (if input.model.isNavbarExpanded then column [ centerY ] [ line (if input.model.isNavbarExpanded then { elementAttributes = [ centerX , centerY , moveDown 13 , rotate (degrees 45) , Events.onClick input.contentMessage , transitionStyleSlow ] , sharedModel = input.sharedModel , svgAttributes = [ SvgAttr.width "30" ] } else { elementAttributes = [ centerX , centerY , Events.onClick input.contentMessage , transitionStyleSlow ] , sharedModel = input.sharedModel , svgAttributes = [ SvgAttr.width "30" ] } ) , line (if input.model.isNavbarExpanded then { elementAttributes = [ centerX , centerY , moveUp 22 , transparent True , Events.onClick input.contentMessage , transitionStyleSlow ] , sharedModel = input.sharedModel , svgAttributes = [ SvgAttr.width "30" ] } else { elementAttributes = [ centerX , centerY , moveUp 22 , Events.onClick input.contentMessage , transitionStyleSlow ] , sharedModel = input.sharedModel , svgAttributes = [ SvgAttr.width "30" ] } ) , line (if input.model.isNavbarExpanded then { elementAttributes = [ centerX , centerY , rotate (degrees -45) , moveUp 56 , Events.onClick input.contentMessage , transitionStyleSlow ] , sharedModel = input.sharedModel , svgAttributes = [ SvgAttr.width "30" ] } else { elementAttributes = [ centerX , centerY , moveUp 44 , Events.onClick input.contentMessage , transitionStyleSlow ] , sharedModel = input.sharedModel , svgAttributes = [ SvgAttr.width "30" ] } ) ] ] sidebar : NavbarInput contentMsg -> Element contentMsg sidebar input = bar input sidebarLogo [ Events.onMouseEnter <| input.contentMessage , Events.onMouseLeave <| input.contentMessage , height fill , htmlAttribute <| style "overflow" "hidden" , htmlAttribute <| style "z-index" "2" , D.widthEach { bottom = 0 , top = 0 , left = 0 , right = 3 } , glowDeepDarkGreyNavbar , D.color colourTheme.textDarkOrange , if input.model.isNavbarExpanded then width <| px 225 else width <| px barReservedRegion.sidebar ] sidebarLogo : NavbarInput contentMsg -> Element contentMsg sidebarLogo input = let logo : Element msg logo = el [ width <| px 40 , transitionStyleMedium , if input.model.isNavbarExpanded then htmlAttribute (style "transform" "rotate(360deg)") else htmlAttribute (style "transform" "rotate(0deg)") ] <| html upRootSmall text : Element msg text = el ([ centerY , transitionStyleMedium , width <| px 145 ] ++ (if input.model.isNavbarExpanded then [] else [ transparent True ] ) ) <| html upRootLarge in el [ transitionStyleMedium , height <| px 60 , width fill ] <| el [ alignRight , centerY ] <| el [ transitionStyleMedium , onLeft text , paddingEach { top = 0 , right = 12 , bottom = 0 , left = 12 } , width fill ] logo items : NavbarInput contentMsg -> Element contentMsg items input = let navbarUnfucker : List (Attr () msg) navbarUnfucker = [ transparent True , htmlAttribute (style "position" "absolute") , htmlAttribute (style "z-index" "-10") , htmlAttribute (style "opacity" "0") , htmlAttribute (style "pointer-events" "none") , htmlAttribute (style "visibility" "hidden") ] in column [ height fill , width fill ] <| List.map (\x -> el ([ width fill , transitionStyleMedium ] ++ (if input.model.isNavbarExpanded then case input.sharedModel.device.class of _ -> [ transparent False ] else case input.sharedModel.device.class of Phone -> navbarUnfucker Tablet -> navbarUnfucker _ -> [ transparent False ] ) ) <| makeItem input { icon = x.icon , isCurrent = x.isCurrent , isNewTabLink = x.isNewTabLink , isSubscriberOnly = x.isSubscriberOnly , name = x.name , sharedModel = input.sharedModel , url = x.url } ) [ { icon = home , isCurrent = input.route.path == Path.Home_ , isNewTabLink = False , isSubscriberOnly = False , name = String.toUpper pageNames.pageHome , url = Path.toString Path.Home_ } , { icon = services , isCurrent = input.route.path == Path.Services , isNewTabLink = False , isSubscriberOnly = False , name = String.toUpper pageNames.pageServices , url = Path.toString Path.Services } , { icon = hyperBlog , isCurrent = input.route.path == Path.Blog , isNewTabLink = False , isSubscriberOnly = False , name = String.toUpper pageNames.pageHyperBlog , url = Path.toString Path.Blog } , { icon = video , isCurrent = False , isNewTabLink = True , isSubscriberOnly = False , name = String.toUpper "Video" , url = "https://video.uprootnutrition.com" } , { icon = debate , isCurrent = input.route.path == Path.Debate , isNewTabLink = False , isSubscriberOnly = False , name = String.toUpper pageNames.pageDebate , url = Path.toString Path.Debate } , { icon = nutriDex , isCurrent = input.route.path == Path.Nutridex , isNewTabLink = False , isSubscriberOnly = False , name = String.toUpper pageNames.pageNutriDex , url = Path.toString Path.Nutridex } , { icon = interviews , isCurrent = input.route.path == Path.Interviews , isNewTabLink = False , isSubscriberOnly = False , name = String.toUpper pageNames.pageInterviews , url = Path.toString Path.Interviews } , { icon = donate , isCurrent = input.route.path == Path.Donate , isNewTabLink = False , isSubscriberOnly = False , name = String.toUpper pageNames.pageDonate , url = Path.toString Path.Donate } , { icon = contact , isCurrent = input.route.path == Path.Contact , isNewTabLink = False , isSubscriberOnly = False , name = String.toUpper pageNames.pageContact , url = Path.toString Path.Contact } ] makeItemLogic : NavbarInput contentMsg -> RowInput contentMsg -> Element contentMsg makeItemLogic input route = if input.model.isNavbarExpanded then case input.sharedModel.device.class of _ -> makeItem input route else case input.sharedModel.device.class of Phone -> el [] none Tablet -> el [] none _ -> makeItem input route makeItem : NavbarInput contentMsg -> RowInput contentMsg -> Element contentMsg makeItem input route = (if route.isNewTabLink then newTabLink else link ) [ width fill ] { label = row ([ mouseOver (if route.isCurrent then [] else [ B.gradient { angle = 1.57 , steps = case input.sharedModel.device.class of Phone -> [ colourTheme.backgroundLightGrey , colourTheme.backgroundDarkGrey , colourTheme.backgroundDarkGrey ] Tablet -> [ colourTheme.backgroundLightGrey , colourTheme.backgroundDarkGrey , colourTheme.backgroundDarkGrey ] _ -> [ colourTheme.backgroundDarkGrey , colourTheme.backgroundLightGrey , colourTheme.backgroundLightGrey ] } , F.color colourTheme.textLightOrange ] ) , paddingEach { top = 0 , right = 0 , bottom = 0 , left = 23 } , spacing 12 , transitionStyleMedium , width fill ] ++ (if route.isCurrent then [ B.gradient { angle = 1.57 , steps = case input.sharedModel.device.class of Phone -> [ colourTheme.backgroundDeepDarkGrey , colourTheme.backgroundDeepDarkGrey , colourTheme.backgroundDarkGrey ] Tablet -> [ colourTheme.backgroundDeepDarkGrey , colourTheme.backgroundDeepDarkGrey , colourTheme.backgroundDarkGrey ] _ -> [ colourTheme.backgroundDarkGrey , colourTheme.backgroundDarkGrey , colourTheme.backgroundDeepDarkGrey ] } , F.color colourTheme.textLightOrange ] else [] ) ++ (case input.sharedModel.device.class of Phone -> [ Events.onClick input.contentMessage ] Tablet -> [ Events.onClick input.contentMessage ] _ -> [] ) ) [ itemIcon input route , itemText input route , case route.isNewTabLink of True -> itemLeavingIcon input route False -> none ] , url = route.url } itemIcon : NavbarInput contentMsg -> RowInput contentMsg -> Element contentMsg itemIcon input route = el ([ height <| px 50 , width <| px 20 ] ++ (if Maybe.Extra.isNothing route.sharedModel.user && route.isSubscriberOnly then [ inFront <| lock { elementAttributes = [ moveDown 19 , moveLeft 20 , F.color colourTheme.barRed ] , sharedModel = route.sharedModel , svgAttributes = [ SvgAttr.width "12" ] } ] else [] ) ) <| route.icon { elementAttributes = [ centerX , centerY ] , sharedModel = route.sharedModel , svgAttributes = [ SvgAttr.width "30" ] } itemLeavingIcon : NavbarInput contentMsg -> RowInput contentMsg -> Element contentMsg itemLeavingIcon input route = el [ height <| px 50 , width <| px 20 , E.alignRight , paddingXY 10 0 ] <| leaving { elementAttributes = [ E.alignRight , E.alignTop , centerY ] , sharedModel = route.sharedModel , svgAttributes = [ SvgAttr.width "15" ] } itemText : NavbarInput contentMsg -> RowInput contentMsg -> Element contentMsg itemText input route = el [ specialNavbarTransition -- This special transition is needed to avoid weird animation sequencing rather in Chrome-based browsers. , if input.model.isNavbarExpanded then transparent False else transparent True , F.bold , paddingEach { top = 6 , right = 0 , bottom = 3 , left = 0 } ] <| text route.name footerItems : NavbarInput contentMsg -> Element contentMsg footerItems input = row ([ scrollbarY , height <| px 50 , transitionStyleMedium , width <| px 223 ] ++ (if input.model.isNavbarExpanded then [] else [ transparent True ] ) ) <| List.map (\x -> makeFooterIcon input { icon = x.icon , isNewTabLink = x.isNewTabLink , url = x.url , sharedModel = input.sharedModel } ) [ { icon = gitlab , isNewTabLink = True , url = "https://gitlab.com/upRootNutrition/website" } , { icon = twitter , isNewTabLink = True , url = "https://x.com/upRootNutrition" } -- , { icon = mastodon -- , isNewTabLink = True -- , url = "https://social.uprootnutrition.com/@nick" -- } , { icon = discord , isNewTabLink = True , url = "https://discord.gg/eeYQ2wJknS" } ] footerIcon : FooterInput contentMsg -> Element contentMsg footerIcon route = el [ height <| px 50 , width <| px 20 , alignBottom ] <| route.icon { elementAttributes = [ centerX , centerY ] , sharedModel = route.sharedModel , svgAttributes = [ SvgAttr.width "20" ] } makeFooterIcon : NavbarInput contentMsg -> FooterInput contentMsg -> Element contentMsg makeFooterIcon input route = row [ centerX ] [ (if route.isNewTabLink then newTabLink else link ) [ width fill ] { label = row [ mouseOver [ F.color colourTheme.textLightOrange ] , paddingEach { top = 0 , right = 10 , bottom = 0 , left = 10 } , transitionStyleMedium , width fill , F.color colourTheme.textLightGrey ] [ footerIcon route ] , url = route.url } ] -- Types: type alias NavbarInput contentMsg = { route : Route () , sharedModel : Shared.Model , model : Model , contentMessage : contentMsg , languageSelectorMessage : contentMsg , content : View.View contentMsg } type alias NavbarMaker contentMsg = NavbarInput contentMsg -> Element contentMsg type alias NavbarLogoMaker contentMsg = NavbarMaker contentMsg type alias RowInput msg = { icon : TySvg.OuterPart msg -> Element msg , isCurrent : Bool , isNewTabLink : Bool , isSubscriberOnly : Bool , name : String , url : String , sharedModel : Shared.Model } type alias FooterInput msg = { icon : TySvg.OuterPart msg -> Element msg , isNewTabLink : Bool , url : String , sharedModel : Shared.Model }