@react-magma/charts 14.0.0-rc.3 → 14.0.0-rc.5

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