@cnamts/synapse 0.0.4-alpha → 0.0.5-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/design-system-v3.d.ts +932 -224
  2. package/dist/design-system-v3.js +3374 -1502
  3. package/dist/design-system-v3.umd.cjs +6 -6
  4. package/dist/style.css +1 -1
  5. package/package.json +2 -1
  6. package/src/assets/tokens.scss +0 -1
  7. package/src/components/Alert/Accessibilite.mdx +14 -0
  8. package/src/components/Alert/Accessibilite.stories.ts +166 -0
  9. package/src/components/Alert/AccessibiliteItems.ts +152 -0
  10. package/src/components/Alert/constants/ExpertiseLevelEnum.ts +4 -0
  11. package/src/components/BackBtn/Accessibilite.mdx +14 -0
  12. package/src/components/BackBtn/Accessibilite.stories.ts +166 -0
  13. package/src/components/BackBtn/AccessibiliteItems.ts +132 -0
  14. package/src/components/BackBtn/constants/ExpertiseLevelEnum.ts +4 -0
  15. package/src/components/BackToTopBtn/Accessibilite.mdx +14 -0
  16. package/src/components/BackToTopBtn/Accessibilite.stories.ts +166 -0
  17. package/src/components/BackToTopBtn/AccessibiliteItems.ts +82 -0
  18. package/src/components/BackToTopBtn/constants/ExpertiseLevelEnum.ts +4 -0
  19. package/src/components/CollapsibleList/Accessibilite.mdx +14 -0
  20. package/src/components/CollapsibleList/Accessibilite.stories.ts +166 -0
  21. package/src/components/CollapsibleList/AccessibiliteItems.ts +129 -0
  22. package/src/components/CollapsibleList/constants/ExpertiseLevelEnum.ts +4 -0
  23. package/src/components/CopyBtn/Accessibilite.mdx +14 -0
  24. package/src/components/CopyBtn/Accessibilite.stories.ts +166 -0
  25. package/src/components/CopyBtn/AccessibiliteItems.ts +135 -0
  26. package/src/components/CopyBtn/config.ts +2 -0
  27. package/src/components/CopyBtn/constants/ExpertiseLevelEnum.ts +4 -0
  28. package/src/components/Customs/SyBtnSelect/SyBtnSelect.mdx +44 -0
  29. package/src/components/Customs/SyBtnSelect/SyBtnSelect.stories.ts +606 -0
  30. package/src/components/Customs/SyBtnSelect/SyBtnSelect.vue +246 -0
  31. package/src/components/Customs/SyBtnSelect/tests/SyBtnSelect.spec.ts +168 -0
  32. package/src/components/Customs/SyInputSelect/SyInputSelect.mdx +40 -0
  33. package/src/components/Customs/{CustomInputSelect/CustomInputSelect.stories.ts → SyInputSelect/SyInputSelect.stories.ts} +29 -29
  34. package/src/components/Customs/{CustomInputSelect/CustomInputSelect.vue → SyInputSelect/SyInputSelect.vue} +10 -2
  35. package/src/components/Customs/SyInputSelect/tests/SyInputSelect.spec.ts +140 -0
  36. package/src/components/Customs/{CustomSelect/CustomSelect.mdx → SySelect/SySelect.mdx} +8 -8
  37. package/src/components/Customs/{CustomSelect/CustomSelect.stories.ts → SySelect/SySelect.stories.ts} +25 -25
  38. package/src/components/Customs/{CustomSelect/CustomSelect.vue → SySelect/SySelect.vue} +9 -5
  39. package/src/components/Customs/{CustomInputSelect/tests/CustomInputSelect.spec.ts → SySelect/tests/SySelect.spec.ts} +48 -71
  40. package/src/components/Customs/SyTextField/SyTextField.mdx +44 -0
  41. package/src/components/Customs/{CustomTextField/CustomTextField.stories.ts → SyTextField/SyTextField.stories.ts} +34 -34
  42. package/src/components/Customs/{CustomTextField/tests/CustomTextField.spec.ts → SyTextField/tests/SyTextField.spec.ts} +3 -3
  43. package/src/components/Customs/{CustomTextField/tests/__snapshots__/CustomTextField.spec.ts.snap → SyTextField/tests/__snapshots__/SyTextField.spec.ts.snap} +3 -5
  44. package/src/components/DialogBox/DialogBox.mdx +14 -0
  45. package/src/components/DialogBox/DialogBox.stories.ts +798 -0
  46. package/src/components/DialogBox/DialogBox.vue +181 -0
  47. package/src/components/DialogBox/config.ts +25 -0
  48. package/src/components/DialogBox/locales.ts +5 -0
  49. package/src/components/DialogBox/tests/DialogBox.spec.ts +329 -0
  50. package/src/components/DialogBox/tests/__snapshots__/DialogBox.spec.ts.snap +46 -0
  51. package/src/components/ErrorPage/ErrorPage.mdx +21 -0
  52. package/src/components/ErrorPage/ErrorPage.stories.ts +133 -0
  53. package/src/components/ErrorPage/ErrorPage.vue +93 -0
  54. package/src/components/ErrorPage/locales.ts +5 -0
  55. package/src/components/ErrorPage/tests/ErrorPage.spec.ts +40 -0
  56. package/src/components/ErrorPage/tests/__snapshots__/ErrorPage.spec.ts.snap +78 -0
  57. package/src/components/FooterBar/FooterBar.stories.ts +556 -8
  58. package/src/components/FooterBar/config.ts +2 -3
  59. package/src/components/FooterBar/tests/FooterBar.spec.ts +1 -1
  60. package/src/components/FooterBar/tests/FooterBarConfig.spec.ts +1 -1
  61. package/src/components/FooterBar/tests/__snapshots__/FooterBar.spec.ts.snap +2 -2
  62. package/src/components/FranceConnectBtn/Accessibilite.mdx +14 -0
  63. package/src/components/FranceConnectBtn/Accessibilite.stories.ts +194 -0
  64. package/src/components/FranceConnectBtn/AccessibiliteItems.ts +199 -0
  65. package/src/components/FranceConnectBtn/constants/ExpertiseLevelEnum.ts +4 -0
  66. package/src/components/HeaderBar/HeaderBar.stories.ts +60 -2
  67. package/src/components/HeaderNavigationBar/HeaderNavigationBar.mdx +6 -6
  68. package/src/components/HeaderNavigationBar/tests/HeaderNavigationBar.spec.ts +1 -1
  69. package/src/components/HeaderToolbar/HeaderToolbar.vue +2 -2
  70. package/src/components/HeaderToolbar/tests/HeaderToolbar.spec.ts +36 -2
  71. package/src/components/LangBtn/Accessibilite.mdx +14 -0
  72. package/src/components/LangBtn/Accessibilite.stories.ts +166 -0
  73. package/src/components/LangBtn/AccessibiliteItems.ts +132 -0
  74. package/src/components/LangBtn/constants/ExpertiseLevelEnum.ts +4 -0
  75. package/src/components/LangBtn/tests/LangBtn.spec.ts +1 -1
  76. package/src/components/LangBtn/tests/__snapshots__/LangBtn.spec.ts.snap +1 -1
  77. package/src/components/MaintenancePage/MaintenancePage.mdx +11 -0
  78. package/src/components/MaintenancePage/MaintenancePage.stories.ts +41 -0
  79. package/src/components/MaintenancePage/MaintenancePage.vue +25 -0
  80. package/src/components/MaintenancePage/assets/maintenance.svg +1 -0
  81. package/src/components/MaintenancePage/index.ts +3 -0
  82. package/src/components/MaintenancePage/locales.ts +5 -0
  83. package/src/components/MaintenancePage/tests/MaintenancePage.spec.ts +12 -0
  84. package/src/components/MaintenancePage/tests/__snapshots__/MaintenancePage.spec.ts.snap +3 -0
  85. package/src/components/NotFoundPage/NotFoundPage.mdx +19 -0
  86. package/src/components/NotFoundPage/NotFoundPage.stories.ts +76 -0
  87. package/src/components/NotFoundPage/NotFoundPage.vue +52 -0
  88. package/src/components/NotFoundPage/assets/not-found.svg +1 -0
  89. package/src/components/NotFoundPage/locales.ts +6 -0
  90. package/src/components/NotFoundPage/tests/NotFoundPage.spec.ts +38 -0
  91. package/src/components/NotFoundPage/tests/__snapshots__/NotFoundPage.spec.ts.snap +76 -0
  92. package/src/components/NotificationBar/Accessibilite.mdx +14 -0
  93. package/src/components/NotificationBar/Accessibilite.stories.ts +166 -0
  94. package/src/components/NotificationBar/AccessibiliteItems.ts +174 -0
  95. package/src/components/NotificationBar/constants/ExpertiseLevelEnum.ts +4 -0
  96. package/src/components/NotificationBar/options.ts +1 -0
  97. package/src/components/PageContainer/Accessibilite.mdx +14 -0
  98. package/src/components/PageContainer/Accessibilite.stories.ts +166 -0
  99. package/src/components/PageContainer/AccessibiliteItems.ts +52 -0
  100. package/src/components/PageContainer/constants/ExpertiseLevelEnum.ts +4 -0
  101. package/src/components/PhoneField/PhoneField.vue +5 -5
  102. package/src/components/PhoneField/tests/PhoneField.spec.ts +3 -3
  103. package/src/components/SkipLink/Accessibilite.mdx +14 -0
  104. package/src/components/SkipLink/Accessibilite.stories.ts +167 -0
  105. package/src/components/SkipLink/AccessibiliteItems.ts +77 -0
  106. package/src/components/SkipLink/constants/ExpertiseLevelEnum.ts +4 -0
  107. package/src/components/SocialMediaLinks/SocialMediaLinks.mdx +17 -13
  108. package/src/components/SocialMediaLinks/SocialMediaLinks.stories.ts +2 -1
  109. package/src/components/SocialMediaLinks/tests/SocialMediaLinks.spec.ts +1 -1
  110. package/src/components/SocialMediaLinks/tests/__snapshots__/SocialMediaLinks.spec.ts.snap +2 -2
  111. package/src/components/SubHeader/SubHeader.vue +1 -1
  112. package/src/components/UserMenuBtn/UserMenuBtn.mdx +35 -0
  113. package/src/components/UserMenuBtn/UserMenuBtn.stories.ts +438 -0
  114. package/src/components/UserMenuBtn/UserMenuBtn.vue +105 -0
  115. package/src/components/UserMenuBtn/config.ts +24 -0
  116. package/src/components/UserMenuBtn/tests/UserMenuBtn.spec.ts +125 -0
  117. package/src/components/index.ts +12 -3
  118. package/src/composables/index.ts +8 -0
  119. package/src/composables/rules/tests/useFieldValidation.spec.ts +82 -0
  120. package/src/composables/rules/useFieldValidation.ts +53 -0
  121. package/src/designTokens/index.ts +2 -0
  122. package/src/designTokens/tokens/cnam/cnamDarkTheme.ts +5 -0
  123. package/src/designTokens/tokens/cnam/cnamLightTheme.ts +1 -0
  124. package/src/main.ts +2 -0
  125. package/src/stories/Fondamentaux/Arrondis.mdx +24 -0
  126. package/src/stories/{Guidelines → Fondamentaux}/Colors.mdx +1 -1
  127. package/src/stories/Fondamentaux/Conteneurs.mdx +7 -0
  128. package/src/stories/Fondamentaux/CustomisationEtThemes.mdx +7 -0
  129. package/src/stories/Fondamentaux/Elevations.mdx +14 -0
  130. package/src/stories/Fondamentaux/Espacements.mdx +29 -0
  131. package/src/stories/{Guidelines → Fondamentaux}/Introduction.mdx +1 -1
  132. package/src/stories/Fondamentaux/StylesTypographiques.mdx +33 -0
  133. package/src/stories/Fondamentaux/Typographie.mdx +58 -0
  134. package/src/stories/GuideDuDev/CommentUtiliserLesRules.mdx +132 -0
  135. package/src/stories/{Fondamentaux → Guidelines}/Accessibilite/Accessibilite.stories.ts +1 -1
  136. package/src/stories/{Fondamentaux → Guidelines}/EcoConception/Econception.stories.ts +2 -2
  137. package/src/components/Customs/CustomInputSelect/CustomInputSelect.mdx +0 -40
  138. package/src/components/Customs/CustomSelect/tests/CustomSelect.spec.ts +0 -236
  139. package/src/components/Customs/CustomTextField/CustomTextField.mdx +0 -44
  140. package/src/stories/Guidelines/CustomisationEtThemes.mdx +0 -3
  141. package/src/stories/Guidelines/Typo.mdx +0 -53
  142. /package/src/components/Customs/{CustomInputSelect → SyInputSelect}/config.ts +0 -0
  143. /package/src/components/Customs/{CustomTextField/CustomTextField.vue → SyTextField/SyTextField.vue} +0 -0
  144. /package/src/components/Customs/{CustomTextField → SyTextField}/types.d.ts +0 -0
  145. /package/src/stories/{Fondamentaux → Guidelines}/Accessibilite/Accessibilite.mdx +0 -0
  146. /package/src/stories/{Fondamentaux → Guidelines}/Accessibilite/AccessibiliteItems.ts +0 -0
  147. /package/src/stories/{Fondamentaux → Guidelines}/Accessibilite/constants/ExpertiseLevelEnum.ts +0 -0
  148. /package/src/stories/{Fondamentaux → Guidelines}/Accessibilite/constants/RGAALevelEnum.ts +0 -0
  149. /package/src/stories/{Fondamentaux → Guidelines}/EcoConception/EcoConception.mdx +0 -0
  150. /package/src/stories/{Fondamentaux → Guidelines}/EcoConception/ecoDesignItems.ts +0 -0
@@ -43,7 +43,7 @@
43
43
  isOpen.value = false
44
44
  }
45
45
 
46
- const inputId = ref(`custom-input-select-${Math.random().toString(36).substring(7)}`)
46
+ const inputId = ref(`sy-input-select-${Math.random().toString(36).substring(7)}`)
47
47
 
48
48
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
49
49
  const selectItem = (item: any) => {
@@ -113,6 +113,14 @@
113
113
  localErrorMessages.value = []
114
114
  return true
115
115
  }
116
+
117
+ defineExpose({
118
+ isOpen,
119
+ closeList,
120
+ selectItem,
121
+ selectedItem,
122
+ getItemText,
123
+ })
116
124
  </script>
117
125
 
118
126
  <template>
@@ -128,7 +136,7 @@
128
136
  <div
129
137
  ref="menu"
130
138
  v-click-outside="closeList"
131
- :class="['custom-select', buttonClass, 'text-'+options.menu.color]"
139
+ :class="['sy-input-select', buttonClass, 'text-'+options.menu.color]"
132
140
  role="menu"
133
141
  tabindex="0"
134
142
  @click="toggleMenu"
@@ -0,0 +1,140 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { expect, describe, it } from 'vitest'
3
+ import SyInputSelect from '../SyInputSelect.vue'
4
+ import { vuetify } from '@tests/unit/setup'
5
+
6
+ describe('SyInputSelect', () => {
7
+ it('renders the component with default props', () => {
8
+ const wrapper = mount(SyInputSelect, {
9
+ global: {
10
+ plugins: [vuetify],
11
+ },
12
+ })
13
+ expect(wrapper.exists()).toBe(true)
14
+ expect(wrapper.find('.sy-input-select').text()).toBe('Sélectionnez une option')
15
+ })
16
+
17
+ it('displays the selected item text', async () => {
18
+ const items = [{ text: 'Option 1', value: '1' }, { text: 'Option 2', value: '2' }]
19
+ const wrapper = mount(SyInputSelect, {
20
+ props: { items, modelValue: { text: 'Option 1', value: '1' } },
21
+ global: {
22
+ plugins: [vuetify],
23
+ },
24
+ })
25
+ expect(wrapper.find('.sy-input-select').text()).toContain('Option 1')
26
+ })
27
+
28
+ it('does not render error messages when not provided', () => {
29
+ const wrapper = mount(SyInputSelect, {
30
+ global: {
31
+ plugins: [vuetify],
32
+ },
33
+ })
34
+ expect(wrapper.find('.v-messages__message').exists()).toBe(false)
35
+ })
36
+
37
+ it('does not render the label when not provided', () => {
38
+ const wrapper = mount(SyInputSelect, {
39
+ global: {
40
+ plugins: [vuetify],
41
+ },
42
+ })
43
+ expect(wrapper.find('label').exists()).toBe(false)
44
+ })
45
+
46
+ it('formats items correctly', () => {
47
+ const items = ['Option 1', 'Option 2']
48
+ const wrapper = mount(SyInputSelect, {
49
+ props: { items, textKey: 'text', valueKey: 'value' },
50
+ global: {
51
+ plugins: [vuetify],
52
+ },
53
+ })
54
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
55
+ const formattedItems = (wrapper.vm as any).formattedItems
56
+ expect(formattedItems).toEqual([
57
+ { text: 'Option 1', value: 'Option 1' },
58
+ { text: 'Option 2', value: 'Option 2' },
59
+ ])
60
+ })
61
+
62
+ it('applies the correct button class when outlined is true', () => {
63
+ const wrapper = mount(SyInputSelect, {
64
+ props: { outlined: true },
65
+ global: {
66
+ plugins: [vuetify],
67
+ },
68
+ })
69
+ expect(wrapper.find('.sy-input-select').classes()).toContain('v-btn--variant-outlined')
70
+ })
71
+
72
+ it('toggles the menu when the button is clicked', async () => {
73
+ const wrapper = mount(SyInputSelect, {
74
+ global: {
75
+ plugins: [vuetify],
76
+ },
77
+ })
78
+ const button = wrapper.find('.sy-input-select')
79
+ await button.trigger('click')
80
+ expect(wrapper.vm.isOpen).toBe(true)
81
+ await button.trigger('click')
82
+ expect(wrapper.vm.isOpen).toBe(false)
83
+ })
84
+
85
+ it('use closeList method', async () => {
86
+ const wrapper = mount(SyInputSelect, {
87
+ global: {
88
+ plugins: [vuetify],
89
+ },
90
+ })
91
+ await wrapper.vm.closeList()
92
+ expect(wrapper.vm.isOpen).toBe(false)
93
+ })
94
+
95
+ it('selectItem method', async () => {
96
+ const wrapper = mount(SyInputSelect, {
97
+ global: {
98
+ plugins: [vuetify],
99
+ },
100
+ })
101
+ await wrapper.vm.selectItem({ text: 'Option 1', value: '1' })
102
+ expect(wrapper.vm.isOpen).toBe(false)
103
+ expect(wrapper.vm.selectedItem).toEqual({ text: 'Option 1', value: '1' })
104
+ })
105
+
106
+ it('getItemText method', async () => {
107
+ const wrapper = mount(SyInputSelect, {
108
+ global: {
109
+ plugins: [vuetify],
110
+ },
111
+ })
112
+ const item = { text: 'Option 1', value: '1' }
113
+ const text = wrapper.vm.getItemText(item)
114
+ expect(text).toBe('Option 1')
115
+ })
116
+
117
+ it('watch modelValue', async () => {
118
+ const wrapper = mount(SyInputSelect, {
119
+ props: { modelValue: { text: 'Option 1', value: '1' } },
120
+ global: {
121
+ plugins: [vuetify],
122
+ },
123
+ })
124
+ expect(wrapper.vm.selectedItem).toEqual({ text: 'Option 1', value: '1' })
125
+ await wrapper.setProps({ modelValue: { text: 'Option 2', value: '2' } })
126
+ expect(wrapper.vm.selectedItem).toEqual({ text: 'Option 2', value: '2' })
127
+ })
128
+
129
+ it('watch errorMessages', async () => {
130
+ const wrapper = mount(SyInputSelect, {
131
+ props: { errorMessages: ['Error message'] },
132
+ global: {
133
+ plugins: [vuetify],
134
+ },
135
+ })
136
+ expect(wrapper.find('.v-messages__message').exists()).toBe(true)
137
+ await wrapper.setProps({ errorMessages: [] })
138
+ expect(wrapper.find('.v-messages__message').exists()).toBe(false)
139
+ })
140
+ })
@@ -1,26 +1,26 @@
1
1
  import { Canvas, Meta, Controls, Story, Source } from '@storybook/blocks';
2
- import * as CustomSelectStories from "./CustomSelect.stories";
2
+ import * as SySelectStories from "./SySelect.stories";
3
3
 
4
- <Meta of={CustomSelectStories} />
4
+ <Meta of={SySelectStories} />
5
5
 
6
- # CustomSelect
6
+ # SySelect
7
7
 
8
- Le composant `CustomSelect` est utilisé pour proposer une alternative au `v-select` de Vuetify qui ne respecte pas les règles d'accessibilité RGAA.
8
+ Le composant `SySelect` est utilisé pour proposer une alternative au `v-select` de Vuetify qui ne respecte pas les règles d'accessibilité RGAA.
9
9
 
10
- <Canvas of={CustomSelectStories.Default} />
10
+ <Canvas of={SySelectStories.Default} />
11
11
 
12
- <Story of={CustomSelectStories.Info} />
12
+ <Story of={SySelectStories.Info} />
13
13
 
14
14
  # API
15
15
 
16
- <Controls of={CustomSelectStories.Default} />
16
+ <Controls of={SySelectStories.Default} />
17
17
 
18
18
  # Exemple d'utilisation
19
19
 
20
20
  <Source dark code={`
21
21
  <script setup lang="ts">
22
22
  import { ref } from 'vue'
23
- import CustomSelect from '@cnamts/synapse'
23
+ import SySelect from '@cnamts/synapse'
24
24
 
25
25
  const selectedValue = ref(undefined)
26
26
 
@@ -1,15 +1,15 @@
1
1
  import type { Meta, StoryObj } from '@storybook/vue3'
2
- import CustomSelect from '@/components/Customs/CustomSelect/CustomSelect.vue'
2
+ import SySelect from '@/components/Customs/SySelect/SySelect.vue'
3
3
  import { VBtn, VMenu, VList, VListItem, VListItemTitle } from 'vuetify/components'
4
4
  import { ref } from 'vue'
5
5
  import Alert from '../../Alert/Alert.vue'
6
6
 
7
7
  const meta = {
8
- title: 'Composants/Formulaires/CustomSelect',
9
- component: CustomSelect,
8
+ title: 'Composants/Formulaires/SySelect',
9
+ component: SySelect,
10
10
  parameters: {
11
11
  layout: 'fullscreen',
12
- controls: { exclude: ['selectedValue'] },
12
+ controls: { exclude: ['selectedValue', 'isOpen', 'closeList'] },
13
13
  },
14
14
  argTypes: {
15
15
  selectedValue: { control: 'text' },
@@ -17,7 +17,7 @@ const meta = {
17
17
  errorMessages: { control: 'object' },
18
18
  required: { control: 'boolean' },
19
19
  },
20
- } as Meta<typeof CustomSelect>
20
+ } as Meta<typeof SySelect>
21
21
 
22
22
  export default meta
23
23
 
@@ -29,7 +29,7 @@ export const Default: Story = {
29
29
  name: 'Template',
30
30
  code: `
31
31
  <template>
32
- <CustomSelect
32
+ <SySelect
33
33
  v-model="value"
34
34
  :items="items"
35
35
  />
@@ -40,7 +40,7 @@ export const Default: Story = {
40
40
  name: 'Script',
41
41
  code: `
42
42
  <script setup lang="ts">
43
- import CustomSelect from '@cnamts/CustomSelect'
43
+ import SySelect from '@cnamts/SySelect'
44
44
 
45
45
  const items = [
46
46
  { text: 'Option 1', value: '1' },
@@ -59,13 +59,13 @@ export const Default: Story = {
59
59
  },
60
60
  render: (args) => {
61
61
  return {
62
- components: { CustomSelect, VBtn, VMenu, VList, VListItem, VListItemTitle },
62
+ components: { SySelect, VBtn, VMenu, VList, VListItem, VListItemTitle },
63
63
  setup() {
64
64
  return { args }
65
65
  },
66
66
  template: `
67
67
  <div class="d-flex flex-wrap align-center pa-4">
68
- <CustomSelect
68
+ <SySelect
69
69
  v-bind="args"
70
70
  />
71
71
  </div>
@@ -82,7 +82,7 @@ export const Outlined: Story = {
82
82
  name: 'Template',
83
83
  code: `
84
84
  <template>
85
- <CustomSelect
85
+ <SySelect
86
86
  v-model="value"
87
87
  :items="items"
88
88
  outlined
@@ -94,7 +94,7 @@ export const Outlined: Story = {
94
94
  name: 'Script',
95
95
  code: `
96
96
  <script setup lang="ts">
97
- import CustomSelect from '@cnamts/CustomSelect'
97
+ import SySelect from '@cnamts/SySelect'
98
98
 
99
99
  const items = [
100
100
  { text: 'Option 1', value: '1' },
@@ -113,13 +113,13 @@ export const Outlined: Story = {
113
113
  },
114
114
  render: (args) => {
115
115
  return {
116
- components: { CustomSelect, VBtn, VMenu, VList, VListItem, VListItemTitle },
116
+ components: { SySelect, VBtn, VMenu, VList, VListItem, VListItemTitle },
117
117
  setup() {
118
118
  return { args }
119
119
  },
120
120
  template: `
121
121
  <div class="d-flex flex-wrap align-center pa-4" style="z-index: 99999">
122
- <CustomSelect
122
+ <SySelect
123
123
  v-bind="args"
124
124
  outlined
125
125
  />
@@ -136,7 +136,7 @@ export const Required: Story = {
136
136
  name: 'Template',
137
137
  code: `
138
138
  <template>
139
- <CustomSelect
139
+ <SySelect
140
140
  v-model="value"
141
141
  :items="items"
142
142
  required
@@ -148,7 +148,7 @@ export const Required: Story = {
148
148
  name: 'Script',
149
149
  code: `
150
150
  <script setup lang="ts">
151
- import CustomSelect from '@cnamts/CustomSelect'
151
+ import SySelect from '@cnamts/SySelect'
152
152
 
153
153
  const items = [
154
154
  { text: 'Option 1', value: '1' },
@@ -168,13 +168,13 @@ export const Required: Story = {
168
168
  },
169
169
  render: (args) => {
170
170
  return {
171
- components: { CustomSelect },
171
+ components: { SySelect },
172
172
  setup() {
173
173
  return { args }
174
174
  },
175
175
  template: `
176
176
  <div class="d-flex flex-wrap align-center pa-4">
177
- <CustomSelect
177
+ <SySelect
178
178
  v-bind="args"
179
179
  :required="args.required"
180
180
  />
@@ -191,7 +191,7 @@ export const withCustomError: Story = {
191
191
  name: 'Template',
192
192
  code: `
193
193
  <template>
194
- <CustomSelect
194
+ <SySelect
195
195
  v-model="value"
196
196
  :items="items"
197
197
  :error-messages="errorMessages"
@@ -206,7 +206,7 @@ export const withCustomError: Story = {
206
206
  name: 'Script',
207
207
  code: `
208
208
  <script setup lang="ts">
209
- import CustomSelect from '@cnamts/CustomSelect'
209
+ import SySelect from '@cnamts/SySelect'
210
210
  import { ref } from 'vue'
211
211
 
212
212
  const items = [
@@ -232,7 +232,7 @@ export const withCustomError: Story = {
232
232
  },
233
233
  render: (args) => {
234
234
  return {
235
- components: { CustomSelect, VBtn, VMenu, VList, VListItem, VListItemTitle },
235
+ components: { SySelect, VBtn, VMenu, VList, VListItem, VListItemTitle },
236
236
  setup() {
237
237
  const errorMessages = ref([])
238
238
  const triggerError = () => {
@@ -243,7 +243,7 @@ export const withCustomError: Story = {
243
243
  },
244
244
  template: `
245
245
  <div class="d-flex flex-wrap align-center pa-4">
246
- <CustomSelect
246
+ <SySelect
247
247
  v-bind="args"
248
248
  :error-messages="errorMessages"
249
249
  />
@@ -265,7 +265,7 @@ export const withCustomKey: Story = {
265
265
  name: 'Template',
266
266
  code: `
267
267
  <template>
268
- <CustomSelect
268
+ <SySelect
269
269
  v-model="value"
270
270
  :items="items"
271
271
  text-key="customKey"
@@ -278,7 +278,7 @@ export const withCustomKey: Story = {
278
278
  name: 'Script',
279
279
  code: `
280
280
  <script setup lang="ts">
281
- import CustomSelect from '@cnamts/CustomSelect'
281
+ import SySelect from '@cnamts/SySelect'
282
282
 
283
283
  const items = [
284
284
  { customKey: 'Choix 1', value: '1' },
@@ -297,13 +297,13 @@ export const withCustomKey: Story = {
297
297
  },
298
298
  render: (args) => {
299
299
  return {
300
- components: { CustomSelect, VBtn, VMenu, VList, VListItem, VListItemTitle },
300
+ components: { SySelect, VBtn, VMenu, VList, VListItem, VListItemTitle },
301
301
  setup() {
302
302
  return { args }
303
303
  },
304
304
  template: `
305
305
  <div class="d-flex flex-wrap align-center pa-4">
306
- <CustomSelect
306
+ <SySelect
307
307
  v-bind="args"
308
308
  outlined
309
309
  text-key="customKey"
@@ -26,7 +26,7 @@
26
26
  },
27
27
  menuId: {
28
28
  type: String,
29
- default: 'custom-select-menu',
29
+ default: 'sy-select-menu',
30
30
  },
31
31
  outlined: {
32
32
  type: Boolean,
@@ -45,7 +45,7 @@
45
45
  const emit = defineEmits(['update:modelValue'])
46
46
 
47
47
  const isOpen = ref(false)
48
- const selectedItem = ref<Record<string, unknown > | string | null>(props.modelValue)
48
+ const selectedItem = ref<Record<string, unknown > | string | null | undefined>(props.modelValue)
49
49
  const hasError = ref(false)
50
50
 
51
51
  const toggleMenu = () => {
@@ -54,7 +54,7 @@
54
54
  const closeList = () => {
55
55
  isOpen.value = false
56
56
  }
57
- const inputId = ref(`custom-select-${Math.random().toString(36).substring(7)}`)
57
+ const inputId = ref(`sy-select-${Math.random().toString(36).substring(7)}`)
58
58
 
59
59
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
60
60
  const selectItem = (item: any) => {
@@ -97,6 +97,10 @@
97
97
  hasError.value = !newValue && !selectedItem.value && isRequired.value
98
98
  })
99
99
 
100
+ defineExpose({
101
+ isOpen,
102
+ closeList,
103
+ })
100
104
  </script>
101
105
 
102
106
  <template>
@@ -115,7 +119,7 @@
115
119
  :error-messages="errorMessages"
116
120
  :variant="outlined ? 'outlined' : 'underlined'"
117
121
  :rules="isRequired ? ['Le champ est requis.'] : []"
118
- class="custom-select"
122
+ class="sy-select"
119
123
  @click="toggleMenu"
120
124
  @keydown.enter.prevent="toggleMenu"
121
125
  @keydown.space.prevent="toggleMenu"
@@ -159,7 +163,7 @@
159
163
  <style scoped lang="scss">
160
164
  @use '@/assets/tokens.scss';
161
165
 
162
- .custom-select {
166
+ .sy-select {
163
167
  display: flex;
164
168
  flex-direction: column;
165
169
  min-width: 225px;
@@ -1,90 +1,51 @@
1
1
  import { mount } from '@vue/test-utils'
2
2
  import { expect, describe, it } from 'vitest'
3
- import CustomSelect from '../CustomInputSelect.vue'
3
+ import SySelect from '../SySelect.vue'
4
4
  import { vuetify } from '@tests/unit/setup'
5
5
 
6
- describe('CustomInputSelect.vue', () => {
6
+ describe('SySelect.vue', () => {
7
7
  it('renders the component with default props', () => {
8
- const wrapper = mount(CustomSelect, {
8
+ const wrapper = mount(SySelect, {
9
9
  global: {
10
10
  plugins: [vuetify],
11
11
  },
12
12
  })
13
13
  expect(wrapper.exists()).toBe(true)
14
- expect(wrapper.find('.custom-select').text()).toBe('Sélectionnez une option')
14
+ expect(wrapper.find('.sy-select').text()).toBe('')
15
15
  })
16
16
 
17
- it('toggles the menu when clicked', async () => {
18
- const wrapper = mount(CustomSelect, {
19
- global: {
20
- plugins: [vuetify],
21
- },
22
- })
23
- await wrapper.find('.custom-select').trigger('click')
24
- expect(wrapper.find('.v-list').exists()).toBe(true)
25
- await wrapper.find('.custom-select').trigger('click')
26
- expect(wrapper.find('.v-list').exists()).toBe(false)
27
- })
28
-
29
- it('selects an item when clicked', async () => {
30
- const items = [{ text: 'Option 1', value: '1' }, { text: 'Option 2', value: '2' }]
31
- const wrapper = mount(CustomSelect, {
32
- props: { items },
33
- global: {
34
- plugins: [vuetify],
35
- },
36
- })
37
- await wrapper.find('.custom-select').trigger('click')
38
- const firstItem = wrapper.findAll('.v-list-item').at(0)
39
- if (firstItem) {
40
- await firstItem.trigger('click')
41
- }
42
- expect(wrapper.emitted()['update:modelValue'][0]).toEqual([{ text: 'Option 1', value: '1' }])
43
- })
44
-
45
- it('closes the menu when an item is selected', async () => {
17
+ it('displays the selected item text', async () => {
46
18
  const items = [{ text: 'Option 1', value: '1' }, { text: 'Option 2', value: '2' }]
47
- const wrapper = mount(CustomSelect, {
48
- props: { items },
19
+ const wrapper = mount(SySelect, {
20
+ props: { items, modelValue: { text: 'Option 1', value: '1' } },
49
21
  global: {
50
22
  plugins: [vuetify],
51
23
  },
52
24
  })
53
- await wrapper.find('.custom-select').trigger('click')
25
+ await wrapper.find('.sy-select').trigger('click')
54
26
  const firstItem = wrapper.findAll('.v-list-item').at(0)
55
27
  if (firstItem) {
56
28
  await firstItem.trigger('click')
57
29
  }
58
- expect(wrapper.find('.v-list').exists()).toBe(false)
59
- })
60
-
61
- it('displays the selected item text', async () => {
62
- const items = [{ text: 'Option 1', value: '1' }, { text: 'Option 2', value: '2' }]
63
- const wrapper = mount(CustomSelect, {
64
- props: { items, modelValue: { text: 'Option 1', value: '1' } },
65
- global: {
66
- plugins: [vuetify],
67
- },
68
- })
69
- expect(wrapper.find('.custom-select').text()).toContain('Option 1')
30
+ expect(wrapper.find('input').element.value).toBe('Option 1')
70
31
  })
71
32
 
72
33
  it('closes the menu on escape key press', async () => {
73
34
  const items = [{ text: 'Option 1', value: '1' }, { text: 'Option 2', value: '2' }]
74
- const wrapper = mount(CustomSelect, {
35
+ const wrapper = mount(SySelect, {
75
36
  props: { items },
76
37
  global: {
77
38
  plugins: [vuetify],
78
39
  },
79
40
  })
80
- await wrapper.find('.custom-select').trigger('click')
41
+ await wrapper.find('.sy-select').trigger('click')
81
42
  await wrapper.find('.v-list').trigger('keydown.esc')
82
43
  expect(wrapper.find('.v-list').exists()).toBe(false)
83
44
  })
84
45
 
85
46
  it('renders error messages when provided', () => {
86
47
  const errorMessages = ['Error 1']
87
- const wrapper = mount(CustomSelect, {
48
+ const wrapper = mount(SySelect, {
88
49
  props: { errorMessages },
89
50
  global: {
90
51
  plugins: [vuetify],
@@ -94,7 +55,7 @@ describe('CustomInputSelect.vue', () => {
94
55
  })
95
56
 
96
57
  it('does not render error messages when not provided', () => {
97
- const wrapper = mount(CustomSelect, {
58
+ const wrapper = mount(SySelect, {
98
59
  global: {
99
60
  plugins: [vuetify],
100
61
  },
@@ -102,17 +63,8 @@ describe('CustomInputSelect.vue', () => {
102
63
  expect(wrapper.find('.v-messages__message').exists()).toBe(false)
103
64
  })
104
65
 
105
- it('does not render the label when not provided', () => {
106
- const wrapper = mount(CustomSelect, {
107
- global: {
108
- plugins: [vuetify],
109
- },
110
- })
111
- expect(wrapper.find('label').exists()).toBe(false)
112
- })
113
-
114
66
  it('returns the correct item text using getItemText', () => {
115
- const wrapper = mount(CustomSelect, {
67
+ const wrapper = mount(SySelect, {
116
68
  props: { textKey: 'text' },
117
69
  global: {
118
70
  plugins: [vuetify],
@@ -125,7 +77,7 @@ describe('CustomInputSelect.vue', () => {
125
77
  })
126
78
 
127
79
  it('returns default text when selectedItem is null', () => {
128
- const wrapper = mount(CustomSelect, {
80
+ const wrapper = mount(SySelect, {
129
81
  global: {
130
82
  plugins: [vuetify],
131
83
  },
@@ -136,7 +88,7 @@ describe('CustomInputSelect.vue', () => {
136
88
  })
137
89
 
138
90
  it('returns the correct text when selectedItem is an object', async () => {
139
- const wrapper = mount(CustomSelect, {
91
+ const wrapper = mount(SySelect, {
140
92
  props: { modelValue: { text: 'Option 1', value: '1' }, textKey: 'text' },
141
93
  global: {
142
94
  plugins: [vuetify],
@@ -150,7 +102,7 @@ describe('CustomInputSelect.vue', () => {
150
102
 
151
103
  it('formats items correctly', () => {
152
104
  const items = ['Option 1', 'Option 2']
153
- const wrapper = mount(CustomSelect, {
105
+ const wrapper = mount(SySelect, {
154
106
  props: { items, textKey: 'text', valueKey: 'value' },
155
107
  global: {
156
108
  plugins: [vuetify],
@@ -165,27 +117,27 @@ describe('CustomInputSelect.vue', () => {
165
117
  })
166
118
 
167
119
  it('applies the correct button class when outlined is true', () => {
168
- const wrapper = mount(CustomSelect, {
120
+ const wrapper = mount(SySelect, {
169
121
  props: { outlined: true },
170
122
  global: {
171
123
  plugins: [vuetify],
172
124
  },
173
125
  })
174
- expect(wrapper.find('.custom-select').classes()).toContain('v-btn--variant-outlined')
126
+ expect(wrapper.find('.v-field--variant-outlined').exists()).toBe(true)
175
127
  })
176
128
 
177
129
  it('does not apply the outlined button class when outlined is false', () => {
178
- const wrapper = mount(CustomSelect, {
130
+ const wrapper = mount(SySelect, {
179
131
  props: { outlined: false },
180
132
  global: {
181
133
  plugins: [vuetify],
182
134
  },
183
135
  })
184
- expect(wrapper.find('.custom-select').classes()).not.toContain('v-btn--variant-outlined')
136
+ expect(wrapper.find('.sy-select').classes()).not.toContain('v-btn--variant-outlined')
185
137
  })
186
138
 
187
139
  it('updates selectedItem when v-model changes', async () => {
188
- const wrapper = mount(CustomSelect, {
140
+ const wrapper = mount(SySelect, {
189
141
  props: { modelValue: { text: 'Option 1', value: '1' }, textKey: 'text' },
190
142
  global: {
191
143
  plugins: [vuetify],
@@ -200,7 +152,7 @@ describe('CustomInputSelect.vue', () => {
200
152
  })
201
153
 
202
154
  it('emits update:modelValue when selectedItem changes', async () => {
203
- const wrapper = mount(CustomSelect, {
155
+ const wrapper = mount(SySelect, {
204
156
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
205
157
  props: { modelValue: null as any, textKey: 'text' },
206
158
  global: {
@@ -213,4 +165,29 @@ describe('CustomInputSelect.vue', () => {
213
165
  await wrapper.vm.$nextTick()
214
166
  expect(wrapper.emitted()['update:modelValue'][0]).toEqual([{ text: 'Option 1', value: '1' }])
215
167
  })
168
+
169
+ it('closes the menu when v-click-outside directive is called', async () => {
170
+ const wrapper = mount(SySelect, {
171
+ global: {
172
+ plugins: [vuetify],
173
+ },
174
+ })
175
+ await wrapper.find('.sy-select').trigger('click')
176
+ expect(wrapper.find('.v-list').exists()).toBe(true)
177
+ await wrapper.find('.sy-select').trigger('mouseleave')
178
+ await wrapper.find('.sy-select').trigger('click')
179
+ await wrapper.vm.$nextTick()
180
+
181
+ expect(wrapper.find('.v-list').exists()).toBe(false)
182
+ })
183
+
184
+ it('use closeList method', async () => {
185
+ const wrapper = mount(SySelect, {
186
+ global: {
187
+ plugins: [vuetify],
188
+ },
189
+ })
190
+ await wrapper.vm.closeList()
191
+ expect(wrapper.vm.isOpen).toBe(false)
192
+ })
216
193
  })