Skip to content

Commit 780d521

Browse files
committed
feat: update ExtensionCard and ExtensionCardActions to use role="switch" for better accessibility
1 parent 918a48b commit 780d521

6 files changed

Lines changed: 53 additions & 19 deletions

File tree

packages/components/src/extension-manager/components/ExtensionCard.vue

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,15 @@ const handleNameKeydown = (event: KeyboardEvent) => {
131131
/>
132132
</div>
133133

134-
<div v-if="normalizedProgress !== undefined" class="tr-extension-card__progress">
134+
<div
135+
v-if="normalizedProgress !== undefined"
136+
class="tr-extension-card__progress"
137+
role="progressbar"
138+
:aria-label="name"
139+
aria-valuemin="0"
140+
aria-valuemax="100"
141+
:aria-valuenow="normalizedProgress === 'indeterminate' ? undefined : normalizedProgress"
142+
>
135143
<span
136144
class="tr-extension-card__progress-bar"
137145
:class="{ 'is-indeterminate': normalizedProgress === 'indeterminate' }"

packages/components/src/extension-manager/components/ExtensionCardActions.vue

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,15 @@ const emit = defineEmits<{
2525
const handleSwitch = (action: Extract<ExtensionCardRenderableAction, { type: 'switch' }>, event: Event) => {
2626
if (action.disabled) return
2727
28+
const input = event.currentTarget as HTMLInputElement
29+
const checked = input.checked
30+
31+
input.checked = Boolean(action.checked)
32+
2833
emit('action', {
2934
id: action.id,
3035
type: action.type,
31-
checked: (event.target as HTMLInputElement).checked,
36+
checked,
3237
})
3338
}
3439
@@ -57,6 +62,7 @@ const handleCustom = (action: Extract<ExtensionCardRenderableAction, { type: 'cu
5762
>
5863
<input
5964
type="checkbox"
65+
role="switch"
6066
:checked="action.checked"
6167
:disabled="action.disabled"
6268
:aria-label="action.label"
@@ -140,14 +146,14 @@ const handleCustom = (action: Extract<ExtensionCardRenderableAction, { type: 'cu
140146
}
141147
142148
.tr-extension-card-primary-actions__switch input:checked + .tr-extension-card-primary-actions__switch-track::after {
143-
transform: translateX(16px);
149+
transform: translateX(18px);
144150
}
145151
146152
.tr-extension-card-primary-actions__switch-track {
147153
position: relative;
148154
display: block;
149-
width: 36px;
150-
height: 20px;
155+
width: 40px;
156+
height: 22px;
151157
border-radius: 999px;
152158
background: var(--tr-extension-card-switch-bg-color);
153159
transition: background 0.2s ease;
@@ -157,8 +163,8 @@ const handleCustom = (action: Extract<ExtensionCardRenderableAction, { type: 'cu
157163
position: absolute;
158164
top: 2px;
159165
left: 2px;
160-
width: 16px;
161-
height: 16px;
166+
width: 18px;
167+
height: 18px;
162168
border-radius: 50%;
163169
background: #fff;
164170
box-shadow: 0 1px 3px rgb(0 0 0 / 16%);
@@ -172,14 +178,13 @@ const handleCustom = (action: Extract<ExtensionCardRenderableAction, { type: 'cu
172178
justify-content: center;
173179
gap: 6px;
174180
min-width: 64px;
175-
padding: 4px 8px;
181+
padding: 4px 12px;
176182
border: 0;
177183
border-radius: 999px;
178184
background: var(--tr-extension-card-bg-color-hover);
179185
color: var(--tr-text-primary);
180186
cursor: pointer;
181-
font-size: 12px;
182-
line-height: 16px;
187+
font-size: 14px;
183188
transition: background-color 0.2s ease;
184189
}
185190

packages/test/component/extension-manager/ExtensionCard.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ test.describe('standalone ExtensionCard', () => {
2929
const overflowRegion = card.locator('.tr-extension-card__more-action')
3030
const overflowMenu = overflowRegion.locator('.tr-extension-card__more-menu')
3131

32-
await expect(primaryRegion.getByRole('checkbox', { name: '启用扩展' })).toBeVisible()
32+
await expect(primaryRegion.getByRole('switch', { name: '启用扩展' })).toBeVisible()
3333
await expect(primaryRegion.getByRole('button', { name: '禁用操作' })).toBeDisabled()
3434
await expect(primaryRegion.getByRole('button', { name: '隐藏操作' })).toHaveCount(0)
3535
await expect(primaryRegion.getByRole('button', { name: '安装' })).toHaveCount(0)
@@ -38,14 +38,14 @@ test.describe('standalone ExtensionCard', () => {
3838
await expect(overflowMenu).toBeHidden()
3939

4040
await overflowRegion.getByRole('button', { name: '扩展操作' }).click()
41-
await expect(primaryRegion.getByRole('checkbox', { name: '启用扩展' })).toBeVisible()
41+
await expect(primaryRegion.getByRole('switch', { name: '启用扩展' })).toBeVisible()
4242
await expect(primaryRegion.getByRole('button', { name: '禁用操作' })).toBeDisabled()
4343
await expect(primaryRegion.getByRole('button', { name: '安装' })).toHaveCount(0)
4444
await expect(primaryRegion.getByRole('button', { name: '检查' })).toHaveCount(0)
4545
await expect(overflowMenu).toBeVisible()
4646
await expect(overflowMenu.getByRole('button', { name: '安装' })).toBeVisible()
4747
await expect(overflowMenu.getByRole('button', { name: '检查' })).toBeVisible()
48-
await expect(overflowMenu.getByRole('checkbox', { name: '启用扩展' })).toHaveCount(0)
48+
await expect(overflowMenu.getByRole('switch', { name: '启用扩展' })).toHaveCount(0)
4949
await expect(overflowMenu.getByRole('button', { name: '禁用操作' })).toHaveCount(0)
5050
await expect(overflowMenu.getByRole('button', { name: '隐藏操作' })).toHaveCount(0)
5151
})

packages/test/component/extension-manager/ExtensionCardActionEvent.fixture.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const actions = ref<ExtensionCardAction[]>([
1313
{ id: 'delete-extension', type: 'button', label: '删除' },
1414
])
1515
const lastEvent = ref<ExtensionCardActionEvent>()
16+
const commitSwitchChanges = ref(true)
1617
1718
const eventChecked = computed(() => {
1819
return typeof lastEvent.value?.checked === 'boolean' ? String(lastEvent.value.checked) : ''
@@ -22,7 +23,7 @@ const eventPayload = computed(() => JSON.stringify(lastEvent.value?.payload ?? n
2223
2324
const handleAction = (event: ExtensionCardActionEvent) => {
2425
lastEvent.value = event
25-
if (event.type !== 'switch' || typeof event.checked !== 'boolean') return
26+
if (event.type !== 'switch' || typeof event.checked !== 'boolean' || !commitSwitchChanges.value) return
2627
2728
actions.value = actions.value.map((action) =>
2829
action.id === event.id && action.type === 'switch' ? { ...action, checked: event.checked } : action,
@@ -32,6 +33,10 @@ const handleAction = (event: ExtensionCardActionEvent) => {
3233

3334
<template>
3435
<div>
36+
<button data-testid="ignore-switch-updates" type="button" @click="commitSwitchChanges = false">
37+
Ignore switch updates
38+
</button>
39+
3540
<ExtensionCard
3641
data-testid="action-event-card"
3742
name="Action event card"

packages/test/component/extension-manager/ExtensionCardActionEvent.spec.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,33 @@ import { expect, test } from '@playwright/experimental-ct-vue'
22
import ExtensionCardActionEventFixture from './ExtensionCardActionEvent.fixture.vue'
33

44
test.describe('standalone ExtensionCard action events', () => {
5-
test('emits a controlled switch event with the resulting checked value', async ({ mount }) => {
5+
test('emits the next checked value for a controlled switch', async ({ mount }) => {
66
const component = await mount(ExtensionCardActionEventFixture)
7-
const toggle = component.getByRole('checkbox', { name: '扩展开关' })
7+
const toggle = component.getByRole('switch', { name: '扩展开关' })
88

99
await expect(toggle).toBeChecked()
1010
await component.locator('.tr-extension-card-primary-actions__switch-track').click()
1111
await expect(toggle).not.toBeChecked()
1212
await expect(component.getByTestId('event-id')).toHaveText('toggle-extension')
1313
await expect(component.getByTestId('event-type')).toHaveText('switch')
1414
await expect(component.getByTestId('event-checked')).toHaveText('false')
15+
16+
await component.locator('.tr-extension-card-primary-actions__switch-track').click()
17+
await expect(toggle).toBeChecked()
18+
await expect(component.getByTestId('event-checked')).toHaveText('true')
19+
})
20+
21+
test('keeps the native checked state controlled by props when switch updates are not committed', async ({
22+
mount,
23+
}) => {
24+
const component = await mount(ExtensionCardActionEventFixture)
25+
const toggle = component.getByRole('switch', { name: '扩展开关' })
26+
27+
await component.getByTestId('ignore-switch-updates').click()
28+
await component.locator('.tr-extension-card-primary-actions__switch-track').click()
29+
30+
await expect(component.getByTestId('event-checked')).toHaveText('false')
31+
await expect(toggle).toBeChecked()
1532
})
1633

1734
test('emits a button action with its presentation type', async ({ mount }) => {

packages/test/component/extension-manager/ExtensionCardGrid.spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ test.describe('standalone ExtensionCardGrid', () => {
1616
await expect(alphaCard).toHaveCount(1)
1717
await expect(alphaCard.getByText('Alpha extension', { exact: true })).toBeVisible()
1818
await expect(alphaCard.getByText('Alpha description', { exact: true })).toBeVisible()
19-
await expect(alphaCard.getByRole('checkbox', { name: 'Enable Alpha' })).toBeVisible()
19+
await expect(alphaCard.getByRole('switch', { name: 'Enable Alpha' })).toBeVisible()
2020
await expect(alphaCard.getByRole('button', { name: 'Install Alpha' })).toBeVisible()
2121
await expect(alphaCard.getByRole('button', { name: 'Inspect Alpha' })).toBeVisible()
2222
})
@@ -136,12 +136,11 @@ test.describe('standalone ExtensionCardGrid', () => {
136136
const component = await mount(ExtensionCardGridFixture)
137137
const card = component.getByTestId('default-grid').locator(':scope > li[data-card-id="alpha"] > *')
138138

139-
await card.getByRole('checkbox', { name: 'Enable Alpha' }).uncheck({ force: true })
140139
await card.getByRole('button', { name: 'Install Alpha' }).click()
141140
await card.getByRole('button', { name: 'Inspect Alpha' }).click()
142141

143142
await expect(component.getByTestId('action-events')).toHaveText(
144-
'alpha:toggle-alpha:switch:false|alpha:install-alpha:button:|alpha:inspect-alpha:custom:',
143+
'alpha:install-alpha:button:|alpha:inspect-alpha:custom:',
145144
)
146145

147146
await card.getByRole('button', { name: 'Alpha extension' }).click()

0 commit comments

Comments
 (0)