@axinom/mosaic-ui 0.32.0-rc.11 → 0.32.0-rc.13

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-ui",
3
- "version": "0.32.0-rc.11",
3
+ "version": "0.32.0-rc.13",
4
4
  "description": "UI components for building Axinom Mosaic applications",
5
5
  "author": "Axinom",
6
6
  "license": "PROPRIETARY",
@@ -102,5 +102,5 @@
102
102
  "publishConfig": {
103
103
  "access": "public"
104
104
  },
105
- "gitHead": "095178757a7c929d25376adb504388b86c747871"
105
+ "gitHead": "b3a214238f1d54e9f537981372ab779e7fdc7d62"
106
106
  }
@@ -105,4 +105,15 @@ describe('AccordionItem', () => {
105
105
 
106
106
  expect(containerStyles).toEqual({ maxHeight: 100 });
107
107
  });
108
+
109
+ it('creates a class based off of the className prop', () => {
110
+ const mockClassName = 'test-class';
111
+ const wrapper = shallow(
112
+ <AccordionItem header={<b>Item 1</b>} className={mockClassName} />,
113
+ );
114
+
115
+ const item = wrapper.find('.test-class');
116
+
117
+ expect(item.hasClass(mockClassName)).toBe(true);
118
+ });
108
119
  });
@@ -18,6 +18,9 @@ export interface AccordionItemProps {
18
18
 
19
19
  /** Sticky state of the current row. When used within the Accordion component, this prop will be overridden with the value from stickyRows. */
20
20
  sticky?: boolean;
21
+
22
+ /** CSS Class name for additional styles */
23
+ className?: string;
21
24
  }
22
25
 
23
26
  /**
@@ -34,6 +37,7 @@ export const AccordionItem: React.FC<AccordionItemProps> = ({
34
37
  isExpanded = false,
35
38
  allowLeftSpacing = true,
36
39
  sticky = false,
40
+ className,
37
41
  }) => {
38
42
  const [scrollHeight, setScrollHeight] = useState<number>(0);
39
43
  const elementRef = useRef<HTMLDivElement>(null);
@@ -45,7 +49,7 @@ export const AccordionItem: React.FC<AccordionItemProps> = ({
45
49
  }, [scrollHeight, children]);
46
50
 
47
51
  return (
48
- <div data-test-id="accordion-item">
52
+ <div data-test-id="accordion-item" className={className}>
49
53
  <div
50
54
  data-test-id="accordion-item-row"
51
55
  className={clsx(
@@ -36,6 +36,11 @@ export interface ExplorerBulkAction<T extends Data>
36
36
  * Whether the Explorer should reload the data once the bulk action is completed
37
37
  */
38
38
  reloadData?: boolean;
39
+ /**
40
+ * Whether or not to show toast notification once the bulk action is started
41
+ * (shown by default)
42
+ */
43
+ showStartedNotification?: boolean;
39
44
  }
40
45
 
41
46
  export type PageIdentifier = string | number | undefined;
@@ -270,7 +270,7 @@ describe('Explorer', () => {
270
270
  const header = wrapper.find(PageHeader);
271
271
 
272
272
  await act(async () => {
273
- header.prop('bulkActions')?.[0].onClick();
273
+ header.prop('bulkActions')?.[0].onClick?.();
274
274
 
275
275
  await wrapper.update();
276
276
  });
@@ -280,7 +280,7 @@ describe('Explorer', () => {
280
280
  });
281
281
 
282
282
  it('Calls "showNotification" when bulk action is clicked', async () => {
283
- const [provider, spy] = getDataProvider();
283
+ const [provider] = getDataProvider();
284
284
 
285
285
  const label = 'Something';
286
286
  const wrapper = await actWithReturn(async () => {
@@ -303,7 +303,7 @@ describe('Explorer', () => {
303
303
 
304
304
  const header = wrapper.find(PageHeader);
305
305
  await act(async () => {
306
- header.prop('bulkActions')?.[0].onClick();
306
+ header.prop('bulkActions')?.[0].onClick?.();
307
307
  await wrapper.update();
308
308
  });
309
309
 
@@ -313,6 +313,38 @@ describe('Explorer', () => {
313
313
  });
314
314
  });
315
315
 
316
+ it('Does not call "showNotification" when `showStartedNotification=false` and bulk action is clicked', async () => {
317
+ const [provider] = getDataProvider();
318
+
319
+ const label = 'Something';
320
+ const wrapper = await actWithReturn(async () => {
321
+ const wrapper = mount(
322
+ <Explorer
323
+ columns={mockListColumns}
324
+ dataProvider={provider}
325
+ stationKey="mock-key"
326
+ bulkActions={[
327
+ {
328
+ label,
329
+ onClick: jest.fn(),
330
+ reloadData: true,
331
+ showStartedNotification: false,
332
+ },
333
+ ]}
334
+ />,
335
+ );
336
+ return wrapper;
337
+ });
338
+
339
+ const header = wrapper.find(PageHeader);
340
+ await act(async () => {
341
+ header.prop('bulkActions')?.[0].onClick?.();
342
+ await wrapper.update();
343
+ });
344
+
345
+ expect(showNotificationSpy).toHaveBeenCalledTimes(0);
346
+ });
347
+
316
348
  it.todo(`raises page header bulk action with 'SELECT_ALL'`);
317
349
 
318
350
  it.todo(`raises page header bulk action with 'SINGLE_ITEMS'`);
@@ -1672,7 +1704,7 @@ describe('Explorer', () => {
1672
1704
  const menu = wrapper.find(InlineMenu);
1673
1705
 
1674
1706
  await act(async () => {
1675
- await menu.prop('actions')?.[0].onActionSelected();
1707
+ await menu.prop('actions')?.[0].onActionSelected?.();
1676
1708
  await wrapper.update();
1677
1709
  });
1678
1710
 
@@ -1728,7 +1760,7 @@ describe('Explorer', () => {
1728
1760
  const menu = wrapper.find(InlineMenu);
1729
1761
 
1730
1762
  await act(async () => {
1731
- await menu.prop('actions')?.[0].onActionSelected();
1763
+ await menu.prop('actions')?.[0].onActionSelected?.();
1732
1764
  await wrapper.update();
1733
1765
  });
1734
1766
 
@@ -365,9 +365,11 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
365
365
  return {
366
366
  ...action,
367
367
  onClick: async () => {
368
- showNotification({
369
- title: `Bulk Action '${action.label}' Started`,
370
- });
368
+ if (action.showStartedNotification !== false) {
369
+ showNotification({
370
+ title: `Bulk Action '${action.label}' Started`,
371
+ });
372
+ }
371
373
 
372
374
  try {
373
375
  const result = await action.onClick(getBulkActionSelection());
@@ -2,7 +2,9 @@ import { mount, shallow } from 'enzyme';
2
2
  import React from 'react';
3
3
  import { act } from 'react-dom/test-utils';
4
4
  import { actWithReturn } from '../../../helpers/testing';
5
+ import * as app from '../../../initialize';
5
6
  import { Column } from '../../List';
7
+ import { PageHeader } from '../../PageHeader';
6
8
  import { PageHeaderBulkActions } from '../../PageHeader/PageHeaderBulkActions/PageHeaderBulkActions';
7
9
  import { Explorer } from '../Explorer';
8
10
  import { ExplorerDataProvider } from '../Explorer.model';
@@ -103,6 +105,34 @@ describe('SelectionExplorer', () => {
103
105
  expect(bulkActions.exists()).toBe(false);
104
106
  });
105
107
 
108
+ it('Does not call "showNotification" when "Apply Selection" is clicked', async () => {
109
+ const [provider] = getDataProvider();
110
+ const showNotificationSpy: jest.SpyInstance = jest.spyOn(
111
+ app,
112
+ 'showNotification',
113
+ );
114
+
115
+ const wrapper = await actWithReturn(async () => {
116
+ const wrapper = mount(
117
+ <SelectionExplorer
118
+ columns={mockListColumns}
119
+ dataProvider={provider}
120
+ stationKey="mock-key"
121
+ allowBulkSelect={true}
122
+ />,
123
+ );
124
+ return wrapper;
125
+ });
126
+
127
+ const header = wrapper.find(PageHeader);
128
+ await act(async () => {
129
+ header.prop('bulkActions')?.[0].onClick?.();
130
+ await wrapper.update();
131
+ });
132
+
133
+ expect(showNotificationSpy).toHaveBeenCalledTimes(0);
134
+ });
135
+
106
136
  it('sends onSelection callback when the selection of a single item is triggered', async () => {
107
137
  const [provider] = getDataProvider();
108
138
  const spy = jest.fn();
@@ -68,6 +68,7 @@ export const SelectionExplorer = React.forwardRef(function SelectionExplorer<
68
68
  onClick: ((arg: ItemSelection<T>) => {
69
69
  onSelection(arg);
70
70
  }) as ExplorerBulkAction<T>['onClick'],
71
+ showStartedNotification: false,
71
72
  },
72
73
  ]
73
74
  : []),
@@ -11,7 +11,7 @@
11
11
  row-gap: 10px;
12
12
  }
13
13
 
14
- margin-bottom: 30px;
14
+ margin-bottom: 20px;
15
15
 
16
16
  .title {
17
17
  color: var(--infopanel-subtitle-color, $infopanel-subtitle-color);
@@ -7,12 +7,19 @@
7
7
  grid-template-columns: 1fr;
8
8
 
9
9
  &.hasTitle {
10
- grid-template-rows: max-content max-content;
10
+ grid-template-rows: max-content;
11
+
12
+ padding-top: 6px;
13
+ padding-bottom: 6px;
14
+
15
+ &.expanded {
16
+ grid-template-rows: max-content max-content;
17
+ }
11
18
  }
12
19
 
13
20
  padding: 30px;
14
21
 
15
- row-gap: 30px;
22
+ row-gap: 20px;
16
23
 
17
24
  .title,
18
25
  .main {
@@ -37,4 +44,29 @@
37
44
  --infopanel-background-color,
38
45
  $infopanel-background-color
39
46
  );
47
+
48
+ .collapsibleSection {
49
+ & > div:first-child {
50
+ background-color: unset;
51
+
52
+ div:last-child {
53
+ margin-left: -18px;
54
+ }
55
+
56
+ svg {
57
+ margin-left: -34px;
58
+ }
59
+ }
60
+
61
+ & > div:last-child {
62
+ border-bottom: unset;
63
+ }
64
+
65
+ & > div > div:last-child {
66
+ background-color: var(
67
+ --infopanel-background-color,
68
+ $infopanel-background-color
69
+ );
70
+ }
71
+ }
40
72
  }
@@ -1,5 +1,6 @@
1
1
  import { shallow } from 'enzyme';
2
2
  import React from 'react';
3
+ import { AccordionItem } from '../../Accordion';
3
4
  import { Section } from './Section';
4
5
 
5
6
  describe('Section Tests', () => {
@@ -8,4 +9,120 @@ describe('Section Tests', () => {
8
9
 
9
10
  expect(wrapper).toBeTruthy();
10
11
  });
12
+
13
+ it('should not render anything if section has no children', () => {
14
+ const wrapper = shallow(<Section></Section>);
15
+
16
+ const section = wrapper.find('.container');
17
+
18
+ expect(section.exists()).toBe(false);
19
+ });
20
+
21
+ it('creates a class based off of the className prop', () => {
22
+ const mockClassName = 'test-class';
23
+ const wrapper = shallow(
24
+ <Section className={mockClassName}>
25
+ <p>test</p>
26
+ </Section>,
27
+ );
28
+
29
+ const item = wrapper.find('.test-class');
30
+
31
+ expect(item.hasClass(mockClassName)).toBe(true);
32
+ });
33
+
34
+ it('accepts styles', () => {
35
+ const mockStyles: React.CSSProperties = { backgroundColor: 'blue' };
36
+ const wrapper = shallow(
37
+ <Section style={mockStyles}>
38
+ <p>test</p>
39
+ </Section>,
40
+ );
41
+
42
+ const section = wrapper.find('.container');
43
+
44
+ expect(section.prop('style')).toBe(mockStyles);
45
+ });
46
+
47
+ it(`should not be toggleable if no 'title' is given`, () => {
48
+ const wrapper = shallow(
49
+ <Section>
50
+ <p>test</p>
51
+ </Section>,
52
+ );
53
+
54
+ const item = wrapper.find(AccordionItem);
55
+
56
+ expect(item.exists()).toBe(false);
57
+ });
58
+
59
+ it(`should be toggleable if a 'title' is given`, () => {
60
+ const wrapper = shallow(
61
+ <Section title="test">
62
+ <p>test</p>
63
+ </Section>,
64
+ );
65
+
66
+ const item = wrapper.find(AccordionItem);
67
+
68
+ expect(item.exists()).toBe(true);
69
+ });
70
+
71
+ it('section should be expanded by default', () => {
72
+ const wrapper = shallow(
73
+ <Section>
74
+ <p>test</p>
75
+ </Section>,
76
+ );
77
+
78
+ const section = wrapper.find('.container');
79
+
80
+ expect(section.hasClass('expanded')).toBe(true);
81
+ });
82
+
83
+ it(`section is collasped if 'expandedByDefault' is set to false`, () => {
84
+ const wrapper = shallow(
85
+ <Section expandedByDefault={false}>
86
+ <p>test</p>
87
+ </Section>,
88
+ );
89
+
90
+ const section = wrapper.find('.container');
91
+
92
+ expect(section.hasClass('expanded')).toBe(false);
93
+ });
94
+
95
+ it('section can be toggled when the title is clicked', () => {
96
+ const wrapper = shallow(
97
+ <Section title="test">
98
+ <p>test</p>
99
+ </Section>,
100
+ );
101
+
102
+ let section = wrapper.find('.container').first();
103
+ let item = wrapper.find(AccordionItem);
104
+
105
+ expect(section.hasClass('expanded')).toBe(true);
106
+ expect(item.prop('isExpanded')).toBe(true);
107
+
108
+ item = wrapper.find(AccordionItem);
109
+ item.prop('toggleExpanded')!();
110
+ wrapper.update();
111
+
112
+ section = wrapper.find('.container').first();
113
+ item = wrapper.find(AccordionItem);
114
+
115
+ expect(section.hasClass('expanded')).toBe(false);
116
+ expect(item.prop('isExpanded')).toBe(false);
117
+
118
+ item = wrapper.find(AccordionItem);
119
+ item.prop('toggleExpanded')!();
120
+ wrapper.update();
121
+
122
+ section = wrapper.find('.container').first();
123
+ item = wrapper.find(AccordionItem);
124
+
125
+ expect(section.hasClass('expanded')).toBe(true);
126
+ expect(item.prop('isExpanded')).toBe(true);
127
+ });
11
128
  });
@@ -1,10 +1,11 @@
1
1
  import clsx from 'clsx';
2
- import React from 'react';
2
+ import React, { useState } from 'react';
3
+ import { AccordionItem } from '../../Accordion';
3
4
  import { Paragraph } from '../Paragraph/Paragraph';
4
5
  import classes from './Section.scss';
5
6
 
6
7
  export interface SectionProps {
7
- /** Section Title */
8
+ /** Section Title. If set, will allow the section to be expandable/collapsible */
8
9
  title?: string;
9
10
 
10
11
  /** Optional class */
@@ -12,6 +13,9 @@ export interface SectionProps {
12
13
 
13
14
  /** Optional styles */
14
15
  style?: React.CSSProperties;
16
+
17
+ /** Whether the section should be expanded or collapsed intially (default: true)*/
18
+ expandedByDefault?: boolean;
15
19
  }
16
20
 
17
21
  export const Section: React.FC<SectionProps> = ({
@@ -19,7 +23,9 @@ export const Section: React.FC<SectionProps> = ({
19
23
  title,
20
24
  style,
21
25
  children,
26
+ expandedByDefault = true,
22
27
  }) => {
28
+ const [expanded, setExpanded] = useState<boolean>(expandedByDefault);
23
29
  const validChildren: React.ReactNode[] = [];
24
30
 
25
31
  // Special case for removing paragraph components if they are empty
@@ -42,21 +48,38 @@ export const Section: React.FC<SectionProps> = ({
42
48
  <div
43
49
  className={clsx(
44
50
  classes.container,
45
- { [classes.hasTitle]: title },
51
+ { [classes.hasTitle]: title, [classes.expanded]: expanded },
46
52
  'info-panel-section-container',
47
53
  className,
48
54
  )}
49
55
  style={style}
50
56
  data-test-id="section"
51
57
  >
52
- {title && (
53
- <div className={classes.title} data-test-id="section-title">
54
- {title}
58
+ {title ? (
59
+ <AccordionItem
60
+ header={
61
+ title ? (
62
+ <div className={classes.title} data-test-id="section-title">
63
+ {title}
64
+ </div>
65
+ ) : (
66
+ <div className={classes.title} data-test-id="section-title"></div>
67
+ )
68
+ }
69
+ allowLeftSpacing={false}
70
+ isExpanded={expanded}
71
+ toggleExpanded={() => setExpanded((prev) => !prev)}
72
+ className={classes.collapsibleSection}
73
+ >
74
+ <div className={classes.main} data-test-id="section-content">
75
+ {validChildren}
76
+ </div>
77
+ </AccordionItem>
78
+ ) : (
79
+ <div className={classes.main} data-test-id="section-content">
80
+ {validChildren}
55
81
  </div>
56
82
  )}
57
- <div className={classes.main} data-test-id="section-content">
58
- {validChildren}
59
- </div>
60
83
  </div>
61
84
  );
62
85
  };