@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.
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/dist/design-system-v3.d.ts +246 -0
- package/dist/design-system-v3.js +5425 -0
- package/dist/design-system-v3.umd.cjs +2 -0
- package/dist/style.css +1 -0
- package/package.json +104 -0
- package/src/assets/tokens.scss +500 -0
- package/src/components/Alert/Alert.mdx +36 -0
- package/src/components/Alert/Alert.stories.ts +115 -0
- package/src/components/Alert/Alert.vue +248 -0
- package/src/components/Alert/locales.ts +3 -0
- package/src/components/Alert/tests/Alert.spec.ts +105 -0
- package/src/components/Alert/tests/__snapshots__/Alert.spec.ts.snap +15 -0
- package/src/components/BackBtn/BackBtn.mdx +26 -0
- package/src/components/BackBtn/BackBtn.stories.ts +138 -0
- package/src/components/BackBtn/BackBtn.vue +60 -0
- package/src/components/BackBtn/locales.ts +3 -0
- package/src/components/BackBtn/tests/BackBtn.spec.ts +103 -0
- package/src/components/BackBtn/tests/__snapshots__/BackBtn.spec.ts.snap +9 -0
- package/src/components/BackToTopBtn/BackToTopBtn.mdx +52 -0
- package/src/components/BackToTopBtn/BackToTopBtn.stories.ts +188 -0
- package/src/components/BackToTopBtn/BackToTopBtn.vue +137 -0
- package/src/components/BackToTopBtn/config.ts +12 -0
- package/src/components/BackToTopBtn/locales.ts +3 -0
- package/src/components/BackToTopBtn/tests/BackToTopBtn.spec.ts +173 -0
- package/src/components/BackToTopBtn/tests/__snapshots__/BackToTopBtn.spec.ts.snap +17 -0
- package/src/components/Beta/beta.mdx +5 -0
- package/src/components/CopyBtn/CopyBtn.mdx +38 -0
- package/src/components/CopyBtn/CopyBtn.stories.ts +209 -0
- package/src/components/CopyBtn/CopyBtn.vue +103 -0
- package/src/components/CopyBtn/config.ts +17 -0
- package/src/components/CopyBtn/locales.ts +3 -0
- package/src/components/CopyBtn/tests/CopyBtn.spec.ts +99 -0
- package/src/components/CopyBtn/tests/__snapshots__/CopyBtn.spec.ts.snap +7 -0
- package/src/components/Deprecated/deprecated.mdx +5 -0
- package/src/components/DownloadBtn/DownloadBtn.mdx +94 -0
- package/src/components/DownloadBtn/DownloadBtn.stories.ts +211 -0
- package/src/components/DownloadBtn/DownloadBtn.vue +113 -0
- package/src/components/DownloadBtn/config.ts +13 -0
- package/src/components/DownloadBtn/tests/DownloadBtn.spec.ts +82 -0
- package/src/components/DownloadBtn/tests/__snapshots__/DownloadBtn.spec.ts.snap +17 -0
- package/src/components/DownloadBtn/tests/data/filePromise.ts +53 -0
- package/src/components/DownloadBtn/tests/data/test.json +0 -0
- package/src/components/FranceConnectBtn/FranceConnectBtn.mdx +34 -0
- package/src/components/FranceConnectBtn/FranceConnectBtn.stories.ts +92 -0
- package/src/components/FranceConnectBtn/FranceConnectBtn.vue +154 -0
- package/src/components/FranceConnectBtn/locales.ts +6 -0
- package/src/components/FranceConnectBtn/tests/FranceConnectBtn.spec.ts +62 -0
- package/src/components/FranceConnectBtn/tests/__snapshots__/FranceConnectBtn.spec.ts.snap +36 -0
- package/src/components/LangBtn/LangBtn.mdx +37 -0
- package/src/components/LangBtn/LangBtn.stories.ts +147 -0
- package/src/components/LangBtn/LangBtn.vue +167 -0
- package/src/components/LangBtn/config.ts +17 -0
- package/src/components/LangBtn/locales.ts +3 -0
- package/src/components/LangBtn/tests/Config.spec.ts +24 -0
- package/src/components/LangBtn/tests/LangBtn.spec.ts +283 -0
- package/src/components/LangBtn/tests/__snapshots__/LangBtn.spec.ts.snap +11 -0
- package/src/components/LangBtn/types.d.ts +7 -0
- package/src/components/NotificationBar/NotificationBar.mdx +94 -0
- package/src/components/NotificationBar/NotificationBar.stories.ts +366 -0
- package/src/components/NotificationBar/NotificationBar.vue +296 -0
- package/src/components/NotificationBar/options.ts +15 -0
- package/src/components/NotificationBar/tests/NotificationBar.spec.ts +332 -0
- package/src/components/NotificationBar/tests/__snapshots__/NotificationBar.spec.ts.snap +7 -0
- package/src/components/NotificationBar/types.ts +7 -0
- package/src/components/PageContainer/PageContainer.mdx +29 -0
- package/src/components/PageContainer/PageContainer.stories.ts +115 -0
- package/src/components/PageContainer/PageContainer.vue +68 -0
- package/src/components/PageContainer/tests/PageContainer.spec.ts +56 -0
- package/src/components/PageContainer/tests/__snapshots__/PageContainer.spec.ts.snap +7 -0
- package/src/components/SkipLink/SkipLink.mdx +55 -0
- package/src/components/SkipLink/SkipLink.stories.ts +70 -0
- package/src/components/SkipLink/SkipLink.vue +79 -0
- package/src/components/SkipLink/locales.ts +3 -0
- package/src/components/SkipLink/tests/__snapshots__/skipLink.spec.ts.snap +3 -0
- package/src/components/SkipLink/tests/skipLink.spec.ts +46 -0
- package/src/components/index.ts +8 -0
- package/src/composables/useCustomizableOptions.ts +23 -0
- package/src/designTokens/bootstrapColors.md +66 -0
- package/src/designTokens/cnamColors.md +193 -0
- package/src/designTokens/index.ts +15 -0
- package/src/designTokens/tokens/bootstrap/bootstrapColors.ts +158 -0
- package/src/designTokens/tokens/bootstrap/bootstrapLightTheme.ts +22 -0
- package/src/designTokens/tokens/cnam/cnamColors.ts +171 -0
- package/src/designTokens/tokens/cnam/cnamContextual.ts +58 -0
- package/src/designTokens/tokens/cnam/cnamLightTheme.ts +90 -0
- package/src/designTokens/tokens/cnam/cnamSemantic.ts +87 -0
- package/src/designTokens/tokens/json/contextual-tokens.json +156 -0
- package/src/designTokens/tokens/json/primitives.json +209 -0
- package/src/designTokens/tokens/json/semantic.json +120 -0
- package/src/designTokens/utils/convertGaps.ts +11 -0
- package/src/designTokens/utils/convertSemanticsToken.ts +32 -0
- package/src/designTokens/utils/createFlattenTheme.ts +19 -0
- package/src/designTokens/utils/index.ts +4 -0
- package/src/main.ts +2 -0
- package/src/services/NotificationService.ts +27 -0
- package/src/stories/Fondamentaux/Accessibilite/Accessibilite.mdx +52 -0
- package/src/stories/Fondamentaux/Accessibilite/Accessibilite.stories.ts +36 -0
- package/src/stories/Fondamentaux/Accessibilite/AccessibiliteItems.ts +706 -0
- package/src/stories/Fondamentaux/Accessibilite/constants/ExpertiseLevelEnum.ts +5 -0
- package/src/stories/Fondamentaux/Accessibilite/constants/RGAALevelEnum.ts +4 -0
- package/src/stories/Fondamentaux/EcoConception/EcoConception.mdx +24 -0
- package/src/stories/Fondamentaux/EcoConception/Econception.stories.ts +30 -0
- package/src/stories/Fondamentaux/EcoConception/ecoDesignItems.ts +55 -0
- package/src/stories/GuideDuDev/CommentContribuer.mdx +22 -0
- package/src/stories/GuideDuDev/components.stories.ts +23 -0
- package/src/stories/GuideDuDev/moduleDeNotification.mdx +182 -0
- package/src/stories/GuideDuDev/vuetifyOptions.mdx +72 -0
- package/src/stories/Guidelines/Colors.mdx +220 -0
- package/src/stories/Guidelines/CustomisationEtThemes.mdx +3 -0
- package/src/stories/Guidelines/Introduction.mdx +35 -0
- package/src/stories/Guidelines/Typo.mdx +53 -0
- package/src/stories/Home/Accueil.mdx +7 -0
- package/src/stories/Home/PolitiqueDeConfidentialite.mdx +4 -0
- package/src/stories/Home/synapse.webp +0 -0
- package/src/temp/TestA11y.vue +14 -0
- package/src/temp/TestComponent.vue +37 -0
- package/src/temp/TestDTComponent.vue +93 -0
- package/src/temp/customizableOptions.vue +18 -0
- package/src/temp/gridsTests.vue +54 -0
- package/src/temp/options.json +5 -0
- package/src/types/vuetifyTypes.ts +3 -0
- package/src/utils/convertToUnit/index.ts +16 -0
- package/src/utils/convertToUnit/test/convertToUnit.spec.ts +32 -0
- package/src/utils/functions/copyToClipboard/index.ts +38 -0
- package/src/utils/functions/copyToClipboard/tests/copyToClipboard.spec.ts +104 -0
- package/src/utils/functions/downloadFile/index.ts +37 -0
- package/src/utils/functions/downloadFile/tests/downloadFile.spec.ts +69 -0
- 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,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,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
|
+
`} />
|