diff --git a/src/App.vue b/src/App.vue
index c7eecad..3c30836 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,12 +1,14 @@
-
+
+
diff --git a/src/assets/style/primevue-configuration.css b/src/assets/style/primevue-configuration.css
index c080610..3736d1a 100644
--- a/src/assets/style/primevue-configuration.css
+++ b/src/assets/style/primevue-configuration.css
@@ -1 +1,2 @@
@import './primevue-style/button.css';
+@import './primevue-style/accordion.css';
diff --git a/src/assets/style/primevue-style/accordion.css b/src/assets/style/primevue-style/accordion.css
new file mode 100644
index 0000000..ffca950
--- /dev/null
+++ b/src/assets/style/primevue-style/accordion.css
@@ -0,0 +1,42 @@
+:root{
+ /* panel */
+ --p-accordion-transition-duration: 0.2s;
+ --p-accordion-panel-border-width: 0px;
+ --p-accordion-panel-border-color: none;
+ /* header */
+ --p-accordion-header-color: var(--text-action-high-blue-france);
+ --p-accordion-header-hover-color: var(--text-action-high-blue-france);
+ --p-accordion-header-active-color: var(--text-action-high-blue-france);
+ --p-accordion-header-active-hover-color: var(--text-action-high-blue-france);
+ --p-accordion-header-padding: 0.75rem 1rem;
+ --p-accordion-header-font-weight: var(--text-body-MD-standard-text-Medium-weight);
+ --p-accordion-header-border-radius: 0px;
+ --p-accordion-header-border-width: 1px;
+ --p-accordion-header-border-color: var(--border-default-grey);
+ --p-accordion-header-background: var(--background-transparent);
+ --p-accordion-header-hover-background: var(--background-transparent-hover);
+ --p-accordion-header-active-background: var(--background-open-blue-france);
+ --p-accordion-header-active-hover-background: var(--background-open-blue-france-hover);
+ /* header first top border */
+ --p-accordion-header-first-top-border-radius: 0px;
+ --p-accordion-header-first-border-width: 1px;
+ /* header last bottom border */
+ --p-accordion-header-last-bottom-border-radius: 0px;
+ --p-accordion-header-last-active-bottom-border-radius: 0px;
+ /* focus */
+ --p-accordion-header-focus-ring-width: var(--focus-width);
+ --p-accordion-header-focus-ring-style: var(--focus-style);
+ --p-accordion-header-focus-ring-color: var(--focus-color);
+ --p-accordion-header-focus-ring-offset: var(--focus-offset);
+ /* icon */
+ --p-accordion-header-toggle-icon-color: var(--text-action-high-blue-france);
+ --p-accordion-header-toggle-icon-hover-color: var(--text-action-high-blue-france);
+ --p-accordion-header-toggle-icon-active-color: var(--text-action-high-blue-france);
+ --p-accordion-header-toggle-icon-active-hover-color: var(--text-action-high-blue-france);
+ /* content */
+ --p-accordion-content-border-width: 0px;
+ --p-accordion-content-border-color: none;
+ --p-accordion-content-background: var(--background-transparent);
+ --p-accordion-content-color: var(--text-default-grey);
+ --p-accordion-content-padding: 0px;
+}
diff --git a/src/components/accordion/IVAccordion.type.ts b/src/components/accordion/IVAccordion.type.ts
new file mode 100644
index 0000000..1b61e80
--- /dev/null
+++ b/src/components/accordion/IVAccordion.type.ts
@@ -0,0 +1,28 @@
+/**
+ * Interface representing the configuration and state of an Accordion component.
+ */
+export default interface IVAccordion {
+ /**
+ * Optional unique identifier for the accordion instance.
+ */
+ id?: string;
+
+ /**
+ * Indicates whether the accordion is disabled.
+ * When true, user interaction is prevented.
+ */
+ disabled?: boolean;
+
+ /**
+ * The current value(s) of the accordion.
+ * Can be a single value or an array of values, depending on the selection mode.
+ * Accepts string, number, or null.
+ */
+ value?: null | string | number | string[] | number[];
+
+ /**
+ * The value associated with a specific panel within the accordion.
+ * Useful for identifying or controlling individual panels.
+ */
+ panelValue?: undefined | string | number;
+}
diff --git a/src/components/accordion/VAccordion.vue b/src/components/accordion/VAccordion.vue
new file mode 100644
index 0000000..1eca4ce
--- /dev/null
+++ b/src/components/accordion/VAccordion.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/accordion/VAccordionChild.vue b/src/components/accordion/VAccordionChild.vue
new file mode 100644
index 0000000..e5c208d
--- /dev/null
+++ b/src/components/accordion/VAccordionChild.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/VAccordion.spec.ts b/test/VAccordion.spec.ts
new file mode 100644
index 0000000..bdcf631
--- /dev/null
+++ b/test/VAccordion.spec.ts
@@ -0,0 +1,153 @@
+import { describe, test, expect } from 'vitest'
+import { mount } from '@vue/test-utils'
+import { h } from 'vue'
+import VAccordion from '../src/components/accordion/VAccordion.vue'
+import VAccordionChild from '../src/components/accordion/VAccordionChild.vue'
+
+describe('VAccordion', () => {
+ const factory = () =>
+ mount(VAccordion, {
+ props: {
+ value: '0',
+ },
+ slots: {
+ default: () => [
+ h(VAccordionChild, { panelValue: '0' }, {
+ header: () => 'Un titre d\'accordéon 1',
+ content: () => h('p', 'Contenu 1'),
+ }),
+ h(VAccordionChild, { panelValue: '1', disabled: true }, {
+ header: () => 'Un titre d\'accordéon 2',
+ content: () => h('p', 'Contenu 2'),
+ }),
+ h(VAccordionChild, { panelValue: '2' }, {
+ header: () => 'Un titre d\'accordéon 3',
+ content: () => h('p', 'Contenu 3'),
+ }),
+ ],
+ },
+ global: {
+ components: {
+ VAccordionChild,
+ },
+ },
+ })
+
+ test('renders three accordion panels', () => {
+ const wrapper = factory()
+ const panels = wrapper.findAll('.p-accordionpanel')
+ expect(panels).toHaveLength(3)
+ })
+
+ test('activates the first panel by default', () => {
+ const wrapper = factory()
+ const firstPanel = wrapper.findAll('.p-accordionpanel')[0]
+ expect(firstPanel.classes()).toContain('p-accordionpanel-active')
+ expect(firstPanel.attributes('data-p-active')).toBe('true')
+ })
+
+ test('disables the second panel', () => {
+ const wrapper = factory()
+ const secondPanel = wrapper.findAll('.p-accordionpanel')[1]
+ expect(secondPanel.classes()).toContain('p-disabled')
+ expect(secondPanel.attributes('data-p-disabled')).toBe('true')
+ expect(secondPanel.find('button').attributes('disabled')).toBeDefined()
+ })
+
+ test('does not emit update:value when a disabled panel is clicked', async () => {
+ const wrapper = mount(VAccordion, {
+ props: {
+ value: null,
+ },
+ slots: {
+ default: () => [
+ h(VAccordionChild, { panelValue: '0', disabled: true }, {
+ header: () => 'Panneau désactivé',
+ content: () => h('p', 'Contenu'),
+ }),
+ ],
+ },
+ global: {
+ components: {
+ VAccordionChild,
+ },
+ },
+ })
+
+ const header = wrapper.find('.p-accordionheader')
+ expect(header.attributes('disabled')).toBeDefined()
+ await header.trigger('click')
+ expect(wrapper.emitted('update:value')).toBeFalsy()
+ })
+
+ test('emits update:value when a panel is clicked', async () => {
+ const wrapper = mount(VAccordion, {
+ props: {
+ value: null,
+ },
+ slots: {
+ default: `
+
+ Panel 1
+ Contenu 1
+
+
+ Panel 2
+ Contenu 2
+
+ `,
+ },
+ global: {
+ components: {
+ VAccordionChild,
+ },
+ },
+ })
+
+ const headers = wrapper.findAll('.p-accordionheader')
+ await headers[1].trigger('click')
+
+ // Checks that the event has been issued with the correct value
+ expect(wrapper.emitted('update:value')).toBeTruthy()
+ expect(wrapper.emitted('update:value')![0]).toEqual(['1'])
+ })
+
+ test('toggles an active panel when clicked again', async () => {
+ const wrapper = mount(VAccordion, {
+ props: {
+ value: '0',
+ },
+ slots: {
+ default: () => [
+ h(VAccordionChild, { panelValue: '0' }, {
+ header: () => 'Panneau 1',
+ content: () => h('p', 'Contenu 1'),
+ }),
+ ],
+ },
+ global: {
+ components: {
+ VAccordionChild,
+ },
+ },
+ })
+
+ const header = wrapper.find('.p-accordionheader')
+
+ // Vérifie que le panneau est actif au départ
+ const panel = wrapper.find('.p-accordionpanel')
+ expect(panel.attributes('data-p-active')).toBe('true')
+
+ // Clique une première fois pour le refermer
+ await header.trigger('click')
+
+ // Vérifie que le panneau est maintenant inactif
+ expect(panel.attributes('data-p-active')).toBe('false')
+
+ // Vérifie que l'événement a bien été émis avec `null` ou une valeur vide
+ const emitted = wrapper.emitted('update:value')
+ expect(emitted).toBeTruthy()
+ const lastEmission = emitted![emitted!.length - 1]
+ expect(lastEmission[0]).toBe(null)
+ })
+})