@pareto-engineering/design-system 4.9.2 → 4.10.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 (31) hide show
  1. package/dist/cjs/a/AreaChart/AreaChart.js +176 -0
  2. package/dist/cjs/a/AreaChart/index.js +13 -0
  3. package/dist/cjs/a/AreaChart/styles.scss +89 -0
  4. package/dist/cjs/a/XMLEditor/XMLEditor.js +1 -1
  5. package/dist/cjs/a/index.js +8 -1
  6. package/dist/cjs/b/Button/styles.scss +1 -0
  7. package/dist/cjs/g/FormBuilder/FormBuilder.js +3 -3
  8. package/dist/cjs/g/FormBuilder/common/Renderer/Renderer.js +38 -38
  9. package/dist/cjs/g/FormBuilder/common/Renderer/common/Section/Section.js +34 -8
  10. package/dist/es/a/AreaChart/AreaChart.js +163 -0
  11. package/dist/es/a/AreaChart/index.js +1 -0
  12. package/dist/es/a/AreaChart/styles.scss +89 -0
  13. package/dist/es/a/XMLEditor/XMLEditor.js +2 -2
  14. package/dist/es/a/index.js +2 -1
  15. package/dist/es/b/Button/styles.scss +1 -0
  16. package/dist/es/g/FormBuilder/FormBuilder.js +3 -3
  17. package/dist/es/g/FormBuilder/common/Renderer/Renderer.js +39 -39
  18. package/dist/es/g/FormBuilder/common/Renderer/common/Section/Section.js +35 -9
  19. package/package.json +4 -3
  20. package/src/stories/a/AreaChart.stories.jsx +118 -0
  21. package/src/ui/a/AreaChart/AreaChart.jsx +185 -0
  22. package/src/ui/a/AreaChart/index.js +1 -0
  23. package/src/ui/a/AreaChart/styles.scss +89 -0
  24. package/src/ui/a/XMLEditor/XMLEditor.jsx +4 -1
  25. package/src/ui/a/index.js +1 -0
  26. package/src/ui/b/Button/styles.scss +1 -0
  27. package/src/ui/g/FormBuilder/FormBuilder.jsx +2 -2
  28. package/src/ui/g/FormBuilder/common/Renderer/Renderer.jsx +40 -39
  29. package/src/ui/g/FormBuilder/common/Renderer/common/Section/Section.jsx +41 -10
  30. package/tests/__snapshots__/Storyshots.test.js.snap +501 -1
  31. package/tests/test-setup.js +11 -0
@@ -0,0 +1,185 @@
1
+ // front/packages/design-system/src/ui/a/AreaChart/AreaChart.jsx
2
+
3
+ import * as React from 'react'
4
+
5
+ import PropTypes from 'prop-types'
6
+
7
+ import {
8
+ AreaChart as RechartsAreaChart,
9
+ Area,
10
+ XAxis,
11
+ YAxis,
12
+ CartesianGrid,
13
+ Tooltip,
14
+ ResponsiveContainer,
15
+ } from 'recharts'
16
+
17
+ import styleNames from '@pareto-engineering/bem/exports'
18
+
19
+ import './styles.scss'
20
+
21
+ // Local Definitions
22
+
23
+ const baseClassName = styleNames.base
24
+
25
+ const componentClassName = 'area-chart'
26
+
27
+ const AreaChart = ({
28
+ id,
29
+ className: userClassName,
30
+ data,
31
+ title,
32
+ xKey,
33
+ yKeys,
34
+ xLabel,
35
+ yLabel,
36
+ colors,
37
+ filled,
38
+ height,
39
+ width,
40
+ // ...otherProps
41
+ }) => {
42
+ const processedData = data.map((item) => {
43
+ const yValues = yKeys.map((key) => item[key])
44
+ const lowerBound = Math.min(...yValues)
45
+ const upperBound = Math.max(...yValues)
46
+ const margin = (upperBound - lowerBound) * 0.1
47
+ return {
48
+ ...item,
49
+ bounds:[lowerBound - margin, upperBound + margin],
50
+ }
51
+ })
52
+
53
+ const yAxisBounds = () => {
54
+ const yValues = data.map((item) => yKeys.map((key) => item[key]))
55
+ const min = Math.min(...yValues.flat())
56
+ const max = Math.max(...yValues.flat())
57
+ const margin = (max - min) * 0.1
58
+ return [min - margin, max + margin]
59
+ }
60
+
61
+ const CustomTooltipContent = ({ active, payload, label }) => {
62
+ if (active && payload && payload.length) {
63
+ const newPayload = payload.filter((item) => item.name !== 'bounds')
64
+ return (
65
+ <div className="custom-tooltip">
66
+ <p className="label">{`${xLabel}: ${label}`}</p>
67
+ {newPayload.map((entry) => (
68
+ <p className="label" key={`${entry.name}`} style={{ color: entry.color }}>
69
+ {`${entry.name}: ${entry.value}`}
70
+ </p>
71
+ ))}
72
+ </div>
73
+ )
74
+ }
75
+ return null
76
+ }
77
+
78
+ const CustomLegend = ({ colorsArray, yKeysArray }) => (
79
+ <div className="custom-legend">
80
+ {yKeysArray.map((key, index) => (
81
+ <div key={key} className="item">
82
+ <span
83
+ className="line"
84
+ style={{ backgroundColor: colorsArray[index] }}
85
+ />
86
+ <span className="text">{key}</span>
87
+ </div>
88
+ ))}
89
+ </div>
90
+ )
91
+
92
+ return (
93
+ <div
94
+ id={id}
95
+ className={[
96
+ baseClassName,
97
+ componentClassName,
98
+ userClassName,
99
+ ]
100
+ .filter((e) => e)
101
+ .join(' ')}
102
+ >
103
+ <h3>{title}</h3>
104
+ <CustomLegend colorsArray={colors} yKeysArray={yKeys} />
105
+ <ResponsiveContainer width={width} height={height}>
106
+ <RechartsAreaChart data={processedData}>
107
+ <CartesianGrid strokeDasharray="3 3" />
108
+ <XAxis
109
+ dataKey={xKey}
110
+ label={{ value: xLabel, position: 'insideBottom', offset: -5 }} // Adjusted offset for padding
111
+ axisLine={false}
112
+ tickLine={false}
113
+ tickCount={3}
114
+ />
115
+ <YAxis
116
+ domain={yAxisBounds}
117
+ label={{
118
+ value:yLabel, angle:-90, position:'insideLeft', offset:15,
119
+ }}
120
+ axisLine={false}
121
+ tickLine={false}
122
+ tickFormatter={(value) => value.toFixed(2)}
123
+ />
124
+ <Tooltip content={<CustomTooltipContent />} />
125
+ {filled && (
126
+ <Area
127
+ id="bounds"
128
+ type="linear"
129
+ dataKey="bounds"
130
+ stroke="none"
131
+ fill="var(--hard-ui-main-2)"
132
+ fillOpacity={0.4}
133
+ activeDot={false}
134
+ dot={false}
135
+ label={false}
136
+ isAnimationActive={false}
137
+ />
138
+ )}
139
+ {yKeys.map((key, index) => (
140
+ <Area
141
+ id={key}
142
+ key={key}
143
+ type="linear"
144
+ dataKey={key}
145
+ stroke={colors[index]}
146
+ fill="none"
147
+ connectNulls
148
+ dot={false}
149
+ activeDot={{ r: 4 }}
150
+ isAnimationActive={false}
151
+ />
152
+ ))}
153
+ </RechartsAreaChart>
154
+ </ResponsiveContainer>
155
+ </div>
156
+ )
157
+ }
158
+
159
+ AreaChart.propTypes = {
160
+ // eslint-disable-next-line react/forbid-prop-types
161
+ data :PropTypes.arrayOf(PropTypes.object).isRequired,
162
+ title :PropTypes.string.isRequired,
163
+ xKey :PropTypes.string.isRequired,
164
+ yKeys :PropTypes.arrayOf(PropTypes.string).isRequired,
165
+ xLabel:PropTypes.string,
166
+ yLabel:PropTypes.string,
167
+ colors:PropTypes.arrayOf(PropTypes.string).isRequired,
168
+ filled:PropTypes.bool,
169
+ height:PropTypes.oneOfType([
170
+ PropTypes.string,
171
+ PropTypes.number,
172
+ ]),
173
+ width:PropTypes.oneOfType([
174
+ PropTypes.string,
175
+ PropTypes.number,
176
+ ]),
177
+ }
178
+
179
+ AreaChart.defaultProps = {
180
+ filled:false,
181
+ width :'100%',
182
+ height:300,
183
+ }
184
+
185
+ export default AreaChart
@@ -0,0 +1 @@
1
+ export { default as AreaChart } from './AreaChart'
@@ -0,0 +1,89 @@
1
+ @use "@pareto-engineering/bem";
2
+
3
+ $default-margin: 1rem;
4
+ $default-padding: 1rem;
5
+ $default-box-shadow: 0 .25rem .75rem var(--ui-lines);
6
+ $default-text-font-size: calc(var(--s-1) * 1rem);
7
+ $default-border-radius: .25rem;
8
+ $default-legend-gap: .625rem;
9
+ $default-legend-padding: calc($default-padding * .125) calc($default-padding * .625);
10
+ $default-legend-line-width: 1.25rem;
11
+ $default-legend-line-height: .125rem;
12
+ $default-legend-line-margin-right: .3125rem;
13
+ $default-border-line-width: .0625rem;
14
+
15
+ .#{bem.$base} {
16
+ &.area-chart {
17
+ background-color: var(--background-far);
18
+ border-radius: var(--theme-default-border-radius);
19
+ box-shadow: $default-box-shadow;
20
+ margin: $default-margin 0;
21
+ padding: $default-padding;
22
+
23
+ h3 {
24
+ color: var(--subtitle);
25
+ margin: calc($default-margin / 5);
26
+ text-align: left;
27
+ }
28
+
29
+ .custom-legend {
30
+ display: flex;
31
+ gap: $default-legend-gap;
32
+ justify-content: flex-end;
33
+ padding-bottom: $default-padding;
34
+ padding-right: calc($default-padding * .25);
35
+
36
+ .item {
37
+ align-items: center;
38
+ border: $default-border-line-width solid var(--ui-lines);
39
+ border-radius: $default-border-radius;
40
+ display: flex;
41
+ padding: $default-legend-padding;
42
+ }
43
+
44
+ .line {
45
+ display: inline-block;
46
+ height: $default-legend-line-height;
47
+ margin-right: $default-legend-line-margin-right;
48
+ width: $default-legend-line-width;
49
+ }
50
+
51
+ .text {
52
+ color: var(--paragraph);
53
+ font-size: calc($default-text-font-size * .75);
54
+ }
55
+ }
56
+
57
+ .custom-tooltip {
58
+ background-color: var(--background-far);
59
+ border: $default-border-line-width solid var(--ui-lines);
60
+ border-radius: $default-border-radius;
61
+ padding: calc($default-padding * .25);
62
+
63
+ .label {
64
+ color: var(--hard-paragraph);
65
+ font-size: $default-text-font-size;
66
+ margin: calc($default-margin * .25);
67
+ }
68
+ }
69
+
70
+ /* stylelint-disable selector-max-compound-selectors -- nested elements */
71
+ .recharts-wrapper {
72
+ .recharts-surface {
73
+ .recharts-cartesian-grid line {
74
+ stroke: var(--ui-lines);
75
+ }
76
+
77
+ .recharts-text {
78
+ fill: var(--soft-paragraph);
79
+ font-size: calc($default-text-font-size * .75);
80
+ }
81
+
82
+ .recharts-text.recharts-label {
83
+ fill: var(--paragraph);
84
+ font-size: $default-text-font-size;
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
@@ -23,6 +23,8 @@ import {
23
23
  import {
24
24
  defaultKeymap,
25
25
  indentWithTab,
26
+ history,
27
+ historyKeymap,
26
28
  } from '@codemirror/commands'
27
29
 
28
30
  import {
@@ -65,7 +67,7 @@ const XMLEditor = ({
65
67
  const startState = EditorState.create({
66
68
  doc :config,
67
69
  extensions:[
68
- keymap.of([defaultKeymap, indentWithTab]),
70
+ keymap.of([defaultKeymap, indentWithTab, ...historyKeymap]),
69
71
  indentOnInput(),
70
72
  lineNumbers(),
71
73
  bracketMatching(),
@@ -78,6 +80,7 @@ const XMLEditor = ({
78
80
  rectangularSelection(),
79
81
  crosshairCursor(),
80
82
  xml(),
83
+ history(),
81
84
  theme,
82
85
  EditorState.readOnly.of(readOnly),
83
86
  EditorView.updateListener.of((view) => {
package/src/ui/a/index.js CHANGED
@@ -30,3 +30,4 @@ export { ToggleSwitch } from './ToggleSwitch'
30
30
  export { XMLEditor } from './XMLEditor'
31
31
  export { DatePicker } from './DatePicker'
32
32
  export { Tooltip } from './Tooltip'
33
+ export { AreaChart } from './AreaChart'
@@ -124,6 +124,7 @@ $default-animation-time: .31s;
124
124
  }
125
125
 
126
126
  &.#{bem.$modifier-simple} {
127
+ --stroke-color: var(--x, var(--#{$default-color}));
127
128
  background: transparent;
128
129
  border: 1px solid transparent;
129
130
  color: var(--x, var(--#{$default-color}));
@@ -29,8 +29,8 @@ const FormBuilder = ({
29
29
  formBuilderId,
30
30
  onBuilderFormSave,
31
31
  onBuilderError,
32
- onRendererError,
33
32
  onRendererFormSave,
33
+ onFileUpload,
34
34
  onBuilderValidate,
35
35
  initialBuilderValues,
36
36
  fileUploadStatus,
@@ -75,9 +75,9 @@ const FormBuilder = ({
75
75
  onSave={onRendererFormSave}
76
76
  readOnly={readOnly}
77
77
  shouldSubmit={shouldSubmit}
78
- onError={onRendererError}
79
78
  fileUploadStatus={fileUploadStatus}
80
79
  handleFileDelete={handleFileDelete}
80
+ onFileUpload={onFileUpload}
81
81
  />
82
82
  )}
83
83
  </div>
@@ -1,7 +1,7 @@
1
1
  /* @pareto-engineering/generator-front 1.1.1-alpha.2 */
2
2
  import * as React from 'react'
3
3
 
4
- import { useState, useEffect } from 'react'
4
+ import { useState, useEffect, useRef } from 'react'
5
5
 
6
6
  import { Formik, Form } from 'formik'
7
7
 
@@ -21,31 +21,6 @@ const baseClassName = styleNames.base
21
21
 
22
22
  const componentClassName = 'renderer'
23
23
 
24
- const reconstructFormDataWithValues = (formData, values) => {
25
- const valuesMap = {}
26
- Object.keys(values).forEach(async (key) => {
27
- if (key.includes('files')) {
28
- const files = values[key].map((file) => (
29
- file instanceof File ? URL.createObjectURL(file) : file
30
- ))
31
- valuesMap[key] = files
32
- } else {
33
- valuesMap[key] = values[key]
34
- }
35
- })
36
- const newData = {
37
- ...formData,
38
- sections:formData.sections.map((section) => ({
39
- ...section,
40
- inputs:section.inputs.map((input) => ({
41
- ...input,
42
- value:valuesMap[input.name] !== undefined ? valuesMap[input.name] : input.value,
43
- })),
44
- })),
45
- }
46
- return newData
47
- }
48
-
49
24
  const validate = (currentSectionIndex, formData, values) => {
50
25
  const errors = {}
51
26
 
@@ -68,6 +43,26 @@ const validate = (currentSectionIndex, formData, values) => {
68
43
  return errors
69
44
  }
70
45
 
46
+ const reconstructFormDataWithValues = (formData, values) => {
47
+ const valuesMap = {}
48
+ Object.keys(values).forEach(async (key) => {
49
+ if (!key.includes('files')) {
50
+ valuesMap[key] = values[key]
51
+ }
52
+ })
53
+ const newData = {
54
+ ...formData,
55
+ sections:formData.sections.map((section) => ({
56
+ ...section,
57
+ inputs:section.inputs.map((input) => ({
58
+ ...input,
59
+ value:valuesMap[input.name] !== undefined ? valuesMap[input.name] : input.value,
60
+ })),
61
+ })),
62
+ }
63
+ return newData
64
+ }
65
+
71
66
  /**
72
67
  * This is the component description.
73
68
  */
@@ -78,15 +73,16 @@ const Renderer = ({
78
73
  formData,
79
74
  readOnly,
80
75
  onSave,
81
- onError,
82
76
  shouldSubmit,
83
77
  fileUploadStatus,
84
78
  handleFileDelete,
79
+ onFileUpload,
80
+ shouldUpdateInputStateInRealTime = true,
85
81
  // ...otherProps
86
82
  }) => {
87
83
  const [currentSectionIndex, setCurrentSectionIndex] = useState(0)
88
84
  const [sectionHistory, setSectionHistory] = useState([])
89
- const [updatedFormData, setUpdatedFormData] = useState(formData)
85
+ const [updatedFormData, setUpdatedFormData] = useState({ ...formData })
90
86
 
91
87
  useEffect(() => {
92
88
  setUpdatedFormData(formData)
@@ -135,6 +131,13 @@ const Renderer = ({
135
131
  const isSubmit = currentSectionIndex === updatedFormData.sections.length - 1
136
132
  || updatedFormData.sections[currentSectionIndex].navigation.nextSection === 'submit'
137
133
 
134
+ const ref = useRef(null)
135
+
136
+ const currentSectionInputs = updatedFormData.sections[currentSectionIndex].inputs
137
+ const hasErrorsOnInitialRender = currentSectionInputs
138
+ .some((input) => (input.required && !ref.current?.values[input.name])
139
+ || ref.current?.errors[input.name])
140
+
138
141
  return (
139
142
  <div
140
143
  id={id}
@@ -151,21 +154,18 @@ const Renderer = ({
151
154
  <Formik
152
155
  initialValues={initialValues}
153
156
  onSubmit={handleSubmit}
154
- validate={(values) => validate(currentSectionIndex, formData, values)}
157
+ validate={(values) => validate(currentSectionIndex, updatedFormData, values)}
158
+ innerRef={ref}
155
159
  >
156
160
  {({ values, errors }) => {
157
161
  useEffect(() => {
158
- const formDataWithValues = reconstructFormDataWithValues(updatedFormData, values)
159
- setUpdatedFormData(formDataWithValues)
160
- onSave?.(formDataWithValues)
162
+ if (shouldUpdateInputStateInRealTime) {
163
+ const formDataWithValues = reconstructFormDataWithValues(updatedFormData, values)
164
+ onSave?.(formDataWithValues)
165
+ }
161
166
  }, [values])
162
167
 
163
- useEffect(() => {
164
- onError?.({ errors, values })
165
- }, [errors, values])
166
-
167
168
  const hasErrors = Object.keys(errors).length > 0
168
-
169
169
  return (
170
170
  <Form>
171
171
  {updatedFormData.sections.map((section, sectionIndex) => (
@@ -174,9 +174,10 @@ const Renderer = ({
174
174
  key={`${section.title}`}
175
175
  {...section}
176
176
  readOnly={readOnly}
177
- setUpdatedFormData={setUpdatedFormData}
178
177
  fileUploadStatus={fileUploadStatus}
179
178
  handleFileDelete={handleFileDelete}
179
+ onFileUpload={onFileUpload}
180
+ sectionIndex={sectionIndex}
180
181
  />
181
182
  )
182
183
  ))}
@@ -197,7 +198,7 @@ const Renderer = ({
197
198
  )
198
199
  }
199
200
  {(!isSubmit || shouldSubmit) && (
200
- <Button color="interactive" isGradient type="submit" disabled={hasErrors}>
201
+ <Button color="interactive" isGradient type="submit" disabled={hasErrors || hasErrorsOnInitialRender}>
201
202
  {isSubmit ? 'Submit' : 'Next'}
202
203
  </Button>
203
204
  )}
@@ -6,7 +6,7 @@ import PropTypes from 'prop-types'
6
6
 
7
7
  // Local Definitions
8
8
 
9
- import { FormInput } from 'ui/f'
9
+ import { FormInput, getFileType } from 'ui/f'
10
10
 
11
11
  import { ExpandableLexicalPreview } from 'ui/g'
12
12
 
@@ -16,6 +16,15 @@ const baseClassName = styleNames.base
16
16
 
17
17
  const componentClassName = 'section'
18
18
 
19
+ const fileTypeMapper = {
20
+ VID :'Video',
21
+ TXT :'Text',
22
+ IMG :'Image',
23
+ PDF :'PDF',
24
+ AUD :'Audio',
25
+ FILE:'Generic',
26
+ }
27
+
19
28
  /**
20
29
  * This is the component description.
21
30
  */
@@ -29,6 +38,8 @@ const Section = ({
29
38
  readOnly,
30
39
  fileUploadStatus,
31
40
  handleFileDelete,
41
+ onFileUpload,
42
+ sectionIndex,
32
43
  // ...otherProps
33
44
  }) => (
34
45
  <div
@@ -49,15 +60,35 @@ const Section = ({
49
60
  nodes={description}
50
61
  name="instructions"
51
62
  />
52
- {inputs?.map((input) => (
53
- <FormInput
54
- key={input.name}
55
- {...input}
56
- disabled={readOnly}
57
- uploadStatus={fileUploadStatus}
58
- handleFileDelete={handleFileDelete}
59
- />
60
- ))}
63
+ {inputs?.map((input, inputIndex) => {
64
+ const isFileInput = input.type === 'file'
65
+ return (
66
+ <FormInput
67
+ key={input.name}
68
+ {...input}
69
+ disabled={readOnly}
70
+ {...(isFileInput && {
71
+ uploadStatus:fileUploadStatus,
72
+ handleFileDelete,
73
+ onChange :(files) => {
74
+ const filesToUpload = files
75
+ ?.filter((file) => file instanceof File)
76
+ .map((file) => ({
77
+ file,
78
+ name :file.name,
79
+ mimeType:file.type,
80
+ type :fileTypeMapper[getFileType(file)] || 'Generic',
81
+ title :file.name,
82
+ sectionIndex,
83
+ inputIndex,
84
+ }))
85
+
86
+ onFileUpload(filesToUpload)
87
+ },
88
+ })}
89
+ />
90
+ )
91
+ })}
61
92
  </div>
62
93
  )
63
94