@megafon/ui-core 2.4.0 → 2.5.0

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/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [2.5.0](https://github.com/MegafonWebLab/megafon-ui/compare/@megafon/ui-core@2.4.0...@megafon/ui-core@2.5.0) (2022-02-07)
7
+
8
+
9
+ ### Features
10
+
11
+ * **tabs:** add prop outerObserveContainer for observing sticky mode from outside ([bc20c76](https://github.com/MegafonWebLab/megafon-ui/commit/bc20c76f497d88da19ea03cfc66a0c38a7559698))
12
+
13
+
14
+
15
+
16
+
6
17
  # [2.4.0](https://github.com/MegafonWebLab/megafon-ui/compare/@megafon/ui-core@2.3.0...@megafon/ui-core@2.4.0) (2022-01-31)
7
18
 
8
19
 
@@ -41,6 +41,8 @@ export interface ITabsProps {
41
41
  defaultIndex?: number;
42
42
  /** Рендер содержимого только для текущего таба */
43
43
  renderOnlyCurrentPanel?: boolean;
44
+ /** Внешний контейнер для режима фиксация табов */
45
+ outerObserveContainer?: HTMLDivElement | null;
44
46
  /** Обработчика клика по табам */
45
47
  onTabClick?: (index: number) => void;
46
48
  children: Array<React.ReactElement<ITabProps>>;
@@ -67,10 +67,12 @@ var Tabs = function Tabs(_ref) {
67
67
  _ref$renderOnlyCurren = _ref.renderOnlyCurrentPanel,
68
68
  renderOnlyCurrentPanel = _ref$renderOnlyCurren === void 0 ? false : _ref$renderOnlyCurren,
69
69
  children = _ref.children,
70
- onTabClick = _ref.onTabClick;
70
+ onTabClick = _ref.onTabClick,
71
+ outerObserveContainer = _ref.outerObserveContainer;
71
72
  var tabsRef = React.useRef([]);
72
73
  var rootRef = React.useRef(null);
73
74
  var tabListRef = React.useRef(null);
75
+ var intersectionObserverRef = React.useRef();
74
76
 
75
77
  var _React$useState = React.useState(),
76
78
  _React$useState2 = _slicedToArray(_React$useState, 2),
@@ -165,6 +167,65 @@ var Tabs = function Tabs(_ref) {
165
167
  right: documentWidth - right
166
168
  });
167
169
  }, [sticky]);
170
+ var stickyON = React.useCallback(function (leftOffset, rightOffset) {
171
+ var documentWidth = document.documentElement.clientWidth;
172
+ setStickyOffset({
173
+ left: leftOffset,
174
+ right: documentWidth - rightOffset
175
+ });
176
+ setSticky(true);
177
+ }, []);
178
+ var stickyOFF = React.useCallback(function () {
179
+ setStickyOffset({
180
+ left: 0,
181
+ right: 0
182
+ });
183
+ setSticky(false);
184
+ }, []);
185
+ var isContainerNotFitViewport = React.useCallback(function () {
186
+ var _a;
187
+
188
+ var containerHeight = (_a = outerObserveContainer || rootRef.current) === null || _a === void 0 ? void 0 : _a.clientHeight;
189
+ return containerHeight && containerHeight > window.innerHeight;
190
+ }, [outerObserveContainer]);
191
+ var addIntersectionObserver = React.useCallback(function () {
192
+ var observerOptions = {
193
+ threshold: [0, 1]
194
+ };
195
+
196
+ if (isContainerNotFitViewport()) {
197
+ observerOptions.rootMargin = '0px 0px -100%';
198
+ }
199
+
200
+ intersectionObserverRef.current = new IntersectionObserver(function (entries) {
201
+ entries.forEach(function (_ref2) {
202
+ var isIntersecting = _ref2.isIntersecting,
203
+ _ref2$boundingClientR = _ref2.boundingClientRect,
204
+ top = _ref2$boundingClientR.top,
205
+ left = _ref2$boundingClientR.left,
206
+ right = _ref2$boundingClientR.right;
207
+
208
+ if (!tabListRef.current) {
209
+ return;
210
+ }
211
+
212
+ var _tabListRef$current$g = tabListRef.current.getBoundingClientRect(),
213
+ height = _tabListRef$current$g.height,
214
+ tabListNodeLeft = _tabListRef$current$g.left,
215
+ tabListNodeRight = _tabListRef$current$g.right;
216
+
217
+ var leftOffset = outerObserveContainer ? tabListNodeLeft : left;
218
+ var rightOffset = outerObserveContainer ? tabListNodeRight : right;
219
+ setTabListHeight(height);
220
+
221
+ if (isIntersecting) {
222
+ top < 0 ? stickyON(leftOffset, rightOffset) : stickyOFF();
223
+ } else {
224
+ stickyOFF();
225
+ }
226
+ });
227
+ }, observerOptions);
228
+ }, [isContainerNotFitViewport, outerObserveContainer, stickyOFF, stickyON]);
168
229
  var handleTabInnerClick = React.useCallback(function (index) {
169
230
  return function () {
170
231
  setUnderlineTransition('all');
@@ -186,6 +247,28 @@ var Tabs = function Tabs(_ref) {
186
247
  var handleNextArrowClick = React.useCallback(function () {
187
248
  swiperInstance === null || swiperInstance === void 0 ? void 0 : swiperInstance.slideNext();
188
249
  }, [swiperInstance]);
250
+ var handleReachBeginning = React.useCallback(function (swiper) {
251
+ setBeginning(swiper.isBeginning);
252
+ }, []);
253
+ var handleReachEnd = React.useCallback(function (swiper) {
254
+ setEnd(swiper.isEnd);
255
+ }, []);
256
+ var handleFromEdge = React.useCallback(function (swiper) {
257
+ setBeginning(swiper.isBeginning);
258
+ setEnd(swiper.isEnd);
259
+ }, []);
260
+ var addObserveEvent = React.useCallback(function () {
261
+ var _a;
262
+
263
+ var rootRefNode = rootRef.current;
264
+ rootRefNode && ((_a = intersectionObserverRef.current) === null || _a === void 0 ? void 0 : _a.observe(outerObserveContainer || rootRefNode));
265
+ }, [outerObserveContainer]);
266
+ var removeObserveEvent = React.useCallback(function () {
267
+ var _a;
268
+
269
+ var rootRefNode = rootRef.current;
270
+ rootRefNode && ((_a = intersectionObserverRef.current) === null || _a === void 0 ? void 0 : _a.unobserve(outerObserveContainer || rootRefNode));
271
+ }, [outerObserveContainer]);
189
272
  var renderTab = React.useCallback(function (index, title, icon, href) {
190
273
  var ElementType = href ? 'a' : 'div';
191
274
  return /*#__PURE__*/React.createElement(ElementType, {
@@ -234,67 +317,26 @@ var Tabs = function Tabs(_ref) {
234
317
  }, panel);
235
318
  });
236
319
  }, [children, currentIndex, renderOnlyCurrentPanel]);
237
- var handleReachBeginning = React.useCallback(function (swiper) {
238
- setBeginning(swiper.isBeginning);
239
- }, []);
240
- var handleReachEnd = React.useCallback(function (swiper) {
241
- setEnd(swiper.isEnd);
242
- }, []);
243
- var handleFromEdge = React.useCallback(function (swiper) {
244
- setBeginning(swiper.isBeginning);
245
- setEnd(swiper.isEnd);
246
- }, []);
247
320
  React.useEffect(function () {
248
- var rootRefNode = rootRef.current;
249
- var observer = new IntersectionObserver(function (entries) {
250
- entries.forEach(function (_ref2) {
251
- var isIntersecting = _ref2.isIntersecting,
252
- _ref2$boundingClientR = _ref2.boundingClientRect,
253
- top = _ref2$boundingClientR.top,
254
- left = _ref2$boundingClientR.left,
255
- right = _ref2$boundingClientR.right;
256
-
257
- if (!sticky || !rootRefNode || !tabListRef.current) {
258
- return;
259
- }
260
-
261
- var listHeight = tabListRef.current.clientHeight;
262
- setTabListHeight(listHeight);
263
-
264
- var stickyON = function stickyON(leftOffset, rightOffset) {
265
- var documentWidth = document.documentElement.clientWidth;
266
- setStickyOffset({
267
- left: leftOffset,
268
- right: documentWidth - rightOffset
269
- });
270
- setSticky(true);
271
- };
272
-
273
- var stickyOFF = function stickyOFF() {
274
- setStickyOffset({
275
- left: 0,
276
- right: 0
277
- });
278
- setSticky(false);
279
- };
321
+ if (!sticky) {
322
+ return undefined;
323
+ }
280
324
 
281
- if (isIntersecting) {
282
- top < 0 ? stickyON(left, right) : stickyOFF();
283
- } else {
284
- top < 0 && stickyOFF();
285
- }
286
- });
287
- }, {
288
- threshold: [0, 1]
289
- });
290
- rootRefNode && observer.observe(rootRefNode);
325
+ addIntersectionObserver();
326
+ addObserveEvent();
291
327
  return function () {
292
- rootRefNode && observer.unobserve(rootRefNode);
328
+ removeObserveEvent();
293
329
  };
294
- }, [calculateSticky, sticky]);
330
+ }, [addIntersectionObserver, sticky, addObserveEvent, removeObserveEvent]);
295
331
  React.useEffect(function () {
296
332
  var handleResize = throttle(function () {
297
333
  calculateSticky();
334
+
335
+ if (sticky && isContainerNotFitViewport()) {
336
+ removeObserveEvent();
337
+ addIntersectionObserver();
338
+ addObserveEvent();
339
+ }
298
340
  }, 300);
299
341
  var activeTabNode = tabsRef.current[currentIndex];
300
342
  var resizeObserver = new ResizeObserver(function (entries) {
@@ -311,8 +353,9 @@ var Tabs = function Tabs(_ref) {
311
353
  window.addEventListener('resize', handleResize);
312
354
  return function () {
313
355
  window.removeEventListener('resize', handleResize);
356
+ resizeObserver.unobserve(activeTabNode);
314
357
  };
315
- }, [calculateUnderline, calculateSticky, currentIndex]);
358
+ }, [sticky, currentIndex, addObserveEvent, calculateSticky, removeObserveEvent, calculateUnderline, addIntersectionObserver, isContainerNotFitViewport]);
316
359
  React.useEffect(function () {
317
360
  if (!swiperInstance) {
318
361
  return;
@@ -391,6 +434,7 @@ Tabs.propTypes = {
391
434
  currentIndex: PropTypes.number,
392
435
  defaultIndex: PropTypes.number,
393
436
  renderOnlyCurrentPanel: PropTypes.bool,
437
+ outerObserveContainer: PropTypes.oneOfType([PropTypes.elementType, PropTypes.any]),
394
438
  onTabClick: PropTypes.func
395
439
  };
396
440
  export default Tabs;
@@ -41,6 +41,8 @@ export interface ITabsProps {
41
41
  defaultIndex?: number;
42
42
  /** Рендер содержимого только для текущего таба */
43
43
  renderOnlyCurrentPanel?: boolean;
44
+ /** Внешний контейнер для режима фиксация табов */
45
+ outerObserveContainer?: HTMLDivElement | null;
44
46
  /** Обработчика клика по табам */
45
47
  onTabClick?: (index: number) => void;
46
48
  children: Array<React.ReactElement<ITabProps>>;
@@ -97,10 +97,12 @@ var Tabs = function Tabs(_ref) {
97
97
  _ref$renderOnlyCurren = _ref.renderOnlyCurrentPanel,
98
98
  renderOnlyCurrentPanel = _ref$renderOnlyCurren === void 0 ? false : _ref$renderOnlyCurren,
99
99
  children = _ref.children,
100
- onTabClick = _ref.onTabClick;
100
+ onTabClick = _ref.onTabClick,
101
+ outerObserveContainer = _ref.outerObserveContainer;
101
102
  var tabsRef = React.useRef([]);
102
103
  var rootRef = React.useRef(null);
103
104
  var tabListRef = React.useRef(null);
105
+ var intersectionObserverRef = React.useRef();
104
106
 
105
107
  var _React$useState = React.useState(),
106
108
  _React$useState2 = (0, _slicedToArray2["default"])(_React$useState, 2),
@@ -194,6 +196,65 @@ var Tabs = function Tabs(_ref) {
194
196
  right: documentWidth - right
195
197
  });
196
198
  }, [sticky]);
199
+ var stickyON = React.useCallback(function (leftOffset, rightOffset) {
200
+ var documentWidth = document.documentElement.clientWidth;
201
+ setStickyOffset({
202
+ left: leftOffset,
203
+ right: documentWidth - rightOffset
204
+ });
205
+ setSticky(true);
206
+ }, []);
207
+ var stickyOFF = React.useCallback(function () {
208
+ setStickyOffset({
209
+ left: 0,
210
+ right: 0
211
+ });
212
+ setSticky(false);
213
+ }, []);
214
+ var isContainerNotFitViewport = React.useCallback(function () {
215
+ var _a;
216
+
217
+ var containerHeight = (_a = outerObserveContainer || rootRef.current) === null || _a === void 0 ? void 0 : _a.clientHeight;
218
+ return containerHeight && containerHeight > window.innerHeight;
219
+ }, [outerObserveContainer]);
220
+ var addIntersectionObserver = React.useCallback(function () {
221
+ var observerOptions = {
222
+ threshold: [0, 1]
223
+ };
224
+
225
+ if (isContainerNotFitViewport()) {
226
+ observerOptions.rootMargin = '0px 0px -100%';
227
+ }
228
+
229
+ intersectionObserverRef.current = new IntersectionObserver(function (entries) {
230
+ entries.forEach(function (_ref2) {
231
+ var isIntersecting = _ref2.isIntersecting,
232
+ _ref2$boundingClientR = _ref2.boundingClientRect,
233
+ top = _ref2$boundingClientR.top,
234
+ left = _ref2$boundingClientR.left,
235
+ right = _ref2$boundingClientR.right;
236
+
237
+ if (!tabListRef.current) {
238
+ return;
239
+ }
240
+
241
+ var _tabListRef$current$g = tabListRef.current.getBoundingClientRect(),
242
+ height = _tabListRef$current$g.height,
243
+ tabListNodeLeft = _tabListRef$current$g.left,
244
+ tabListNodeRight = _tabListRef$current$g.right;
245
+
246
+ var leftOffset = outerObserveContainer ? tabListNodeLeft : left;
247
+ var rightOffset = outerObserveContainer ? tabListNodeRight : right;
248
+ setTabListHeight(height);
249
+
250
+ if (isIntersecting) {
251
+ top < 0 ? stickyON(leftOffset, rightOffset) : stickyOFF();
252
+ } else {
253
+ stickyOFF();
254
+ }
255
+ });
256
+ }, observerOptions);
257
+ }, [isContainerNotFitViewport, outerObserveContainer, stickyOFF, stickyON]);
197
258
  var handleTabInnerClick = React.useCallback(function (index) {
198
259
  return function () {
199
260
  setUnderlineTransition('all');
@@ -215,6 +276,28 @@ var Tabs = function Tabs(_ref) {
215
276
  var handleNextArrowClick = React.useCallback(function () {
216
277
  swiperInstance === null || swiperInstance === void 0 ? void 0 : swiperInstance.slideNext();
217
278
  }, [swiperInstance]);
279
+ var handleReachBeginning = React.useCallback(function (swiper) {
280
+ setBeginning(swiper.isBeginning);
281
+ }, []);
282
+ var handleReachEnd = React.useCallback(function (swiper) {
283
+ setEnd(swiper.isEnd);
284
+ }, []);
285
+ var handleFromEdge = React.useCallback(function (swiper) {
286
+ setBeginning(swiper.isBeginning);
287
+ setEnd(swiper.isEnd);
288
+ }, []);
289
+ var addObserveEvent = React.useCallback(function () {
290
+ var _a;
291
+
292
+ var rootRefNode = rootRef.current;
293
+ rootRefNode && ((_a = intersectionObserverRef.current) === null || _a === void 0 ? void 0 : _a.observe(outerObserveContainer || rootRefNode));
294
+ }, [outerObserveContainer]);
295
+ var removeObserveEvent = React.useCallback(function () {
296
+ var _a;
297
+
298
+ var rootRefNode = rootRef.current;
299
+ rootRefNode && ((_a = intersectionObserverRef.current) === null || _a === void 0 ? void 0 : _a.unobserve(outerObserveContainer || rootRefNode));
300
+ }, [outerObserveContainer]);
218
301
  var renderTab = React.useCallback(function (index, title, icon, href) {
219
302
  var ElementType = href ? 'a' : 'div';
220
303
  return /*#__PURE__*/React.createElement(ElementType, {
@@ -263,67 +346,26 @@ var Tabs = function Tabs(_ref) {
263
346
  }, panel);
264
347
  });
265
348
  }, [children, currentIndex, renderOnlyCurrentPanel]);
266
- var handleReachBeginning = React.useCallback(function (swiper) {
267
- setBeginning(swiper.isBeginning);
268
- }, []);
269
- var handleReachEnd = React.useCallback(function (swiper) {
270
- setEnd(swiper.isEnd);
271
- }, []);
272
- var handleFromEdge = React.useCallback(function (swiper) {
273
- setBeginning(swiper.isBeginning);
274
- setEnd(swiper.isEnd);
275
- }, []);
276
349
  React.useEffect(function () {
277
- var rootRefNode = rootRef.current;
278
- var observer = new IntersectionObserver(function (entries) {
279
- entries.forEach(function (_ref2) {
280
- var isIntersecting = _ref2.isIntersecting,
281
- _ref2$boundingClientR = _ref2.boundingClientRect,
282
- top = _ref2$boundingClientR.top,
283
- left = _ref2$boundingClientR.left,
284
- right = _ref2$boundingClientR.right;
285
-
286
- if (!sticky || !rootRefNode || !tabListRef.current) {
287
- return;
288
- }
289
-
290
- var listHeight = tabListRef.current.clientHeight;
291
- setTabListHeight(listHeight);
292
-
293
- var stickyON = function stickyON(leftOffset, rightOffset) {
294
- var documentWidth = document.documentElement.clientWidth;
295
- setStickyOffset({
296
- left: leftOffset,
297
- right: documentWidth - rightOffset
298
- });
299
- setSticky(true);
300
- };
301
-
302
- var stickyOFF = function stickyOFF() {
303
- setStickyOffset({
304
- left: 0,
305
- right: 0
306
- });
307
- setSticky(false);
308
- };
350
+ if (!sticky) {
351
+ return undefined;
352
+ }
309
353
 
310
- if (isIntersecting) {
311
- top < 0 ? stickyON(left, right) : stickyOFF();
312
- } else {
313
- top < 0 && stickyOFF();
314
- }
315
- });
316
- }, {
317
- threshold: [0, 1]
318
- });
319
- rootRefNode && observer.observe(rootRefNode);
354
+ addIntersectionObserver();
355
+ addObserveEvent();
320
356
  return function () {
321
- rootRefNode && observer.unobserve(rootRefNode);
357
+ removeObserveEvent();
322
358
  };
323
- }, [calculateSticky, sticky]);
359
+ }, [addIntersectionObserver, sticky, addObserveEvent, removeObserveEvent]);
324
360
  React.useEffect(function () {
325
361
  var handleResize = (0, _lodash["default"])(function () {
326
362
  calculateSticky();
363
+
364
+ if (sticky && isContainerNotFitViewport()) {
365
+ removeObserveEvent();
366
+ addIntersectionObserver();
367
+ addObserveEvent();
368
+ }
327
369
  }, 300);
328
370
  var activeTabNode = tabsRef.current[currentIndex];
329
371
  var resizeObserver = new ResizeObserver(function (entries) {
@@ -340,8 +382,9 @@ var Tabs = function Tabs(_ref) {
340
382
  window.addEventListener('resize', handleResize);
341
383
  return function () {
342
384
  window.removeEventListener('resize', handleResize);
385
+ resizeObserver.unobserve(activeTabNode);
343
386
  };
344
- }, [calculateUnderline, calculateSticky, currentIndex]);
387
+ }, [sticky, currentIndex, addObserveEvent, calculateSticky, removeObserveEvent, calculateUnderline, addIntersectionObserver, isContainerNotFitViewport]);
345
388
  React.useEffect(function () {
346
389
  if (!swiperInstance) {
347
390
  return;
@@ -420,6 +463,7 @@ Tabs.propTypes = {
420
463
  currentIndex: _propTypes["default"].number,
421
464
  defaultIndex: _propTypes["default"].number,
422
465
  renderOnlyCurrentPanel: _propTypes["default"].bool,
466
+ outerObserveContainer: _propTypes["default"].oneOfType([_propTypes["default"].elementType, _propTypes["default"].any]),
423
467
  onTabClick: _propTypes["default"].func
424
468
  };
425
469
  var _default = Tabs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@megafon/ui-core",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "styles"
@@ -53,7 +53,7 @@
53
53
  "@babel/preset-env": "^7.8.6",
54
54
  "@babel/preset-react": "^7.8.3",
55
55
  "@babel/preset-typescript": "^7.8.3",
56
- "@megafon/ui-icons": "^0.1.1",
56
+ "@megafon/ui-icons": "^0.2.0",
57
57
  "@svgr/core": "^2.4.1",
58
58
  "@testing-library/react-hooks": "^7.0.1",
59
59
  "@types/enzyme": "^3.10.5",
@@ -96,5 +96,5 @@
96
96
  "react-popper": "^2.2.3",
97
97
  "swiper": "^6.5.6"
98
98
  },
99
- "gitHead": "1c92184284a8224e5832b4c2218231edfcbb4d5f"
99
+ "gitHead": "28c22a646a8c5c18ef4a4060c1b1232aa45db14c"
100
100
  }