noredink-ui/styleguide-app/App.elm

367 lines
11 KiB
Elm
Raw Normal View History

module App exposing (Effect(..), Model, Msg(..), init, perform, subscriptions, update, view)
2018-02-13 00:32:38 +03:00
2021-11-05 21:26:43 +03:00
import Accessibility.Styled as Html exposing (Html)
import Accessibility.Styled.Key as Key
import Browser exposing (Document, UrlRequest(..))
2020-03-24 22:26:41 +03:00
import Browser.Dom
import Browser.Navigation exposing (Key)
2022-03-30 21:15:14 +03:00
import Category exposing (Category)
import Css exposing (..)
import Css.Media exposing (withMedia)
import Dict exposing (Dict)
import Example exposing (Example)
import Examples
2022-03-15 21:06:13 +03:00
import Html.Styled.Attributes exposing (..)
2022-03-29 00:50:12 +03:00
import Http
import Json.Decode as Decode
2020-11-06 21:51:33 +03:00
import Nri.Ui.CssVendorPrefix.V1 as VendorPrefixed
import Nri.Ui.Heading.V2 as Heading
2022-03-29 23:43:24 +03:00
import Nri.Ui.MediaQuery.V1 exposing (mobile)
import Nri.Ui.Page.V3 as Page
2022-03-29 23:43:24 +03:00
import Nri.Ui.SideNav.V3 as SideNav
import Nri.Ui.Sprite.V1 as Sprite
2022-05-10 01:36:47 +03:00
import Nri.Ui.UiIcon.V1 as UiIcon
2022-03-15 21:06:13 +03:00
import Routes exposing (Route)
import Sort.Set as Set
2020-03-24 22:26:41 +03:00
import Task
import Url exposing (Url)
2018-02-13 00:32:38 +03:00
type alias Model key =
{ -- Global UI
route : Route
, previousRoute : Maybe Route
, moduleStates : Dict String (Example Examples.State Examples.Msg)
, navigationKey : key
, elliePackageDependencies : Result Http.Error (Dict String String)
}
init : () -> Url -> key -> ( Model key, Effect )
2018-12-05 21:56:04 +03:00
init () url key =
( { route = Routes.fromLocation url
, previousRoute = Nothing
, moduleStates =
Dict.fromList
(List.map (\example -> ( example.name, example )) Examples.all)
2018-12-05 21:56:04 +03:00
, navigationKey = key
, elliePackageDependencies = Ok Dict.empty
2018-02-13 00:32:38 +03:00
}
2022-03-29 00:50:12 +03:00
, Cmd.batch
[ loadPackage
, loadApplicationDependencies
]
|> Command
2018-02-13 00:32:38 +03:00
)
type Msg
= UpdateModuleStates String Examples.Msg
| OnUrlRequest Browser.UrlRequest
| OnUrlChange Url
| ChangeRoute Route
2020-03-24 22:26:41 +03:00
| SkipToMainContent
2022-03-29 00:50:12 +03:00
| LoadedPackages (Result Http.Error (Dict String String))
| Focused (Result Browser.Dom.Error ())
update : Msg -> Model key -> ( Model key, Effect )
update action model =
case action of
UpdateModuleStates key exampleMsg ->
case Dict.get key model.moduleStates of
Just example ->
example.update exampleMsg example.state
|> Tuple.mapFirst
(\newState ->
{ model
| moduleStates =
Dict.insert key
{ example | state = newState }
model.moduleStates
}
)
|> Tuple.mapSecond (Cmd.map (UpdateModuleStates key) >> Command)
Nothing ->
( model, None )
OnUrlRequest request ->
case request of
Internal loc ->
( model, GoToUrl loc )
External loc ->
( model, Load loc )
OnUrlChange route ->
( { model
| route = Routes.fromLocation route
, previousRoute = Just model.route
}
, None
)
ChangeRoute route ->
( model
, GoToRoute route
)
2020-03-24 22:26:41 +03:00
SkipToMainContent ->
( model
, FocusOn "maincontent"
2020-03-24 22:26:41 +03:00
)
2022-03-29 00:52:06 +03:00
LoadedPackages newPackagesResult ->
2022-03-29 20:31:06 +03:00
let
-- Ellie gets really slow to compile if we include all the packages, unfortunately!
-- feel free to adjust the settings here if you need more packages for a particular example.
removedPackages =
[ "avh4/elm-debug-controls"
, "BrianHicks/elm-particle"
, "elm-community/random-extra"
, "elm/browser"
, "elm/http"
, "elm/json"
, "elm/parser"
, "elm/random"
, "elm/regex"
, "elm/svg"
, "elm/url"
, "elm-community/string-extra"
, "Gizra/elm-keyboard-event"
, "pablohirafuji/elm-markdown"
, "rtfeldman/elm-sorter-experiment"
, "tesk9/accessible-html-with-css"
, "tesk9/palette"
, "wernerdegroot/listzipper"
]
in
2022-03-29 00:52:06 +03:00
( { model
| elliePackageDependencies =
2022-03-29 20:31:06 +03:00
List.foldl (\name -> Result.map (Dict.remove name))
(Result.map2 Dict.union model.elliePackageDependencies newPackagesResult)
removedPackages
2022-03-29 00:52:06 +03:00
}
, None
2022-03-29 00:50:12 +03:00
)
Focused _ ->
( model, None )
type Effect
= GoToRoute Route
| GoToUrl Url
| Load String
| FocusOn String
| None
| Command (Cmd Msg)
perform : Key -> Effect -> Cmd Msg
perform navigationKey effect =
case effect of
GoToRoute route ->
Browser.Navigation.pushUrl navigationKey (Routes.toString route)
GoToUrl url ->
Browser.Navigation.pushUrl navigationKey (Url.toString url)
Load loc ->
Browser.Navigation.load loc
FocusOn id ->
Task.attempt Focused (Browser.Dom.focus id)
None ->
Cmd.none
Command cmd ->
cmd
subscriptions : Model key -> Sub Msg
subscriptions model =
Dict.values model.moduleStates
|> List.map (\example -> Sub.map (UpdateModuleStates example.name) (example.subscriptions example.state))
|> Sub.batch
view : Model key -> Document Msg
view model =
2020-06-19 23:52:02 +03:00
let
findExampleByName name =
Dict.values model.moduleStates
|> List.filter (\m -> m.name == name)
|> List.head
toBody view_ =
List.map Html.toUnstyled
[ view_
, Html.map never Sprite.attach
]
in
case model.route of
Routes.Doodad doodad ->
case findExampleByName doodad of
Just example ->
2022-03-30 21:24:26 +03:00
{ title = example.name ++ " in the NoRedInk Style Guide"
, body =
viewExample model example
|> Html.map (UpdateModuleStates example.name)
|> toBody
}
Nothing ->
2022-03-30 21:24:26 +03:00
{ title = "Not found in the NoRedInk Style Guide"
, body = toBody notFound
}
Routes.Category category ->
2022-03-30 21:24:26 +03:00
{ title = Category.forDisplay category ++ " Category in the NoRedInk Style Guide"
, body = toBody (viewCategory model category)
}
Routes.All ->
2022-03-30 21:24:26 +03:00
{ title = "NoRedInk Style Guide"
, body = toBody (viewAll model)
}
viewExample : Model key -> Example a msg -> Html msg
2022-03-30 21:01:16 +03:00
viewExample model example =
Html.div [ css [ maxWidth (Css.px 1400), margin auto ] ]
[ Example.view model.previousRoute
{ packageDependencies = model.elliePackageDependencies }
example
]
2022-03-30 21:13:11 +03:00
notFound : Html Msg
notFound =
Page.notFound
{ link = ChangeRoute Routes.All
, recoveryText = Page.ReturnTo "Component Library"
}
viewAll : Model key -> Html Msg
2022-03-30 21:15:58 +03:00
viewAll model =
2022-05-20 22:20:12 +03:00
withSideNav model.route <|
viewPreviews "all" (Dict.values model.moduleStates)
2022-03-30 21:15:58 +03:00
viewCategory : Model key -> Category -> Html Msg
2022-03-30 21:15:14 +03:00
viewCategory model category =
withSideNav model.route
2022-05-20 22:20:12 +03:00
(model.moduleStates
2022-03-30 21:15:14 +03:00
|> Dict.values
|> List.filter
(\doodad ->
Set.memberOf
(Set.fromList Category.sorter doodad.categories)
category
)
|> viewPreviews (Category.forId category)
2022-05-20 22:20:12 +03:00
)
2022-03-30 21:15:14 +03:00
2022-05-20 22:20:12 +03:00
withSideNav : Route -> Html Msg -> Html Msg
withSideNav currentRoute content =
2020-06-20 00:12:53 +03:00
Html.div
[ css
[ displayFlex
, withMedia [ mobile ] [ flexDirection column, alignItems stretch ]
2020-06-20 00:12:53 +03:00
, alignItems flexStart
2021-12-03 22:40:57 +03:00
, maxWidth (Css.px 1400)
, margin auto
2020-06-20 00:12:53 +03:00
]
]
[ navigation currentRoute
2021-11-05 21:26:43 +03:00
, Html.main_
[ css
[ flexGrow (int 1)
, margin2 (px 40) zero
, Css.minHeight (Css.vh 100)
]
, id "maincontent"
, Key.tabbable False
2021-11-05 21:26:43 +03:00
]
2022-05-20 22:20:12 +03:00
[ Html.div [ css [ Css.marginBottom (Css.px 30) ] ]
[ Routes.viewBreadCrumbs currentRoute
]
, content
]
]
viewPreviews : String -> List (Example state msg) -> Html Msg
viewPreviews containerId examples =
examples
|> List.map (\example -> Example.preview ChangeRoute example)
|> Html.div
[ id containerId
, css
[ Css.displayFlex
, Css.flexWrap Css.wrap
, Css.property "gap" "10px"
]
]
2021-05-28 04:23:07 +03:00
navigation : Route -> Html Msg
navigation currentRoute =
let
2021-12-04 00:34:32 +03:00
categoryNavLinks : List (SideNav.Entry Route Msg)
categoryNavLinks =
List.map
(\category ->
SideNav.entry (Category.forDisplay category)
[ SideNav.href (Routes.Category category) ]
)
Category.all
in
2021-12-03 19:45:06 +03:00
SideNav.view
2022-03-07 23:26:56 +03:00
{ isCurrentRoute = (==) currentRoute
, routeToString = Routes.toString
2021-12-03 19:45:06 +03:00
, onSkipNav = SkipToMainContent
}
2022-03-29 23:43:24 +03:00
[ SideNav.navNotMobileCss
[ VendorPrefixed.value "position" "sticky"
, top (px 55)
]
]
2022-05-10 19:50:07 +03:00
(SideNav.entry "Usage Guidelines"
2022-05-10 01:36:47 +03:00
[ SideNav.linkExternal "https://paper.dropbox.com/doc/UI-Style-Guide-and-Caveats--BhJHYronm1RGM1hRfnkvhrZMAg-PvOLxeX3oyujYEzdJx5pu"
, SideNav.icon UiIcon.openInNewTab
]
:: SideNav.entry "All" [ SideNav.href Routes.All ]
2021-12-04 00:34:32 +03:00
:: categoryNavLinks
)
2022-03-29 00:50:12 +03:00
loadPackage : Cmd Msg
loadPackage =
Http.get
{ url = "/package.json"
, expect =
Http.expectJson
LoadedPackages
(Decode.map2 Dict.singleton
(Decode.field "name" Decode.string)
(Decode.field "version" Decode.string)
)
}
loadApplicationDependencies : Cmd Msg
loadApplicationDependencies =
Http.get
{ url = "/application.json"
, expect =
Http.expectJson
LoadedPackages
(Decode.at [ "dependencies", "direct" ] (Decode.dict Decode.string))
}