Merge pull request #794 from NoRedInk/hack-day/tessa/sidebar

Adds general sidenav component
This commit is contained in:
Tessa 2021-12-09 11:02:21 -08:00 committed by GitHub
commit 524dd55301
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 558 additions and 125 deletions

View File

@ -47,6 +47,7 @@
"Nri.Ui.RadioButton.V3",
"Nri.Ui.SegmentedControl.V14",
"Nri.Ui.Select.V8",
"Nri.Ui.SideNav.V1",
"Nri.Ui.Slide.V1",
"Nri.Ui.SlideModal.V2",
"Nri.Ui.SortableTable.V2",

View File

@ -23,9 +23,10 @@ import Nri.Ui.Html.Attributes.V2 as AttributeExtras exposing (targetBlank)
{-| -}
type alias ClickableAttributes msg =
type alias ClickableAttributes route msg =
{ linkType : Link
, url : String
, url : Maybe route
, urlString : Maybe String
, onClick : Maybe msg
}
@ -40,58 +41,67 @@ type Link
{-| -}
init : ClickableAttributes msg
init : ClickableAttributes route msg
init =
{ linkType = Default
, url = "#"
, url = Nothing
, urlString = Nothing
, onClick = Nothing
}
{-| -}
onClick : msg -> ClickableAttributes msg -> ClickableAttributes msg
onClick : msg -> ClickableAttributes route msg -> ClickableAttributes route msg
onClick msg clickableAttributes =
{ clickableAttributes | onClick = Just msg }
{-| -}
href : String -> ClickableAttributes msg -> ClickableAttributes msg
href : route -> ClickableAttributes route msg -> ClickableAttributes route msg
href url clickableAttributes =
{ clickableAttributes | url = url }
{ clickableAttributes | url = Just url }
{-| -}
linkSpa : String -> ClickableAttributes msg -> ClickableAttributes msg
linkSpa : route -> ClickableAttributes route msg -> ClickableAttributes route msg
linkSpa url clickableAttributes =
{ clickableAttributes | linkType = SinglePageApp, url = url }
{ clickableAttributes | linkType = SinglePageApp, url = Just url }
{-| -}
linkWithMethod : { method : String, url : String } -> ClickableAttributes msg -> ClickableAttributes msg
linkWithMethod : { method : String, url : route } -> ClickableAttributes route msg -> ClickableAttributes route msg
linkWithMethod { method, url } clickableAttributes =
{ clickableAttributes | linkType = WithMethod method, url = url }
{ clickableAttributes | linkType = WithMethod method, url = Just url }
{-| -}
linkWithTracking : { track : msg, url : String } -> ClickableAttributes msg -> ClickableAttributes msg
linkWithTracking { track, url } _ =
{ linkType = WithTracking, url = url, onClick = Just track }
linkWithTracking : { track : msg, url : route } -> ClickableAttributes route msg -> ClickableAttributes route msg
linkWithTracking { track, url } clickableAttributes =
{ clickableAttributes
| linkType = WithTracking
, url = Just url
, onClick = Just track
}
{-| -}
linkExternal : String -> ClickableAttributes msg -> ClickableAttributes msg
linkExternal : String -> ClickableAttributes route msg -> ClickableAttributes route msg
linkExternal url clickableAttributes =
{ clickableAttributes | linkType = External, url = url }
{ clickableAttributes | linkType = External, urlString = Just url }
{-| -}
linkExternalWithTracking : { track : msg, url : String } -> ClickableAttributes msg -> ClickableAttributes msg
linkExternalWithTracking { track, url } _ =
{ linkType = ExternalWithTracking, url = url, onClick = Just track }
linkExternalWithTracking : { track : msg, url : String } -> ClickableAttributes route msg -> ClickableAttributes route msg
linkExternalWithTracking { track, url } clickableAttributes =
{ clickableAttributes
| linkType = ExternalWithTracking
, urlString = Just url
, onClick = Just track
}
{-| -}
toButtonAttributes : ClickableAttributes msg -> List (Attribute msg)
toButtonAttributes : ClickableAttributes route msg -> List (Attribute msg)
toButtonAttributes clickableAttributes =
case clickableAttributes.onClick of
Just handler ->
@ -102,12 +112,24 @@ toButtonAttributes clickableAttributes =
{-| -}
toLinkAttributes : ClickableAttributes msg -> ( String, List (Attribute msg) )
toLinkAttributes clickableAttributes =
toLinkAttributes : (route -> String) -> ClickableAttributes route msg -> ( String, List (Attribute msg) )
toLinkAttributes routeToString clickableAttributes =
let
stringUrl =
case ( clickableAttributes.urlString, clickableAttributes.url ) of
( Just url, _ ) ->
url
( _, Just route ) ->
routeToString route
( Nothing, Nothing ) ->
"#"
in
case clickableAttributes.linkType of
Default ->
( "link"
, [ Attributes.href clickableAttributes.url
, [ Attributes.href stringUrl
, Attributes.target "_self"
]
)
@ -116,17 +138,17 @@ toLinkAttributes clickableAttributes =
( "linkSpa"
, case clickableAttributes.onClick of
Just handler ->
[ Attributes.href clickableAttributes.url
[ Attributes.href stringUrl
, EventExtras.onClickPreventDefaultForLinkWithHref handler
]
Nothing ->
[ Attributes.href clickableAttributes.url ]
[ Attributes.href stringUrl ]
)
WithMethod method ->
( "linkWithMethod"
, [ Attributes.href clickableAttributes.url
, [ Attributes.href stringUrl
, Attributes.attribute "data-method" method
]
)
@ -135,18 +157,18 @@ toLinkAttributes clickableAttributes =
( "linkWithTracking"
, case clickableAttributes.onClick of
Just track ->
[ Attributes.href clickableAttributes.url
[ Attributes.href stringUrl
, Events.preventDefaultOn "click"
(Json.Decode.succeed ( track, True ))
]
Nothing ->
[ Attributes.href clickableAttributes.url ]
[ Attributes.href stringUrl ]
)
External ->
( "linkExternal"
, Attributes.href clickableAttributes.url
, Attributes.href stringUrl
:: targetBlank
)
@ -154,13 +176,13 @@ toLinkAttributes clickableAttributes =
( "linkExternalWithTracking"
, case clickableAttributes.onClick of
Just handler ->
[ Attributes.href clickableAttributes.url
[ Attributes.href stringUrl
, Events.onClick handler
, Events.on "auxclick" (Json.Decode.succeed handler)
]
++ targetBlank
Nothing ->
Attributes.href clickableAttributes.url
Attributes.href stringUrl
:: targetBlank
)

View File

@ -187,7 +187,7 @@ css styles =
setClickableAttributes :
(ClickableAttributes msg -> ClickableAttributes msg)
(ClickableAttributes String msg -> ClickableAttributes String msg)
-> Attribute msg
setClickableAttributes apply =
set
@ -467,7 +467,7 @@ type ButtonOrLink msg
type alias ButtonOrLinkAttributes msg =
{ clickableAttributes : ClickableAttributes msg
{ clickableAttributes : ClickableAttributes String msg
, size : ButtonSize
, style : ColorPalette
, width : ButtonWidth
@ -523,7 +523,7 @@ renderLink ((ButtonOrLink config) as link_) =
getColorPalette link_
( linkFunctionName, attributes ) =
ClickableAttributes.toLinkAttributes config.clickableAttributes
ClickableAttributes.toLinkAttributes identity config.clickableAttributes
in
Nri.Ui.styled Styled.a
(styledName linkFunctionName)

View File

@ -87,7 +87,7 @@ link name icon attributes =
setClickableAttributes :
(ClickableAttributes msg -> ClickableAttributes msg)
(ClickableAttributes String msg -> ClickableAttributes String msg)
-> Attribute msg
setClickableAttributes apply =
set
@ -384,7 +384,7 @@ type ButtonOrLink msg
type alias ButtonOrLinkAttributes msg =
{ clickableAttributes : ClickableAttributes msg
{ clickableAttributes : ClickableAttributes String msg
, label : String
, icon : Svg
, disabled : Bool
@ -435,7 +435,7 @@ renderLink : ButtonOrLink msg -> Html msg
renderLink ((ButtonOrLink config) as link_) =
let
( linkFunctionName, extraAttrs ) =
ClickableAttributes.toLinkAttributes config.clickableAttributes
ClickableAttributes.toLinkAttributes identity config.clickableAttributes
theme =
if config.disabled then

View File

@ -178,7 +178,7 @@ css styles =
setClickableAttributes :
(ClickableAttributes msg -> ClickableAttributes msg)
(ClickableAttributes String msg -> ClickableAttributes String msg)
-> Attribute msg
setClickableAttributes apply =
set
@ -269,7 +269,7 @@ link label_ attributes =
|> List.foldl (\(Attribute attribute) l -> attribute l) defaults
( name, clickableAttributes ) =
ClickableAttributes.toLinkAttributes config.clickableAttributes
ClickableAttributes.toLinkAttributes identity config.clickableAttributes
in
Nri.Ui.styled Html.a
(dataDescriptor name)
@ -366,7 +366,7 @@ dataDescriptor descriptor =
type alias ClickableTextAttributes msg =
{ clickableAttributes : ClickableAttributes msg
{ clickableAttributes : ClickableAttributes String msg
, label : String
, size : Size
, icon : Maybe Svg

462
src/Nri/Ui/SideNav/V1.elm Normal file
View File

@ -0,0 +1,462 @@
module Nri.Ui.SideNav.V1 exposing
( view, Config
, entry, entryWithChildren, html, Entry
, icon, custom, css, nriDescription, testId, id
, onClick
, href, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
, primary, secondary
, premiumLevel
)
{-|
@docs view, Config
@docs entry, entryWithChildren, html, Entry
@docs icon, custom, css, nriDescription, testId, id
## Behavior
@docs onClick
@docs href, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
## Change the color scheme
@docs primary, secondary
## Change the state
@docs premiumLevel
-}
import Accessibility.Styled exposing (..)
import ClickableAttributes exposing (ClickableAttributes)
import Css exposing (..)
import Css.Media as Media
import Html.Styled
import Html.Styled.Attributes as Attributes exposing (css)
import Html.Styled.Events as Events
import Nri.Ui
import Nri.Ui.ClickableText.V3 as ClickableText
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Data.PremiumLevel as PremiumLevel exposing (PremiumLevel)
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Html.Attributes.V2 as ExtraAttributes
import Nri.Ui.Html.V3 exposing (viewJust)
import Nri.Ui.Svg.V1 as Svg exposing (Svg)
import Nri.Ui.UiIcon.V1 as UiIcon
import String exposing (toLower)
import String.Extra exposing (dasherize)
{-| Use `entry` to create a sidebar entry.
-}
type Entry route msg
= Entry (List (Entry route msg)) (EntryConfig route msg)
| Html (List (Html msg))
{-| -}
entry : String -> List (Attribute route msg) -> Entry route msg
entry title attributes =
attributes
|> List.foldl (\(Attribute attribute) b -> attribute b) (build title)
|> Entry []
{-| -}
entryWithChildren : String -> List (Attribute route msg) -> List (Entry route msg) -> Entry route msg
entryWithChildren title attributes children =
attributes
|> List.foldl (\(Attribute attribute) b -> attribute b) (build title)
|> Entry children
{-| -}
html : List (Html msg) -> Entry route msg
html =
Html
{-| -}
type alias Config route msg =
{ userPremiumLevel : PremiumLevel
, isCurrentRoute : route -> Bool
, routeToString : route -> String
, onSkipNav : msg
, css : List Style
}
{-| -}
view : Config route msg -> List (Entry route msg) -> Html msg
view config entries =
styled nav
[ flexBasis (px 250)
, flexShrink (num 0)
, borderRadius (px 8)
, backgroundColor Colors.gray96
, padding (px 20)
, marginRight (px 20)
, batch config.css
]
[]
(viewSkipLink config.onSkipNav
:: List.map (viewSidebarEntry config []) entries
)
viewSkipLink : msg -> Html msg
viewSkipLink onSkip =
ClickableText.button "Skip to main content"
[ ClickableText.icon UiIcon.arrowPointingRight
, ClickableText.small
, ClickableText.css
[ Css.pseudoClass "not(:focus)"
-- TODO: use Accessibility.Styled.Style.invisibleStyle
-- when we're on a higher version of tesk9/accessible-html-with-css
-- than 2.2.1
[ 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
]
]
, ClickableText.onClick onSkip
]
viewSidebarEntry : Config route msg -> List Css.Style -> Entry route msg -> Html msg
viewSidebarEntry config extraStyles entry_ =
case entry_ of
Entry children entryConfig ->
if PremiumLevel.allowedFor entryConfig.premiumLevel config.userPremiumLevel then
if anyLinkDescendants (isCurrentRoute config) children then
div [ Attributes.css extraStyles ]
(styled span
(sharedEntryStyles
++ [ backgroundColor Colors.gray92
, color Colors.navy
, fontWeight bold
, cursor default
, marginBottom (px 10)
]
)
[]
[ text entryConfig.title ]
:: List.map (viewSidebarEntry config [ marginLeft (px 20) ]) children
)
else
viewSidebarLeaf config extraStyles entryConfig
else
viewLockedEntry extraStyles entryConfig
Html html_ ->
div [ Attributes.css extraStyles ] html_
isCurrentRoute : Config route msg -> EntryConfig route msg -> Bool
isCurrentRoute config { route } =
Maybe.map config.isCurrentRoute route
|> Maybe.withDefault False
anyLinkDescendants : (EntryConfig route msg -> Bool) -> List (Entry route msg) -> Bool
anyLinkDescendants f children =
List.any
(\entry_ ->
case entry_ of
Entry children_ entryConfig ->
f entryConfig || anyLinkDescendants f children_
Html _ ->
False
)
children
viewSidebarLeaf :
Config route msg
-> List Style
-> EntryConfig route msg
-> Html msg
viewSidebarLeaf config extraStyles entryConfig =
let
( linkFunctionName, attributes ) =
ClickableAttributes.toLinkAttributes config.routeToString
entryConfig.clickableAttributes
in
Nri.Ui.styled Html.Styled.a
("Nri-Ui-SideNav-" ++ linkFunctionName)
(sharedEntryStyles
++ extraStyles
++ (if isCurrentRoute config entryConfig then
[ backgroundColor Colors.glacier
, color Colors.navy
, fontWeight bold
, visited [ color Colors.navy ]
]
else
[]
)
++ entryConfig.customStyles
)
(attributes ++ entryConfig.customAttributes)
[ viewJust
(\icon_ ->
icon_
|> Svg.withWidth (px 20)
|> Svg.withHeight (px 20)
|> Svg.withCss [ marginRight (px 5) ]
|> Svg.toHtml
)
entryConfig.icon
, text entryConfig.title
]
viewLockedEntry : List Style -> EntryConfig route msg -> Html msg
viewLockedEntry extraStyles entryConfig =
styled Html.Styled.button
[ batch sharedEntryStyles
, important (color Colors.gray45)
, borderWidth zero
, batch extraStyles
]
(case entryConfig.onLockedContent of
Just event ->
Events.onClick event :: entryConfig.customAttributes
Nothing ->
entryConfig.customAttributes
)
[ UiIcon.premiumLock
|> Svg.withWidth (px 17)
|> Svg.withHeight (px 25)
|> Svg.withCss [ marginRight (px 10), minWidth (px 17) ]
|> Svg.toHtml
, text entryConfig.title
]
sharedEntryStyles : List Style
sharedEntryStyles =
[ paddingLeft (px 20)
, paddingRight (px 20)
, height (px 45)
, displayFlex
, borderRadius (px 8)
, alignItems center
, Fonts.baseFont
, color Colors.navy
, backgroundColor transparent
, textDecoration none
, fontSize (px 15)
, fontWeight (int 600)
, textAlign left
, cursor pointer
]
-- Entry Customization helpers
{-| -}
type alias EntryConfig route msg =
{ icon : Maybe Svg
, title : String
, route : Maybe route
, clickableAttributes : ClickableAttributes route msg
, customAttributes : List (Html.Styled.Attribute msg)
, customStyles : List Style
, premiumLevel : PremiumLevel
, onLockedContent : Maybe msg
}
build : String -> EntryConfig route msg
build title =
{ icon = Nothing
, title = title
, route = Nothing
, clickableAttributes = ClickableAttributes.init
, customAttributes = []
, customStyles = []
, premiumLevel = PremiumLevel.Free
, onLockedContent = Nothing
}
type Attribute route msg
= Attribute (EntryConfig route msg -> EntryConfig route msg)
{-| -}
icon : Svg -> Attribute route msg
icon icon_ =
Attribute (\attributes -> { attributes | icon = Just icon_ })
{-| -}
premiumLevel : PremiumLevel -> msg -> Attribute route msg
premiumLevel level ifLocked =
Attribute
(\attributes ->
{ attributes
| premiumLevel = level
, onLockedContent = Just ifLocked
}
)
{-| Use this helper to add custom attributes.
Do NOT use this helper to add css styles, as they may not be applied the way
you want/expect if underlying Button styles change.
Instead, please use the `css` helper.
-}
custom : List (Html.Styled.Attribute msg) -> Attribute route msg
custom attributes =
Attribute
(\config ->
{ config
| customAttributes = List.append config.customAttributes attributes
}
)
{-| -}
nriDescription : String -> Attribute route msg
nriDescription description =
custom [ ExtraAttributes.nriDescription description ]
{-| -}
testId : String -> Attribute route msg
testId id_ =
custom [ ExtraAttributes.testId id_ ]
{-| -}
id : String -> Attribute route msg
id id_ =
custom [ Attributes.id id_ ]
{-| -}
css : List Style -> Attribute route msg
css styles =
Attribute
(\config ->
{ config
| customStyles = List.append config.customStyles styles
}
)
{-| -}
primary : Attribute route msg
primary =
Attribute (\attributes -> { attributes | customStyles = [] })
{-| -}
secondary : Attribute route msg
secondary =
Attribute
(\attributes ->
{ attributes
| customStyles =
[ backgroundColor Colors.white
, boxShadow3 zero (px 2) Colors.gray75
, border3 (px 1) solid Colors.gray75
]
}
)
-- LINKING, CLICKING, and TRACKING BEHAVIOR
setClickableAttributes :
Maybe route
-> (ClickableAttributes route msg -> ClickableAttributes route msg)
-> Attribute route msg
setClickableAttributes route apply =
Attribute
(\attributes ->
{ attributes
| route =
case route of
Just r ->
Just r
Nothing ->
attributes.route
, clickableAttributes = apply attributes.clickableAttributes
}
)
{-| -}
onClick : msg -> Attribute route msg
onClick msg =
setClickableAttributes Nothing (ClickableAttributes.onClick msg)
{-| -}
href : route -> Attribute route msg
href route =
setClickableAttributes (Just route) (ClickableAttributes.href route)
{-| Use this link for routing within a single page app.
This will make a normal <a> tag, but change the Events.onClick behavior to avoid reloading the page.
See <https://github.com/elm-lang/html/issues/110> for details on this implementation.
-}
linkSpa : route -> Attribute route msg
linkSpa route =
setClickableAttributes (Just route)
(ClickableAttributes.linkSpa route)
{-| -}
linkWithMethod : { method : String, url : route } -> Attribute route msg
linkWithMethod config =
setClickableAttributes (Just config.url)
(ClickableAttributes.linkWithMethod config)
{-| -}
linkWithTracking : { track : msg, url : route } -> Attribute route msg
linkWithTracking config =
setClickableAttributes (Just config.url)
(ClickableAttributes.linkWithTracking config)
{-| -}
linkExternal : String -> Attribute route msg
linkExternal url =
setClickableAttributes Nothing (ClickableAttributes.linkExternal url)
{-| -}
linkExternalWithTracking : { track : msg, url : String } -> Attribute route msg
linkExternalWithTracking config =
setClickableAttributes Nothing (ClickableAttributes.linkExternalWithTracking config)

View File

@ -4,7 +4,7 @@ import Accessibility.Styled as Html exposing (Html)
import Browser exposing (Document, UrlRequest(..))
import Browser.Dom
import Browser.Navigation exposing (Key)
import Category
import Category exposing (Category)
import Css exposing (..)
import Css.Media exposing (withMedia)
import Dict exposing (Dict)
@ -16,10 +16,13 @@ import Html.Styled.Events as Events
import Nri.Ui.ClickableText.V3 as ClickableText
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.CssVendorPrefix.V1 as VendorPrefixed
import Nri.Ui.Data.PremiumLevel as PremiumLevel
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Heading.V2 as Heading
import Nri.Ui.MediaQuery.V1 exposing (mobile, notMobile)
import Nri.Ui.Page.V3 as Page
import Nri.Ui.SideNav.V1 as SideNav
import Nri.Ui.UiIcon.V1 as UiIcon
import Routes as Routes exposing (Route(..))
import Sort.Set as Set exposing (Set)
import Task
@ -145,7 +148,12 @@ view_ model =
Routes.Doodad doodad ->
case List.head (examples (\m -> m.name == doodad)) of
Just example ->
Html.main_ []
Html.main_
[ css
[ maxWidth (Css.px 1400)
, margin auto
]
]
[ Example.view model.previousRoute example
|> Html.map (UpdateModuleStates example.name)
]
@ -182,6 +190,8 @@ withSideNav currentRoute content =
[ displayFlex
, withMedia [ mobile ] [ flexDirection column, alignItems stretch ]
, alignItems flexStart
, maxWidth (Css.px 1400)
, margin auto
]
]
[ navigation currentRoute
@ -221,92 +231,29 @@ viewPreviews containerId examples =
navigation : Route -> Html Msg
navigation route =
navigation currentRoute =
let
isActive category =
case route of
Routes.Category routeCategory ->
category == routeCategory
_ ->
False
link active hash displayName =
ClickableText.link displayName
[ ClickableText.small
, ClickableText.css
[ Css.color Colors.navy
, Css.display Css.block
, Css.padding (Css.px 8)
, Css.borderRadius (Css.px 8)
, if active then
Css.backgroundColor Colors.glacier
else
Css.batch []
]
, ClickableText.href hash
]
navLink category =
link (isActive category)
(Routes.toString (Routes.Category category))
(Category.forDisplay category)
toNavLi element =
Html.li
[ css
[ margin zero
, listStyle none
, textDecoration none
]
]
[ element ]
categoryNavLinks : List (SideNav.Entry Route Msg)
categoryNavLinks =
List.map
(\category ->
SideNav.entry (Category.forDisplay category)
[ SideNav.href (Routes.Category category) ]
)
Category.all
in
Html.nav
[ css
[ backgroundColor Colors.gray96
, withMedia [ notMobile ]
SideNav.view
{ userPremiumLevel = PremiumLevel.Free
, isCurrentRoute = (==) currentRoute
, routeToString = Routes.toString
, onSkipNav = SkipToMainContent
, css =
[ withMedia [ notMobile ]
[ VendorPrefixed.value "position" "sticky"
, top (px 55)
, flexShrink zero
, borderRadius (px 8)
, marginRight (px 40)
, padding (px 20)
, flexBasis (px 200)
]
]
, attribute "aria-label" "Main Navigation"
]
[ Html.button
[ css
[ backgroundColor transparent
, borderStyle none
, textDecoration none
, color Colors.azure
, Fonts.baseFont
, Css.marginBottom (px 20)
, 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
]
]
, Events.onClick SkipToMainContent
, id "skip"
]
[ Html.text "Skip to main content" ]
, (link (route == Routes.All) "#/" "All"
:: List.map navLink Category.all
)
|> List.map toNavLi
|> Html.ul
[ css [ margin zero, padding zero ]
, id "categories"
]
]
}
(SideNav.entry "All" [ SideNav.href Routes.All ]
:: categoryNavLinks
)

View File

@ -43,6 +43,7 @@
"Nri.Ui.RadioButton.V3",
"Nri.Ui.SegmentedControl.V14",
"Nri.Ui.Select.V8",
"Nri.Ui.SideNav.V1",
"Nri.Ui.Slide.V1",
"Nri.Ui.SlideModal.V2",
"Nri.Ui.SortableTable.V2",