Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(Tabs): used ResizeObserver to detect tab width changes #1366

Merged
merged 2 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦

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