mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-09-21 04:07:54 +03:00
Merge pull request #1514 from NoRedInk/tessa/outline
🌟 Extracting Outline from the monolith
This commit is contained in:
commit
2dbbb04512
@ -28,6 +28,7 @@ import Examples.Logo as Logo
|
||||
import Examples.Menu as Menu
|
||||
import Examples.Message as Message
|
||||
import Examples.Modal as Modal
|
||||
import Examples.Outline as Outline
|
||||
import Examples.Page as Page
|
||||
import Examples.Pagination as Pagination
|
||||
import Examples.Panel as Panel
|
||||
@ -567,6 +568,25 @@ all =
|
||||
ModalState childState ->
|
||||
Just childState
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
, Outline.example
|
||||
|> Example.wrapMsg OutlineMsg
|
||||
(\msg ->
|
||||
case msg of
|
||||
OutlineMsg childMsg ->
|
||||
Just childMsg
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
|> Example.wrapState OutlineState
|
||||
(\msg ->
|
||||
case msg of
|
||||
OutlineState childState ->
|
||||
Just childState
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
@ -1076,6 +1096,7 @@ type State
|
||||
| MenuState Menu.State
|
||||
| MessageState Message.State
|
||||
| ModalState Modal.State
|
||||
| OutlineState Outline.State
|
||||
| PageState Page.State
|
||||
| PaginationState Pagination.State
|
||||
| PanelState Panel.State
|
||||
@ -1132,6 +1153,7 @@ type Msg
|
||||
| TabsMinimalMsg TabsMinimal.Msg
|
||||
| MessageMsg Message.Msg
|
||||
| ModalMsg Modal.Msg
|
||||
| OutlineMsg Outline.Msg
|
||||
| PageMsg Page.Msg
|
||||
| PaginationMsg Pagination.Msg
|
||||
| PanelMsg Panel.Msg
|
||||
|
490
component-catalog/src/Examples/Outline.elm
Normal file
490
component-catalog/src/Examples/Outline.elm
Normal file
@ -0,0 +1,490 @@
|
||||
module Examples.Outline exposing (example, State, Msg)
|
||||
|
||||
{-|
|
||||
|
||||
@docs example, State, Msg
|
||||
|
||||
-}
|
||||
|
||||
import Category exposing (Category(..))
|
||||
import Code
|
||||
import CommonControls
|
||||
import Css exposing (Color)
|
||||
import Css.Media exposing (withMedia)
|
||||
import Debug.Control as Control exposing (Control)
|
||||
import Debug.Control.View as ControlView
|
||||
import Example exposing (Example)
|
||||
import Html.Styled exposing (..)
|
||||
import Html.Styled.Attributes exposing (css)
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Heading.V3 as Heading
|
||||
import Nri.Ui.MediaQuery.V1 exposing (mobile)
|
||||
import Nri.Ui.Outline.V1 as Outline exposing (KeyedOutline, Outline, RowTheme)
|
||||
import Nri.Ui.Spacing.V1 as Spacing
|
||||
import Svg.Styled as Svg
|
||||
import Svg.Styled.Attributes as SvgAttrs
|
||||
|
||||
|
||||
moduleName : String
|
||||
moduleName =
|
||||
"Outline"
|
||||
|
||||
|
||||
version : Int
|
||||
version =
|
||||
1
|
||||
|
||||
|
||||
{-| -}
|
||||
example : Example State Msg
|
||||
example =
|
||||
{ name = moduleName
|
||||
, version = version
|
||||
, categories = [ Layout, Instructional ]
|
||||
, keyboardSupport = []
|
||||
, state = init
|
||||
, update = update
|
||||
, subscriptions = \_ -> Sub.none
|
||||
, preview = [ preview ]
|
||||
, about = []
|
||||
, view =
|
||||
\ellieLinkConfig state ->
|
||||
let
|
||||
settings =
|
||||
Control.currentValue state.control
|
||||
in
|
||||
[ ControlView.view
|
||||
{ ellieLinkConfig = ellieLinkConfig
|
||||
, name = moduleName
|
||||
, version = version
|
||||
, update = UpdateControl
|
||||
, settings = state.control
|
||||
, mainType = Just "RootHtml.Html msg"
|
||||
, extraCode = []
|
||||
, renderExample = Code.unstyledView
|
||||
, toExampleCode =
|
||||
\_ ->
|
||||
[ { sectionName = "Customizable Example"
|
||||
, code =
|
||||
case settings.type_ of
|
||||
Plain ->
|
||||
Code.fromModule moduleName "view"
|
||||
++ Code.listMultiline
|
||||
[ Code.fromModule moduleName "row "
|
||||
++ Code.recordMultiline
|
||||
[ ( "title", Code.maybe (Maybe.map Code.string settings.title) )
|
||||
, ( "content", "text " ++ Code.string settings.content )
|
||||
, ( "palette", Tuple.first settings.palette )
|
||||
, ( "rows", Code.listMultiline [ "-- …" ] 3 )
|
||||
]
|
||||
2
|
||||
]
|
||||
1
|
||||
|
||||
Keyed ->
|
||||
Code.fromModule moduleName "viewKeyed"
|
||||
++ Code.listMultiline
|
||||
[ Code.fromModule moduleName "keyedRow "
|
||||
++ Code.string "base-node"
|
||||
++ Code.recordMultiline
|
||||
[ ( "title", Code.maybe (Maybe.map Code.string settings.title) )
|
||||
, ( "content", "text " ++ Code.string settings.content )
|
||||
, ( "palette", Tuple.first settings.palette )
|
||||
, ( "rows", Code.listMultiline [ "-- …" ] 3 )
|
||||
]
|
||||
2
|
||||
]
|
||||
1
|
||||
|
||||
KeyedWithExtraContent extraContent ->
|
||||
Code.fromModule moduleName "viewKeyed"
|
||||
++ Code.listMultiline
|
||||
[ Code.fromModule moduleName "keyedRowWithExtraContent "
|
||||
++ Code.string "base-node"
|
||||
++ Code.recordMultiline
|
||||
[ ( "title", Code.maybe (Maybe.map Code.string settings.title) )
|
||||
, ( "content", "text " ++ Code.string settings.content )
|
||||
, ( "palette", Tuple.first settings.palette )
|
||||
, ( "rows", Code.listMultiline [ "-- …" ] 3 )
|
||||
, ( "extraContent"
|
||||
, "text " ++ Code.string "Extra content…"
|
||||
)
|
||||
]
|
||||
2
|
||||
]
|
||||
1
|
||||
}
|
||||
]
|
||||
}
|
||||
, Heading.h2
|
||||
[ Heading.plaintext "Customizable Example"
|
||||
, Heading.css [ Css.marginTop Spacing.verticalSpacerPx ]
|
||||
]
|
||||
, case settings.type_ of
|
||||
Plain ->
|
||||
Outline.view
|
||||
[ Outline.row
|
||||
{ title = settings.title
|
||||
, content = text settings.content
|
||||
, palette = Tuple.second settings.palette
|
||||
, rows = plainRows
|
||||
}
|
||||
]
|
||||
|
||||
Keyed ->
|
||||
Outline.viewKeyed
|
||||
[ Outline.keyedRow "base-node"
|
||||
{ title = settings.title
|
||||
, content = text settings.content
|
||||
, palette = Tuple.second settings.palette
|
||||
, rows = keyedRows
|
||||
}
|
||||
]
|
||||
|
||||
KeyedWithExtraContent extraContent ->
|
||||
Outline.viewKeyed
|
||||
[ Outline.keyedRowWithExtraContent "base-node"
|
||||
{ title = settings.title
|
||||
, content = text settings.content
|
||||
, palette = Tuple.second settings.palette
|
||||
, rows = keyedRows
|
||||
, extraContent =
|
||||
pre [ css [ Css.margin Css.zero ] ]
|
||||
[ text extraContent ]
|
||||
}
|
||||
]
|
||||
, Heading.h2
|
||||
[ Heading.plaintext "Row Themes"
|
||||
, Heading.css [ Css.margin2 Spacing.verticalSpacerPx Css.zero ]
|
||||
]
|
||||
, div
|
||||
[ css
|
||||
[ Css.displayFlex
|
||||
, Css.property "gap" "20px"
|
||||
, withMedia [ mobile ] [ Css.flexWrap Css.wrap ]
|
||||
]
|
||||
]
|
||||
[ Outline.view
|
||||
[ Outline.row
|
||||
{ title = Just "Outline.view"
|
||||
, content = text "Regular outlines support custom row themes (like this node's theme) as well as predefined themes."
|
||||
, palette =
|
||||
{ border = Colors.azure
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.gray96
|
||||
}
|
||||
, rows =
|
||||
List.map
|
||||
(\( themeName, theme ) ->
|
||||
Outline.row
|
||||
{ title = Just themeName
|
||||
, content = text ""
|
||||
, palette = theme
|
||||
, rows = []
|
||||
}
|
||||
)
|
||||
allRowThemes
|
||||
}
|
||||
]
|
||||
, Outline.viewKeyed
|
||||
[ Outline.keyedRow "base"
|
||||
{ title = Just "Outline.viewKeyed"
|
||||
, content = text "Keyed outlines support custom row themes (like this node's theme) as well as predefined themes."
|
||||
, palette =
|
||||
{ border = Colors.azure
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.gray96
|
||||
}
|
||||
, rows =
|
||||
List.map
|
||||
(\( themeName, theme ) ->
|
||||
Outline.keyedRow ("row-" ++ themeName)
|
||||
{ title = Just themeName
|
||||
, content = text ""
|
||||
, palette = theme
|
||||
, rows = []
|
||||
}
|
||||
)
|
||||
allRowThemes
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
plainRows : List (Outline msg)
|
||||
plainRows =
|
||||
[ Outline.row
|
||||
{ title = Just "Node 2"
|
||||
, content = text ""
|
||||
, palette = Outline.cornflower
|
||||
, rows = []
|
||||
}
|
||||
, Outline.row
|
||||
{ title = Just "Node 3"
|
||||
, content = text ""
|
||||
, palette = Outline.cornflower
|
||||
, rows = []
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
keyedRows : List (KeyedOutline msg)
|
||||
keyedRows =
|
||||
[ Outline.keyedRow "node-2"
|
||||
{ title = Just "Node 2"
|
||||
, content = text ""
|
||||
, palette = Outline.cornflower
|
||||
, rows = []
|
||||
}
|
||||
, Outline.keyedRow "node-3"
|
||||
{ title = Just "Node 3"
|
||||
, content = text ""
|
||||
, palette = Outline.cornflower
|
||||
, rows = []
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
preview : Html msg
|
||||
preview =
|
||||
Svg.svg
|
||||
[ SvgAttrs.viewBox "5 0 90 90"
|
||||
, SvgAttrs.width "100%"
|
||||
, SvgAttrs.height "100%"
|
||||
]
|
||||
[ -- Connecting lines
|
||||
Svg.path
|
||||
[ SvgAttrs.d "M14 41, 14 58 Q14 60 16 60 L 20 60"
|
||||
, SvgAttrs.stroke Colors.purple.value
|
||||
, SvgAttrs.fill "none"
|
||||
]
|
||||
[]
|
||||
, Svg.path
|
||||
[ SvgAttrs.d "M14 29, 14 40 Q14 42 16 42 L 20 42"
|
||||
, SvgAttrs.stroke Colors.greenDarkest.value
|
||||
, SvgAttrs.fill "none"
|
||||
]
|
||||
[]
|
||||
|
||||
-- Azure node
|
||||
, -- white box
|
||||
Svg.rect
|
||||
[ SvgAttrs.x "10"
|
||||
, SvgAttrs.y "13"
|
||||
, SvgAttrs.width "80"
|
||||
, SvgAttrs.height "16"
|
||||
, SvgAttrs.rx "2"
|
||||
, SvgAttrs.fill Colors.white.value
|
||||
, SvgAttrs.stroke Colors.azure.value
|
||||
, SvgAttrs.strokeWidth "0.5"
|
||||
]
|
||||
[]
|
||||
, -- azure label
|
||||
Svg.rect
|
||||
[ SvgAttrs.x "5"
|
||||
, SvgAttrs.y "10"
|
||||
, SvgAttrs.width "16"
|
||||
, SvgAttrs.height "6"
|
||||
, SvgAttrs.rx "4"
|
||||
, SvgAttrs.fill Colors.azure.value
|
||||
]
|
||||
[]
|
||||
|
||||
-- Green node
|
||||
, -- white box
|
||||
Svg.rect
|
||||
[ SvgAttrs.x "20"
|
||||
, SvgAttrs.y "35"
|
||||
, SvgAttrs.width "70"
|
||||
, SvgAttrs.height "12"
|
||||
, SvgAttrs.rx "2"
|
||||
, SvgAttrs.fill Colors.white.value
|
||||
, SvgAttrs.stroke Colors.greenDarkest.value
|
||||
, SvgAttrs.strokeWidth "0.5"
|
||||
]
|
||||
[]
|
||||
, -- green label
|
||||
Svg.rect
|
||||
[ SvgAttrs.x "16"
|
||||
, SvgAttrs.y "32"
|
||||
, SvgAttrs.width "14"
|
||||
, SvgAttrs.height "6"
|
||||
, SvgAttrs.rx "4"
|
||||
, SvgAttrs.fill Colors.greenDarkest.value
|
||||
]
|
||||
[]
|
||||
|
||||
-- Purple node
|
||||
, -- white box
|
||||
Svg.rect
|
||||
[ SvgAttrs.x "20"
|
||||
, SvgAttrs.y "54"
|
||||
, SvgAttrs.width "70"
|
||||
, SvgAttrs.height "12"
|
||||
, SvgAttrs.rx "2"
|
||||
, SvgAttrs.fill Colors.white.value
|
||||
, SvgAttrs.stroke Colors.purple.value
|
||||
, SvgAttrs.strokeWidth "0.5"
|
||||
]
|
||||
[]
|
||||
, -- purple label
|
||||
Svg.rect
|
||||
[ SvgAttrs.x "16"
|
||||
, SvgAttrs.y "50"
|
||||
, SvgAttrs.width "17"
|
||||
, SvgAttrs.height "6"
|
||||
, SvgAttrs.rx "4"
|
||||
, SvgAttrs.fill Colors.purple.value
|
||||
]
|
||||
[]
|
||||
]
|
||||
|
||||
|
||||
allRowThemes : List ( String, RowTheme )
|
||||
allRowThemes =
|
||||
[ ( "purpleBordered", Outline.purpleBordered )
|
||||
, ( "greenBordered", Outline.greenBordered )
|
||||
, ( "blueDashBordered", Outline.blueDashBordered )
|
||||
, ( "red", Outline.red )
|
||||
, ( "green", Outline.green )
|
||||
, ( "aqua", Outline.aqua )
|
||||
, ( "turquoise", Outline.turquoise )
|
||||
, ( "cornflower", Outline.cornflower )
|
||||
, ( "blue", Outline.blue )
|
||||
, ( "darkBlue", Outline.darkBlue )
|
||||
, ( "purple", Outline.purple )
|
||||
, ( "darkGray", Outline.darkGray )
|
||||
, ( "gray", Outline.gray )
|
||||
, ( "white", Outline.white )
|
||||
]
|
||||
|
||||
|
||||
borderColorList : List ( String, Color )
|
||||
borderColorList =
|
||||
[ ( "azure", Colors.azure )
|
||||
, ( "cornflower", Colors.cornflower )
|
||||
, ( "gray45", Colors.gray45 )
|
||||
, ( "gray75", Colors.gray75 )
|
||||
, ( "green", Colors.green )
|
||||
, ( "navy", Colors.navy )
|
||||
, ( "purple", Colors.purple )
|
||||
, ( "red", Colors.red )
|
||||
, ( "turquoise", Colors.turquoise )
|
||||
]
|
||||
|
||||
|
||||
backgroundColorList : List ( String, Color )
|
||||
backgroundColorList =
|
||||
[ ( "gray96", Colors.gray96 )
|
||||
, ( "aquaLight", Colors.aquaLight )
|
||||
, ( "cornflowerLight", Colors.cornflowerLight )
|
||||
, ( "frost", Colors.frost )
|
||||
, ( "greenLightest", Colors.greenLightest )
|
||||
, ( "purpleLight", Colors.purpleLight )
|
||||
, ( "redLight", Colors.redLight )
|
||||
, ( "turquoiseLight", Colors.turquoiseLight )
|
||||
, ( "white", Colors.white )
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias State =
|
||||
{ control : Control Settings
|
||||
}
|
||||
|
||||
|
||||
type alias Settings =
|
||||
{ title : Maybe String
|
||||
, content : String
|
||||
, palette : ( String, RowTheme )
|
||||
, type_ : RowType
|
||||
}
|
||||
|
||||
|
||||
init : State
|
||||
init =
|
||||
{ control =
|
||||
Control.record Settings
|
||||
|> Control.field "title" (Control.maybe True (Control.string "Title"))
|
||||
|> Control.field "content" (Control.string "")
|
||||
|> Control.field "palette"
|
||||
(Control.choice
|
||||
(List.map
|
||||
(\( name, value ) ->
|
||||
( name, Control.value ( Code.fromModule moduleName "." ++ name, value ) )
|
||||
)
|
||||
allRowThemes
|
||||
++ [ ( "custom", customRowTheme ) ]
|
||||
)
|
||||
)
|
||||
|> Control.field "type"
|
||||
(Control.choice
|
||||
[ ( "plain", Control.value Plain )
|
||||
, ( "keyed", Control.value Keyed )
|
||||
, ( "keyed with extra content"
|
||||
, [ "Extra content!"
|
||||
, "This content requires the height of the connecting arrow to increase. Do NOT use vertical margin on this element."
|
||||
, "Extra content is used for selecting which drafts to compare on the results views for Topic Sentence Peer Review results."
|
||||
, "Check it out!"
|
||||
]
|
||||
|> String.join "\n\n"
|
||||
|> Control.stringTextarea
|
||||
|> Control.map KeyedWithExtraContent
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
type RowType
|
||||
= Plain
|
||||
| Keyed
|
||||
| KeyedWithExtraContent String
|
||||
|
||||
|
||||
customRowTheme : Control ( String, RowTheme )
|
||||
customRowTheme =
|
||||
Control.record
|
||||
(\( a1, a2 ) ( b1, b2 ) ( c1, c2 ) ->
|
||||
( Code.recordMultiline
|
||||
[ ( "border", a1 )
|
||||
, ( "borderStyle", b1 )
|
||||
, ( "background", c1 )
|
||||
]
|
||||
3
|
||||
, RowTheme a2 b2 c2
|
||||
)
|
||||
)
|
||||
|> Control.field "border" (CommonControls.choice "Colors" borderColorList)
|
||||
|> Control.field "borderStyle"
|
||||
(Control.choice
|
||||
[ ( "none", Control.value ( "Css.batch []", Css.batch [] ) )
|
||||
, ( "1px solid"
|
||||
, Control.value
|
||||
( "Css.batch [ Css.borderWidth (Css.px 1), Css.borderStyle Css.solid ]"
|
||||
, Css.batch [ Css.borderWidth (Css.px 1), Css.borderStyle Css.solid ]
|
||||
)
|
||||
)
|
||||
, ( "1px dashed"
|
||||
, Control.value
|
||||
( "Css.batch [ Css.borderWidth (Css.px 1), Css.borderStyle Css.dashed ]"
|
||||
, Css.batch [ Css.borderWidth (Css.px 1), Css.borderStyle Css.dashed ]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
|> Control.field "background" (CommonControls.choice "Colors" backgroundColorList)
|
||||
|
||||
|
||||
{-| -}
|
||||
type Msg
|
||||
= UpdateControl (Control Settings)
|
||||
|
||||
|
||||
update : Msg -> State -> ( State, Cmd Msg )
|
||||
update msg state =
|
||||
case msg of
|
||||
UpdateControl settings ->
|
||||
( { state | control = settings }, Cmd.none )
|
1
elm.json
1
elm.json
@ -59,6 +59,7 @@
|
||||
"Nri.Ui.Message.V4",
|
||||
"Nri.Ui.Modal.V11",
|
||||
"Nri.Ui.Modal.V12",
|
||||
"Nri.Ui.Outline.V1",
|
||||
"Nri.Ui.Page.V3",
|
||||
"Nri.Ui.Pagination.V1",
|
||||
"Nri.Ui.Palette.V1",
|
||||
|
@ -232,6 +232,7 @@ describe("UI tests", function () {
|
||||
const skippedRules = {
|
||||
// Loading's color contrast check seems to change behavior depending on whether Percy snapshots are taken or not
|
||||
Loading: ["color-contrast"],
|
||||
Outline: ["color-contrast"],
|
||||
RadioButton: ["duplicate-id"],
|
||||
};
|
||||
|
||||
|
633
src/Nri/Ui/Outline/V1.elm
Normal file
633
src/Nri/Ui/Outline/V1.elm
Normal file
@ -0,0 +1,633 @@
|
||||
module Nri.Ui.Outline.V1 exposing
|
||||
( Outline, view, row
|
||||
, KeyedOutline, viewKeyed, keyedRow, keyedRowWithExtraContent
|
||||
, RowTheme
|
||||
, white, gray, darkGray, blue, darkBlue, purple, turquoise, green, red, aqua, cornflower
|
||||
, blueDashBordered
|
||||
, purpleBordered, greenBordered
|
||||
)
|
||||
|
||||
{-| A nestable layout that can be themed.
|
||||
|
||||
@docs Outline, view, row
|
||||
|
||||
When you're adding or removing elements, use KeyedOutline and corresponding helpers:
|
||||
|
||||
@docs KeyedOutline, viewKeyed, keyedRow, keyedRowWithExtraContent
|
||||
|
||||
|
||||
## Predefined color palettes for use with Outlines and KeyedOutlines.
|
||||
|
||||
@docs RowTheme
|
||||
@docs white, gray, darkGray, blue, darkBlue, purple, turquoise, green, red, aqua, cornflower
|
||||
@docs blueDashBordered
|
||||
@docs purpleBordered, greenBordered
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (..)
|
||||
import Html.Styled exposing (..)
|
||||
import Html.Styled.Attributes exposing (css)
|
||||
import Html.Styled.Keyed
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.Html.Attributes.V2 as Attributes
|
||||
import Nri.Ui.Html.V3 exposing (viewJust)
|
||||
|
||||
|
||||
{-| -}
|
||||
type Outline msg
|
||||
= Outline
|
||||
{ rows : List (Outline msg)
|
||||
, title : Maybe String
|
||||
, content : Html msg
|
||||
, palette : RowTheme
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
import Html.Styled exposing (..)
|
||||
import Nri.Ui.Outline.V1 as Outline
|
||||
|
||||
main : Html msg
|
||||
main =
|
||||
Outline.view []
|
||||
|
||||
-}
|
||||
view : List (Outline msg) -> Html msg
|
||||
view rows =
|
||||
case rows of
|
||||
[] ->
|
||||
text ""
|
||||
|
||||
_ ->
|
||||
view_ rows
|
||||
|
||||
|
||||
view_ : List (Outline msg) -> Html msg
|
||||
view_ rows =
|
||||
Html.Styled.ul
|
||||
[ Html.Styled.Attributes.css
|
||||
[ Css.listStyle Css.none
|
||||
, Css.margin4 (Css.px 10) Css.zero Css.zero Css.zero
|
||||
, Css.padding Css.zero
|
||||
]
|
||||
]
|
||||
(viewRows Root rows)
|
||||
|
||||
|
||||
viewRows : Hierarchy -> List (Outline msg) -> List (Html msg)
|
||||
viewRows hierarchy rows =
|
||||
let
|
||||
orderedNodeColors =
|
||||
(List.map (\(Outline { palette }) -> Just palette.border) rows ++ [ Nothing ])
|
||||
|> List.drop 1
|
||||
in
|
||||
List.map2 (viewRow hierarchy) orderedNodeColors rows
|
||||
|
||||
|
||||
viewRow : Hierarchy -> Maybe Css.Color -> Outline msg -> Html msg
|
||||
viewRow hierarchy nextNodeColor (Outline config) =
|
||||
utilViewRow hierarchy
|
||||
nextNodeColor
|
||||
{ rows = config.rows
|
||||
, title = config.title
|
||||
, content = config.content
|
||||
, palette = config.palette
|
||||
, extraContent = Nothing
|
||||
}
|
||||
(\(Outline { palette }) -> palette.border)
|
||||
(Html.Styled.ul
|
||||
[ css
|
||||
[ Css.marginLeft (Css.px 25)
|
||||
, Css.paddingLeft (Css.px 25)
|
||||
, Css.paddingTop (Css.px 25)
|
||||
, Css.listStyleType Css.none
|
||||
]
|
||||
]
|
||||
(viewRows Child config.rows)
|
||||
)
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
import Html.Styled exposing (..)
|
||||
import Nri.Ui.Outline.V1 as Outline exposing (Outline)
|
||||
|
||||
myRow : Outline msg
|
||||
myRow =
|
||||
Outline.row
|
||||
{ title = Just "My node"
|
||||
, content = text "This is my content"
|
||||
, palette = RowTheme.red
|
||||
, rows = []
|
||||
}
|
||||
|
||||
-}
|
||||
row :
|
||||
{ title : Maybe String
|
||||
, content : Html msg
|
||||
, palette : RowTheme
|
||||
, rows : List (Outline msg)
|
||||
}
|
||||
-> Outline msg
|
||||
row config =
|
||||
Outline config
|
||||
|
||||
|
||||
|
||||
-- KEYED OUTLINE
|
||||
|
||||
|
||||
{-| Aliased strictly for exporting
|
||||
-}
|
||||
type KeyedOutline msg
|
||||
= KeyedOutline
|
||||
String
|
||||
{ extraContent : Maybe (Html msg)
|
||||
, rows : List (KeyedOutline msg)
|
||||
, title : Maybe String
|
||||
, content : Html msg
|
||||
, palette : RowTheme
|
||||
}
|
||||
|
||||
|
||||
{-| The row view.
|
||||
|
||||
import Html.Styled exposing (..)
|
||||
import Nri.Ui.Outline.V1 as Outline
|
||||
|
||||
main : Html msg
|
||||
main =
|
||||
Outline.viewKeyed [{- Rows go here -}]
|
||||
|
||||
-}
|
||||
viewKeyed : List (KeyedOutline msg) -> Html msg
|
||||
viewKeyed rows =
|
||||
case rows of
|
||||
[] ->
|
||||
text ""
|
||||
|
||||
_ ->
|
||||
viewKeyed_ rows
|
||||
|
||||
|
||||
viewKeyed_ : List (KeyedOutline msg) -> Html msg
|
||||
viewKeyed_ rows =
|
||||
Html.Styled.Keyed.node "ul"
|
||||
[ Html.Styled.Attributes.css
|
||||
[ Css.listStyle Css.none
|
||||
, Css.margin4 (Css.px 10) Css.zero Css.zero Css.zero
|
||||
, Css.padding Css.zero
|
||||
]
|
||||
]
|
||||
(viewKeyedRows Root rows)
|
||||
|
||||
|
||||
viewKeyedRows : Hierarchy -> List (KeyedOutline msg) -> List ( String, Html msg )
|
||||
viewKeyedRows hierarchy rows =
|
||||
let
|
||||
orderedNodeColors =
|
||||
(List.map (\(KeyedOutline _ { palette }) -> Just palette.border) rows ++ [ Nothing ])
|
||||
|> List.drop 1
|
||||
in
|
||||
List.map2 (viewKeyedRow hierarchy) orderedNodeColors rows
|
||||
|
||||
|
||||
viewKeyedRow : Hierarchy -> Maybe Css.Color -> KeyedOutline msg -> ( String, Html msg )
|
||||
viewKeyedRow hierarchy nextNodeColor (KeyedOutline key config) =
|
||||
( key
|
||||
, utilViewRow hierarchy
|
||||
nextNodeColor
|
||||
config
|
||||
(\(KeyedOutline _ { palette }) -> palette.border)
|
||||
(Html.Styled.Keyed.node "ul"
|
||||
[ css
|
||||
[ Css.marginLeft (Css.px 25)
|
||||
, Css.paddingLeft (Css.px 25)
|
||||
, Css.paddingTop (Css.px 25)
|
||||
, Css.listStyleType Css.none
|
||||
]
|
||||
]
|
||||
(viewKeyedRows Child config.rows)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
{-| Render an unstyled row with only the outline styles.
|
||||
|
||||
import Html.Styled exposing (..)
|
||||
import Nri.Ui.Outline.V1 as Outline
|
||||
|
||||
main : Html msg
|
||||
main =
|
||||
Outline.viewKeyed []
|
||||
[ Outline.keyedRow someKey
|
||||
{ title = Just "My outline node"
|
||||
, content = text "This is my content"
|
||||
, palette = RowTheme.red
|
||||
, rows = []
|
||||
}
|
||||
]
|
||||
|
||||
-}
|
||||
keyedRow :
|
||||
String
|
||||
->
|
||||
{ title : Maybe String
|
||||
, content : Html msg
|
||||
, palette : RowTheme
|
||||
, rows : List (KeyedOutline msg)
|
||||
}
|
||||
-> KeyedOutline msg
|
||||
keyedRow key config =
|
||||
KeyedOutline key
|
||||
{ title = config.title
|
||||
, content = config.content
|
||||
, palette = config.palette
|
||||
, rows = config.rows
|
||||
, extraContent = Nothing
|
||||
}
|
||||
|
||||
|
||||
{-| Render a row with extra content. This row cannot have child rows.
|
||||
|
||||
import Html.Styled exposing (..)
|
||||
import Nri.Ui.Outline.V1 as Outline
|
||||
|
||||
main : Html msg
|
||||
main =
|
||||
Outline.view
|
||||
[ Outline.keyedRowWithExtraContent someKey
|
||||
{ title = Just "My outline node"
|
||||
, content = text "This is my content"
|
||||
, palette = RowTheme.red
|
||||
, extraContent = text "My extra content"
|
||||
, rows = []
|
||||
}
|
||||
]
|
||||
|
||||
-}
|
||||
keyedRowWithExtraContent :
|
||||
String
|
||||
->
|
||||
{ rows : List (KeyedOutline msg)
|
||||
, extraContent : Html msg
|
||||
, title : Maybe String
|
||||
, content : Html msg
|
||||
, palette : RowTheme
|
||||
}
|
||||
-> KeyedOutline msg
|
||||
keyedRowWithExtraContent key config =
|
||||
KeyedOutline key
|
||||
{ title = config.title
|
||||
, content = config.content
|
||||
, palette = config.palette
|
||||
, rows = config.rows
|
||||
, extraContent = Just config.extraContent
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- THEMES
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias RowTheme =
|
||||
{ border : Color
|
||||
, borderStyle : Style
|
||||
, background : Color
|
||||
}
|
||||
|
||||
|
||||
{-| Aqua palette
|
||||
-}
|
||||
aqua : RowTheme
|
||||
aqua =
|
||||
{ border = Colors.aquaDark
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.aquaLight
|
||||
}
|
||||
|
||||
|
||||
{-| Dark Gray palette
|
||||
-}
|
||||
darkGray : RowTheme
|
||||
darkGray =
|
||||
{ border = Colors.gray45
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.gray96
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
gray : RowTheme
|
||||
gray =
|
||||
{ border = Colors.gray45
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.white
|
||||
}
|
||||
|
||||
|
||||
{-| Blue palette
|
||||
-}
|
||||
blue : RowTheme
|
||||
blue =
|
||||
{ border = Colors.azure
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.frost
|
||||
}
|
||||
|
||||
|
||||
{-| Blue palette
|
||||
-}
|
||||
blueDashBordered : RowTheme
|
||||
blueDashBordered =
|
||||
{ border = Colors.azure
|
||||
, borderStyle = Css.batch [ Css.borderWidth (Css.px 1), Css.borderStyle Css.dashed ]
|
||||
, background = Colors.frost
|
||||
}
|
||||
|
||||
|
||||
{-| Dark blue palette
|
||||
-}
|
||||
darkBlue : RowTheme
|
||||
darkBlue =
|
||||
{ border = Colors.navy
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.frost
|
||||
}
|
||||
|
||||
|
||||
{-| Purple palette with a purple border instead of a purple background color
|
||||
-}
|
||||
purple : RowTheme
|
||||
purple =
|
||||
{ border = Colors.purple
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.purpleLight
|
||||
}
|
||||
|
||||
|
||||
{-| Purple palette with a purple border instead of a purple background color
|
||||
-}
|
||||
purpleBordered : RowTheme
|
||||
purpleBordered =
|
||||
{ border = Colors.purple
|
||||
, borderStyle = Css.batch [ Css.borderWidth (Css.px 1), Css.borderStyle Css.solid ]
|
||||
, background = Colors.white
|
||||
}
|
||||
|
||||
|
||||
{-| Turquoise palette
|
||||
-}
|
||||
turquoise : RowTheme
|
||||
turquoise =
|
||||
{ border = Colors.turquoiseDark
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.turquoiseLight
|
||||
}
|
||||
|
||||
|
||||
{-| Green palette
|
||||
-}
|
||||
green : RowTheme
|
||||
green =
|
||||
{ border = Colors.greenDarkest
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.greenLightest
|
||||
}
|
||||
|
||||
|
||||
{-| Green palette with a green border instead of a green background color
|
||||
-}
|
||||
greenBordered : RowTheme
|
||||
greenBordered =
|
||||
{ border = Colors.greenDarkest
|
||||
, borderStyle = Css.batch [ Css.borderWidth (Css.px 1), Css.borderStyle Css.solid ]
|
||||
, background = Colors.white
|
||||
}
|
||||
|
||||
|
||||
{-| Red palette
|
||||
-}
|
||||
red : RowTheme
|
||||
red =
|
||||
{ border = Colors.red
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.redLight
|
||||
}
|
||||
|
||||
|
||||
{-| White palette (borders are blue)
|
||||
-}
|
||||
white : RowTheme
|
||||
white =
|
||||
{ border = Colors.navy
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.white
|
||||
}
|
||||
|
||||
|
||||
{-| Cornflower palette
|
||||
-}
|
||||
cornflower : RowTheme
|
||||
cornflower =
|
||||
{ border = Colors.cornflowerDark
|
||||
, borderStyle = Css.batch []
|
||||
, background = Colors.cornflowerLight
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- UTILS
|
||||
|
||||
|
||||
wrapViewWithTitleBubble :
|
||||
{ title : String
|
||||
, content : Html msg
|
||||
, palette : RowTheme
|
||||
}
|
||||
-> Html msg
|
||||
wrapViewWithTitleBubble config =
|
||||
let
|
||||
kebabTitle =
|
||||
String.replace " " "-" (String.toLower config.title)
|
||||
in
|
||||
Html.Styled.div []
|
||||
[ Html.Styled.div
|
||||
[ Html.Styled.Attributes.attribute "data-nri-description" "outline-title"
|
||||
, css
|
||||
[ Fonts.baseFont
|
||||
, borderRadius (px 18)
|
||||
, color Colors.white
|
||||
, display inlineBlock
|
||||
, fontSize (px 15)
|
||||
, fontWeight bold
|
||||
, height (px 35)
|
||||
, lineHeight (px 35)
|
||||
, left (px -10)
|
||||
, padding2 zero (px 15)
|
||||
, position absolute
|
||||
, top (px -15)
|
||||
, backgroundColor config.palette.border
|
||||
]
|
||||
]
|
||||
[ Html.Styled.text config.title ]
|
||||
, Html.Styled.div
|
||||
[ Attributes.testId (kebabTitle ++ "-text")
|
||||
, css
|
||||
[ borderRadius (px 8)
|
||||
, color Colors.gray20
|
||||
, fontSize (px 18)
|
||||
, Fonts.quizFont
|
||||
, padding3 (px 30) (px 15) (px 15)
|
||||
, lineHeight (px 30)
|
||||
, backgroundColor config.palette.background
|
||||
, config.palette.borderStyle
|
||||
, borderColor config.palette.border
|
||||
, after [ borderColor config.palette.border ]
|
||||
]
|
||||
]
|
||||
[ config.content ]
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
utilViewRow :
|
||||
Hierarchy
|
||||
-> Maybe Color
|
||||
->
|
||||
{ title : Maybe String
|
||||
, content : Html msg
|
||||
, extraContent : Maybe (Html msg)
|
||||
, palette : RowTheme
|
||||
, rows : List outline
|
||||
}
|
||||
-> (outline -> Color)
|
||||
-> Html msg
|
||||
-> Html msg
|
||||
utilViewRow hierarchy nextNodeColor config getOutlineBorder children =
|
||||
let
|
||||
rowAttrs =
|
||||
[ Html.Styled.Attributes.attribute "data-nri-description" "outline-row"
|
||||
, css
|
||||
[ paddingBottom (px 25)
|
||||
, position relative
|
||||
, lastChild [ paddingBottom zero ]
|
||||
, case hierarchy of
|
||||
Child ->
|
||||
verticalChildConnector config.palette.border nextNodeColor
|
||||
|
||||
Root ->
|
||||
Css.batch []
|
||||
]
|
||||
]
|
||||
in
|
||||
Html.Styled.li
|
||||
rowAttrs
|
||||
[ Html.Styled.div
|
||||
[ css
|
||||
[ position relative
|
||||
, case hierarchy of
|
||||
Child ->
|
||||
horizontalChildConnector config.palette.border
|
||||
|
||||
Root ->
|
||||
Css.batch []
|
||||
]
|
||||
]
|
||||
[ case config.title of
|
||||
Just title ->
|
||||
wrapViewWithTitleBubble
|
||||
{ title = title
|
||||
, content = config.content
|
||||
, palette = config.palette
|
||||
}
|
||||
|
||||
Nothing ->
|
||||
config.content
|
||||
, viewJust (viewExtraContent (Maybe.map getOutlineBorder (List.head config.rows))) config.extraContent
|
||||
]
|
||||
, if List.isEmpty config.rows then
|
||||
text ""
|
||||
|
||||
else
|
||||
children
|
||||
]
|
||||
|
||||
|
||||
verticalChildConnector : Color -> Maybe Color -> Style
|
||||
verticalChildConnector paletteBorder nextNodeColor =
|
||||
Css.batch
|
||||
[ after
|
||||
[ property "content" "''"
|
||||
, position absolute
|
||||
, top (px -25)
|
||||
, left (px -18)
|
||||
, width (px 18)
|
||||
, borderLeft3 (px 1) solid Colors.gray75
|
||||
, property "height" "calc(16px)"
|
||||
, borderColor paletteBorder
|
||||
]
|
||||
, before
|
||||
(case nextNodeColor of
|
||||
Just border ->
|
||||
[ property "content" "''"
|
||||
, position absolute
|
||||
, left (px -18)
|
||||
, width (px 18)
|
||||
, borderLeft3 (px 1) solid Colors.gray75
|
||||
, property "height" "calc(100%)"
|
||||
, borderColor border
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
)
|
||||
, lastChild
|
||||
[ after [ borderLeftWidth zero ]
|
||||
, before [ borderLeftWidth zero ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
horizontalChildConnector : Color -> Style
|
||||
horizontalChildConnector paletteBorder =
|
||||
after
|
||||
[ property "content" "''"
|
||||
, height (pct 80)
|
||||
, width (px 18)
|
||||
, borderBottom3 (px 1) solid Colors.gray75
|
||||
, borderLeft3 (px 1) solid Colors.gray75
|
||||
, left (px -18)
|
||||
, borderRadius4 zero zero zero (px 4)
|
||||
, top (px -25)
|
||||
, position absolute
|
||||
, maxHeight (px 60)
|
||||
, borderColor paletteBorder
|
||||
]
|
||||
|
||||
|
||||
viewExtraContent : Maybe Color -> Html msg -> Html msg
|
||||
viewExtraContent border content =
|
||||
div
|
||||
[ css
|
||||
[ marginLeft (px 32)
|
||||
, Maybe.map (borderLeft3 (px 1) solid) border
|
||||
|> Maybe.withDefault (Css.batch [])
|
||||
, paddingLeft (px 15)
|
||||
]
|
||||
]
|
||||
[ content ]
|
||||
|
||||
|
||||
|
||||
-- Types
|
||||
|
||||
|
||||
{-| -}
|
||||
type Hierarchy
|
||||
= Root
|
||||
| Child
|
63
tests/Spec/Nri/Ui/Outline.elm
Normal file
63
tests/Spec/Nri/Ui/Outline.elm
Normal file
@ -0,0 +1,63 @@
|
||||
module Spec.Nri.Ui.Outline exposing (spec)
|
||||
|
||||
import Expect exposing (Expectation)
|
||||
import Html.Styled as Html exposing (Html, toUnstyled)
|
||||
import Nri.Ui.Outline.V1 as Outline
|
||||
import Test exposing (..)
|
||||
import Test.Html.Query as Query
|
||||
import Test.Html.Selector exposing (..)
|
||||
|
||||
|
||||
spec : Test
|
||||
spec =
|
||||
describe "Nri.Ui.Outline"
|
||||
[ test "view without rows does not render anything" <|
|
||||
\() ->
|
||||
Outline.view []
|
||||
|> hasNoUl
|
||||
, test "viewKeyed without rows does not render anything" <|
|
||||
\() ->
|
||||
Outline.viewKeyed []
|
||||
|> hasNoUl
|
||||
, test "view with rows renders ul with lis" <|
|
||||
\() ->
|
||||
Outline.view
|
||||
[ Outline.row
|
||||
{ title = Nothing
|
||||
, content = Html.text ""
|
||||
, palette = Outline.gray
|
||||
, rows = []
|
||||
}
|
||||
]
|
||||
|> hasOneUlWithOneLi
|
||||
, test "viewKeyed with rows renders ul with lis" <|
|
||||
\() ->
|
||||
Outline.viewKeyed
|
||||
[ Outline.keyedRow "key"
|
||||
{ title = Nothing
|
||||
, content = Html.text ""
|
||||
, palette = Outline.gray
|
||||
, rows = []
|
||||
}
|
||||
]
|
||||
|> hasOneUlWithOneLi
|
||||
]
|
||||
|
||||
|
||||
hasNoUl : Html msg -> Expectation
|
||||
hasNoUl content =
|
||||
Html.div [] [ content ]
|
||||
|> toUnstyled
|
||||
|> Query.fromHtml
|
||||
|> Query.hasNot [ tag "ul" ]
|
||||
|
||||
|
||||
hasOneUlWithOneLi : Html msg -> Expectation
|
||||
hasOneUlWithOneLi content =
|
||||
Html.div [] [ content ]
|
||||
|> toUnstyled
|
||||
|> Query.fromHtml
|
||||
|> Expect.all
|
||||
[ Query.count (Expect.equal 1) << Query.findAll [ tag "ul" ]
|
||||
, Query.count (Expect.equal 1) << Query.findAll [ tag "li" ]
|
||||
]
|
@ -55,6 +55,7 @@
|
||||
"Nri.Ui.Message.V4",
|
||||
"Nri.Ui.Modal.V11",
|
||||
"Nri.Ui.Modal.V12",
|
||||
"Nri.Ui.Outline.V1",
|
||||
"Nri.Ui.Page.V3",
|
||||
"Nri.Ui.Pagination.V1",
|
||||
"Nri.Ui.Palette.V1",
|
||||
|
Loading…
Reference in New Issue
Block a user