This commit is contained in:
@@ -9,6 +9,7 @@ button, input, select, textarea { font: inherit; }
|
|||||||
button, a, input, select, textarea { &:focus-visible { outline: var(--border-strong) solid var(--color-focus); outline-offset: 3px; box-shadow: var(--glow-focus); } }
|
button, a, input, select, textarea { &:focus-visible { outline: var(--border-strong) solid var(--color-focus); outline-offset: 3px; box-shadow: var(--glow-focus); } }
|
||||||
button { cursor: pointer; }
|
button { cursor: pointer; }
|
||||||
h1, h2, h3, h4 { margin: 0; font-family: var(--font-display); line-height: var(--leading-tight); }
|
h1, h2, h3, h4 { margin: 0; font-family: var(--font-display); line-height: var(--leading-tight); }
|
||||||
|
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; border: 0; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
|
||||||
|
|
||||||
.catalog-page, .detail-page, .showcase-page { min-height: 100vh; background: var(--color-background); }
|
.catalog-page, .detail-page, .showcase-page { min-height: 100vh; background: var(--color-background); }
|
||||||
.catalog-header, .catalog-results { width: min(1120px, calc(100% - var(--space-8))); margin: 0 auto; }
|
.catalog-header, .catalog-results { width: min(1120px, calc(100% - var(--space-8))); margin: 0 auto; }
|
||||||
@@ -55,6 +56,7 @@ h1 { max-width: 760px; margin: var(--space-3) 0 var(--space-4); font-size: var(-
|
|||||||
.admin-workspace-grid form { display: grid; gap: var(--space-3); }
|
.admin-workspace-grid form { display: grid; gap: var(--space-3); }
|
||||||
.admin-wizard { display: grid; gap: var(--space-4); margin: var(--space-8) 0; }
|
.admin-wizard { display: grid; gap: var(--space-4); margin: var(--space-8) 0; }
|
||||||
.admin-wizard__nav { display: flex; justify-content: space-between; gap: var(--space-3); }
|
.admin-wizard__nav { display: flex; justify-content: space-between; gap: var(--space-3); }
|
||||||
|
.admin-wizard > [role="region"]:focus { outline: var(--border-strong) solid var(--color-focus); outline-offset: 3px; }
|
||||||
.admin-list { margin-top: var(--space-8); }
|
.admin-list { margin-top: var(--space-8); }
|
||||||
.admin-list h2 { margin-bottom: var(--space-3); font-size: var(--text-xl); }
|
.admin-list h2 { margin-bottom: var(--space-3); font-size: var(--text-xl); }
|
||||||
.admin-list ul { display: grid; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
|
.admin-list ul { display: grid; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
|
||||||
|
|||||||
@@ -1,8 +1,27 @@
|
|||||||
|
import { expect, userEvent, within } from "@storybook/test";
|
||||||
|
|
||||||
import AdminHomePage from "./AdminHomePage.vue";
|
import AdminHomePage from "./AdminHomePage.vue";
|
||||||
|
|
||||||
export default { title: "Pages/AdminHomePage", component: AdminHomePage };
|
export default { title: "Pages/AdminHomePage", component: AdminHomePage };
|
||||||
export const Overview = {};
|
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 = {
|
export const ProviderMappingWorkflow = {
|
||||||
parameters: {
|
parameters: {
|
||||||
docs: {
|
docs: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { computed, nextTick, onMounted, ref } from "vue";
|
||||||
|
|
||||||
import { useAuth } from "@/composables/useAuth.js";
|
import { useAuth } from "@/composables/useAuth.js";
|
||||||
import InventoryWorkflowStepper from "@/components/molecules/InventoryWorkflowStepper.vue";
|
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) {
|
function goToStep(stepId) {
|
||||||
if (!WIZARD_STEP_IDS.includes(stepId)) return;
|
if (!WIZARD_STEP_IDS.includes(stepId)) return;
|
||||||
activeStepId.value = stepId;
|
activeStepId.value = stepId;
|
||||||
localStorage.setItem(WIZARD_STORAGE_KEY, stepId);
|
localStorage.setItem(WIZARD_STORAGE_KEY, stepId);
|
||||||
|
nextTick(() => wizardPanels[stepId]?.focus());
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToNextStep() {
|
function goToNextStep() {
|
||||||
@@ -586,10 +599,11 @@ onMounted(() => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<InventoryWorkflowStepper :steps="workflowSteps" :active-step-id="activeStepId" @select="goToStep" />
|
<InventoryWorkflowStepper :steps="workflowSteps" :active-step-id="activeStepId" @select="goToStep" />
|
||||||
|
<p class="visually-hidden" role="status">{{ activeStepAnnouncement }}</p>
|
||||||
|
|
||||||
<div class="admin-wizard">
|
<div class="admin-wizard">
|
||||||
|
<div v-show="activeStepId === 'workflow-product'" :ref="(el) => setWizardPanelRef('workflow-product', el)" tabindex="-1" role="region" aria-label="Product step">
|
||||||
<ProductIdentityStep
|
<ProductIdentityStep
|
||||||
v-show="activeStepId === 'workflow-product'"
|
|
||||||
:product-name="productName"
|
:product-name="productName"
|
||||||
:category-id="categoryId"
|
:category-id="categoryId"
|
||||||
:categories="categories"
|
:categories="categories"
|
||||||
@@ -598,8 +612,9 @@ onMounted(() => {
|
|||||||
@update:category-id="categoryId = $event"
|
@update:category-id="categoryId = $event"
|
||||||
@submit="createProduct"
|
@submit="createProduct"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-show="activeStepId === 'workflow-mapping'" :ref="(el) => setWizardPanelRef('workflow-mapping', el)" tabindex="-1" role="region" aria-label="Sources step">
|
||||||
<ProviderMappingStep
|
<ProviderMappingStep
|
||||||
v-show="activeStepId === 'workflow-mapping'"
|
|
||||||
:product-id="mappingProductId"
|
:product-id="mappingProductId"
|
||||||
:provider-id="mappingProviderId"
|
:provider-id="mappingProviderId"
|
||||||
:mapping-type="mappingType"
|
:mapping-type="mappingType"
|
||||||
@@ -615,8 +630,9 @@ onMounted(() => {
|
|||||||
@product-change="loadProviderMappings($event)"
|
@product-change="loadProviderMappings($event)"
|
||||||
@submit="createProviderMapping"
|
@submit="createProviderMapping"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-show="activeStepId === 'workflow-inventory'" :ref="(el) => setWizardPanelRef('workflow-inventory', el)" tabindex="-1" role="region" aria-label="Copy step">
|
||||||
<InventoryCopyStep
|
<InventoryCopyStep
|
||||||
v-show="activeStepId === 'workflow-inventory'"
|
|
||||||
:product-id="inventoryProductId"
|
:product-id="inventoryProductId"
|
||||||
:condition-id="conditionId"
|
:condition-id="conditionId"
|
||||||
:products="products"
|
:products="products"
|
||||||
@@ -626,13 +642,15 @@ onMounted(() => {
|
|||||||
@update:condition-id="conditionId = $event"
|
@update:condition-id="conditionId = $event"
|
||||||
@submit="createInventory"
|
@submit="createInventory"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-show="activeStepId === 'workflow-images'" :ref="(el) => setWizardPanelRef('workflow-images', el)" tabindex="-1" role="region" aria-label="Images step">
|
||||||
<InventoryImagesStep
|
<InventoryImagesStep
|
||||||
v-show="activeStepId === 'workflow-images'"
|
|
||||||
:inventory="inventory"
|
:inventory="inventory"
|
||||||
:missing-images="dashboard?.counts.missingImages ?? 0"
|
:missing-images="dashboard?.counts.missingImages ?? 0"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-show="activeStepId === 'workflow-pricing'" :ref="(el) => setWizardPanelRef('workflow-pricing', el)" tabindex="-1" role="region" aria-label="Pricing step">
|
||||||
<PricingReviewStep
|
<PricingReviewStep
|
||||||
v-show="activeStepId === 'workflow-pricing'"
|
|
||||||
:inventory-item-id="pricingInventoryId"
|
:inventory-item-id="pricingInventoryId"
|
||||||
:provider="pricingProvider"
|
:provider="pricingProvider"
|
||||||
:source-identifier="pricingSource"
|
:source-identifier="pricingSource"
|
||||||
@@ -652,8 +670,9 @@ onMounted(() => {
|
|||||||
@refresh="refreshPricing"
|
@refresh="refreshPricing"
|
||||||
@override="overridePricing"
|
@override="overridePricing"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-show="activeStepId === 'workflow-listing'" :ref="(el) => setWizardPanelRef('workflow-listing', el)" tabindex="-1" role="region" aria-label="List step">
|
||||||
<ReviewListStep
|
<ReviewListStep
|
||||||
v-show="activeStepId === 'workflow-listing'"
|
|
||||||
:inventory-item-id="listingInventoryId"
|
:inventory-item-id="listingInventoryId"
|
||||||
:asking-price="askingPrice"
|
:asking-price="askingPrice"
|
||||||
:inventory="inventory"
|
:inventory="inventory"
|
||||||
@@ -662,6 +681,7 @@ onMounted(() => {
|
|||||||
@update:asking-price="askingPrice = $event"
|
@update:asking-price="askingPrice = $event"
|
||||||
@submit="createListing"
|
@submit="createListing"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div class="admin-wizard__nav">
|
<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 === 0" @click="goToPreviousStep">Back</button>
|
||||||
<button class="button button--secondary" type="button" :disabled="activeStepIndex === WIZARD_STEP_IDS.length - 1" @click="goToNextStep">Next</button>
|
<button class="button button--secondary" type="button" :disabled="activeStepIndex === WIZARD_STEP_IDS.length - 1" @click="goToNextStep">Next</button>
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ Remaining roadmap areas are approved external provider credentials/adapters, bro
|
|||||||
- The guided inventory workflow is now a true single-step-at-a-time wizard: the stepper is clickable, only the active stage's panel is shown, Back/Next controls advance between stages, and the active stage persists across reloads through local storage.
|
- The guided inventory workflow is now a true single-step-at-a-time wizard: the stepper is clickable, only the active stage's panel is shown, Back/Next controls advance between stages, and the active stage persists across reloads through local storage.
|
||||||
- Listing management (publish/mark pending) is now a dedicated `/admin/listings` screen with its own navigation entry; the guided workflow overview links to it and only tracks listing creation and a lightweight count for wizard completion.
|
- Listing management (publish/mark pending) is now a dedicated `/admin/listings` screen with its own navigation entry; the guided workflow overview links to it and only tracks listing creation and a lightweight count for wizard completion.
|
||||||
- Sale recording/voiding and acquisition recording are now dedicated `/admin/sales` and `/admin/acquisitions` screens with their own navigation entries; the guided workflow overview links to both instead of embedding their forms and history inline.
|
- Sale recording/voiding and acquisition recording are now dedicated `/admin/sales` and `/admin/acquisitions` screens with their own navigation entries; the guided workflow overview links to both instead of embedding their forms and history inline.
|
||||||
|
- The guided wizard now manages focus and announces step changes: selecting a stage (via the stepper or Back/Next) moves keyboard/assistive-technology focus to that stage's labeled region and updates a live-region status announcement, with Storybook interaction coverage.
|
||||||
|
|
||||||
## Local Commands
|
## Local Commands
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user