@react-magma/charts 13.0.4-next.0 → 14.0.0-next.1

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 (35) hide show
  1. package/dist/charts.js +447 -27
  2. package/dist/charts.js.map +1 -1
  3. package/dist/charts.modern.module.js +443 -30
  4. package/dist/charts.modern.module.js.map +1 -1
  5. package/dist/charts.umd.js +1303 -153
  6. package/dist/charts.umd.js.map +1 -1
  7. package/dist/components/CarbonChart/CarbonChart.d.ts +41 -0
  8. package/dist/components/ChartTable/ChartDataTable.d.ts +19 -0
  9. package/dist/components/ChartTable/ChartFullscreenButton.d.ts +26 -0
  10. package/dist/components/ChartTable/ChartMoreOptionsButton.d.ts +18 -0
  11. package/dist/components/ChartTable/ChartTable.stories.d.ts +116 -0
  12. package/dist/components/ChartTable/ChartTable.test.d.ts +1 -0
  13. package/dist/components/ChartTable/ChartTableButton.d.ts +24 -0
  14. package/dist/components/ChartTable/ChartTableModal.d.ts +44 -0
  15. package/dist/components/ChartTable/ChartToolbar.d.ts +19 -0
  16. package/dist/components/ChartTable/chartToolbarI18n.d.ts +16 -0
  17. package/dist/components/ChartTable/index.d.ts +14 -0
  18. package/dist/components/LineChart/DataTable.d.ts +1 -1
  19. package/dist/index.d.ts +1 -0
  20. package/package.json +5 -5
  21. package/src/components/CarbonChart/CarbonChart.test.js +143 -2
  22. package/src/components/CarbonChart/CarbonChart.tsx +603 -15
  23. package/src/components/ChartTable/ChartDataTable.tsx +72 -0
  24. package/src/components/ChartTable/ChartFullscreenButton.tsx +59 -0
  25. package/src/components/ChartTable/ChartMoreOptionsButton.tsx +47 -0
  26. package/src/components/ChartTable/ChartTable.stories.tsx +152 -0
  27. package/src/components/ChartTable/ChartTable.test.tsx +444 -0
  28. package/src/components/ChartTable/ChartTableButton.tsx +55 -0
  29. package/src/components/ChartTable/ChartTableModal.tsx +135 -0
  30. package/src/components/ChartTable/ChartToolbar.tsx +50 -0
  31. package/src/components/ChartTable/chartToolbarI18n.ts +55 -0
  32. package/src/components/ChartTable/index.ts +23 -0
  33. package/src/components/LineChart/DataTable.tsx +3 -3
  34. package/src/components/LineChart/LineChart.tsx +1 -1
  35. package/src/index.ts +1 -0
@@ -0,0 +1,444 @@
1
+ import React from 'react';
2
+
3
+ import {
4
+ act,
5
+ render,
6
+ screen,
7
+ fireEvent,
8
+ waitFor,
9
+ } from '@testing-library/react';
10
+ import { DropdownMenuItem } from 'react-magma-dom';
11
+ import {
12
+ FullscreenExitIcon,
13
+ FullscreenIcon,
14
+ MoreVertIcon,
15
+ TableChartIcon,
16
+ } from 'react-magma-icons';
17
+
18
+ import { ChartDataTable } from './ChartDataTable';
19
+ import { ChartFullscreenButton } from './ChartFullscreenButton';
20
+ import { ChartMoreOptionsButton } from './ChartMoreOptionsButton';
21
+ import { ChartTableButton } from './ChartTableButton';
22
+ import { ChartTableModal } from './ChartTableModal';
23
+ import { ChartToolbar } from './ChartToolbar';
24
+
25
+ const dataSet = [
26
+ { group: 'High performance', value: 50 },
27
+ { group: 'Average performance', value: 30 },
28
+ { group: 'Poor performance', value: 15 },
29
+ { group: 'Not attempted', value: 5 },
30
+ ];
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // ChartDataTable
34
+ // ---------------------------------------------------------------------------
35
+ describe('ChartDataTable', () => {
36
+ it('derives column headers from dataset keys when no columns prop is given', () => {
37
+ render(<ChartDataTable dataSet={dataSet} />);
38
+
39
+ expect(
40
+ screen.getByRole('columnheader', { name: 'Group' })
41
+ ).toBeInTheDocument();
42
+ expect(
43
+ screen.getByRole('columnheader', { name: 'Value' })
44
+ ).toBeInTheDocument();
45
+ });
46
+
47
+ it('renders all data rows', () => {
48
+ render(<ChartDataTable dataSet={dataSet} />);
49
+
50
+ expect(
51
+ screen.getByRole('cell', { name: 'High performance' })
52
+ ).toBeInTheDocument();
53
+ expect(screen.getByRole('cell', { name: '50' })).toBeInTheDocument();
54
+ expect(
55
+ screen.getByRole('cell', { name: 'Not attempted' })
56
+ ).toBeInTheDocument();
57
+ expect(screen.getByRole('cell', { name: '5' })).toBeInTheDocument();
58
+ });
59
+
60
+ it('accepts custom column definitions', () => {
61
+ const columns = [
62
+ { header: 'Category', key: 'group' },
63
+ { header: 'Count', key: 'value' },
64
+ ];
65
+ render(<ChartDataTable columns={columns} dataSet={dataSet} />);
66
+
67
+ expect(
68
+ screen.getByRole('columnheader', { name: 'Category' })
69
+ ).toBeInTheDocument();
70
+ expect(
71
+ screen.getByRole('columnheader', { name: 'Count' })
72
+ ).toBeInTheDocument();
73
+ });
74
+
75
+ it('renders with isInverse without error', () => {
76
+ render(<ChartDataTable dataSet={dataSet} isInverse />);
77
+
78
+ expect(screen.getByRole('table')).toBeInTheDocument();
79
+ });
80
+ });
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // ChartTableModal
84
+ // ---------------------------------------------------------------------------
85
+ describe('ChartTableModal', () => {
86
+ it('renders a dialog with semantic heading when open', () => {
87
+ render(
88
+ <ChartTableModal
89
+ dataSet={dataSet}
90
+ isOpen
91
+ onClose={jest.fn()}
92
+ title="Overall Performance"
93
+ />
94
+ );
95
+
96
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
97
+ expect(
98
+ screen.getByRole('heading', {
99
+ level: 2,
100
+ name: 'Tabular representation Overall Performance',
101
+ })
102
+ ).toBeInTheDocument();
103
+ });
104
+
105
+ it('respects custom headerLevel', () => {
106
+ render(
107
+ <ChartTableModal
108
+ dataSet={dataSet}
109
+ headerLevel={1}
110
+ isOpen
111
+ onClose={jest.fn()}
112
+ title="Test"
113
+ />
114
+ );
115
+
116
+ expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument();
117
+ });
118
+
119
+ it('respects custom headerLabel', () => {
120
+ render(
121
+ <ChartTableModal
122
+ dataSet={dataSet}
123
+ headerLabel="Data table"
124
+ isOpen
125
+ onClose={jest.fn()}
126
+ title="My Chart"
127
+ />
128
+ );
129
+
130
+ expect(
131
+ screen.getByRole('heading', { name: 'Data table My Chart' })
132
+ ).toBeInTheDocument();
133
+ });
134
+
135
+ it('does not render dialog when closed', () => {
136
+ render(
137
+ <ChartTableModal
138
+ dataSet={dataSet}
139
+ isOpen={false}
140
+ onClose={jest.fn()}
141
+ title="Test"
142
+ />
143
+ );
144
+
145
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
146
+ });
147
+
148
+ it('renders the data table inside the modal', () => {
149
+ render(
150
+ <ChartTableModal
151
+ dataSet={dataSet}
152
+ isOpen
153
+ onClose={jest.fn()}
154
+ title="Test"
155
+ />
156
+ );
157
+
158
+ expect(
159
+ screen.getByRole('columnheader', { name: 'Group' })
160
+ ).toBeInTheDocument();
161
+ expect(
162
+ screen.getByRole('cell', { name: 'High performance' })
163
+ ).toBeInTheDocument();
164
+ });
165
+
166
+ it('calls onClose when close button is activated', async () => {
167
+ jest.useFakeTimers();
168
+ const onClose = jest.fn();
169
+ render(
170
+ <ChartTableModal
171
+ dataSet={dataSet}
172
+ isOpen
173
+ onClose={onClose}
174
+ title="Test"
175
+ />
176
+ );
177
+
178
+ fireEvent.click(screen.getByTestId('modal-closebtn'));
179
+
180
+ await act(async () => {
181
+ jest.runAllTimers();
182
+ });
183
+
184
+ expect(onClose).toHaveBeenCalledTimes(1);
185
+ jest.useRealTimers();
186
+ });
187
+
188
+ it('renders with isInverse without error', () => {
189
+ render(
190
+ <ChartTableModal
191
+ dataSet={dataSet}
192
+ isInverse
193
+ isOpen
194
+ onClose={jest.fn()}
195
+ title="Inverse Modal"
196
+ />
197
+ );
198
+
199
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
200
+ });
201
+ });
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // ChartTableButton
205
+ // ---------------------------------------------------------------------------
206
+ describe('ChartTableButton', () => {
207
+ const icon = <TableChartIcon />;
208
+
209
+ it('renders with aria-haspopup="dialog"', () => {
210
+ render(
211
+ <ChartTableButton
212
+ ariaLabel="Overall Performance"
213
+ icon={icon}
214
+ isTableOpen={false}
215
+ onClick={jest.fn()}
216
+ />
217
+ );
218
+
219
+ const button = screen.getByRole('button', { name: 'Overall Performance' });
220
+ expect(button).toHaveAttribute('aria-haspopup', 'dialog');
221
+ });
222
+
223
+ it('sets aria-expanded to false when modal is closed', () => {
224
+ render(
225
+ <ChartTableButton
226
+ ariaLabel="Overall Performance"
227
+ icon={icon}
228
+ isTableOpen={false}
229
+ onClick={jest.fn()}
230
+ />
231
+ );
232
+
233
+ expect(
234
+ screen.getByRole('button', { name: 'Overall Performance' })
235
+ ).toHaveAttribute('aria-expanded', 'false');
236
+ });
237
+
238
+ it('sets aria-expanded to true when modal is open', () => {
239
+ render(
240
+ <ChartTableButton
241
+ ariaLabel="Overall Performance"
242
+ icon={icon}
243
+ isTableOpen
244
+ onClick={jest.fn()}
245
+ />
246
+ );
247
+
248
+ expect(
249
+ screen.getByRole('button', { name: 'Overall Performance' })
250
+ ).toHaveAttribute('aria-expanded', 'true');
251
+ });
252
+
253
+ it('calls onClick when activated', () => {
254
+ const onClick = jest.fn();
255
+ render(
256
+ <ChartTableButton
257
+ ariaLabel="Overall Performance"
258
+ icon={icon}
259
+ isTableOpen={false}
260
+ onClick={onClick}
261
+ />
262
+ );
263
+
264
+ fireEvent.click(
265
+ screen.getByRole('button', { name: 'Overall Performance' })
266
+ );
267
+ expect(onClick).toHaveBeenCalledTimes(1);
268
+ });
269
+
270
+ it('renders with custom tooltip content on hover', async () => {
271
+ render(
272
+ <ChartTableButton
273
+ ariaLabel="Overall Performance"
274
+ icon={icon}
275
+ isTableOpen={false}
276
+ onClick={jest.fn()}
277
+ tooltipContent="View data as table"
278
+ />
279
+ );
280
+
281
+ const button = screen.getByRole('button', { name: 'Overall Performance' });
282
+ fireEvent.mouseEnter(button);
283
+
284
+ await waitFor(() => {
285
+ expect(screen.getByText('View data as table')).toBeInTheDocument();
286
+ });
287
+ });
288
+ });
289
+
290
+ // ---------------------------------------------------------------------------
291
+ // ChartFullscreenButton
292
+ // ---------------------------------------------------------------------------
293
+ describe('ChartFullscreenButton', () => {
294
+ it('does NOT have aria-haspopup', () => {
295
+ render(
296
+ <ChartFullscreenButton
297
+ ariaLabel="View chart in full screen"
298
+ icon={<FullscreenIcon />}
299
+ isFullscreen={false}
300
+ onClick={jest.fn()}
301
+ />
302
+ );
303
+
304
+ const button = screen.getByRole('button', {
305
+ name: 'View chart in full screen',
306
+ });
307
+ expect(button).not.toHaveAttribute('aria-haspopup');
308
+ });
309
+
310
+ it('calls onClick when activated', () => {
311
+ const onClick = jest.fn();
312
+ render(
313
+ <ChartFullscreenButton
314
+ ariaLabel="View chart in full screen"
315
+ icon={<FullscreenIcon />}
316
+ isFullscreen={false}
317
+ onClick={onClick}
318
+ />
319
+ );
320
+
321
+ fireEvent.click(
322
+ screen.getByRole('button', { name: 'View chart in full screen' })
323
+ );
324
+ expect(onClick).toHaveBeenCalledTimes(1);
325
+ });
326
+
327
+ it('shows "Make full screen" tooltip by default', async () => {
328
+ render(
329
+ <ChartFullscreenButton
330
+ ariaLabel="Fullscreen"
331
+ icon={<FullscreenIcon />}
332
+ isFullscreen={false}
333
+ onClick={jest.fn()}
334
+ />
335
+ );
336
+
337
+ fireEvent.mouseEnter(screen.getByRole('button', { name: 'Fullscreen' }));
338
+ await waitFor(() => {
339
+ expect(screen.getByText('Make full screen')).toBeInTheDocument();
340
+ });
341
+ });
342
+
343
+ it('shows "Exit full screen" tooltip when in fullscreen', async () => {
344
+ render(
345
+ <ChartFullscreenButton
346
+ ariaLabel="Fullscreen"
347
+ icon={<FullscreenIcon />}
348
+ exitIcon={<FullscreenExitIcon />}
349
+ isFullscreen
350
+ onClick={jest.fn()}
351
+ />
352
+ );
353
+
354
+ fireEvent.mouseEnter(screen.getByRole('button', { name: 'Fullscreen' }));
355
+ await waitFor(() => {
356
+ expect(screen.getByText('Exit full screen')).toBeInTheDocument();
357
+ });
358
+ });
359
+ });
360
+
361
+ // ---------------------------------------------------------------------------
362
+ // ChartMoreOptionsButton
363
+ // ---------------------------------------------------------------------------
364
+ describe('ChartMoreOptionsButton', () => {
365
+ it('renders with default "More options" label', () => {
366
+ render(
367
+ <ChartMoreOptionsButton icon={<MoreVertIcon />}>
368
+ <DropdownMenuItem>Download</DropdownMenuItem>
369
+ </ChartMoreOptionsButton>
370
+ );
371
+
372
+ expect(
373
+ screen.getByRole('button', { name: 'More options' })
374
+ ).toBeInTheDocument();
375
+ });
376
+
377
+ it('renders with custom aria-label', () => {
378
+ render(
379
+ <ChartMoreOptionsButton ariaLabel="Chart actions" icon={<MoreVertIcon />}>
380
+ <DropdownMenuItem>Download</DropdownMenuItem>
381
+ </ChartMoreOptionsButton>
382
+ );
383
+
384
+ expect(
385
+ screen.getByRole('button', { name: 'Chart actions' })
386
+ ).toBeInTheDocument();
387
+ });
388
+
389
+ it('opens dropdown menu on click', () => {
390
+ render(
391
+ <ChartMoreOptionsButton icon={<MoreVertIcon />}>
392
+ <DropdownMenuItem>Download as CSV</DropdownMenuItem>
393
+ </ChartMoreOptionsButton>
394
+ );
395
+
396
+ fireEvent.click(screen.getByRole('button', { name: 'More options' }));
397
+ expect(screen.getByText('Download as CSV')).toBeVisible();
398
+ });
399
+ });
400
+
401
+ // ---------------------------------------------------------------------------
402
+ // ChartToolbar
403
+ // ---------------------------------------------------------------------------
404
+ describe('ChartToolbar', () => {
405
+ it('renders the title as a heading', () => {
406
+ render(
407
+ <ChartToolbar title="Overall Performance">
408
+ <button>Action</button>
409
+ </ChartToolbar>
410
+ );
411
+
412
+ expect(
413
+ screen.getByRole('heading', { level: 3, name: 'Overall Performance' })
414
+ ).toBeInTheDocument();
415
+ });
416
+
417
+ it('renders children action buttons', () => {
418
+ render(
419
+ <ChartToolbar title="Test">
420
+ <button>Show as table</button>
421
+ <button>Full screen</button>
422
+ </ChartToolbar>
423
+ );
424
+
425
+ expect(
426
+ screen.getByRole('button', { name: 'Show as table' })
427
+ ).toBeInTheDocument();
428
+ expect(
429
+ screen.getByRole('button', { name: 'Full screen' })
430
+ ).toBeInTheDocument();
431
+ });
432
+
433
+ it('respects custom heading level', () => {
434
+ render(
435
+ <ChartToolbar title="Test" headingLevel={2}>
436
+ <button>Action</button>
437
+ </ChartToolbar>
438
+ );
439
+
440
+ expect(
441
+ screen.getByRole('heading', { level: 2, name: 'Test' })
442
+ ).toBeInTheDocument();
443
+ });
444
+ });
@@ -0,0 +1,55 @@
1
+ import * as React from 'react';
2
+
3
+ import { ButtonVariant, IconButton, Tooltip } from 'react-magma-dom';
4
+
5
+ import { useChartToolbarI18n } from './chartToolbarI18n';
6
+
7
+ export interface ChartTableButtonProps {
8
+ /** Accessible label for the button – should describe the chart, e.g. "Overall Performance" */
9
+ ariaLabel: string;
10
+ /** Icon element rendered inside the button */
11
+ icon: React.ReactElement;
12
+ /**
13
+ * If true, the button uses inverse (dark) styling.
14
+ * @default false
15
+ */
16
+ isInverse?: boolean;
17
+ /** Whether the associated modal is currently open */
18
+ isTableOpen: boolean;
19
+ /** Click handler – should open the modal */
20
+ onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
21
+ /** Optional ref forwarded to the underlying IconButton */
22
+ buttonRef?: React.Ref<HTMLButtonElement>;
23
+ /**
24
+ * Tooltip text shown on hover.
25
+ * @default "Show as table" (i18n overridable)
26
+ */
27
+ tooltipContent?: string;
28
+ }
29
+
30
+ export function ChartTableButton({
31
+ ariaLabel,
32
+ buttonRef,
33
+ icon,
34
+ isInverse,
35
+ isTableOpen,
36
+ onClick,
37
+ tooltipContent,
38
+ }: ChartTableButtonProps) {
39
+ const t = useChartToolbarI18n();
40
+ const resolvedTooltip = tooltipContent ?? t.showAsTableTooltip;
41
+ return (
42
+ <Tooltip content={resolvedTooltip} isInverse={isInverse}>
43
+ <IconButton
44
+ aria-expanded={isTableOpen}
45
+ aria-haspopup="dialog"
46
+ aria-label={ariaLabel}
47
+ icon={icon}
48
+ isInverse={isInverse}
49
+ onClick={onClick}
50
+ ref={buttonRef}
51
+ variant={ButtonVariant.link}
52
+ />
53
+ </Tooltip>
54
+ );
55
+ }
@@ -0,0 +1,135 @@
1
+ import * as React from 'react';
2
+
3
+ import styled from '@emotion/styled';
4
+ import { Button, Modal, ModalSize, ThemeContext } from 'react-magma-dom';
5
+
6
+ import { ChartDataTable, ChartDataTableColumn } from './ChartDataTable';
7
+ import { useChartToolbarI18n } from './chartToolbarI18n';
8
+
9
+ export interface ChartTableModalProps {
10
+ /** The chart's data passed to ChartDataTable */
11
+ dataSet: Array<Record<string, React.ReactNode>>;
12
+ /** Column definitions forwarded to ChartDataTable */
13
+ columns?: ChartDataTableColumn[];
14
+ /**
15
+ * DOM element to portal the modal into. Defaults to `document.body`.
16
+ * When the chart is in fullscreen, pass the fullscreen element so the
17
+ * modal renders inside it and is visible to the browser.
18
+ */
19
+ portalContainer?: HTMLElement | null;
20
+ /**
21
+ * If true, the modal uses inverse (dark) styling.
22
+ * @default false
23
+ */
24
+ isInverse?: boolean;
25
+ /** Whether the modal is open */
26
+ isOpen: boolean;
27
+ /** Called when the modal requests to close */
28
+ onClose: () => void;
29
+ /** Called when the "Download as CSV" footer button is clicked */
30
+ onDownloadCsv?: () => void;
31
+ /** Chart title – displayed as a second line in the modal heading */
32
+ title: string;
33
+ /**
34
+ * Heading level for the modal header (1–6).
35
+ * @default 2
36
+ */
37
+ headerLevel?: 1 | 2 | 3 | 4 | 5 | 6;
38
+ /**
39
+ * First line of the modal heading.
40
+ * @default "Tabular representation" (i18n overridable)
41
+ */
42
+ headerLabel?: string;
43
+ /**
44
+ * Magma Modal size.
45
+ * @default ModalSize.large
46
+ */
47
+ size?: ModalSize;
48
+ }
49
+
50
+ const ModalFooter = styled.div<{ theme: any }>`
51
+ display: flex;
52
+ justify-content: flex-end;
53
+ padding-top: ${props => props.theme.spaceScale.spacing05};
54
+ `;
55
+
56
+ const HeaderLabel = styled.span<{ isInverse?: boolean; theme: any }>`
57
+ display: block;
58
+ font-size: ${props => props.theme.typeScale.size02.fontSize};
59
+ font-weight: normal;
60
+ color: ${props =>
61
+ props.isInverse
62
+ ? props.theme.colors.neutral100
63
+ : props.theme.colors.neutral500};
64
+ line-height: ${props => props.theme.typeScale.size02.lineHeight};
65
+ `;
66
+
67
+ const containerStyle: React.CSSProperties = {
68
+ display: 'flex',
69
+ alignItems: 'center',
70
+ justifyContent: 'center',
71
+ };
72
+ const contentStyle: React.CSSProperties = {
73
+ width: '100%',
74
+ margin: 0,
75
+ };
76
+
77
+ const HeaderTitle = styled.span`
78
+ display: block;
79
+ `;
80
+
81
+ export function ChartTableModal({
82
+ columns,
83
+ portalContainer,
84
+ dataSet,
85
+ headerLabel,
86
+ headerLevel = 2,
87
+ isInverse,
88
+ isOpen,
89
+ onClose,
90
+ onDownloadCsv,
91
+ size = ModalSize.large,
92
+ title,
93
+ }: ChartTableModalProps) {
94
+ const t = useChartToolbarI18n();
95
+ const theme = React.useContext(ThemeContext);
96
+ const resolvedHeaderLabel = headerLabel ?? t.tabularRepresentationLabel;
97
+ const header = React.useMemo(
98
+ () => (
99
+ <span>
100
+ <HeaderLabel isInverse={isInverse} theme={theme}>
101
+ {resolvedHeaderLabel}
102
+ </HeaderLabel>
103
+ <HeaderTitle>{title}</HeaderTitle>
104
+ </span>
105
+ ),
106
+ [resolvedHeaderLabel, title, isInverse, theme]
107
+ );
108
+
109
+ return (
110
+ <Modal
111
+ portalContainer={portalContainer}
112
+ containerStyle={portalContainer ? containerStyle : undefined}
113
+ header={header}
114
+ headerLevel={headerLevel}
115
+ isInverse={isInverse}
116
+ isOpen={isOpen}
117
+ onClose={onClose}
118
+ size={size}
119
+ style={portalContainer ? contentStyle : undefined}
120
+ >
121
+ <ChartDataTable
122
+ columns={columns}
123
+ dataSet={dataSet}
124
+ isInverse={isInverse}
125
+ />
126
+ {onDownloadCsv && (
127
+ <ModalFooter theme={theme}>
128
+ <Button isInverse={isInverse} onClick={onDownloadCsv}>
129
+ {t.downloadAsCsv}
130
+ </Button>
131
+ </ModalFooter>
132
+ )}
133
+ </Modal>
134
+ );
135
+ }
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+
3
+ import styled from '@emotion/styled';
4
+ import { Heading, ThemeContext, TypographyVisualStyle } from 'react-magma-dom';
5
+
6
+ export interface ChartToolbarProps {
7
+ /** Toolbar action buttons (ChartTableButton, ChartFullscreenButton, ChartMoreOptionsButton, etc.) */
8
+ children: React.ReactNode;
9
+ /**
10
+ * Heading level for the chart title.
11
+ * @default 3
12
+ */
13
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
14
+ /**
15
+ * Visual style for the heading.
16
+ * @default TypographyVisualStyle.headingSmall
17
+ */
18
+ headingVisualStyle?: TypographyVisualStyle;
19
+ /** Chart title text */
20
+ title: string;
21
+ }
22
+
23
+ const ToolbarWrapper = styled.div`
24
+ align-items: center;
25
+ display: flex;
26
+ justify-content: space-between;
27
+ `;
28
+
29
+ const ActionsWrapper = styled.div<{ theme: any }>`
30
+ align-items: center;
31
+ display: flex;
32
+ gap: ${props => props.theme.spaceScale.spacing02};
33
+ `;
34
+
35
+ export function ChartToolbar({
36
+ children,
37
+ headingLevel = 3,
38
+ headingVisualStyle = TypographyVisualStyle.headingSmall,
39
+ title,
40
+ }: ChartToolbarProps) {
41
+ const theme = React.useContext(ThemeContext);
42
+ return (
43
+ <ToolbarWrapper>
44
+ <Heading level={headingLevel} noMargins visualStyle={headingVisualStyle}>
45
+ {title}
46
+ </Heading>
47
+ <ActionsWrapper theme={theme}>{children}</ActionsWrapper>
48
+ </ToolbarWrapper>
49
+ );
50
+ }