Skip to content

Conversation

@davelinke
Copy link
Contributor

@davelinke davelinke commented Jan 31, 2025

What?

Adding separator on pill tabs to diversify pill tab groups

Why?

When working with pill tabs, in the use case for filtering datasets, there may be filters that are provided by default by the app, but there's also the possibility for the user to add custom filter criteria and save it. In order to differentiate the diverse groups of pill tabs, we are introducing a pill separator that would suggest diverse groups of pills.

Screenshots/Screen Recordings

Screenshot 2025-01-31 at 1 39 19 PM

Testing/Proof

dev.tsx code
import { Flex, FlexItem, Panel, PillTabs, Text, Link, Grid, Modal } from '@bigcommerce/big-design';
import { Page, Header } from '@bigcommerce/big-design-patterns';
import React, { FunctionComponent, useState } from 'react';

const PagePillSeparators: FunctionComponent = () => {
  const [activePills, setActivePills] = useState<string[]>([]);
  const [items, setItems] = useState([
    { title: 'All', id: 'all' },
    { title: 'Featured', id: 'featured', separator: true },
    { title: 'Free shipping', id: 'free-shipping' },
    { title: 'Out of stock', id: 'out-of-stock' },
    { title: 'Inventory low', id: 'inventory-low' },
    { title: 'Custom view 1', id: 'custom-view-1', separator: true },
    { title: 'Custom view 2', id: 'custom-view-2' },
    { title: 'Custom view 3', id: 'custom-view-3' },
    { title: 'Manage pills', id: 'manage', separator: true },
  ]);
  const [isOpen, setIsOpen] = useState(false);
  const Card: React.FC<{ name: string; description: string }> = ({ name, description }) => (
    <Flex
      border="box"
      borderRadius="normal"
      flexDirection="column"
      margin="xxSmall"
      padding="medium"
    >
      <FlexItem marginBottom="xxSmall">
        <Text bold>{name}</Text>
      </FlexItem>
      <FlexItem flexGrow={1} marginBottom="large">
        <Text>{description}</Text>
      </FlexItem>
      <FlexItem>
        <Link href="#">Install</Link>
      </FlexItem>
    </Flex>
  );

  const onPillClick = (pillId: string) => {
    if (pillId !== 'manage') {
      setActivePills([pillId]);
    } else {
      setIsOpen(true);
    }
  };
  // lets define cards tha correspond to the active pill
  const cards = [
    {
      description: 'Featured products',
      name: 'Featured products',
      type: 'featured',
    },
    {
      description: 'Products with free shipping',
      name: 'Free shipping',
      type: 'free-shipping',
    },
    {
      description: 'Out of stock products',
      name: 'Out of stock',
      type: 'out-of-stock',
    },
    {
      description: 'Products with low inventory',
      name: 'Inventory low',
      type: 'inventory-low',
    },
    {
      description: 'Custom view 1',
      name: 'Custom view 1',
      type: 'custom-view-1',
    },
    {
      description: 'Custom view 2',
      name: 'Custom view 2',
      type: 'custom-view-2',
    },
    {
      description: 'Custom view 3',
      name: 'Custom view 3',
      type: 'custom-view-3',
    },
  ];
  const isFiltered = Boolean(activePills.length);
  const filteredCards =
    isFiltered && activePills[0] === 'all'
      ? cards
      : cards.filter(({ type }) => activePills.includes(type));

  const appCards = isFiltered ? filteredCards : cards;

  const addPill = () => {
    const newItems = [...items];
    // add pill in the second position of the array, add separator and remove separator from the pill that gets shifted forward
    newItems.splice(1, 0, { title: 'New pill', id: 'new-pill', separator: true });
    // remove separator from the pill that gets shifted forward
    newItems[2].separator = false;
    setItems(newItems);
    // trigger resize event so pill tabs can recalculate their width
    window.setTimeout(() => {
      window.dispatchEvent(new Event('resize'));
    },10);
  }

  return (
    <>
      <Page
        header={
          <Header
            description="an example on how we use pill tabs for filtering"
            title="Pill Tabs"
          />
        }
      >
        <Panel
          header="Pills example"
          action={{ text: 'Add new pill', onClick: () => addPill() }}
        >
          <PillTabs activePills={activePills} items={items} onPillClick={onPillClick} />
          <Grid gridColumns="repeat(2, 1fr)" marginTop={'xLarge'}>
            {appCards.map(({ name, description }) => (
              <Card description={description} key={name} name={name} />
            ))}
          </Grid>
        </Panel>
        <Modal
          actions={[
            {
              text: 'Cancel',
              variant: 'subtle',
              onClick: () => setIsOpen(false),
            },
            { text: 'Apply', onClick: () => setIsOpen(false) },
          ]}
          closeOnClickOutside={false}
          closeOnEscKey={true}
          header="Modal title"
          isOpen={isOpen}
          onClose={() => setIsOpen(false)}
        >
          <Text>
            Ea tempor sunt amet labore proident dolor proident commodo in exercitation ea nulla sunt
            pariatur. Nulla sunt ipsum do eu consectetur exercitation occaecat labore aliqua. Aute
            elit occaecat esse ea fugiat esse. Reprehenderit sunt ea ea mollit commodo tempor amet
            fugiat.
          </Text>
        </Modal>
      </Page>
    </>
  );
};

export default PagePillSeparators;

@changeset-bot
Copy link

changeset-bot bot commented Jan 31, 2025

⚠️ No Changeset found

Latest commit: 802dc12

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

export interface PillTabItem {
id: string;
title: string;
separator?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we could improve this interface, right now:

  1. Clients are responsible for deciding when a stylistic element should be used (the separator)
  2. Clients have an explicit concept of groups, but this is only implicit within our props

Imagine instead something more like:

// as it was before
export interface PillTabItem {
  id: string;
  title: string;
}

// a new concept for grouping PillTabItems
export interface PillTabGroup {
  title: string; // could be optional, to be used for accessibility, e.g. the title of a fieldset 
  items: PillTabItem[];
}

export interface PillTabsProps {
  items: PillTabItem[] | PillTabGroup[]; // the component accepts an array of one or the other
  activePills: string[];
  onPillClick: (itemId: string) => void;
}

Usage:

  const itemGroups = [
    { items: [{ title: 'All', id: 'all' }] },
    {
      title: 'Standard filters',
      items: [
        { title: 'Featured', id: 'featured' },
        { title: 'Free shipping', id: 'free-shipping' },
      ],
    },
    {
      title: 'Custom views',
      items: [
        { title: 'Custom view 1', id: 'custom-view-1' },
        { title: 'Custom view 2', id: 'custom-view-2' },
      ],
    },
  ];

  render(<TestComponent activePills={[]} items={itemGroups} onPillClick={onClick} />);

Now:

  1. BigDesign is solely responsible for the aesthetics of how a group of items is delineated (e.g. should we move from a separating line to something else, the client is not impacted)
  2. The props API explicitly embodies the concept of groups, which is closer to how clients will be thinking of them
  3. (Bonus) we can something like a fieldset to wrap these now explicit groups to provide an accessible (as well as aesthetic) concept of grouping

Copy link
Contributor Author

@davelinke davelinke Feb 24, 2025

Choose a reason for hiding this comment

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

This makes sense, in fact that was my first idea. I guess then the pill tab group would show a prepended separator only if it's not the first group within a set.

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, and the nice thing is that that logic is now being handled inside the component

these union types:

items: PillTabItem[] | PillTabGroup[];

can make implementation less straight forward, so let me know if you need a hand with that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants