Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(steps): support w3c WAI-ARIA pattern #1183

Merged
merged 8 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/docs/components/Steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ Breaking things down into multiple steps can improve the user experience by keep

| Prop name | Description | Type | Values | Default |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| activateOnFocus | Set the step active on navigation focus | boolean | - | <code style='white-space: nowrap; padding: 0;'>false</code> |
| animateInitially | Apply animation on the initial render | boolean | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>steps: {<br>&nbsp;&nbsp;animateInitially: false<br>}</code> |
| animated | Step navigation is animated | boolean | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>steps: {<br>&nbsp;&nbsp;animated: true<br>}</code> |
| animation | Transition animation name | [string, string, string, string] \| [string, string] | `[next`, `prev]`, `[right`, `left`, `down`, `up]` | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>steps: {<br>&nbsp;&nbsp;animation: [ "slide-next", "slide-prev", "slide-down", "slide-up",]<br>}</code> |
| ariaLabel | Accessibility aria-label to be passed to the tablist wrapper element | string | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>steps: {<br>&nbsp;&nbsp;ariaLabel: undefined<br>}</code> |
| ariaNextLabel | Accessibility next button aria label | string | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>steps: {<br>&nbsp;&nbsp;ariaNextLabel: "Next"<br>}</code> |
| ariaPreviousLabel | Accessibility previous button aria label | string | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>steps: {<br>&nbsp;&nbsp;ariaPreviousLabel: "Previous"<br>}</code> |
| hasNavigation | Next and previous buttons below the component. You can use this property if you want to use your own custom navigation items. | boolean | - | <code style='white-space: nowrap; padding: 0;'>true</code> |
Expand Down Expand Up @@ -91,7 +93,6 @@ Breaking things down into multiple steps can improve the user experience by keep

| Prop name | Description | Type | Values | Default |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| ariaRole | Role attribute to be passed to the li wrapper for better accessibility | string | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>steps: {<br>&nbsp;&nbsp;ariaRole: "tab"<br>}</code> |
| clickable | Item can be used directly to navigate.<br/>If undefined, previous steps are clickable while the others are not | boolean | - | |
| component | Component to be injected. | Component | - | |
| content | Text content, unnecessary when default slot is used | string | - | |
Expand All @@ -103,7 +104,6 @@ Breaking things down into multiple steps can improve the user experience by keep
| override | Override existing theme classes completely | boolean | - | |
| props | Props to be binded to the injected component | any | - | |
| step | Step marker content (when there is no icon) | number \| string | - | |
| tag | Step item tag name | DynamicComponent | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>steps: {<br>&nbsp;&nbsp;itemTag: "button"<br>}</code> |
| value | Item value (it will be used as v-model of wrapper component) - default is an uuid | string\|number\|object | - | |
| variant | Default style for the step.<br/>This will override parent type.<br/>Could be used to set a completed step to "success" for example | string | - | |
| visible | Show/hide item | boolean | - | <code style='white-space: nowrap; padding: 0;'>true</code> |
Expand Down
48 changes: 21 additions & 27 deletions packages/oruga/src/components/steps/StepItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ const props = withDefaults(defineProps<StepItemProps<T, C>>(), {
visible: true,
icon: () => getDefault("steps.icon"),
iconPack: () => getDefault("steps.iconPack"),
tag: () => getDefault("steps.itemTag", "button"),
ariaRole: () => getDefault("steps.ariaRole", "tab"),
content: undefined,
component: undefined,
props: undefined,
Expand All @@ -48,15 +46,14 @@ const itemValue = props.value ?? useId();

const slots = useSlots();

// provided data is a computed ref to enjure reactivity
// provided data is a computed ref to ensure reactivity
const providedData = computed<StepItemComponent<T>>(() => ({
...props,
value: itemValue,
$slots: slots,
navClasses: navItemClasses.value,
stepClasses: stepClasses.value,
labelClasses: stepLabelClasses.value,
iconClasses: stepIconClasses.value,
labelClasses: stepLabelClasses.value,
isTransitioning: isTransitioning.value,
activate,
deactivate,
Expand Down Expand Up @@ -118,45 +115,40 @@ function beforeLeave(): void {

// --- Computed Component Classes ---

const navItemClasses = defineClasses(
["navItemClass", "o-steps__nav-item"],
const stepClasses = defineClasses(
["stepClass", "o-steps__step"],
[
"navItemVariantClass",
"o-steps__nav-item--",
"stepVariantClass",
"o-steps__step--",
computed(() => parent.value?.variant || props.variant),
computed(() => !!parent.value?.variant || !!props.variant),
],
["navItemActiveClass", "o-steps__nav-item--active", null, isActive],
["stepActiveClass", "o-steps__step--active", null, isActive],
["stepClickableClass", "o-steps__step--clickable", null, isClickable],
[
"stepDisabledClass",
"o-steps__step--disabled",
null,
computed(() => props.disabled),
],
[
"navItemPreviousClass",
"o-steps__nav-item--previous",
"stepPreviousClass",
"o-steps__step--previous",
null,
computed(() => item.value.index < parent.value?.activeIndex),
],
[
"navItemNextClass",
"o-steps__nav-item--next",
"stepNextClass",
"o-steps__step--next",
null,
computed(() => item.value.index > parent.value?.activeIndex),
],
);

const stepClasses = defineClasses(
["stepClass", "o-steps__step"],
[
"stepLabelPositionClass",
"o-steps__step-label-",
"o-steps__step--label-",
computed(() => parent.value?.labelPosition),
computed(() => !!parent.value?.labelPosition),
],
["stepActiveClass", "o-steps__step--active", null, isActive],
["stepClickableClass", "o-steps__step--clickable", null, isClickable],
[
"stepDisabledClass",
"o-steps__step--disabled",
null,
computed(() => props.disabled),
],
);

const stepLabelClasses = defineClasses([
Expand Down Expand Up @@ -184,6 +176,8 @@ const panelClasses = defineClasses(["stepPanelClass", "o-steps__panel"]);
data-oruga="steps-item"
:data-id="`steps-${item.identifier}`"
:class="panelClasses"
role="tabpanel"
:hidden="!isActive"
:aria-labelledby="`tab-${item.identifier}`"
aria-roledescription="item">
<!--
Expand Down
Loading