Skip to content

Commit

Permalink
feat(component): implement FeatureTag component (#1567)
Browse files Browse the repository at this point in the history
* feat(component): create FeatureTag base components

* feat(component): fix component structure and tests

* feat(component): add html anchor props

* feat(component): fix test version mismatch

* feat(component): remove html anchor functionality from FeatureTag

* feat: add FeatureSet component

* feat(component): lint FeatureSet component

* feat(component): refresh FeatureSet snapshot

* feat(component): add FeatureSet documentation

* feat(component): add changeset for FeatureSet

* refactor(component): reorg and cleanup of styles

---------

Co-authored-by: Chancellor Clark <[email protected]>
  • Loading branch information
davelinke and chanceaclark authored Jan 29, 2025
1 parent 06b238a commit 5e0589f
Show file tree
Hide file tree
Showing 14 changed files with 385 additions and 5 deletions.
6 changes: 6 additions & 0 deletions .changeset/thick-coats-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@bigcommerce/big-design': minor
'@bigcommerce/docs': minor
---

Added FeatureSet component
8 changes: 4 additions & 4 deletions packages/big-design/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ module.exports = {
setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
coverageThreshold: {
global: {
statements: 95.73,
branches: 86.97,
functions: 97,
lines: 95.77,
statements: 96.35,
branches: 88.7,
functions: 97.55,
lines: 96.73,
},
},
};
24 changes: 24 additions & 0 deletions packages/big-design/src/components/FeatureSet/FeatureSet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { ComponentPropsWithoutRef, memo } from 'react';

import { MarginProps } from '../../helpers';

import { StyledUl } from './styled';
import { Tag, TagProps } from './Tag';

export interface FeatureSetProps extends ComponentPropsWithoutRef<'ul'>, MarginProps {
tags: TagProps[];
}

export const FeatureSet: React.FC<FeatureSetProps> = memo(
({ tags, className, style, ...props }) => {
return tags && tags.length > 0 ? (
<StyledUl {...props}>
{tags.map((tag, index) => (
<Tag {...tag} key={index} />
))}
</StyledUl>
) : null;
},
);

FeatureSet.displayName = 'FeatureSet';
25 changes: 25 additions & 0 deletions packages/big-design/src/components/FeatureSet/Tag/Tag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { memo, ReactNode, useId } from 'react';

import { StyleableSmall } from '../../Typography/private';

import { StyledLi } from './styled';

export interface TagProps {
icon?: ReactNode;
label: string;
}

export const Tag: React.FC<TagProps> = memo(({ icon, label }) => {
const id = useId();

return label ? (
<StyledLi aria-labelledby={id}>
{icon}
<StyleableSmall as="span" color="currentColor" ellipsis id={id} margin="none">
{label}
</StyleableSmall>
</StyledLi>
) : null;
});

Tag.displayName = 'Tag';
1 change: 1 addition & 0 deletions packages/big-design/src/components/FeatureSet/Tag/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Tag, type TagProps } from './Tag';
21 changes: 21 additions & 0 deletions packages/big-design/src/components/FeatureSet/Tag/styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { theme as defaultTheme } from '@bigcommerce/big-design-theme';
import styled from 'styled-components';

export const StyledLi = styled.li.attrs({ theme: defaultTheme })`
align-items: center;
background-color: ${({ theme }) => theme.colors.secondary20};
border-radius: ${({ theme }) => theme.borderRadius.normal};
display: inline-flex;
color: ${({ theme }) => theme.colors.secondary60};
flex-wrap: nowrap;
gap: ${({ theme }) => theme.spacing.xxSmall};
justify-content: center;
padding-block: ${({ theme }) => theme.helpers.remCalc(2)};
padding-inline: ${({ theme }) => theme.spacing.xSmall};
& > svg {
height: ${({ theme }) => theme.spacing.medium};
flex-shrink: 0;
width: ${({ theme }) => theme.spacing.medium};
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`render feature set 1`] = `
.c0 {
list-style-type: none;
margin: 0;
padding: 0;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
gap: 0.75rem;
}
.c2 {
color: currentColor;
margin: 0 0 1rem;
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
color: currentColor;
font-size: 0.875rem;
font-weight: 400;
line-height: 1.25rem;
margin: 0 0 0.75rem;
margin: 0;
}
.c2:last-child {
margin-bottom: 0;
}
.c1 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background-color: #ECEEF5;
border-radius: 0.25rem;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
color: #5E637A;
-webkit-flex-wrap: nowrap;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
gap: 0.25rem;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
padding-block: 0.125rem;
padding-inline: 0.5rem;
}
.c1 > svg {
height: 1rem;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
width: 1rem;
}
<ul
class="c0"
>
<li
aria-labelledby=":r0:"
class="c1"
>
<span
class="c2"
color="currentColor"
id=":r0:"
>
Feature 1
</span>
</li>
<li
aria-labelledby=":r1:"
class="c1"
>
<span
class="c2"
color="currentColor"
id=":r1:"
>
Feature 2
</span>
</li>
</ul>
`;
1 change: 1 addition & 0 deletions packages/big-design/src/components/FeatureSet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FeatureSet, type FeatureSetProps } from './FeatureSet';
58 changes: 58 additions & 0 deletions packages/big-design/src/components/FeatureSet/spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { AutoAwesomeIcon } from '@bigcommerce/big-design-icons';
import { render, screen } from '@testing-library/react';
import React from 'react';

import 'jest-styled-components';

import { FeatureSet } from './index';

test('render feature set', () => {
const { container } = render(
<FeatureSet tags={[{ label: 'Feature 1' }, { label: 'Feature 2' }]} />,
);

expect(container.firstChild).toMatchSnapshot();
});

test("doesn't forward styles", () => {
const { container } = render(
<FeatureSet
className="test"
style={{ background: 'red' }}
tags={[{ label: 'Feature 1' }, { label: 'Feature 2' }]}
/>,
);

expect(container.getElementsByClassName('test')[0]).toBeUndefined();
expect(screen.getByRole('list')).not.toHaveStyle('background: red');
});

test("doesn't render if tags are empty", () => {
render(<FeatureSet tags={[]} />);

expect(screen.queryByRole('list')).not.toBeInTheDocument();
});

test('allows margins to be set', () => {
render(<FeatureSet margin="medium" tags={[{ label: 'Feature 1' }, { label: 'Feature 2' }]} />);

expect(screen.getByRole('list')).toHaveStyle('margin: 1rem');
});

test('renders tag with icon', () => {
render(
<FeatureSet
tags={[{ label: 'Feature 1', icon: <AutoAwesomeIcon /> }, { label: 'Feature 2' }]}
/>,
);

expect(
screen.getByRole('listitem', { name: 'Feature 1' }).getElementsByTagName('svg')[0],
).toBeInTheDocument();
});

test("doesn't render tag with invalid label", () => {
render(<FeatureSet tags={[{ label: '' }]} />);

expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
});
14 changes: 14 additions & 0 deletions packages/big-design/src/components/FeatureSet/styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { theme as defaultTheme } from '@bigcommerce/big-design-theme';
import styled from 'styled-components';

import { withMargins } from '../../helpers';

export const StyledUl = styled.ul.attrs({ theme: defaultTheme })`
${({ theme }) => theme.helpers.listReset}
${withMargins()};
display: inline-flex;
flex-wrap: wrap;
gap: ${({ theme }) => theme.spacing.small};
`;
3 changes: 2 additions & 1 deletion packages/big-design/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ export * from './Chip';
export * from './Collapse';
export * from './Datepicker';
export * from './Dropdown';
export * from './FeatureSet';
export * from './Flex';
export * from './Fieldset';
export * from './FileUploader';
export * from './Form';
export * from './GlobalStyles';
export * from './Grid';
Expand Down Expand Up @@ -43,4 +45,3 @@ export * from './Tooltip';
export * from './Toggle';
export * from './Typography';
export * from './Worksheet';
export * from './FileUploader';
55 changes: 55 additions & 0 deletions packages/docs/PropTables/FeatureSetPropTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';

import { Code, NextLink, Prop, PropTable, PropTableWrapper } from '../components';

const featureSetProps: Prop[] = [
{
name: 'tags',
types: (
<NextLink href={{ hash: 'feature-tag-prop-table', query: { props: 'feature-tag' } }}>
TagProps[]
</NextLink>
),
description: (
<>
See{' '}
<NextLink href={{ hash: 'feature-tag-prop-table', query: { props: 'feature-tag' } }}>
TagProps
</NextLink>{' '}
for usage.
</>
),
required: true,
},
];

export const FeatureSetPropTable: React.FC<PropTableWrapper> = (props) => (
<PropTable propList={featureSetProps} title="Feature Set" {...props} />
);

const featureTagProps: Prop[] = [
{
name: 'icon',
types: <NextLink href="/icons">Icon</NextLink>,
description: (
<>
Pass in an <NextLink href="/icons">Icon</NextLink> component to display at the start of the
tag.
</>
),
},
{
name: 'label',
types: 'string',
description: (
<>
Defines the <Code primary>FeatureTag</Code> text.
</>
),
required: true,
},
];

export const FeatureTagPropTable: React.FC<PropTableWrapper> = (props) => (
<PropTable propList={featureTagProps} title="FeatureTag" {...props} />
);
1 change: 1 addition & 0 deletions packages/docs/components/SideNav/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export const SideNav: React.FC = () => {
<SideNavGroup title="Status &amp; Feedback">
<SideNavLink href="/alert">Alert</SideNavLink>
<SideNavLink href="/badge">Badge</SideNavLink>
<SideNavLink href="/feature-set">FeatureSet</SideNavLink>
<SideNavLink href="/inline-message">InlineMessage</SideNavLink>
<SideNavLink href="/message">Message</SideNavLink>
<SideNavLink href="/progress-bar">ProgressBar</SideNavLink>
Expand Down
Loading

0 comments on commit 5e0589f

Please sign in to comment.