@pareto-engineering/design-system 4.2.1 → 4.3.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 (58) hide show
  1. package/dist/cjs/f/FormInput/FormInput.js +6 -0
  2. package/dist/cjs/f/common/utils/index.js +8 -1
  3. package/dist/cjs/f/common/utils/validators.js +17 -0
  4. package/dist/cjs/f/fields/ChoicesInput/common/Choice/Choice.js +3 -2
  5. package/dist/cjs/f/fields/FileUpload/FileUpload.js +234 -0
  6. package/dist/cjs/f/fields/FileUpload/common/FilePreview/FilePreview.js +118 -0
  7. package/dist/cjs/f/fields/FileUpload/common/FilePreview/index.js +13 -0
  8. package/dist/cjs/f/fields/FileUpload/common/FilePreview/styles.scss +93 -0
  9. package/dist/cjs/f/fields/FileUpload/common/index.js +12 -0
  10. package/dist/cjs/f/fields/FileUpload/index.js +32 -0
  11. package/dist/cjs/f/fields/FileUpload/styles.scss +73 -0
  12. package/dist/cjs/f/fields/FileUpload/utils.js +45 -0
  13. package/dist/cjs/f/fields/index.js +26 -1
  14. package/dist/cjs/g/FormBuilder/FormBuilder.js +11 -3
  15. package/dist/cjs/g/FormBuilder/common/Builder/common/InputBuilder/InputBuilder.js +34 -7
  16. package/dist/cjs/g/FormBuilder/common/Builder/common/InputBuilder/styles.scss +11 -0
  17. package/dist/cjs/g/FormBuilder/common/Renderer/Renderer.js +14 -4
  18. package/dist/cjs/g/FormBuilder/common/Renderer/common/Section/Section.js +8 -4
  19. package/dist/es/f/FormInput/FormInput.js +7 -1
  20. package/dist/es/f/common/utils/index.js +2 -1
  21. package/dist/es/f/common/utils/validators.js +10 -0
  22. package/dist/es/f/fields/ChoicesInput/common/Choice/Choice.js +3 -2
  23. package/dist/es/f/fields/FileUpload/FileUpload.js +223 -0
  24. package/dist/es/f/fields/FileUpload/common/FilePreview/FilePreview.js +108 -0
  25. package/dist/es/f/fields/FileUpload/common/FilePreview/index.js +2 -0
  26. package/dist/es/f/fields/FileUpload/common/FilePreview/styles.scss +93 -0
  27. package/dist/es/f/fields/FileUpload/common/index.js +2 -0
  28. package/dist/es/f/fields/FileUpload/index.js +3 -0
  29. package/dist/es/f/fields/FileUpload/styles.scss +73 -0
  30. package/dist/es/f/fields/FileUpload/utils.js +37 -0
  31. package/dist/es/f/fields/index.js +2 -1
  32. package/dist/es/g/FormBuilder/FormBuilder.js +11 -3
  33. package/dist/es/g/FormBuilder/common/Builder/common/InputBuilder/InputBuilder.js +35 -8
  34. package/dist/es/g/FormBuilder/common/Builder/common/InputBuilder/styles.scss +11 -0
  35. package/dist/es/g/FormBuilder/common/Renderer/Renderer.js +14 -4
  36. package/dist/es/g/FormBuilder/common/Renderer/common/Section/Section.js +8 -3
  37. package/package.json +3 -3
  38. package/src/stories/f/FileUpload.stories.jsx +55 -0
  39. package/src/stories/g/FormBuilder.stories.jsx +19 -97
  40. package/src/ui/f/FormInput/FormInput.jsx +11 -0
  41. package/src/ui/f/common/utils/index.js +1 -0
  42. package/src/ui/f/common/utils/validators.js +10 -0
  43. package/src/ui/f/fields/ChoicesInput/common/Choice/Choice.jsx +2 -0
  44. package/src/ui/f/fields/FileUpload/FileUpload.jsx +279 -0
  45. package/src/ui/f/fields/FileUpload/common/FilePreview/FilePreview.jsx +160 -0
  46. package/src/ui/f/fields/FileUpload/common/FilePreview/index.js +2 -0
  47. package/src/ui/f/fields/FileUpload/common/FilePreview/styles.scss +93 -0
  48. package/src/ui/f/fields/FileUpload/common/index.js +2 -0
  49. package/src/ui/f/fields/FileUpload/index.js +3 -0
  50. package/src/ui/f/fields/FileUpload/styles.scss +73 -0
  51. package/src/ui/f/fields/FileUpload/utils.js +49 -0
  52. package/src/ui/f/fields/index.js +6 -0
  53. package/src/ui/g/FormBuilder/FormBuilder.jsx +9 -0
  54. package/src/ui/g/FormBuilder/common/Builder/common/InputBuilder/InputBuilder.jsx +53 -11
  55. package/src/ui/g/FormBuilder/common/Builder/common/InputBuilder/styles.scss +11 -0
  56. package/src/ui/g/FormBuilder/common/Renderer/Renderer.jsx +17 -3
  57. package/src/ui/g/FormBuilder/common/Renderer/common/Section/Section.jsx +11 -2
  58. package/tests/__snapshots__/Storyshots.test.js.snap +180 -154
@@ -41,109 +41,28 @@ export const Builder = {
41
41
  )
42
42
  },
43
43
  }
44
+
44
45
  const sampleFormData = {
45
46
  sections:[
46
47
  {
47
- title :'This is a survey of some of the best LLM models out there',
48
- description:'{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Best LLM models for software developers","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"heading","version":1,"tag":"h3"},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Chat GPT","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Perplexity","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":2},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Gemini","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":3},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Claude","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":4}],"direction":"ltr","format":"","indent":0,"type":"list","version":1,"listType":"bullet","start":1,"tag":"ul"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Please add more exciting models that we might missed","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"quote","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"You can make them ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"bold","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" or ","type":"text","version":1},{"detail":0,"format":2,"mode":"normal","style":"","text":"Italic or ","type":"text","version":1},{"detail":0,"format":10,"mode":"normal","style":"","text":"underlined","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1},{"children":[{"detail":0,"format":4,"mode":"normal","style":"color: #110b53;","text":"You can also color them and strike through them!","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}',
48
+ title :'Hello',
49
+ description:'{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Test file upload ","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}',
49
50
  key :'section-0',
50
51
  inputs :[
51
52
  {
52
- name :'section_0_input_0',
53
- label :'Name the Model',
54
- type :'text',
55
- required :true,
56
- options :[],
57
- conditionalLogic:null,
58
- description :'',
59
- },
60
- {
61
- name :'section_0_input_1',
62
- label :'Describe the Model in detail',
63
- type :'textarea',
64
- required :false,
65
- options :[],
66
- conditionalLogic:null,
67
- description :'brief model description',
68
- },
69
- ],
70
- orderedInputDragIds:[],
71
- navigation :{
72
- nextSection:'next',
73
- },
74
- },
75
- {
76
- title :'Rate the Models',
77
- description:'{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Best LLM models for software developers","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Chat GPT","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Perplexity","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":2},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Gemini","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":3},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Claude","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":4}],"direction":"ltr","format":"","indent":0,"type":"list","version":1,"listType":"bullet","start":1,"tag":"ul"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Please add more exciting models that we might missed","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"quote","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"You can make them ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"bold","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" or ","type":"text","version":1},{"detail":0,"format":2,"mode":"normal","style":"","text":"Italic or ","type":"text","version":1},{"detail":0,"format":10,"mode":"normal","style":"","text":"underlined","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1},{"children":[{"detail":0,"format":4,"mode":"normal","style":"","text":"You can also color them and strike through them!","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}',
78
- key :'section-1',
79
- inputs :[
80
- {
81
- type :'choices',
82
- options:[
83
- {
84
- key :'sections-1-inputs-0-options--1',
85
- value :'Chat GPT',
86
- label :'Chat GPT',
87
- nextSection:'',
88
- },
89
- {
90
- key :'sections-1-inputs-0-options-0',
91
- value :'Gemini',
92
- label :'Gemini',
93
- nextSection:'',
94
- },
53
+ name :'section_0_input_0',
54
+ label :'The file upload ',
55
+ type :'file',
56
+ required :false,
57
+ options :[],
58
+ conditionalLogic :null,
59
+ description :'',
60
+ maxCount :3,
61
+ showSpecificFileTypes:true,
62
+ accept :[
63
+ '.mp4,.ogg,.webm,video/mp4,video/ogg,video/webm',
64
+ '.pdf,application/pdf',
95
65
  ],
96
- required :true,
97
- description:'',
98
- name :'section_1_input_0',
99
- label :'Which is better between the 2?',
100
- },
101
- {
102
- name :'section_1_input_1',
103
- label :'Choose the worst of them ',
104
- type :'multiple',
105
- required:false,
106
- options :[
107
- {
108
- key :'sections-1-inputs-1-options--1',
109
- value :'Chat GPT',
110
- label :'Chat GPT',
111
- nextSection:'',
112
- },
113
- {
114
- key :'sections-1-inputs-1-options-0',
115
- value :'Gemini',
116
- label :'Gemini',
117
- nextSection:'',
118
- },
119
- {
120
- key :'sections-1-inputs-1-options-1',
121
- value :'Claude',
122
- label :'Claude',
123
- nextSection:'',
124
- },
125
- ],
126
- conditionalLogic:null,
127
- description :'',
128
- },
129
- ],
130
- orderedInputDragIds:[],
131
- navigation :{
132
- nextSection:'next',
133
- },
134
- },
135
- {
136
- title :'General sentiments',
137
- description:'{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Best LLM models for software developers","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"heading","version":1,"tag":"h3"},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Chat GPT","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Perplexity","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":2},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Gemini","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":3},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Claude","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":4}],"direction":"ltr","format":"","indent":0,"type":"list","version":1,"listType":"bullet","start":1,"tag":"ul"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Please add more exciting models that we might missed","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"quote","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"You can make them ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"bold","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" or ","type":"text","version":1},{"detail":0,"format":2,"mode":"normal","style":"","text":"Italic or ","type":"text","version":1},{"detail":0,"format":10,"mode":"normal","style":"","text":"underlined","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1},{"children":[{"detail":0,"format":4,"mode":"normal","style":"","text":"You can also color them and strike through them!","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}',
138
- key :'section-2',
139
- inputs :[
140
- {
141
- type :'textarea',
142
- options :[],
143
- required :true,
144
- description:'',
145
- name :'section_2_input_0',
146
- label :'Give a final brief description of your findings',
147
66
  },
148
67
  ],
149
68
  orderedInputDragIds:[],
@@ -168,7 +87,10 @@ export const Renderer = {
168
87
 
169
88
  return (
170
89
  <>
171
- <FormBuilder {...args} onRendererFormSave={handleFormDataUpdateWithValues} />
90
+ <FormBuilder
91
+ {...args}
92
+ onRendererFormSave={handleFormDataUpdateWithValues}
93
+ />
172
94
  <pre>{JSON.stringify(values, null, 2)}</pre>
173
95
  </>
174
96
  )
@@ -17,6 +17,7 @@ import {
17
17
  QueryChoices,
18
18
  LinkInput,
19
19
  EditorInput,
20
+ FileUpload,
20
21
  } from '../fields'
21
22
 
22
23
  import './styles.scss'
@@ -147,6 +148,16 @@ const FormInput = ({
147
148
  )
148
149
  }
149
150
 
151
+ if (type === 'file') {
152
+ return (
153
+ <FileUpload
154
+ className={newClassName}
155
+ disabled={disabled}
156
+ {...otherProps}
157
+ />
158
+ )
159
+ }
160
+
150
161
  if (extraTypes?.[type]) {
151
162
  const Component = extraTypes[type]
152
163
  return (
@@ -1 +1,2 @@
1
1
  export { lookUpInputValueFromFetchedOptions } from './lookUpInputValueFromFetchedOptions'
2
+ export { integerAndGreaterThanZero } from './validators'
@@ -0,0 +1,10 @@
1
+ export const integerAndGreaterThanZero = (value) => {
2
+ let error = ''
3
+ if ((value && value <= 0) || value === 0) {
4
+ error = 'Value must be greator than zero.'
5
+ }
6
+ if (value && value % 1 !== 0) {
7
+ error = 'You must enter a whole number.'
8
+ }
9
+ return error
10
+ }
@@ -27,6 +27,7 @@ const Choice = ({
27
27
  multiple,
28
28
  validate,
29
29
  disabled,
30
+ ...otherProps
30
31
  }) => {
31
32
  const type = multiple ? 'checkbox' : 'radio'
32
33
 
@@ -53,6 +54,7 @@ const Choice = ({
53
54
  id={id}
54
55
  name={name}
55
56
  {...field}
57
+ {...otherProps}
56
58
  value={value}
57
59
  disabled={disabled}
58
60
  />
@@ -0,0 +1,279 @@
1
+ /* @pareto-engineering/generator-front 1.1.1-alpha.2 */
2
+ import * as React from 'react'
3
+
4
+ import PropTypes from 'prop-types'
5
+
6
+ import { useField } from 'formik'
7
+
8
+ import styleNames from '@pareto-engineering/bem/exports'
9
+
10
+ import './styles.scss'
11
+
12
+ import { FilePreview } from './common'
13
+
14
+ import { FormLabel, FormDescription } from '../../common'
15
+
16
+ // Local Definitions
17
+
18
+ const baseClassName = styleNames.base
19
+
20
+ const componentClassName = 'file-upload'
21
+
22
+ /**
23
+ * This is the component description.
24
+ */
25
+ const FileUpload = ({
26
+ id,
27
+ className:userClassName,
28
+ style,
29
+ label,
30
+ description,
31
+ accept,
32
+ multiple,
33
+ name,
34
+ disabled,
35
+ maxCount,
36
+ maxCountError,
37
+ maxSize,
38
+ maxSizeError,
39
+ onChange,
40
+ uploadStatus,
41
+ filePreviewAlignment,
42
+ optional,
43
+ labelColor,
44
+ color,
45
+ viewOnly,
46
+ handleFileDelete,
47
+ // ...otherProps
48
+ }) => {
49
+ const [field, , helpers] = useField({ name })
50
+
51
+ const { value } = field
52
+
53
+ const { setError, setValue, setTouched } = helpers
54
+
55
+ const handleChange = (event) => {
56
+ setTouched(true, false)
57
+ const newFiles = Array.from(event.currentTarget.files)
58
+ const currentFiles = value || []
59
+
60
+ const currentCount = currentFiles.length + newFiles.length
61
+ if (maxCount && currentCount > maxCount) {
62
+ setError(maxCountError || `Maximum of ${maxCount} files allowed`)
63
+ return
64
+ }
65
+
66
+ const duplicateFiles = newFiles.filter(
67
+ (newFile) => currentFiles.some((currentFile) => currentFile.name === newFile.name),
68
+ )
69
+
70
+ if (duplicateFiles.length > 0) {
71
+ setError('You cannot upload the same file twice.')
72
+ return
73
+ }
74
+
75
+ if (maxSize) {
76
+ const oversizedFiles = newFiles.filter((file) => file.size > maxSize)
77
+ if (oversizedFiles.length > 0) {
78
+ setError(maxSizeError || `File size exceeds the limit of ${maxSize} bytes`)
79
+ return
80
+ }
81
+ }
82
+
83
+ const updatedFiles = [...currentFiles, ...newFiles]
84
+
85
+ setValue(updatedFiles)
86
+ onChange?.(updatedFiles)
87
+
88
+ // eslint-disable-next-line no-param-reassign
89
+ event.target.value = null
90
+ }
91
+
92
+ const handleDelete = ({ fileIndex, identifier }) => {
93
+ const updatedFiles = value?.filter((_, i) => i !== fileIndex)
94
+ setValue(updatedFiles)
95
+
96
+ handleFileDelete?.({ fileIndex, identifier })
97
+ }
98
+
99
+ const acceptOptions = Array.isArray(accept) ? accept?.join(',') : accept
100
+
101
+ return (
102
+ <div
103
+ id={id}
104
+ className={[
105
+
106
+ baseClassName,
107
+
108
+ componentClassName,
109
+ userClassName,
110
+ `preview-${filePreviewAlignment}`,
111
+ `y-${color}`,
112
+ ]
113
+ .filter((e) => e)
114
+ .join(' ')}
115
+ style={style}
116
+ >
117
+ <p>{label}</p>
118
+ <FormDescription className="s-1" description={description} name={name} />
119
+ {!viewOnly && (
120
+ <FormLabel
121
+ name={name}
122
+ color={labelColor}
123
+ optional={optional}
124
+ >
125
+ <input
126
+ id={name}
127
+ className="file"
128
+ type="file"
129
+ accept={acceptOptions}
130
+ multiple={multiple || (maxCount && maxCount > 0)}
131
+ disabled={disabled}
132
+ onChange={handleChange}
133
+ />
134
+ <span className="ai-icon">
135
+ U
136
+ </span>
137
+ <span>
138
+ Attach file
139
+ </span>
140
+ </FormLabel>
141
+ )}
142
+ {value?.length > 0 && (
143
+ <div className="attached-files">
144
+ <p>Attached files:</p>
145
+ <div className="files">
146
+ {value.map((file, indx) => {
147
+ const isFileObject = file instanceof File
148
+
149
+ return (
150
+ <FilePreview
151
+ file={file}
152
+ key={isFileObject ? file.name : file}
153
+ disabled={disabled}
154
+ handleDelete={() => handleDelete({
155
+ fileIndex :indx,
156
+ identifier:isFileObject ? file.name : file?.id,
157
+ })}
158
+ uploadStatus={uploadStatus?.[file?.name]}
159
+ viewOnly={viewOnly}
160
+ />
161
+ )
162
+ })}
163
+ </div>
164
+ </div>
165
+ )}
166
+ </div>
167
+ )
168
+ }
169
+
170
+ FileUpload.propTypes = {
171
+ /**
172
+ * The HTML id for this element
173
+ */
174
+ id:PropTypes.string,
175
+
176
+ /**
177
+ * The HTML class names for this element
178
+ */
179
+ className:PropTypes.string,
180
+
181
+ /**
182
+ * The React-written, css properties for this element.
183
+ */
184
+ style:PropTypes.objectOf(PropTypes.string),
185
+
186
+ /**
187
+ * The label text for the file input
188
+ */
189
+ label:PropTypes.string,
190
+
191
+ /**
192
+ * The description text below the label
193
+ */
194
+ description:PropTypes.string,
195
+
196
+ /**
197
+ * The color of the text
198
+ */
199
+ color:PropTypes.string,
200
+
201
+ /**
202
+ * The accepted file types (e.g., 'image/*')
203
+ */
204
+ accept:PropTypes.string,
205
+
206
+ /**
207
+ * Whether to allow multiple file uploads
208
+ */
209
+ multiple:PropTypes.bool,
210
+
211
+ /**
212
+ * The name attribute for the input
213
+ */
214
+ name:PropTypes.string.isRequired,
215
+
216
+ /**
217
+ * Whether the the input should be disabled
218
+ */
219
+ disabled:PropTypes.bool,
220
+
221
+ /**
222
+ * The maximum number of files allowed
223
+ */
224
+ maxCount:PropTypes.number,
225
+
226
+ /**
227
+ * The error message when max count is exceeded
228
+ */
229
+ maxCountError:PropTypes.string,
230
+
231
+ /**
232
+ * The maximum size of files allowed (in bytes)
233
+ */
234
+ maxSize:PropTypes.number,
235
+
236
+ /**
237
+ * The error message when file size exceeds max size
238
+ */
239
+ maxSizeError:PropTypes.string,
240
+
241
+ /**
242
+ * The upload progress value (0-100)
243
+ */
244
+ // progress:PropTypes.number,
245
+
246
+ /**
247
+ * The function to handle file change events
248
+ */
249
+ onChange:PropTypes.func,
250
+
251
+ /**
252
+ * The function to handle file delete events
253
+ */
254
+ handleFileDelete:PropTypes.func,
255
+
256
+ /**
257
+ * The upload status of the file with the file name as the key
258
+ */
259
+ uploadStatus:PropTypes.objectOf(PropTypes.string),
260
+
261
+ /**
262
+ * The alignment for file previews WRT the upload input
263
+ */
264
+ filePreviewAlignment:PropTypes.oneOf(['left', 'right', 'top', 'bottom']),
265
+
266
+ /**
267
+ * Whether the file upload is in view only mode
268
+ */
269
+ viewOnly:PropTypes.bool,
270
+ }
271
+
272
+ FileUpload.defaultProps = {
273
+ disabled :false,
274
+ color :'paragraph',
275
+ filePreviewAlignment:'bottom',
276
+ viewOnly :false,
277
+ }
278
+
279
+ export default FileUpload
@@ -0,0 +1,160 @@
1
+ /* @pareto-engineering/generator-front 1.1.1-alpha.2 */
2
+ import * as React from 'react'
3
+
4
+ import PropTypes from 'prop-types'
5
+
6
+ import styleNames from '@pareto-engineering/bem/exports'
7
+
8
+ import { LoadingCircle, ProgressBar } from 'ui/a'
9
+
10
+ // Local Definitions
11
+
12
+ import './styles.scss'
13
+
14
+ import { Button } from 'ui/b'
15
+
16
+ import { getFileType, getFileTypeFromUrl } from '../../utils'
17
+
18
+ const baseClassName = styleNames.base
19
+
20
+ const componentClassName = 'file-preview'
21
+
22
+ const statusColorMap = {
23
+ pending:'yellow',
24
+ error :'red',
25
+ success:'green',
26
+ }
27
+
28
+ const FILE_UPLOAD_COMPLETE_PROGRESS = 100
29
+
30
+ /**
31
+ * This is the component description.
32
+ */
33
+ const FilePreview = ({
34
+ id,
35
+ className:userClassName,
36
+ style,
37
+ file,
38
+ disabled,
39
+ handleDelete,
40
+ uploadStatus,
41
+ color,
42
+ uploadProgress,
43
+ // ...otherProps
44
+ }) => {
45
+ const isFileObject = file instanceof File
46
+ const type = isFileObject ? getFileType(file) : getFileTypeFromUrl(file?.url)
47
+
48
+ const status = uploadStatus?.status
49
+ const statusColor = statusColorMap[status] ?? color
50
+
51
+ const isPending = status === 'pending'
52
+ // isPreview means the file is uploaded and previewed while other files can still be uploaded
53
+ const isSuccess = status === 'success' || file?.isPreview
54
+
55
+ const handlePreview = () => {
56
+ if (!isFileObject) {
57
+ window.open(file.url, '_blank')
58
+ }
59
+ }
60
+
61
+ const canDelete = file?.isPreview || isFileObject
62
+
63
+ return (
64
+ <div
65
+ id={id}
66
+ className={[
67
+
68
+ baseClassName,
69
+
70
+ componentClassName,
71
+ userClassName,
72
+ ]
73
+ .filter((e) => e)
74
+ .join(' ')}
75
+ style={style}
76
+ >
77
+ <div className="identity">
78
+ <span
79
+ className={
80
+ [
81
+ 'type',
82
+ type.toLowerCase(),
83
+ ]
84
+ .filter((e) => e)
85
+ .join(' ')
86
+ }
87
+ >
88
+ {type}
89
+ </span>
90
+ <span title={file.name} className="name">{file.name}</span>
91
+ <Button
92
+ isCompact
93
+ isSimple
94
+ onClick={() => (canDelete ? handleDelete() : handlePreview())}
95
+ >
96
+ <span className="icon x-ui-icons c-x">{canDelete ? 'Y' : '9'}</span>
97
+ </Button>
98
+ </div>
99
+ {!disabled && (
100
+ <div className={`progress-status x-${statusColor}`}>
101
+ <div className="status">
102
+ {isPending ? (
103
+ <LoadingCircle
104
+ className={`x-${statusColor}`}
105
+ heightWidth="1rem"
106
+ color={statusColor}
107
+ />
108
+ ) : (
109
+ <>
110
+ <span className="icon">
111
+ {isSuccess ? 'I' : 'Y'}
112
+ </span>
113
+ <span>{isSuccess ? 'Uploaded' : 'Error'}</span>
114
+ </>
115
+ )}
116
+ </div>
117
+ <ProgressBar
118
+ color={statusColor}
119
+ progress={isPending ? uploadProgress : FILE_UPLOAD_COMPLETE_PROGRESS}
120
+ height="3px"
121
+ />
122
+ </div>
123
+ )}
124
+ </div>
125
+ )
126
+ }
127
+
128
+ FilePreview.propTypes = {
129
+ /**
130
+ * The HTML id for this element
131
+ */
132
+ id:PropTypes.string,
133
+
134
+ /**
135
+ * The HTML class names for this element
136
+ */
137
+ className:PropTypes.string,
138
+
139
+ /**
140
+ * The React-written, css properties for this element.
141
+ */
142
+ style:PropTypes.objectOf(PropTypes.string),
143
+
144
+ /**
145
+ * The base color of the file preview
146
+ */
147
+ color:PropTypes.string,
148
+
149
+ /**
150
+ * The upload progress of the file
151
+ */
152
+ uploadProgress:PropTypes.number,
153
+ }
154
+
155
+ FilePreview.defaultProps = {
156
+ color :'green',
157
+ uploadProgress:45,
158
+ }
159
+
160
+ export default FilePreview
@@ -0,0 +1,2 @@
1
+ /* @pareto-engineering/generator-front 1.1.1-alpha.2 */
2
+ export { default as FilePreview } from './FilePreview'