@carbon/vue 3.0.6-alpha.0 → 3.0.8-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +7 -0
  2. package/dist/carbon-vue-3.common.js +16111 -9105
  3. package/dist/carbon-vue-3.common.js.map +1 -1
  4. package/dist/carbon-vue-3.umd.js +16111 -9105
  5. package/dist/carbon-vue-3.umd.js.map +1 -1
  6. package/dist/carbon-vue-3.umd.min.js +4 -4
  7. package/dist/carbon-vue-3.umd.min.js.map +1 -1
  8. package/package.json +9 -6
  9. package/src/components/CvAccordion/CvAccordion.stories.js +1 -0
  10. package/src/components/CvButton/CvButton.stories.js +3 -3
  11. package/src/components/CvButton/CvButtonSkeleton.stories.js +79 -0
  12. package/src/components/CvButton/CvButtonSkeleton.vue +19 -0
  13. package/src/components/CvButton/index.js +8 -1
  14. package/src/components/CvCheckbox/CvCheckbox.stories.js +18 -7
  15. package/src/components/CvCheckbox/CvCheckbox.vue +19 -3
  16. package/src/components/CvCheckbox/CvCheckboxSkeleton.vue +16 -0
  17. package/src/components/CvCheckbox/__tests__/__snapshots__/CvCheckboxSkeleton.spec.js.snap +3 -0
  18. package/src/components/CvCheckbox/index.js +2 -1
  19. package/src/components/CvDatePicker/CvDatePicker.vue +1 -1
  20. package/src/components/CvDropdown/CvDropdown.stories.mdx +407 -0
  21. package/src/components/CvDropdown/CvDropdown.vue +416 -0
  22. package/src/components/CvDropdown/CvDropdownItem.vue +86 -0
  23. package/src/components/CvDropdown/CvDropdownSkeleton.vue +17 -0
  24. package/src/components/CvDropdown/index.js +5 -0
  25. package/src/components/CvFileUploader/CvFileUploader.stories.mdx +333 -0
  26. package/src/components/CvFileUploader/CvFileUploader.vue +286 -0
  27. package/src/components/CvFileUploader/CvFileUploaderItem.vue +92 -0
  28. package/src/components/CvFileUploader/CvFileUploaderSkeleton.vue +16 -0
  29. package/src/components/CvFileUploader/const.js +10 -0
  30. package/src/components/CvFileUploader/index.js +5 -0
  31. package/src/components/CvForm/CvFormGroup.vue +1 -1
  32. package/src/components/CvInlineLoading/CvInlineLoading.stories.mdx +87 -0
  33. package/src/components/CvInlineLoading/CvInlineLoading.vue +144 -0
  34. package/src/components/CvInlineLoading/consts.js +9 -0
  35. package/src/components/CvInlineLoading/index.js +4 -0
  36. package/src/components/CvModal/CvModal.stories.mdx +4 -2
  37. package/src/components/CvModal/CvModal.vue +3 -5
  38. package/src/components/CvMultiSelect/CvMultiSelect.stories.mdx +333 -0
  39. package/src/components/CvMultiSelect/CvMultiSelect.vue +705 -0
  40. package/src/components/CvMultiSelect/consts.js +16 -0
  41. package/src/components/CvMultiSelect/index.js +3 -0
  42. package/src/components/CvNumberInput/CvNumberInput.stories.js +265 -0
  43. package/src/components/CvNumberInput/CvNumberInput.vue +227 -0
  44. package/src/components/CvNumberInput/CvNumberInputSkeleton.stories.js +30 -0
  45. package/src/components/CvNumberInput/CvNumberInputSkeleton.vue +10 -0
  46. package/src/components/CvNumberInput/StorybookGroupConst.js +1 -0
  47. package/src/components/CvNumberInput/__tests__/__snapshots__/CvNumberInput.spec.js.snap +21 -0
  48. package/src/components/CvNumberInput/index.js +5 -0
  49. package/src/components/CvProgress/CvProgress.stories.mdx +202 -0
  50. package/src/components/CvProgress/CvProgress.vue +78 -0
  51. package/src/components/CvProgress/CvProgressStep.vue +124 -0
  52. package/src/components/CvProgress/index.js +4 -0
  53. package/src/components/CvRadioButton/CvRadioButton.stories.js +166 -0
  54. package/src/components/CvRadioButton/CvRadioButton.vue +57 -0
  55. package/src/components/CvRadioButton/CvRadioGroup.vue +27 -0
  56. package/src/components/CvRadioButton/__tests__/__snapshots__/CvRadioButton.spec.js.snap +27 -0
  57. package/src/components/CvRadioButton/index.js +6 -0
  58. package/src/components/CvSearch/CvSearch.stories.mdx +168 -0
  59. package/src/components/CvSearch/CvSearch.vue +250 -0
  60. package/src/components/CvSearch/index.js +3 -0
  61. package/src/components/CvSelect/CvSelect.vue +27 -6
  62. package/src/components/CvSlider/CvSlider.stories.js +166 -0
  63. package/src/components/CvSlider/CvSlider.vue +324 -0
  64. package/src/components/CvSlider/CvSliderSkeleton.stories.js +26 -0
  65. package/src/components/CvSlider/CvSliderSkeleton.vue +18 -0
  66. package/src/components/CvSlider/__tests__/__snapshots__/CvSlider.spec.js.snap +13 -0
  67. package/src/components/CvSlider/__tests__/__snapshots__/CvSliderSkeleton.spec.js.snap +13 -0
  68. package/src/components/CvSlider/index.js +5 -0
  69. package/src/components/CvSlider/sbSliderPrefix.js +2 -0
  70. package/src/components/CvTabs/CvTab.vue +77 -0
  71. package/src/components/CvTabs/CvTabs.stories.mdx +175 -0
  72. package/src/components/CvTabs/CvTabs.vue +406 -0
  73. package/src/components/CvTabs/CvTabsSkeleton.vue +24 -0
  74. package/src/components/CvTabs/_SbContainer.vue +22 -0
  75. package/src/components/CvTabs/index.js +5 -0
  76. package/src/components/CvTextArea/CvTextArea.stories.js +2 -2
  77. package/src/components/CvTextArea/CvTextAreaSkeleton.stories.js +29 -0
  78. package/src/components/CvTextArea/CvTextAreaSkeleton.vue +10 -0
  79. package/src/components/CvTextArea/StorybookGroupConst.js +1 -0
  80. package/src/components/CvTextArea/__tests__/__snapshots__/CvTextAreaSkeleton.spec.js.snap +7 -0
  81. package/src/components/CvTextArea/index.js +2 -1
  82. package/src/components/CvTimePicker/CvTimePicker.stories.mdx +325 -0
  83. package/src/components/CvTimePicker/CvTimePicker.vue +217 -0
  84. package/src/components/CvTimePicker/index.js +21 -0
  85. package/src/components/CvToggle/CvToggle-Skeleton.vue +44 -0
  86. package/src/components/CvToggle/CvToggle.stories.js +249 -0
  87. package/src/components/CvToggle/CvToggle.vue +87 -0
  88. package/src/components/CvToggle/__tests__/__snapshots__/CvToggle.spec.js.snap +7 -0
  89. package/src/components/CvToggle/index.js +4 -0
  90. package/src/components/CvTooltip/CvDefinitionTooltip.stories.js +80 -0
  91. package/src/components/CvTooltip/CvDefinitionTooltip.vue +61 -0
  92. package/src/components/CvTooltip/CvInteractiveTooltip.stories.js +90 -0
  93. package/src/components/CvTooltip/CvInteractiveTooltip.vue +267 -0
  94. package/src/components/CvTooltip/CvTooltip.stories.js +128 -0
  95. package/src/components/CvTooltip/CvTooltip.vue +44 -0
  96. package/src/components/CvTooltip/__tests__/__snapshots__/CvTooltip.spec.js.snap +525 -0
  97. package/src/components/CvTooltip/consts.js +15 -0
  98. package/src/components/CvTooltip/index.js +6 -0
  99. package/src/components/CvTooltip/sbTooltipPrefix.js +2 -0
  100. package/src/components/CvUIShell/CvSideNav.vue +10 -1
  101. package/src/components/CvUIShell/CvUIShell.stories.mdx +116 -50
  102. package/src/global/storybook-utils/story-source-code.js +1 -0
  103. package/src/index.d.ts +6 -0
  104. package/src/index.js +11 -0
  105. package/src/use/cvCheck.js +3 -0
  106. package/src/use/cvInput.js +34 -0
  107. package/src/use/index.js +2 -0
  108. package/src/use/useMethods.js +18 -0
  109. package/src/use/useRadio.js +25 -0
@@ -0,0 +1,333 @@
1
+ import { Canvas, Meta, Story } from '@storybook/addon-docs';
2
+ import { sbCompPrefix } from '../../global/storybook-utils';
3
+ import { CvFileUploader, CvFileUploaderSkeleton } from '.';
4
+ import { KINDS } from './const';
5
+ import { action } from '@storybook/addon-actions';
6
+ import { nextTick, ref } from 'vue';
7
+ import { Add16 } from '@carbon/icons-vue';
8
+ import { buttonKinds, buttonSizes } from '../CvButton/consts';
9
+
10
+ <Meta title={`${sbCompPrefix}/CvFileUploader`} component={CvFileUploader} />
11
+
12
+ export const Template = args => ({
13
+ components: {
14
+ CvFileUploader,
15
+ CvFileUploaderSkeleton,
16
+ Add16
17
+ },
18
+ setup() {
19
+ return {
20
+ slotContent: args.slot,
21
+ args: {
22
+ ...args,
23
+ template: undefined,
24
+ },
25
+ };
26
+ },
27
+ template: args.template,
28
+ });
29
+ const defaultTemplate = `
30
+ <div style="display: inline-flex; align-items: flex-start;">
31
+ <cv-file-uploader v-bind="args" />
32
+ </div>
33
+ `;
34
+ const slotTemplate = `
35
+ <div style="display: inline-flex; align-items: flex-start;">
36
+ <cv-file-uploader v-bind="args">
37
+ <template v-slot:drop-target>
38
+ <Add16 />
39
+ <span>File Drop</span>
40
+ <Add16 />
41
+ </template>
42
+ </cv-file-uploader>
43
+ </div>
44
+ `;
45
+ const skeletonTemplate = `
46
+ <div style="display: inline-flex; align-items: flex-start;">
47
+ <cv-file-uploader-skeleton />
48
+ </div>
49
+ `;
50
+
51
+ export const args = {
52
+ // attributes
53
+ multiple: {
54
+ type: 'boolean',
55
+ table: {
56
+ type: { summary: 'boolean' },
57
+ category: 'attributes',
58
+ },
59
+ description: 'standard input attribute for file type, allow multiple files to be added in a single prompt',
60
+ },
61
+ // props
62
+ accept: {
63
+ type: 'string',
64
+ table: {
65
+ type: { summary: 'string' },
66
+ category: 'props',
67
+ },
68
+ description: 'standard input attribute for file type',
69
+ },
70
+ buttonKind: {
71
+ control: 'select',
72
+ options: buttonKinds,
73
+ defaultValue: 'primary',
74
+ table: {
75
+ type: { summary: 'string' },
76
+ category: 'props',
77
+ defaultValue: { summary: 'primary' },
78
+ },
79
+ description: 'Button kind',
80
+ },
81
+ buttonLabel: {
82
+ type: 'string',
83
+ deprecated: true,
84
+ table: {
85
+ type: { summary: 'string' },
86
+ category: 'props',
87
+ },
88
+ description: '`button-label` prop deprecated in favour of drop-target-label',
89
+ },
90
+ buttonSize: {
91
+ control: 'select',
92
+ options: buttonSizes,
93
+ defaultValue: '',
94
+ table: {
95
+ type: { summary: 'string' },
96
+ category: 'props',
97
+ defaultValue: { summary: '' },
98
+ },
99
+ description: 'Button size',
100
+ },
101
+ clearOnReselect: {
102
+ type: 'boolean',
103
+ table: {
104
+ type: { summary: 'boolean' },
105
+ category: 'props',
106
+ },
107
+ description: 'Reset file list when files are added',
108
+ },
109
+ dropTargetLabel: {
110
+ type: 'string',
111
+ table: {
112
+ type: { summary: 'string' },
113
+ category: 'props',
114
+ },
115
+ description: "Drag & drop zone label. Also used as button label for button kind file uploader",
116
+ },
117
+ helperText: {
118
+ type: 'string',
119
+ table: {
120
+ type: { summary: 'string' },
121
+ category: 'props',
122
+ },
123
+ description: 'Helper text positioned below label',
124
+ },
125
+ kind: {
126
+ type: 'string',
127
+ control: 'select',
128
+ defaultValue: KINDS.DRAG_TARGET,
129
+ options: [KINDS.DRAG_TARGET, KINDS.BUTTON],
130
+ table: {
131
+ type: { summary: `${KINDS.DRAG_TARGET} | ${KINDS.BUTTON}` },
132
+ category: 'props',
133
+ defaultValue: { summary: KINDS.DRAG_TARGET },
134
+ },
135
+ description: 'Kind of file uploader to present',
136
+ },
137
+ label: {
138
+ table: {
139
+ type: { summary: 'text' },
140
+ category: 'props',
141
+ },
142
+ description: 'File input label',
143
+ },
144
+ removable: {
145
+ type: 'boolean',
146
+ table: {
147
+ type: { summary: 'boolean' },
148
+ category: 'props',
149
+ },
150
+ description: 'Enable file items remove button',
151
+ },
152
+ removeAriaLabel: {
153
+ type: 'string',
154
+ defaultValue: 'Remove selected file',
155
+ table: {
156
+ type: { summary: 'string' },
157
+ category: 'props',
158
+ defaultValue: { summary: 'Remove selected file' },
159
+ },
160
+ description: 'Aria label for item remove button',
161
+ },
162
+ // exposed methods
163
+ clear: {
164
+ control: 'none',
165
+ type: 'function',
166
+ table: {
167
+ type: { summary: '() => void' },
168
+ category: 'exposed methods',
169
+ },
170
+ description: 'Clear file list',
171
+ },
172
+ remove: {
173
+ control: 'none',
174
+ type: 'function',
175
+ table: {
176
+ type: { summary: '(index: number) => void' },
177
+ category: 'exposed methods',
178
+ },
179
+ description: 'Removes an individual file',
180
+ },
181
+ setState: {
182
+ control: 'none',
183
+ type: 'function',
184
+ table: {
185
+ type: { summary: '(index: number, state: string) => void' },
186
+ category: 'exposed methods',
187
+ },
188
+ description: 'Update the "state" field of a file. State must be `""`, `complete` or `loading`',
189
+ },
190
+ setInvalidMessage: {
191
+ control: 'none',
192
+ type: 'function',
193
+ table: {
194
+ type: { summary: '(index: number, invalidMessage: string) => void' },
195
+ category: 'exposed methods',
196
+ },
197
+ description: 'Update the "invalidMessage" field of a file.',
198
+ },
199
+ // slots
200
+ 'drop-target': {
201
+ control: 'none',
202
+ type: 'string',
203
+ table: {
204
+ type: { summary: 'string | HTML | Component' },
205
+ category: 'slots',
206
+ },
207
+ description: 'Overrides the drop-target-label with slotted content',
208
+ },
209
+ };
210
+
211
+ # CvFileUploader
212
+
213
+ Migration notes:
214
+
215
+ - The `v-model` is different in Vue 3 than Vue 2. You can still specify the `v-model="something"` for two way data bind,
216
+ but the value property is now `modelValue` instead of `files` and event linked to v-model is `update:modelValue`.
217
+
218
+ <Canvas>
219
+ <Story
220
+ name="Default"
221
+ parameters={{
222
+ controls: {
223
+ exclude: [
224
+ 'template',
225
+ 'props',
226
+ 'modelValue',
227
+ 'update:modelValue',
228
+ ],
229
+ },
230
+ docs: { source: { code: defaultTemplate } },
231
+ }}
232
+ args={{
233
+ template: defaultTemplate,
234
+ label: 'Choose files to upload',
235
+ helperText: 'Select the files you want to upload',
236
+ removable: true,
237
+ }}
238
+ argTypes={args}
239
+ >
240
+ {Template.bind({})}
241
+ </Story>
242
+ </Canvas>
243
+
244
+ # CvFileUploader Button
245
+
246
+ <Canvas>
247
+ <Story
248
+ name="FilerUploader Button"
249
+ parameters={{
250
+ controls: {
251
+ exclude: [
252
+ 'template',
253
+ 'props',
254
+ 'modelValue',
255
+ 'update:modelValue',
256
+ ],
257
+ },
258
+ docs: { source: { code: defaultTemplate } },
259
+ }}
260
+ args={{
261
+ template: defaultTemplate,
262
+ label: 'Choose files to upload',
263
+ helperText: 'Select the files you want to upload',
264
+ kind: KINDS.BUTTON,
265
+ removable: true,
266
+ }}
267
+ argTypes={{
268
+ ...args,
269
+ kind: {
270
+ ...args.kind,
271
+ control: 'none',
272
+ },
273
+ }}
274
+ >
275
+ {Template.bind({})}
276
+ </Story>
277
+ </Canvas>
278
+
279
+ # CvFileUploader Slotted
280
+
281
+ <Canvas>
282
+ <Story
283
+ name="Slot"
284
+ parameters={{
285
+ controls: {
286
+ exclude: [
287
+ 'template',
288
+ 'props',
289
+ 'modelValue',
290
+ 'update:modelValue',
291
+ ],
292
+ },
293
+ docs: { source: { code: slotTemplate } },
294
+ }}
295
+ args={{
296
+ template: slotTemplate,
297
+ label: 'Choose files to upload',
298
+ helperText: 'Select the files you want to upload',
299
+ removable: true,
300
+ }}
301
+ argTypes={{
302
+ ...args,
303
+ kind: {
304
+ ...args.kind,
305
+ control: 'none',
306
+ },
307
+ }}
308
+ >
309
+ {Template.bind({})}
310
+ </Story>
311
+ </Canvas>
312
+
313
+ # CvFileUploaderSkeleton
314
+
315
+ <Canvas>
316
+ <Story
317
+ name="Skeleton"
318
+ parameters={{
319
+ controls: {
320
+ exclude: [
321
+ 'template',
322
+ ],
323
+ },
324
+ docs: { source: { code: skeletonTemplate } },
325
+ }}
326
+ args={{
327
+ template: skeletonTemplate,
328
+ }}
329
+ argTypes={{}}
330
+ >
331
+ {Template.bind({})}
332
+ </Story>
333
+ </Canvas>
@@ -0,0 +1,286 @@
1
+ <template>
2
+ <cv-form-item class="cv-file-uploader">
3
+ <p :class="`${carbonPrefix}--file--label`">{{ label }}</p>
4
+ <p :class="`${carbonPrefix}--label-description`">{{ helperText }}</p>
5
+ <div v-if="kind === 'button'" :class="`${carbonPrefix}--file`" data-file>
6
+ <label
7
+ :for="cvId"
8
+ :class="buttonClasses"
9
+ tabindex="0"
10
+ @keydown.enter.prevent="onKeyHit"
11
+ @keydown.space.prevent="onKeyHit"
12
+ >
13
+ <slot name="drop-target">{{ internalDropTargetLabel }}</slot>
14
+ </label>
15
+ <input
16
+ :id="cvId"
17
+ ref="fileInput"
18
+ :class="`${carbonPrefix}--file-input`"
19
+ :accept="accept"
20
+ type="file"
21
+ v-bind="$attrs"
22
+ data-file-uploader
23
+ data-target="[data-file-container]"
24
+ @change="onChange"
25
+ />
26
+ </div>
27
+ <div v-else :class="`${carbonPrefix}--file`" data-file>
28
+ <label :for="cvId">
29
+ <div
30
+ role="button"
31
+ tabindex="0"
32
+ data-file-drop-container
33
+ :class="[
34
+ `${carbonPrefix}--file-browse-btn`,
35
+ `${carbonPrefix}--file__drop-container`,
36
+ {
37
+ [`${carbonPrefix}--file__drop-container--drag-over`]: allowDrop,
38
+ },
39
+ ]"
40
+ @dragover="onDragEvent"
41
+ @dragleave="onDragEvent"
42
+ @drop="onDragEvent"
43
+ @keydown.enter.prevent="onKeyHit"
44
+ @keydown.space.prevent="onKeyHit"
45
+ >
46
+ <slot name="drop-target">{{ internalDropTargetLabel }}</slot>
47
+ </div>
48
+ <input
49
+ :id="cvId"
50
+ ref="fileInput"
51
+ :class="`${carbonPrefix}--file-input`"
52
+ :accept="accept"
53
+ type="file"
54
+ v-bind="$attrs"
55
+ tabindex="-1"
56
+ data-file-uploader
57
+ data-target="[data-file-container]"
58
+ @change="onChange"
59
+ />
60
+ </label>
61
+ </div>
62
+ <div
63
+ :class="`${carbonPrefix}--file ${carbonPrefix}--file-container`"
64
+ data-file-container
65
+ >
66
+ <cv-file-uploader-item
67
+ v-for="(item, index) in internalFiles"
68
+ :key="item.file.name"
69
+ :item="item"
70
+ :removable="removable"
71
+ :removeAriaLabel="removeAriaLabel"
72
+ @remove="onItemRemove(index)"
73
+ />
74
+ </div>
75
+ </cv-form-item>
76
+ </template>
77
+
78
+ <script setup>
79
+ import { ref, computed, onMounted, watch } from 'vue';
80
+ import CvFileUploaderItem from './CvFileUploaderItem.vue';
81
+ import { carbonPrefix } from '../../global/settings';
82
+ import { CvFormItem } from '../CvForm';
83
+ import { useCvId, props as cvIdProps } from '../../use/cvId';
84
+ import { KINDS, STATES } from './const';
85
+ import {
86
+ props as commonCvButtonProps,
87
+ useCvButtonCommon,
88
+ } from '../CvButton/CvButtonCommon';
89
+
90
+ const emit = defineEmits(['update:modelValue']);
91
+
92
+ const props = defineProps({
93
+ accept: String,
94
+ buttonKind: commonCvButtonProps.kind,
95
+ buttonLabel: {
96
+ type: String,
97
+ default: undefined,
98
+ deprecated: true,
99
+ validator: val => {
100
+ if (val !== undefined && process.env.NODE_ENV === 'development') {
101
+ console.warn(
102
+ 'CvFileUploader: button-label prop deprecated in favour of drop-target-label'
103
+ );
104
+ }
105
+ return true;
106
+ },
107
+ },
108
+ buttonSize: commonCvButtonProps.size,
109
+ clearOnReselect: Boolean,
110
+ dropTargetLabel: String,
111
+ helperText: String,
112
+ kind: {
113
+ type: String,
114
+ default: KINDS.DRAG_TARGET,
115
+ validator: val => {
116
+ const validValues = Object.values(KINDS);
117
+ if (!validValues.includes(val)) {
118
+ console.warn(
119
+ `CvFileUploader: valid values for 'kind' are ${validValues}`
120
+ );
121
+ }
122
+ return true;
123
+ },
124
+ },
125
+ label: String,
126
+ modelValue: Array,
127
+ removable: Boolean,
128
+ removeAriaLabel: String,
129
+ ...cvIdProps,
130
+ });
131
+
132
+ // DOM Elements
133
+ const fileInput = ref(null);
134
+
135
+ // Data
136
+ const allowDrop = ref(false);
137
+ const internalFiles = ref([]);
138
+
139
+ // Computed
140
+ const { buttonClasses } = useCvButtonCommon(props.buttonKind, props.buttonSize);
141
+ const cvId = useCvId(props);
142
+ const internalDropTargetLabel = computed(() => {
143
+ return (
144
+ props.dropTargetLabel ||
145
+ props.buttonLabel ||
146
+ 'Drag and drop files here or upload'
147
+ );
148
+ });
149
+ const isFileTypeAllowed = computed(() => {
150
+ const acceptAny = !props.accept;
151
+ const allowedTypes = props.accept
152
+ ? props.accept.split(',').map(x => x.trim())
153
+ : [];
154
+ if (allowedTypes.includes('.jpg')) {
155
+ allowedTypes.push('.jpeg');
156
+ }
157
+ return file => {
158
+ if (acceptAny) return true;
159
+ const extension = '.' + file.name.split('.').pop();
160
+ return allowedTypes.includes(file.type) || allowedTypes.includes(extension);
161
+ };
162
+ });
163
+
164
+ // Watchers
165
+ watch(
166
+ () => props.modelValue,
167
+ newValue => {
168
+ internalFiles.value = newValue || [];
169
+ }
170
+ );
171
+
172
+ // Methods
173
+ function addFiles(files) {
174
+ for (const file of files) {
175
+ const internalFile = internalFiles.value.find(
176
+ item => item.file.name === file.name
177
+ );
178
+ const newFile = createInternalFile(internalFile?.file || file);
179
+
180
+ if (internalFile) {
181
+ internalFile.state = newFile.initialState;
182
+ internalFile.invalidMessageTitle = newFile.invalidMessageTitle;
183
+ internalFile.invalidMessage = newFile.invalidMessage;
184
+ } else {
185
+ internalFiles.value.push(newFile);
186
+ }
187
+ }
188
+ // clear input we do not need to maintain it
189
+ fileInput.value.value = '';
190
+ emit('update:modelValue', internalFiles.value);
191
+ }
192
+
193
+ function createInternalFile(file) {
194
+ const initialState = props.initialStateUploading
195
+ ? STATES.UPLOADING
196
+ : STATES.NONE;
197
+ const isTypeNotAllowed = !isFileTypeAllowed.value(file);
198
+ const invalidMessageTitle = isTypeNotAllowed ? 'Invalid file type' : '';
199
+ const invalidMessage = isTypeNotAllowed
200
+ ? `"${file.name}" does not have a valid file type.`
201
+ : '';
202
+
203
+ return {
204
+ state: initialState,
205
+ file,
206
+ invalidMessageTitle,
207
+ invalidMessage,
208
+ };
209
+ }
210
+
211
+ function onChange(ev) {
212
+ if (ev.target.files.length !== 0 && props.clearOnReselect) {
213
+ internalFiles.value = [];
214
+ }
215
+ addFiles(ev.target.files);
216
+ }
217
+
218
+ function onDragEvent(evt) {
219
+ if (Array.prototype.indexOf.call(evt.dataTransfer.types, 'Files') === -1) {
220
+ return;
221
+ }
222
+
223
+ if (evt.type === 'dragleave') {
224
+ allowDrop.value = false;
225
+ return;
226
+ }
227
+
228
+ if (evt.type === 'dragover') {
229
+ evt.preventDefault();
230
+ const dropEffect = 'copy';
231
+ if (Array.isArray(evt.dataTransfer.types)) {
232
+ try {
233
+ // IE11 throws a "permission denied" error accessing `.effectAllowed`
234
+ evt.dataTransfer.effectAllowed = dropEffect;
235
+ } catch (e) {
236
+ // ignore
237
+ }
238
+ }
239
+ evt.dataTransfer.dropEffect = dropEffect;
240
+ allowDrop.value = true;
241
+ }
242
+
243
+ if (evt.type === 'drop') {
244
+ evt.preventDefault();
245
+ addFiles(evt.dataTransfer.files);
246
+ allowDrop.value = false;
247
+ }
248
+ }
249
+
250
+ function onItemRemove(index) {
251
+ internalFiles.value.splice(index, 1);
252
+ emit('update:modelValue', this.internalFiles);
253
+ }
254
+
255
+ function onKeyHit() {
256
+ fileInput.value.click();
257
+ }
258
+
259
+ // Lifecycle Hooks
260
+ onMounted(() => {
261
+ internalFiles.value = props.modelValue || [];
262
+ });
263
+
264
+ // exposed methods
265
+ defineExpose({
266
+ setState(index, state) {
267
+ if ([STATES.COMPLETE, STATES.UPLOADING, STATES.NONE].includes(state)) {
268
+ internalFiles.value[index].state = state;
269
+ }
270
+ },
271
+ clear() {
272
+ internalFiles.value = [];
273
+ emit('update:modelValue', internalFiles);
274
+ },
275
+ setInvalidMessage(index, message) {
276
+ internalFiles.value[index].invalidMessage = message;
277
+ },
278
+ remove: onItemRemove,
279
+ });
280
+ </script>
281
+
282
+ <script>
283
+ export default {
284
+ inheritAttrs: false,
285
+ };
286
+ </script>
@@ -0,0 +1,92 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ `${carbonPrefix}--file__selected-file`,
5
+ { [`${carbonPrefix}--file__selected-file--invalid`]: isInvalid },
6
+ ]"
7
+ >
8
+ <p :class="`${carbonPrefix}--file-filename`" :title="item.file?.name">
9
+ {{ item.file?.name }}
10
+ </p>
11
+ <span :class="`${carbonPrefix}--file__state-container`">
12
+ <div
13
+ v-if="item.state === 'uploading'"
14
+ :class="`${carbonPrefix}--inline-loading__animation`"
15
+ >
16
+ <div
17
+ data-inline-loading-spinner
18
+ :class="`${carbonPrefix}--loading ${carbonPrefix}--loading--small`"
19
+ >
20
+ <svg
21
+ :class="`${carbonPrefix}--loading__svg`"
22
+ viewBox="-75 -75 150 150"
23
+ >
24
+ <circle
25
+ :class="`${carbonPrefix}--loading__background`"
26
+ cx="0"
27
+ cy="0"
28
+ r="37.5"
29
+ />
30
+ <circle
31
+ :class="`${carbonPrefix}--loading__stroke`"
32
+ cx="0"
33
+ cy="0"
34
+ r="37.5"
35
+ />
36
+ </svg>
37
+ </div>
38
+ </div>
39
+ <CheckmarkFilled16
40
+ v-if="item.state === 'complete'"
41
+ :class="`${carbonPrefix}--file-complete`"
42
+ />
43
+ <WarningFilled16
44
+ v-if="isInvalid"
45
+ :class="`${carbonPrefix}--file--invalid`"
46
+ />
47
+ <button
48
+ v-if="removable"
49
+ type="button"
50
+ :class="`${carbonPrefix}--file-close`"
51
+ :alt="removeAriaLabel"
52
+ :arial-label="removeAriaLabel"
53
+ @click="emit('remove', item)"
54
+ >
55
+ <Close16 />
56
+ <span :class="`${carbonPrefix}--visually-hidden`">
57
+ {{ removeAriaLabel }}
58
+ </span>
59
+ </button>
60
+ </span>
61
+ <!-- check role (alert) and id / accessibility -->
62
+ <div v-if="isInvalid" :class="`${carbonPrefix}--form-requirement`">
63
+ <div :class="`${carbonPrefix}--form-requirement__title`">
64
+ {{ item.invalidMessageTitle || 'Invalid file' }}
65
+ </div>
66
+ <p :class="`${carbonPrefix}--form-requirement__supplement`">
67
+ {{ item.invalidMessage }}
68
+ </p>
69
+ </div>
70
+ </div>
71
+ </template>
72
+
73
+ <script setup>
74
+ import { computed } from 'vue';
75
+ import { carbonPrefix } from '../../global/settings';
76
+ import CheckmarkFilled16 from '@carbon/icons-vue/es/checkmark--filled/16';
77
+ import WarningFilled16 from '@carbon/icons-vue/es/warning--filled/16';
78
+ import Close16 from '@carbon/icons-vue/es/close/16';
79
+
80
+ const emit = defineEmits(['remove']);
81
+
82
+ const props = defineProps({
83
+ item: Object,
84
+ removable: Boolean,
85
+ removeAriaLabel: { type: String, default: 'Remove selected file' },
86
+ });
87
+
88
+ // Computed
89
+ const isInvalid = computed(() => {
90
+ return props.item.invalidMessage && props.item.invalidMessage.length;
91
+ });
92
+ </script>