@openedx/paragon 23.14.0 → 23.14.2
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/README.md +80 -14
- package/dist/Alert/index.d.ts +20 -0
- package/dist/Alert/index.js +21 -80
- package/dist/Alert/index.js.map +1 -1
- package/dist/Bubble/index.d.ts +6 -2
- package/dist/Bubble/index.js +4 -26
- package/dist/Bubble/index.js.map +1 -1
- package/dist/DataTable/DropdownFilters.js +7 -2
- package/dist/DataTable/DropdownFilters.js.map +1 -1
- package/dist/DataTable/index.js +8 -1
- package/dist/DataTable/index.js.map +1 -1
- package/dist/DataTable/messages.js +5 -0
- package/dist/Modal/ModalDialog.d.ts +6 -65
- package/dist/Modal/ModalDialog.js +0 -64
- package/dist/Modal/ModalDialog.js.map +1 -1
- package/dist/Modal/ModalLayer.d.ts +0 -25
- package/dist/Modal/ModalLayer.js +0 -19
- package/dist/Modal/ModalLayer.js.map +1 -1
- package/dist/ProductTour/index.js +8 -2
- package/dist/ProductTour/index.js.map +1 -1
- package/dist/Spinner/index.d.ts +11 -0
- package/dist/Spinner/index.js +1 -11
- package/dist/Spinner/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/Alert/Alert.test.tsx +14 -1
- package/src/Alert/index.tsx +42 -93
- package/src/Bubble/index.tsx +12 -32
- package/src/DataTable/DropdownFilters.jsx +9 -2
- package/src/DataTable/README.md +19 -18
- package/src/DataTable/index.jsx +6 -1
- package/src/DataTable/messages.js +5 -0
- package/src/DataTable/tests/DataTable.test.jsx +51 -1
- package/src/DataTable/tests/DropdownFilters.test.jsx +11 -7
- package/src/Modal/ModalDialog.tsx +6 -66
- package/src/Modal/ModalLayer.tsx +0 -22
- package/src/ProductTour/ProductTour.test.jsx +40 -2
- package/src/ProductTour/index.jsx +8 -2
- package/src/Spinner/{index.jsx → index.tsx} +10 -15
- package/src/index.ts +1 -2
- /package/src/Spinner/{Spinner.test.jsx → Spinner.test.tsx} +0 -0
- /package/src/Spinner/__snapshots__/{Spinner.test.jsx.snap → Spinner.test.tsx.snap} +0 -0
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import React, { useContext, useMemo } from 'react';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
2
3
|
import DataTableContext from './DataTableContext';
|
|
3
4
|
import { DropdownButton } from '../Dropdown';
|
|
4
5
|
import useWindowSize from '../hooks/useWindowSizeHook';
|
|
5
6
|
import breakpoints from '../utils/breakpoints';
|
|
7
|
+
import messages from './messages';
|
|
6
8
|
|
|
7
9
|
/** The first filter will be as an input, additional filters will be available in a dropdown. */
|
|
8
10
|
function DropdownFilters() {
|
|
11
|
+
const intl = useIntl();
|
|
9
12
|
const { width } = useWindowSize();
|
|
10
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
columns, numBreakoutFilters, filtersTitle,
|
|
15
|
+
} = useContext(DataTableContext);
|
|
11
16
|
|
|
12
17
|
const [breakoutFilters, otherFilters] = useMemo(() => {
|
|
13
18
|
if (!columns) {
|
|
@@ -25,6 +30,8 @@ function DropdownFilters() {
|
|
|
25
30
|
return [boFilters, dropdownFilters];
|
|
26
31
|
}, [columns, width, numBreakoutFilters]);
|
|
27
32
|
|
|
33
|
+
const dropdownTitle = filtersTitle || intl.formatMessage(messages.filtersDropdownTitle);
|
|
34
|
+
|
|
28
35
|
return (
|
|
29
36
|
<div className="pgn__data-table-filters">
|
|
30
37
|
{breakoutFilters.length > 0 && breakoutFilters.map((column) => (
|
|
@@ -33,7 +40,7 @@ function DropdownFilters() {
|
|
|
33
40
|
</div>
|
|
34
41
|
))}
|
|
35
42
|
{otherFilters.length > 0 && (
|
|
36
|
-
<DropdownButton variant="outline-primary" id="table-filters-dropdown" title=
|
|
43
|
+
<DropdownButton variant="outline-primary" id="table-filters-dropdown" title={dropdownTitle}>
|
|
37
44
|
{otherFilters.map((column) => (
|
|
38
45
|
<div
|
|
39
46
|
key={column.Header}
|
package/src/DataTable/README.md
CHANGED
|
@@ -176,7 +176,7 @@ To enable proper selection behavior with backend pagination (i.e., when ``isSele
|
|
|
176
176
|
producer: 'Isao Takahata',
|
|
177
177
|
release_date: 1986,
|
|
178
178
|
rt_score: 95,
|
|
179
|
-
},
|
|
179
|
+
},
|
|
180
180
|
{
|
|
181
181
|
id: '12cfb892-aac0-4c5b-94af-521852e46d6a',
|
|
182
182
|
title: 'Grave of the Fireflies',
|
|
@@ -567,7 +567,7 @@ Can be used to show the loading state when ``DataTable`` is asynchronously fetch
|
|
|
567
567
|
},
|
|
568
568
|
];
|
|
569
569
|
{/* end example state */}
|
|
570
|
-
|
|
570
|
+
|
|
571
571
|
return (
|
|
572
572
|
<>
|
|
573
573
|
{/* start example form block */}
|
|
@@ -634,7 +634,7 @@ You can pass a function to render custom components for bulk actions and table a
|
|
|
634
634
|
Enroll
|
|
635
635
|
</Button>
|
|
636
636
|
);
|
|
637
|
-
|
|
637
|
+
|
|
638
638
|
const EnrollAction = ({ selectedFlatRows, ...rest }) => (
|
|
639
639
|
// Here is access to the selectedFlatRows, isEntireTableSelected, tableInstance
|
|
640
640
|
<Button variant="danger" onClick={() => console.log('Enroll', selectedFlatRows, rest)}>
|
|
@@ -647,13 +647,13 @@ You can pass a function to render custom components for bulk actions and table a
|
|
|
647
647
|
Assign
|
|
648
648
|
</Button>
|
|
649
649
|
);
|
|
650
|
-
|
|
650
|
+
|
|
651
651
|
const ExtraAction = ({ text, selectedFlatRows, ...rest }) => (
|
|
652
652
|
<Button onClick={() => console.log(`Extra Action ${text}`, selectedFlatRows, rest)}>
|
|
653
653
|
{`Extra Action ${text}`}
|
|
654
654
|
</Button>
|
|
655
655
|
);
|
|
656
|
-
|
|
656
|
+
|
|
657
657
|
return (
|
|
658
658
|
<DataTable
|
|
659
659
|
isSelectable
|
|
@@ -734,7 +734,7 @@ You can pass a function to render custom components for bulk actions and table a
|
|
|
734
734
|
</DataTable>
|
|
735
735
|
)
|
|
736
736
|
}
|
|
737
|
-
|
|
737
|
+
|
|
738
738
|
```
|
|
739
739
|
|
|
740
740
|
#### Actions with Data view toggle enabled
|
|
@@ -889,7 +889,7 @@ a responsive grid of cards.
|
|
|
889
889
|
Clear Selection
|
|
890
890
|
</Component>
|
|
891
891
|
);
|
|
892
|
-
|
|
892
|
+
|
|
893
893
|
return (
|
|
894
894
|
<DataTable
|
|
895
895
|
isFilterable
|
|
@@ -1103,7 +1103,7 @@ Use `columnSizes` prop of `CardView` component to define how many `Cards` are sh
|
|
|
1103
1103
|
|
|
1104
1104
|
const ExampleCard = ({ className, original }) => {
|
|
1105
1105
|
const { name, color, famous_for: famousFor } = original;
|
|
1106
|
-
|
|
1106
|
+
|
|
1107
1107
|
return (
|
|
1108
1108
|
<Card className={className}>
|
|
1109
1109
|
<Card.ImageCap src="https://picsum.photos/360/200/" srcAlt="Card image" />
|
|
@@ -1195,7 +1195,7 @@ You can also display `Cards` with horizontal view. If the table is selectable co
|
|
|
1195
1195
|
<Card.Header title={name} />
|
|
1196
1196
|
<Card.Section>
|
|
1197
1197
|
<dl>
|
|
1198
|
-
<dt>Color</dt>
|
|
1198
|
+
<dt>Color</dt>
|
|
1199
1199
|
<dd>{color}</dd>
|
|
1200
1200
|
<dt>Famous For</dt>
|
|
1201
1201
|
<dd>{famousFor}</dd>
|
|
@@ -1217,7 +1217,7 @@ You can also display `Cards` with horizontal view. If the table is selectable co
|
|
|
1217
1217
|
name: 'Lil Bub',
|
|
1218
1218
|
color: 'brown tabby',
|
|
1219
1219
|
famous_for: 'weird tongue',
|
|
1220
|
-
},
|
|
1220
|
+
},
|
|
1221
1221
|
{
|
|
1222
1222
|
name: 'Grumpy Cat',
|
|
1223
1223
|
color: 'siamese',
|
|
@@ -1258,6 +1258,7 @@ For a more desktop friendly view, you can move filters into a sidebar by providi
|
|
|
1258
1258
|
```jsx live
|
|
1259
1259
|
<DataTable
|
|
1260
1260
|
showFiltersInSidebar
|
|
1261
|
+
filtersTitle="Color filters"
|
|
1261
1262
|
isFilterable
|
|
1262
1263
|
isSortable
|
|
1263
1264
|
defaultColumnValues={{ Filter: TextFilter }}
|
|
@@ -1330,17 +1331,17 @@ For a more desktop friendly view, you can move filters into a sidebar by providi
|
|
|
1330
1331
|
```
|
|
1331
1332
|
|
|
1332
1333
|
## Expandable rows
|
|
1333
|
-
`DataTable` supports expandable rows which once expanded render additional content under the row. Displayed content
|
|
1334
|
+
`DataTable` supports expandable rows which once expanded render additional content under the row. Displayed content
|
|
1334
1335
|
is controlled by the `renderRowSubComponent` prop, which is a function that receives `row` as its single prop and renders expanded view, you also
|
|
1335
1336
|
need to pass `isEpandable` prop to `DataTable` to indicate that it should support expand behavior for rows.
|
|
1336
|
-
Finally, an additional column is required to be included into `columns` prop which will contain handlers for expand / collapse behavior, see examples below.
|
|
1337
|
+
Finally, an additional column is required to be included into `columns` prop which will contain handlers for expand / collapse behavior, see examples below.
|
|
1337
1338
|
|
|
1338
1339
|
### Default view
|
|
1339
1340
|
|
|
1340
1341
|
Here we use default expander column offered by Paragon and for each row render value of the `name` attribute as its subcomponent.
|
|
1341
1342
|
|
|
1342
1343
|
```jsx live
|
|
1343
|
-
<DataTable
|
|
1344
|
+
<DataTable
|
|
1344
1345
|
isExpandable
|
|
1345
1346
|
itemCount={7}
|
|
1346
1347
|
renderRowSubComponent={({ row }) => <div className='text-center'>{row.values.name}</div>}
|
|
@@ -1457,10 +1458,10 @@ You can create your own custom expander column and use it, see code example belo
|
|
|
1457
1458
|
</DataTable>
|
|
1458
1459
|
</div>
|
|
1459
1460
|
)
|
|
1460
|
-
|
|
1461
|
+
|
|
1461
1462
|
return (
|
|
1462
1463
|
<DataTable
|
|
1463
|
-
isExpandable
|
|
1464
|
+
isExpandable
|
|
1464
1465
|
renderRowSubComponent={renderSubComponent}
|
|
1465
1466
|
itemCount={3}
|
|
1466
1467
|
data={[
|
|
@@ -1481,7 +1482,7 @@ You can create your own custom expander column and use it, see code example belo
|
|
|
1481
1482
|
reason: 'Felt like it',
|
|
1482
1483
|
},
|
|
1483
1484
|
{
|
|
1484
|
-
name: 'Smoothie',
|
|
1485
|
+
name: 'Smoothie',
|
|
1485
1486
|
color: 'orange tabby',
|
|
1486
1487
|
famous_for: 'modeling',
|
|
1487
1488
|
date_modified: currentDate,
|
|
@@ -1527,7 +1528,7 @@ You can create your own cell content by passing the `Cell` property to a specifi
|
|
|
1527
1528
|
newColors[index] = cellColors[index] < 3 ? cellColors[index] + 1 : 0;
|
|
1528
1529
|
setCellColors(newColors);
|
|
1529
1530
|
};
|
|
1530
|
-
|
|
1531
|
+
|
|
1531
1532
|
return (
|
|
1532
1533
|
<DataTable
|
|
1533
1534
|
isExpandable
|
|
@@ -1544,7 +1545,7 @@ You can create your own cell content by passing the `Cell` property to a specifi
|
|
|
1544
1545
|
famous_for: 'serving moods',
|
|
1545
1546
|
},
|
|
1546
1547
|
{
|
|
1547
|
-
name: 'Smoothie',
|
|
1548
|
+
name: 'Smoothie',
|
|
1548
1549
|
color: 'orange tabby',
|
|
1549
1550
|
famous_for: 'modeling',
|
|
1550
1551
|
},
|
package/src/DataTable/index.jsx
CHANGED
|
@@ -49,6 +49,7 @@ function DataTable({
|
|
|
49
49
|
EmptyTableComponent,
|
|
50
50
|
manualSelectColumn,
|
|
51
51
|
showFiltersInSidebar,
|
|
52
|
+
filtersTitle,
|
|
52
53
|
dataViewToggleOptions,
|
|
53
54
|
disableElevation,
|
|
54
55
|
isLoading,
|
|
@@ -215,6 +216,7 @@ function DataTable({
|
|
|
215
216
|
manualSelectColumn,
|
|
216
217
|
maxSelectedRows,
|
|
217
218
|
onMaxSelectedRows,
|
|
219
|
+
filtersTitle,
|
|
218
220
|
...selectionProps,
|
|
219
221
|
...selectionActions,
|
|
220
222
|
...props,
|
|
@@ -222,7 +224,7 @@ function DataTable({
|
|
|
222
224
|
|
|
223
225
|
return (
|
|
224
226
|
<DataTableContext.Provider value={enhancedInstance}>
|
|
225
|
-
<DataTableLayout>
|
|
227
|
+
<DataTableLayout filtersTitle={filtersTitle}>
|
|
226
228
|
<div className={classNames('pgn__data-table-wrapper', {
|
|
227
229
|
'hide-shadow': !!disableElevation,
|
|
228
230
|
})}
|
|
@@ -264,6 +266,7 @@ DataTable.defaultProps = {
|
|
|
264
266
|
FilterStatusComponent: FilterStatus,
|
|
265
267
|
RowStatusComponent: RowStatus,
|
|
266
268
|
showFiltersInSidebar: false,
|
|
269
|
+
filtersTitle: undefined,
|
|
267
270
|
dataViewToggleOptions: {
|
|
268
271
|
isDataViewToggleEnabled: false,
|
|
269
272
|
onDataViewToggle: () => {},
|
|
@@ -425,6 +428,8 @@ DataTable.propTypes = {
|
|
|
425
428
|
children: PropTypes.node,
|
|
426
429
|
/** If true filters will be shown on sidebar instead */
|
|
427
430
|
showFiltersInSidebar: PropTypes.bool,
|
|
431
|
+
/** Title of the filters section */
|
|
432
|
+
filtersTitle: PropTypes.string,
|
|
428
433
|
/** options for data view toggle */
|
|
429
434
|
dataViewToggleOptions: PropTypes.shape({
|
|
430
435
|
/** Whether to show a toggle button group which allows view switching between card and table views */
|
|
@@ -6,6 +6,11 @@ const messages = defineMessages({
|
|
|
6
6
|
defaultMessage: 'table pagination',
|
|
7
7
|
description: 'Accessibile name for the navigation element of a pagination component',
|
|
8
8
|
},
|
|
9
|
+
filtersDropdownTitle: {
|
|
10
|
+
id: 'pgn.DataTable.filtersDropdownTitle',
|
|
11
|
+
defaultMessage: 'Filters',
|
|
12
|
+
description: 'Title of the filters dropdown',
|
|
13
|
+
},
|
|
9
14
|
});
|
|
10
15
|
|
|
11
16
|
export default messages;
|
|
@@ -8,6 +8,7 @@ import DataTable from '..';
|
|
|
8
8
|
import DataTableContext from '../DataTableContext';
|
|
9
9
|
import { TextFilter } from '../..';
|
|
10
10
|
import { SELECT_ALL_TEST_ID } from '../selection/data/constants';
|
|
11
|
+
import messages from '../messages';
|
|
11
12
|
|
|
12
13
|
const additionalColumns = [
|
|
13
14
|
{
|
|
@@ -162,6 +163,55 @@ describe('<DataTable />', () => {
|
|
|
162
163
|
expect(screen.getByText('Action')).toBeInTheDocument();
|
|
163
164
|
expect(screen.getByText('More')).toBeInTheDocument();
|
|
164
165
|
});
|
|
166
|
+
|
|
167
|
+
it('displays the custom filters title in the sidebar', () => {
|
|
168
|
+
render(
|
|
169
|
+
<DataTableWrapper
|
|
170
|
+
showFiltersInSidebar
|
|
171
|
+
filtersTitle="Refine Your Search"
|
|
172
|
+
isFilterable
|
|
173
|
+
defaultColumnValues={{ Filter: TextFilter }}
|
|
174
|
+
{...props}
|
|
175
|
+
/>,
|
|
176
|
+
);
|
|
177
|
+
expect(screen.getByRole('heading', { name: 'Refine Your Search' })).toBeInTheDocument();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('displays the default filters title in the sidebar', () => {
|
|
181
|
+
render(
|
|
182
|
+
<DataTableWrapper
|
|
183
|
+
showFiltersInSidebar
|
|
184
|
+
isFilterable
|
|
185
|
+
defaultColumnValues={{ Filter: TextFilter }}
|
|
186
|
+
{...props}
|
|
187
|
+
/>,
|
|
188
|
+
);
|
|
189
|
+
expect(screen.getByRole('heading', { name: messages.filtersDropdownTitle.defaultMessage })).toBeInTheDocument();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('displays the custom filters title in the filters dropdown', () => {
|
|
193
|
+
render(
|
|
194
|
+
<DataTableWrapper
|
|
195
|
+
filtersTitle="Refine Your Search"
|
|
196
|
+
isFilterable
|
|
197
|
+
defaultColumnValues={{ Filter: TextFilter }}
|
|
198
|
+
{...props}
|
|
199
|
+
/>,
|
|
200
|
+
);
|
|
201
|
+
expect(screen.getByRole('button', { name: 'Refine Your Search' })).toBeInTheDocument();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('displays the default filters title in the filters dropdown', () => {
|
|
205
|
+
render(
|
|
206
|
+
<DataTableWrapper
|
|
207
|
+
isFilterable
|
|
208
|
+
defaultColumnValues={{ Filter: TextFilter }}
|
|
209
|
+
{...props}
|
|
210
|
+
/>,
|
|
211
|
+
);
|
|
212
|
+
expect(screen.getByRole('button', { name: messages.filtersDropdownTitle.defaultMessage })).toBeInTheDocument();
|
|
213
|
+
});
|
|
214
|
+
|
|
165
215
|
it('calls useTable with the data and columns', () => {
|
|
166
216
|
const spy = jest.spyOn(reactTable, 'useTable');
|
|
167
217
|
render(<DataTableWrapper {...props} />);
|
|
@@ -213,7 +263,7 @@ describe('<DataTable />', () => {
|
|
|
213
263
|
fetchData: jest.fn(),
|
|
214
264
|
};
|
|
215
265
|
render(<DataTableWrapper {...propsWithSelection} />);
|
|
216
|
-
const filtersButton = screen.getByRole('button', { name:
|
|
266
|
+
const filtersButton = screen.getByRole('button', { name: messages.filtersDropdownTitle.defaultMessage });
|
|
217
267
|
|
|
218
268
|
await userEvent.click(filtersButton);
|
|
219
269
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { IntlProvider } from 'react-intl';
|
|
4
5
|
|
|
5
6
|
import DropdownFilters from '../DropdownFilters';
|
|
6
7
|
import { useWindowSize } from '../..';
|
|
7
8
|
import DataTableContext from '../DataTableContext';
|
|
9
|
+
import messages from '../messages';
|
|
8
10
|
|
|
9
11
|
jest.mock('../../hooks/useWindowSizeHook');
|
|
10
12
|
|
|
@@ -31,9 +33,11 @@ const instance = {
|
|
|
31
33
|
// eslint-disable-next-line react/prop-types
|
|
32
34
|
function DropdownFiltersWrapper({ value = instance, props }) {
|
|
33
35
|
return (
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
|
|
36
|
+
<IntlProvider locale="en">
|
|
37
|
+
<DataTableContext.Provider value={value}>
|
|
38
|
+
<DropdownFilters {...props} />
|
|
39
|
+
</DataTableContext.Provider>
|
|
40
|
+
</IntlProvider>
|
|
37
41
|
);
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -56,7 +60,7 @@ describe('<DropdownFilters />', () => {
|
|
|
56
60
|
// filter should be rendered in the dropdown, so should not be present before
|
|
57
61
|
// clicking the button.
|
|
58
62
|
expect(screen.queryByText('Occupation filter')).toBeNull();
|
|
59
|
-
const filtersButton = screen.getByRole('button', { name:
|
|
63
|
+
const filtersButton = screen.getByRole('button', { name: messages.filtersDropdownTitle.defaultMessage });
|
|
60
64
|
await userEvent.click(filtersButton);
|
|
61
65
|
expect(screen.getByText('Occupation filter')).toBeInTheDocument();
|
|
62
66
|
});
|
|
@@ -65,7 +69,7 @@ describe('<DropdownFilters />', () => {
|
|
|
65
69
|
useWindowSize.mockReturnValue({ width: 800 });
|
|
66
70
|
render(<DropdownFiltersWrapper />);
|
|
67
71
|
expect(screen.queryByText('DOB filter')).toBeNull();
|
|
68
|
-
const filtersButton = screen.getByRole('button', { name:
|
|
72
|
+
const filtersButton = screen.getByRole('button', { name: messages.filtersDropdownTitle.defaultMessage });
|
|
69
73
|
await userEvent.click(filtersButton);
|
|
70
74
|
expect(screen.queryByText('DOB filter')).toBeNull();
|
|
71
75
|
});
|
|
@@ -74,7 +78,7 @@ describe('<DropdownFilters />', () => {
|
|
|
74
78
|
useWindowSize.mockReturnValue({ width: 800 });
|
|
75
79
|
render(<DropdownFiltersWrapper value={{ columns: [instance.columns[1]] }} />);
|
|
76
80
|
expect(screen.getByText('Occupation filter')).toBeInTheDocument();
|
|
77
|
-
expect(screen.queryByRole('button', { name:
|
|
81
|
+
expect(screen.queryByRole('button', { name: messages.filtersDropdownTitle.defaultMessage })).toBeNull();
|
|
78
82
|
});
|
|
79
83
|
});
|
|
80
84
|
|
|
@@ -88,7 +92,7 @@ describe('<DropdownFilters />', () => {
|
|
|
88
92
|
it('renders all filters in the dropdown', async () => {
|
|
89
93
|
useWindowSize.mockReturnValue({ width: 500 });
|
|
90
94
|
render(<DropdownFiltersWrapper />);
|
|
91
|
-
const filtersButton = screen.getByRole('button', { name:
|
|
95
|
+
const filtersButton = screen.getByRole('button', { name: messages.filtersDropdownTitle.defaultMessage });
|
|
92
96
|
await userEvent.click(filtersButton);
|
|
93
97
|
expect(screen.getByText('Bears filter')).toBeInTheDocument();
|
|
94
98
|
expect(screen.getByText('Occupation filter')).toBeInTheDocument();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
2
|
import classNames from 'classnames';
|
|
4
3
|
import { useMediaQuery } from 'react-responsive';
|
|
5
4
|
import { useIntl } from 'react-intl';
|
|
@@ -52,7 +51,12 @@ interface Props {
|
|
|
52
51
|
isBlocking?: boolean;
|
|
53
52
|
/** Specifies the z-index of the modal */
|
|
54
53
|
zIndex?: number;
|
|
55
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Specifies whether overflow content inside the modal should be visible.
|
|
56
|
+
* - `true` - content that exceeds the modal boundaries will remain visible outside the modal's main viewport,
|
|
57
|
+
* rather than being clipped or hidden.
|
|
58
|
+
* - `false` - any overflow content will be clipped to fit within the modal's dimensions.
|
|
59
|
+
*/
|
|
56
60
|
isOverflowVisible: boolean;
|
|
57
61
|
}
|
|
58
62
|
|
|
@@ -109,70 +113,6 @@ function ModalDialog({
|
|
|
109
113
|
);
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
ModalDialog.propTypes = {
|
|
113
|
-
/**
|
|
114
|
-
* Specifies the content of the dialog
|
|
115
|
-
*/
|
|
116
|
-
children: PropTypes.node.isRequired,
|
|
117
|
-
/**
|
|
118
|
-
* The aria-label of the dialog
|
|
119
|
-
*/
|
|
120
|
-
title: PropTypes.string.isRequired,
|
|
121
|
-
/**
|
|
122
|
-
* A callback to close the modal dialog
|
|
123
|
-
*/
|
|
124
|
-
onClose: PropTypes.func.isRequired,
|
|
125
|
-
/**
|
|
126
|
-
* Is the modal dialog open or closed
|
|
127
|
-
*/
|
|
128
|
-
isOpen: PropTypes.bool,
|
|
129
|
-
/**
|
|
130
|
-
* The close 'x' icon button in the top right of the dialog box
|
|
131
|
-
*/
|
|
132
|
-
hasCloseButton: PropTypes.bool,
|
|
133
|
-
/**
|
|
134
|
-
* Sizes determine the maximum width of the dialog box
|
|
135
|
-
*/
|
|
136
|
-
size: PropTypes.oneOf(['sm', 'md', 'lg', 'xl', 'fullscreen']),
|
|
137
|
-
/**
|
|
138
|
-
* The visual style of the dialog box
|
|
139
|
-
*/
|
|
140
|
-
variant: PropTypes.oneOf(['default', 'warning', 'danger', 'success', 'dark']),
|
|
141
|
-
/**
|
|
142
|
-
* The label supplied to the close icon button if one is rendered
|
|
143
|
-
*/
|
|
144
|
-
closeLabel: PropTypes.string,
|
|
145
|
-
/**
|
|
146
|
-
* Specifies class name to append to the base element
|
|
147
|
-
*/
|
|
148
|
-
className: PropTypes.string,
|
|
149
|
-
/**
|
|
150
|
-
* Determines where a scrollbar should appear if a modal is too large for the
|
|
151
|
-
* viewport. When false, the ``ModalDialog``. Body receives a scrollbar, when true
|
|
152
|
-
* the browser window itself receives the scrollbar.
|
|
153
|
-
*/
|
|
154
|
-
isFullscreenScroll: PropTypes.bool,
|
|
155
|
-
/**
|
|
156
|
-
* To show full screen view on mobile screens
|
|
157
|
-
*/
|
|
158
|
-
isFullscreenOnMobile: PropTypes.bool,
|
|
159
|
-
/**
|
|
160
|
-
* Prevent clicking on the backdrop or pressing Esc to close the modal
|
|
161
|
-
*/
|
|
162
|
-
isBlocking: PropTypes.bool,
|
|
163
|
-
/**
|
|
164
|
-
* Specifies the z-index of the modal
|
|
165
|
-
*/
|
|
166
|
-
zIndex: PropTypes.number,
|
|
167
|
-
/**
|
|
168
|
-
* Specifies whether overflow content inside the modal should be visible.
|
|
169
|
-
* - `true` - content that exceeds the modal boundaries will remain visible outside the modal's main viewport,
|
|
170
|
-
* rather than being clipped or hidden.
|
|
171
|
-
* - `false` - any overflow content will be clipped to fit within the modal's dimensions.
|
|
172
|
-
*/
|
|
173
|
-
isOverflowVisible: PropTypes.bool.isRequired,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
116
|
ModalDialog.Header = ModalDialogHeader;
|
|
177
117
|
ModalDialog.Title = ModalDialogTitle;
|
|
178
118
|
ModalDialog.Footer = ModalDialogFooter;
|
package/src/Modal/ModalLayer.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
|
-
import PropTypes from 'prop-types';
|
|
4
3
|
import { FocusOn } from 'react-focus-on';
|
|
5
4
|
import Portal from './Portal';
|
|
6
5
|
import { ModalContextProvider } from './ModalContext';
|
|
@@ -18,19 +17,11 @@ function ModalBackdrop({ onClick }: { onClick?: () => void }) {
|
|
|
18
17
|
);
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
ModalBackdrop.propTypes = {
|
|
22
|
-
onClick: PropTypes.func,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
20
|
// istanbul ignore next
|
|
26
21
|
function ModalContentContainer({ children = null }: { children?: React.ReactNode }) {
|
|
27
22
|
return <div className="pgn__modal-content-container">{children}</div>;
|
|
28
23
|
}
|
|
29
24
|
|
|
30
|
-
ModalContentContainer.propTypes = {
|
|
31
|
-
children: PropTypes.node,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
25
|
interface Props {
|
|
35
26
|
/** Specifies the contents of the modal */
|
|
36
27
|
children: React.ReactNode;
|
|
@@ -94,18 +85,5 @@ function ModalLayer({
|
|
|
94
85
|
);
|
|
95
86
|
}
|
|
96
87
|
|
|
97
|
-
ModalLayer.propTypes = {
|
|
98
|
-
/** Specifies the contents of the modal */
|
|
99
|
-
children: PropTypes.node.isRequired,
|
|
100
|
-
/** A callback function for when the modal is dismissed */
|
|
101
|
-
onClose: PropTypes.func.isRequired,
|
|
102
|
-
/** Is the modal dialog open or closed */
|
|
103
|
-
isOpen: PropTypes.bool.isRequired,
|
|
104
|
-
/** Prevent clicking on the backdrop or pressing Esc to close the modal */
|
|
105
|
-
isBlocking: PropTypes.bool,
|
|
106
|
-
/** Specifies the z-index of the modal */
|
|
107
|
-
zIndex: PropTypes.number,
|
|
108
|
-
};
|
|
109
|
-
|
|
110
88
|
export { ModalBackdrop, ModalContentContainer };
|
|
111
89
|
export default ModalLayer;
|
|
@@ -19,6 +19,8 @@ describe('<ProductTour />', () => {
|
|
|
19
19
|
<div id="target-4">...</div>
|
|
20
20
|
</>
|
|
21
21
|
);
|
|
22
|
+
const handleAdvance = jest.fn();
|
|
23
|
+
const handleBack = jest.fn();
|
|
22
24
|
const handleDismiss = jest.fn();
|
|
23
25
|
const handleEnd = jest.fn();
|
|
24
26
|
const handleEscape = jest.fn();
|
|
@@ -31,6 +33,8 @@ describe('<ProductTour />', () => {
|
|
|
31
33
|
advanceButtonText: 'Next',
|
|
32
34
|
enabled: false,
|
|
33
35
|
endButtonText: 'Okay',
|
|
36
|
+
onAdvance: handleAdvance,
|
|
37
|
+
onBack: handleBack,
|
|
34
38
|
onDismiss: handleDismiss,
|
|
35
39
|
onEnd: handleEnd,
|
|
36
40
|
tourId: 'disabledTour',
|
|
@@ -48,6 +52,8 @@ describe('<ProductTour />', () => {
|
|
|
48
52
|
backButtonText: 'Back',
|
|
49
53
|
enabled: true,
|
|
50
54
|
endButtonText: 'Okay',
|
|
55
|
+
onAdvance: handleAdvance,
|
|
56
|
+
onBack: handleBack,
|
|
51
57
|
onDismiss: handleDismiss,
|
|
52
58
|
onEnd: handleEnd,
|
|
53
59
|
tourId: 'enabledTour',
|
|
@@ -60,6 +66,7 @@ describe('<ProductTour />', () => {
|
|
|
60
66
|
{
|
|
61
67
|
body: 'Checkpoint 2',
|
|
62
68
|
target: '#target-2',
|
|
69
|
+
onAdvance: customOnAdvance,
|
|
63
70
|
},
|
|
64
71
|
{
|
|
65
72
|
body: 'Checkpoint 3',
|
|
@@ -82,6 +89,7 @@ describe('<ProductTour />', () => {
|
|
|
82
89
|
|
|
83
90
|
afterEach(() => {
|
|
84
91
|
popperMock.mockReset();
|
|
92
|
+
jest.resetAllMocks();
|
|
85
93
|
});
|
|
86
94
|
|
|
87
95
|
// eslint-disable-next-line react/prop-types
|
|
@@ -115,6 +123,38 @@ describe('<ProductTour />', () => {
|
|
|
115
123
|
|
|
116
124
|
// Verify the second Checkpoint has rendered
|
|
117
125
|
expect(screen.getByText('Checkpoint 2')).toBeInTheDocument();
|
|
126
|
+
expect(handleAdvance).toHaveBeenCalled();
|
|
127
|
+
|
|
128
|
+
await userEvent.click(advanceButton);
|
|
129
|
+
expect(screen.getByText('Checkpoint 3')).toBeInTheDocument();
|
|
130
|
+
expect(customOnAdvance).toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('onClick of back button rewinds to last checkpoint', async () => {
|
|
134
|
+
render(<ProductTourWrapper tours={[tourData]} />);
|
|
135
|
+
// Verify the first Checkpoint has rendered
|
|
136
|
+
expect(screen.getByRole('heading', { name: 'Checkpoint 1' })).toBeInTheDocument();
|
|
137
|
+
|
|
138
|
+
// Click the advance button
|
|
139
|
+
const advanceButton = screen.getByRole('button', { name: 'Next' });
|
|
140
|
+
await userEvent.click(advanceButton);
|
|
141
|
+
|
|
142
|
+
// go forward to the 3rd checkpoint
|
|
143
|
+
expect(screen.getByText('Checkpoint 2')).toBeInTheDocument();
|
|
144
|
+
await userEvent.click(advanceButton);
|
|
145
|
+
expect(screen.getByText('Checkpoint 3')).toBeInTheDocument();
|
|
146
|
+
|
|
147
|
+
// First back button should use custom on back function
|
|
148
|
+
let backButton = screen.getByRole('button', { name: 'Override back' });
|
|
149
|
+
await userEvent.click(backButton);
|
|
150
|
+
expect(screen.getByText('Checkpoint 2')).toBeInTheDocument();
|
|
151
|
+
expect(customOnBack).toHaveBeenCalled();
|
|
152
|
+
|
|
153
|
+
// Second back button should use the tour's default back function
|
|
154
|
+
backButton = screen.getByRole('button', { name: 'Back' });
|
|
155
|
+
await userEvent.click(backButton);
|
|
156
|
+
expect(screen.getByText('Checkpoint 1')).toBeInTheDocument();
|
|
157
|
+
expect(handleBack).toHaveBeenCalled();
|
|
118
158
|
});
|
|
119
159
|
|
|
120
160
|
it('onClick of dismiss button disables tour', async () => {
|
|
@@ -191,7 +231,6 @@ describe('<ProductTour />', () => {
|
|
|
191
231
|
|
|
192
232
|
// Verify no Checkpoints have rendered
|
|
193
233
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
194
|
-
expect(handleEnd).toHaveBeenCalledTimes(1);
|
|
195
234
|
expect(customOnEnd).not.toHaveBeenCalled();
|
|
196
235
|
});
|
|
197
236
|
|
|
@@ -284,7 +323,6 @@ describe('<ProductTour />', () => {
|
|
|
284
323
|
expect(screen.getByText('Checkpoint 4')).toBeInTheDocument();
|
|
285
324
|
const endButton = screen.getByRole('button', { name: 'Override end' });
|
|
286
325
|
await user.click(endButton);
|
|
287
|
-
expect(handleEnd).toBeCalledTimes(1);
|
|
288
326
|
expect(customOnEnd).toHaveBeenCalledTimes(1);
|
|
289
327
|
expect(screen.queryByText('Checkpoint 4')).not.toBeInTheDocument();
|
|
290
328
|
});
|
|
@@ -12,7 +12,8 @@ const ProductTour = React.forwardRef(({ tours }, ref) => {
|
|
|
12
12
|
startingIndex,
|
|
13
13
|
onEscape,
|
|
14
14
|
onEnd,
|
|
15
|
-
|
|
15
|
+
onAdvance: tourOnAdvance,
|
|
16
|
+
onBack: tourOnBack,
|
|
16
17
|
onDismiss: tourOnDismiss,
|
|
17
18
|
advanceButtonText: tourAdvanceButtonText,
|
|
18
19
|
dismissAltText: tourDismissAltText,
|
|
@@ -27,6 +28,7 @@ const ProductTour = React.forwardRef(({ tours }, ref) => {
|
|
|
27
28
|
title,
|
|
28
29
|
body,
|
|
29
30
|
onAdvance,
|
|
31
|
+
onBack,
|
|
30
32
|
onDismiss,
|
|
31
33
|
advanceButtonText,
|
|
32
34
|
dismissAltText,
|
|
@@ -85,6 +87,8 @@ const ProductTour = React.forwardRef(({ tours }, ref) => {
|
|
|
85
87
|
setIndex(index + 1);
|
|
86
88
|
if (onAdvance) {
|
|
87
89
|
onAdvance();
|
|
90
|
+
} else if (tourOnAdvance) {
|
|
91
|
+
tourOnAdvance();
|
|
88
92
|
}
|
|
89
93
|
};
|
|
90
94
|
|
|
@@ -92,6 +96,8 @@ const ProductTour = React.forwardRef(({ tours }, ref) => {
|
|
|
92
96
|
setIndex(index - 1);
|
|
93
97
|
if (onBack) {
|
|
94
98
|
onBack();
|
|
99
|
+
} else if (tourOnBack) {
|
|
100
|
+
tourOnBack();
|
|
95
101
|
}
|
|
96
102
|
};
|
|
97
103
|
|
|
@@ -100,7 +106,7 @@ const ProductTour = React.forwardRef(({ tours }, ref) => {
|
|
|
100
106
|
setIsTourEnabled(false);
|
|
101
107
|
if (onDismiss) {
|
|
102
108
|
onDismiss();
|
|
103
|
-
} else {
|
|
109
|
+
} else if (tourOnDismiss) {
|
|
104
110
|
tourOnDismiss();
|
|
105
111
|
}
|
|
106
112
|
setCurrentCheckpointData(null);
|