website/frontend/src/Layouts/Navbar.elm
2024-12-12 01:36:31 -06:00

740 lines
20 KiB
Elm
Executable file

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 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
, lock
, mastodon
, nutriDex
, services
, twitter
, upRootLarge
, upRootMedium
, upRootSmall
)
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
]
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 =
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 350 ] <| html upRootLarge
}
, el
[ height <| px 50
, width <| px 40
, alignRight
]
<|
(if input.model.isNavbarExpanded then
circleX
else
circleDots
)
{ elementAttributes =
[ centerX
, centerY
, Events.onClick input.contentMessage
]
, 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 =
column
[ scrollbarY
, height fill
, width fill
]
<|
List.map
(\x ->
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.HyperBlog
, isNewTabLink = False
, isSubscriberOnly = False
, name = String.toUpper pageNames.pageHyperBlog
, url = Path.toString Path.HyperBlog
}
, { 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
}
]
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 route
, itemText input route
]
, url = route.url
}
itemIcon : RowInput contentMsg -> Element contentMsg
itemIcon 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" ]
}
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
]
<|
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
}