Skip to content

Commit

Permalink
feat(component): add Pill Tabs component (#515)
Browse files Browse the repository at this point in the history
  • Loading branch information
rtalvarez authored Apr 16, 2021
1 parent bfe4276 commit b89518f
Show file tree
Hide file tree
Showing 26 changed files with 3,510 additions and 46 deletions.
142 changes: 142 additions & 0 deletions packages/big-design/src/components/PillTabs/PillTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { CheckIcon, MoreHorizIcon } from '@bigcommerce/big-design-icons';
import React, { createRef, useCallback, useEffect, useMemo, useState } from 'react';

import { useWindowResizeListener } from '../../hooks';
import { Button } from '../Button';
import { Dropdown } from '../Dropdown';
import { Flex } from '../Flex';

import { StyledFlexItem, StyledPillTab } from './styled';

export interface PillTabItem {
id: string;
title: string;
}

export interface PillTabsProps {
items: PillTabItem[];
activePills: string[];
onPillClick: (itemId: string) => void;
}

export const PillTabs: React.FC<PillTabsProps> = ({ activePills, items, onPillClick }) => {
const parentRef = createRef<HTMLDivElement>();
const dropdownRef = createRef<HTMLDivElement>();
const [isMenuVisible, setIsMenuVisible] = useState(false);
const [pillsState, setPillsState] = useState(
items.map((item) => ({
isVisible: true,
item,
ref: createRef<HTMLDivElement>(),
})),
);

const hideOverflowedPills = useCallback(() => {
const parentWidth = parentRef.current?.offsetWidth;
const dropdownWidth = dropdownRef.current?.offsetWidth;

if (!parentWidth || !dropdownWidth) {
return;
}

let remainingWidth = parentWidth;

const newState = pillsState.map((stateObj) => {
const pillWidth = stateObj.ref.current?.offsetWidth;

if (!pillWidth) {
return stateObj;
}

if (remainingWidth - pillWidth > dropdownWidth) {
remainingWidth = remainingWidth - pillWidth;

return {
...stateObj,
isVisible: true,
};
}

return {
...stateObj,
isVisible: false,
};
});

const visiblePills = pillsState.filter((stateObj) => stateObj.isVisible);
const newVisiblePills = newState.filter((stateObj) => stateObj.isVisible);

if (visiblePills.length !== newVisiblePills.length) {
setIsMenuVisible(newVisiblePills.length !== items.length);
setPillsState(newState);
}
}, [items, parentRef, dropdownRef, pillsState]);

const renderedDropdown = useMemo(() => {
const dropdownItems = pillsState
.filter((stateObj) => !stateObj.isVisible)
.map((stateObj) => {
const item = items.find(({ title }) => title === stateObj.item.title);
const isActive = item ? activePills.includes(item.id) : false;

return {
content: stateObj.item.title,
onItemClick: () => onPillClick(stateObj.item.id),
hash: stateObj.item.title.toLowerCase(),
icon: isActive ? <CheckIcon /> : undefined,
};
});

return (
<StyledFlexItem
data-testid="pilltabs-dropdown-toggle"
isVisible={isMenuVisible}
ref={dropdownRef}
role="listitem"
>
<Dropdown items={dropdownItems} toggle={<Button iconOnly={<MoreHorizIcon title="add" />} variant="subtle" />} />
</StyledFlexItem>
);
}, [items, pillsState, isMenuVisible, dropdownRef, activePills, onPillClick]);

const renderedPills = useMemo(
() =>
items.map((item, index) => (
<StyledFlexItem
data-testid={`pilltabs-pill-${index}`}
key={index}
ref={pillsState[index].ref}
isVisible={pillsState[index].isVisible}
role="listitem"
>
<StyledPillTab
disabled={!pillsState[index].isVisible}
variant="subtle"
isActive={activePills.includes(item.id)}
onClick={() => onPillClick(item.id)}
marginRight="xLarge"
>
{item.title}
</StyledPillTab>
</StyledFlexItem>
)),
[items, pillsState, activePills, onPillClick],
);

useEffect(() => {
hideOverflowedPills();
}, [items, parentRef, pillsState, hideOverflowedPills]);

useWindowResizeListener(() => {
hideOverflowedPills();
});

return items.length > 0 ? (
<Flex data-testid="pilltabs-wrapper" flexDirection="row" flexWrap="nowrap" ref={parentRef} role="list">
{renderedPills}
{renderedDropdown}
</Flex>
) : null;
};

PillTabs.displayName = 'Pill Tabs';
Loading

0 comments on commit b89518f

Please sign in to comment.