ui: Transition App Chrome to use new Disclosure Menus (#12334)

* Add %panel CSS component

* Deprecate old menu-panel component

* Various smallish tweaks to disclosure-menu

* Move all menus in the app chrome to use new DisclosureMenu

* Follow up CSS to move all app chrome menus to new components

* Don't prevent default any events from anchors

* Add a tick to click steps
This commit is contained in:
John Cowen 2022-02-21 12:22:59 +00:00 committed by GitHub
parent 602e08ada7
commit 73b6687c5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 875 additions and 435 deletions

3
.changelog/12334.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:enhancement
ui: Slightly improve usability of main navigation
```

View File

@ -119,32 +119,32 @@
Logout
</Action>
</Portal>
<PopoverMenu @position="right" as |components api|>
<BlockSlot @name="trigger">
<DisclosureMenu as |disclosure|>
<disclosure.Action
{{on 'click' disclosure.toggle}}
>
Logout
</BlockSlot>
<BlockSlot @name="menu">
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
{{!TODO: It might be nice to use one of our recursive components here}}
{{#if authDialog.token.AccessorID}}
<li role="none">
<AuthProfile
@item={{authDialog.token}}
/>
</li>
<MenuSeparator />
{{/if}}
<MenuItem
class="dangerous"
@onclick={{action authDialog.logout}}
</disclosure.Action>
<disclosure.Menu as |panel|>
{{#if authDialog.token.AccessorID}}
<AuthProfile
@item={{authDialog.token}}
/>
{{/if}}
<panel.Menu as |menu|>
<menu.Separator />
<menu.Item
class="dangerous"
>
<menu.Action
{{on 'click' (optional authDialog.logout)}}
>
<BlockSlot @name="label">
Logout
</BlockSlot>
</MenuItem>
{{/let}}
</BlockSlot>
</PopoverMenu>
Logout
</menu.Action>
</menu.Item>
</panel.Menu>
</disclosure.Menu>
</DisclosureMenu>
</:authorized>
</AuthDialog>

View File

@ -1,74 +1,77 @@
{{#if (can "use nspaces")}}
{{#if (can "choose nspaces")}}
{{#let
(or @nspace 'default')
as |nspace|}}
<li
class="nspaces"
data-test-nspace-menu
>
<PopoverMenu
aria-label="Namespace"
@position="left"
as |components api|>
<BlockSlot @name="trigger">
{{nspace}}
</BlockSlot>
<BlockSlot @name="menu">
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
{{#if (gt @nspaces.length 0)}}
<DataSource
@src={{uri
'/${partition}/*/${dc}/namespaces'
(hash
partition=@partition
dc=@dc.Name
)
}}
@onchange={{fn (optional @onchange)}}
@loading="lazy"
/>
{{else}}
<DataSource
@src={{uri
'/${partition}/*/${dc}/namespaces'
(hash
partition=@partition
dc=@dc.Name
)
}}
@onchange={{fn (optional @onchange)}}
/>
{{/if}}
{{#each (reject-by 'DeletedAt' @nspaces) as |item|}}
<MenuItem
class={{if (eq nspace item.Name) 'is-active'}}
{{#if (can "choose nspaces")}}
{{#let
(or @nspace 'default')
as |nspace|}}
<li
class="nspaces"
data-test-nspace-menu
>
<DisclosureMenu
aria-label="Namespace"
as |disclosure|>
<disclosure.Action
{{on 'click' disclosure.toggle}}
>
{{nspace}}
</disclosure.Action>
<disclosure.Menu as |panel|>
{{#if (gt @nspaces.length 0)}}
<DataSource
@src={{uri
'/${partition}/*/${dc}/namespaces'
(hash
partition=@partition
dc=@dc.Name
)
}}
@onchange={{fn (optional @onchange)}}
/>
{{else}}
<DataSource
@src={{uri
'/${partition}/*/${dc}/namespaces'
(hash
partition=@partition
dc=@dc.Name
)
}}
@onchange={{fn (optional @onchange)}}
/>
{{/if}}
<panel.Menu as |menu|>
{{#each (reject-by 'DeletedAt' @nspaces) as |item|}}
<menu.Item
aria-current={{if (eq nspace item.Name) 'true'}}
>
<menu.Action
{{on 'click' disclosure.close}}
@href={{href-to '.' params=(hash
partition=(if (gt @partition.length 0) @partition undefined)
nspace=item.Name
)}}
>
<BlockSlot @name="label">
{{item.Name}}
</BlockSlot>
</MenuItem>
{{/each}}
{{#if (can 'manage nspaces')}}
<MenuSeparator />
<MenuItem
data-test-main-nav-nspaces
{{item.Name}}
</menu.Action>
</menu.Item>
{{/each}}
{{#if (can 'manage nspaces')}}
<menu.Separator />
<menu.Item
data-test-main-nav-nspaces
>
<menu.Action
{{on 'click' disclosure.close}}
@href={{href-to 'dc.nspaces' @dc.Name}}
>
<BlockSlot @name="label">
Manage Namespaces
</BlockSlot>
</MenuItem>
{{/if}}
{{/let}}
</BlockSlot>
</PopoverMenu>
</li>
{{/let}}
{{/if}}
Manage Namespaces
</menu.Action>
</menu.Item>
{{/if}}
</panel.Menu>
</disclosure.Menu>
</DisclosureMenu>
</li>
{{/let}}
{{/if}}
{{/if}}

View File

@ -6,51 +6,56 @@ as |partition|}}
class="partitions"
data-test-partition-menu
>
<PopoverMenu
aria-label="Admin Partition"
@position="left"
as |components api|>
<BlockSlot @name="trigger">
{{partition}}
</BlockSlot>
<BlockSlot @name="menu">
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
<DataSource
@src={{uri
'/*/*/${dc}/partitions'
(hash
dc=@dc.Name
)
}}
@onchange={{fn (optional @onchange)}}
/>
<DisclosureMenu
aria-label="Admin Partition"
as |disclosure|>
<disclosure.Action
{{on 'click' disclosure.toggle}}
>
{{partition}}
</disclosure.Action>
<disclosure.Menu as |panel|>
<DataSource
@src={{uri
'/*/*/${dc}/partitions'
(hash
dc=@dc.Name
)
}}
@onchange={{fn (optional @onchange)}}
/>
<panel.Menu as |menu|>
{{#each (reject-by 'DeletedAt' @partitions) as |item|}}
<MenuItem
<menu.Item
class={{if (eq partition item.Name) 'is-active'}}
@href={{href-to '.' params=(hash
partition=item.Name
nspace=undefined
)}}
>
<BlockSlot @name="label">
<menu.Action
{{on 'click' disclosure.close}}
@href={{href-to '.' params=(hash
partition=item.Name
nspace=undefined
)}}
>
{{item.Name}}
</BlockSlot>
</MenuItem>
</menu.Action>
</menu.Item>
{{/each}}
{{#if (can 'manage partitions')}}
<MenuSeparator />
<MenuItem
{{#if (can 'manage partitions')}}
<menu.Separator />
<menu.Item
data-test-main-nav-partitions
@href={{href-to 'dc.partitions.index' @dc.Name}}
>
<BlockSlot @name="label">
<menu.Action
{{on 'click' disclosure.close}}
@href={{href-to 'dc.partitions.index' @dc.Name}}
>
Manage Partitions
</BlockSlot>
</MenuItem>
{{/if}}
{{/let}}
</BlockSlot>
</PopoverMenu>
</menu.Action>
</menu.Item>
{{/if}}
</panel.Menu>
</disclosure.Menu>
</DisclosureMenu>
</li>
{{else}}
<li

View File

@ -52,13 +52,22 @@
margin-left: auto;
}
%main-nav-vertical-hoisted {
top: 11px;
top: 18px;
}
%main-nav-vertical-hoisted > .popover-menu > label > button {
%main-nav-vertical-hoisted [aria-label]::before {
display: none !important;
}
%main-nav-horizontal [aria-haspopup='menu'] ~ * {
position: absolute;
right: 0;
min-width: 192px;
}
%main-nav-horizontal [aria-expanded],
%main-nav-vertical-hoisted [aria-expanded] {
@extend %main-nav-horizontal-popover-menu-trigger;
@extend %main-nav-horizontal-action;
border: none;
}
%main-nav-vertical-hoisted.is-active > label > * {
%main-nav-horizontal-popover-menu-trigger {
@extend %main-nav-horizontal-action-active;
}
%footer,

View File

@ -0,0 +1,49 @@
<li
class="dcs"
data-test-datacenter-menu
>
<DisclosureMenu
aria-label="Datacenter"
as |disclosure|>
<disclosure.Action
{{on 'click' disclosure.toggle}}
>
{{@dc.Name}}
</disclosure.Action>
<disclosure.Menu as |panel|>
<DataSource
@src={{uri '/*/*/*/datacenters'}}
@onchange={{action (mut @dcs) value="data"}}
/>
<panel.Menu as |menu|>
{{#each (sort-by 'Name' @dcs) as |item|}}
<menu.Item
aria-current={{if (eq @dc.Name item.Name) 'true'}}
class={{class-map
(array 'is-local' item.Local)
(array 'is-primary' item.Primary)
}}
>
<menu.Action
{{on 'click' disclosure.close}}
@href={{href-to '.' params=(hash
dc=item.Name
partition=undefined
nspace=(if (gt @nspace.length 0) @nspace undefined)
)}}
>
{{item.Name}}
{{#if item.Primary}}
<span>Primary</span>
{{/if}}
{{#if item.Local}}
<span>Local</span>
{{/if}}
</menu.Action>
</menu.Item>
{{/each}}
</panel.Menu>
</disclosure.Menu>
</DisclosureMenu>
</li>

View File

@ -19,13 +19,15 @@ common usecase of having a floating menu.
>
{{if disclosure.expanded 'Close' 'Open'}}
</disclosure.Action>
<disclosure.Menu as |menu|>
<menu.Item>
<menu.Action>Item 1</menu.Action>
</menu.Item>
<menu.Item>
<menu.Action>Item 2</menu.Action>
</menu.Item>
<disclosure.Menu as |panel|>
<panel.Menu as |menu|>
<menu.Item>
<menu.Action>Item 1</menu.Action>
</menu.Item>
<menu.Item>
<menu.Action>Item 2</menu.Action>
</menu.Item>
</panel.Menu>
</disclosure.Menu>
</DisclosureMenu>
</figure>
@ -46,13 +48,15 @@ common usecase of having a floating menu.
(array 'top' this.height)
(array 'background-color' 'rgb(var(--tone-gray-000))')
}}
as |menu|>
<menu.Item>
<menu.Action>Item 1</menu.Action>
</menu.Item>
<menu.Item>
<menu.Action>Item 2</menu.Action>
</menu.Item>
as |panel|>
<panel.Menu as |menu|>
<menu.Item>
<menu.Action>Item 1</menu.Action>
</menu.Item>
<menu.Item>
<menu.Action>Item 2</menu.Action>
</menu.Item>
</panel.Menu>
</disclosure.Menu>
</DisclosureMenu>
</figure>

View File

@ -4,12 +4,16 @@
}}
...attributes
>
<Disclosure as |disclosure|>
<Disclosure
@expanded={{@expanded}}
as |disclosure|>
{{yield (hash
Action=(component 'disclosure-menu/action' disclosure=disclosure)
Menu=(component 'disclosure-menu/menu' disclosure=disclosure)
disclosure=disclosure
toggle=disclosure.toggle
close=disclosure.close
open=disclosure.open
expanded=disclosure.expanded
)}}
</Disclosure>

View File

@ -1,3 +1,6 @@
.disclosure-menu {
position: relative;
}
.disclosure-menu [aria-expanded] ~ * {
@extend %menu-panel;
}

View File

@ -1,15 +1,11 @@
<@disclosure.Details as |details|>
<Menu
{{on-outside 'click' @disclosure.close}}
@disclosure={{@disclosure}}
...attributes
as |menu|>
{{yield (hash
items=menu.items
Item=menu.Item
Action=menu.Action
Separator=menu.Separator
)}}
</Menu>
<div
{{on-outside 'click' @disclosure.close}}
...attributes
>
{{yield (hash
Menu=(component 'menu' disclosure=@disclosure)
)}}
</div>
</@disclosure.Details>

View File

@ -87,53 +87,12 @@
<:main-nav>
<ul>
<li
class="dcs"
data-test-datacenter-menu
>
<PopoverMenu
aria-label="Datacenter"
@position="left"
as |components|>
<BlockSlot @name="trigger">
{{@dc.Name}}
</BlockSlot>
<BlockSlot @name="menu">
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
<DataSource
@src={{uri '/*/*/*/datacenters'}}
@onchange={{action (mut @dcs) value="data"}}
@loading="lazy"
/>
{{#each (sort-by 'Name' @dcs) as |item|}}
<MenuItem
data-test-datacenter-picker
class={{concat
(if (eq @dc.Name item.Name) 'is-active')
(if item.Local ' is-local')
(if item.Primary ' is-primary')
}}
@href={{href-to '.' params=(hash
dc=item.Name
partition=undefined
nspace=(if (gt @nspace.length 0) @nspace undefined)
)}}
>
<BlockSlot @name="label">
{{item.Name}}
{{#if item.Primary}}
<span>Primary</span>
{{/if}}
{{#if item.Local}}
<span>Local</span>
{{/if}}
</BlockSlot>
</MenuItem>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverMenu>
</li>
<Consul::Datacenter::Selector
@dc={{@dc}}
@partition={{@partition}}
@nspace={{@nspace}}
@dcs={{@dcs}}
/>
<Consul::Partition::Selector
@dc={{@dc}}
@partition={{@partition}}
@ -182,45 +141,52 @@
<li
data-test-main-nav-help
>
<PopoverMenu @position="right" as |components|>
<BlockSlot @name="trigger">
<DisclosureMenu
as |disclosure|>
<disclosure.Action
{{on 'click' disclosure.toggle}}
>
Help
</BlockSlot>
<BlockSlot @name="menu">
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
<MenuSeparator>
<BlockSlot @name="label">
Consul v{{env 'CONSUL_VERSION'}}
</BlockSlot>
</MenuSeparator>
<MenuItem
</disclosure.Action>
<disclosure.Menu as |panel|>
<panel.Menu as |menu|>
<menu.Separator>
Consul v{{env 'CONSUL_VERSION'}}
</menu.Separator>
<menu.Item
class="docs-link"
@href={{env 'CONSUL_DOCS_URL'}}
>
<BlockSlot @name="label">
<menu.Action
@href={{env 'CONSUL_DOCS_URL'}}
@external={{true}}
>
Documentation
</BlockSlot>
</MenuItem>
<MenuItem
</menu.Action>
</menu.Item>
<menu.Item
class="learn-link"
@href={{concat (env 'CONSUL_DOCS_LEARN_URL') '/consul'}}
>
<BlockSlot @name="label">
<menu.Action
@href={{concat (env 'CONSUL_DOCS_LEARN_URL') '/consul'}}
@external={{true}}
>
HashiCorp Learn
</BlockSlot>
</MenuItem>
<MenuSeparator />
<MenuItem
class="learn-link"
@href={{env 'CONSUL_REPO_ISSUES_URL'}}
</menu.Action>
</menu.Item>
<menu.Separator />
<menu.Item
class="feedback-link"
>
<BlockSlot @name="label">
<menu.Action
@href={{env 'CONSUL_REPO_ISSUES_URL'}}
@external={{true}}
>
Provide Feedback
</BlockSlot>
</MenuItem>
{{/let}}
</BlockSlot>
</PopoverMenu>
</menu.Action>
</menu.Item>
</panel.Menu>
</disclosure.Menu>
</DisclosureMenu>
</li>
<li
data-test-main-nav-settings

View File

@ -1,11 +1,18 @@
%hashicorp-consul {
[role='banner'] nav .dcs {
nav .dcs {
@extend %main-nav-vertical-hoisted;
left: 100px;
}
[role='banner'] nav .dcs .popover-menu[aria-label]::before {
display: none;
nav .dcs .menu-panel {
min-width: 250px;
}
nav li.partitions,
nav li.nspaces {
@extend %main-nav-vertical-popover-menu;
/* --panel-height: 300px;
--row-height: 43px; */
}
[role='banner'] a svg {
fill: rgb(var(--tone-brand-600));
}

View File

@ -50,7 +50,7 @@ export default (collection, clickable, attribute, is, authForm, emptyState) => s
':checked',
'[data-test-nspace-menu] > input[type="checkbox"]'
);
page.navigation.dcs = collection('[data-test-datacenter-picker]', {
page.navigation.dcs = collection('[data-test-datacenter-menu] li', {
name: clickable('a'),
});
return page;

View File

@ -5,6 +5,7 @@
%main-nav-horizontal > ul > li > a,
%main-nav-horizontal > ul > li > span,
%main-nav-horizontal > ul > li > button,
%main-nav-horizontal-popover-menu-trigger,
%main-nav-horizontal > ul > li > .popover-menu > label > button {
@extend %main-nav-horizontal-action;
}

View File

@ -5,6 +5,15 @@
%main-nav-horizontal-action > a {
color: inherit;
}
%main-nav-horizontal-popover-menu-trigger::after {
@extend %with-chevron-down-mask, %as-pseudo;
width: 16px;
height: 16px;
position: relative;
}
%main-nav-horizontal-popover-menu-trigger[aria-expanded='true']::after {
@extend %with-chevron-up-mask;
}
/**/
/* reduced size hamburger menu */
%main-nav-horizontal-toggle {

View File

@ -30,34 +30,12 @@
%main-nav-vertical > ul > li > label {
@extend %main-nav-vertical-action;
}
/**/
%main-nav-vertical .popover-menu {
margin-top: 0.5rem;
}
%main-nav-vertical .popover-menu .menu-panel {
top: 37px !important;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
%main-nav-vertical .popover-menu > label > button {
border: var(--decor-border-100);
border-color: rgb(var(--tone-gray-500));
color: rgb(var(--tone-gray-999));
width: calc(100% - 20px);
z-index: 100;
text-align: left;
padding: 10px;
border-radius: var(--decor-radius-100);
}
%main-nav-vertical .popover-menu > label > button::after {
float: right;
}
%main-nav-vertical .popover-menu .menu-panel {
top: 28px;
z-index: 100;
}
/* menu-panels in the main navigation are treated slightly differently */
%main-nav-vertical label + div {
%main-nav-vertical-popover-menu .disclosure-menu button + * {
@extend %main-nav-vertical-menu-panel;
}
/**/
%main-nav-vertical-popover-menu .disclosure-menu > button {
@extend %main-nav-vertical-popover-menu-trigger;
@extend %internal-button;
}

View File

@ -11,14 +11,13 @@
%main-nav-vertical:not(.in-viewport) {
visibility: hidden;
}
%main-nav-vertical li.partitions,
%main-nav-vertical li.partition,
%main-nav-vertical li.partitions,
%main-nav-vertical li.nspaces {
margin-bottom: 25px;
padding: 0 26px;
}
%main-nav-vertical li.dcs {
margin-bottom: 18px;
padding: 0 18px;
}
// TODO: We no longer have the rule that menu-panel buttons only contain two
@ -41,9 +40,21 @@
margin-top: 0.7rem;
padding-bottom: 0;
}
%main-nav-vertical-popover-menu .disclosure {
position: relative;
}
%main-nav-vertical-popover-menu-trigger {
width: 100%;
text-align: left;
padding: 10px;
}
%main-nav-vertical-popover-menu-trigger::after {
float: right;
}
%main-nav-vertical-menu-panel {
min-width: 248px;
position: absolute;
z-index: 1;
width: calc(100% - 2px);
}
%main-nav-vertical-hoisted {
visibility: visible;

View File

@ -1,8 +1,8 @@
%main-nav-vertical-action {
@extend %p1;
cursor: pointer;
border-right: var(--decor-border-400);
border-color: var(--transparent);
@extend %p1;
}
%main-nav-vertical-action > a {
color: inherit;
@ -41,28 +41,38 @@
background-color: rgb(var(--tone-gray-150));
border-color: rgb(var(--tone-gray-999));
}
%main-nav-vertical li[aria-label]::before,
%main-nav-vertical .popover-menu[aria-label]::before {
%main-nav-vertical [aria-label]::before {
color: rgb(var(--tone-gray-700));
content: attr(aria-label);
display: block;
margin-top: -0.5rem;
margin-bottom: 0.5rem;
}
%main-nav-vertical .is-primary span,
%main-nav-vertical .is-local span {
@extend %pill-200;
color: rgb(var(--tone-gray-000));
background-color: rgb(var(--tone-gray-500));
}
%main-nav-vertical .nspaces .menu-panel > div {
%main-nav-vertical-popover-menu-trigger {
border: var(--decor-border-100);
border-color: rgb(var(--tone-gray-500));
border-radius: var(--decor-radius-100);
font-weight: inherit;
background-color: rgb(var(--tone-gray-050));
color: rgb(var(--tone-gray-999));
padding-left: 36px;
}
%main-nav-vertical .nspaces .menu-panel > div::before {
@extend %with-info-circle-fill-mask, %as-pseudo;
color: rgb(var(--tone-blue-500));
/* sizes the icon not the text */
font-size: 1.1em;
%main-nav-vertical-popover-menu-trigger[aria-expanded='true'] {
border-bottom-left-radius: var(--decor-radius-000);
border-bottom-right-radius: var(--decor-radius-000);
}
%main-nav-vertical-popover-menu-trigger::after {
@extend %with-chevron-down-mask, %as-pseudo;
width: 16px;
height: 16px;
position: relative;
}
%main-nav-vertical-popover-menu-trigger[aria-expanded='true']::after {
@extend %with-chevron-up-mask;
}
%main-nav-vertical-menu-panel {
border-top-left-radius: var(--decor-radius-000);
border-top-right-radius: var(--decor-radius-000);
border-top: var(--decor-border-000);
}

View File

@ -0,0 +1,169 @@
# MenuPanel
```hbs preview-template
{{#each
(array 'light' 'dark')
as |theme|}}
<figure>
<figcaption>Without a header</figcaption>
<div
class={{class-map
'menu-panel'
(array (concat 'theme-' theme))
}}
>
<ul role="menu">
<li aria-current="true" role="none">
<Action role="menuitem">Item 1<span>Label</span><span>Label 2</span></Action>
</li>
<li role="separator">
Item some title text
</li>
<li role="none">
<Action role="menuitem">Item 2</Action>
</li>
<li role="separator"></li>
<li role="none">
<Action role="menuitem">Item 3</Action>
</li>
</ul>
</div>
</figure>
<figure>
<figcaption>With a header</figcaption>
<div
class={{class-map
'menu-panel'
(array (concat 'theme-' theme))
}}
>
<div>
<p>Some content explaining what the menu is about</p>
</div>
<ul role="menu">
<li aria-current="true" role="none">
<Action role="menuitem">Item 1<span>Label</span><span>Label 2</span></Action>
</li>
<li role="separator">
Item some title text
</li>
<li role="none">
<Action role="menuitem">Item 2</Action>
</li>
<li role="separator"></li>
<li role="none">
<Action role="menuitem">Item 3</Action>
</li>
</ul>
</div>
</figure>
<figure>
<StateChart
@src={{state-chart 'boolean'}}
as |State Guard StateAction dispatch state|>
<Action>Focus Left</Action>
<DisclosureMenu as |disclosure|>
<disclosure.Action
{{on 'click' disclosure.toggle}}
>
{{if disclosure.expanded 'Close' 'Open'}}
</disclosure.Action>
<disclosure.Menu
style={{style-map
(array 'max-height' (if (state-matches state 'true') (add 0 this.rect.height)) 'px')
}}
class={{class-map
(array 'menu-panel')
(array 'menu-panel-confirming' (state-matches state 'true'))
(array (concat 'theme-' theme))
}}
as |panel|>
<div
{{on-resize
(dom-position (set this 'header'))
}}
>
<p>Some text in here</p>
</div>
<panel.Menu as |menu|>
<menu.Item
aria-current="true"
>
<menu.Action>
Item 1
<span>Label</span>
<span>Label 2</span>
</menu.Action>
</menu.Item>
<menu.Separator>
Item some title text
</menu.Separator>
<menu.Item>
<menu.Action>
Item 2
</menu.Action>
</menu.Item>
<menu.Separator />
<menu.Item
class="dangerous"
>
<menu.Action
{{on "click" (fn dispatch 'TOGGLE')}}
>
Item 3
</menu.Action>
<div
{{on-resize
(dom-position (set this 'rect'))
}}
style={{style-map
(array 'top' (if (state-matches state 'true') (sub 0 this.header.height)) 'px')
}}
class={{class-map
'menu-panel-confirmation'
'informed-action'
'confirmation-alert'
'warning'
}}
>
<div>
<header>Hi</header>
<p>Body</p>
</div>
<ul>
<li>
<Action
@tabindex="-1"
{{on "click" (queue disclosure.close (fn dispatch 'TOGGLE'))}}
>
Confirm
</Action>
</li>
<li>
<Action
@tabindex="-1"
{{on "click" (fn dispatch 'TOGGLE')}}
>
Cancel
</Action>
</li>
</ul>
</div>
</menu.Item>
</panel.Menu>
</disclosure.Menu>
</DisclosureMenu>
<Action>Focus Right</Action>
</StateChart>
</figure>
{{/each}}
```

View File

@ -0,0 +1,58 @@
/* old stuff */
%menu-panel {
overflow: hidden;
}
%menu-panel-deprecated {
position: absolute;
}
%menu-panel-deprecated [type='checkbox'] {
display: none;
}
%menu-panel-deprecated {
transition: max-height 150ms;
}
%menu-panel-deprecated {
transition: min-height 150ms, max-height 150ms;
min-height: 0;
}
%menu-panel-deprecated:not(.confirmation) [type='checkbox'] ~ * {
transition: transform 150ms;
}
%menu-panel-deprecated [type='checkbox']:checked ~ * {
transform: translateX(calc(-100% - 10px));
}
%menu-panel-deprecated.confirmation [role='menu'] {
min-height: 205px !important;
}
%menu-panel-deprecated [type='checkbox']:checked ~ * {
/* this needs to autocalculate */
min-height: 143px;
max-height: 143px;
}
%menu-panel-deprecated [id$='-']:first-child:checked ~ ul label[for$='-'] * [role='menu'],
%menu-panel-deprecated [id$='-']:first-child:checked ~ ul > li > [role='menu'] {
display: block;
}
/**/
%menu-panel-deprecated > ul > li > div[role='menu'] {
position: absolute;
top: 0;
left: calc(100% + 10px);
}
%menu-panel-deprecated > ul > li > *:not(div[role='menu']) {
position: relative;
}
%menu-panel-deprecated:not(.left) {
right: 0px !important;
left: auto !important;
}
%menu-panel-deprecated.left {
left: 0px;
}
%menu-panel-deprecated:not(.above) {
top: 28px;
}
%menu-panel-deprecated.above {
bottom: 42px;
}

View File

@ -3,11 +3,12 @@
change=(action "change")
) as |api|}}
<div
class={{join ' ' (compact (array
'menu-panel'
position
(if isConfirmation 'confirmation')
))}}
class={{class-map
(array 'menu-panel')
(array 'menu-panel-deprecated')
(array position)
(array isConfirmation 'confirmation')
}}
{{did-insert (action 'connect')}}
>
<YieldSlot @name="controls">

View File

@ -1,22 +1,50 @@
@import './skin';
@import './layout';
@import './deprecated';
.menu-panel {
@extend %menu-panel;
}
.menu-panel-deprecated {
@extend %menu-panel-deprecated;
}
%menu-panel {
@extend %panel;
}
%menu-panel-item span {
@extend %menu-panel-badge;
}
%menu-panel [role='separator'] {
@extend %panel-separator;
@extend %menu-panel-separator;
}
%menu-panel > div {
@extend %menu-panel-header;
}
// %menu-panel > ul > li > *:not(div),
%menu-panel [role='menuitem'] {
%menu-panel > ul {
@extend %menu-panel-body;
}
%menu-panel-body > li {
@extend %menu-panel-item;
}
%menu-panel-body > [role='treeitem'],
%menu-panel-body > li > [role='menuitem'],
%menu-panel-body > li > [role='option'] {
@extend %menu-panel-button;
}
%menu-panel-button + * {
@extend %menu-panel-confirmation;
}
%menu-panel-item[aria-selected] > *,
%menu-panel-item[aria-checked] > *,
%menu-panel-item[aria-current] > *,
%menu-panel-item.is-active > * {
@extend %menu-panel-button-selected;
}
%menu-panel-button {
@extend %internal-button;
}
%menu-panel > ul > li.dangerous > *:not(div) {
/* first-child is highly likely to be the button/or anchor*/
%menu-panel-item.dangerous > *:first-child {
@extend %internal-button-dangerous;
}
%menu-panel .informed-action {
border: 0 !important;
}

View File

@ -1,115 +1,7 @@
%menu-panel {
position: absolute;
}
%menu-panel [type='checkbox'] {
display: none;
}
%menu-panel {
overflow: hidden;
transition: min-height 150ms, max-height 150ms;
min-height: 0;
}
%menu-panel:not(.confirmation) [type='checkbox'] ~ * {
transition: transform 150ms;
}
%menu-panel [type='checkbox']:checked ~ * {
transform: translateX(calc(-100% - 10px));
}
%menu-panel.confirmation [role='menu'] {
min-height: 200px !important;
}
%menu-panel [role='menuitem'] {
display: flex;
justify-content: space-between;
}
%menu-panel [role='menuitem']:after {
@extend %as-pseudo;
display: block !important;
background-position: center right !important;
}
%menu-panel-sub-panel {
position: absolute;
top: 0;
left: calc(100% + 10px);
display: none;
}
/* TODO: once everything is using ListCollection */
/* this can go */
%menu-panel [type='checkbox']:checked ~ * {
/* this needs to autocalculate */
min-height: 143px;
max-height: 143px;
}
%menu-panel [id$='-']:first-child:checked ~ ul label[for$='-'] * [role='menu'],
%menu-panel [id$='-']:first-child:checked ~ ul > li > [role='menu'] {
display: block;
}
/**/
%menu-panel > ul > li > div[role='menu'] {
@extend %menu-panel-sub-panel;
}
%menu-panel > ul > li > *:not(div[role='menu']) {
position: relative;
}
%menu-panel:not(.left) {
right: 0px;
left: auto;
}
%menu-panel.left {
left: 0px;
}
%menu-panel:not(.above) {
top: 28px;
}
%menu-panel.above {
bottom: 42px;
}
%menu-panel > ul {
margin: 0;
padding: 4px 0;
}
%menu-panel > ul,
%menu-panel > ul > li,
%menu-panel > ul > li > * {
width: 100%;
}
%menu-panel > ul > li > * {
text-align: left !important;
}
%menu-panel-separator {
padding-top: 0.35em;
}
%menu-panel-separator:not(:first-child) {
margin-top: 0.35em;
}
%menu-panel-separator:not(:empty) {
padding-left: 1em;
padding-right: 1em;
padding-bottom: 0.1em;
}
%menu-panel-header {
padding: 10px;
padding: 0.625rem var(--padding-x); /* 10px */
white-space: normal;
}
/* here the !important is only needed for what seems to be a difference */
/* with the CSS before and after compression */
/* i.e. before compression this style is applied */
/* after compression it is in the source but doesn't seem to get */
/* applied (unless you add the !important) */
%menu-panel .is-active {
position: relative !important;
}
%menu-panel .is-active > *::after {
position: absolute;
top: 50%;
margin-top: -8px;
right: 10px;
}
%menu-panel-header::before {
position: absolute;
left: 15px;
top: calc(10px + 0.1em);
}
%menu-panel-header {
max-width: fit-content;
}
@ -118,3 +10,63 @@
max-width: 200px;
}
}
%menu-panel-header::before {
position: absolute;
left: 15px;
top: calc(10px + 0.1em);
}
%menu-panel-body {
margin: 0;
padding: calc(var(--padding-y) - 0.625rem) 0; /* 10px */
}
%menu-panel-body,
%menu-panel-item,
%menu-panel-item > * {
width: 100%;
}
%menu-panel-item,
%menu-panel-button {
text-align: left;
}
%menu-panel-badge {
padding: 0 8px;
margin-left: 0.5rem; /* 8px */
}
%menu-panel-button {
display: flex;
}
%menu-panel-button::after {
margin-left: auto;
/* as we are using margin-left for right align */
/* we can't use it for an absolute margin-left */
/* so cheat with a bit of padding/translate */
padding-right: var(--padding-x);
transform: translate(calc(var(--padding-x) / 2), 0);
}
%menu-panel-separator {
padding-top: 0.375rem; /* 6px */
}
%menu-panel-separator:not(:first-child) {
margin-top: 0.275rem; /* 6px */
}
%menu-panel-separator:not(:empty) {
padding-left: var(--padding-x);
padding-right: var(--padding-x);
padding-bottom: 0.125rem; /* 2px */
}
%menu-panel.menu-panel-confirming {
overflow: hidden;
}
%menu-panel-confirmation {
position: absolute;
top: 0;
left: calc(100% + 10px);
}
%menu-panel-body {
transition: transform 150ms;
}
%menu-panel.menu-panel-confirming > ul {
transform: translateX(calc(-100% - 10px));
}

View File

@ -1,34 +1,32 @@
%menu-panel {
border: var(--decor-border-100);
border-radius: var(--decor-radius-200);
box-shadow: var(--decor-elevation-600);
}
%menu-panel > ul > li {
list-style-type: none;
%menu-panel-button-selected::after {
@extend %with-check-plain-mask, %as-pseudo;
}
%menu-panel-header {
@extend %p2;
}
%menu-panel-header + ul {
border-top: var(--decor-border-100);
border-color: rgb(var(--tone-border, var(--tone-gray-300)));
}
/* if the first item is a separator and it */
/* contains text don't add a line */
%menu-panel-separator:first-child:not(:empty) {
border: none;
}
%menu-panel-separator {
@extend %p3;
text-transform: uppercase;
font-weight: var(--typo-weight-medium);
}
%menu-panel-header + ul,
%menu-panel-separator:not(:first-child) {
border-top: var(--decor-border-100);
}
%menu-panel .is-active > *::after {
@extend %with-check-plain-mask, %as-pseudo;
}
%menu-panel {
border-color: rgb(var(--tone-gray-300));
background-color: rgb(var(--tone-gray-000));
}
%menu-panel-separator {
color: rgb(var(--tone-gray-600));
}
%menu-panel-header + ul,
%menu-panel-separator:not(:first-child) {
border-color: rgb(var(--tone-gray-300));
%menu-panel-item {
list-style-type: none;
}
%menu-panel-badge {
@extend %pill;
color: rgb(var(--tone-gray-000));
background-color: rgb(var(--tone-gray-500));
}
%menu-panel-body .informed-action {
border: 0 !important;
}

View File

@ -1,6 +1,9 @@
<Action
role="menuitem"
...attributes
@href={{@href}}
{{on 'click' (if @href @disclosure.close (noop))}}
@external={{@external}}
>
{{yield}}
</Action>

View File

@ -9,7 +9,7 @@
}}
>
{{yield (hash
Action=(component 'menu/action')
Action=(component 'menu/action' disclosure=@disclosure)
Item=(component 'menu/item')
Separator=(component 'menu/separator')
)}}

View File

@ -1,6 +1,4 @@
<li
role="separator"
...attributes
>
{{yield}}
</li>
>{{yield}}</li>

View File

@ -0,0 +1,126 @@
---
type: css
---
# Panel
Very basic 'panel' card-like CSS component currently used for menu-panels.
When building components using `panel` please make use of the CSS custom
properties available to help maintain consistency within the panel.
**Very important**: Please avoid using style attributes for doing this the
below is only for illustrative purposes. Please use this CSS component as a
building block for other CSS instead.
```hbs preview-template
<figure>
<figcaption>Panel with no padding (in dark mode)</figcaption>
<div
class={{class-map
"panel"
"theme-dark"
}}
...attributes
>
<div>
<p>Some text purposefully with no padding</p>
</div>
<hr />
<div>
<p>Along with a separator ^ again purposefully with no padding</p>
</div>
</div>
</figure>
<figure>
<figcaption>Panel using inherited padding for consistency</figcaption>
<div
class={{class-map
"panel"
}}
...attributes
>
<Action
style={{style-map
(array 'width' '100%')
(array 'border-bottom' '1px solid rgb(var(--tone-border))')
(array 'padding' 'var(--padding-x) var(--padding-y)')
}}
>
Full Width Button
</Action>
<div
style={{style-map
(array 'padding' 'var(--padding-x) var(--padding-y)')
}}
>
<p>Some text with padding</p>
</div>
<hr />
<div
style={{style-map
(array 'padding' 'var(--padding-x) var(--padding-y)')
}}
>
<p>Along with a separator ^ again with padding</p>
</div>
</div>
</figure>
<figure>
<figcaption>Panel using larger padding and different color borders</figcaption>
<div
class={{class-map
"panel"
}}
style={{style-map
(array '--padding-x' '24px')
(array '--padding-y' '24px')
(array '--tone-border' 'var(--tone-strawberry-500)')
}}
...attributes
>
<Action
style={{style-map
(array 'width' '100%')
(array 'border-bottom' '1px solid rgb(var(--tone-border))')
(array 'padding' 'var(--padding-x) var(--padding-y)')
}}
>
Full Width Button
</Action>
<div
style={{style-map
(array 'padding' 'var(--padding-x) var(--padding-y)')
}}
>
<p>Some text with padding</p>
</div>
<hr />
<div
style={{style-map
(array 'padding' 'var(--padding-x) var(--padding-y)')
}}
>
<p>Along with a separator ^ again with padding</p>
</div>
</div>
</figure>
```
```css
.panel {
@extend %panel;
}
.panel hr {
@extend %panel-separator;
}
```
## CSS Properties
| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `--tone-border` | `color` | --tone-gray-300 | Default color for all borders |
| `--padding-x` | `length` | 14px | Default x padding to be used for padding values within the component |
| `--padding-y` | `length` | 14px | Default y padding to be used for padding values within the component |

View File

@ -0,0 +1,8 @@
#docfy-demo-preview-panel {
.panel {
@extend %panel;
}
.panel hr {
@extend %panel-separator;
}
}

View File

@ -0,0 +1,2 @@
@import './skin';
@import './layout';

View File

@ -0,0 +1,11 @@
%panel {
--padding-x: 14px;
--padding-y: 14px;
/* max-height: var(--panel-height, auto); */
}
%panel {
position: relative;
}
%panel-separator {
margin: 0;
}

View File

@ -0,0 +1,17 @@
%panel {
--tone-border: var(--tone-gray-300);
border: var(--decor-border-100);
border-radius: var(--decor-radius-200);
box-shadow: var(--decor-elevation-600);
}
%panel-separator {
border-top: var(--decor-border-100);
}
%panel {
color: rgb(var(--tone-gray-900));
background-color: rgb(var(--tone-gray-000));
}
%panel,
%panel-separator {
border-color: rgb(var(--tone-border));
}

View File

@ -1,6 +1,9 @@
.popover-select {
@extend %popover-select;
}
.popover-menu .menu-panel {
position: absolute !important;
}
%popover-select label {
height: 100%;
}

View File

@ -67,7 +67,9 @@ export default Component.extend({
actions: {
dispatch: function(eventName, e) {
if (e && e.preventDefault) {
e.preventDefault();
if (typeof e.target.nodeName === 'undefined' || e.target.nodeName.toLowerCase() !== 'a') {
e.preventDefault();
}
}
this.dispatch(eventName, e);
},

View File

@ -26,6 +26,7 @@
@import 'consul-ui/components/more-popover-menu';
@import 'consul-ui/components/oidc-select';
@import 'consul-ui/components/radio-card';
@import 'consul-ui/components/panel';
@import 'consul-ui/components/pill';
@import 'consul-ui/components/popover-menu';
@import 'consul-ui/components/popover-select';

View File

@ -4,6 +4,7 @@
// temporary component debugging setup
@import 'consul-ui/components/main-nav-vertical/debug';
@import 'consul-ui/components/badge/debug';
@import 'consul-ui/components/panel/debug';
@import 'consul-ui/components/shadow-template/debug';
@import 'consul-ui/components/csv-list/debug';
@import 'consul-ui/components/horizontal-kv-list/debug';
@ -11,6 +12,9 @@
@import 'consul-ui/components/inline-alert/debug';
@import 'consul-ui/components/definition-table/debug';
.theme-dark {
@extend %theme-dark;
}
%debug-grid {
display: flex;
flex-wrap: wrap;

View File

@ -10,10 +10,11 @@ export default function(scenario, find, click) {
'I click $property on the $component',
'I click $property on the $component component',
],
function(property, component, next) {
async function(property, component, next) {
if (typeof component === 'string') {
property = `${component}.${property}`;
}
await new Promise(resolve => setTimeout(resolve, 0));
return find(property)();
}
);