@centreon/ui 24.5.2 → 24.5.3

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/package.json +8 -4
  2. package/public/mockServiceWorker.js +81 -100
  3. package/src/ActionsList/index.stories.tsx +7 -1
  4. package/src/Dashboard/Dashboard.styles.ts +3 -2
  5. package/src/Dashboard/Item.tsx +11 -2
  6. package/src/Dashboard/Layout.tsx +4 -2
  7. package/src/Graph/BarStack/BarStack.stories.tsx +8 -6
  8. package/src/Graph/BarStack/ResponsiveBarStack.tsx +3 -3
  9. package/src/Graph/HeatMap/HeatMap.stories.tsx +20 -0
  10. package/src/Graph/LineChart/index.stories.tsx +1 -1
  11. package/src/Graph/PieChart/PieChart.stories.tsx +11 -15
  12. package/src/Graph/PieChart/ResponsivePie.tsx +1 -1
  13. package/src/Graph/Tree/DescendantNodes.tsx +1 -0
  14. package/src/Graph/Tree/Links.tsx +15 -2
  15. package/src/Graph/Tree/Tree.cypress.spec.tsx +24 -0
  16. package/src/Graph/Tree/Tree.stories.tsx +17 -1
  17. package/src/Graph/Tree/models.ts +3 -0
  18. package/src/TimePeriods/index.stories.tsx +7 -12
  19. package/src/TopCounterElements/TopCounterLayout.tsx +3 -4
  20. package/src/TopCounterElements/useCloseOnLegacyPage.tsx +9 -6
  21. package/src/api/QueryProvider.tsx +1 -1
  22. package/src/api/logger.ts +11 -0
  23. package/src/api/useFetchQuery/index.test.ts +0 -5
  24. package/src/api/useFetchQuery/index.ts +2 -5
  25. package/src/api/useGraphQuery/index.ts +7 -1
  26. package/src/api/useMutationQuery/index.ts +2 -5
  27. package/src/api/useRequest/index.test.ts +0 -3
  28. package/src/api/useRequest/index.ts +3 -6
  29. package/src/components/Form/AccessRights/AccessRights.cypress.spec.tsx +27 -13
  30. package/src/components/Form/AccessRights/AccessRights.stories.tsx +0 -19
  31. package/src/components/Form/AccessRights/AccessRights.styles.ts +1 -1
  32. package/src/components/Form/AccessRights/AccessRights.tsx +6 -5
  33. package/src/components/Form/AccessRights/Actions/Actions.styles.ts +3 -7
  34. package/src/components/Form/AccessRights/Actions/Actions.tsx +15 -32
  35. package/src/components/Form/AccessRights/Actions/useActions.ts +4 -37
  36. package/src/components/Form/AccessRights/models.ts +0 -3
  37. package/src/components/Form/AccessRights/storiesData.ts +0 -3
  38. package/src/components/Form/AccessRights/useAccessRightsChange.ts +30 -0
  39. package/src/components/Form/AccessRights/utils.ts +18 -0
  40. package/src/components/Form/Dashboard/translatedLabels.ts +0 -1
  41. package/src/components/List/Item/ListItem.styles.ts +2 -2
  42. package/src/components/Tabs/Tab.styles.ts +25 -0
  43. package/src/components/Tabs/TabPanel.tsx +22 -0
  44. package/src/components/Tabs/Tabs.cypress.spec.tsx +70 -0
  45. package/src/components/Tabs/Tabs.stories.tsx +55 -0
  46. package/src/components/Tabs/Tabs.tsx +55 -0
  47. package/src/components/Tabs/index.ts +6 -0
  48. package/src/components/Zoom/Minimap.tsx +4 -2
  49. package/src/components/Zoom/Zoom.cypress.spec.tsx +13 -13
  50. package/src/components/Zoom/Zoom.tsx +4 -1
  51. package/src/components/Zoom/ZoomContent.tsx +5 -2
  52. package/src/components/index.ts +1 -0
  53. package/src/index.ts +1 -1
  54. package/src/utils/index.ts +1 -0
  55. package/src/utils/resourcesStatusURL.ts +166 -0
  56. package/src/utils/usePluralizedTranslation.test.ts +159 -0
  57. package/src/utils/usePluralizedTranslation.ts +20 -3
  58. package/src/components/Form/Dashboard/DashboardForm.stories.ts +0 -39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centreon/ui",
3
- "version": "24.5.2",
3
+ "version": "24.5.3",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "update:deps": "pnpx npm-check-updates -i --format group",
@@ -53,6 +53,8 @@
53
53
  "@cypress/webpack-dev-server": "^3.7.4",
54
54
  "@faker-js/faker": "^8.4.1",
55
55
  "@mdx-js/react": "^3.0.1",
56
+ "@modern-js/prod-server": "^2.49.2",
57
+ "@modern-js/storybook": "^2.49.2",
56
58
  "@simonsmith/cypress-image-snapshot": "^9.0.1",
57
59
  "@storybook/addon-a11y": "^7.0.9",
58
60
  "@storybook/addon-docs": "^7.0.9",
@@ -60,7 +62,7 @@
60
62
  "@storybook/addon-interactions": "^7.0.9",
61
63
  "@storybook/addon-styling": "^1.0.6",
62
64
  "@storybook/blocks": "^7.0.26",
63
- "@storybook/builder-vite": "^7.0.9",
65
+ "@storybook/builder-vite": "^7.6.18",
64
66
  "@storybook/jest": "^0.1.0",
65
67
  "@storybook/manager-api": "^7.0.9",
66
68
  "@storybook/mdx2-csf": "^1.1.0",
@@ -102,7 +104,7 @@
102
104
  "use-resize-observer": "^9.1.0",
103
105
  "vite": "^4.3.5",
104
106
  "vite-plugin-istanbul": "^5.0.0",
105
- "vite-plugin-svgr": "^3.2.0",
107
+ "vite-plugin-svgr": "^3.3.0",
106
108
  "vite-plugin-turbosnap": "^1.0.3"
107
109
  },
108
110
  "dependencies": {
@@ -169,6 +171,8 @@
169
171
  "output": "./junit.xml"
170
172
  },
171
173
  "msw": {
172
- "workerDirectory": "public"
174
+ "workerDirectory": [
175
+ "public"
176
+ ]
173
177
  }
174
178
  }
@@ -2,13 +2,15 @@
2
2
  /* tslint:disable */
3
3
 
4
4
  /**
5
- * Mock Service Worker (1.3.2).
5
+ * Mock Service Worker.
6
6
  * @see https://github.com/mswjs/msw
7
7
  * - Please do NOT modify this file.
8
8
  * - Please do NOT serve this file on production.
9
9
  */
10
10
 
11
- const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70'
11
+ const PACKAGE_VERSION = '2.2.14'
12
+ const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423'
13
+ const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
12
14
  const activeClientIds = new Set()
13
15
 
14
16
  self.addEventListener('install', function () {
@@ -47,7 +49,10 @@ self.addEventListener('message', async function (event) {
47
49
  case 'INTEGRITY_CHECK_REQUEST': {
48
50
  sendToClient(client, {
49
51
  type: 'INTEGRITY_CHECK_RESPONSE',
50
- payload: INTEGRITY_CHECKSUM,
52
+ payload: {
53
+ packageVersion: PACKAGE_VERSION,
54
+ checksum: INTEGRITY_CHECKSUM,
55
+ },
51
56
  })
52
57
  break
53
58
  }
@@ -86,12 +91,6 @@ self.addEventListener('message', async function (event) {
86
91
 
87
92
  self.addEventListener('fetch', function (event) {
88
93
  const { request } = event
89
- const accept = request.headers.get('accept') || ''
90
-
91
- // Bypass server-sent events.
92
- if (accept.includes('text/event-stream')) {
93
- return
94
- }
95
94
 
96
95
  // Bypass navigation requests.
97
96
  if (request.mode === 'navigate') {
@@ -112,29 +111,8 @@ self.addEventListener('fetch', function (event) {
112
111
  }
113
112
 
114
113
  // Generate unique request ID.
115
- const requestId = Math.random().toString(16).slice(2)
116
-
117
- event.respondWith(
118
- handleRequest(event, requestId).catch((error) => {
119
- if (error.name === 'NetworkError') {
120
- console.warn(
121
- '[MSW] Successfully emulated a network error for the "%s %s" request.',
122
- request.method,
123
- request.url,
124
- )
125
- return
126
- }
127
-
128
- // At this point, any exception indicates an issue with the original request/response.
129
- console.error(
130
- `\
131
- [MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
132
- request.method,
133
- request.url,
134
- `${error.name}: ${error.message}`,
135
- )
136
- }),
137
- )
114
+ const requestId = crypto.randomUUID()
115
+ event.respondWith(handleRequest(event, requestId))
138
116
  })
139
117
 
140
118
  async function handleRequest(event, requestId) {
@@ -146,21 +124,24 @@ async function handleRequest(event, requestId) {
146
124
  // this message will pend indefinitely.
147
125
  if (client && activeClientIds.has(client.id)) {
148
126
  ;(async function () {
149
- const clonedResponse = response.clone()
150
- sendToClient(client, {
151
- type: 'RESPONSE',
152
- payload: {
153
- requestId,
154
- type: clonedResponse.type,
155
- ok: clonedResponse.ok,
156
- status: clonedResponse.status,
157
- statusText: clonedResponse.statusText,
158
- body:
159
- clonedResponse.body === null ? null : await clonedResponse.text(),
160
- headers: Object.fromEntries(clonedResponse.headers.entries()),
161
- redirected: clonedResponse.redirected,
127
+ const responseClone = response.clone()
128
+
129
+ sendToClient(
130
+ client,
131
+ {
132
+ type: 'RESPONSE',
133
+ payload: {
134
+ requestId,
135
+ isMockedResponse: IS_MOCKED_RESPONSE in response,
136
+ type: responseClone.type,
137
+ status: responseClone.status,
138
+ statusText: responseClone.statusText,
139
+ body: responseClone.body,
140
+ headers: Object.fromEntries(responseClone.headers.entries()),
141
+ },
162
142
  },
163
- })
143
+ [responseClone.body],
144
+ )
164
145
  })()
165
146
  }
166
147
 
@@ -196,20 +177,20 @@ async function resolveMainClient(event) {
196
177
 
197
178
  async function getResponse(event, client, requestId) {
198
179
  const { request } = event
199
- const clonedRequest = request.clone()
180
+
181
+ // Clone the request because it might've been already used
182
+ // (i.e. its body has been read and sent to the client).
183
+ const requestClone = request.clone()
200
184
 
201
185
  function passthrough() {
202
- // Clone the request because it might've been already used
203
- // (i.e. its body has been read and sent to the client).
204
- const headers = Object.fromEntries(clonedRequest.headers.entries())
186
+ const headers = Object.fromEntries(requestClone.headers.entries())
205
187
 
206
- // Remove MSW-specific request headers so the bypassed requests
207
- // comply with the server's CORS preflight check.
208
- // Operate with the headers as an object because request "Headers"
209
- // are immutable.
210
- delete headers['x-msw-bypass']
188
+ // Remove internal MSW request header so the passthrough request
189
+ // complies with any potential CORS preflight checks on the server.
190
+ // Some servers forbid unknown request headers.
191
+ delete headers['x-msw-intention']
211
192
 
212
- return fetch(clonedRequest, { headers })
193
+ return fetch(requestClone, { headers })
213
194
  }
214
195
 
215
196
  // Bypass mocking when the client is not active.
@@ -225,57 +206,46 @@ async function getResponse(event, client, requestId) {
225
206
  return passthrough()
226
207
  }
227
208
 
228
- // Bypass requests with the explicit bypass header.
229
- // Such requests can be issued by "ctx.fetch()".
230
- if (request.headers.get('x-msw-bypass') === 'true') {
231
- return passthrough()
232
- }
233
-
234
209
  // Notify the client that a request has been intercepted.
235
- const clientMessage = await sendToClient(client, {
236
- type: 'REQUEST',
237
- payload: {
238
- id: requestId,
239
- url: request.url,
240
- method: request.method,
241
- headers: Object.fromEntries(request.headers.entries()),
242
- cache: request.cache,
243
- mode: request.mode,
244
- credentials: request.credentials,
245
- destination: request.destination,
246
- integrity: request.integrity,
247
- redirect: request.redirect,
248
- referrer: request.referrer,
249
- referrerPolicy: request.referrerPolicy,
250
- body: await request.text(),
251
- bodyUsed: request.bodyUsed,
252
- keepalive: request.keepalive,
210
+ const requestBuffer = await request.arrayBuffer()
211
+ const clientMessage = await sendToClient(
212
+ client,
213
+ {
214
+ type: 'REQUEST',
215
+ payload: {
216
+ id: requestId,
217
+ url: request.url,
218
+ mode: request.mode,
219
+ method: request.method,
220
+ headers: Object.fromEntries(request.headers.entries()),
221
+ cache: request.cache,
222
+ credentials: request.credentials,
223
+ destination: request.destination,
224
+ integrity: request.integrity,
225
+ redirect: request.redirect,
226
+ referrer: request.referrer,
227
+ referrerPolicy: request.referrerPolicy,
228
+ body: requestBuffer,
229
+ keepalive: request.keepalive,
230
+ },
253
231
  },
254
- })
232
+ [requestBuffer],
233
+ )
255
234
 
256
235
  switch (clientMessage.type) {
257
236
  case 'MOCK_RESPONSE': {
258
237
  return respondWithMock(clientMessage.data)
259
238
  }
260
239
 
261
- case 'MOCK_NOT_FOUND': {
240
+ case 'PASSTHROUGH': {
262
241
  return passthrough()
263
242
  }
264
-
265
- case 'NETWORK_ERROR': {
266
- const { name, message } = clientMessage.data
267
- const networkError = new Error(message)
268
- networkError.name = name
269
-
270
- // Rejecting a "respondWith" promise emulates a network error.
271
- throw networkError
272
- }
273
243
  }
274
244
 
275
245
  return passthrough()
276
246
  }
277
247
 
278
- function sendToClient(client, message) {
248
+ function sendToClient(client, message, transferrables = []) {
279
249
  return new Promise((resolve, reject) => {
280
250
  const channel = new MessageChannel()
281
251
 
@@ -287,17 +257,28 @@ function sendToClient(client, message) {
287
257
  resolve(event.data)
288
258
  }
289
259
 
290
- client.postMessage(message, [channel.port2])
260
+ client.postMessage(
261
+ message,
262
+ [channel.port2].concat(transferrables.filter(Boolean)),
263
+ )
291
264
  })
292
265
  }
293
266
 
294
- function sleep(timeMs) {
295
- return new Promise((resolve) => {
296
- setTimeout(resolve, timeMs)
267
+ async function respondWithMock(response) {
268
+ // Setting response status code to 0 is a no-op.
269
+ // However, when responding with a "Response.error()", the produced Response
270
+ // instance will have status code set to 0. Since it's not possible to create
271
+ // a Response instance with status code 0, handle that use-case separately.
272
+ if (response.status === 0) {
273
+ return Response.error()
274
+ }
275
+
276
+ const mockedResponse = new Response(response.body, response)
277
+
278
+ Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
279
+ value: true,
280
+ enumerable: true,
297
281
  })
298
- }
299
282
 
300
- async function respondWithMock(response) {
301
- await sleep(response.delay)
302
- return new Response(response.body, response)
283
+ return mockedResponse
303
284
  }
@@ -4,6 +4,8 @@ import DeleteIcon from '@mui/icons-material/Delete';
4
4
  import EditIcon from '@mui/icons-material/Edit';
5
5
  import CopyIcon from '@mui/icons-material/ContentCopy';
6
6
 
7
+ import { ActionVariants } from './models';
8
+
7
9
  import ActionsList from '.';
8
10
 
9
11
  const actions = [
@@ -24,7 +26,11 @@ const actions = [
24
26
  }
25
27
  ];
26
28
 
27
- const actionsWithVariants = [
29
+ const actionsWithVariants: Array<{
30
+ label: string;
31
+ onClick: () => void;
32
+ variant?: ActionVariants;
33
+ }> = [
28
34
  {
29
35
  label: 'No variant',
30
36
  onClick: (): void => undefined
@@ -17,7 +17,7 @@ export const useDashboardLayoutStyles = makeStyles<boolean>()(
17
17
  boxShadow: theme.shadows[3]
18
18
  },
19
19
  '& .react-grid-item.react-grid-placeholder': {
20
- backgroundColor: alpha(theme.palette.primary.main, 0.7)
20
+ backgroundColor: alpha(theme.palette.primary.main, 0.2)
21
21
  },
22
22
  '& .react-grid-item.resizing': {
23
23
  boxShadow: theme.shadows[3]
@@ -61,7 +61,8 @@ export const useDashboardLayoutStyles = makeStyles<boolean>()(
61
61
  },
62
62
  '& .react-resizable-handle:hover': {
63
63
  opacity: 1
64
- }
64
+ },
65
+ position: 'relative'
65
66
  }
66
67
  })
67
68
  );
@@ -15,6 +15,7 @@ import { useMemoComponent } from '../utils';
15
15
  import { useDashboardItemStyles } from './Dashboard.styles';
16
16
 
17
17
  interface DashboardItemProps {
18
+ additionalMemoProps?: Array<unknown>;
18
19
  canMove?: boolean;
19
20
  children: ReactElement;
20
21
  className?: string;
@@ -39,7 +40,8 @@ const Item = forwardRef<HTMLDivElement, DashboardItemProps>(
39
40
  onTouchEnd,
40
41
  id,
41
42
  disablePadding = false,
42
- canMove = false
43
+ canMove = false,
44
+ additionalMemoProps = []
43
45
  }: DashboardItemProps,
44
46
  ref: ForwardedRef<HTMLDivElement>
45
47
  ): ReactElement => {
@@ -92,7 +94,14 @@ const Item = forwardRef<HTMLDivElement, DashboardItemProps>(
92
94
  </Card>
93
95
  </div>
94
96
  ),
95
- memoProps: [style, className, header, theme.palette.mode, canMove]
97
+ memoProps: [
98
+ style,
99
+ className,
100
+ header,
101
+ theme.palette.mode,
102
+ canMove,
103
+ ...additionalMemoProps
104
+ ]
96
105
  });
97
106
  }
98
107
  );
@@ -16,6 +16,7 @@ import Grid from './Grid';
16
16
  const ReactGridLayout = WidthProvider(GridLayout);
17
17
 
18
18
  interface DashboardLayoutProps<T> {
19
+ additionalMemoProps?: Array<unknown>;
19
20
  changeLayout?: (newLayout: Array<Layout>) => void;
20
21
  children: Array<JSX.Element>;
21
22
  displayGrid?: boolean;
@@ -28,7 +29,8 @@ const DashboardLayout = <T extends Layout>({
28
29
  changeLayout,
29
30
  displayGrid,
30
31
  layout,
31
- isStatic = false
32
+ isStatic = false,
33
+ additionalMemoProps = []
32
34
  }: DashboardLayoutProps<T>): JSX.Element => {
33
35
  const { classes } = useDashboardLayoutStyles(isStatic);
34
36
 
@@ -72,7 +74,7 @@ const DashboardLayout = <T extends Layout>({
72
74
  </ParentSize>
73
75
  </ResponsiveHeight>
74
76
  ),
75
- memoProps: [columns, layout, displayGrid, isStatic]
77
+ memoProps: [columns, layout, displayGrid, isStatic, ...additionalMemoProps]
76
78
  });
77
79
  };
78
80
 
@@ -1,6 +1,7 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
2
 
3
3
  import { BarType } from './models';
4
+ import ResponsiveBarStack from './ResponsiveBarStack';
4
5
 
5
6
  import { BarStack } from '.';
6
7
 
@@ -26,7 +27,12 @@ const dataWithSmallNumber = [
26
27
  ];
27
28
 
28
29
  const meta: Meta<typeof BarStack> = {
29
- component: BarStack
30
+ component: BarStack,
31
+ parameters: {
32
+ chromatic: {
33
+ delay: 1000
34
+ }
35
+ }
30
36
  };
31
37
 
32
38
  export default meta;
@@ -41,11 +47,7 @@ const TooltipContent = ({ label, color, value }: BarType): JSX.Element => {
41
47
  };
42
48
 
43
49
  const Template = (args): JSX.Element => {
44
- return (
45
- <div style={{ height: '300px', width: '500px' }}>
46
- <BarStack {...args} />
47
- </div>
48
- );
50
+ return <ResponsiveBarStack height={300} width={500} {...args} />;
49
51
  };
50
52
 
51
53
  export const Vertical: Story = {
@@ -20,7 +20,7 @@ const DefaultLengd = ({ scale, direction }: LegendProps): JSX.Element => (
20
20
  <LegendComponent direction={direction} scale={scale} />
21
21
  );
22
22
 
23
- const BarStack = ({
23
+ const ResponsiveBarStack = ({
24
24
  title,
25
25
  data,
26
26
  width,
@@ -73,7 +73,7 @@ const BarStack = ({
73
73
  <div
74
74
  className={classes.svgWrapper}
75
75
  style={{
76
- height,
76
+ minHeight: height,
77
77
  width: svgWrapperWidth
78
78
  }}
79
79
  >
@@ -206,4 +206,4 @@ const BarStack = ({
206
206
  );
207
207
  };
208
208
 
209
- export default BarStack;
209
+ export default ResponsiveBarStack;
@@ -55,6 +55,26 @@ const Template = (args): JSX.Element => {
55
55
  return <HeatMap {...args} arrowClassName={classes.arrow} />;
56
56
  };
57
57
 
58
+ const TileContent = ({
59
+ data,
60
+ backgroundColor
61
+ }: {
62
+ backgroundColor: string;
63
+ data: Data;
64
+ }): JSX.Element => (
65
+ <div
66
+ style={{
67
+ alignItems: 'center',
68
+ backgroundColor,
69
+ display: 'flex',
70
+ height: '100%',
71
+ justifyContent: 'center'
72
+ }}
73
+ >
74
+ {data.counter}
75
+ </div>
76
+ );
77
+
58
78
  export const normal: Story = {
59
79
  args: {
60
80
  children: TileContent,
@@ -354,7 +354,7 @@ export const LineChartWithTimePeriod: Story = {
354
354
  start: defaultStart
355
355
  },
356
356
  parameters: {
357
- chromatic: { diffThreshold: 0.1 }
357
+ chromatic: { disableSnapshot: true }
358
358
  }
359
359
  };
360
360
 
@@ -1,8 +1,7 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
2
 
3
3
  import { ArcType } from './models';
4
-
5
- import { PieChart } from '.';
4
+ import ResponsivePie from './ResponsivePie';
6
5
 
7
6
  const data = [
8
7
  { color: '#88B922', label: 'Ok', value: 148 },
@@ -18,19 +17,20 @@ const dataWithBigNumbers = [
18
17
  { color: '#FF6666', label: 'Down', value: 122222 }
19
18
  ];
20
19
 
21
- const meta: Meta<typeof PieChart> = {
22
- component: PieChart
20
+ const meta: Meta<typeof ResponsivePie> = {
21
+ component: ResponsivePie,
22
+ parameters: {
23
+ chromatic: {
24
+ delay: 1000
25
+ }
26
+ }
23
27
  };
24
28
 
25
29
  export default meta;
26
- type Story = StoryObj<typeof PieChart>;
30
+ type Story = StoryObj<typeof ResponsivePie>;
27
31
 
28
32
  const Template = (args): JSX.Element => {
29
- return (
30
- <div style={{ height: '350px', width: '350px' }}>
31
- <PieChart {...args} />
32
- </div>
33
- );
33
+ return <ResponsivePie height={350} width={350} {...args} />;
34
34
  };
35
35
 
36
36
  export const Pie: Story = {
@@ -141,11 +141,7 @@ export const DonutWithTooltip: Story = {
141
141
  };
142
142
 
143
143
  const TemplateForSmallDimensions = (args): JSX.Element => {
144
- return (
145
- <div style={{ height: '130px', width: '130px' }}>
146
- <PieChart {...args} />
147
- </div>
148
- );
144
+ return <ResponsivePie height={130} width={130} {...args} />;
149
145
  };
150
146
 
151
147
  export const PieWithSmallDimensions: Story = {
@@ -93,7 +93,7 @@ const ResponsivePie = ({
93
93
  <div
94
94
  className={classes.svgWrapper}
95
95
  style={{
96
- height,
96
+ minHeight: height,
97
97
  width: svgWrapperWidth
98
98
  }}
99
99
  >
@@ -65,6 +65,7 @@ const DescendantNodes = <TData extends BaseProp>({
65
65
  <Group key={key} left={left} top={top}>
66
66
  <foreignObject
67
67
  height={nodeSize.height}
68
+ style={{ userSelect: 'none' }}
68
69
  width={nodeSize.width}
69
70
  x={-nodeSize.width / 2}
70
71
  y={-nodeSize.height / 2}
@@ -1,5 +1,10 @@
1
- import { LinkHorizontal } from '@visx/shape';
1
+ import {
2
+ LinkHorizontal,
3
+ LinkHorizontalStep,
4
+ LinkHorizontalLine
5
+ } from '@visx/shape';
2
6
  import { HierarchyPointLink } from '@visx/hierarchy/lib/types';
7
+ import { always, cond, equals, T } from 'ramda';
3
8
 
4
9
  import { useTheme } from '@mui/material';
5
10
 
@@ -9,6 +14,12 @@ interface Props<TData> extends Pick<TreeProps<TData>, 'treeLink'> {
9
14
  links: Array<HierarchyPointLink<Node<TData>>>;
10
15
  }
11
16
 
17
+ const getLinkComponent = cond([
18
+ [equals('line'), always(LinkHorizontalLine)],
19
+ [equals('step'), always(LinkHorizontalStep)],
20
+ [T, always(LinkHorizontal)]
21
+ ]);
22
+
12
23
  const Links = <TData extends BaseProp>({
13
24
  links,
14
25
  treeLink
@@ -24,10 +35,12 @@ const Links = <TData extends BaseProp>({
24
35
  .descendants()
25
36
  .map((ancestor) => ancestor.data.data.id);
26
37
 
38
+ const LinkComponent = getLinkComponent(treeLink?.type);
39
+
27
40
  const key = `${link.source.data.data.id}-${link.source.data.data.name}-${ancestorIds}_${link.target.data.data.id}-${link.target.data.data.name}-${descendantIds}`;
28
41
 
29
42
  return (
30
- <LinkHorizontal
43
+ <LinkComponent
31
44
  data={link}
32
45
  data-testid={`${link.source.data.data.id}_to_${link.target.data.data.id}`}
33
46
  fill="none"
@@ -168,4 +168,28 @@ describe('Complex data tree', () => {
168
168
 
169
169
  cy.makeSnapshot();
170
170
  });
171
+
172
+ it('displays the tree with step links when a prop is set', () => {
173
+ initializeStandaloneTree({
174
+ treeLink: {
175
+ type: 'step'
176
+ }
177
+ });
178
+
179
+ cy.contains('T').should('be.visible');
180
+
181
+ cy.makeSnapshot();
182
+ });
183
+
184
+ it('displays the tree with line links when a prop is set', () => {
185
+ initializeStandaloneTree({
186
+ treeLink: {
187
+ type: 'line'
188
+ }
189
+ });
190
+
191
+ cy.contains('T').should('be.visible');
192
+
193
+ cy.makeSnapshot();
194
+ });
171
195
  });
@@ -82,6 +82,21 @@ export const WithDefaultExpandFilter: Story = {
82
82
  render: StandaloneTreeTemplate
83
83
  };
84
84
 
85
+ export const WithStepLink: Story = {
86
+ args: {
87
+ children: SimpleContent,
88
+ node: {
89
+ height: 90,
90
+ width: 90
91
+ },
92
+ tree: simpleData,
93
+ treeLink: {
94
+ type: 'step'
95
+ }
96
+ },
97
+ render: StandaloneTreeTemplate
98
+ };
99
+
85
100
  export const WithCustomLinks: Story = {
86
101
  args: {
87
102
  children: SimpleContent,
@@ -95,7 +110,8 @@ export const WithCustomLinks: Story = {
95
110
  getStrokeDasharray: ({ target }) =>
96
111
  target.status === 'ok' ? '5,5' : '0',
97
112
  getStrokeOpacity: ({ target }) => (target.status === 'ok' ? 0.8 : 1),
98
- getStrokeWidth: ({ target }) => (target.status === 'ok' ? 1 : 2)
113
+ getStrokeWidth: ({ target }) => (target.status === 'ok' ? 1 : 2),
114
+ type: 'line'
99
115
  }
100
116
  },
101
117
  render: StandaloneTreeTemplate