@cnamts/synapse 0.0.8-alpha → 0.0.10-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 (156) hide show
  1. package/dist/design-system-v3.d.ts +1152 -127
  2. package/dist/design-system-v3.js +4888 -2605
  3. package/dist/design-system-v3.umd.cjs +1 -1
  4. package/dist/style.css +1 -1
  5. package/package.json +1 -1
  6. package/src/assets/settings.scss +1 -1
  7. package/src/components/ContextualMenu/Accessibilite.mdx +14 -0
  8. package/src/components/ContextualMenu/Accessibilite.stories.ts +191 -0
  9. package/src/components/ContextualMenu/AccessibiliteItems.ts +89 -0
  10. package/src/components/ContextualMenu/constants/ExpertiseLevelEnum.ts +4 -0
  11. package/src/components/Customs/SySelect/SySelect.stories.ts +7 -7
  12. package/src/components/Customs/SySelect/SySelect.vue +9 -4
  13. package/src/components/Customs/SySelect/tests/SySelect.spec.ts +2 -2
  14. package/src/components/Customs/SyTextField/SyTextField.stories.ts +187 -2
  15. package/src/components/Customs/SyTextField/SyTextField.vue +185 -16
  16. package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +2 -4
  17. package/src/components/Customs/SyTextField/tests/__snapshots__/SyTextField.spec.ts.snap +18 -16
  18. package/src/components/Customs/SyTextField/types.d.ts +2 -2
  19. package/src/components/DatePicker/Accessibilite.mdx +14 -0
  20. package/src/components/DatePicker/Accessibilite.stories.ts +191 -0
  21. package/src/components/DatePicker/AccessibiliteItems.ts +233 -0
  22. package/src/components/DatePicker/DatePicker.mdx +186 -0
  23. package/src/components/DatePicker/DatePicker.stories.ts +787 -0
  24. package/src/components/DatePicker/DatePicker.vue +574 -0
  25. package/src/components/DatePicker/DateTextInput.vue +409 -0
  26. package/src/components/DatePicker/constants/ExpertiseLevelEnum.ts +4 -0
  27. package/src/components/DatePicker/tests/DatePicker.spec.ts +266 -0
  28. package/src/components/DialogBox/DialogBox.stories.ts +1 -1
  29. package/src/components/ExternalLinks/Accessibilite.mdx +14 -0
  30. package/src/components/ExternalLinks/Accessibilite.stories.ts +191 -0
  31. package/src/components/ExternalLinks/AccessibiliteItems.ts +197 -0
  32. package/src/components/ExternalLinks/constants/ExpertiseLevelEnum.ts +4 -0
  33. package/src/components/ExternalLinks/tests/__snapshots__/ExternalLinks.spec.ts.snap +9 -9
  34. package/src/components/FileList/FileList.mdx +103 -0
  35. package/src/components/FileList/FileList.stories.ts +562 -0
  36. package/src/components/FileList/FileList.vue +78 -0
  37. package/src/components/FileList/UploadItem/UploadItem.vue +270 -0
  38. package/src/components/FileList/UploadItem/locales.ts +9 -0
  39. package/src/components/FileList/tests/FileList.spec.ts +176 -0
  40. package/src/components/FilePreview/FilePreview.mdx +82 -0
  41. package/src/components/FilePreview/FilePreview.stories.ts +242 -0
  42. package/src/components/FilePreview/FilePreview.vue +68 -0
  43. package/src/components/FilePreview/config.ts +10 -0
  44. package/src/components/FilePreview/locales.ts +4 -0
  45. package/src/components/FilePreview/tests/FilePreview.spec.ts +124 -0
  46. package/src/components/FilePreview/tests/__snapshots__/FilePreview.spec.ts.snap +11 -0
  47. package/src/components/FileUpload/FileUpload.mdx +165 -0
  48. package/src/components/FileUpload/FileUpload.stories.ts +429 -0
  49. package/src/components/FileUpload/FileUpload.vue +195 -0
  50. package/src/components/FileUpload/FileUploadContent.vue +109 -0
  51. package/src/components/FileUpload/locales.ts +10 -0
  52. package/src/components/FileUpload/tests/FileUpload.spec.ts +332 -0
  53. package/src/components/FileUpload/tests/__snapshots__/FileUpload.spec.ts.snap +7 -0
  54. package/src/components/FileUpload/useFileDrop.ts +23 -0
  55. package/src/components/FileUpload/validateFiles.ts +39 -0
  56. package/src/components/NirField/NirField.stories.ts +1 -1
  57. package/src/components/NirField/NirField.vue +2 -1
  58. package/src/components/PasswordField/Accessibilite.mdx +14 -0
  59. package/src/components/PasswordField/Accessibilite.stories.ts +191 -0
  60. package/src/components/PasswordField/AccessibiliteItems.ts +184 -0
  61. package/src/components/PasswordField/PasswordField.vue +3 -3
  62. package/src/components/PasswordField/constants/ExpertiseLevelEnum.ts +4 -0
  63. package/src/components/PeriodField/PeriodField.mdx +32 -0
  64. package/src/components/PeriodField/PeriodField.stories.ts +807 -0
  65. package/src/components/PeriodField/PeriodField.vue +355 -0
  66. package/src/components/PeriodField/tests/PeriodField.spec.ts +348 -0
  67. package/src/components/PhoneField/PhoneField.vue +44 -60
  68. package/src/components/PhoneField/tests/PhoneField.spec.ts +0 -15
  69. package/src/components/RangeField/Accessibilite.mdx +14 -0
  70. package/src/components/RangeField/Accessibilite.stories.ts +191 -0
  71. package/src/components/RangeField/AccessibiliteItems.ts +179 -0
  72. package/src/components/RangeField/RangeField.mdx +54 -0
  73. package/src/components/RangeField/RangeField.stories.ts +189 -0
  74. package/src/components/RangeField/RangeField.vue +157 -0
  75. package/src/components/RangeField/RangeSlider/RangeSlider.vue +387 -0
  76. package/src/components/RangeField/RangeSlider/Tooltip/Tooltip.vue +64 -0
  77. package/src/components/RangeField/RangeSlider/tests/__snapshots__/rangeSlider.spec.ts.snap +27 -0
  78. package/src/components/RangeField/RangeSlider/tests/rangeSlider.spec.ts +100 -0
  79. package/src/components/RangeField/RangeSlider/tests/useDoubleSlider.spec.ts +246 -0
  80. package/src/components/RangeField/RangeSlider/tests/useMouseSlide.spec.ts +204 -0
  81. package/src/components/RangeField/RangeSlider/tests/useThumb.spec.ts +22 -0
  82. package/src/components/RangeField/RangeSlider/tests/useThumbKeyboard.spec.ts +233 -0
  83. package/src/components/RangeField/RangeSlider/tests/useTooltipsNudge.spec.ts +150 -0
  84. package/src/components/RangeField/RangeSlider/tests/useTrack.spec.ts +314 -0
  85. package/src/components/RangeField/RangeSlider/tests/vAnimateClick.spec.ts +32 -0
  86. package/src/components/RangeField/RangeSlider/types.ts +15 -0
  87. package/src/components/RangeField/RangeSlider/useMouseSlide.ts +109 -0
  88. package/src/components/RangeField/RangeSlider/useRangeSlider.ts +126 -0
  89. package/src/components/RangeField/RangeSlider/useThumb.ts +18 -0
  90. package/src/components/RangeField/RangeSlider/useThumbKeyboard.ts +84 -0
  91. package/src/components/RangeField/RangeSlider/useTooltipsNudge.ts +92 -0
  92. package/src/components/RangeField/RangeSlider/useTrack.ts +116 -0
  93. package/src/components/RangeField/RangeSlider/vAnimateClick.ts +19 -0
  94. package/src/components/RangeField/config.ts +7 -0
  95. package/src/components/RangeField/constants/ExpertiseLevelEnum.ts +4 -0
  96. package/src/components/RangeField/locales.ts +4 -0
  97. package/src/components/RangeField/tests/RangeField.spec.ts +224 -0
  98. package/src/components/RangeField/tests/__snapshots__/RangeField.spec.ts.snap +379 -0
  99. package/src/components/RatingPicker/Accessibilite.mdx +14 -0
  100. package/src/components/RatingPicker/Accessibilite.stories.ts +191 -0
  101. package/src/components/RatingPicker/AccessibiliteItems.ts +208 -0
  102. package/src/components/RatingPicker/EmotionPicker/EmotionPicker.vue +205 -0
  103. package/src/components/RatingPicker/EmotionPicker/locales.ts +3 -0
  104. package/src/components/RatingPicker/EmotionPicker/tests/EmotionPicker.spec.ts +104 -0
  105. package/src/components/RatingPicker/EmotionPicker/tests/__snapshots__/EmotionPicker.spec.ts.snap +66 -0
  106. package/src/components/RatingPicker/NumberPicker/NumberPicker.vue +159 -0
  107. package/src/components/RatingPicker/NumberPicker/locales.ts +4 -0
  108. package/src/components/RatingPicker/NumberPicker/tests/NumberPicker.spec.ts +73 -0
  109. package/src/components/RatingPicker/NumberPicker/tests/__snapshots__/NumberPicker.spec.ts.snap +105 -0
  110. package/src/components/RatingPicker/Rating.ts +45 -0
  111. package/src/components/RatingPicker/RatingPicker.mdx +56 -0
  112. package/src/components/RatingPicker/RatingPicker.stories.ts +515 -0
  113. package/src/components/RatingPicker/RatingPicker.vue +122 -0
  114. package/src/components/RatingPicker/StarsPicker/StarsPicker.vue +116 -0
  115. package/src/components/RatingPicker/StarsPicker/tests/StarsPicker.spec.ts +95 -0
  116. package/src/components/RatingPicker/StarsPicker/tests/__snapshots__/StarsPicker.spec.ts.snap +36 -0
  117. package/src/components/RatingPicker/constants/ExpertiseLevelEnum.ts +4 -0
  118. package/src/components/RatingPicker/locales.ts +3 -0
  119. package/src/components/RatingPicker/tests/Rating.spec.ts +104 -0
  120. package/src/components/RatingPicker/tests/RatingPicker.spec.ts +187 -0
  121. package/src/components/RatingPicker/tests/__snapshots__/RatingPicker.spec.ts.snap +108 -0
  122. package/src/components/SearchListField/Accessibilite.mdx +14 -0
  123. package/src/components/SearchListField/Accessibilite.stories.ts +191 -0
  124. package/src/components/SearchListField/AccessibiliteItems.ts +310 -0
  125. package/src/components/SearchListField/SearchListField.mdx +74 -0
  126. package/src/components/SearchListField/SearchListField.stories.ts +126 -0
  127. package/src/components/SearchListField/SearchListField.vue +194 -0
  128. package/src/components/SearchListField/constants/ExpertiseLevelEnum.ts +4 -0
  129. package/src/components/SearchListField/locales.ts +5 -0
  130. package/src/components/SearchListField/tests/SearchListField.spec.ts +323 -0
  131. package/src/components/SearchListField/types.d.ts +4 -0
  132. package/src/components/SelectBtnField/Accessibilite.mdx +14 -0
  133. package/src/components/SelectBtnField/Accessibilite.stories.ts +191 -0
  134. package/src/components/SelectBtnField/AccessibiliteItems.ts +191 -0
  135. package/src/components/SelectBtnField/SelectBtnField.mdx +50 -0
  136. package/src/components/SelectBtnField/SelectBtnField.stories.ts +763 -0
  137. package/src/components/SelectBtnField/SelectBtnField.vue +283 -0
  138. package/src/components/SelectBtnField/config.ts +11 -0
  139. package/src/components/SelectBtnField/constants/ExpertiseLevelEnum.ts +4 -0
  140. package/src/components/SelectBtnField/tests/SelectBtnField.spec.ts +327 -0
  141. package/src/components/SelectBtnField/tests/__snapshots__/SelectBtnField.spec.ts.snap +125 -0
  142. package/src/components/SelectBtnField/types.d.ts +11 -0
  143. package/src/components/SyAlert/SyAlert.vue +11 -9
  144. package/src/components/TableToolbar/TableToolbar.mdx +130 -0
  145. package/src/components/TableToolbar/TableToolbar.stories.ts +935 -0
  146. package/src/components/TableToolbar/TableToolbar.vue +168 -0
  147. package/src/components/TableToolbar/config.ts +24 -0
  148. package/src/components/TableToolbar/locales.ts +6 -0
  149. package/src/components/TableToolbar/tests/TableToolbar.spec.ts +166 -0
  150. package/src/components/TableToolbar/tests/__snapshots__/TableToolbar.spec.ts.snap +359 -0
  151. package/src/components/index.ts +11 -1
  152. package/src/composables/rules/useFieldValidation.ts +174 -44
  153. package/src/designTokens/index.ts +3 -3
  154. package/src/stories/Fondamentaux/CustomisationEtThemes.mdx +52 -2
  155. package/src/utils/calcHumanFileSize/index.ts +12 -0
  156. package/src/utils/calcHumanFileSize/tests/calcHumanFileSize.spec.ts +21 -0
@@ -0,0 +1,242 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import { onMounted, ref } from 'vue'
3
+ import FilePreview from './FilePreview.vue'
4
+ import FileUpload from '../FileUpload/FileUpload.vue'
5
+
6
+ const meta: Meta = {
7
+ title: 'Composants/Données/FilePreview',
8
+ component: FilePreview,
9
+ argTypes: {
10
+ file: {
11
+ control: false,
12
+ table: {
13
+ type: {
14
+ summary: 'File | Blob',
15
+ },
16
+ category: 'props',
17
+ },
18
+ description: 'Fichier à afficher',
19
+ },
20
+ options: {
21
+ control: {
22
+ type: 'object',
23
+ },
24
+ table: {
25
+ type: {
26
+ summary: 'Object',
27
+ detail: `{
28
+ pdf?: Record<string, string>,
29
+ image?: Record<string, string>,
30
+ }`,
31
+ },
32
+ category: 'props',
33
+ },
34
+ description: 'Configuration des attributs pour les balises `object` et `img`. Par défaut, l\'image a une description vide.',
35
+ },
36
+ locales: {
37
+ description: 'Traductions',
38
+ control: {
39
+ type: 'object',
40
+ },
41
+ table: {
42
+ category: 'props',
43
+ type: {
44
+ summary: 'Record<string, string>',
45
+ },
46
+ defaultValue: {
47
+ summary: 'locales',
48
+ detail: `{
49
+ previewNotAvailable: 'Impossible de prévisualiser le fichier.',
50
+ previewTypeNotAvailable: 'Impossible de prévisualiser ce type de fichier.',
51
+ }`,
52
+ },
53
+ },
54
+ },
55
+ default: {
56
+ control: {
57
+ type: 'text',
58
+ },
59
+ table: {
60
+ category: 'slots',
61
+ },
62
+ description: 'Remplace le contenu par défaut affiché quand le fichier n\'est pas une image ou un pdf',
63
+ },
64
+ },
65
+ } satisfies Meta<typeof FilePreview>
66
+
67
+ export default meta
68
+
69
+ type Story = StoryObj<typeof FilePreview>
70
+
71
+ export const Default: Story = {
72
+ render: args => ({
73
+ components: { FilePreview },
74
+ template: `
75
+ <div>
76
+ <input type="file" @change="file = $event.target.files[0]" id="file-test-upload" />
77
+ <FilePreview v-bind="args" :file >
78
+ <template #default v-if="args.default">
79
+ {{ args.default }}
80
+ </template>
81
+ </FilePreview>
82
+ </div>
83
+ `,
84
+ setup: () => {
85
+ const file = ref<File | undefined>()
86
+ return { args, file }
87
+ },
88
+ }),
89
+ parameters: {
90
+ sourceCode: [
91
+ {
92
+ name: 'Template',
93
+ code: `
94
+ <div>
95
+ <input type="file" @change="file = $event.target.files[0]" />
96
+ <FilePreview :file="file" />
97
+ </div>
98
+ `,
99
+ },
100
+ {
101
+ name: 'Script',
102
+ code: `
103
+ import { ref } from 'vue'
104
+ import { FilePreview } from '@cnamts/synapse'
105
+
106
+ const file = ref<File | undefined>()`,
107
+ },
108
+ ],
109
+ },
110
+ }
111
+
112
+ export const UnsupportedFile: Story = {
113
+ render: args => ({
114
+ components: { FilePreview },
115
+ template: `
116
+ <FilePreview v-bind="args" :file="file" >
117
+ <template #default v-if="args.default">
118
+ {{ args.default }}
119
+ </template>
120
+ </FilePreview>
121
+ `,
122
+ setup: () => {
123
+ const file = ref({
124
+ name: 'document.txt',
125
+ size: 1000,
126
+ type: 'text/plain',
127
+ } as File)
128
+ return { args, file }
129
+ },
130
+ }),
131
+ parameters: {
132
+ sourceCode: [
133
+ {
134
+ name: 'Template',
135
+ code: `
136
+ <FilePreview :file="file" />
137
+ `,
138
+ },
139
+ {
140
+ name: 'Script',
141
+ code: `
142
+ import { ref } from 'vue'
143
+ import { FilePreview } from '@cnamts/synapse'
144
+
145
+ const file = ref({
146
+ name: 'document.txt',
147
+ size: 1000,
148
+ type: 'text/plain',
149
+ } as File)`,
150
+ },
151
+ ],
152
+ },
153
+ }
154
+
155
+ export const FromApi: Story = {
156
+ render: args => ({
157
+ components: { FilePreview },
158
+ template: `
159
+ <FilePreview v-bind="args" :file="file">
160
+ <template #default v-if="args.default">
161
+ {{ args.default }}
162
+ </template>
163
+ </FilePreview>
164
+ `,
165
+ setup: () => {
166
+ const file = ref<File | Blob | undefined>()
167
+
168
+ onMounted(() => {
169
+ fetch('https://picsum.photos/seed/picsum/750/350')
170
+ .then(res => res.blob())
171
+ .then(blob => file.value = blob)
172
+ })
173
+ return { args, file }
174
+ },
175
+ }),
176
+ parameters: {
177
+ sourceCode: [
178
+ {
179
+ name: 'Template',
180
+ code: `
181
+ <FilePreview :file="file" />
182
+ `,
183
+ },
184
+ {
185
+ name: 'Script',
186
+ code: `
187
+ import { onMounted, ref } from 'vue'
188
+ import { FilePreview } from '@cnamts/synapse'
189
+
190
+ const file = ref<File | Blob | undefined>()
191
+
192
+ onMounted(() => {
193
+ fetch('https://picsum.photos/seed/picsum/750/350')
194
+ .then(res => res.blob())
195
+ .then(blob => file.value = blob)
196
+ })`,
197
+ },
198
+ ],
199
+ },
200
+ }
201
+
202
+ export const WithFileUpload: Story = {
203
+ render: args => ({
204
+ components: { FilePreview, FileUpload },
205
+ template: `
206
+ <div>
207
+ <FileUpload v-model="files" class="mb-4"/>
208
+ <FilePreview v-bind="args" :file="files[0]">
209
+ <template #default v-if="args.default">
210
+ {{ args.default }}
211
+ </template>
212
+ </FilePreview>
213
+ </div>
214
+ `,
215
+ setup: () => {
216
+ const files = ref<File[]>([])
217
+
218
+ return { args, files }
219
+ },
220
+ }),
221
+ parameters: {
222
+ sourceCode: [
223
+ {
224
+ name: 'Template',
225
+ code: `
226
+ <div>
227
+ <FileUpload v-model="files" class="mb-4"/>
228
+ <FilePreview :file="files[0]"/>
229
+ </div>
230
+ `,
231
+ },
232
+ {
233
+ name: 'Script',
234
+ code: `
235
+ import { ref } from 'vue'
236
+ import { FilePreview, FileUpload } from '@cnamts/synapse'
237
+
238
+ const files = ref<File[]>([])`,
239
+ },
240
+ ],
241
+ },
242
+ }
@@ -0,0 +1,68 @@
1
+ <script setup lang="ts">
2
+ import deepmerge from 'deepmerge'
3
+ import { computed, onUnmounted, ref, watch } from 'vue'
4
+ import { config } from './config'
5
+ import { locales as defaultLocales } from './locales'
6
+
7
+ const props = withDefaults(defineProps<{
8
+ file?: File | Blob
9
+ options?: {
10
+ pdf?: Record<string, string>
11
+ image?: Record<string, string>
12
+ }
13
+ locales?: typeof defaultLocales
14
+ }>(), {
15
+ file: undefined,
16
+ options: undefined,
17
+ locales: () => defaultLocales,
18
+ })
19
+
20
+ const fileURL = ref('')
21
+ const isPdf = computed(() => props.file?.type === 'application/pdf')
22
+ const isImage = computed(() => props.file ? /^image\//.test(props.file.type) : false)
23
+ const filePreviewOptions = computed(() => deepmerge(config, props.options || {}))
24
+
25
+ const getFileURL = () => {
26
+ if (!props.file || !(isPdf.value || isImage.value)) return
27
+ fileURL.value = URL.createObjectURL(props.file)
28
+ }
29
+
30
+ const revokeFileURL = () => {
31
+ URL.revokeObjectURL(fileURL.value)
32
+ }
33
+
34
+ watch(() => props.file, getFileURL, { immediate: true })
35
+
36
+ onUnmounted(revokeFileURL)
37
+ </script>
38
+
39
+ <template>
40
+ <div
41
+ v-if="file"
42
+ class="sy-file-preview"
43
+ >
44
+ <object
45
+ v-if="isPdf"
46
+ :data="fileURL"
47
+ v-bind="filePreviewOptions.pdf"
48
+ type="application/pdf"
49
+ @load="revokeFileURL"
50
+ >
51
+ <p class="mb-0">{{ locales.previewNotAvailable }}</p>
52
+ </object>
53
+
54
+ <img
55
+ v-else-if="isImage"
56
+ :src="fileURL"
57
+ :alt="filePreviewOptions.image.alt || ''"
58
+ v-bind="filePreviewOptions.image"
59
+ @load="revokeFileURL"
60
+ >
61
+
62
+ <slot v-else>
63
+ <p class="mb-0">
64
+ {{ locales.previewTypeNotAvailable }}
65
+ </p>
66
+ </slot>
67
+ </div>
68
+ </template>
@@ -0,0 +1,10 @@
1
+ export const config = {
2
+ pdf: {
3
+ height: '556px',
4
+ width: '100%',
5
+ },
6
+ image: {
7
+ // https://github.com/vuejs/core/issues/2801
8
+ style: 'width: 100%;',
9
+ },
10
+ }
@@ -0,0 +1,4 @@
1
+ export const locales = {
2
+ previewNotAvailable: 'Impossible de prévisualiser le fichier.',
3
+ previewTypeNotAvailable: 'Impossible de prévisualiser ce type de fichier.',
4
+ }
@@ -0,0 +1,124 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import { VueWrapper, mount } from '@vue/test-utils'
3
+
4
+ import FilePreview from '../FilePreview.vue'
5
+ import { vuetify } from '@tests/unit/setup'
6
+ import { locales } from '../locales'
7
+
8
+ const testFileImg = {
9
+ name: 'avatar.png',
10
+ size: 1000,
11
+ type: 'image/png',
12
+ } as File
13
+
14
+ const testFilePdf = {
15
+ name: 'document.pdf',
16
+ size: 1000,
17
+ type: 'application/pdf',
18
+ } as File
19
+
20
+ describe('FilePreview', async () => {
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ let wrapper: VueWrapper<any>
23
+ global.URL.createObjectURL = vi.fn()
24
+
25
+ beforeEach(() => {
26
+ wrapper = mount(FilePreview, {
27
+ global: {
28
+ plugins: [vuetify],
29
+ },
30
+ })
31
+ })
32
+
33
+ it('renders correctly with an image', async () => {
34
+ await wrapper.setProps({ file: testFileImg })
35
+
36
+ expect(wrapper.html()).toMatchSnapshot()
37
+
38
+ wrapper.unmount()
39
+ })
40
+
41
+ it('renders correctly with a pdf', async () => {
42
+ await wrapper.setProps({ file: testFilePdf })
43
+
44
+ expect(wrapper.html()).toMatchSnapshot()
45
+
46
+ wrapper.unmount()
47
+ })
48
+
49
+ it('render correctly with a file that is not an image or pdf', async () => {
50
+ await wrapper.setProps({
51
+ file: {
52
+ name: 'document.txt',
53
+ size: 1000,
54
+ type: 'text/plain',
55
+ } as File,
56
+ })
57
+
58
+ expect(wrapper.text()).toContain(locales.previewTypeNotAvailable)
59
+
60
+ wrapper.unmount()
61
+ })
62
+
63
+ it('updates the preview when the file changes', async () => {
64
+ await wrapper.setProps({ file: testFileImg })
65
+
66
+ expect(wrapper.find('img').exists()).toBe(true)
67
+
68
+ await wrapper.setProps({
69
+ file: testFilePdf,
70
+ })
71
+
72
+ expect(wrapper.find('img').exists()).toBe(false)
73
+ expect(wrapper.find('object').exists()).toBe(true)
74
+
75
+ await wrapper.setProps({
76
+ file: null,
77
+ })
78
+
79
+ expect(wrapper.find('img').exists()).toBe(false)
80
+ expect(wrapper.find('object').exists()).toBe(false)
81
+ expect(wrapper.text()).toBe('')
82
+
83
+ wrapper.unmount()
84
+ })
85
+
86
+ it('with options', async () => {
87
+ await wrapper.setProps({
88
+ file: testFileImg,
89
+ options: {
90
+ image: {
91
+ alt: 'Photo de paysage montagneux.',
92
+ },
93
+ },
94
+ })
95
+
96
+ expect(wrapper.html()).toMatchSnapshot()
97
+
98
+ wrapper.unmount()
99
+ })
100
+
101
+ it('show an error when the type is not supported', async () => {
102
+ await wrapper.setProps({
103
+ file: {
104
+ name: 'document.txt',
105
+ size: 1000,
106
+ type: 'text/plain',
107
+ } as File,
108
+ })
109
+
110
+ expect(wrapper.text()).toContain(locales.previewTypeNotAvailable)
111
+
112
+ wrapper.unmount()
113
+ })
114
+
115
+ it('show nothing when the file is null', async () => {
116
+ await wrapper.setProps({
117
+ file: null,
118
+ })
119
+
120
+ expect(wrapper.html()).toMatchInlineSnapshot(`"<!--v-if-->"`)
121
+
122
+ wrapper.unmount()
123
+ })
124
+ })
@@ -0,0 +1,11 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`FilePreview > renders correctly with a pdf 1`] = `
4
+ "<div class="sy-file-preview"><object height="556px" width="100%" type="application/pdf">
5
+ <p class="mb-0">Impossible de prévisualiser le fichier.</p>
6
+ </object></div>"
7
+ `;
8
+
9
+ exports[`FilePreview > renders correctly with an image 1`] = `"<div class="sy-file-preview"><img alt="" style="width: 100%;"></div>"`;
10
+
11
+ exports[`FilePreview > with options 1`] = `"<div class="sy-file-preview"><img alt="Photo de paysage montagneux." style="width: 100%;"></div>"`;
@@ -0,0 +1,165 @@
1
+ import {Controls, Canvas, Meta, Source} from '@storybook/blocks';
2
+
3
+ import * as FileUploadStories from './FileUpload.stories.ts';
4
+
5
+ <Meta of={FileUploadStories} />
6
+
7
+ # FileUpload
8
+
9
+ Le composant `FileUpload` est un champ d'upload de fichier par glisser-déposer ou par sélection de fichier.
10
+
11
+ <Canvas of={FileUploadStories.Default} />
12
+
13
+ # API
14
+
15
+ <Controls of={FileUploadStories.Default} />
16
+
17
+ ## Gérer l'envoi des fichiers
18
+
19
+ Vous pouvez envoyer les fichiers sur un serveur en un objet FormData.
20
+
21
+ <Source dark language="typescript" code={`
22
+ <script setup lang="ts">
23
+ import { FileUpload } from '@cnamts/synapse'
24
+ import { ref } from 'vue'
25
+
26
+ const files = ref<File[]>([])
27
+
28
+ function upload() {
29
+ const formData = new FormData()
30
+ files.value.forEach((file: File) => {
31
+ formData.append(file.name, file)
32
+ })
33
+
34
+ fetch('http://localhost:3000/upload', {
35
+ method: 'POST',
36
+ body: formData,
37
+ })
38
+ }
39
+
40
+ </script>
41
+
42
+ <template>
43
+ <main class="pa-12">
44
+ {{ files?.map(file => file.name).join(', ') }}
45
+
46
+ <br>
47
+ <FileUpload
48
+ v-model="files"
49
+ :multiple="true"
50
+ @error="console.log"
51
+ />
52
+
53
+ <VBtn
54
+ @click="upload"
55
+ >
56
+ submit
57
+ </VBtn>
58
+ </main>
59
+ </template>
60
+ `} />
61
+
62
+ Même exemple avec axios :
63
+
64
+ <Source dark language="typescript" code={`
65
+ <script setup lang="ts">
66
+ import { FileUpload } from '@cnamts/synapse'
67
+ import { ref } from 'vue'
68
+ import axios from 'axios'
69
+
70
+ const files = ref<File[]>([])
71
+
72
+ function upload() {
73
+ const formData = new FormData()
74
+ files.value.forEach((file: File) => {
75
+ formData.append(file.name, file)
76
+ })
77
+
78
+ axios.post('http://localhost:3000/upload', formData, {
79
+ headers: {
80
+ 'Content-Type': 'multipart/form-data',
81
+ },
82
+ })
83
+ }
84
+
85
+ </script>
86
+
87
+ <template>
88
+ <main class="pa-12">
89
+ {{ files?.map(file => file.name).join(', ') }}
90
+
91
+ <br>
92
+ <FileUpload
93
+ v-model="files"
94
+ :multiple="true"
95
+ @error="console.log"
96
+ />
97
+
98
+ <VBtn
99
+ @click="upload"
100
+ >
101
+ submit
102
+ </VBtn>
103
+
104
+ </main>
105
+ </template>
106
+ `} />
107
+
108
+ ## Coté backend
109
+
110
+ Voici un exemple de code pour gérer l'upload de fichier en Node.js avec Express,
111
+ ce code est destiné à des fins de tests uniquement.
112
+
113
+ <Source dark language="javascript" code={`
114
+ import express from "express";
115
+ import fileUpload from "express-fileupload";
116
+ import cors from "cors";
117
+ import { join } from "path";
118
+ import { existsSync, mkdirSync } from "fs";
119
+
120
+ const app = express();
121
+
122
+ app.use(fileUpload());
123
+ app.use(cors({ origin: true, credentials: true }));
124
+
125
+ const uploadDir = join(import.meta.dirname, "uploads");
126
+
127
+ if (!existsSync(uploadDir)) {
128
+ mkdirSync(uploadDir);
129
+ }
130
+
131
+ app.post("/upload", async function (req, res) {
132
+ if (!req.files || Object.keys(req.files).length === 0) {
133
+ return res.status(400).send("No files were uploaded.");
134
+ }
135
+
136
+ let errors = [];
137
+
138
+ const uploadPromises = Object.keys(req.files).map(async (fileKey) => {
139
+ const file = req.files[fileKey];
140
+ const filePath = join(uploadDir, file.name);
141
+
142
+ try {
143
+ await file.mv(filePath);
144
+ } catch (err) {
145
+ errors.push(err);
146
+ }
147
+ });
148
+
149
+ try {
150
+ await Promise.all(uploadPromises);
151
+
152
+ if (errors.length > 0) {
153
+ return res.status(500).send(errors.join(", "));
154
+ }
155
+ res.send("Files uploaded!");
156
+ } catch (err) {
157
+ res.status(500).send("An error occurred during file upload.");
158
+ }
159
+ });
160
+
161
+ app.listen(3000, function () {
162
+ console.log("App is listening on port 3000");
163
+ });
164
+
165
+ `} />