fix: use radiogroup for instance switcher (#1634)

* fix: use radiogroup for instance switcher

progress on #1633

* fixup

* add unique id
This commit is contained in:
Nolan Lawson 2019-11-17 20:51:28 -05:00 committed by GitHub
parent 53b6c5f6a1
commit 92d77c34be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 25 deletions

View file

@ -0,0 +1,43 @@
<!-- Modeled after https://www.w3.org/TR/2016/WD-wai-aria-practices-1.1-20160317/examples/radio/radio.html -->
<div class="radio-group focus-fix {className}"
role="radiogroup"
aria-label={label}
aria-owns={ariaOwns}
on:keydown="onKeyDown(event)"
ref:radiogroup
>
<slot></slot>
</div>
<script>
import { times } from '../../_utils/lodash-lite'
export default {
methods: {
onKeyDown (e) {
// ArrowUp and ArrowDown should change the focused/checked radio button per
// https://www.w3.org/TR/2016/WD-wai-aria-practices-1.1-20160317/examples/radio/radio.html
const { key, target } = e
if (!['ArrowUp', 'ArrowDown'].includes(key)) {
return
}
if (!target.classList.contains('radio-group-button')) {
return
}
const buttons = Array.from(this.refs.radiogroup.getElementsByClassName('radio-group-button'))
const len = buttons.length
const index = Math.max(0, buttons.findIndex(button => button.getAttribute('aria-checked') === 'true'))
const newIndex = (len + (index + (key === 'ArrowUp' ? -1 : 1))) % len // increment/decrement and wrap around
buttons[newIndex].focus()
buttons[newIndex].click()
}
},
data: () => ({
className: ''
}),
computed: {
ariaOwns: ({ length, id }) => (
times(length, index => `radio-group-button-${id}-${index}`).join(' ')
)
}
}
</script>

View file

@ -0,0 +1,17 @@
<button id="radio-group-button-{id}-{index}"
class="radio-group-button {className}"
role="radio"
aria-label={label}
title={label}
aria-checked={checked}
on:click
>
<slot></slot>
</button>
<script>
export default {
data: () => ({
className: ''
})
}
</script>

View file

@ -3,26 +3,30 @@
{#if $isUserLoggedIn} {#if $isUserLoggedIn}
<p>Instances you've logged in to:</p> <p>Instances you've logged in to:</p>
<SettingsList label="Instances"> <RadioGroup id="instance-switch" label="Switch to instance" length={numInstances}>
{#each instanceStates as instance} <SettingsList label="Instances">
<SettingsListRow> {#each instanceStates as instance}
<SettingsListButton className="instance-switcher-instance-name" <SettingsListRow>
href="/settings/instances/{instance.name}" <SettingsListButton className="instance-switcher-instance-name"
label={instance.name} href="/settings/instances/{instance.name}"
ariaLabel={instance.label} /> label={instance.name}
<div class="instance-switcher-button-wrapper"> ariaLabel={instance.label} />
<button class="instance-switcher-button" <div class="instance-switcher-button-wrapper">
aria-label={instance.switchLabel} <RadioGroupButton
title={instance.switchLabel} id="instance-switch"
aria-pressed={instance.current} className="instance-switcher-button"
on:click="onSwitchToThisInstance(event, instance.name)"> label={instance.switchLabel}
<SvgIcon className="instance-switcher-button-svg" checked={instance.current}
href={instance.current ? '#fa-star' : '#fa-star-o'} /> index={instance.index}
</button> on:click="onSwitchToThisInstance(event, instance.name)">
</div> <SvgIcon className="instance-switcher-button-svg"
</SettingsListRow> href={instance.current ? '#fa-star' : '#fa-star-o'} />
{/each} </RadioGroupButton>
</SettingsList> </div>
</SettingsListRow>
{/each}
</SettingsList>
</RadioGroup>
<p><a rel="prefetch" href="/settings/instances/add">Add another instance</a></p> <p><a rel="prefetch" href="/settings/instances/add">Add another instance</a></p>
{:else} {:else}
<p>You're not logged in to any instances.</p> <p>You're not logged in to any instances.</p>
@ -40,7 +44,7 @@
border: 1px solid var(--settings-list-item-border); border: 1px solid var(--settings-list-item-border);
min-width: 44px; min-width: 44px;
} }
.instance-switcher-button { :global(.instance-switcher-button) {
display: flex; display: flex;
position: absolute; position: absolute;
top: 0; top: 0;
@ -69,6 +73,8 @@
import SettingsListRow from '../../../_components/settings/SettingsListRow.html' import SettingsListRow from '../../../_components/settings/SettingsListRow.html'
import SettingsListButton from '../../../_components/settings/SettingsListButton.html' import SettingsListButton from '../../../_components/settings/SettingsListButton.html'
import SvgIcon from '../../../_components/SvgIcon.html' import SvgIcon from '../../../_components/SvgIcon.html'
import RadioGroup from '../../../_components/radio/RadioGroup.html'
import RadioGroupButton from '../../../_components/radio/RadioGroupButton.html'
export default { export default {
components: { components: {
@ -76,7 +82,9 @@
SettingsList, SettingsList,
SettingsListRow, SettingsListRow,
SettingsListButton, SettingsListButton,
SvgIcon SvgIcon,
RadioGroup,
RadioGroupButton
}, },
methods: { methods: {
onSwitchToThisInstance (e, instanceName) { onSwitchToThisInstance (e, instanceName) {
@ -88,13 +96,15 @@
store: () => store, store: () => store,
computed: { computed: {
instanceStates: ({ $loggedInInstancesAsList }) => ( instanceStates: ({ $loggedInInstancesAsList }) => (
$loggedInInstancesAsList.map(({ name, current }) => ({ $loggedInInstancesAsList.map(({ name, current }, index) => ({
index,
name, name,
current, current,
label: `${name} ${current ? '(current instance)' : ''}`, label: `${name} ${current ? '(current instance)' : ''}`,
switchLabel: current ? `${name} is the current instance` : `Switch to ${name}` switchLabel: `Switch to ${name}`
})) }))
) ),
numInstances: ({ $loggedInInstancesAsList }) => $loggedInInstancesAsList.length
} }
} }
</script> </script>

View file

@ -36,3 +36,11 @@ export function sum (list) {
} }
return total return total
} }
export function times (n, func) {
const res = []
for (let i = 0; i < n; i++) {
res.push(func(i))
}
return res
}