feat: manage focus and announce steps in guided wizard
Quality / test (push) Canceled after 0s

This commit is contained in:
2026-09-10 06:37:02 -04:00
parent 1fa4bb1c7a
commit 934aad2afd
4 changed files with 55 additions and 13 deletions
@@ -1,8 +1,27 @@
import { expect, userEvent, within } from "@storybook/test";
import AdminHomePage from "./AdminHomePage.vue";
export default { title: "Pages/AdminHomePage", component: AdminHomePage };
export const Overview = {};
export const WizardStepFocusManagement = {
parameters: {
docs: {
description: {
story:
"Selecting a wizard stage moves focus to that stage's panel and announces the step change for assistive technology.",
},
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole("button", { name: /Sources/ }));
await expect(canvas.getByRole("region", { name: "Sources step" })).toHaveFocus();
await expect(await canvas.findByText("Step 2 of 6: Sources - Link markets")).toBeInTheDocument();
},
};
export const ProviderMappingWorkflow = {
parameters: {
docs: {
+33 -13
View File
@@ -1,5 +1,5 @@
<script setup>
import { computed, onMounted, ref } from "vue";
import { computed, nextTick, onMounted, ref } from "vue";
import { useAuth } from "@/composables/useAuth.js";
import InventoryWorkflowStepper from "@/components/molecules/InventoryWorkflowStepper.vue";
@@ -428,10 +428,23 @@ async function overridePricing() {
}
}
const wizardPanels = {};
function setWizardPanelRef(stepId, el) {
wizardPanels[stepId] = el;
}
const activeStepAnnouncement = computed(() => {
const index = activeStepIndex.value;
const step = workflowSteps.value[index];
if (!step) return "";
return `Step ${index + 1} of ${WIZARD_STEP_IDS.length}: ${step.label} - ${step.description}`;
});
function goToStep(stepId) {
if (!WIZARD_STEP_IDS.includes(stepId)) return;
activeStepId.value = stepId;
localStorage.setItem(WIZARD_STORAGE_KEY, stepId);
nextTick(() => wizardPanels[stepId]?.focus());
}
function goToNextStep() {
@@ -586,10 +599,11 @@ onMounted(() => {
</section>
<InventoryWorkflowStepper :steps="workflowSteps" :active-step-id="activeStepId" @select="goToStep" />
<p class="visually-hidden" role="status">{{ activeStepAnnouncement }}</p>
<div class="admin-wizard">
<ProductIdentityStep
v-show="activeStepId === 'workflow-product'"
<div v-show="activeStepId === 'workflow-product'" :ref="(el) => setWizardPanelRef('workflow-product', el)" tabindex="-1" role="region" aria-label="Product step">
<ProductIdentityStep
:product-name="productName"
:category-id="categoryId"
:categories="categories"
@@ -598,8 +612,9 @@ onMounted(() => {
@update:category-id="categoryId = $event"
@submit="createProduct"
/>
<ProviderMappingStep
v-show="activeStepId === 'workflow-mapping'"
</div>
<div v-show="activeStepId === 'workflow-mapping'" :ref="(el) => setWizardPanelRef('workflow-mapping', el)" tabindex="-1" role="region" aria-label="Sources step">
<ProviderMappingStep
:product-id="mappingProductId"
:provider-id="mappingProviderId"
:mapping-type="mappingType"
@@ -615,8 +630,9 @@ onMounted(() => {
@product-change="loadProviderMappings($event)"
@submit="createProviderMapping"
/>
<InventoryCopyStep
v-show="activeStepId === 'workflow-inventory'"
</div>
<div v-show="activeStepId === 'workflow-inventory'" :ref="(el) => setWizardPanelRef('workflow-inventory', el)" tabindex="-1" role="region" aria-label="Copy step">
<InventoryCopyStep
:product-id="inventoryProductId"
:condition-id="conditionId"
:products="products"
@@ -626,13 +642,15 @@ onMounted(() => {
@update:condition-id="conditionId = $event"
@submit="createInventory"
/>
<InventoryImagesStep
v-show="activeStepId === 'workflow-images'"
</div>
<div v-show="activeStepId === 'workflow-images'" :ref="(el) => setWizardPanelRef('workflow-images', el)" tabindex="-1" role="region" aria-label="Images step">
<InventoryImagesStep
:inventory="inventory"
:missing-images="dashboard?.counts.missingImages ?? 0"
/>
<PricingReviewStep
v-show="activeStepId === 'workflow-pricing'"
</div>
<div v-show="activeStepId === 'workflow-pricing'" :ref="(el) => setWizardPanelRef('workflow-pricing', el)" tabindex="-1" role="region" aria-label="Pricing step">
<PricingReviewStep
:inventory-item-id="pricingInventoryId"
:provider="pricingProvider"
:source-identifier="pricingSource"
@@ -652,8 +670,9 @@ onMounted(() => {
@refresh="refreshPricing"
@override="overridePricing"
/>
<ReviewListStep
v-show="activeStepId === 'workflow-listing'"
</div>
<div v-show="activeStepId === 'workflow-listing'" :ref="(el) => setWizardPanelRef('workflow-listing', el)" tabindex="-1" role="region" aria-label="List step">
<ReviewListStep
:inventory-item-id="listingInventoryId"
:asking-price="askingPrice"
:inventory="inventory"
@@ -662,6 +681,7 @@ onMounted(() => {
@update:asking-price="askingPrice = $event"
@submit="createListing"
/>
</div>
<div class="admin-wizard__nav">
<button class="button button--secondary" type="button" :disabled="activeStepIndex === 0" @click="goToPreviousStep">Back</button>
<button class="button button--secondary" type="button" :disabled="activeStepIndex === WIZARD_STEP_IDS.length - 1" @click="goToNextStep">Next</button>