@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.
Files changed (133) hide show
  1. package/dist/components/Accordion/Accordion.d.ts +39 -0
  2. package/dist/components/Accordion/config.d.ts +9 -0
  3. package/dist/components/ChipList/ChipList.d.ts +1 -1
  4. package/dist/components/CookiesSelection/CookiesSelection.d.ts +26 -26
  5. package/dist/components/CopyBtn/CopyBtn.d.ts +2 -0
  6. package/dist/components/Customs/SyInputSelect/SyInputSelect.d.ts +12 -0
  7. package/dist/components/Customs/SySelect/SySelect.d.ts +43 -16
  8. package/dist/components/Customs/SyTextField/SyTextField.d.ts +1391 -1
  9. package/dist/components/DatePicker/DatePicker.d.ts +2810 -16
  10. package/dist/components/DatePicker/DateTextInput.d.ts +1401 -4
  11. package/dist/components/DiacriticPicker/DiacriticPicker.d.ts +27 -0
  12. package/dist/components/DiacriticPicker/config.d.ts +14 -0
  13. package/dist/components/DiacriticPicker/locales.d.ts +6 -0
  14. package/dist/components/DownloadBtn/DownloadBtn.d.ts +1 -1
  15. package/dist/components/FooterBar/FooterBar.d.ts +1 -1
  16. package/dist/components/LangBtn/LangBtn.d.ts +4 -4
  17. package/dist/components/NirField/NirField.d.ts +2796 -4
  18. package/dist/components/NotificationBar/NotificationBar.d.ts +1 -1
  19. package/dist/components/PasswordField/PasswordField.d.ts +1 -1
  20. package/dist/components/PeriodField/PeriodField.d.ts +5636 -48
  21. package/dist/components/PhoneField/PhoneField.d.ts +1 -0
  22. package/dist/components/PhoneField/tests/types.d.ts +18 -0
  23. package/dist/components/SyAlert/SyAlert.d.ts +72 -1
  24. package/dist/components/SyTextArea/SyTextArea.d.ts +900 -0
  25. package/dist/components/SyTextArea/locales.d.ts +3 -0
  26. package/dist/components/SyTextArea/trimStartOnUpdate.d.ts +1 -0
  27. package/dist/components/SyTextArea/useTextActions.d.ts +13 -0
  28. package/dist/components/SyTextArea/wrapText.d.ts +1 -0
  29. package/dist/components/TableToolbar/TableToolbar.d.ts +10 -4
  30. package/dist/components/TableToolbar/config.d.ts +3 -2
  31. package/dist/components/UploadWorkflow/UploadWorkflow.d.ts +26 -26
  32. package/dist/components/index.d.ts +4 -0
  33. package/dist/composables/date/useDateFormat.d.ts +2 -2
  34. package/dist/composables/date/useDateFormatDayjs.d.ts +23 -0
  35. package/dist/composables/date/useDateInitializationDayjs.d.ts +18 -0
  36. package/dist/composables/date/useHolidayDay.d.ts +36 -0
  37. package/dist/design-system-v3.js +5106 -4208
  38. package/dist/design-system-v3.umd.cjs +4 -1
  39. package/dist/designTokens/tokens/pa/paLightTheme.d.ts +1 -32
  40. package/dist/style.css +1 -1
  41. package/dist/utils/rules/index.d.ts +1 -0
  42. package/dist/utils/rules/isHolidayDay/index.d.ts +11 -0
  43. package/dist/utils/rules/isHolidayDay/locales.d.ts +2 -0
  44. package/package.json +3 -2
  45. package/src/assets/settings.scss +12 -0
  46. package/src/components/Accordion/Accordion.mdx +69 -0
  47. package/src/components/Accordion/Accordion.stories.ts +262 -0
  48. package/src/components/Accordion/Accordion.vue +319 -0
  49. package/src/components/Accordion/config.ts +9 -0
  50. package/src/components/Accordion/tests/__snapshots__/accordion.spec.ts.snap +155 -0
  51. package/src/components/Accordion/tests/accordion.spec.ts +492 -0
  52. package/src/components/CopyBtn/CopyBtn.stories.ts +189 -0
  53. package/src/components/CopyBtn/CopyBtn.vue +29 -1
  54. package/src/components/CopyBtn/tests/CopyBtn.spec.ts +102 -0
  55. package/src/components/Customs/SyInputSelect/SyInputSelect.stories.ts +155 -1
  56. package/src/components/Customs/SyInputSelect/SyInputSelect.vue +97 -14
  57. package/src/components/Customs/SyInputSelect/tests/SyInputSelect.spec.ts +386 -106
  58. package/src/components/Customs/SySelect/SySelect.stories.ts +121 -2
  59. package/src/components/Customs/SySelect/SySelect.vue +33 -8
  60. package/src/components/Customs/SySelect/tests/SySelect.spec.ts +290 -1
  61. package/src/components/Customs/SyTextField/Accessibilite.stories.ts +7 -0
  62. package/src/components/Customs/SyTextField/SyTextField.stories.ts +13 -0
  63. package/src/components/Customs/SyTextField/SyTextField.vue +87 -20
  64. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +795 -0
  65. package/src/components/DatePicker/DatePicker.stories.ts +432 -1
  66. package/src/components/DatePicker/DatePicker.vue +82 -27
  67. package/src/components/DatePicker/DatePickerValidation.stories.ts +9 -1
  68. package/src/components/DatePicker/DateTextInput.vue +101 -138
  69. package/src/components/DatePicker/docExamples/DatePickerBidirectionalValidation.vue +282 -0
  70. package/src/components/DatePicker/examples/DatePickerHolidayRule.vue +130 -0
  71. package/src/components/DatePicker/tests/DatePicker.spec.ts +33 -32
  72. package/src/components/DatePicker/tests/DateTextInput.spec.ts +81 -33
  73. package/src/components/DiacriticPicker/DiacriticPicker.mdx +104 -0
  74. package/src/components/DiacriticPicker/DiacriticPicker.stories.ts +447 -0
  75. package/src/components/DiacriticPicker/DiacriticPicker.vue +262 -0
  76. package/src/components/DiacriticPicker/config.ts +15 -0
  77. package/src/components/DiacriticPicker/locales.ts +6 -0
  78. package/src/components/DiacriticPicker/tests/DiatriticPicker.spec.ts +132 -0
  79. package/src/components/DialogBox/DialogBox.vue +1 -3
  80. package/src/components/NirField/NirField.stories.ts +172 -0
  81. package/src/components/NirField/NirField.vue +15 -7
  82. package/src/components/NotificationBar/Accessibilite.stories.ts +1 -1
  83. package/src/components/NotificationBar/NotificationBar.stories.ts +14 -0
  84. package/src/components/NotificationBar/NotificationBar.vue +26 -3
  85. package/src/components/NotificationBar/{options.ts → config.ts} +0 -1
  86. package/src/components/PaginatedTable/PaginatedTable.vue +0 -11
  87. package/src/components/PasswordField/PasswordField.stories.ts +4 -3
  88. package/src/components/PasswordField/PasswordField.vue +26 -18
  89. package/src/components/PasswordField/tests/PasswordField.spec.ts +1 -10
  90. package/src/components/PhoneField/PhoneField.stories.ts +143 -0
  91. package/src/components/PhoneField/PhoneField.vue +88 -30
  92. package/src/components/PhoneField/tests/PhoneField.additional.spec.ts +266 -0
  93. package/src/components/PhoneField/tests/PhoneField.spec.ts +248 -28
  94. package/src/components/PhoneField/tests/types.d.ts +19 -0
  95. package/src/components/SyAlert/Accessibilite.stories.ts +4 -0
  96. package/src/components/SyAlert/SyAlert.mdx +3 -7
  97. package/src/components/SyAlert/SyAlert.stories.ts +19 -12
  98. package/src/components/SyAlert/SyAlert.vue +88 -51
  99. package/src/components/SyAlert/tests/SyAlert.spec.ts +20 -2
  100. package/src/components/SyAlert/tests/__snapshots__/SyAlert.spec.ts.snap +83 -75
  101. package/src/components/SyTextArea/SyTextArea.mdx +17 -0
  102. package/src/components/SyTextArea/SyTextArea.stories.ts +322 -0
  103. package/src/components/SyTextArea/SyTextArea.vue +113 -0
  104. package/src/components/SyTextArea/locales.ts +3 -0
  105. package/src/components/SyTextArea/tests/SyTextArea.spec.ts +194 -0
  106. package/src/components/SyTextArea/trimStartOnUpdate.ts +12 -0
  107. package/src/components/SyTextArea/useTextActions.ts +52 -0
  108. package/src/components/SyTextArea/wrapText.ts +42 -0
  109. package/src/components/TableToolbar/TableToolbar.mdx +86 -1
  110. package/src/components/TableToolbar/TableToolbar.stories.ts +422 -74
  111. package/src/components/TableToolbar/TableToolbar.vue +25 -8
  112. package/src/components/TableToolbar/config.ts +3 -2
  113. package/src/components/TableToolbar/tests/__snapshots__/TableToolbar.spec.ts.snap +35 -12
  114. package/src/components/index.ts +4 -0
  115. package/src/composables/date/useDateFormat.ts +17 -1
  116. package/src/composables/date/useDateFormatDayjs.ts +84 -0
  117. package/src/composables/date/useDateInitializationDayjs.ts +133 -0
  118. package/src/composables/date/useHolidayDay.ts +98 -0
  119. package/src/composables/rules/useFieldValidation.ts +16 -3
  120. package/src/composables/validation/useValidation.ts +2 -1
  121. package/src/designTokens/tokens/pa/paLightTheme.ts +10 -41
  122. package/src/stories/Accessibilite/Avancement/Avancement.mdx +12 -0
  123. package/src/stories/Accessibilite/Avancement/Avancement.stories.ts +134 -0
  124. package/src/stories/Accessibilite/Introduction.mdx +5 -2
  125. package/src/stories/DesignTokens/colors.stories.ts +100 -41
  126. package/src/utils/rules/index.ts +1 -0
  127. package/src/utils/rules/isHolidayDay/IsHolidayDay.mdx +52 -0
  128. package/src/utils/rules/isHolidayDay/IsHolidayDay.stories.ts +129 -0
  129. package/src/utils/rules/isHolidayDay/index.ts +36 -0
  130. package/src/utils/rules/isHolidayDay/locales.ts +5 -0
  131. package/src/utils/rules/isHolidayDay/tests/isHolidayDay.spec.ts +35 -0
  132. /package/dist/components/NotificationBar/{options.d.ts → config.d.ts} +0 -0
  133. /package/src/components/DatePicker/{DatePickerValidationExamples.vue → docExamples/DatePickerValidationExamples.vue} +0 -0
@@ -0,0 +1,6 @@
1
+ export const locales = {
2
+ inputAriaLabel: 'Champ de saisie avec diacritiques',
3
+ title: 'caractères accentués',
4
+ uppercaseChars: 'Caractères majuscules',
5
+ lowercaseChars: 'Caractères minuscules',
6
+ }
@@ -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
- const filteredElements = elements.filter(element => (
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/3967" target="_blank" style="color:#0C41BD;">issue #3967</a>)</p>
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 './options'
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>
@@ -1,5 +1,4 @@
1
1
  type VariantType = 'text' | 'flat' | 'elevated' | 'tonal' | 'outlined' | 'plain'
2
- // TODO rename file to config.ts
3
2
 
4
3
  const defaultOptions = {
5
4
  snackBar: {
@@ -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 'Mot de passe fort'
526
+ return true
527
527
  }
528
528
  return false
529
529
  },
530
- fieldIdentifier: 'password',
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="false"
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
- 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
- })
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 } = useValidation({
105
- customRules: defaultRules.value,
106
- warningRules: props.customWarningRules || [],
107
- successRules: props.customSuccessRules || [],
108
- showSuccessMessages: props.showSuccessMessages,
109
- fieldIdentifier: props.label || 'password',
110
- disableErrorHandling: props.disableErrorHandling,
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>