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
43
src/routes/_components/radio/RadioGroup.html
Normal file
43
src/routes/_components/radio/RadioGroup.html
Normal 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>
|
17
src/routes/_components/radio/RadioGroupButton.html
Normal file
17
src/routes/_components/radio/RadioGroupButton.html
Normal 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>
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue