@cnamts/synapse 0.0.11-alpha → 0.0.12-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/design-system-v3.js +3878 -3189
  2. package/dist/design-system-v3.umd.cjs +1 -1
  3. package/dist/src/components/Amelipro/types/languages.d.ts +6 -0
  4. package/dist/src/components/Amelipro/types/types.d.ts +65 -0
  5. package/dist/src/components/CookieBanner/CookieBanner.d.ts +1 -1
  6. package/dist/src/components/Customs/SyInputSelect/SyInputSelect.d.ts +2 -0
  7. package/dist/src/components/Customs/SyTextField/SyTextField.d.ts +29 -23
  8. package/dist/src/components/Customs/SyTextField/types.d.ts +1 -0
  9. package/dist/src/components/DatePicker/DatePicker.d.ts +70 -59
  10. package/dist/src/components/DatePicker/DateTextInput.d.ts +67 -56
  11. package/dist/src/components/ErrorPage/ErrorPage.d.ts +1 -1
  12. package/dist/src/components/FileList/FileList.d.ts +1 -0
  13. package/dist/src/components/FileList/UploadItem/UploadItem.d.ts +1 -1
  14. package/dist/src/components/FilterSideBar/FilterSideBar.d.ts +31 -0
  15. package/dist/src/components/FilterSideBar/locales.d.ts +7 -0
  16. package/dist/src/components/FilterSideBar/tests/FilterSideBar.spec.d.ts +1 -0
  17. package/dist/src/components/LangBtn/LangBtn.d.ts +2 -2
  18. package/dist/src/components/NirField/NirField.d.ts +940 -0
  19. package/dist/src/components/NotificationBar/NotificationBar.d.ts +1 -1
  20. package/dist/src/components/PasswordField/PasswordField.d.ts +40 -8
  21. package/dist/src/components/PeriodField/PeriodField.d.ts +142 -120
  22. package/dist/src/components/PhoneField/PhoneField.d.ts +11 -2
  23. package/dist/src/components/RatingPicker/EmotionPicker/EmotionPicker.d.ts +1 -1
  24. package/dist/src/components/RatingPicker/NumberPicker/NumberPicker.d.ts +1 -1
  25. package/dist/src/components/RatingPicker/StarsPicker/StarsPicker.d.ts +1 -1
  26. package/dist/src/components/UploadWorkflow/config.d.ts +29 -0
  27. package/dist/src/components/UploadWorkflow/locales.d.ts +7 -0
  28. package/dist/src/components/UploadWorkflow/tests/UploadWorkflow.spec.d.ts +1 -0
  29. package/dist/src/components/UploadWorkflow/types.d.ts +19 -0
  30. package/dist/src/components/UploadWorkflow/useFileList.d.ts +10 -0
  31. package/dist/src/components/UploadWorkflow/useFileUploadJourney.d.ts +9 -0
  32. package/dist/src/components/index.d.ts +2 -0
  33. package/dist/src/composables/rules/useFieldValidation.d.ts +1 -0
  34. package/dist/src/composables/validation/tests/useValidation.spec.d.ts +1 -0
  35. package/dist/src/composables/validation/useValidation.d.ts +39 -0
  36. package/dist/src/designTokens/index.d.ts +3 -1
  37. package/dist/src/vuetifyConfig.d.ts +81 -0
  38. package/dist/style.css +1 -1
  39. package/package.json +1 -1
  40. package/src/assets/_elevations.scss +89 -0
  41. package/src/assets/_fonts.scss +6 -0
  42. package/src/assets/_radius.scss +86 -0
  43. package/src/assets/_spacers.scss +149 -0
  44. package/src/assets/settings.scss +7 -3
  45. package/src/assets/tokens.scss +32 -29
  46. package/src/components/Amelipro/types/languages.d.ts +6 -0
  47. package/src/components/Amelipro/types/types.d.ts +65 -0
  48. package/src/components/Customs/SyInputSelect/SyInputSelect.stories.ts +65 -0
  49. package/src/components/Customs/SyInputSelect/SyInputSelect.vue +13 -3
  50. package/src/components/Customs/SySelect/SySelect.stories.ts +88 -5
  51. package/src/components/Customs/SySelect/SySelect.vue +36 -10
  52. package/src/components/Customs/SySelect/tests/SySelect.spec.ts +135 -2
  53. package/src/components/Customs/SyTextField/SyTextField.stories.ts +576 -85
  54. package/src/components/Customs/SyTextField/SyTextField.vue +132 -104
  55. package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +190 -38
  56. package/src/components/Customs/SyTextField/types.d.ts +1 -0
  57. package/src/components/DatePicker/DatePicker.vue +405 -137
  58. package/src/components/DatePicker/DateTextInput.vue +15 -0
  59. package/src/components/DatePicker/tests/DatePicker.spec.ts +8 -15
  60. package/src/components/FileList/FileList.vue +2 -1
  61. package/src/components/FileList/UploadItem/UploadItem.vue +10 -0
  62. package/src/components/FileUpload/FileUpload.stories.ts +84 -0
  63. package/src/components/FileUpload/FileUpload.vue +1 -0
  64. package/src/components/FileUpload/tests/FileUpload.spec.ts +4 -4
  65. package/src/components/FilterInline/FilterInline.mdx +180 -34
  66. package/src/components/FilterInline/FilterInline.stories.ts +363 -6
  67. package/src/components/FilterSideBar/FilterSideBar.mdx +237 -0
  68. package/src/components/FilterSideBar/FilterSideBar.stories.ts +798 -0
  69. package/src/components/FilterSideBar/FilterSideBar.vue +193 -0
  70. package/src/components/FilterSideBar/locales.ts +8 -0
  71. package/src/components/FilterSideBar/tests/FilterSideBar.spec.ts +305 -0
  72. package/src/components/FilterSideBar/tests/__snapshots__/FilterSideBar.spec.ts.snap +39 -0
  73. package/src/components/HeaderBar/Usages.mdx +1 -1
  74. package/src/components/NirField/NirField.stories.ts +573 -29
  75. package/src/components/NirField/NirField.vue +397 -359
  76. package/src/components/NirField/tests/NirField.spec.ts +88 -52
  77. package/src/components/NirField/tests//342/200/257dataset/342/200/257.md +12 -0
  78. package/src/components/NotificationBar/Accessibilite.stories.ts +4 -0
  79. package/src/components/NotificationBar/NotificationBar.stories.ts +18 -13
  80. package/src/components/PasswordField/PasswordField.mdx +129 -47
  81. package/src/components/PasswordField/PasswordField.stories.ts +924 -120
  82. package/src/components/PasswordField/PasswordField.vue +209 -99
  83. package/src/components/PasswordField/tests/PasswordField.spec.ts +138 -9
  84. package/src/components/PeriodField/PeriodField.vue +55 -54
  85. package/src/components/PhoneField/PhoneField.stories.ts +69 -0
  86. package/src/components/PhoneField/PhoneField.vue +3 -0
  87. package/src/components/PhoneField/indicatifs.ts +1 -1
  88. package/src/components/UploadWorkflow/UploadWorkflow.mdx +75 -0
  89. package/src/components/UploadWorkflow/UploadWorkflow.stories.ts +943 -0
  90. package/src/components/UploadWorkflow/UploadWorkflow.vue +230 -0
  91. package/src/components/UploadWorkflow/config.ts +29 -0
  92. package/src/components/UploadWorkflow/locales.ts +8 -0
  93. package/src/components/UploadWorkflow/tests/UploadWorkflow.spec.ts +257 -0
  94. package/src/components/UploadWorkflow/tests/__snapshots__/UploadWorkflow.spec.ts.snap +54 -0
  95. package/src/components/UploadWorkflow/types.ts +21 -0
  96. package/src/components/UploadWorkflow/useFileList.ts +84 -0
  97. package/src/components/UploadWorkflow/useFileUploadJourney.ts +18 -0
  98. package/src/components/index.ts +2 -0
  99. package/src/composables/rules/useFieldValidation.ts +5 -2
  100. package/src/composables/validation/tests/useValidation.spec.ts +154 -0
  101. package/src/composables/validation/useValidation.ts +165 -0
  102. package/src/designTokens/index.ts +4 -0
  103. package/src/stories/Demarrer/Accueil.mdx +1 -1
  104. package/src/stories/DesignTokens/ThemePA.mdx +4 -30
  105. package/src/stories/GuideDuDev/UtiliserLesRules.mdx +319 -76
  106. package/src/stories/GuideDuDev/moduleDeNotification.mdx +1 -1
  107. package/src/vuetifyConfig.ts +61 -0
  108. package/src/composables/useFilterable/__snapshots__/useFilterable.spec.ts.snap +0 -3
@@ -0,0 +1,230 @@
1
+ <script setup lang="ts">
2
+ import useCustomizableOptions, {
3
+ type CustomizableOptions,
4
+ } from '@/composables/useCustomizableOptions'
5
+ import { useWidthable, type Widthable } from '@/composables/widthable'
6
+ import { required } from '@/utils/rules/required'
7
+ import { computed, reactive, ref, toRef, watch } from 'vue'
8
+ import DialogBox from '../DialogBox/DialogBox.vue'
9
+ import FileList from '../FileList/FileList.vue'
10
+ import FilePreview from '../FilePreview/FilePreview.vue'
11
+ import FileUpload from '../FileUpload/FileUpload.vue'
12
+ import { config } from './config'
13
+ import { locales as defaultLocales } from './locales'
14
+ import type { FileItem, SelectedFile, UploadItem } from './types'
15
+ import useFileUploadJourney from './useFileUploadJourney'
16
+ import useFileList from './useFileList'
17
+
18
+ const props = withDefaults(
19
+ defineProps<
20
+ Widthable &
21
+ CustomizableOptions & {
22
+ uploadList: UploadItem[]
23
+ sectionTitle?: string
24
+ showFilePreview?: boolean
25
+ locales?: typeof defaultLocales
26
+ }
27
+ >(),
28
+ {
29
+ sectionTitle: undefined,
30
+ showFilePreview: false,
31
+ locales: () => defaultLocales,
32
+ },
33
+ )
34
+
35
+ const emits = defineEmits<{
36
+ (e: 'error', value: string[]): void
37
+ (e: 'update:modelValue', value: SelectedFile[]): void
38
+ }>()
39
+
40
+ const selectedFiles = defineModel<SelectedFile[]>({
41
+ type: Array,
42
+ default: reactive([]),
43
+ })
44
+ watch(
45
+ selectedFiles,
46
+ (value) => {
47
+ emits('update:modelValue', value)
48
+ },
49
+ { deep: true },
50
+ )
51
+
52
+ const { widthStyles } = useWidthable(props)
53
+ const options = useCustomizableOptions(config, props)
54
+
55
+ const fileUpload = ref<typeof FileUpload>()
56
+ const { selectItems, selectedItem } = useFileUploadJourney(
57
+ toRef(props, 'uploadList'),
58
+ )
59
+
60
+ const title = computed(
61
+ () => props.sectionTitle ?? props.locales.title(!!props.uploadList.length),
62
+ )
63
+
64
+ const { addOrReplaceFile, resetFile, setItemOnError, filledUploadList }
65
+ = useFileList(selectedFiles, toRef(props, 'uploadList'))
66
+
67
+ // handle FileList
68
+ let inlineSelectedItemId: string | undefined = undefined
69
+ function uploadInline(item: FileItem) {
70
+ inlineSelectedItemId = item.id
71
+
72
+ fileUpload.value!.fileInput!.click()
73
+ }
74
+ function previewFile(file: FileItem & { file?: File }) {
75
+ showPreviewDialog.value = true
76
+ fileToPreview.value = file.file
77
+ }
78
+
79
+ // handle FileUpload
80
+ const uploadedFiles = ref<File[]>([])
81
+
82
+ const showFileUpload = computed(
83
+ () => (
84
+ selectedFiles.value.length < props.uploadList.length
85
+ || props.uploadList.find(uploadItem => uploadItem.state === 'error') !== undefined
86
+ ),
87
+ )
88
+
89
+ function fileSelected(files: File[]) {
90
+ const fileId
91
+ = inlineSelectedItemId ?? (props.uploadList.length === 1
92
+ ? props.uploadList[0].id
93
+ : undefined)
94
+ inlineSelectedItemId = undefined
95
+
96
+ if (props.showFilePreview || fileId === undefined) {
97
+ showSelectDialog.value = true
98
+ selectedItem.value = fileId
99
+
100
+ return
101
+ }
102
+
103
+ addOrReplaceFile(files[0], fileId)
104
+ }
105
+
106
+ function uploadError(errors: string[]) {
107
+ emits('error', errors)
108
+ if (!inlineSelectedItemId) {
109
+ return
110
+ }
111
+ setItemOnError(inlineSelectedItemId)
112
+ inlineSelectedItemId = undefined
113
+ }
114
+
115
+ // handle DialogBox
116
+ const showSelectDialog = ref(false)
117
+
118
+ function dialogConfirm() {
119
+ if (!selectedItem.value) {
120
+ return
121
+ }
122
+
123
+ addOrReplaceFile(uploadedFiles.value[0], selectedItem.value)
124
+
125
+ showSelectDialog.value = false
126
+ selectedItem.value = undefined
127
+ }
128
+
129
+ // handle preview
130
+ const fileToPreview = ref<File>()
131
+ const showPreviewDialog = ref(false)
132
+ </script>
133
+
134
+ <template>
135
+ <div
136
+ :style="widthStyles"
137
+ class="sy-upload-workflow white"
138
+ >
139
+ <slot name="title">
140
+ <h4 class="text-h6 mb-2">
141
+ {{ title }}
142
+ </h4>
143
+ </slot>
144
+
145
+ <FileList
146
+ v-bind="options.fileList"
147
+ :upload-list="filledUploadList"
148
+ @upload="uploadInline"
149
+ @retry="uploadInline"
150
+ @delete="resetFile"
151
+ @preview="previewFile"
152
+ />
153
+
154
+ <Transition>
155
+ <FileUpload
156
+ v-if="showFileUpload"
157
+ ref="fileUpload"
158
+ v-bind="options.fileUpload"
159
+ v-model="uploadedFiles"
160
+ @error="uploadError"
161
+ @update:model-value="fileSelected"
162
+ />
163
+ </Transition>
164
+
165
+ <DialogBox
166
+ v-model="showSelectDialog"
167
+ v-bind="options.dialog"
168
+ @cancel="showSelectDialog = false"
169
+ @confirm="dialogConfirm"
170
+ >
171
+ <template #title>
172
+ <slot name="modal-title">
173
+ {{ locales.modalTitle }}
174
+ </slot>
175
+ </template>
176
+ <slot name="modal-description" />
177
+
178
+ <VForm
179
+ v-if="true"
180
+ ref="form"
181
+ v-bind="options.form"
182
+ class="mb-2"
183
+ >
184
+ <VSelect
185
+ v-model="selectedItem"
186
+ v-bind="options.select"
187
+ :items="selectItems"
188
+ item-title="text"
189
+ item-value="value"
190
+ :rules="[required]"
191
+ color="primary"
192
+ />
193
+ </VForm>
194
+
195
+ <FilePreview
196
+ v-if="showFilePreview"
197
+ :options="options.filePreview"
198
+ :file="uploadedFiles[0]"
199
+ />
200
+ </DialogBox>
201
+
202
+ <DialogBox
203
+ v-model="showPreviewDialog"
204
+ v-bind="options.previewDialog"
205
+ hide-actions
206
+ >
207
+ <slot name="preview-description" />
208
+
209
+ <FilePreview
210
+ :options="options.filePreview"
211
+ :file="fileToPreview"
212
+ />
213
+ </DialogBox>
214
+ </div>
215
+ </template>
216
+
217
+ <style lang="scss" scoped>
218
+ .v-enter-active,
219
+ .v-leave-active {
220
+ interpolate-size: allow-keywords;
221
+ transition: height 0.2s ease, opacity 0.2s ease;
222
+ overflow: hidden;
223
+ }
224
+
225
+ .v-enter-from,
226
+ .v-leave-to {
227
+ height: 0;
228
+ opacity: 0;
229
+ }
230
+ </style>
@@ -0,0 +1,29 @@
1
+ export const config = {
2
+ fileUpload: {
3
+ class: 'mt-6',
4
+ },
5
+ dialog: {
6
+ persistent: true,
7
+ width: '550',
8
+ },
9
+ card: {
10
+ class: 'pa-4',
11
+ },
12
+ select: {
13
+ variant: 'outlined',
14
+ validateOnBlur: true,
15
+ label: 'Nature du fichier',
16
+ },
17
+ layout: {
18
+ wrap: true,
19
+ class: 'mt-2',
20
+ },
21
+ cancelBtn: {
22
+ text: true,
23
+ class: 'mr-4',
24
+ color: 'accent',
25
+ },
26
+ confirmBtn: {
27
+ color: 'accent',
28
+ },
29
+ } as const
@@ -0,0 +1,8 @@
1
+ export const locales = {
2
+ title: (plural: boolean): string =>
3
+ `Document${plural ? 's' : ''} à nous transmettre`,
4
+ importTitle: 'Importer des fichiers',
5
+ modalTitle: 'Fichier transmis',
6
+ cancelBtn: 'Retour',
7
+ confirmBtn: 'Confirmer',
8
+ } as const
@@ -0,0 +1,257 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import { vuetify } from '@tests/unit/setup'
4
+
5
+ import UploadWorkflow from '../UploadWorkflow.vue'
6
+ import { locales as fileListLocales } from '@/components/FileList/UploadItem/locales'
7
+ import { locales as FileUploadLocales } from '@/components/FileUpload/locales'
8
+ import { afterEach } from 'node:test'
9
+ import { VSelect } from 'vuetify/components'
10
+
11
+ describe('UploadWorkflow', () => {
12
+ afterEach(() => {
13
+ vi.clearAllMocks()
14
+ document.body.innerHTML = ''
15
+ })
16
+
17
+ it('render the upload list', async () => {
18
+ const wrapper = mount(UploadWorkflow, {
19
+ props: {
20
+ uploadList: [
21
+ {
22
+ id: 'ID',
23
+ title: 'Carte d\'identité',
24
+ },
25
+ {
26
+ id: 'bill',
27
+ title: 'Facture de soin',
28
+ },
29
+ ],
30
+ },
31
+ global: {
32
+ plugins: [vuetify],
33
+ },
34
+ })
35
+
36
+ expect(wrapper.html()).toMatchSnapshot()
37
+ expect(wrapper.findAll('.file-item')).toHaveLength(2)
38
+ expect(wrapper.find('.sy-file-upload').isVisible()).toBeTruthy()
39
+ })
40
+
41
+ it('shows the file in the list when set with the list button', async () => {
42
+ const wrapper = mount(UploadWorkflow, {
43
+ props: {
44
+ uploadList: [
45
+ {
46
+ id: 'ID',
47
+ title: 'Carte d\'identité',
48
+ },
49
+ {
50
+ id: 'bill',
51
+ title: 'Facture de soin',
52
+ },
53
+ ],
54
+ },
55
+ global: {
56
+ plugins: [vuetify],
57
+ },
58
+ })
59
+
60
+ await wrapper.find('.file-item button').trigger('click')
61
+
62
+ const file: File = new File([''], 'theFilename.pdf', {
63
+ type: 'application/pdf',
64
+ })
65
+
66
+ await wrapper.find('input').trigger('drop', {
67
+ dataTransfer: {
68
+ files: [file],
69
+ },
70
+ })
71
+
72
+ expect(wrapper.find('.file-item').text()).toContain('theFilename.pdf')
73
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
74
+ })
75
+
76
+ it('shows the item as error in the list when set with the list button', async () => {
77
+ const wrapper = mount(UploadWorkflow, {
78
+ props: {
79
+ uploadList: [
80
+ {
81
+ id: 'ID',
82
+ title: 'Carte d\'identité',
83
+ },
84
+ {
85
+ id: 'bill',
86
+ title: 'Facture de soin',
87
+ },
88
+ ],
89
+ },
90
+ global: {
91
+ plugins: [vuetify],
92
+ },
93
+ })
94
+
95
+ await wrapper.findAll('.file-item button')[1].trigger('click')
96
+
97
+ const file: File = new File([''], 'theFilename.invalid', {
98
+ type: 'application/invalid',
99
+ })
100
+
101
+ const input = wrapper.find('input')
102
+ input.element.files = [file] as unknown as FileList
103
+ await input.trigger('change')
104
+
105
+ expect(wrapper.emitted('error')).toEqual([
106
+ [
107
+ [
108
+ FileUploadLocales.errorExtension('theFilename.invalid', [
109
+ 'pdf',
110
+ 'jpg',
111
+ 'jpeg',
112
+ 'png',
113
+ ]),
114
+ ],
115
+ ],
116
+ ])
117
+ expect(wrapper.findAll('.file-item')[1].text()).toContain(
118
+ fileListLocales.error,
119
+ )
120
+ })
121
+
122
+ it('accept the file when we use the FileUpload component with many items in the uploadList', async () => {
123
+ const wrapper = mount(UploadWorkflow, {
124
+ props: {
125
+ uploadList: [
126
+ {
127
+ id: 'ID',
128
+ title: 'Carte d\'identité',
129
+ },
130
+ {
131
+ id: 'bill',
132
+ title: 'Facture de soin',
133
+ },
134
+ ],
135
+ },
136
+ global: {
137
+ plugins: [vuetify],
138
+ stubs: {
139
+ VDialog: {
140
+ template: '<div><slot /></div>',
141
+ },
142
+ },
143
+ },
144
+ })
145
+
146
+ const file: File = new File([''], 'uploadInField.pdf', {
147
+ type: 'application/pdf',
148
+ })
149
+
150
+ await wrapper.find('input').trigger('drop', {
151
+ dataTransfer: {
152
+ files: [file],
153
+ },
154
+ })
155
+
156
+ wrapper.find('.v-select input').setValue('bill')
157
+ wrapper.findComponent(VSelect).vm.$emit('update:modelValue', 'bill')
158
+
159
+ await wrapper.find('[data-test-id="confirm-btn"]').trigger('click')
160
+
161
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
+ expect((wrapper.emitted('update:modelValue')?.[0][0] as any[]).find(item => item.fileName === 'uploadInField.pdf')).toBeTruthy()
163
+
164
+ expect(wrapper.findAll('.file-item')[1].text()).toContain('uploadInField.pdf')
165
+ })
166
+
167
+ it('replace the selected file', async () => {
168
+ const wrapper = mount(UploadWorkflow, {
169
+ props: {
170
+ uploadList: [
171
+ {
172
+ id: 'ID',
173
+ title: 'Carte d\'identité',
174
+ },
175
+ {
176
+ id: 'bill',
177
+ title: 'Facture de soin',
178
+ },
179
+ ],
180
+ modelValue: [
181
+ {
182
+ fileName: 'file1.pdf',
183
+ file: new File([''], 'file1.pdf', {
184
+ type: 'application/pdf',
185
+ }),
186
+ id: 'ID',
187
+ title: 'Carte d\'identité',
188
+ },
189
+ ],
190
+ },
191
+ global: {
192
+ plugins: [vuetify],
193
+ },
194
+ })
195
+
196
+ await wrapper.find('.file-item button').trigger('click')
197
+
198
+ const file: File = new File([''], 'file2.pdf', {
199
+ type: 'application/pdf',
200
+ })
201
+
202
+ await wrapper.find('input').trigger('drop', {
203
+ dataTransfer: {
204
+ files: [file],
205
+ },
206
+ })
207
+
208
+ expect(wrapper.find('.file-item').text()).toContain('file2.pdf')
209
+ })
210
+
211
+ it('show the preview of an image', async () => {
212
+ const wrapper = mount(UploadWorkflow, {
213
+ props: {
214
+ modelValue: [],
215
+ uploadList: [
216
+ {
217
+ id: 'CERFA1',
218
+ title: 'CERFA 1',
219
+ showPreviewBtn: true,
220
+ },
221
+ {
222
+ id: 'CERFA2',
223
+ title: 'CERFA 2',
224
+ showPreviewBtn: true,
225
+ },
226
+ ],
227
+ showFilePreview: false,
228
+ },
229
+ global: {
230
+ plugins: [vuetify],
231
+ stubs: {
232
+ VDialog: {
233
+ template: '<div><slot /></div>',
234
+ },
235
+ },
236
+ },
237
+ })
238
+
239
+ const image = new File(
240
+ [Buffer.from([100, 97, 116, 97, 58, 14, 79, 8, 113, 97, 65, 43, 57, 55, 89, 69, 88, 51, 66, 101, 70, 86, 112, 121, 112, 121, 121, 121, 80, 81, 104])],
241
+ 'image.png',
242
+ { type: 'image/png' },
243
+ )
244
+
245
+ await wrapper.find('.file-item button').trigger('click')
246
+
247
+ await wrapper.find('input').trigger('drop', {
248
+ dataTransfer: {
249
+ files: [image],
250
+ },
251
+ })
252
+
253
+ await wrapper.find('.file-item__action-preview').trigger('click')
254
+
255
+ expect(wrapper.find('.sy-file-preview img').exists()).toBeTruthy()
256
+ })
257
+ })
@@ -0,0 +1,54 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`UploadWorkflow > render the upload list 1`] = `
4
+ "<div data-v-2a35ee7d="" style="width: 100%;" class="sy-upload-workflow white">
5
+ <h4 data-v-2a35ee7d="" class="text-h6 mb-2">Documents à nous transmettre</h4>
6
+ <ul data-v-1c29bdca="" data-v-2a35ee7d="" class="upload-list" style="width: 100%;">
7
+ <li data-v-55f3ab7e="" data-v-1c29bdca="" class="file-item">
8
+ <div data-v-55f3ab7e="" class="file-item__description">
9
+ <div data-v-55f3ab7e="" class="file-item__content"><span data-v-55f3ab7e="" class="file-item__icon"><!--v-if--><i data-v-55f3ab7e="" class="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z mdi v-icon notranslate v-theme--light text-primary" style="font-size: 24px; height: 24px; width: 24px;" aria-hidden="true"></i></span>
10
+ <div data-v-55f3ab7e="">
11
+ <div data-v-55f3ab7e="" class="file-item__title">Carte d'identité</div>
12
+ <div data-v-55f3ab7e="" class="file-item__name text-base"></div>
13
+ <!--v-if-->
14
+ <!--v-if-->
15
+ </div>
16
+ </div>
17
+ <div data-v-55f3ab7e="" class="file-item__actions"><button data-v-55f3ab7e="" type="button" class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-text file-item__action file-item__action-upload text-primary"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span><span class="v-btn__prepend"><i data-v-55f3ab7e="" class="M2 12H4V17H20V12H22V17C22 18.11 21.11 19 20 19H4C2.9 19 2 18.11 2 17V12M12 2L6.46 7.46L7.88 8.88L11 5.75V15H13V5.75L16.13 8.88L17.55 7.45L12 2Z mdi v-icon notranslate v-theme--light v-icon--size-default text-primary" aria-hidden="true"></i></span><span class="v-btn__content" data-no-activator=""><span data-v-55f3ab7e="">Importer</span></span>
18
+ <!---->
19
+ <!---->
20
+ </button>
21
+ <!--v-if-->
22
+ <!--v-if-->
23
+ </div>
24
+ </div>
25
+ <!--v-if-->
26
+ </li>
27
+ <li data-v-55f3ab7e="" data-v-1c29bdca="" class="file-item">
28
+ <div data-v-55f3ab7e="" class="file-item__description">
29
+ <div data-v-55f3ab7e="" class="file-item__content"><span data-v-55f3ab7e="" class="file-item__icon"><!--v-if--><i data-v-55f3ab7e="" class="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z mdi v-icon notranslate v-theme--light text-primary" style="font-size: 24px; height: 24px; width: 24px;" aria-hidden="true"></i></span>
30
+ <div data-v-55f3ab7e="">
31
+ <div data-v-55f3ab7e="" class="file-item__title">Facture de soin</div>
32
+ <div data-v-55f3ab7e="" class="file-item__name text-base"></div>
33
+ <!--v-if-->
34
+ <!--v-if-->
35
+ </div>
36
+ </div>
37
+ <div data-v-55f3ab7e="" class="file-item__actions"><button data-v-55f3ab7e="" type="button" class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-text file-item__action file-item__action-upload text-primary"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span><span class="v-btn__prepend"><i data-v-55f3ab7e="" class="M2 12H4V17H20V12H22V17C22 18.11 21.11 19 20 19H4C2.9 19 2 18.11 2 17V12M12 2L6.46 7.46L7.88 8.88L11 5.75V15H13V5.75L16.13 8.88L17.55 7.45L12 2Z mdi v-icon notranslate v-theme--light v-icon--size-default text-primary" aria-hidden="true"></i></span><span class="v-btn__content" data-no-activator=""><span data-v-55f3ab7e="">Importer</span></span>
38
+ <!---->
39
+ <!---->
40
+ </button>
41
+ <!--v-if-->
42
+ <!--v-if-->
43
+ </div>
44
+ </div>
45
+ <!--v-if-->
46
+ </li>
47
+ </ul>
48
+ <transition-stub data-v-2a35ee7d="" appear="false" persisted="false" css="true"><label data-v-3451b201="" data-v-2a35ee7d="" for="file-upload-v-0" class="sy-file-upload d-block pa-4 mt-6" style="width: 100%;"><input data-v-3451b201="" id="file-upload-v-0" type="file" accept=".pdf, .jpg, .jpeg, .png" class="sy-file-upload-input"><span data-v-5bd3b751="" data-v-3451b201="" class="sy-file-upload-placeholder"><i data-v-5bd3b751="" class="M11 20H6.5Q4.22 20 2.61 18.43 1 16.85 1 14.58 1 12.63 2.17 11.1 3.35 9.57 5.25 9.15 5.88 6.85 7.75 5.43 9.63 4 12 4 14.93 4 16.96 6.04 19 8.07 19 11 20.73 11.2 21.86 12.5 23 13.78 23 15.5 23 17.38 21.69 18.69 20.38 20 18.5 20H13V12.85L14.6 14.4L16 13L12 9L8 13L9.4 14.4L11 12.85Z mdi v-icon notranslate v-theme--light text-primary" style="font-size: 40px; height: 40px; width: 40px;" aria-hidden="true"></i><span data-v-5bd3b751="" class="mt-1 font-weight-medium text-black"><span data-v-5bd3b751="">Déposer votre fichier ici</span></span><span data-v-5bd3b751="" class="mb-2 sy-file-upload-caption">Ou</span><span data-v-5bd3b751="" class="sy-file-upload-btn bg-primary text-white elevation-2">Choisir un fichier</span><span data-v-5bd3b751="" class="mt-4 sy-file-upload-caption">Taille max. : 10 Mo. Formats acceptés : pdf, jpg, jpeg, png</span></span></label></transition-stub>
49
+ <!---->
50
+ <!---->
51
+ <!---->
52
+ <!---->
53
+ </div>"
54
+ `;
@@ -0,0 +1,21 @@
1
+ import type { FileState } from '@/components/FileList/FileList.vue'
2
+ export type { Item as FileItem } from '@/components/FileList/FileList.vue'
3
+
4
+ export type UploadItem = {
5
+ id: string
6
+ title: string
7
+ state?: FileState | string
8
+ optional?: boolean
9
+ showPreviewBtn?: boolean
10
+ }
11
+
12
+ export type SelectedFile = {
13
+ id: string
14
+ title: string
15
+ state?: FileState
16
+ progress?: number
17
+ optional?: boolean
18
+ showPreviewBtn?: boolean
19
+ fileName: string
20
+ file: File
21
+ }
@@ -0,0 +1,84 @@
1
+ import { computed, ref, type DeepReadonly, type Ref } from 'vue'
2
+ import type { FileState, Item as FileListItem } from '../FileList/FileList.vue'
3
+ import type { FileItem, SelectedFile, UploadItem } from './types'
4
+
5
+ export default function useFileList(
6
+ selectedFiles: Ref<SelectedFile[]>,
7
+ uploadList: DeepReadonly<Ref<UploadItem[]>>,
8
+ ) {
9
+ const errorSelectedFiles = ref<string[]>([])
10
+
11
+ function removeFromErrorList(fileId: string) {
12
+ const errorIndex = errorSelectedFiles.value.findIndex(item => item === fileId)
13
+ if (errorIndex !== -1) {
14
+ errorSelectedFiles.value.splice(errorIndex, 1)
15
+ }
16
+ }
17
+
18
+ function findSelectedFile(fileId: string) {
19
+ return selectedFiles.value.find(item => item.id === fileId)
20
+ }
21
+
22
+ function resetFile(fileItem: FileListItem | SelectedFile) {
23
+ const itemIndex = selectedFiles.value.findIndex(item => item.id === fileItem.id)
24
+ selectedFiles.value.splice(itemIndex, 1)
25
+ }
26
+
27
+ function replaceFile(file: File, item: SelectedFile, state: FileState = 'success') {
28
+ item.file = file
29
+ item.fileName = file.name
30
+ item.state = state
31
+ }
32
+
33
+ function addOrReplaceFile(file: File, fileId: string, state: FileState = 'success') {
34
+ const selectedFile = findSelectedFile(fileId)
35
+
36
+ if (selectedFile) {
37
+ replaceFile(file, selectedFile, state)
38
+ }
39
+ else {
40
+ const uploadItemIndex = uploadList.value.findIndex(item => item.id === fileId)
41
+
42
+ selectedFiles.value.push({
43
+ id: uploadList.value[uploadItemIndex].id,
44
+ title: uploadList.value[uploadItemIndex].title,
45
+ state: state,
46
+ optional: !!uploadList.value[uploadItemIndex].optional,
47
+ showPreviewBtn: !!uploadList.value[uploadItemIndex].showPreviewBtn,
48
+ fileName: file.name,
49
+ file,
50
+ })
51
+ removeFromErrorList(fileId)
52
+ }
53
+ }
54
+
55
+ function setItemOnError(fileId: string) {
56
+ const selectedFile = findSelectedFile(fileId)
57
+ if (selectedFile) {
58
+ resetFile(selectedFile)
59
+ }
60
+ errorSelectedFiles.value.push(fileId)
61
+ }
62
+
63
+ const filledUploadList = computed<FileItem[]>(() => {
64
+ return uploadList.value.map((uploadItem) => {
65
+ const matchingUploadedItem = findSelectedFile(uploadItem.id)
66
+ const error = errorSelectedFiles.value.includes(uploadItem.id)
67
+ const state = uploadItem.state ?? (error ? 'error' : 'initial')
68
+
69
+ return ({
70
+ ...uploadItem,
71
+ state,
72
+ ...matchingUploadedItem,
73
+ })
74
+ })
75
+ })
76
+
77
+ return {
78
+ addOrReplaceFile,
79
+ replaceFile,
80
+ resetFile,
81
+ setItemOnError,
82
+ filledUploadList,
83
+ }
84
+ }