@openedx/paragon 21.12.0 → 21.12.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.
- package/dist/Container/index.js +6 -2
- package/dist/Container/index.js.map +1 -1
- package/dist/Dropdown/index.js.map +1 -1
- package/dist/Form/FormSwitch.js +3 -0
- package/dist/Form/FormSwitch.js.map +1 -1
- package/dist/Hyperlink/index.js +7 -6
- package/dist/Hyperlink/index.js.map +1 -1
- package/dist/Icon/index.js +17 -10
- package/dist/Icon/index.js.map +1 -1
- package/dist/IconButton/index.js +1 -1
- package/dist/IconButton/index.js.map +1 -1
- package/dist/Layout/index.js.map +1 -1
- package/dist/Modal/ModalDialog.js +3 -0
- package/dist/Modal/ModalDialog.js.map +1 -1
- package/dist/Modal/index.js +11 -6
- package/dist/Modal/index.js.map +1 -1
- package/dist/Popover/index.js +8 -8
- package/dist/Popover/index.js.map +1 -1
- package/dist/ProductTour/Checkpoint.js +10 -8
- package/dist/ProductTour/Checkpoint.js.map +1 -1
- package/dist/ProductTour/messages.js +16 -0
- package/dist/SearchField/SearchFieldAdvanced.js +12 -7
- package/dist/SearchField/SearchFieldAdvanced.js.map +1 -1
- package/dist/SearchField/SearchFieldLabel.js +3 -3
- package/dist/SearchField/SearchFieldLabel.js.map +1 -1
- package/dist/SearchField/index.js +0 -1
- package/dist/SearchField/index.js.map +1 -1
- package/dist/Tabs/index.js +13 -13
- package/dist/Tabs/index.js.map +1 -1
- package/dist/hooks/useIndexOfLastVisibleChild.js +33 -38
- package/dist/hooks/useIndexOfLastVisibleChild.js.map +1 -1
- package/dist/i18n/messages/ar.json +2 -1
- package/dist/i18n/messages/ca.json +2 -1
- package/dist/i18n/messages/es_419.json +2 -1
- package/dist/i18n/messages/es_AR.json +2 -1
- package/dist/i18n/messages/es_ES.json +2 -1
- package/dist/i18n/messages/fr.json +2 -1
- package/dist/i18n/messages/he.json +2 -1
- package/dist/i18n/messages/id.json +2 -1
- package/dist/i18n/messages/it_IT.json +2 -1
- package/dist/i18n/messages/ko_KR.json +2 -1
- package/dist/i18n/messages/pl.json +2 -1
- package/dist/i18n/messages/pt_BR.json +2 -1
- package/dist/i18n/messages/pt_PT.json +2 -1
- package/dist/i18n/messages/ru.json +2 -1
- package/dist/i18n/messages/th.json +2 -1
- package/dist/i18n/messages/tr_TR.json +2 -1
- package/dist/i18n/messages/uk.json +2 -1
- package/dist/i18n/messages/zh_CN.json +2 -1
- package/package.json +1 -1
- package/src/Container/index.jsx +4 -0
- package/src/Dropdown/index.jsx +4 -0
- package/src/Form/FormSwitch.jsx +3 -0
- package/src/Hyperlink/index.jsx +7 -6
- package/src/Icon/index.jsx +17 -10
- package/src/IconButton/index.jsx +1 -1
- package/src/Layout/index.jsx +1 -4
- package/src/Modal/ModalDialog.jsx +3 -0
- package/src/Modal/index.jsx +11 -6
- package/src/Popover/README.md +0 -1
- package/src/Popover/index.jsx +11 -11
- package/src/ProductTour/Checkpoint.jsx +9 -6
- package/src/ProductTour/messages.js +16 -0
- package/src/SearchField/SearchFieldAdvanced.jsx +12 -7
- package/src/SearchField/SearchFieldLabel.jsx +3 -3
- package/src/SearchField/index.jsx +0 -1
- package/src/Tabs/index.jsx +19 -13
- package/src/hooks/tests/useIndexOfLastVisibleChild.test.jsx +3 -3
- package/src/hooks/useIndexOfLastVisibleChild.jsx +36 -38
- package/src/hooks/useIndexOfLastVisibleChild.mdx +3 -3
- package/src/i18n/messages/ar.json +2 -1
- package/src/i18n/messages/ca.json +2 -1
- package/src/i18n/messages/es_419.json +2 -1
- package/src/i18n/messages/es_AR.json +2 -1
- package/src/i18n/messages/es_ES.json +2 -1
- package/src/i18n/messages/fr.json +2 -1
- package/src/i18n/messages/he.json +2 -1
- package/src/i18n/messages/id.json +2 -1
- package/src/i18n/messages/it_IT.json +2 -1
- package/src/i18n/messages/ko_KR.json +2 -1
- package/src/i18n/messages/pl.json +2 -1
- package/src/i18n/messages/pt_BR.json +2 -1
- package/src/i18n/messages/pt_PT.json +2 -1
- package/src/i18n/messages/ru.json +2 -1
- package/src/i18n/messages/th.json +2 -1
- package/src/i18n/messages/tr_TR.json +2 -1
- package/src/i18n/messages/uk.json +2 -1
- package/src/i18n/messages/zh_CN.json +2 -1
package/src/Modal/index.jsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable max-len */
|
|
2
1
|
import React from 'react';
|
|
3
2
|
import ReactDOM from 'react-dom';
|
|
4
3
|
import classNames from 'classnames';
|
|
@@ -271,21 +270,26 @@ class Modal extends React.Component {
|
|
|
271
270
|
Modal.propTypes = {
|
|
272
271
|
/** specifies whether the modal renders open or closed on the initial render. It defaults to false. */
|
|
273
272
|
open: PropTypes.bool,
|
|
274
|
-
/** is the selector for an element in the dom which the modal should be rendered under.
|
|
275
|
-
|
|
273
|
+
/** is the selector for an element in the dom which the modal should be rendered under.
|
|
274
|
+
* It uses querySelector to find the first element that matches that selector,
|
|
275
|
+
* and then creates a React portal to a div underneath the parent element.
|
|
276
|
+
*/
|
|
276
277
|
parentSelector: PropTypes.string,
|
|
277
278
|
/** a string or an element that is rendered inside of the modal title, above the modal body. */
|
|
278
279
|
title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
|
|
279
280
|
/** a string or an element that is rendered inside of the modal body, between the title and the footer. */
|
|
280
281
|
body: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
|
|
281
|
-
/** an array of either elements or shapes that take the form of the buttonPropTypes.
|
|
282
|
+
/** an array of either elements or shapes that take the form of the buttonPropTypes.
|
|
283
|
+
* See the [buttonPropTypes](https://github.com/openedx/paragon/blob/master/src/Button/index.jsx#L40)
|
|
284
|
+
* for a list of acceptable props to pass as part of a button. */
|
|
282
285
|
buttons: PropTypes.arrayOf(PropTypes.oneOfType([
|
|
283
286
|
PropTypes.element,
|
|
284
287
|
PropTypes.shape({}), // TODO: Only accept nodes in the future
|
|
285
288
|
])),
|
|
286
289
|
/** specifies the display text of the default Close button. It defaults to "Close". */
|
|
287
290
|
closeText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
|
288
|
-
/** a function that is called on close. It can be used to perform actions upon closing of the modal,
|
|
291
|
+
/** a function that is called on close. It can be used to perform actions upon closing of the modal,
|
|
292
|
+
* such as restoring focus to the previous logical focusable element. */
|
|
289
293
|
onClose: PropTypes.func.isRequired,
|
|
290
294
|
variant: PropTypes.shape({
|
|
291
295
|
status: PropTypes.string,
|
|
@@ -295,7 +299,8 @@ Modal.propTypes = {
|
|
|
295
299
|
/** specifies whether a close button is rendered in the modal header. It defaults to true. */
|
|
296
300
|
renderHeaderCloseButton: PropTypes.bool,
|
|
297
301
|
/**
|
|
298
|
-
* Specifies optional classes to add to the element with the '.modal-dialog' class.
|
|
302
|
+
* Specifies optional classes to add to the element with the '.modal-dialog' class.
|
|
303
|
+
* See Bootstrap documentation for possible classes. Some options: modal-lg, modal-sm, modal-dialog-centered
|
|
299
304
|
*/
|
|
300
305
|
dialogClassName: PropTypes.string,
|
|
301
306
|
};
|
package/src/Popover/README.md
CHANGED
package/src/Popover/index.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
|
-
import
|
|
4
|
+
import BasePopover from 'react-bootstrap/Popover';
|
|
5
5
|
import BasePopoverTitle from 'react-bootstrap/PopoverTitle';
|
|
6
6
|
import BasePopoverContent from 'react-bootstrap/PopoverContent';
|
|
7
7
|
|
|
@@ -13,18 +13,18 @@ const PLACEMENT_VARIANTS = [
|
|
|
13
13
|
'right',
|
|
14
14
|
];
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const Popover = React.forwardRef(({
|
|
17
17
|
children,
|
|
18
18
|
variant,
|
|
19
19
|
...props
|
|
20
20
|
}, ref) => (
|
|
21
|
-
<
|
|
21
|
+
<BasePopover
|
|
22
22
|
{...props}
|
|
23
23
|
className={classNames({ [`popover-${variant}`]: !!variant }, props.className)}
|
|
24
24
|
ref={ref}
|
|
25
25
|
>
|
|
26
26
|
{children}
|
|
27
|
-
</
|
|
27
|
+
</BasePopover>
|
|
28
28
|
));
|
|
29
29
|
|
|
30
30
|
function PopoverTitle(props) {
|
|
@@ -44,8 +44,8 @@ const commonPropTypes = {
|
|
|
44
44
|
PopoverTitle.propTypes = commonPropTypes;
|
|
45
45
|
PopoverContent.propTypes = commonPropTypes;
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
...
|
|
47
|
+
Popover.propTypes = {
|
|
48
|
+
...BasePopover.propTypes,
|
|
49
49
|
/** An html id attribute, necessary for accessibility. */
|
|
50
50
|
id: PropTypes.string.isRequired,
|
|
51
51
|
/**
|
|
@@ -88,8 +88,8 @@ WrapperPopover.propTypes = {
|
|
|
88
88
|
variant: PropTypes.string,
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
...
|
|
91
|
+
Popover.defaultProps = {
|
|
92
|
+
...BasePopover.defaultProps,
|
|
93
93
|
placement: 'right',
|
|
94
94
|
title: undefined,
|
|
95
95
|
arrowProps: undefined,
|
|
@@ -111,8 +111,8 @@ PopoverContent.defaultProps = {
|
|
|
111
111
|
bsPrefix: 'popover-body',
|
|
112
112
|
};
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
Popover.Title = PopoverTitle;
|
|
115
|
+
Popover.Content = PopoverContent;
|
|
116
116
|
|
|
117
117
|
export { PopoverTitle, PopoverContent };
|
|
118
|
-
export default
|
|
118
|
+
export default Popover;
|
|
@@ -10,6 +10,7 @@ import CheckpointActionRow from './CheckpointActionRow';
|
|
|
10
10
|
import CheckpointBody from './CheckpointBody';
|
|
11
11
|
import CheckpointBreadcrumbs from './CheckpointBreadcrumbs';
|
|
12
12
|
import CheckpointTitle from './CheckpointTitle';
|
|
13
|
+
import messages from './messages';
|
|
13
14
|
|
|
14
15
|
const Checkpoint = React.forwardRef(({
|
|
15
16
|
body,
|
|
@@ -99,10 +100,8 @@ const Checkpoint = React.forwardRef(({
|
|
|
99
100
|
>
|
|
100
101
|
<span className="sr-only">
|
|
101
102
|
<FormattedMessage
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
value={{ step: index + 1 }}
|
|
105
|
-
description="Screen-reader message to indicate the user's position in a sequence of checkpoints."
|
|
103
|
+
{...messages.topPositionText}
|
|
104
|
+
values={{ step: index + 1 }}
|
|
106
105
|
/>
|
|
107
106
|
</span>
|
|
108
107
|
{(title || !isOnlyCheckpoint) && (
|
|
@@ -118,8 +117,12 @@ const Checkpoint = React.forwardRef(({
|
|
|
118
117
|
{...props}
|
|
119
118
|
/>
|
|
120
119
|
<div id="pgn__checkpoint-arrow" data-popper-arrow />
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
<span className="sr-only">
|
|
121
|
+
<FormattedMessage
|
|
122
|
+
{...messages.bottomPositionText}
|
|
123
|
+
values={{ step: index + 1 }}
|
|
124
|
+
/>
|
|
125
|
+
</span>
|
|
123
126
|
</div>
|
|
124
127
|
);
|
|
125
128
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineMessages } from 'react-intl';
|
|
2
|
+
|
|
3
|
+
const messages = defineMessages({
|
|
4
|
+
topPositionText: {
|
|
5
|
+
id: 'pgn.ProductTour.Checkpoint.top-position-text',
|
|
6
|
+
defaultMessage: 'Top of step {step}',
|
|
7
|
+
description: 'Screen-reader message to notify user that they are located at the bottom of the product tour step.',
|
|
8
|
+
},
|
|
9
|
+
bottomPositionText: {
|
|
10
|
+
id: 'pgn.ProductTour.Checkpoint.bottom-position-text',
|
|
11
|
+
defaultMessage: 'Bottom of step {step}',
|
|
12
|
+
description: 'Screen-reader message to notify user that they are located at the bottom of the product tour step.',
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export default messages;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable max-len */
|
|
2
1
|
import React, {
|
|
3
2
|
useRef, createContext, useState, useEffect,
|
|
4
3
|
} from 'react';
|
|
@@ -138,27 +137,32 @@ SearchFieldAdvanced.propTypes = {
|
|
|
138
137
|
onSubmit: PropTypes.func.isRequired,
|
|
139
138
|
/** specifies a custom class name. */
|
|
140
139
|
className: PropTypes.string,
|
|
141
|
-
/** specifies a callback function for when the user loses focus in the `SearchField` component.
|
|
140
|
+
/** specifies a callback function for when the user loses focus in the `SearchField` component.
|
|
141
|
+
* The default is an empty function. For example:
|
|
142
142
|
```jsx
|
|
143
143
|
<SearchField onBlur={event => console.log(event)} />
|
|
144
144
|
``` */
|
|
145
145
|
onBlur: PropTypes.func,
|
|
146
|
-
/** specifies a callback function for when the value in `SearchField` is changed by the user.
|
|
146
|
+
/** specifies a callback function for when the value in `SearchField` is changed by the user.
|
|
147
|
+
* The default is an empty function. For example:
|
|
147
148
|
```jsx
|
|
148
149
|
<SearchField onChange={value => console.log(value)} />
|
|
149
150
|
``` */
|
|
150
151
|
onChange: PropTypes.func,
|
|
151
|
-
/** specifies a callback function for when the value in `SearchField` is cleared by the user.
|
|
152
|
+
/** specifies a callback function for when the value in `SearchField` is cleared by the user.
|
|
153
|
+
* The default is an empty function. For example:
|
|
152
154
|
```jsx
|
|
153
155
|
<SearchField onClear={() => console.log('search cleared')} />
|
|
154
156
|
``` */
|
|
155
157
|
onClear: PropTypes.func,
|
|
156
|
-
/** specifies a callback function for when the user focuses in the `SearchField` component.
|
|
158
|
+
/** specifies a callback function for when the user focuses in the `SearchField` component.
|
|
159
|
+
* The default is an empty function. For example:
|
|
157
160
|
```jsx
|
|
158
161
|
<SearchField onFocus={event => console.log(event)} />
|
|
159
162
|
``` */
|
|
160
163
|
onFocus: PropTypes.func,
|
|
161
|
-
/** specifies the screenreader text for both the clear and submit buttons (e.g., for i18n translations).
|
|
164
|
+
/** specifies the screenreader text for both the clear and submit buttons (e.g., for i18n translations).
|
|
165
|
+
* The default is `{ label: 'search', clearButton: 'clear search', searchButton: 'submit search' }`. */
|
|
162
166
|
screenReaderText: PropTypes.shape({
|
|
163
167
|
label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
|
|
164
168
|
submitButton: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
|
|
@@ -171,7 +175,8 @@ SearchFieldAdvanced.propTypes = {
|
|
|
171
175
|
submit: PropTypes.element.isRequired,
|
|
172
176
|
clear: PropTypes.element,
|
|
173
177
|
}),
|
|
174
|
-
/** specifies the aria-label attribute on the form element. This is useful if you use the `SearchField` component
|
|
178
|
+
/** specifies the aria-label attribute on the form element. This is useful if you use the `SearchField` component
|
|
179
|
+
* more than once on a page. */
|
|
175
180
|
formAriaLabel: PropTypes.string,
|
|
176
181
|
/** Specifies whether the `SearchField` is disabled. */
|
|
177
182
|
disabled: PropTypes.bool,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable max-len */
|
|
2
1
|
import React, { useContext } from 'react';
|
|
3
2
|
import PropTypes from 'prop-types';
|
|
4
3
|
import classNames from 'classnames';
|
|
@@ -21,8 +20,9 @@ function SearchFieldLabel({ children, ...props }) {
|
|
|
21
20
|
|
|
22
21
|
SearchFieldLabel.propTypes = {
|
|
23
22
|
/**
|
|
24
|
-
* specifies the label to use for the input field (e.g., for i18n translations).
|
|
25
|
-
* a screenreader-only label will be used in
|
|
23
|
+
* specifies the label to use for the input field (e.g., for i18n translations).
|
|
24
|
+
* Note: if `children` is not provided, a screenreader-only label will be used in
|
|
25
|
+
* its placed based on the `screenReaderText.label` prop for `SearchField.Advanced`.
|
|
26
26
|
*/
|
|
27
27
|
children: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
|
28
28
|
};
|
package/src/Tabs/index.jsx
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useEffect,
|
|
3
|
+
useMemo,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
useCallback,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import classNames from 'classnames';
|
|
3
9
|
import PropTypes from 'prop-types';
|
|
4
10
|
import BaseTabs from 'react-bootstrap/Tabs';
|
|
@@ -18,15 +24,15 @@ function Tabs({
|
|
|
18
24
|
activeKey,
|
|
19
25
|
...props
|
|
20
26
|
}) {
|
|
21
|
-
const containerElementRef =
|
|
27
|
+
const [containerElementRef, setContainerElementRef] = useState(null);
|
|
22
28
|
const overflowElementRef = useRef(null);
|
|
23
29
|
const indexOfLastVisibleChild = useIndexOfLastVisibleChild(
|
|
24
|
-
containerElementRef
|
|
30
|
+
containerElementRef?.firstChild,
|
|
25
31
|
overflowElementRef.current?.parentNode,
|
|
26
32
|
);
|
|
27
33
|
|
|
28
34
|
useEffect(() => {
|
|
29
|
-
if (containerElementRef
|
|
35
|
+
if (containerElementRef) {
|
|
30
36
|
const observer = new MutationObserver((mutations => {
|
|
31
37
|
mutations.forEach(mutation => {
|
|
32
38
|
// React-Bootstrap attribute 'data-rb-event-key' is responsible for the tab identification
|
|
@@ -35,8 +41,8 @@ function Tabs({
|
|
|
35
41
|
const isActive = mutation.target.getAttribute('aria-selected') === 'true';
|
|
36
42
|
// datakey attribute is added manually to the dropdown
|
|
37
43
|
// elements so that they correspond to the native tabs' eventKey
|
|
38
|
-
const element = containerElementRef.
|
|
39
|
-
const moreTab = containerElementRef.
|
|
44
|
+
const element = containerElementRef.querySelector(`[datakey='${eventKey}']`);
|
|
45
|
+
const moreTab = containerElementRef.querySelector('.pgn__tab_more');
|
|
40
46
|
if (isActive) {
|
|
41
47
|
element?.classList.add('active');
|
|
42
48
|
// Here we add active class to the 'More Tab' if element exists in the dropdown
|
|
@@ -50,13 +56,13 @@ function Tabs({
|
|
|
50
56
|
}
|
|
51
57
|
});
|
|
52
58
|
}));
|
|
53
|
-
observer.observe(containerElementRef
|
|
59
|
+
observer.observe(containerElementRef, {
|
|
54
60
|
attributes: true, subtree: true, attributeFilter: ['aria-selected'],
|
|
55
61
|
});
|
|
56
62
|
return () => observer.disconnect();
|
|
57
63
|
}
|
|
58
64
|
return undefined;
|
|
59
|
-
}, []);
|
|
65
|
+
}, [containerElementRef]);
|
|
60
66
|
|
|
61
67
|
useEffect(() => {
|
|
62
68
|
if (overflowElementRef.current?.parentNode) {
|
|
@@ -64,10 +70,10 @@ function Tabs({
|
|
|
64
70
|
}
|
|
65
71
|
}, [overflowElementRef.current?.parentNode]);
|
|
66
72
|
|
|
67
|
-
const handleDropdownTabClick = (eventKey) => {
|
|
68
|
-
const hiddenTab = containerElementRef.
|
|
73
|
+
const handleDropdownTabClick = useCallback((eventKey) => {
|
|
74
|
+
const hiddenTab = containerElementRef.querySelector(`[data-rb-event-key='${eventKey}']`);
|
|
69
75
|
hiddenTab.click();
|
|
70
|
-
};
|
|
76
|
+
}, [containerElementRef]);
|
|
71
77
|
|
|
72
78
|
const tabsChildren = useMemo(() => {
|
|
73
79
|
const indexOfOverflowStart = indexOfLastVisibleChild + 1;
|
|
@@ -165,10 +171,10 @@ function Tabs({
|
|
|
165
171
|
/>
|
|
166
172
|
));
|
|
167
173
|
return childrenList;
|
|
168
|
-
}, [activeKey, children, defaultActiveKey, indexOfLastVisibleChild, moreTabText]);
|
|
174
|
+
}, [activeKey, children, defaultActiveKey, indexOfLastVisibleChild, moreTabText, handleDropdownTabClick]);
|
|
169
175
|
|
|
170
176
|
return (
|
|
171
|
-
<div ref={
|
|
177
|
+
<div ref={setContainerElementRef}>
|
|
172
178
|
<BaseTabs
|
|
173
179
|
defaultActiveKey={defaultActiveKey}
|
|
174
180
|
activeKey={activeKey}
|
|
@@ -12,12 +12,12 @@ window.ResizeObserver = window.ResizeObserver
|
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
14
|
function TestComponent() {
|
|
15
|
-
const containerElementRef = React.
|
|
15
|
+
const [containerElementRef, setContainerElementRef] = React.useState(null);
|
|
16
16
|
const overflowElementRef = React.useRef(null);
|
|
17
|
-
const indexOfLastVisibleChild = useIndexOfLastVisibleChild(containerElementRef
|
|
17
|
+
const indexOfLastVisibleChild = useIndexOfLastVisibleChild(containerElementRef, overflowElementRef.current);
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
|
-
<div ref={
|
|
20
|
+
<div ref={setContainerElementRef} style={{ display: 'flex' }}>
|
|
21
21
|
<div style={{ width: '250px' }} className="element">Element 1</div>
|
|
22
22
|
<div style={{ width: '250px' }} className="element">Element 2</div>
|
|
23
23
|
<div style={{ width: '250px' }} className="element">Element 3</div>
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { useLayoutEffect, useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import useWindowSize from './useWindowSize';
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* This hook will find the index of the last child of a containing element
|
|
7
5
|
* that fits within its bounding rectangle. This is done by summing the widths
|
|
@@ -10,48 +8,48 @@ import useWindowSize from './useWindowSize';
|
|
|
10
8
|
* @param {Element} containerElementRef - container element
|
|
11
9
|
* @param {Element} overflowElementRef - overflow element
|
|
12
10
|
*
|
|
13
|
-
* The hook returns
|
|
14
|
-
* [indexOfLastVisibleChild, containerElementRef, overflowElementRef]
|
|
15
|
-
*
|
|
16
|
-
* indexOfLastVisibleChild - the index of the last visible child
|
|
17
|
-
* containerElementRef - a ref to be added to the containing html node
|
|
18
|
-
* overflowElementRef - a ref to be added to an html node inside the container
|
|
19
|
-
* that is likely to be used to contain a "More" type dropdown or other
|
|
20
|
-
* mechanism to reveal hidden children. The width of this element is always
|
|
21
|
-
* included when determining which children will fit or not. Usage of this ref
|
|
22
|
-
* is optional.
|
|
11
|
+
* The hook returns the index of the last visible child.
|
|
23
12
|
*/
|
|
24
13
|
const useIndexOfLastVisibleChild = (containerElementRef, overflowElementRef) => {
|
|
25
14
|
const [indexOfLastVisibleChild, setIndexOfLastVisibleChild] = useState(-1);
|
|
26
|
-
const windowSize = useWindowSize();
|
|
27
15
|
|
|
28
16
|
useLayoutEffect(() => {
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
function updateLastVisibleChildIndex() {
|
|
18
|
+
// Get array of child nodes from NodeList form
|
|
19
|
+
const childNodesArr = Array.prototype.slice.call(containerElementRef.children);
|
|
20
|
+
const { nextIndexOfLastVisibleChild } = childNodesArr
|
|
21
|
+
// filter out the overflow element
|
|
22
|
+
.filter(childNode => childNode !== overflowElementRef)
|
|
23
|
+
// sum the widths to find the last visible element's index
|
|
24
|
+
.reduce((acc, childNode, index) => {
|
|
25
|
+
acc.sumWidth += childNode.getBoundingClientRect().width;
|
|
26
|
+
if (acc.sumWidth <= containerElementRef.getBoundingClientRect().width) {
|
|
27
|
+
acc.nextIndexOfLastVisibleChild = index;
|
|
28
|
+
}
|
|
29
|
+
return acc;
|
|
30
|
+
}, {
|
|
31
|
+
// Include the overflow element's width to begin with. Doing this means
|
|
32
|
+
// sometimes we'll show a dropdown with one item in it when it would fit,
|
|
33
|
+
// but allowing this case dramatically simplifies the calculations we need
|
|
34
|
+
// to do above.
|
|
35
|
+
sumWidth: overflowElementRef ? overflowElementRef.getBoundingClientRect().width : 0,
|
|
36
|
+
nextIndexOfLastVisibleChild: -1,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
setIndexOfLastVisibleChild(nextIndexOfLastVisibleChild);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (containerElementRef) {
|
|
43
|
+
updateLastVisibleChildIndex();
|
|
44
|
+
|
|
45
|
+
const resizeObserver = new ResizeObserver(() => updateLastVisibleChildIndex());
|
|
46
|
+
resizeObserver.observe(containerElementRef);
|
|
47
|
+
|
|
48
|
+
return () => resizeObserver.disconnect();
|
|
31
49
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// filter out the overflow element
|
|
36
|
-
.filter(childNode => childNode !== overflowElementRef)
|
|
37
|
-
// sum the widths to find the last visible element's index
|
|
38
|
-
.reduce((acc, childNode, index) => {
|
|
39
|
-
acc.sumWidth += childNode.getBoundingClientRect().width;
|
|
40
|
-
if (acc.sumWidth <= containerElementRef.getBoundingClientRect().width) {
|
|
41
|
-
acc.nextIndexOfLastVisibleChild = index;
|
|
42
|
-
}
|
|
43
|
-
return acc;
|
|
44
|
-
}, {
|
|
45
|
-
// Include the overflow element's width to begin with. Doing this means
|
|
46
|
-
// sometimes we'll show a dropdown with one item in it when it would fit,
|
|
47
|
-
// but allowing this case dramatically simplifies the calculations we need
|
|
48
|
-
// to do above.
|
|
49
|
-
sumWidth: overflowElementRef ? overflowElementRef.getBoundingClientRect().width : 0,
|
|
50
|
-
nextIndexOfLastVisibleChild: -1,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
setIndexOfLastVisibleChild(nextIndexOfLastVisibleChild);
|
|
54
|
-
}, [windowSize, containerElementRef, overflowElementRef]);
|
|
50
|
+
|
|
51
|
+
return undefined;
|
|
52
|
+
}, [containerElementRef, overflowElementRef]);
|
|
55
53
|
|
|
56
54
|
return indexOfLastVisibleChild;
|
|
57
55
|
};
|
|
@@ -25,10 +25,10 @@ of the children until they exceed the width of the container.
|
|
|
25
25
|
pointerEvents: 'none',
|
|
26
26
|
visibility: 'hidden',
|
|
27
27
|
};
|
|
28
|
-
const containerElementRef = React.
|
|
28
|
+
const [containerElementRef, setContainerElementRef] = React.useState(null);
|
|
29
29
|
const overflowElementRef = React.useRef(null);
|
|
30
30
|
const indexOfLastVisibleChild = useIndexOfLastVisibleChild(
|
|
31
|
-
containerElementRef
|
|
31
|
+
containerElementRef,
|
|
32
32
|
overflowElementRef.current,
|
|
33
33
|
);
|
|
34
34
|
const elements = ['Element 1', 'Element 2', 'Element 3', 'Element 4', 'Element 5', 'Element 6', 'Element 7'];
|
|
@@ -71,7 +71,7 @@ of the children until they exceed the width of the container.
|
|
|
71
71
|
}, [indexOfLastVisibleChild]);
|
|
72
72
|
|
|
73
73
|
return (
|
|
74
|
-
<div className="d-flex" ref={
|
|
74
|
+
<div className="d-flex" ref={setContainerElementRef}>
|
|
75
75
|
{children}
|
|
76
76
|
</div>
|
|
77
77
|
)
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "إغلاق قائمة الخيارات",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "فتح قائمة الخيارات",
|
|
30
30
|
"pgn.Toast.closeLabel": "إغلاق ",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Close the options menu",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Open the options menu",
|
|
30
30
|
"pgn.Toast.closeLabel": "Close",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Cerrar el menú de opciones",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Abre el menú de opciones",
|
|
30
30
|
"pgn.Toast.closeLabel": "Cerrar",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Cerrar el menú de opciones",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Abre el menú de opciones",
|
|
30
30
|
"pgn.Toast.closeLabel": "Cerrar",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Cerrar el menú de opciones",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Abre el menú de opciones",
|
|
30
30
|
"pgn.Toast.closeLabel": "Cerrar",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Close the options menu",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Open the options menu",
|
|
30
30
|
"pgn.Toast.closeLabel": "Close",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Close the options menu",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Open the options menu",
|
|
30
30
|
"pgn.Toast.closeLabel": "Close",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Close the options menu",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Open the options menu",
|
|
30
30
|
"pgn.Toast.closeLabel": "Close",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Close the options menu",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Open the options menu",
|
|
30
30
|
"pgn.Toast.closeLabel": "Chiudi",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Close the options menu",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Open the options menu",
|
|
30
30
|
"pgn.Toast.closeLabel": "Close",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Close the options menu",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Open the options menu",
|
|
30
30
|
"pgn.Toast.closeLabel": "Zamknij",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Close the options menu",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Open the options menu",
|
|
30
30
|
"pgn.Toast.closeLabel": "Close",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Fechar o menu de opções",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Abrir o menu de opções",
|
|
30
30
|
"pgn.Toast.closeLabel": "Fechar",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Close the options menu",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Open the options menu",
|
|
30
30
|
"pgn.Toast.closeLabel": "Close",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Close the options menu",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Open the options menu",
|
|
30
30
|
"pgn.Toast.closeLabel": "Close",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Seçenekler menüsünü kapat",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Seçenekler menüsünü aç",
|
|
30
30
|
"pgn.Toast.closeLabel": "Kapat",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"pgn.FormAutosuggest.iconButtonClosed": "Close the options menu",
|
|
29
29
|
"pgn.FormAutosuggest.iconButtonOpened": "Open the options menu",
|
|
30
30
|
"pgn.Toast.closeLabel": "Close",
|
|
31
|
-
"pgn.ProductTour.Checkpoint.position-text": "Top of step {step}"
|
|
31
|
+
"pgn.ProductTour.Checkpoint.top-position-text": "Top of step {step}",
|
|
32
|
+
"pgn.ProductTour.Checkpoint.bottom-position-text": "Bottom of step {step}"
|
|
32
33
|
}
|