Skip to content

Commit

Permalink
Merge pull request #368 from PrestaShopCorp/346-feature-accessibility…
Browse files Browse the repository at this point in the history
…-no-possibility-to-read-select-content-on-components

[Improve-Accessibility] - fixed #346 #317 #363
  • Loading branch information
mattgoud authored Sep 18, 2024
2 parents 2d85aa1 + 7ae377e commit 8eddf24
Show file tree
Hide file tree
Showing 70 changed files with 1,426 additions and 344 deletions.
14 changes: 11 additions & 3 deletions docs/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
module.exports = {
import tailwindcss from 'tailwindcss';
import autoprefixer from 'autoprefixer';
import type { StorybookConfig } from '@storybook/vue3-vite';

const config: StorybookConfig = {
stories: [
'../stories/*/**.@(js|jsx|ts|tsx|mdx)',
'../../packages/components/**/*.stories.@(js|jsx|ts|tsx|mdx)'
Expand All @@ -16,10 +20,12 @@ module.exports = {
options: {}
},
async viteFinal(config) {
config.resolve.dedupe = ['@storybook/client-api'];
if (config.resolve) {
config.resolve.dedupe = ['@storybook/client-api'];
}
config.css = {
postcss: {
plugins: [require('tailwindcss'), require('autoprefixer')]
plugins: [tailwindcss, autoprefixer]
}
};
return {
Expand All @@ -37,3 +43,5 @@ module.exports = {
autodocs: true
}
};

export default config;
3 changes: 2 additions & 1 deletion docs/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { resolve } from 'path';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@prestashopcorp/puik-theme/assets': resolve(__dirname, './node_modules/@prestashopcorp/puik-theme/assets'),
Expand Down
26 changes: 25 additions & 1 deletion packages/components/accordion/src/accordion.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@
'puik-accordion--disabled': disabled,
'puik-accordion--border-none': borderNone,
}"
role="region"
:aria-labelledby="`header-${id}`"
>
<button
:id="`header-${id}`"
ref="accordionHeader"
:aria-expanded="isExpanded"
:aria-controls="id"
class="puik-accordion__header"
:disabled="disabled"
:data-test="dataTest != undefined ? `button-${dataTest}` : undefined"
role="button"
:aria-label="title"
@click="onClick"
@keydown="onKeydown"
>
<puik-icon
v-if="icon"
Expand Down Expand Up @@ -50,15 +57,17 @@
<div
v-show="isExpanded"
:id="id"
ref="accordionContent"
class="puik-accordion__content"
tabindex="-1"
>
<slot />
</div>
</div>
</template>

<script setup lang="ts">
import { computed, inject } from 'vue';
import { computed, inject, nextTick, ref } from 'vue';
import { generateId } from '@prestashopcorp/puik-utils';
import { PuikIcon } from '@prestashopcorp/puik-components/icon';
import { accordionGroupKey } from './accordion-group';
Expand All @@ -84,9 +93,24 @@ const isExpanded = computed(() => {
accordionsList.value.push({ name: props.name, expanded: isExpanded.value });
const accordionContent = ref<HTMLDivElement | null>(null);
const accordionHeader = ref<HTMLButtonElement | null>(null);
function onClick() {
handleChange(props.name);
emit('click', props.name);
nextTick(() => {
if (isExpanded.value && accordionContent.value) {
accordionContent.value.focus();
}
});
}
function onKeydown(event: KeyboardEvent) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
onClick();
}
}
</script>

Expand Down
3 changes: 2 additions & 1 deletion packages/components/alert/src/alert.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '@prestashopcorp/puik-components/alert/style/css';
import type Alert from './alert.vue';
import { PuikAriaLive } from '@prestashopcorp/puik-components/base/src/common';

export enum PuikAlertVariants {
Success = 'success',
Expand All @@ -24,7 +25,7 @@ export interface AlertProps {
buttonLabel?: string
buttonWrapLabel?: boolean
linkLabel?: string
ariaLive?: 'polite' | 'assertive'
ariaLive?: `${PuikAriaLive}`
dataTest?: string
}

Expand Down
25 changes: 20 additions & 5 deletions packages/components/alert/src/alert.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
`puik-alert--${variant}`,
{ 'puik-alert--no-borders': disableBorders },
]"
role="alert"
v-bind="{
...(title && title.trim() && { 'aria-labelledby': `title-${ariaId}` }),
...(($slots.default || (description && description.trim())) && { 'aria-describedby': `description-${ariaId}` })
}"
:aria-live="ariaLive"
:data-test="dataTest"
tabindex="0"
>
<div class="puik-alert__container">
<div class="puik-alert__content">
Expand All @@ -16,26 +22,31 @@
class="puik-alert__icon"
/>
<div class="puik-alert__text">
<p
<h4
v-if="title"
:id="`title-${ariaId}`"
class="puik-alert__title"
:data-test="dataTest != undefined ? `title-${dataTest}` : undefined"
>
{{ title }}
</p>
<span
</h4>
<p
v-if="$slots.default || description"
:id="`description-${ariaId}`"
class="puik-alert__description"
:data-test="
dataTest != undefined ? `description-${dataTest}` : undefined
"
><slot>{{ description }}</slot></span>
>
<slot>{{ description }}</slot>
</p>
</div>
</div>
<PuikLink
v-if="linkLabel"
class="puik-alert__link"
:data-test="dataTest != undefined ? `link-${dataTest}` : undefined"
tabindex="0"
@click="clickLink"
>
{{ linkLabel }}
Expand Down Expand Up @@ -65,9 +76,11 @@

<script setup lang="ts">
import { computed } from 'vue';
import { generateId } from '@prestashopcorp/puik-utils';
import { PuikButton } from '@prestashopcorp/puik-components/button';
import { PuikIcon } from '@prestashopcorp/puik-components/icon';
import { PuikLink } from '@prestashopcorp/puik-components/link';
import { PuikAriaLive } from '@prestashopcorp/puik-components/base/src/common';
import { PuikAlertVariants, ICONS } from './alert';
import type { AlertProps, AlertEmits } from './alert';
defineOptions({
Expand All @@ -76,11 +89,13 @@ defineOptions({
const props = withDefaults(defineProps<AlertProps>(), {
variant: PuikAlertVariants.Success,
ariaLive: 'polite'
ariaLive: PuikAriaLive.Polite
});
const emit = defineEmits<AlertEmits>();
const ariaId = `${generateId()}`;
const icon = computed(() => ICONS[props.variant]);
const click = (event: Event) => emit('click', event);
Expand Down
17 changes: 17 additions & 0 deletions packages/components/alert/stories/alert.stories.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { action } from '@storybook/addon-actions';
import { PuikAriaLive } from '@prestashopcorp/puik-components/base/src/common';
import { PuikAlert, PuikAlertVariants } from '@prestashopcorp/puik-components';
import type { StoryObj, Meta, StoryFn, Args } from '@storybook/vue3';

const alertVariants = Object.values(PuikAlertVariants);
const alertVariantsSummary = alertVariants.join('|');

const alertAriaLive = Object.values(PuikAriaLive);
const alertAriaLiveSummary = alertAriaLive.join('|');

export default {
title: 'Components/Alert',
component: PuikAlert,
Expand Down Expand Up @@ -62,6 +66,19 @@ export default {
},
linkLabel: {
description: 'Label of the link'
},
ariaLive: {
description: 'option for "aria-live" attribute',
control: 'select',
options: alertAriaLive,
table: {
defaultValue: {
summary: 'polite'
},
type: {
summary: alertAriaLiveSummary
}
}
}
},
args: {
Expand Down
23 changes: 23 additions & 0 deletions packages/components/alert/test/alert.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,27 @@ describe('Alert tests', () => {
expect(findCloseButton().attributes('data-test')).toBe('close-alert');
expect(findLink().attributes('data-test')).toBe('link-alert');
});

it('should set the aria-labelledby and aria-describedby attributes correctly', () => {
const ariaId = 'test-id';
factory({
title: 'Test Title',
description: 'Test Description'
}, {
attrs: {
'aria-labelledby': `title-${ariaId}`,
'aria-describedby': `description-${ariaId}`
}
});

expect(findAlert().attributes('aria-labelledby')).toBe(`title-${ariaId}`);
expect(findAlert().attributes('aria-describedby')).toBe(`description-${ariaId}`);
});

it('should set the aria-live attribute correctly', () => {
factory({
ariaLive: 'assertive'
});
expect(findAlert().attributes('aria-live')).toBe('assertive');
});
});
1 change: 1 addition & 0 deletions packages/components/avatar/src/avatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface AvatarProps {
lastName?: string
singleInitial?: Boolean
dataTest?: string
ariaLabel?: string
}

export type AvatarInstance = InstanceType<typeof Avatar>;
26 changes: 25 additions & 1 deletion packages/components/avatar/src/avatar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
:id="id"
:class="`puik-avatar puik-avatar--${size} puik-avatar--${type} puik-avatar--${mode}`"
:data-test="dataTest"
role="img"
:aria-label="ariaLabel || altText"
tabindex="0"
>
<img
v-if="src && type == PuikAvatarType.Photo"
:src="src"
:alt="alt"
:alt="alt || avatarAltDefault"
:data-test="dataTest != undefined ? `image-${dataTest}` : undefined"
>
<puik-icon
Expand All @@ -16,12 +19,16 @@
:font-size="ICONS_FONTSIZE[props.size]"
:color="AVATAR_MODE[props.mode]"
:data-test="dataTest != undefined ? `icon-${dataTest}` : undefined"
role="img"
:aria-label="ariaLabel ||iconAltText"
/>
<div
v-else
:key="initials"
:class="`puik-avatar_initials puik-avatar_initials--${size}`"
:data-test="dataTest != undefined ? `initials-${dataTest}` : undefined"
role="img"
:aria-label="ariaLabel || initialsAltText"
>
{{ initials }}
</div>
Expand All @@ -30,6 +37,7 @@

<script setup lang="ts">
import { computed } from 'vue';
import { useLocale } from '@prestashopcorp/puik-locale';
import { getInitialLetter } from '@prestashopcorp/puik-utils';
import { PuikIcon } from '@prestashopcorp/puik-components/icon';
import {
Expand All @@ -53,6 +61,9 @@ const props = withDefaults(defineProps<AvatarProps>(), {
lastName: ''
});
const { t } = useLocale();
const avatarAltDefault = t('puik.avatar.altDefault');
const initials = computed(() => {
const firstInitial = props.firstName
? getInitialLetter(props.firstName, 0)
Expand All @@ -72,6 +83,19 @@ const initials = computed(() => {
return initialsValue;
});
const altText = computed(() => {
if (props.type === PuikAvatarType.Photo) {
return props.alt || avatarAltDefault;
} else if (props.type === PuikAvatarType.Icon) {
return props.icon || avatarAltDefault;
} else {
return initials.value;
}
});
const iconAltText = computed(() => props.icon || avatarAltDefault);
const initialsAltText = computed(() => initials.value);
</script>

<style lang="scss">
Expand Down
11 changes: 11 additions & 0 deletions packages/components/avatar/stories/avatar.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ export default {
}
}
},
ariaLabel: {
description: 'Set the aria-label attribute for accessibility',
table: {
defaultValue: {
summary: 'undefined'
},
type: {
summary: 'string'
}
}
},
alt: {
description: 'Image alt attribute if avatar type prop is set to "photo"',
control: 'text',
Expand Down
Loading

0 comments on commit 8eddf24

Please sign in to comment.