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