@cnamts/synapse 0.0.0-alpha.0

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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -0
  3. package/dist/design-system-v3.d.ts +246 -0
  4. package/dist/design-system-v3.js +5425 -0
  5. package/dist/design-system-v3.umd.cjs +2 -0
  6. package/dist/style.css +1 -0
  7. package/package.json +104 -0
  8. package/src/assets/tokens.scss +500 -0
  9. package/src/components/Alert/Alert.mdx +36 -0
  10. package/src/components/Alert/Alert.stories.ts +115 -0
  11. package/src/components/Alert/Alert.vue +248 -0
  12. package/src/components/Alert/locales.ts +3 -0
  13. package/src/components/Alert/tests/Alert.spec.ts +105 -0
  14. package/src/components/Alert/tests/__snapshots__/Alert.spec.ts.snap +15 -0
  15. package/src/components/BackBtn/BackBtn.mdx +26 -0
  16. package/src/components/BackBtn/BackBtn.stories.ts +138 -0
  17. package/src/components/BackBtn/BackBtn.vue +60 -0
  18. package/src/components/BackBtn/locales.ts +3 -0
  19. package/src/components/BackBtn/tests/BackBtn.spec.ts +103 -0
  20. package/src/components/BackBtn/tests/__snapshots__/BackBtn.spec.ts.snap +9 -0
  21. package/src/components/BackToTopBtn/BackToTopBtn.mdx +52 -0
  22. package/src/components/BackToTopBtn/BackToTopBtn.stories.ts +188 -0
  23. package/src/components/BackToTopBtn/BackToTopBtn.vue +137 -0
  24. package/src/components/BackToTopBtn/config.ts +12 -0
  25. package/src/components/BackToTopBtn/locales.ts +3 -0
  26. package/src/components/BackToTopBtn/tests/BackToTopBtn.spec.ts +173 -0
  27. package/src/components/BackToTopBtn/tests/__snapshots__/BackToTopBtn.spec.ts.snap +17 -0
  28. package/src/components/Beta/beta.mdx +5 -0
  29. package/src/components/CopyBtn/CopyBtn.mdx +38 -0
  30. package/src/components/CopyBtn/CopyBtn.stories.ts +209 -0
  31. package/src/components/CopyBtn/CopyBtn.vue +103 -0
  32. package/src/components/CopyBtn/config.ts +17 -0
  33. package/src/components/CopyBtn/locales.ts +3 -0
  34. package/src/components/CopyBtn/tests/CopyBtn.spec.ts +99 -0
  35. package/src/components/CopyBtn/tests/__snapshots__/CopyBtn.spec.ts.snap +7 -0
  36. package/src/components/Deprecated/deprecated.mdx +5 -0
  37. package/src/components/DownloadBtn/DownloadBtn.mdx +94 -0
  38. package/src/components/DownloadBtn/DownloadBtn.stories.ts +211 -0
  39. package/src/components/DownloadBtn/DownloadBtn.vue +113 -0
  40. package/src/components/DownloadBtn/config.ts +13 -0
  41. package/src/components/DownloadBtn/tests/DownloadBtn.spec.ts +82 -0
  42. package/src/components/DownloadBtn/tests/__snapshots__/DownloadBtn.spec.ts.snap +17 -0
  43. package/src/components/DownloadBtn/tests/data/filePromise.ts +53 -0
  44. package/src/components/DownloadBtn/tests/data/test.json +0 -0
  45. package/src/components/FranceConnectBtn/FranceConnectBtn.mdx +34 -0
  46. package/src/components/FranceConnectBtn/FranceConnectBtn.stories.ts +92 -0
  47. package/src/components/FranceConnectBtn/FranceConnectBtn.vue +154 -0
  48. package/src/components/FranceConnectBtn/locales.ts +6 -0
  49. package/src/components/FranceConnectBtn/tests/FranceConnectBtn.spec.ts +62 -0
  50. package/src/components/FranceConnectBtn/tests/__snapshots__/FranceConnectBtn.spec.ts.snap +36 -0
  51. package/src/components/LangBtn/LangBtn.mdx +37 -0
  52. package/src/components/LangBtn/LangBtn.stories.ts +147 -0
  53. package/src/components/LangBtn/LangBtn.vue +167 -0
  54. package/src/components/LangBtn/config.ts +17 -0
  55. package/src/components/LangBtn/locales.ts +3 -0
  56. package/src/components/LangBtn/tests/Config.spec.ts +24 -0
  57. package/src/components/LangBtn/tests/LangBtn.spec.ts +283 -0
  58. package/src/components/LangBtn/tests/__snapshots__/LangBtn.spec.ts.snap +11 -0
  59. package/src/components/LangBtn/types.d.ts +7 -0
  60. package/src/components/NotificationBar/NotificationBar.mdx +94 -0
  61. package/src/components/NotificationBar/NotificationBar.stories.ts +366 -0
  62. package/src/components/NotificationBar/NotificationBar.vue +296 -0
  63. package/src/components/NotificationBar/options.ts +15 -0
  64. package/src/components/NotificationBar/tests/NotificationBar.spec.ts +332 -0
  65. package/src/components/NotificationBar/tests/__snapshots__/NotificationBar.spec.ts.snap +7 -0
  66. package/src/components/NotificationBar/types.ts +7 -0
  67. package/src/components/PageContainer/PageContainer.mdx +29 -0
  68. package/src/components/PageContainer/PageContainer.stories.ts +115 -0
  69. package/src/components/PageContainer/PageContainer.vue +68 -0
  70. package/src/components/PageContainer/tests/PageContainer.spec.ts +56 -0
  71. package/src/components/PageContainer/tests/__snapshots__/PageContainer.spec.ts.snap +7 -0
  72. package/src/components/SkipLink/SkipLink.mdx +55 -0
  73. package/src/components/SkipLink/SkipLink.stories.ts +70 -0
  74. package/src/components/SkipLink/SkipLink.vue +79 -0
  75. package/src/components/SkipLink/locales.ts +3 -0
  76. package/src/components/SkipLink/tests/__snapshots__/skipLink.spec.ts.snap +3 -0
  77. package/src/components/SkipLink/tests/skipLink.spec.ts +46 -0
  78. package/src/components/index.ts +8 -0
  79. package/src/composables/useCustomizableOptions.ts +23 -0
  80. package/src/designTokens/bootstrapColors.md +66 -0
  81. package/src/designTokens/cnamColors.md +193 -0
  82. package/src/designTokens/index.ts +15 -0
  83. package/src/designTokens/tokens/bootstrap/bootstrapColors.ts +158 -0
  84. package/src/designTokens/tokens/bootstrap/bootstrapLightTheme.ts +22 -0
  85. package/src/designTokens/tokens/cnam/cnamColors.ts +171 -0
  86. package/src/designTokens/tokens/cnam/cnamContextual.ts +58 -0
  87. package/src/designTokens/tokens/cnam/cnamLightTheme.ts +90 -0
  88. package/src/designTokens/tokens/cnam/cnamSemantic.ts +87 -0
  89. package/src/designTokens/tokens/json/contextual-tokens.json +156 -0
  90. package/src/designTokens/tokens/json/primitives.json +209 -0
  91. package/src/designTokens/tokens/json/semantic.json +120 -0
  92. package/src/designTokens/utils/convertGaps.ts +11 -0
  93. package/src/designTokens/utils/convertSemanticsToken.ts +32 -0
  94. package/src/designTokens/utils/createFlattenTheme.ts +19 -0
  95. package/src/designTokens/utils/index.ts +4 -0
  96. package/src/main.ts +2 -0
  97. package/src/services/NotificationService.ts +27 -0
  98. package/src/stories/Fondamentaux/Accessibilite/Accessibilite.mdx +52 -0
  99. package/src/stories/Fondamentaux/Accessibilite/Accessibilite.stories.ts +36 -0
  100. package/src/stories/Fondamentaux/Accessibilite/AccessibiliteItems.ts +706 -0
  101. package/src/stories/Fondamentaux/Accessibilite/constants/ExpertiseLevelEnum.ts +5 -0
  102. package/src/stories/Fondamentaux/Accessibilite/constants/RGAALevelEnum.ts +4 -0
  103. package/src/stories/Fondamentaux/EcoConception/EcoConception.mdx +24 -0
  104. package/src/stories/Fondamentaux/EcoConception/Econception.stories.ts +30 -0
  105. package/src/stories/Fondamentaux/EcoConception/ecoDesignItems.ts +55 -0
  106. package/src/stories/GuideDuDev/CommentContribuer.mdx +22 -0
  107. package/src/stories/GuideDuDev/components.stories.ts +23 -0
  108. package/src/stories/GuideDuDev/moduleDeNotification.mdx +182 -0
  109. package/src/stories/GuideDuDev/vuetifyOptions.mdx +72 -0
  110. package/src/stories/Guidelines/Colors.mdx +220 -0
  111. package/src/stories/Guidelines/CustomisationEtThemes.mdx +3 -0
  112. package/src/stories/Guidelines/Introduction.mdx +35 -0
  113. package/src/stories/Guidelines/Typo.mdx +53 -0
  114. package/src/stories/Home/Accueil.mdx +7 -0
  115. package/src/stories/Home/PolitiqueDeConfidentialite.mdx +4 -0
  116. package/src/stories/Home/synapse.webp +0 -0
  117. package/src/temp/TestA11y.vue +14 -0
  118. package/src/temp/TestComponent.vue +37 -0
  119. package/src/temp/TestDTComponent.vue +93 -0
  120. package/src/temp/customizableOptions.vue +18 -0
  121. package/src/temp/gridsTests.vue +54 -0
  122. package/src/temp/options.json +5 -0
  123. package/src/types/vuetifyTypes.ts +3 -0
  124. package/src/utils/convertToUnit/index.ts +16 -0
  125. package/src/utils/convertToUnit/test/convertToUnit.spec.ts +32 -0
  126. package/src/utils/functions/copyToClipboard/index.ts +38 -0
  127. package/src/utils/functions/copyToClipboard/tests/copyToClipboard.spec.ts +104 -0
  128. package/src/utils/functions/downloadFile/index.ts +37 -0
  129. package/src/utils/functions/downloadFile/tests/downloadFile.spec.ts +69 -0
  130. package/src/utils/functions/downloadFile/types.ts +1 -0
@@ -0,0 +1,167 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, watch } from 'vue'
3
+ import type { AllLanguagesChar } from '@/components/LangBtn/types'
4
+ import { mdiMenuDown } from '@mdi/js'
5
+ import { locales } from './locales'
6
+ import ISO6391 from 'iso-639-1'
7
+ import useCustomizableOptions, { type CustomizableOptions } from '@/composables/useCustomizableOptions'
8
+ import defaultOptions from './config'
9
+
10
+ const props = withDefaults(defineProps<CustomizableOptions & {
11
+ modelValue?: string
12
+ hideDownArrow?: boolean
13
+ ariaLabel?: string
14
+ ariaOwns?: string
15
+ availableLanguages?: string[] | AllLanguagesChar
16
+ }>(), {
17
+ modelValue: 'fr',
18
+ hideDownArrow: false,
19
+ ariaLabel: locales.label,
20
+ ariaOwns: 'lang-btn',
21
+ availableLanguages: () => ['fr', 'en'],
22
+ validator: (value: string[] | AllLanguagesChar): boolean => {
23
+ if (Array.isArray(value)) {
24
+ return value.length > 0
25
+ }
26
+ else {
27
+ return value === '*'
28
+ }
29
+ },
30
+ })
31
+
32
+ const options = useCustomizableOptions(defaultOptions, props)
33
+
34
+ const emit = defineEmits(['update:modelValue', 'change'])
35
+ const menu = ref(false)
36
+ const selectedLanguage = ref(props.modelValue)
37
+
38
+ watch(
39
+ () => props.modelValue,
40
+ (newVal) => {
41
+ selectedLanguage.value = newVal
42
+ },
43
+ )
44
+
45
+ function updateLang(lang: string) {
46
+ selectedLanguage.value = lang
47
+ emit('update:modelValue', lang)
48
+ emit('change', lang)
49
+ menu.value = false
50
+ }
51
+
52
+ interface LanguageInfo {
53
+ code: string
54
+ name: string
55
+ nativeName: string
56
+ }
57
+
58
+ type LanguagesData = Record<string, LanguageInfo>
59
+
60
+ const isMenuOpen = computed(() => menu.value)
61
+ const menuId = computed(() => `lang-menu-id`)
62
+
63
+ const languagesData = computed<LanguagesData>(() => {
64
+ const data: LanguagesData = {}
65
+ let languageCodes: string[]
66
+
67
+ if (props.availableLanguages === '*') {
68
+ languageCodes = ISO6391.getAllCodes()
69
+ }
70
+ else {
71
+ languageCodes = props.availableLanguages as string[]
72
+ }
73
+
74
+ languageCodes.forEach((language) => {
75
+ data[language] = {
76
+ code: language,
77
+ name: ISO6391.getName(language) || language,
78
+ nativeName: ISO6391.getNativeName(language) || ISO6391.getName(language) || language,
79
+ }
80
+ })
81
+
82
+ return data
83
+ })
84
+
85
+ const currentLangData = computed(() => {
86
+ const langInfo = languagesData.value[selectedLanguage.value]
87
+ return {
88
+ name: langInfo?.nativeName || selectedLanguage.value,
89
+ label: `${props.ariaLabel} ${langInfo?.nativeName || selectedLanguage.value}`,
90
+ }
91
+ })
92
+
93
+ defineExpose({
94
+ currentLangData,
95
+ updateLang,
96
+ selectedLanguage,
97
+ })
98
+ </script>
99
+
100
+ <template>
101
+ <div :id="menuId">
102
+ <VMenu
103
+ v-bind="options.menu"
104
+ :id="isMenuOpen ? 'lang-menu' : menuId "
105
+ v-model="menu"
106
+ role="menu"
107
+ location="bottom"
108
+ >
109
+ <template #activator="{ props: activatorProps }">
110
+ <VBtn
111
+ id="lang-menu-btn"
112
+ :aria-label="`${props.ariaLabel} ${currentLangData.name}`"
113
+ aria-haspopup="menu"
114
+ :aria-controls="menuId"
115
+ :aria-owns="menuId"
116
+ :aria-expanded="isMenuOpen"
117
+ v-bind="{
118
+ ...options.btn,
119
+ ...activatorProps,
120
+ }"
121
+ class="vd-lang-btn"
122
+ >
123
+ {{ currentLangData.name }}
124
+ <VIcon
125
+ v-if="!hideDownArrow"
126
+ v-bind="options.icon"
127
+ class="ml-1"
128
+ >
129
+ {{ mdiMenuDown }}
130
+ </VIcon>
131
+ </VBtn>
132
+ </template>
133
+ <VList
134
+ v-bind="options.list"
135
+ aria-labelledby="lang-menu-btn"
136
+ >
137
+ <VListItem
138
+ v-for="(langData, code, index) in languagesData"
139
+ v-bind="options.listTile"
140
+ :key="code"
141
+ role="menuitem"
142
+ :tabindex="index + 1"
143
+ :aria-label="`${props.ariaLabel} ${langData.nativeName}`"
144
+ :aria-labelledby="`${menuId} ${langData.nativeName}`"
145
+ @click="updateLang(code)"
146
+ >
147
+ <VListItemTitle v-bind="options.listTileTitle">
148
+ {{ langData.nativeName }}
149
+ </VListItemTitle>
150
+ </VListItem>
151
+ </VList>
152
+ </VMenu>
153
+ </div>
154
+ </template>
155
+ <style lang="scss" scoped>
156
+ @import '../../assets/tokens';
157
+
158
+ .v-list-item:hover {
159
+ background-color: rgba($colors-overlay, 0.005)
160
+ }
161
+
162
+ .vd-lang-btn {
163
+ font-weight: 700;
164
+ --hoverColor: rgba($colors-overlay, 0.5);
165
+ text-transform: capitalize;
166
+ }
167
+ </style>
@@ -0,0 +1,17 @@
1
+ import type { VariantType } from '@/types/vuetifyTypes'
2
+
3
+ const defaultOptions = {
4
+ menu: {
5
+ offsetY: true,
6
+ },
7
+ btn: {
8
+ color: 'primary',
9
+ variant: 'outlined' as VariantType,
10
+ ripple: true,
11
+ },
12
+ icon: {
13
+ class: 'ml-1',
14
+ },
15
+ }
16
+
17
+ export default defaultOptions
@@ -0,0 +1,3 @@
1
+ export const locales = {
2
+ label: 'Choix de la langue. Actuellement',
3
+ }
@@ -0,0 +1,24 @@
1
+ import config from '../config'
2
+ import { describe, it, expect } from 'vitest'
3
+
4
+ describe('config', () => {
5
+ it('should have correct menu configuration', () => {
6
+ expect(config.menu).toEqual({
7
+ offsetY: true,
8
+ })
9
+ })
10
+
11
+ it('should have correct button configuration', () => {
12
+ expect(config.btn).toEqual({
13
+ color: 'primary',
14
+ variant: 'outlined',
15
+ ripple: true,
16
+ })
17
+ })
18
+
19
+ it('should have correct icon configuration', () => {
20
+ expect(config.icon).toEqual({
21
+ class: 'ml-1',
22
+ })
23
+ })
24
+ })
@@ -0,0 +1,283 @@
1
+ import { mount, VueWrapper } from '@vue/test-utils'
2
+ import LangBtn from '../LangBtn.vue'
3
+ import { describe, it, expect, afterEach, vi } from 'vitest'
4
+ import { createVuetify } from 'vuetify'
5
+ import * as components from 'vuetify/components'
6
+ import * as directives from 'vuetify/directives'
7
+ import ISO6391 from 'iso-639-1'
8
+
9
+ const vuetify = createVuetify({
10
+ components,
11
+ directives,
12
+ })
13
+
14
+ describe('LangBtn.vue', () => {
15
+ let wrapper: VueWrapper
16
+
17
+ afterEach(() => {
18
+ if (wrapper) {
19
+ wrapper.unmount()
20
+ }
21
+ })
22
+
23
+ it('renders correctly', async () => {
24
+ wrapper = mount(LangBtn, {
25
+ props: {
26
+ availableLanguages: ['fr', 'co', 'es'],
27
+ },
28
+ global: {
29
+ plugins: [vuetify],
30
+ },
31
+ })
32
+
33
+ expect(wrapper.html()).toMatchSnapshot()
34
+ })
35
+
36
+ it('renders with default props', () => {
37
+ wrapper = mount(LangBtn, {
38
+ props: {
39
+ availableLanguages: ['fr', 'co', 'es'],
40
+ },
41
+ global: {
42
+ plugins: [vuetify],
43
+ },
44
+ })
45
+ expect(wrapper.find('.vd-lang-btn').text()).toBe('Français')
46
+ })
47
+
48
+ it('renders with custom modelValue', () => {
49
+ wrapper = mount(LangBtn, {
50
+ props: {
51
+ modelValue: 'co',
52
+ availableLanguages: ['fr', 'co', 'es'],
53
+ },
54
+ global: {
55
+ plugins: [vuetify],
56
+ },
57
+ })
58
+ expect(wrapper.find('.vd-lang-btn').text()).toBe('corsu')
59
+ })
60
+
61
+ it('updates selectedLanguage when modelValue prop changes', async () => {
62
+ wrapper = mount(LangBtn, {
63
+ props: {
64
+ modelValue: 'fr',
65
+ availableLanguages: ['fr', 'co', 'es'],
66
+ },
67
+ global: {
68
+ plugins: [vuetify],
69
+ },
70
+ })
71
+
72
+ expect(wrapper.find('.vd-lang-btn').text()).toBe('Français')
73
+
74
+ await wrapper.setProps({ modelValue: 'es' })
75
+ await wrapper.vm.$nextTick()
76
+
77
+ expect(wrapper.find('.vd-lang-btn').text()).toBe('Español')
78
+ })
79
+
80
+ it('updates language when a language is selected', async () => {
81
+ })
82
+
83
+ it('opens the menu when clicked', async () => {
84
+ wrapper = mount(LangBtn, {
85
+ props: {
86
+ availableLanguages: ['fr', 'co', 'es'],
87
+ },
88
+ global: {
89
+ plugins: [vuetify],
90
+ },
91
+ attachTo: document.body,
92
+ })
93
+
94
+ expect(document.body.querySelector('.v-list')).toBeNull()
95
+
96
+ const activatorButton = wrapper.find('.vd-lang-btn')
97
+
98
+ await activatorButton.trigger('click')
99
+ await wrapper.vm.$nextTick()
100
+
101
+ expect(document.body.querySelector('.v-list')).not.toBeNull()
102
+ })
103
+
104
+ it('hides down arrow when hideDownArrow is true', () => {
105
+ wrapper = mount(LangBtn, {
106
+ props: { hideDownArrow: true },
107
+ global: {
108
+ plugins: [vuetify],
109
+ },
110
+ })
111
+ expect(wrapper.find('.v-icon').exists()).toBe(false)
112
+ })
113
+
114
+ it('shows down arrow when hideDownArrow is false', () => {
115
+ wrapper = mount(LangBtn, {
116
+ props: { hideDownArrow: false },
117
+ global: {
118
+ plugins: [vuetify],
119
+ },
120
+ })
121
+ expect(wrapper.find('.v-icon').exists()).toBe(true)
122
+ })
123
+
124
+ it('renders all languages when availableLanguages is "*"', async () => {
125
+ wrapper = mount(LangBtn, {
126
+ props: {
127
+ availableLanguages: '*',
128
+ },
129
+ global: {
130
+ plugins: [vuetify],
131
+ },
132
+ attachTo: document.body,
133
+ })
134
+
135
+ const activatorButton = wrapper.find('.vd-lang-btn')
136
+ await activatorButton.trigger('click')
137
+ await wrapper.vm.$nextTick()
138
+
139
+ const allLanguageCodes = ISO6391.getAllCodes()
140
+
141
+ const listItems = document.body.querySelectorAll('.v-list-item')
142
+
143
+ expect(listItems.length).toBe(allLanguageCodes.length)
144
+ })
145
+
146
+ it('renders only specified languages', async () => {
147
+ const languages = ['fr', 'co', 'es']
148
+ wrapper = mount(LangBtn, {
149
+ props: {
150
+ availableLanguages: languages,
151
+ },
152
+ global: {
153
+ plugins: [vuetify],
154
+ },
155
+ attachTo: document.body,
156
+ })
157
+
158
+ const activatorButton = wrapper.find('.vd-lang-btn')
159
+ await activatorButton.trigger('click')
160
+ await wrapper.vm.$nextTick()
161
+
162
+ const listItems = document.body.querySelectorAll('.v-list-item')
163
+
164
+ expect(listItems.length).toBe(languages.length)
165
+
166
+ const renderedLanguages = Array.from(listItems).map(item =>
167
+ item?.textContent?.trim(),
168
+ )
169
+ const expectedLanguages = languages.map(
170
+ code => ISO6391.getNativeName(code) || code,
171
+ )
172
+ expect(renderedLanguages).toEqual(expectedLanguages)
173
+ })
174
+
175
+ it('uses default ariaLabel when not provided', () => {
176
+ wrapper = mount(LangBtn, {
177
+ props: {
178
+ availableLanguages: ['fr', 'co', 'es'],
179
+ },
180
+ global: {
181
+ plugins: [vuetify],
182
+ },
183
+ })
184
+
185
+ const button = wrapper.find('.vd-lang-btn')
186
+ expect(button.attributes('aria-label')).toBe('Choix de la langue. Actuellement Français')
187
+ })
188
+
189
+ it('uses ariaLabel prop correctly', () => {
190
+ wrapper = mount(LangBtn, {
191
+ props: {
192
+ ariaLabel: 'Language selection',
193
+ availableLanguages: ['fr', 'co', 'es'],
194
+ },
195
+ global: {
196
+ plugins: [vuetify],
197
+ },
198
+ })
199
+
200
+ const button = wrapper.find('.vd-lang-btn')
201
+ expect(button.attributes('aria-label')).toBe('Language selection Français')
202
+ })
203
+
204
+ it('handles modelValue not in availableLanguages', () => {
205
+ wrapper = mount(LangBtn, {
206
+ props: {
207
+ modelValue: 'unknown',
208
+ availableLanguages: ['fr', 'co', 'es'],
209
+ },
210
+ global: {
211
+ plugins: [vuetify],
212
+ },
213
+ })
214
+
215
+ expect(wrapper.find('.vd-lang-btn').text()).toBe('unknown')
216
+ })
217
+
218
+ it('falls back to language code when name and nativeName are unavailable', () => {
219
+ const getNameMock = vi.spyOn(ISO6391, 'getName').mockReturnValue(undefined as unknown as string)
220
+ const getNativeNameMock = vi
221
+ .spyOn(ISO6391, 'getNativeName')
222
+ .mockReturnValue(undefined as unknown as string)
223
+
224
+ wrapper = mount(LangBtn, {
225
+ props: {
226
+ modelValue: 'xx',
227
+ availableLanguages: ['xx'],
228
+ },
229
+ global: {
230
+ plugins: [vuetify],
231
+ },
232
+ })
233
+
234
+ expect(wrapper.find('.vd-lang-btn').text()).toBe('xx')
235
+
236
+ getNameMock.mockRestore()
237
+ getNativeNameMock.mockRestore()
238
+ })
239
+
240
+ it('updates selectedLanguage and emits event when updateLang is called', async () => {
241
+ wrapper = mount(LangBtn, {
242
+ props: {
243
+ availableLanguages: ['fr', 'co', 'es'],
244
+ },
245
+ global: {
246
+ plugins: [vuetify],
247
+ },
248
+ })
249
+
250
+ expect(wrapper.find('.vd-lang-btn').text()).toBe('Français')
251
+
252
+ await wrapper.vm.$.exposed?.updateLang('es')
253
+
254
+ expect(wrapper.find('.vd-lang-btn').text()).toBe('Español')
255
+
256
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
257
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['es'])
258
+ })
259
+
260
+ it('updates language when a language is selected', async () => {
261
+ wrapper = mount(LangBtn, {
262
+ props: {
263
+ availableLanguages: ['fr', 'co', 'es'],
264
+ },
265
+ global: {
266
+ plugins: [vuetify],
267
+ },
268
+ attachTo: document.body,
269
+ })
270
+
271
+ const activatorButton = wrapper.find('.vd-lang-btn')
272
+ await activatorButton.trigger('click')
273
+ await wrapper.vm.$nextTick()
274
+
275
+ const languageItem = document.body.querySelectorAll('.v-list-item')[1]
276
+ languageItem.dispatchEvent(new Event('click'))
277
+ await wrapper.vm.$nextTick()
278
+
279
+ expect(wrapper.find('.vd-lang-btn').text()).toBe('corsu')
280
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
281
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['co'])
282
+ })
283
+ })
@@ -0,0 +1,11 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`LangBtn.vue > renders correctly 1`] = `
4
+ "<div data-v-42678988="" id="lang-menu-id"><button data-v-42678988="" type="button" class="v-btn v-theme--light text-primary v-btn--density-default v-btn--size-default v-btn--variant-outlined vd-lang-btn" id="lang-menu-btn" aria-label="Choix de la langue. Actuellement Français" aria-haspopup="menu" aria-controls="lang-menu-id" aria-owns="lang-menu-id" aria-expanded="false"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
5
+ <!----><span class="v-btn__content" data-no-activator="">Français <i data-v-42678988="" class="M7,10L12,15L17,10H7Z mdi v-icon notranslate v-theme--light v-icon--size-default ml-1" aria-hidden="true"></i></span>
6
+ <!---->
7
+ <!---->
8
+ </button>
9
+ <!---->
10
+ </div>"
11
+ `;
@@ -0,0 +1,7 @@
1
+ export interface Language {
2
+ direction: string
3
+ name: string
4
+ nativeName: string
5
+ }
6
+
7
+ export type AllLanguagesChar = '*'
@@ -0,0 +1,94 @@
1
+ import {Controls, Canvas, Meta, Source} from '@storybook/blocks';
2
+
3
+ import * as NotificationBarStories from './NotificationBar.stories';
4
+
5
+ <Meta of={NotificationBarStories} />
6
+
7
+ # NotificationBar
8
+
9
+ ##### Les notification bars servent à notifier l’utilisateur soit d’un retour d’action.
10
+
11
+ ##### Deux actions sont possible :
12
+ - Fermer la notification lorsqu’elle a été vue
13
+ - Cliquer sur un élément qui permet de faire une action secondaire (comme amener sur une page visée par la notification).
14
+
15
+ ##### L’affichage d’une durée de quelques secondes et disprition automatique de la notification bar est déconseillé pour des raisons d’accessibilité afin de permettre à tout le monde de prendre le temps nécessaire de lire la notification.
16
+
17
+ ##### Les 4 états son disponibles :
18
+ - **Information** : Information supplémentaire nécessaire à la compréhension de l’utilisateur d’un élément spécifique.
19
+ - **Error** : une erreur est survenue, technique ou lié à l’action que l’utilisateur à réalisé
20
+ - **Warning** : Alerte, élément important mais pas bloquant. L’utilisateur a besoin d’être informé d’un élément ou d’une conséquence.
21
+ - **Success** : l’action a été réalisée correctement et prise en compte.
22
+
23
+ <Canvas of={NotificationBarStories.Success} />
24
+
25
+ # API
26
+ <Controls of={NotificationBarStories.Success} />
27
+
28
+ # Exemple d'utilisation
29
+
30
+ <Source dark code={`
31
+ <script setup lang="ts">
32
+ import { VBtn } from 'vuetify/components'
33
+ import NotificationBar from '@/components/NotificationBar/NotificationBar.vue'
34
+ import { useNotificationService } from '@/services/NotificationService'
35
+ import type { Notification } from '@/components/NotificationBar/types'
36
+
37
+ const { addNotification } = useNotificationService()
38
+
39
+ const envoyerNotification = (message: string, type: Notification['type']) => {
40
+ const notification: Notification = {
41
+ id: Date.now().toString(),
42
+ message,
43
+ type,
44
+ timeout: -1,
45
+ }
46
+ addNotification(notification)
47
+ }
48
+ </script>
49
+
50
+ <template>
51
+ <div>
52
+ <NotificationBar
53
+ bottom
54
+ rounded="lg"
55
+ >
56
+ <template #action>
57
+ <VBtn variant="outlined">
58
+ Valider
59
+ </VBtn>
60
+ </template>
61
+ </NotificationBar>
62
+
63
+ <div class="button-group">
64
+ <VBtn
65
+ color="info"
66
+ @click="envoyerNotification('Première notification', 'info')"
67
+ >
68
+ Envoyer Notification 1
69
+ </VBtn>
70
+ <VBtn
71
+ color="success"
72
+ @click="envoyerNotification('Deuxième notification', 'success')"
73
+ >
74
+ Envoyer Notification 2
75
+ </VBtn>
76
+ <VBtn
77
+ color="error"
78
+ @click="envoyerNotification('Troisième notification', 'error')"
79
+ >
80
+ Envoyer Notification 3
81
+ </VBtn>
82
+ </div>
83
+ </div>
84
+ </template>
85
+
86
+ <style scoped>
87
+ .button-group {
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: 16px;
91
+ margin-top: 20px;
92
+ }
93
+ </style>
94
+ `} />