Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: DLT-1797 move SB to docs - Popover to Root Layout #5

Open
wants to merge 4 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .markdownlint.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"no-duplicate-heading": false,
"no-trailing-punctuation": false,
"no-inline-html": false,
"first-line-h1": false
"first-line-h1": false,
"code-block-style": false
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
<dt-popover
:modal="modal"
max-width="264px"
placement="bottom"
:placement="placement"
:fallback-placements="fallbackPlacements"
>
<template #anchor="{ attrs }">
<dt-button
Expand Down Expand Up @@ -59,6 +60,16 @@ export default {
type: Boolean,
default: false,
},

placement: {
type: String,
default: 'bottom',
},

fallbackPlacements: {
type: Array,
default: () => ['auto'],
},
},
};
</script>
130 changes: 130 additions & 0 deletions apps/dialtone-documentation/docs/components/popover.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Your popover should be non-modal when:
- It is not scrollable.
- It contains only components that do not hold state (link, button).

The content slot will be rendered lazily when the popover is open. By default, the popover content will be opened when the anchor is clicked, and closed when clicking outside the content or on `ESC` key press. You may override this behaviour by using `.sync` on the open prop (or `v-model:open` in Vue 3) in which you can open or close the content using whichever condition you wish.

<dialtone-usage>
<template #do>

Expand Down Expand Up @@ -329,6 +331,92 @@ vueCode='
'
showHtmlWarning />

### Fallback placements

The popover uses [headless-tippy](https://atomiks.github.io/tippyjs/v6/headless-tippy/) and
[popper](https://popper.js.org/docs/v2/modifiers/flip/), if the popover opens in a placement where it will
be clipped, it will move to a new position. It will do this automatically by default, but if you want to
manually specify which position it will move to in what order you can do so via the fallbackPlacements prop.

<code-well-header>
<example-popover :fallback-placements="['top']" />
</code-well-header>

<code-example-tabs
vueCode='
<dt-popover
:open="onOpen"
:fallback-placements="[`top`]"
>
<template #anchor>
<dt-button>
View Popover
</dt-button>
</template>
<template
#content="{ close }"
>
<div>
<p class="d-mb4">
This is content rendered within the popover.
</p>
<dt-button
@click="close"
>
Button
</dt-button>
</div>
</template>
</dt-popover>
'
/>

### Padding

Padding options for the popover content are provided via size classes "small", "medium" or "large" in order to standardize the look of the popover content between usages. To remove the padding from the content, you can pass "none". Setting none will also allow you to set custom padding via utility classes (Ex: you only want padding on the left.).

<code-well-header>
<example-popover padding="small" />
</code-well-header>

<code-example-tabs
vueCode='
<dt-popover
:open="onOpen"
padding="small"
>
<template #anchor>
<dt-button>
View Popover
</dt-button>
</template>
<template
#content="{ close }"
>
<div>
<p class="d-mb4">
This is content rendered within the popover.
</p>
<dt-button
@click="close"
>
Button
</dt-button>
</div>
</template>
</dt-popover>
'
/>

### Force close all opened instances

When the popover is open, it will attach an event listener into the window object, so you can close the instances dispatching the `dt-popover-close` event in the window object:

```js
const e = new Event('dt-popover-close');
window.dispatchEvent(e);
```

## Vue API

<component-vue-api component-name="popover" />
Expand All @@ -343,8 +431,50 @@ Popover must contain an anchor and content element. d-modal--transparent can be

If your popover is modal, please see the accessibility section of this page regarding "focus trapping": <a href="components/modal/#accessibility">Modal Accessibility</a>. The same rules will apply here if your popover is modal.

Popovers, in their current implementation, are accessible when used as interactive components. Content will be read to screen reader users, and the popover markup by is appended to the `<body>`.

There are a few important considerations to ensure popover controls are accessible:

- The popover content will have a generic role of "dialog" ( "menu" and "listbox" are also possible roles as well).
- On open, focus will be transferred to the first focusable element within the popover, after close the triggering element will be focused.
- It is possible to include a screen reader visible only close button setting "visually-hidden-close" and "visually-hidden-close-label" props.

<component-accessible-table component-name="popover"/>

## Anchor

The anchor element that activates the popover should be fully accessible by keyboard. The easiest way to do this is by using an element like an `DtButton` that is already accessible. The user should also be able to close the popover content using the `ESC` key for most ARIA roles.

There are some required ARIA attributes for the anchor element (such as `aria-expanded` set based on `open` and `aria-haspopup` that matches the `role`). To make this as straightforward as possible, these ARIA attributes are passed with the correct values as the `attrs` slot-scope to the anchor slot. Applying them is as simple as using `v-bind`:

```vue
<template #anchor="{ attrs }">
<dt-button v-bind="attrs">I'm accessible now!</dt-button>
</template>
```

By default, the dialog content will be labeled by the entire anchor element. To change this, you can do one of 2 things:

- Pass `aria-label`, which is the text label that will be applied to the dialog content.
- Pass `aria-labelledby`, which is an ID of the element that should be used as the descriptive label.

### Keyboard support

The below keyboard functionality is automatically implemented when using the popover component:

- The user can dismiss the popover pressing the `ESC` key, after that the focus will be returned to the element that launched it.
- The user can traverse focusable elements using the `TAB` key. If the popover has a defined header, the focus will be moved to the header buttons after the last focusable element inside content's container.

Additionally you must use the "initialFocusElement" prop to set which element is initially focused when the popover opens. You can set this to "first" to focus the first focusable element, "dialog" to focus the dialog itself, a string starting with '#' to focus an element by id within the dialog or you may pass in an HTMLElement directly. If set to "none" the focus will remain on the anchor, however this is invalid behavior if the popover is modal.

### References

- [tippyjs](https://atomiks.github.io/tippyjs/)
- [popper.js](https://popper.js.org/)
- [Apple. Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/ios/views/popovers/)
- [Spectrum. Accessibility overlay trigger](https://react-spectrum.adobe.com/react-aria/useOverlayTrigger.html)
- [Slack Design. Accessibility, a powerful design tool](https://slack.design/articles/accessibility-a-powerful-design-tool/)

<script setup>
import ExamplePopover from '@exampleComponents/ExamplePopover.vue';
</script>
18 changes: 18 additions & 0 deletions apps/dialtone-documentation/docs/components/presence.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,24 @@ showHtmlWarning />

You may wish to announce any live changes to this component via the screen reader since this is only a visual indicator.

If you'd like for a screen reader to read out any changes to Presence, you should pass the `srText` prop so it is
read by AT and set the `aria-live` attribute to either 'polite' or 'assertive'.
Even though the component has a role of "status" to assist SR apps in reading out status changes, its default
'aria-live' value is set to 'off'.

[See W3C guidelines](https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA22) for more information.

Example:

```html
<dt-presence
presence="active"
sr-text="User {{ user }} is active"
/>
```

Abbreviations / symbols should be read out in full for voiceover / screen readers.

<script setup>
import ExamplePresence from '@exampleComponents/ExamplePresence.vue';
</script>
156 changes: 153 additions & 3 deletions apps/dialtone-documentation/docs/components/radio-group.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ storybook: https://dialtone.dialpad.com/vue/?path=/story/components-radio-group-
name="fruits-radio-group-00"
legend="Fruits"
>
<dt-radio value="apple"><span >Apple</span></dt-radio>
<dt-radio value="banana"><span >Banana</span></dt-radio>
<dt-radio value="other"><span >Other</span></dt-radio>
<dt-radio value="apple"><span>Apple</span></dt-radio>
<dt-radio value="banana"><span>Banana</span></dt-radio>
<dt-radio value="other"><span>Other</span></dt-radio>
</dt-radio-group>
</code-well-header>

Expand Down Expand Up @@ -88,6 +88,138 @@ vueCode='
'
showHtmlWarning />

### With Options

Passing in Radio components programmatically using an options object.

<code-well-header>
<dt-radio-group
ref="optionsExample"
v-model="selectedFruits"
name="fruits-radio-group"
legend="Fruits"
>
<dt-radio
v-for="option in options"
:key="option.value"
:value="option.value"
>
<span>{{ option.label }}</span>
</dt-radio>
</dt-radio-group>
</code-well-header>

<code-example-tabs
:htmlCode='() => $refs.optionsExample'
vueCode='
<dt-radio-group
v-model="selectedFruits"
name="fruits-radio-group"
legend="Fruits"
>
<dt-radio
v-for="option in options"
:key="option.value"
:value="option.value"
>
<span>{{ option.label }}</span>
</dt-radio>
</dt-radio-group>
'
/>

### Without Legend

When no legend is provided it is expected that an `aria-label` is passed into the component.

<code-well-header>
<dt-radio-group
ref="ariaLabelExample"
name="fruits-radio-group"
aria-label="Fruits"
>
<dt-radio value="pear">Pear</dt-radio>
<dt-radio value="kiwi">Kiwi</dt-radio>
</dt-radio-group>
</code-well-header>

<code-example-tabs
:htmlCode='() => $refs.ariaLabelExample'
vueCode='
<dt-radio-group
name="fruits-radio-group"
aria-label="Fruits"
>
<dt-radio value="pear">Pear</dt-radio>
<dt-radio value="kiwi">Kiwi</dt-radio>
</dt-radio-group>
'
/>

### With Slotted Legend

The legend can also be passed by slot.

<code-well-header>
<dt-radio-group
ref="slottedLegendExample"
name="fruits-radio-group"
>
<dt-radio value="pear">Pear</dt-radio>
<dt-radio value="kiwi">Kiwi</dt-radio>
<template #legend>
Fruits
</template>
</dt-radio-group>
</code-well-header>

<code-example-tabs
:htmlCode='() => $refs.slottedLegendExample'
vueCode='
<dt-radio-group
name="fruits-radio-group"
>
<dt-radio value="pear">Pear</dt-radio>
<dt-radio value="kiwi">Kiwi</dt-radio>
<template #legend>
Fruits
</template>
</dt-radio-group>
'
/>

### With Event Hander

The event handler is only needed if you need to do additional processing. The v-model is automatically updated.

<code-well-header>
<dt-radio-group
ref="eventHandlerExample"
v-model="selectedFruits"
name="fruits-radio-group"
legend="Fruits"
@input="onInput"
>
<dt-radio value="pear">Pear</dt-radio>
<dt-radio value="kiwi">Kiwi</dt-radio>
</dt-radio-group>
</code-well-header>

<code-example-tabs
:htmlCode='() => $refs.eventHandlerExample'
vueCode='
<dt-radio-group
v-model="selectedFruits"
name="fruits-radio-group"
legend="Fruits"
@input="onInput"
>
<dt-radio value="pear">Pear</dt-radio>
<dt-radio value="kiwi">Kiwi</dt-radio>
</dt-radio-group>
'
/>

### With validation states

<code-well-header>
Expand Down Expand Up @@ -205,3 +337,21 @@ showHtmlWarning />
## Vue API

<component-vue-api component-name="radiogroup" />

## Accessibility

Radio Groups are typically paired with a legend which identifies the group. If no legend is provided then it is expected
that an `aria-label` will be given in order to provide an invisible label to screen readers.

<script setup>
import { ref } from 'vue';
const options = [
{ label: 'Apple', value: 'apple' },
{ label: 'Banana', value: 'banana' },
{ label: 'Other', value: 'other' }
];
const selectedFruits = ref('apple');

const onInput = () => {};

</script>
Loading