Skip to content

Commit

Permalink
refactor(Tabs): used ResizeObserver to detect tab width changes (#1366)
Browse files Browse the repository at this point in the history
  • Loading branch information
nekitk authored Jul 23, 2021
1 parent 1aeee77 commit edbb728
Show file tree
Hide file tree
Showing 16 changed files with 79 additions and 185 deletions.
6 changes: 6 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
import '@testing-library/jest-dom';

import ResizeObserver from './__mocks__/ResizeObserver';

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react';
import { act, fireEvent, render, RenderResult, screen } from '@testing-library/react';

import ResizeObserver from '../../../../__mocks__/ResizeObserver';
import { cnSelect } from '../../SelectComponentsDeprecated/cnSelect';
import { cnSelectItem } from '../../SelectComponentsDeprecated/SelectItem/SelectItem';
import { BasicSelect, SimpleSelectProps } from '../BasicSelect';
Expand All @@ -14,10 +13,6 @@ type SelectOption = {
const animationDuration = 200;
const testId = 'BasicSelect';

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});

const items = [
{ label: 'Neptunium', value: 'Neptunium' },
{ label: 'Plutonium', value: 'Plutonium' },
Expand Down
5 changes: 0 additions & 5 deletions src/components/Combobox/__tests__/Combobox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as React from 'react';
import { act, fireEvent, render, RenderResult, screen } from '@testing-library/react';

import { groups, items } from '../__mocks__/data.mock';
import ResizeObserver from '../../../../__mocks__/ResizeObserver';
import { cn } from '../../../utils/bem';
import { cnSelect } from '../../SelectComponents/cnSelect';
import { cnSelectGroupLabel } from '../../SelectComponents/SelectGroupLabel/SelectGroupLabel';
Expand All @@ -11,10 +10,6 @@ import { cnSelectValueTag } from '../../SelectComponents/SelectValueTag/SelectVa
import { Combobox, ComboboxProps, defaultGetItemLabel } from '../Combobox';
import { DefaultGroup, DefaultItem } from '../helpers';

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});

const animationDuration = 200;
const testId = 'Combobox';
const cnRenderValue = cn('RenderValue');
Expand Down
5 changes: 0 additions & 5 deletions src/components/ComboboxDeprecated/__tests__/Combobox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@ import React, { useState } from 'react';
import { act, fireEvent, render, screen } from '@testing-library/react';

import { simpleItems } from '../__mocks__/data.mock';
import ResizeObserver from '../../../../__mocks__/ResizeObserver';
import { cnSelect } from '../../SelectComponentsDeprecated/cnSelect';
import { cnSelectItem } from '../../SelectComponentsDeprecated/SelectItem/SelectItem';
import { Combobox } from '../Combobox';

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});

const testId = 'Combobox';
const animationDuration = 200;

Expand Down
5 changes: 0 additions & 5 deletions src/components/ContextMenu/__tests__/ContextMenu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@ import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';

import { exampleItems as items, groups, Item } from '../__mocks__/mock.data';
import ResizeObserver from '../../../../__mocks__/ResizeObserver';
import { cnText } from '../../Text/Text';
import { ContextMenu } from '../ContextMenu';
import { cnContextMenuGroupHeader } from '../ContextMenuGroupHeader/ContextMenuGroupHeader';
import { cnContextMenuItem } from '../ContextMenuItem/ContextMenuItem';
import { cnContextMenuLevel } from '../ContextMenuLevel/ContextMenuLevel';
import { ContextMenuProps } from '../helpers';

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});

const testId = 'ContextMenu';
const additionalClass = 'additionalClass';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import React, { useState } from 'react';
import { act, fireEvent, render, screen } from '@testing-library/react';

import ResizeObserver from '../../../../__mocks__/ResizeObserver';
import { simpleItems } from '../../ComboboxDeprecated/__mocks__/data.mock';
import { cnSelect } from '../../SelectComponentsDeprecated/cnSelect';
import { cnSelectItem } from '../../SelectComponentsDeprecated/SelectItem/SelectItem';
import { MultiCombobox } from '../MultiCombobox';

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});

const testId = 'MultiCombobox';
const animationDuration = 200;

Expand Down
5 changes: 0 additions & 5 deletions src/components/Select/__tests__/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as React from 'react';
import { act, fireEvent, render, RenderResult, screen } from '@testing-library/react';

import { groups, items } from '../__mocks__/data.mock';
import ResizeObserver from '../../../../__mocks__/ResizeObserver';
import { cn } from '../../../utils/bem';
import { cnSelect } from '../../SelectComponents/cnSelect';
import { cnSelectGroupLabel } from '../../SelectComponents/SelectGroupLabel/SelectGroupLabel';
Expand All @@ -14,10 +13,6 @@ const testId = 'Select';
const cnRenderValue = cn('RenderValue');
const cnRenderItem = cn('RenderItem');

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});

const defaultProps: SelectProps = {
items,
groups,
Expand Down
5 changes: 0 additions & 5 deletions src/components/Table/__tests__/Table.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import * as React from 'react';
import { render, screen } from '@testing-library/react';

import ResizeObserver from '../../../../__mocks__/ResizeObserver';
import { Props, Table } from '../Table';

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});

const rows = [
{
id: 'row1',
Expand Down
50 changes: 9 additions & 41 deletions src/components/Tabs/Tabs.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,46 +40,14 @@
}

&-RunningLine {
left: -1px;
width: 1px;
border-radius: 1px 0 0 0;
transition: opacity 0.2s, transform 0.25s;
transform: translateX(var(--tabOffsetLeft, 0));

&,
&::before,
&::after {
position: absolute;
bottom: 0;
height: 2px;
background-color: var(--color-bg-brand);
transform-origin: left center;
}

&::before {
content: '';
left: 1px;
width: var(--tabsWidth);
transition: transform 0.25s;
transform: scaleX(var(--tabRatio, 0.0001));
}

&::after {
content: '';
left: 1px;
width: 1px;
border-radius: 0 1px 0 0;
transition: transform 0.25s;
transform: translateX(var(--tabWidth));
}

&_withOutValue {
opacity: 0;
}
}

&-WrapperRunningLine {
overflow: hidden;
width: 100%;
position: absolute;
left: 0;
bottom: 0;
width: var(--tabSize);
height: 2px;
background-color: var(--color-bg-brand);
transition: transform 0.25s, width 0.25s;
transform: translateX(var(--tabOffset));
transform-origin: left center;
}
}
85 changes: 25 additions & 60 deletions src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import './Tabs.css';

import React, { createRef, useEffect, useMemo, useRef } from 'react';
import React, { createRef, useMemo } from 'react';

import { useChoiceGroup } from '../../hooks/useChoiceGroup/useChoiceGroup';
import { useForkRef } from '../../hooks/useForkRef/useForkRef';
import { useResizeObserved } from '../../hooks/useResizeObserved/useResizeObserved';
import { IconProps, IconPropSize } from '../../icons/Icon/Icon';
import { cn } from '../../utils/bem';
import { getSizeByMap } from '../../utils/getSizeByMap';
Expand Down Expand Up @@ -75,22 +75,6 @@ const sizeMap: Record<TabsPropSize, IconPropSize> = {
m: 's',
};

function setStyleForLine(
lineRef: React.RefObject<HTMLDivElement>,
tabsWidth: number,
tabWidth: number,
tabRatio: number,
tabOffsetLeft: number,
) {
if (lineRef.current) {
const lineStyle = lineRef.current.style;
lineStyle.setProperty('--tabsWidth', `${tabsWidth}px`);
lineStyle.setProperty('--tabWidth', `${tabWidth}px`);
lineStyle.setProperty('--tabRatio', `${tabRatio}`);
lineStyle.setProperty('--tabOffsetLeft', `${tabOffsetLeft}px`);
}
}

function renderItemDefault<ITEM, ITEMELEMENT extends HTMLElement>(
props: RenderItemProps<ITEM, ITEMELEMENT>,
): React.ReactElement {
Expand Down Expand Up @@ -129,51 +113,26 @@ export const Tabs: Tabs = React.forwardRef((props, ref) => {
multiple: false,
});

const constructItemRefs: () => Record<string, React.RefObject<ItemElement>> = () => {
const refs: Record<string, React.RefObject<ItemElement>> = {};
for (const item of items) {
refs[getLabel(item)] = createRef<ItemElement>();
}
return refs;
};

const buttonRefs = useMemo(constructItemRefs, [items, getLabel]);

const rootRef = useRef<HTMLDivElement>(null);
const lineRef = useRef<HTMLDivElement>(null);

const updateLine = () => {
if (rootRef.current && lineRef.current && buttonRefs) {
const rootWidth = rootRef.current.offsetWidth;
if (value) {
const activeItemRef = buttonRefs[getLabel(value)];
if (activeItemRef && activeItemRef.current) {
const itemWidth = activeItemRef.current.offsetWidth;
const itemOffsetLeft = activeItemRef.current.offsetLeft;
setStyleForLine(lineRef, rootWidth, itemWidth, itemWidth / rootWidth, itemOffsetLeft);
}
} else {
setStyleForLine(lineRef, rootWidth, 1, 0.00001, 1);
}
}
};

useEffect(() => updateLine());

const withOutValue = !value;
const tabRefs = useMemo(
() => new Array(items.length).fill(null).map(() => createRef<ItemElement>()),
[items],
);
const tabsDimensions = useResizeObserved(tabRefs, (el) => ({
size: el?.offsetWidth ?? 0,
offset: el?.offsetLeft ?? 0,
}));
const activeTabIdx = (value && items.indexOf(value)) ?? -1;
const activeTabDimensions = tabsDimensions[activeTabIdx];

const iconSize = getSizeByMap(sizeMap, size, iconSizeProp);

return (
<div
className={cnTabs({ size, view }, [className])}
ref={useForkRef<HTMLDivElement>([ref, rootRef])}
{...otherProps}
>
<div className={cnTabs({ size, view }, [className])} ref={ref} {...otherProps}>
<div className={cnTabs('List')}>
{items.map((item) =>
{items.map((item, idx) =>
renderItem({
item,
ref: buttonRefs[getLabel(item)],
ref: tabRefs[idx],
key: getLabel(item),
onChange: getOnChange(item),
checked: getChecked(item),
Expand All @@ -185,9 +144,15 @@ export const Tabs: Tabs = React.forwardRef((props, ref) => {
}),
)}
</div>
<div className={cnTabs('WrapperRunningLine')}>
<div className={cnTabs('RunningLine', { withOutValue })} ref={lineRef} />
</div>
{activeTabDimensions?.size > 0 && (
<div
className={cnTabs('RunningLine')}
style={{
['--tabSize' as string]: `${activeTabDimensions.size}px`,
['--tabOffset' as string]: `${activeTabDimensions.offset}px`,
}}
/>
)}
</div>
);
});
Expand Down
5 changes: 0 additions & 5 deletions src/components/ThemeToggler/__tests__/ThemeToggler.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';

import { exampleThemesThree, exampleThemesTwo } from '../__mocks__/data.mock';
import ResizeObserver from '../../../../__mocks__/ResizeObserver';
import { cnContextMenuItem } from '../../ContextMenu/ContextMenuItem/ContextMenuItem';
import { Props, ThemeToggler } from '../ThemeToggler';

Expand All @@ -13,10 +12,6 @@ type ThemeTogglerProps = Props<Item>;
const defaultSetValue = jest.fn();
const testId = 'ThemeToggler';

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});

const renderComponent = (props: Partial<ThemeTogglerProps>) => {
return render(
<>
Expand Down
5 changes: 0 additions & 5 deletions src/components/UserSelect/__tests__/UserSelect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as React from 'react';
import { act, fireEvent, render, RenderResult, screen } from '@testing-library/react';

import { groups, items } from '../__mocks__/data.mock';
import ResizeObserver from '../../../../__mocks__/ResizeObserver';
import { cn } from '../../../utils/bem';
import { cnSelect } from '../../SelectComponents/cnSelect';
import { cnSelectGroupLabel } from '../../SelectComponents/SelectGroupLabel/SelectGroupLabel';
Expand All @@ -11,10 +10,6 @@ import { defaultGetItemLabel, UserSelect, UserSelectProps } from '../UserSelect'
import { cnUserSelectItem } from '../UserSelectItem/UserSelectItem';
import { cnUserSelectValue } from '../UserSelectValue/UserSelectValue';

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});

const animationDuration = 200;
const testId = 'UserSelect';
const cnRenderValue = cn('RenderValue');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,11 @@ import React, { useState } from 'react';
import { act, fireEvent, render, screen } from '@testing-library/react';

import { simpleItems } from '../__mocks__/data.mock';
import ResizeObserver from '../../../../__mocks__/ResizeObserver';
import { getInitialsForName } from '../../Avatar/Avatar';
import { cnSelect } from '../../SelectComponentsDeprecated/cnSelect';
import { UserSelect } from '../UserSelect';
import { cnUserItem } from '../UserSelectItem/UserSelectItem';

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});

const testId = 'UserSelect';
const animationDuration = 200;

Expand Down
5 changes: 0 additions & 5 deletions src/hocs/withTooltip/__tests__/withTooltip.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react';
import { act, fireEvent, render, screen } from '@testing-library/react';

import ResizeObserver from '../../../../__mocks__/ResizeObserver';
import { Button } from '../../../components/Button/Button';
import {
appearTimeoutDefault,
Expand All @@ -10,10 +9,6 @@ import {
withTooltip,
} from '../withTooltip';

jest.mock('resize-observer-polyfill', () => {
return ResizeObserver;
});

const testId = 'withTooltip';
const tooltipRole = 'Tooltip';

Expand Down
Loading

0 comments on commit edbb728

Please sign in to comment.