ui: Disclosure Component amends plus DisclosureMenu Component (#12304)

* ui: Disclosure amends plus DisclosureMenu

Co-authored-by: Jamie White <jamie@jgwhite.co.uk>
This commit is contained in:
John Cowen 2022-02-11 14:11:16 +00:00 committed by GitHub
parent ab3b765a88
commit 961f144b1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 362 additions and 39 deletions

View File

@ -0,0 +1,91 @@
# DisclosureMenu
A component to be used for making dropup/down/left/right menus i.e. Disclosure
Menus. Please see both Disclosure and Menu components for more details.
The component does not make any guesses around whether you want the panel to
be on another DOM layer/absolutely positioned so you should apply that layout
yourself, but it's root node is relatively positioned to help for the fairly
common usecase of having a floating menu.
```hbs preview-template
<figure>
<figcaption>
Non-floating Menu
</figcaption>
<DisclosureMenu as |disclosure|>
<disclosure.Action
{{on 'click' disclosure.toggle}}
>
{{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>
</DisclosureMenu>
</figure>
<figure>
<figcaption>
Floating Menu
</figcaption>
<DisclosureMenu as |disclosure|>
<disclosure.Action
{{on 'click' disclosure.toggle}}
{{css-prop 'height' returns=(set this 'height')}}
>
{{if disclosure.expanded 'Close' 'Open'}}
</disclosure.Action>
<disclosure.Menu
style={{style-map
(array 'position' 'absolute')
(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>
</disclosure.Menu>
</DisclosureMenu>
</figure>
```
## Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `expanded` | `Boolean` | false | The _initial_ state of the disclosure. Please note: this is the _initial_ state only, please use the `disclosure.open` and `disclosure.close` for controling the state. |
## Exported API
| Name | Type | Description |
| --- | --- | --- |
| `Action` | `GlimmerComponent` | A contextual '<Action />' component with aria attributes correctly applied, please note you still need to add an 'on' modifier here so you can control whether it opens on click/hover etc |
| `Menu` | `MenuComponent` | A contextual '<Menu />' component already wrapped in a disclosure.Details component |
| `toggle` | `Function` | Toggle the open/close state of the disclosure |
| `expanded` | `Boolean` | Whether the disclosure is 'expanded' or not |
| `disclosure` | `DisclosureComponentAPI` | A reference to the full DisclosureComponentAPI |
### menu.Action
An `<Action />` component with the correct aria attributes added.
### menu.Menu
A `<Menu />` component with the correct aria attributes added.
## See
- [Template Source Code](./index.hbs)
---

View File

@ -0,0 +1,6 @@
<@disclosure.Action
aria-haspopup="menu"
...attributes
>
{{yield}}
</@disclosure.Action>

View File

@ -0,0 +1,16 @@
<div
class={{class-map
"disclosure-menu"
}}
...attributes
>
<Disclosure as |disclosure|>
{{yield (hash
Action=(component 'disclosure-menu/action' disclosure=disclosure)
Menu=(component 'disclosure-menu/menu' disclosure=disclosure)
disclosure=disclosure
toggle=disclosure.toggle
expanded=disclosure.expanded
)}}
</Disclosure>
</div>

View File

@ -0,0 +1,3 @@
.disclosure-menu {
position: relative;
}

View File

@ -0,0 +1,15 @@
<@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>
</@disclosure.Details>

View File

@ -1,56 +1,213 @@
# Disclosure
A component which can be used to implement an aria Disclosure pattern.
A renderless component which can be used to implement an aria Disclosure pattern.
The disclosure exports an Action component already configured for use. But if
you want to contruct your own trigger, disclosure has all the properties to
enable you to do so.
The disclosure exports an Action component already configured for use as a
clickable action. But if you want to contruct your own trigger, disclosure has
all the properties to enable you to do so.
You should make use of the `disclosure.panel` property in order to 'tag' the
disclosure panel you are using.
You should make use of the `disclosure.Details` property in order control the
disclosure of the disclosure's content. By default it will automatically do
this for you. But if you need to control this yourself you can make use of the
`@auto` argument (see below for details).
You can use multiple `Details` components which lets you control multiple
areas with a single trigger/button.
Clicking outside will not close the disclosure by default, if you require this
functionality please combine with our `{{on-outside 'click'}}` modifier (see example).
By default, there are no aria attributes that you need to add or think about
as a consumer of the component, but you **should** make use of the
`details.id` property to set the `id=""` on the DOM element you are
disclosing. Every `Details` component has its `id` added to the `Action`
`aria-controls` attribute by default so you don't need to do this yourself if
using the `Details` component.
```hbs preview-template
<Disclosure>
<:button as |disclosure|>
<disclosure.Action
{{on-outside 'click' disclosure.close}}
{{on 'click' disclosure.toggle}}
>
{{if disclosure.expanded "Close" "Open"}}
</disclosure.Action>
</:button>
<:panel as |disclosure|>
<Disclosure as |disclosure|>
<disclosure.Action
{{on-outside 'click' disclosure.close}}
{{on 'click' disclosure.toggle}}
>
{{if disclosure.expanded "Close" "Open"}}
</disclosure.Action>
<disclosure.Details as |details|>
<p
id={{disclosure.panel}}
id={{details.id}}
>
Disclose Me!
</p>
</:panel>
</disclosure.Details>
</Disclosure>
```
You can also use multiple Details components for a single Discloure Action component to control multiple areas.
```hbs preview-template
<Disclosure as |disclosure|>
<disclosure.Action
{{on-outside 'click' disclosure.close}}
{{on 'click' disclosure.toggle}}
>
{{if disclosure.expanded "Close" "Open"}}
</disclosure.Action>
<disclosure.Details as |details|>
<p
id={{details.id}}
>
Disclose Me!
</p>
</disclosure.Details>
<disclosure.Details as |details|>
<p
id={{details.id}}
>
Disclose Me also!
</p>
</disclosure.Details>
</Disclosure>
```
Or use two buttons/Actions to control one Detail (or multiple Details).
```hbs preview-template
<Disclosure as |disclosure|>
<div
{{on-outside 'click' disclosure.close}}
>
<disclosure.Action
{{on 'click' disclosure.toggle}}
>
{{if disclosure.expanded "1 Close" "1 Open"}}
</disclosure.Action>
<disclosure.Action
{{on 'click' disclosure.toggle}}
>
{{if disclosure.expanded "2 Close" "2 Open"}}
</disclosure.Action>
<disclosure.Details as |details|>
<p
id={{details.id}}
>
Disclose Me!
</p>
</disclosure.Details>
</div>
</Disclosure>
```
If you don't want to use the automatic hiding/showing (and therefore removal from the DOM) of the Details component, you can pass a `@auto={{false}}` argument to the Details component, which allows you to either specify a Handlebars conditional yourself or use CSS to hide/show the content to be disclosed.
**Please note:** We use a `style` attribute here just for illustrative purposes, you should consider using a `class` attribute instead if you want to use CSS for disclosure.
```hbs preview-template
<Disclosure as |disclosure|>
<disclosure.Action
{{on-outside 'click' disclosure.close}}
{{on 'click' disclosure.toggle}}
>
{{if disclosure.expanded "Close" "Open"}}
</disclosure.Action>
<disclosure.Details
@auto={{false}}
as |details|>
{{#if details.expanded}}
<p
id={{details.id}}
>
Disclose Me with a hbs conditional!
</p>
{{/if}}
</disclosure.Details>
<disclosure.Details
@auto={{false}}
as |details|>
<p
id={{details.id}}
aria-hidden={{if (not details.expanded) 'false'}}
style={{style-map
(array 'display' (if (not details.expanded) 'none'))
}}
>
Disclose Me via CSS!
</p>
</disclosure.Details>
</Disclosure>
```
By making use of `@auto={{false}}` and hand-rolling your show/hide logic you
can use disclosure to implement slightly more complex UI.
```hbs preview-template
<Disclosure as |disclosure|>
<disclosure.Action
{{on-outside 'click' disclosure.close}}
{{on 'click' disclosure.toggle}}
>
{{if disclosure.expanded "Close" "Open"}}
</disclosure.Action>
<disclosure.Details
as |details|>
<p
id={{details.id}}
>
Disclose Me!
</p>
</disclosure.Details>
<disclosure.Details
@auto={{false}}
as |details|>
{{#if (not details.expanded)}}
<p
id={{details.id}}
>
Disclose Me by default but hide when the other details is disclosed!
</p>
{{/if}}
</disclosure.Details>
</Disclosure>
```
## Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `expanded` | `Boolean` | false | The _initial_ state of the disclosure. Please note: this is the _initial_ state only, please use the `disclosure.open` and `disclosure.close` for controling the state. |
## Exported API
| Name | Type | Description |
| --- | --- | --- |
| `Action` | `GlimmerComponent` | A contextual '<Action />' component with aria attributes correctly applied, please note you still need to add an 'on' modifier here so you can control whether it opens on click/hover etc |
| `Details` | `GlimmerComponent` | A contextual '<Action />' component with aria attributes correctly applied, please note you still need to add an 'on' modifier here so you can control whether it opens on click/hover etc |
| `open` | `Function` | Open the disclosure if its not already open |
| `close` | `Function` | Close the disclosure if its not already closed |
| `toggle` | `Function` | Toggle the open/close state of the disclosure |
| `expanded` | `Boolean` | Whether the disclosure is 'expanded' or not |
| `event` | `Boolean` | The event used to change the state of the disclosure |
| `button` | `string` | An id to use on the trigger for the disclosure |
| `panel` | `string` | An id to use on the panel for the disclosure |
| `button` | `string` | A unique id reference to reference the an Action with if required for a11y reasons |
| `controls` | `string` | An id to use on the panel for the disclosure |
## Slots
| Name | Description |
| --- | --- |
| `button` | Provides a configurable slot in which to add your open/close trigger |
| `panel` | Provides a configurable slot in which to add your disclosed content |
### disclosure.Action
An `<Action />` component with the correct aria attributes added.
### disclosure.Details
#### Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `auto` | `Boolean` | true | Whether to automatically control the disclosure of the details component. Set to false to control this yourself. Please be aware of using `aria-hidden` if using CSS to control the visibility of disclosure (see examples above). |
#### Exported API
| Name | Type | Description |
| --- | --- | --- |
| `id` | `String` | A unique id which you **should** (for aria reasons) use for the root DOM element you are controlling with the disclosure |
| `expanded` | `Boolean` | An alias of `disclosure.expanded`. Whether the disclosure is 'expanded' or not. If disclosure of the `Details` is controlled via CSS you **should** use this to set/unset `aria-hidden` |
## See

View File

@ -1,7 +1,6 @@
<Action
aria-expanded={{if @disclosure.expanded 'true' 'false'}}
aria-controls={{@disclosure.panel}}
id={{@disclosure.button}}
aria-controls={{@disclosure.controls}}
...attributes
>
{{yield}}

View File

@ -0,0 +1,16 @@
{{#let
(unique-id)
as |id|}}
{{#if (or
(and (eq @auto undefined) @disclosure.expanded)
(and (not-eq @auto undefined) (eq @auto false))
)
}}
{{yield (hash
id=id
expanded=@disclosure.expanded
)}}
{{/if}}
{{did-insert (fn @disclosure.add id)}}
{{will-destroy (fn @disclosure.remove id)}}
{{/let}}

View File

@ -1,5 +1,6 @@
<StateChart
@src={{state-chart 'boolean'}}
@initial={{if @expanded 'true' 'false'}}
as |State Guard Action dispatch state|>
{{#let (hash
toggle=(fn dispatch 'TOGGLE')
@ -8,22 +9,17 @@ as |State Guard Action dispatch state|>
expanded=(state-matches state 'true')
event=state.context
button=(unique-id)
panel=(unique-id)
controls=this.ids
) as |_api|}}
{{#let (assign _api (hash
Action=(component 'disclosure/action' disclosure=_api)
Details=(component 'disclosure/details' disclosure=(hash
add=this.add
remove=this.remove
expanded=(state-matches state 'true')
))
)) as |api|}}
<div
class={{class-map
'disclosure'
}}
...attributes
>
{{yield api to="button"}}
<State @matches="true">
{{yield api to="panel"}}
</State>
</div>
{{yield api}}
{{/let}}
{{/let}}
</StateChart>

View File

@ -0,0 +1,23 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { schedule } from '@ember/runloop';
export default class DisclosureComponent extends Component {
@tracked ids = '';
@action
add(id) {
schedule('afterRender', () => {
this.ids = `${this.ids}${this.ids.length > 0 ? ` ` : ``}${id}`;
});
}
@action
remove(id) {
this.ids = this.ids
.split(' ')
.filter(item => item !== id)
.join(' ');
}
}

View File

@ -13,6 +13,7 @@
@import 'consul-ui/components/confirmation-dialog';
@import 'consul-ui/components/copy-button';
@import 'consul-ui/components/definition-table';
@import 'consul-ui/components/disclosure-menu';
@import 'consul-ui/components/display-toggle';
@import 'consul-ui/components/dom-recycling-table';
@import 'consul-ui/components/empty-state';