@cnamts/synapse 0.0.15-alpha → 1.0.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/dist/components/Accordion/Accordion.d.ts +39 -0
- package/dist/components/Accordion/config.d.ts +9 -0
- package/dist/components/ChipList/ChipList.d.ts +1 -1
- package/dist/components/CookiesSelection/CookiesSelection.d.ts +26 -26
- package/dist/components/CopyBtn/CopyBtn.d.ts +2 -0
- package/dist/components/Customs/SyInputSelect/SyInputSelect.d.ts +12 -0
- package/dist/components/Customs/SySelect/SySelect.d.ts +43 -16
- package/dist/components/Customs/SyTextField/SyTextField.d.ts +1391 -1
- package/dist/components/DatePicker/DatePicker.d.ts +2810 -16
- package/dist/components/DatePicker/DateTextInput.d.ts +1401 -4
- package/dist/components/DiacriticPicker/DiacriticPicker.d.ts +27 -0
- package/dist/components/DiacriticPicker/config.d.ts +14 -0
- package/dist/components/DiacriticPicker/locales.d.ts +6 -0
- package/dist/components/DownloadBtn/DownloadBtn.d.ts +1 -1
- package/dist/components/FooterBar/FooterBar.d.ts +1 -1
- package/dist/components/LangBtn/LangBtn.d.ts +4 -4
- package/dist/components/NirField/NirField.d.ts +2796 -4
- package/dist/components/NotificationBar/NotificationBar.d.ts +1 -1
- package/dist/components/PasswordField/PasswordField.d.ts +1 -1
- package/dist/components/PeriodField/PeriodField.d.ts +5636 -48
- package/dist/components/PhoneField/PhoneField.d.ts +1 -0
- package/dist/components/PhoneField/tests/types.d.ts +18 -0
- package/dist/components/SyAlert/SyAlert.d.ts +72 -1
- package/dist/components/SyTextArea/SyTextArea.d.ts +900 -0
- package/dist/components/SyTextArea/locales.d.ts +3 -0
- package/dist/components/SyTextArea/trimStartOnUpdate.d.ts +1 -0
- package/dist/components/SyTextArea/useTextActions.d.ts +13 -0
- package/dist/components/SyTextArea/wrapText.d.ts +1 -0
- package/dist/components/TableToolbar/TableToolbar.d.ts +10 -4
- package/dist/components/TableToolbar/config.d.ts +3 -2
- package/dist/components/UploadWorkflow/UploadWorkflow.d.ts +26 -26
- package/dist/components/index.d.ts +4 -0
- package/dist/composables/date/useDateFormat.d.ts +2 -2
- package/dist/composables/date/useDateFormatDayjs.d.ts +23 -0
- package/dist/composables/date/useDateInitializationDayjs.d.ts +18 -0
- package/dist/composables/date/useHolidayDay.d.ts +36 -0
- package/dist/design-system-v3.js +5106 -4208
- package/dist/design-system-v3.umd.cjs +4 -1
- package/dist/designTokens/tokens/pa/paLightTheme.d.ts +1 -32
- package/dist/style.css +1 -1
- package/dist/utils/rules/index.d.ts +1 -0
- package/dist/utils/rules/isHolidayDay/index.d.ts +11 -0
- package/dist/utils/rules/isHolidayDay/locales.d.ts +2 -0
- package/package.json +3 -2
- package/src/assets/settings.scss +12 -0
- package/src/components/Accordion/Accordion.mdx +69 -0
- package/src/components/Accordion/Accordion.stories.ts +262 -0
- package/src/components/Accordion/Accordion.vue +319 -0
- package/src/components/Accordion/config.ts +9 -0
- package/src/components/Accordion/tests/__snapshots__/accordion.spec.ts.snap +155 -0
- package/src/components/Accordion/tests/accordion.spec.ts +492 -0
- package/src/components/CopyBtn/CopyBtn.stories.ts +189 -0
- package/src/components/CopyBtn/CopyBtn.vue +29 -1
- package/src/components/CopyBtn/tests/CopyBtn.spec.ts +102 -0
- package/src/components/Customs/SyInputSelect/SyInputSelect.stories.ts +155 -1
- package/src/components/Customs/SyInputSelect/SyInputSelect.vue +97 -14
- package/src/components/Customs/SyInputSelect/tests/SyInputSelect.spec.ts +386 -106
- package/src/components/Customs/SySelect/SySelect.stories.ts +121 -2
- package/src/components/Customs/SySelect/SySelect.vue +33 -8
- package/src/components/Customs/SySelect/tests/SySelect.spec.ts +290 -1
- package/src/components/Customs/SyTextField/Accessibilite.stories.ts +7 -0
- package/src/components/Customs/SyTextField/SyTextField.stories.ts +13 -0
- package/src/components/Customs/SyTextField/SyTextField.vue +87 -20
- package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +795 -0
- package/src/components/DatePicker/DatePicker.stories.ts +432 -1
- package/src/components/DatePicker/DatePicker.vue +82 -27
- package/src/components/DatePicker/DatePickerValidation.stories.ts +9 -1
- package/src/components/DatePicker/DateTextInput.vue +101 -138
- package/src/components/DatePicker/docExamples/DatePickerBidirectionalValidation.vue +282 -0
- package/src/components/DatePicker/examples/DatePickerHolidayRule.vue +130 -0
- package/src/components/DatePicker/tests/DatePicker.spec.ts +33 -32
- package/src/components/DatePicker/tests/DateTextInput.spec.ts +81 -33
- package/src/components/DiacriticPicker/DiacriticPicker.mdx +104 -0
- package/src/components/DiacriticPicker/DiacriticPicker.stories.ts +447 -0
- package/src/components/DiacriticPicker/DiacriticPicker.vue +262 -0
- package/src/components/DiacriticPicker/config.ts +15 -0
- package/src/components/DiacriticPicker/locales.ts +6 -0
- package/src/components/DiacriticPicker/tests/DiatriticPicker.spec.ts +132 -0
- package/src/components/DialogBox/DialogBox.vue +1 -3
- package/src/components/NirField/NirField.stories.ts +172 -0
- package/src/components/NirField/NirField.vue +15 -7
- package/src/components/NotificationBar/Accessibilite.stories.ts +1 -1
- package/src/components/NotificationBar/NotificationBar.stories.ts +14 -0
- package/src/components/NotificationBar/NotificationBar.vue +26 -3
- package/src/components/NotificationBar/{options.ts → config.ts} +0 -1
- package/src/components/PaginatedTable/PaginatedTable.vue +0 -11
- package/src/components/PasswordField/PasswordField.stories.ts +4 -3
- package/src/components/PasswordField/PasswordField.vue +26 -18
- package/src/components/PasswordField/tests/PasswordField.spec.ts +1 -10
- package/src/components/PhoneField/PhoneField.stories.ts +143 -0
- package/src/components/PhoneField/PhoneField.vue +88 -30
- package/src/components/PhoneField/tests/PhoneField.additional.spec.ts +266 -0
- package/src/components/PhoneField/tests/PhoneField.spec.ts +248 -28
- package/src/components/PhoneField/tests/types.d.ts +19 -0
- package/src/components/SyAlert/Accessibilite.stories.ts +4 -0
- package/src/components/SyAlert/SyAlert.mdx +3 -7
- package/src/components/SyAlert/SyAlert.stories.ts +19 -12
- package/src/components/SyAlert/SyAlert.vue +88 -51
- package/src/components/SyAlert/tests/SyAlert.spec.ts +20 -2
- package/src/components/SyAlert/tests/__snapshots__/SyAlert.spec.ts.snap +83 -75
- package/src/components/SyTextArea/SyTextArea.mdx +17 -0
- package/src/components/SyTextArea/SyTextArea.stories.ts +322 -0
- package/src/components/SyTextArea/SyTextArea.vue +113 -0
- package/src/components/SyTextArea/locales.ts +3 -0
- package/src/components/SyTextArea/tests/SyTextArea.spec.ts +194 -0
- package/src/components/SyTextArea/trimStartOnUpdate.ts +12 -0
- package/src/components/SyTextArea/useTextActions.ts +52 -0
- package/src/components/SyTextArea/wrapText.ts +42 -0
- package/src/components/TableToolbar/TableToolbar.mdx +86 -1
- package/src/components/TableToolbar/TableToolbar.stories.ts +422 -74
- package/src/components/TableToolbar/TableToolbar.vue +25 -8
- package/src/components/TableToolbar/config.ts +3 -2
- package/src/components/TableToolbar/tests/__snapshots__/TableToolbar.spec.ts.snap +35 -12
- package/src/components/index.ts +4 -0
- package/src/composables/date/useDateFormat.ts +17 -1
- package/src/composables/date/useDateFormatDayjs.ts +84 -0
- package/src/composables/date/useDateInitializationDayjs.ts +133 -0
- package/src/composables/date/useHolidayDay.ts +98 -0
- package/src/composables/rules/useFieldValidation.ts +16 -3
- package/src/composables/validation/useValidation.ts +2 -1
- package/src/designTokens/tokens/pa/paLightTheme.ts +10 -41
- package/src/stories/Accessibilite/Avancement/Avancement.mdx +12 -0
- package/src/stories/Accessibilite/Avancement/Avancement.stories.ts +134 -0
- package/src/stories/Accessibilite/Introduction.mdx +5 -2
- package/src/stories/DesignTokens/colors.stories.ts +100 -41
- package/src/utils/rules/index.ts +1 -0
- package/src/utils/rules/isHolidayDay/IsHolidayDay.mdx +52 -0
- package/src/utils/rules/isHolidayDay/IsHolidayDay.stories.ts +129 -0
- package/src/utils/rules/isHolidayDay/index.ts +36 -0
- package/src/utils/rules/isHolidayDay/locales.ts +5 -0
- package/src/utils/rules/isHolidayDay/tests/isHolidayDay.spec.ts +35 -0
- /package/dist/components/NotificationBar/{options.d.ts → config.d.ts} +0 -0
- /package/src/components/DatePicker/{DatePickerValidationExamples.vue → docExamples/DatePickerValidationExamples.vue} +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* eslint-disable vue/one-component-per-file */
|
|
2
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
3
|
+
import { mount } from '@vue/test-utils'
|
|
4
|
+
import { vuetify } from '@tests/unit/setup'
|
|
5
|
+
import { ref, nextTick, defineComponent, h } from 'vue'
|
|
6
|
+
import DiacriticPicker from '../DiacriticPicker.vue'
|
|
7
|
+
|
|
8
|
+
const defaultProps = {
|
|
9
|
+
modelValue: '',
|
|
10
|
+
diacritics: ['á', 'à', 'â', 'ä'],
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('DiacriticPicker.vue', () => {
|
|
14
|
+
let wrapper: ReturnType<typeof mount>
|
|
15
|
+
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
// Mount component with a slot containing an input
|
|
18
|
+
wrapper = mount(DiacriticPicker, {
|
|
19
|
+
props: defaultProps,
|
|
20
|
+
slots: {
|
|
21
|
+
default: `<input id="diacritic-input-${Math.random().toString(36).substr(2, 9)}" type="text" value="a" />`,
|
|
22
|
+
},
|
|
23
|
+
global: {
|
|
24
|
+
plugins: [vuetify],
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
await nextTick()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('renders the diacritic button', () => {
|
|
31
|
+
const btn = wrapper.find('.sy-diacritic-btn')
|
|
32
|
+
expect(btn.exists()).toBe(true)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('initially hides the dialog', () => {
|
|
36
|
+
const dialog = wrapper.find('.sy-diacritic-dialog')
|
|
37
|
+
expect(dialog.exists()).toBe(false)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('shows dialog when button is clicked', async () => {
|
|
41
|
+
await wrapper.find('.sy-diacritic-btn').trigger('click')
|
|
42
|
+
await nextTick()
|
|
43
|
+
|
|
44
|
+
expect(wrapper.findComponent({ name: 'VDialog' }).vm.$props.modelValue).toBe(true)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('inserts selected character into the input', async () => {
|
|
48
|
+
const model = ref('a')
|
|
49
|
+
|
|
50
|
+
const wrapper = mount(DiacriticPicker, {
|
|
51
|
+
global: { plugins: [vuetify] },
|
|
52
|
+
props: {
|
|
53
|
+
modelValue: model.value,
|
|
54
|
+
onUpdateModelValue: (val: string) => {
|
|
55
|
+
model.value = val
|
|
56
|
+
},
|
|
57
|
+
diacritics: ['á', 'à', 'â', 'ä'],
|
|
58
|
+
},
|
|
59
|
+
slots: {
|
|
60
|
+
default: defineComponent({
|
|
61
|
+
setup() {
|
|
62
|
+
return () => h('input', {
|
|
63
|
+
id: 'sy-diacritic-input',
|
|
64
|
+
value: model.value,
|
|
65
|
+
})
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
await wrapper.find('.sy-diacritic-btn').trigger('click')
|
|
72
|
+
await nextTick()
|
|
73
|
+
|
|
74
|
+
const buttons = wrapper.findAllComponents({ name: 'VBtn' })
|
|
75
|
+
const button = buttons.find(btn => btn.text() === 'á')
|
|
76
|
+
expect(button).toBeTruthy()
|
|
77
|
+
|
|
78
|
+
await button!.trigger('click')
|
|
79
|
+
await nextTick()
|
|
80
|
+
|
|
81
|
+
expect(wrapper.emitted('update:modelValue')?.[0][0]).toBe('aá')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('sets correct ARIA attributes', () => {
|
|
85
|
+
const btn = wrapper.find('.sy-diacritic-btn')
|
|
86
|
+
expect(btn.attributes('aria-haspopup')).toBe('dialog')
|
|
87
|
+
expect(btn.attributes('aria-expanded')).toBe('false')
|
|
88
|
+
expect(btn.attributes('aria-controls')).toContain('diacritic-dialog')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('closes dialog on click:outside', async () => {
|
|
92
|
+
await wrapper.find('.sy-diacritic-btn').trigger('click')
|
|
93
|
+
await nextTick()
|
|
94
|
+
|
|
95
|
+
const card = wrapper.findComponent({ name: 'VCard' })
|
|
96
|
+
await card.trigger('click:outside')
|
|
97
|
+
await nextTick()
|
|
98
|
+
|
|
99
|
+
expect(wrapper.findComponent({ name: 'VDialog' }).vm.$props.modelValue).toBe(false)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('selects next diacritic on keydown', async () => {
|
|
103
|
+
const model1 = ref('a')
|
|
104
|
+
|
|
105
|
+
const wrapper = mount(DiacriticPicker, {
|
|
106
|
+
global: { plugins: [vuetify] },
|
|
107
|
+
props: {
|
|
108
|
+
modelValue: model1.value,
|
|
109
|
+
onUpdateModelValue: (val: string) => {
|
|
110
|
+
model1.value = val
|
|
111
|
+
},
|
|
112
|
+
diacritics: ['á', 'à', 'â', 'ä'],
|
|
113
|
+
},
|
|
114
|
+
slots: {
|
|
115
|
+
default: defineComponent({
|
|
116
|
+
setup() {
|
|
117
|
+
return () => h('input', {
|
|
118
|
+
id: 'sy-diacritic-input',
|
|
119
|
+
value: model1.value,
|
|
120
|
+
})
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const input = wrapper.find('#sy-diacritic-input')
|
|
127
|
+
await input.trigger('keydown', { key: '=' })
|
|
128
|
+
await nextTick()
|
|
129
|
+
|
|
130
|
+
expect(wrapper.emitted('update:modelValue')?.[0][0]).toBe('á')
|
|
131
|
+
})
|
|
132
|
+
})
|
|
@@ -50,12 +50,10 @@
|
|
|
50
50
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
|
51
51
|
))
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
return elements.filter(element => (
|
|
54
54
|
!element.hasAttribute('disabled')
|
|
55
55
|
&& !element.getAttribute('aria-hidden')
|
|
56
56
|
))
|
|
57
|
-
|
|
58
|
-
return filteredElements
|
|
59
57
|
}
|
|
60
58
|
|
|
61
59
|
async function handleFocus(e: KeyboardEvent): Promise<void> {
|
|
@@ -608,6 +608,178 @@ ne sont pas nécessaires dans certains contextes.
|
|
|
608
608
|
}),
|
|
609
609
|
}
|
|
610
610
|
|
|
611
|
+
export const CustomPatternRules: Story = {
|
|
612
|
+
args: {
|
|
613
|
+
...Default.args,
|
|
614
|
+
customRulesPrecedence: true,
|
|
615
|
+
customNumberRules: [
|
|
616
|
+
{
|
|
617
|
+
type: 'custom',
|
|
618
|
+
options: {
|
|
619
|
+
validate: (value: string) => {
|
|
620
|
+
if (!value) return true
|
|
621
|
+
|
|
622
|
+
// Supprimer les espaces pour la validation
|
|
623
|
+
const valueWithoutSpaces = value.replace(/\s/g, '')
|
|
624
|
+
|
|
625
|
+
// Vérifier la longueur
|
|
626
|
+
if (valueWithoutSpaces.length !== 13) {
|
|
627
|
+
return 'Le numéro de sécurité sociale doit contenir 13 caractères.'
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Vérifier le pattern selon les règles spécifiques du NIR français
|
|
631
|
+
// Rang 1: sexe (1 pour homme, 2 pour femme)
|
|
632
|
+
if (!/^[12]/.test(valueWithoutSpaces)) {
|
|
633
|
+
return 'Le premier chiffre doit être 1 (homme) ou 2 (femme).'
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Rangs 2-3: deux derniers chiffres de l'année de naissance
|
|
637
|
+
const anneeNaissance = valueWithoutSpaces.substring(1, 3)
|
|
638
|
+
if (!/^[0-9]{2}$/.test(anneeNaissance)) {
|
|
639
|
+
return 'Les chiffres 2 et 3 doivent représenter l\'année de naissance.'
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Rangs 4-5: mois de naissance (01-12)
|
|
643
|
+
const moisNaissance = valueWithoutSpaces.substring(3, 5)
|
|
644
|
+
if (!/^(0[1-9]|1[0-2])$/.test(moisNaissance)) {
|
|
645
|
+
return 'Les chiffres 4 et 5 doivent représenter un mois valide (01-12).'
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Rangs 6-7: département de naissance
|
|
649
|
+
const departement = valueWithoutSpaces.substring(5, 7)
|
|
650
|
+
if (!((/^[0-9]{2}$/.test(departement) && departement !== '00')
|
|
651
|
+
|| departement === '2A' || departement === '2B' || departement === '99')) {
|
|
652
|
+
return 'Les chiffres 6 et 7 doivent représenter un département valide.'
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Rangs 8-10: code commune ou pays
|
|
656
|
+
const codeCommune = valueWithoutSpaces.substring(7, 10)
|
|
657
|
+
if (!/^[0-9]{3}$/.test(codeCommune)) {
|
|
658
|
+
return 'Les chiffres 8 à 10 doivent représenter un code commune ou pays valide.'
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Rangs 11-13: numéro d'ordre
|
|
662
|
+
const numeroOrdre = valueWithoutSpaces.substring(10, 13)
|
|
663
|
+
if (!/^[0-9]{3}$/.test(numeroOrdre)) {
|
|
664
|
+
return 'Les chiffres 11 à 13 doivent représenter un numéro d\'ordre valide.'
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return true
|
|
668
|
+
},
|
|
669
|
+
message: 'Le numéro de sécurité sociale est invalide.',
|
|
670
|
+
successMessage: 'Le numéro de sécurité sociale est valide.',
|
|
671
|
+
fieldIdentifier: 'Numéro de sécurité sociale',
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
],
|
|
675
|
+
},
|
|
676
|
+
parameters: {
|
|
677
|
+
...Default.parameters,
|
|
678
|
+
docs: {
|
|
679
|
+
description: {
|
|
680
|
+
story: 'Exemple d\'utilisation avec une règle personnalisée qui prend la prévalence sur la validation standard. Cette règle valide le format du NIR selon les règles officielles françaises : 1er chiffre pour le sexe, 2 chiffres pour l\'année de naissance, 2 chiffres pour le mois, 5 chiffres pour le lieu de naissance et 3 chiffres pour le numéro d\'ordre.',
|
|
681
|
+
},
|
|
682
|
+
},
|
|
683
|
+
sourceCode: [
|
|
684
|
+
{
|
|
685
|
+
name: 'Template',
|
|
686
|
+
code: `
|
|
687
|
+
<template>
|
|
688
|
+
<NirField
|
|
689
|
+
v-model="value"
|
|
690
|
+
:required="false"
|
|
691
|
+
numberLabel="Numéro de sécurité sociale"
|
|
692
|
+
keyLabel="Clé"
|
|
693
|
+
:displayKey="true"
|
|
694
|
+
:customRulesPrecedence="true"
|
|
695
|
+
:customNumberRules="[
|
|
696
|
+
{
|
|
697
|
+
type: 'custom',
|
|
698
|
+
options: {
|
|
699
|
+
validate: (value) => {
|
|
700
|
+
if (!value) return true;
|
|
701
|
+
|
|
702
|
+
// Supprimer les espaces pour la validation
|
|
703
|
+
const valueWithoutSpaces = value.replace(/\\s/g, '');
|
|
704
|
+
|
|
705
|
+
// Vérifier la longueur
|
|
706
|
+
if (valueWithoutSpaces.length !== 13) {
|
|
707
|
+
return 'Le numéro de sécurité sociale doit contenir 13 caractères.';
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Vérification selon les règles spécifiques du NIR français
|
|
711
|
+
// Rang 1: sexe (1 pour homme, 2 pour femme)
|
|
712
|
+
if (!/^[12]/.test(valueWithoutSpaces)) {
|
|
713
|
+
return 'Le premier chiffre doit être 1 (homme) ou 2 (femme).';
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Rangs 2-3: deux derniers chiffres de l'année de naissance
|
|
717
|
+
if (!/^[12][0-9]{2}/.test(valueWithoutSpaces)) {
|
|
718
|
+
return 'Les chiffres 2 et 3 doivent représenter l'année de naissance.';
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Rangs 4-5: mois de naissance (01-12)
|
|
722
|
+
const moisNaissance = valueWithoutSpaces.substring(3, 5);
|
|
723
|
+
if (!/^(0[1-9]|1[0-2])$/.test(moisNaissance)) {
|
|
724
|
+
return 'Les chiffres 4 et 5 doivent représenter un mois valide (01-12).';
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Vérification complète du format
|
|
728
|
+
const formatComplet = /^[12][0-9]{2}(0[1-9]|1[0-2])[0-9]{8}$/;
|
|
729
|
+
if (!formatComplet.test(valueWithoutSpaces)) {
|
|
730
|
+
return 'Le format du numéro de sécurité sociale est invalide.';
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
return true;
|
|
734
|
+
},
|
|
735
|
+
message: 'Le numéro de sécurité sociale est invalide.',
|
|
736
|
+
successMessage: 'Le numéro de sécurité sociale est valide.',
|
|
737
|
+
fieldIdentifier: 'Numéro de sécurité sociale',
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
]"
|
|
741
|
+
/>
|
|
742
|
+
</template>
|
|
743
|
+
`,
|
|
744
|
+
},
|
|
745
|
+
],
|
|
746
|
+
},
|
|
747
|
+
render: args => ({
|
|
748
|
+
components: { NirField },
|
|
749
|
+
setup() {
|
|
750
|
+
const value = ref('')
|
|
751
|
+
return { args, value }
|
|
752
|
+
},
|
|
753
|
+
template: `
|
|
754
|
+
<div>
|
|
755
|
+
<h3>Validation avec pattern personnalisé et prévalence</h3>
|
|
756
|
+
<p>Cette démonstration utilise une règle personnalisée qui valide le format du NIR selon le pattern suivant :</p>
|
|
757
|
+
<pre>X XX XX XXX XXX XXX</pre>
|
|
758
|
+
<p>Où :</p>
|
|
759
|
+
<ul>
|
|
760
|
+
<li><strong>X</strong> (rang 1) : sexe (1 pour les hommes, 2 pour les femmes)</li>
|
|
761
|
+
<li><strong>XX</strong> (rangs 2-3) : deux derniers chiffres de l'année de naissance</li>
|
|
762
|
+
<li><strong>XX</strong> (rangs 4-5) : mois de naissance (01-12)</li>
|
|
763
|
+
<li><strong>XXX</strong> (rangs 6-10) : lieu de naissance
|
|
764
|
+
<ul>
|
|
765
|
+
<li>Rangs 6-7 : département (99 si étranger, 2A/2B pour la Corse)</li>
|
|
766
|
+
<li>Rangs 8-10 : code commune ou pays</li>
|
|
767
|
+
</ul>
|
|
768
|
+
</li>
|
|
769
|
+
<li><strong>XXX</strong> (rangs 11-13) : numéro d’ordre</li>
|
|
770
|
+
</ul>
|
|
771
|
+
<p>La propriété <code>customRulesPrecedence</code> est définie à <code>true</code> pour que cette règle soit appliquée avant la validation standard.</p>
|
|
772
|
+
<NirField
|
|
773
|
+
v-model="value"
|
|
774
|
+
v-bind="args"
|
|
775
|
+
class="mt-4"
|
|
776
|
+
/>
|
|
777
|
+
<div class="mt-4">Valeur actuelle : {{ value }}</div>
|
|
778
|
+
</div>
|
|
779
|
+
`,
|
|
780
|
+
}),
|
|
781
|
+
}
|
|
782
|
+
|
|
611
783
|
export const CustomRules: Story = {
|
|
612
784
|
args: {
|
|
613
785
|
...Default.args,
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
customKeyRules?: ValidationRule[]
|
|
24
24
|
customNumberWarningRules?: ValidationRule[]
|
|
25
25
|
customKeyWarningRules?: ValidationRule[]
|
|
26
|
+
customRulesPrecedence?: boolean
|
|
26
27
|
showSuccessMessages?: boolean
|
|
27
28
|
width?: string
|
|
28
29
|
bgColor?: string
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
customKeyRules: () => [],
|
|
57
58
|
customNumberWarningRules: () => [],
|
|
58
59
|
customKeyWarningRules: () => [],
|
|
60
|
+
customRulesPrecedence: false,
|
|
59
61
|
showSuccessMessages: true,
|
|
60
62
|
width: '100%',
|
|
61
63
|
bgColor: undefined,
|
|
@@ -165,7 +167,7 @@
|
|
|
165
167
|
// Règles de validation
|
|
166
168
|
const defaultNumberRules = computed(() => {
|
|
167
169
|
const rules: ValidationRule[] = []
|
|
168
|
-
|
|
170
|
+
if (props.readonly) return
|
|
169
171
|
if (props.required) {
|
|
170
172
|
rules.push({
|
|
171
173
|
type: 'required',
|
|
@@ -176,6 +178,15 @@
|
|
|
176
178
|
})
|
|
177
179
|
}
|
|
178
180
|
|
|
181
|
+
// Ajout des règles personnalisées avec prévalence si demandé
|
|
182
|
+
if (props.customRulesPrecedence && props.customNumberRules && props.customNumberRules.length > 0) {
|
|
183
|
+
rules.push(...props.customNumberRules.map(rule => ({
|
|
184
|
+
...rule,
|
|
185
|
+
options: rule.options || {},
|
|
186
|
+
})))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Règle de validation standard du NIR
|
|
179
190
|
rules.push({
|
|
180
191
|
type: 'custom',
|
|
181
192
|
options: {
|
|
@@ -194,8 +205,8 @@
|
|
|
194
205
|
},
|
|
195
206
|
})
|
|
196
207
|
|
|
197
|
-
// Ajout des règles personnalisées
|
|
198
|
-
if (props.customNumberRules) {
|
|
208
|
+
// Ajout des règles personnalisées sans prévalence (comportement par défaut)
|
|
209
|
+
if (!props.customRulesPrecedence && props.customNumberRules && props.customNumberRules.length > 0) {
|
|
199
210
|
rules.push(...props.customNumberRules.map(rule => ({
|
|
200
211
|
...rule,
|
|
201
212
|
options: rule.options || {},
|
|
@@ -207,7 +218,7 @@
|
|
|
207
218
|
|
|
208
219
|
const defaultKeyRules = computed(() => {
|
|
209
220
|
const rules: ValidationRule[] = []
|
|
210
|
-
|
|
221
|
+
if (props.readonly) return
|
|
211
222
|
if (props.required) {
|
|
212
223
|
rules.push({
|
|
213
224
|
type: 'required',
|
|
@@ -324,7 +335,6 @@
|
|
|
324
335
|
return validateFields(true)
|
|
325
336
|
}
|
|
326
337
|
|
|
327
|
-
// Computed pour statut des champs
|
|
328
338
|
const hasNumberErrors = computed(() => numberValidation.hasError.value)
|
|
329
339
|
const hasNumberWarning = computed(() => !hasNumberErrors.value && numberValidation.hasWarning.value)
|
|
330
340
|
const hasNumberSuccess = computed(() => !hasNumberErrors.value && !hasNumberWarning.value && numberValidation.hasSuccess.value)
|
|
@@ -333,7 +343,6 @@
|
|
|
333
343
|
const hasKeyWarning = computed(() => !hasKeyErrors.value && keyValidation.hasWarning.value)
|
|
334
344
|
const hasKeySuccess = computed(() => !hasKeyErrors.value && !hasKeyWarning.value && keyValidation.hasSuccess.value)
|
|
335
345
|
|
|
336
|
-
// Labels avec astérisque si nécessaire
|
|
337
346
|
const numberLabelWithAsterisk = computed(() => {
|
|
338
347
|
return props.required && props.displayAsterisk ? `${props.numberLabel} *` : props.numberLabel
|
|
339
348
|
})
|
|
@@ -342,7 +351,6 @@
|
|
|
342
351
|
return props.required && props.displayAsterisk ? `${props.keyLabel} *` : props.keyLabel
|
|
343
352
|
})
|
|
344
353
|
|
|
345
|
-
// Gestion des événements
|
|
346
354
|
const handleNumberInput = () => {
|
|
347
355
|
emitValue()
|
|
348
356
|
validateFields()
|
|
@@ -161,7 +161,7 @@ export const Legende: StoryObj = {
|
|
|
161
161
|
</div>
|
|
162
162
|
<div class="mt-4">
|
|
163
163
|
<p>Rapport d’audit manuel : <a href="/audits/NotificationBar.xlsx" style="color:#0C41BD;">Voir le rapport</a></p>
|
|
164
|
-
<p style="color: grey; font-size: 14px">Correctifs en cours (<a href="https://github.com/assurance-maladie-digital/design-system/issues/
|
|
164
|
+
<p style="color: grey; font-size: 14px">Correctifs en cours (<a href="https://github.com/assurance-maladie-digital/design-system/issues/4029" target="_blank" style="color:#0C41BD;">issue #4029</a>)</p>
|
|
165
165
|
</div>
|
|
166
166
|
`,
|
|
167
167
|
}
|
|
@@ -53,6 +53,20 @@ const meta: Meta<typeof NotificationBar> = {
|
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
55
|
},
|
|
56
|
+
// @ts-expect-error the prop is passed down
|
|
57
|
+
type: {
|
|
58
|
+
control: 'select',
|
|
59
|
+
options: ['info', 'success', 'warning', 'error'],
|
|
60
|
+
table: {
|
|
61
|
+
type: {
|
|
62
|
+
summary: 'info | success | warning | error',
|
|
63
|
+
},
|
|
64
|
+
defaultValue: {
|
|
65
|
+
summary: 'info',
|
|
66
|
+
},
|
|
67
|
+
category: 'props',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
56
70
|
},
|
|
57
71
|
}
|
|
58
72
|
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import useCustomizableOptions, { type CustomizableOptions } from '@/composables/useCustomizableOptions'
|
|
3
3
|
import { useNotificationService } from '@/services/NotificationService'
|
|
4
4
|
import { mdiAlertCircleOutline, mdiAlertOctagonOutline, mdiCheckCircleOutline, mdiClose, mdiInformationOutline } from '@mdi/js'
|
|
5
|
-
import { computed, getCurrentInstance, ref, watch } from 'vue'
|
|
5
|
+
import { computed, getCurrentInstance, onMounted, onUnmounted, ref, useAttrs, watch } from 'vue'
|
|
6
6
|
import { useDisplay } from 'vuetify'
|
|
7
|
-
import defaultOptions from './
|
|
7
|
+
import defaultOptions from './config'
|
|
8
8
|
import { type Notification } from './types'
|
|
9
9
|
|
|
10
10
|
const props = withDefaults(defineProps<CustomizableOptions & {
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
bottom: false,
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
+
const attrs = useAttrs()
|
|
21
|
+
|
|
20
22
|
const options = useCustomizableOptions(defaultOptions, props)
|
|
21
23
|
|
|
22
24
|
const display = useDisplay()
|
|
@@ -109,6 +111,20 @@
|
|
|
109
111
|
isNotificationVisible.value = false
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
function closeOnEscape(e: KeyboardEvent) {
|
|
115
|
+
if (e.key == 'Escape' && notificationQueue.value.length) {
|
|
116
|
+
showNextNotification()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
onMounted(() => {
|
|
121
|
+
document.addEventListener('keyup', closeOnEscape)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
onUnmounted(() => {
|
|
125
|
+
document.removeEventListener('keyup', closeOnEscape)
|
|
126
|
+
})
|
|
127
|
+
|
|
112
128
|
defineExpose({
|
|
113
129
|
openNotification,
|
|
114
130
|
showNextNotification,
|
|
@@ -131,7 +147,7 @@
|
|
|
131
147
|
<VSnackbar
|
|
132
148
|
v-bind="options.snackbar"
|
|
133
149
|
v-model="isNotificationVisible"
|
|
134
|
-
role="status"
|
|
150
|
+
:role="attrs?.type === 'error' ? 'alert' : 'status'"
|
|
135
151
|
:eager="true"
|
|
136
152
|
:color="color"
|
|
137
153
|
:location="props.bottom ? 'bottom' : 'top'"
|
|
@@ -139,6 +155,7 @@
|
|
|
139
155
|
:multi-line="hasLongContent"
|
|
140
156
|
:timeout="currentNotification?.timeout ?? -1"
|
|
141
157
|
:width="isMobileVersion || isTabletVersion ? 'auto' : '960px'"
|
|
158
|
+
:min-width="isMobileVersion || isTabletVersion ? 'auto' : undefined"
|
|
142
159
|
:rounded="props.rounded"
|
|
143
160
|
:class="[{ 'long-text': hasLongContent }]"
|
|
144
161
|
>
|
|
@@ -220,5 +237,11 @@
|
|
|
220
237
|
.sy-notification-content {
|
|
221
238
|
min-width: 0;
|
|
222
239
|
word-wrap: break-word;
|
|
240
|
+
overflow-y: auto;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
:deep(.v-snackbar__wrapper) {
|
|
244
|
+
max-height: 100%;
|
|
245
|
+
overflow-y: auto;
|
|
223
246
|
}
|
|
224
247
|
</style>
|
|
@@ -209,17 +209,6 @@
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
.v-data-table-progress {
|
|
213
|
-
.v-progress-linear {
|
|
214
|
-
height: 4px !important;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
.v-progress-linear__background,
|
|
218
|
-
.v-progress-linear__indeterminate {
|
|
219
|
-
background: tokens.$primary-base;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
212
|
.v-field {
|
|
224
213
|
&--active {
|
|
225
214
|
.v-field__prepend-inner > .v-icon,
|
|
@@ -523,11 +523,11 @@ export const WithSuccess: Story = {
|
|
|
523
523
|
const isLongEnough = value.length >= 8
|
|
524
524
|
|
|
525
525
|
if (hasUpperCase && hasLowerCase && hasNumber && hasSpecialChar && isLongEnough) {
|
|
526
|
-
return
|
|
526
|
+
return true
|
|
527
527
|
}
|
|
528
528
|
return false
|
|
529
529
|
},
|
|
530
|
-
|
|
530
|
+
successMessage: 'Mot de passe fort',
|
|
531
531
|
},
|
|
532
532
|
},
|
|
533
533
|
],
|
|
@@ -691,7 +691,7 @@ export const WithValidation: Story = {
|
|
|
691
691
|
:custom-success-rules="customSuccessRules"
|
|
692
692
|
:show-success-messages="true"
|
|
693
693
|
:display-asterisk="true"
|
|
694
|
-
:is-validate-on-blur="
|
|
694
|
+
:is-validate-on-blur="true"
|
|
695
695
|
/>
|
|
696
696
|
<div class="mt-4">
|
|
697
697
|
<p><strong>Conseils pour tester :</strong></p>
|
|
@@ -807,6 +807,7 @@ export const WithCustomRules: Story = {
|
|
|
807
807
|
return true
|
|
808
808
|
},
|
|
809
809
|
fieldIdentifier: 'password',
|
|
810
|
+
successMessage: 'Le mot de passe est sécurisé',
|
|
810
811
|
},
|
|
811
812
|
},
|
|
812
813
|
{
|
|
@@ -88,29 +88,35 @@
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// Règle pour le message de succès
|
|
91
|
-
rules.push({
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
})
|
|
91
|
+
// rules.push({
|
|
92
|
+
// type: 'custom',
|
|
93
|
+
// options: {
|
|
94
|
+
// validate: (value: string) => value ? true : 'Ce champ est requis',
|
|
95
|
+
// successMessage: 'Mot de passe fort',
|
|
96
|
+
// fieldIdentifier: props.label || 'password',
|
|
97
|
+
// },
|
|
98
|
+
// })
|
|
99
99
|
|
|
100
100
|
return rules
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
// Initialisation du composable de validation
|
|
104
|
-
const { errors, warnings, successes, validateField } =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
const { errors, warnings, successes, validateField } = !props.readonly
|
|
105
|
+
? useValidation({
|
|
106
|
+
customRules: defaultRules.value,
|
|
107
|
+
warningRules: props.customWarningRules || [],
|
|
108
|
+
successRules: props.customSuccessRules || [],
|
|
109
|
+
showSuccessMessages: props.showSuccessMessages,
|
|
110
|
+
fieldIdentifier: props.label || 'password',
|
|
111
|
+
disableErrorHandling: props.disableErrorHandling,
|
|
112
|
+
})
|
|
113
|
+
: {
|
|
114
|
+
errors: ref<string[]>([]),
|
|
115
|
+
warnings: ref<string[]>([]),
|
|
116
|
+
successes: ref<string[]>([]),
|
|
117
|
+
validateField: () => {},
|
|
118
|
+
}
|
|
112
119
|
|
|
113
|
-
// Computed pour les états de validation
|
|
114
120
|
const hasError = computed(() => errors.value.length > 0)
|
|
115
121
|
const hasWarning = computed(() => warnings.value.length > 0)
|
|
116
122
|
const hasSuccess = computed(() => successes.value.length > 0 && props.showSuccessMessages)
|
|
@@ -149,6 +155,7 @@
|
|
|
149
155
|
}, { immediate: true })
|
|
150
156
|
|
|
151
157
|
watch(() => password.value, () => {
|
|
158
|
+
if (props.readonly) return
|
|
152
159
|
validateField(password.value, [...defaultRules.value, ...(props.customRules || [])], props.customWarningRules || [], props.customSuccessRules || [])
|
|
153
160
|
emit('update:modelValue', password.value)
|
|
154
161
|
})
|
|
@@ -160,6 +167,7 @@
|
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
const validateOnSubmit = () => {
|
|
170
|
+
if (props.readonly) return
|
|
163
171
|
validateField(password.value, [...defaultRules.value, ...(props.customRules || [])], props.customWarningRules || [], props.customSuccessRules || [])
|
|
164
172
|
const isValid = errors.value.length === 0
|
|
165
173
|
if (isValid) {
|
|
@@ -199,7 +207,7 @@
|
|
|
199
207
|
:rules="[...defaultRules, ...props.customRules]"
|
|
200
208
|
class="vd-password"
|
|
201
209
|
:validate-on="props.isValidateOnBlur ? 'blur lazy' : 'lazy'"
|
|
202
|
-
@blur="props.isValidateOnBlur ? validateField(password, [...defaultRules, ...(props.customRules || [])], props.customWarningRules || [], props.customSuccessRules || []) : () => {}"
|
|
210
|
+
@blur="props.isValidateOnBlur && !props.readonly ? validateField(password, [...defaultRules, ...(props.customRules || [])], props.customWarningRules || [], props.customSuccessRules || []) : () => {}"
|
|
203
211
|
@keydown="handleKeydown"
|
|
204
212
|
>
|
|
205
213
|
<template #append-inner>
|