From 5e0589f8c48a06287f0d0e2e5e87af54229fdcfd Mon Sep 17 00:00:00 2001 From: David Linke Date: Wed, 29 Jan 2025 10:30:49 -0500 Subject: [PATCH] feat(component): implement FeatureTag component (#1567) * 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 --- .changeset/thick-coats-reflect.md | 6 ++ packages/big-design/jest.config.js | 8 +- .../src/components/FeatureSet/FeatureSet.tsx | 24 +++++ .../src/components/FeatureSet/Tag/Tag.tsx | 25 +++++ .../src/components/FeatureSet/Tag/index.ts | 1 + .../src/components/FeatureSet/Tag/styled.tsx | 21 ++++ .../FeatureSet/__snapshots__/spec.tsx.snap | 99 +++++++++++++++++++ .../src/components/FeatureSet/index.ts | 1 + .../src/components/FeatureSet/spec.tsx | 58 +++++++++++ .../src/components/FeatureSet/styled.tsx | 14 +++ packages/big-design/src/components/index.ts | 3 +- .../docs/PropTables/FeatureSetPropTable.tsx | 55 +++++++++++ packages/docs/components/SideNav/SideNav.tsx | 1 + packages/docs/pages/feature-set.tsx | 74 ++++++++++++++ 14 files changed, 385 insertions(+), 5 deletions(-) create mode 100644 .changeset/thick-coats-reflect.md create mode 100644 packages/big-design/src/components/FeatureSet/FeatureSet.tsx create mode 100644 packages/big-design/src/components/FeatureSet/Tag/Tag.tsx create mode 100644 packages/big-design/src/components/FeatureSet/Tag/index.ts create mode 100644 packages/big-design/src/components/FeatureSet/Tag/styled.tsx create mode 100644 packages/big-design/src/components/FeatureSet/__snapshots__/spec.tsx.snap create mode 100644 packages/big-design/src/components/FeatureSet/index.ts create mode 100644 packages/big-design/src/components/FeatureSet/spec.tsx create mode 100644 packages/big-design/src/components/FeatureSet/styled.tsx create mode 100644 packages/docs/PropTables/FeatureSetPropTable.tsx create mode 100644 packages/docs/pages/feature-set.tsx diff --git a/.changeset/thick-coats-reflect.md b/.changeset/thick-coats-reflect.md new file mode 100644 index 000000000..511d2745a --- /dev/null +++ b/.changeset/thick-coats-reflect.md @@ -0,0 +1,6 @@ +--- +'@bigcommerce/big-design': minor +'@bigcommerce/docs': minor +--- + +Added FeatureSet component diff --git a/packages/big-design/jest.config.js b/packages/big-design/jest.config.js index fc47f9387..3f555b7e2 100644 --- a/packages/big-design/jest.config.js +++ b/packages/big-design/jest.config.js @@ -5,10 +5,10 @@ module.exports = { setupFilesAfterEnv: ['/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, }, }, }; diff --git a/packages/big-design/src/components/FeatureSet/FeatureSet.tsx b/packages/big-design/src/components/FeatureSet/FeatureSet.tsx new file mode 100644 index 000000000..4bddd306b --- /dev/null +++ b/packages/big-design/src/components/FeatureSet/FeatureSet.tsx @@ -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 = memo( + ({ tags, className, style, ...props }) => { + return tags && tags.length > 0 ? ( + + {tags.map((tag, index) => ( + + ))} + + ) : null; + }, +); + +FeatureSet.displayName = 'FeatureSet'; diff --git a/packages/big-design/src/components/FeatureSet/Tag/Tag.tsx b/packages/big-design/src/components/FeatureSet/Tag/Tag.tsx new file mode 100644 index 000000000..34a087c5c --- /dev/null +++ b/packages/big-design/src/components/FeatureSet/Tag/Tag.tsx @@ -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 = memo(({ icon, label }) => { + const id = useId(); + + return label ? ( + + {icon} + + {label} + + + ) : null; +}); + +Tag.displayName = 'Tag'; diff --git a/packages/big-design/src/components/FeatureSet/Tag/index.ts b/packages/big-design/src/components/FeatureSet/Tag/index.ts new file mode 100644 index 000000000..107773510 --- /dev/null +++ b/packages/big-design/src/components/FeatureSet/Tag/index.ts @@ -0,0 +1 @@ +export { Tag, type TagProps } from './Tag'; diff --git a/packages/big-design/src/components/FeatureSet/Tag/styled.tsx b/packages/big-design/src/components/FeatureSet/Tag/styled.tsx new file mode 100644 index 000000000..f163a651e --- /dev/null +++ b/packages/big-design/src/components/FeatureSet/Tag/styled.tsx @@ -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}; + } +`; diff --git a/packages/big-design/src/components/FeatureSet/__snapshots__/spec.tsx.snap b/packages/big-design/src/components/FeatureSet/__snapshots__/spec.tsx.snap new file mode 100644 index 000000000..ce49aa872 --- /dev/null +++ b/packages/big-design/src/components/FeatureSet/__snapshots__/spec.tsx.snap @@ -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; +} + +
    +
  • + + Feature 1 + +
  • +
  • + + Feature 2 + +
  • +
+`; diff --git a/packages/big-design/src/components/FeatureSet/index.ts b/packages/big-design/src/components/FeatureSet/index.ts new file mode 100644 index 000000000..82a7aeebc --- /dev/null +++ b/packages/big-design/src/components/FeatureSet/index.ts @@ -0,0 +1 @@ +export { FeatureSet, type FeatureSetProps } from './FeatureSet'; diff --git a/packages/big-design/src/components/FeatureSet/spec.tsx b/packages/big-design/src/components/FeatureSet/spec.tsx new file mode 100644 index 000000000..2da4da97e --- /dev/null +++ b/packages/big-design/src/components/FeatureSet/spec.tsx @@ -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( + , + ); + + expect(container.firstChild).toMatchSnapshot(); +}); + +test("doesn't forward styles", () => { + const { container } = render( + , + ); + + expect(container.getElementsByClassName('test')[0]).toBeUndefined(); + expect(screen.getByRole('list')).not.toHaveStyle('background: red'); +}); + +test("doesn't render if tags are empty", () => { + render(); + + expect(screen.queryByRole('list')).not.toBeInTheDocument(); +}); + +test('allows margins to be set', () => { + render(); + + expect(screen.getByRole('list')).toHaveStyle('margin: 1rem'); +}); + +test('renders tag with icon', () => { + render( + }, { label: 'Feature 2' }]} + />, + ); + + expect( + screen.getByRole('listitem', { name: 'Feature 1' }).getElementsByTagName('svg')[0], + ).toBeInTheDocument(); +}); + +test("doesn't render tag with invalid label", () => { + render(); + + expect(screen.queryByRole('listitem')).not.toBeInTheDocument(); +}); diff --git a/packages/big-design/src/components/FeatureSet/styled.tsx b/packages/big-design/src/components/FeatureSet/styled.tsx new file mode 100644 index 000000000..9ff540a32 --- /dev/null +++ b/packages/big-design/src/components/FeatureSet/styled.tsx @@ -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}; +`; diff --git a/packages/big-design/src/components/index.ts b/packages/big-design/src/components/index.ts index e76e03919..ff9ef4d06 100644 --- a/packages/big-design/src/components/index.ts +++ b/packages/big-design/src/components/index.ts @@ -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'; @@ -43,4 +45,3 @@ export * from './Tooltip'; export * from './Toggle'; export * from './Typography'; export * from './Worksheet'; -export * from './FileUploader'; diff --git a/packages/docs/PropTables/FeatureSetPropTable.tsx b/packages/docs/PropTables/FeatureSetPropTable.tsx new file mode 100644 index 000000000..cf4ad98a2 --- /dev/null +++ b/packages/docs/PropTables/FeatureSetPropTable.tsx @@ -0,0 +1,55 @@ +import React from 'react'; + +import { Code, NextLink, Prop, PropTable, PropTableWrapper } from '../components'; + +const featureSetProps: Prop[] = [ + { + name: 'tags', + types: ( + + TagProps[] + + ), + description: ( + <> + See{' '} + + TagProps + {' '} + for usage. + + ), + required: true, + }, +]; + +export const FeatureSetPropTable: React.FC = (props) => ( + +); + +const featureTagProps: Prop[] = [ + { + name: 'icon', + types: Icon, + description: ( + <> + Pass in an Icon component to display at the start of the + tag. + + ), + }, + { + name: 'label', + types: 'string', + description: ( + <> + Defines the FeatureTag text. + + ), + required: true, + }, +]; + +export const FeatureTagPropTable: React.FC = (props) => ( + +); diff --git a/packages/docs/components/SideNav/SideNav.tsx b/packages/docs/components/SideNav/SideNav.tsx index 0f8dc8fbf..0ffa7ec29 100644 --- a/packages/docs/components/SideNav/SideNav.tsx +++ b/packages/docs/components/SideNav/SideNav.tsx @@ -95,6 +95,7 @@ export const SideNav: React.FC = () => { Alert Badge + FeatureSet InlineMessage Message ProgressBar diff --git a/packages/docs/pages/feature-set.tsx b/packages/docs/pages/feature-set.tsx new file mode 100644 index 000000000..dae565334 --- /dev/null +++ b/packages/docs/pages/feature-set.tsx @@ -0,0 +1,74 @@ +import { FeatureSet, H1, Panel, Text } from '@bigcommerce/big-design'; +import { AutoAwesomeIcon } from '@bigcommerce/big-design-icons'; +import React from 'react'; + +import { Code, CodePreview, ContentRoutingTabs, GuidelinesTable } from '../components'; +import { FeatureSetPropTable, FeatureTagPropTable } from '../PropTables/FeatureSetPropTable'; + +const FeatureSetPage = () => { + return ( + <> +

Feature Set

+ + + + Feature sets are uset to represent a summarized version of + application features. + + + They are to be used within installations and other similar pages where a quick glance of + relevant characteristics is needed. + + + + + + {/* jsx-to-string:start */} + {function Example() { + const tags = [ + { label: 'Feature 1', icon: }, + { label: 'Feature 2', icon: }, + { label: 'Feature 3' }, + ]; + + return ; + }} + {/* jsx-to-string:end */} + + + + + , + }, + { + id: 'feature-tag', + title: 'FeatureTag', + render: () => , + }, + ]} + /> + + + + + + + ); +}; + +export default FeatureSetPage;