From 4f1f1456290ea7977394b3789a283e6cee8d2be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20B=C3=A9ger?= Date: Tue, 25 Aug 2026 12:55:46 +0200 Subject: [PATCH] feat(2417): add AvSkipLinks --- docs/.vitepress/config.js | 1 + docs/components/index.md | 1 + src/components.d.ts | 1 + .../navigation/AvSkipLinks/AvSkipLinks.md | 41 ++++++++++++ .../AvSkipLinks/AvSkipLinks.stories.ts | 62 +++++++++++++++++++ .../AvSkipLinks/AvSkipLinks.stub.ts | 21 +++++++ .../AvSkipLinks/AvSkipLinks.test.ts | 52 ++++++++++++++++ .../AvSkipLinks/AvSkipLinks.types.ts | 11 ++++ .../navigation/AvSkipLinks/AvSkipLinks.vue | 53 ++++++++++++++++ src/components/navigation/index.ts | 1 + 10 files changed, 244 insertions(+) create mode 100644 src/components/navigation/AvSkipLinks/AvSkipLinks.md create mode 100644 src/components/navigation/AvSkipLinks/AvSkipLinks.stories.ts create mode 100644 src/components/navigation/AvSkipLinks/AvSkipLinks.stub.ts create mode 100644 src/components/navigation/AvSkipLinks/AvSkipLinks.test.ts create mode 100644 src/components/navigation/AvSkipLinks/AvSkipLinks.types.ts create mode 100644 src/components/navigation/AvSkipLinks/AvSkipLinks.vue diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js index a5c72fd64..08530352a 100644 --- a/docs/.vitepress/config.js +++ b/docs/.vitepress/config.js @@ -185,6 +185,7 @@ export default defineConfig({ { text: 'AvSideMenu', link: '/components/navigation/AvSideMenu/AvSideMenu.md' }, { text: 'AvSideNavigation', link: '/components/navigation/AvSideNavigation/AvSideNavigation.md' }, { text: 'AvStepper', link: '/components/navigation/AvStepper/AvStepper.md' }, + { text: 'AvSkipLinks', link: '/components/navigation/AvSkipLinks/AvSkipLinks.md' }, ], collapsed: true }, diff --git a/docs/components/index.md b/docs/components/index.md index c0e22c4f7..117bc52f2 100644 --- a/docs/components/index.md +++ b/docs/components/index.md @@ -79,6 +79,7 @@ _Last updated: 2026-01-06_ - [AvSideMenu](navigation/AvSideMenu/AvSideMenu.md) - [AvSideNavigation](navigation/AvSideNavigation/AvSideNavigation.md) - [AvStepper](navigation/AvStepper/AvStepper.md) +- [AvSkipLinks](navigation/AvSkipLinks/AvSkipLinks.md) ## Overlay diff --git a/src/components.d.ts b/src/components.d.ts index f31676e3c..0c131082e 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -56,6 +56,7 @@ declare module 'vue' { AvSelect: typeof import('./components/interaction/selects/AvSelect/AvSelect.vue')['default'] AvSideMenu: typeof import('./components/navigation/AvSideMenu/AvSideMenu.vue')['default'] AvSideNavigation: typeof import('./components/navigation/AvSideNavigation/AvSideNavigation.vue')['default'] + AvSkipLinks: typeof import('./components/navigation/AvSkipLinks/AvSkipLinks.vue')['default'] AvStepper: typeof import('./components/navigation/AvStepper/AvStepper.vue')['default'] AvTab: typeof import('./components/interaction/tabs/AvTab/AvTab.vue')['default'] AvTable: typeof import('./components/dataDisplay/AvTable/AvTable.vue')['default'] diff --git a/src/components/navigation/AvSkipLinks/AvSkipLinks.md b/src/components/navigation/AvSkipLinks/AvSkipLinks.md new file mode 100644 index 000000000..6c04426a3 --- /dev/null +++ b/src/components/navigation/AvSkipLinks/AvSkipLinks.md @@ -0,0 +1,41 @@ +# Navigation - `AvSkipLinks` + +## โœจ Introduction + +The `AvSkipLinks` component is ideal for providing skip links to improve navigation and accessibility. + +## ๐Ÿ—๏ธ Structure + +- The component is a `nav` element with the `av-skip-links` class. +- Props allow you to define the skip links with their labels and target IDs. + +## ๐Ÿท๏ธ Props + +| Name | Type | Default | Mandatory | Description | +| --- | --- | --- | --- | --- | +| `skipLinks` | `Array<{ label: string, targetId: string }>` | `[]` | โœ… | The list of skip links to display. Each skip link should have a `label` and a `targetId`. | + +## ๐Ÿ”Š Events + +None. + +## ๐ŸŽจ Slots + +None. + +## ๐Ÿš€ Storybook demos + +You can find examples of use and demo of the component on its dedicated [Storybook page](https://avenirs-esr.github.io/avenirs-dsav/storybook/?path=/docs/components-navigation-avskiplinks--docs). + +## ๐Ÿ’ก Examples of use + +```vue + +``` diff --git a/src/components/navigation/AvSkipLinks/AvSkipLinks.stories.ts b/src/components/navigation/AvSkipLinks/AvSkipLinks.stories.ts new file mode 100644 index 000000000..a9cc3713a --- /dev/null +++ b/src/components/navigation/AvSkipLinks/AvSkipLinks.stories.ts @@ -0,0 +1,62 @@ +import type { Meta, StoryFn } from '@storybook/vue3' +import AvSkipLinks, { type AvSkipLinksProps } from '@/components/navigation/AvSkipLinks/AvSkipLinks.vue' + +/** + *

Navigation - AvSkipLinks

+ * + *

โœจ Introduction

+ * + *

+ * + * The AvSkipLinks component provides skip links to improve navigation and accessibility. + * + *

+ * + *

๐Ÿ—๏ธ Structure

+ * + * + */ +const meta: Meta = { + title: 'Components/Navigation/AvSkipLinks', + component: AvSkipLinks, + tags: ['autodocs'], + argTypes: { + skipLinks: { + control: 'object', + description: 'Array of skip links with label and targetId.', + }, + }, + args: { + skipLinks: [ + { label: 'Go to main content', targetId: 'main' }, + { label: 'Go to footer', targetId: 'footer' }, + ], + }, +} + +export default meta + +const Template: StoryFn = args => ({ + components: { AvSkipLinks }, + setup () { + return { args } + }, + template: ` + +

Press tab to navigate through the skip links.

+ `, +}) + +export const Default = Template.bind({}) +Default.args = {} diff --git a/src/components/navigation/AvSkipLinks/AvSkipLinks.stub.ts b/src/components/navigation/AvSkipLinks/AvSkipLinks.stub.ts new file mode 100644 index 000000000..6d3ce99ad --- /dev/null +++ b/src/components/navigation/AvSkipLinks/AvSkipLinks.stub.ts @@ -0,0 +1,21 @@ +import type { PropType } from 'vue' +import type { SkipLink } from '@/components/navigation/AvSkipLinks/AvSkipLinks.types' + +export const AvSkipLinksStub = defineComponent({ + name: 'AvSkipLinksStub', + props: { + skipLinks: { + type: Array as PropType, + default: [], + }, + }, + template: ` + + `, +}) diff --git a/src/components/navigation/AvSkipLinks/AvSkipLinks.test.ts b/src/components/navigation/AvSkipLinks/AvSkipLinks.test.ts new file mode 100644 index 000000000..e42e8e245 --- /dev/null +++ b/src/components/navigation/AvSkipLinks/AvSkipLinks.test.ts @@ -0,0 +1,52 @@ +import { mount, type VueWrapper } from '@vue/test-utils' +import { beforeEach } from 'vitest' +import AvSkipLinks from '@/components/navigation/AvSkipLinks/AvSkipLinks.vue' +import { BddTest } from '@/tests' + +BddTest().given('an AvSkipLinks component', () => { + let wrapper: VueWrapper> + + const skipLinks = [ + { label: 'Aller au contenu principal', targetId: 'main' }, + { label: 'Aller au pied de page', targetId: 'footer' }, + ] + + BddTest().when('the component is mounted with skip links', () => { + beforeEach(() => { + wrapper = mount(AvSkipLinks, { + props: { skipLinks }, + }) + }) + + BddTest().then('it should render the nav', () => { + expect(wrapper.find('nav').exists()).toBe(true) + }) + + BddTest().then('it should render the skip links', () => { + const skipLinksElements = wrapper.findAll('a') + expect(skipLinksElements).toHaveLength(skipLinks.length) + + skipLinks.forEach((link, index) => { + const skipLinkElement = skipLinksElements[index] + expect(skipLinkElement.text()).toBe(link.label) + expect(skipLinkElement.attributes('href')).toBe(`#${link.targetId}`) + }) + }) + }) + + BddTest().when('the component is mounted without skip links', () => { + beforeEach(() => { + wrapper = mount(AvSkipLinks, { + props: { skipLinks: [] }, + }) + }) + + BddTest().then('it should not render the nav', () => { + expect(wrapper.find('nav').exists()).toBe(false) + }) + + BddTest().then('it should not render any skip links', () => { + expect(wrapper.findAll('a')).toHaveLength(0) + }) + }) +}) diff --git a/src/components/navigation/AvSkipLinks/AvSkipLinks.types.ts b/src/components/navigation/AvSkipLinks/AvSkipLinks.types.ts new file mode 100644 index 000000000..4925dc560 --- /dev/null +++ b/src/components/navigation/AvSkipLinks/AvSkipLinks.types.ts @@ -0,0 +1,11 @@ +export interface SkipLink { + /** + * Label of the skip link + */ + label: string + + /** + * Id of the target element to skip to + */ + targetId: string +} diff --git a/src/components/navigation/AvSkipLinks/AvSkipLinks.vue b/src/components/navigation/AvSkipLinks/AvSkipLinks.vue new file mode 100644 index 000000000..eddf7b5a5 --- /dev/null +++ b/src/components/navigation/AvSkipLinks/AvSkipLinks.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/components/navigation/index.ts b/src/components/navigation/index.ts index 3152fd0bf..a6b258f7c 100644 --- a/src/components/navigation/index.ts +++ b/src/components/navigation/index.ts @@ -4,4 +4,5 @@ export { default as AvPagination } from './AvPagination/AvPagination.vue' export * from './AvPagination/utils' export { default as AvSideMenu, type AvSideMenuProps } from './AvSideMenu/AvSideMenu.vue' export { default as AvSideNavigation, type AvSideNavigationItem, type AvSideNavigationMenuItem, type AvSideNavigationProps, type AvSideNavigationSelectedItem } from './AvSideNavigation/AvSideNavigation.vue' +export { default as AvSkipLinks, type AvSkipLinksProps } from './AvSkipLinks/AvSkipLinks.vue' export { default as AvStepper, type AvStepperProps } from './AvStepper/AvStepper.vue'