noredink-ui/styleguide-app/Main.elm

325 lines
10 KiB
Elm
Raw Normal View History

2018-09-26 17:02:10 +03:00
module Main exposing (init, main)
2018-02-13 00:32:38 +03:00
2020-06-20 00:12:53 +03:00
import Accessibility.Styled as Html exposing (Html, img, text)
2020-06-19 23:52:02 +03:00
import AtomicDesignType exposing (AtomicDesignType)
import Browser exposing (Document, UrlRequest(..))
2020-03-24 22:26:41 +03:00
import Browser.Dom
import Browser.Navigation exposing (Key)
import Category
import Css exposing (..)
import Dict exposing (Dict)
import Example exposing (Example)
import Examples
import Html as RootHtml
import Html.Attributes
import Html.Styled.Attributes as Attributes exposing (..)
2020-03-24 22:26:41 +03:00
import Html.Styled.Events as Events
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Css.VendorPrefixed as VendorPrefixed
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Heading.V2 as Heading
2018-02-13 00:32:38 +03:00
import Routes as Routes exposing (Route(..))
2020-06-19 23:52:02 +03:00
import Sort.Set as Set exposing (Set)
2020-03-24 22:26:41 +03:00
import Task
import Url exposing (Url)
2018-02-13 00:32:38 +03:00
main : Program () Model Msg
2018-02-13 00:32:38 +03:00
main =
Browser.application
2018-02-13 00:32:38 +03:00
{ init = init
, update = update
, subscriptions = subscriptions
, view = view
, onUrlRequest = OnUrlRequest
, onUrlChange = OnUrlChange
2018-02-13 00:32:38 +03:00
}
type alias Model =
{ -- Global UI
route : Route
, moduleStates : Dict String (Example Examples.State Examples.Msg)
2020-06-19 23:52:02 +03:00
, atomicDesignTypes : Set AtomicDesignType
, navigationKey : Key
}
init : () -> Url -> Key -> ( Model, Cmd Msg )
2018-12-05 21:56:04 +03:00
init () url key =
( { route = Routes.fromLocation url
, moduleStates =
Dict.fromList
(List.map (\example -> ( example.name, example )) Examples.all)
2020-06-19 23:52:02 +03:00
, atomicDesignTypes = Set.fromList AtomicDesignType.sorter AtomicDesignType.all
2018-12-05 21:56:04 +03:00
, navigationKey = key
2018-02-13 00:32:38 +03:00
}
, Cmd.none
)
type Msg
= UpdateModuleStates String Examples.Msg
| OnUrlRequest Browser.UrlRequest
| OnUrlChange Url
2020-06-19 23:52:02 +03:00
| ToggleAtomicDesignType AtomicDesignType Bool
2020-03-24 22:26:41 +03:00
| SkipToMainContent
| NoOp
update : Msg -> Model -> ( Model, Cmd Msg )
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))
Nothing ->
( model, Cmd.none )
OnUrlRequest request ->
case request of
Internal loc ->
( model, Browser.Navigation.pushUrl model.navigationKey (Url.toString loc) )
External loc ->
( model, Browser.Navigation.load loc )
OnUrlChange route ->
( { model | route = Routes.fromLocation route }, Cmd.none )
2020-06-19 23:52:02 +03:00
ToggleAtomicDesignType atomicDesignType isOpen ->
( { model
| atomicDesignTypes =
(if isOpen then
Set.insert
else
Set.remove
)
atomicDesignType
model.atomicDesignTypes
}
, Cmd.none
)
2020-03-24 22:26:41 +03:00
SkipToMainContent ->
( model
, Task.attempt (\_ -> NoOp) (Browser.Dom.focus "maincontent")
)
NoOp ->
( model, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions model =
Dict.values model.moduleStates
|> List.map (\example -> Sub.map (UpdateModuleStates example.name) (example.subscriptions example.state))
|> Sub.batch
view : Model -> Document Msg
view model =
{ title = "Style Guide"
, body = [ view_ model |> Html.toUnstyled ]
}
view_ : Model -> Html Msg
view_ model =
2020-06-19 23:52:02 +03:00
let
examples filterBy =
List.filter
(\m ->
filterBy m
&& Set.memberOf model.atomicDesignTypes m.atomicDesignType
)
(Dict.values model.moduleStates)
mainContentHeader heading =
2020-10-02 07:08:41 +03:00
Heading.h1
[ Heading.customAttr (id "maincontent")
, Heading.customAttr (tabindex -1)
2020-10-02 07:08:41 +03:00
, Heading.css [ marginBottom (px 30) ]
]
[ Html.text heading ]
2020-06-19 23:52:02 +03:00
in
2020-06-20 00:12:53 +03:00
Html.div
[ css
[ displayFlex
, alignItems flexStart
, minHeight (vh 100)
]
]
2020-06-20 00:12:53 +03:00
[ navigation model.route model.atomicDesignTypes
, Html.main_ [ css [ flexGrow (int 1), sectionStyles ] ]
(case model.route of
Routes.Doodad doodad ->
2020-09-09 22:07:07 +03:00
case List.head (examples (\m -> m.name == doodad)) of
Just example ->
[ mainContentHeader ("Viewing " ++ doodad ++ " doodad only")
, Html.div [ id (String.replace "." "-" example.name) ]
[ Example.view example
|> Html.map (UpdateModuleStates example.name)
]
]
Nothing ->
[ Html.text <| "Oops! We couldn't find " ++ doodad ]
Routes.Category category ->
[ mainContentHeader (Category.forDisplay category)
, examples
(\doodad ->
Set.memberOf
(Set.fromList Category.sorter doodad.categories)
category
)
|> List.map
(\example ->
Example.view example
|> Html.map (UpdateModuleStates example.name)
2020-06-19 23:52:02 +03:00
)
|> Html.div [ id (Category.forId category) ]
]
Routes.All ->
[ mainContentHeader "All"
, examples (\_ -> True)
|> List.map
(\example ->
Example.view example
|> Html.map (UpdateModuleStates example.name)
)
|> Html.div []
]
)
]
2020-06-20 00:12:53 +03:00
navigation : Route -> Set AtomicDesignType -> Html Msg
navigation route openAtomicDesignTypes =
let
isActive category =
case route of
Routes.Category routeCategory ->
category == routeCategory
_ ->
False
2020-03-24 22:26:41 +03:00
link active hash displayName =
2020-06-20 00:12:53 +03:00
Html.a
[ css
[ backgroundColor transparent
, borderStyle none
, textDecoration none
, if active then
color Colors.navy
2020-06-20 00:12:53 +03:00
else
color Colors.azure
, Fonts.baseFont
]
, Attributes.href hash
]
[ Html.text displayName ]
navLink category =
2020-03-24 22:26:41 +03:00
link (isActive category)
("#/category/" ++ Debug.toString category)
(Category.forDisplay category)
toNavLi element =
Html.li
[ css
[ margin2 (px 10) zero
, listStyle none
, textDecoration none
]
]
[ element ]
in
2020-06-20 00:12:53 +03:00
Html.nav
[ css
[ flexBasis (px 200)
, backgroundColor Colors.gray96
, marginRight (px 40)
2020-10-02 07:08:41 +03:00
, padding (px 20)
2020-06-20 00:12:53 +03:00
, VendorPrefixed.value "position" "sticky"
, top (px 55)
, flexShrink zero
2020-10-02 07:08:41 +03:00
, borderRadius (px 8)
2020-06-20 00:12:53 +03:00
]
, attribute "aria-label" "Main Navigation"
]
2020-06-20 00:12:53 +03:00
[ Html.button
[ css
[ backgroundColor transparent
, borderStyle none
, textDecoration none
, color Colors.azure
, Fonts.baseFont
, Css.marginBottom (px 20)
2020-09-02 22:53:27 +03:00
, Css.pseudoClass "not(:focus)"
[ Css.property "clip" "rect(1px, 1px, 1px, 1px)"
, Css.position Css.absolute
, Css.height (Css.px 1)
, Css.width (Css.px 1)
, Css.overflow Css.hidden
, Css.margin (Css.px -1)
, Css.padding Css.zero
, Css.border Css.zero
]
2020-06-20 00:12:53 +03:00
]
, Events.onClick SkipToMainContent
, id "skip"
2020-03-24 22:26:41 +03:00
]
[ Html.text "Skip to main content" ]
, Heading.h4 [] [ Html.text "Categories" ]
, (link (route == Routes.All) "#/" "All"
:: List.map navLink Category.all
)
|> List.map toNavLi
2020-06-20 00:12:53 +03:00
|> Html.ul
[ css [ margin4 zero zero (px 40) zero, padding zero ]
, id "categories"
]
2020-06-20 00:12:53 +03:00
, Html.fieldset []
(Html.legend [] [ text "Atomic Design type" ]
:: List.map (checkAtomicDesignType openAtomicDesignTypes) AtomicDesignType.all
)
]
2020-06-20 00:12:53 +03:00
checkAtomicDesignType : Set AtomicDesignType -> AtomicDesignType -> Html Msg
checkAtomicDesignType openAtomicDesignTypes atomicDesignType =
let
isChecked =
Set.memberOf openAtomicDesignTypes atomicDesignType
name =
AtomicDesignType.toString atomicDesignType
in
Html.labelAfter [ css [ display block ] ] (text name) <|
Html.checkbox name
(Just isChecked)
[ Events.onCheck (ToggleAtomicDesignType atomicDesignType) ]
sectionStyles : Css.Style
sectionStyles =
Css.batch [ margin2 (px 40) zero ]