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

feat(component): implement FeatureTag component #1567

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

davelinke
Copy link
Contributor

@davelinke davelinke commented Oct 16, 2024

What?

Introducing the FeatureTag component

Why?

The feature tag component is a stylized link/badge interface element that describes categories of a particular application or feature within the BC properties.

Screenshots/Screen Recordings

Figma

Screenshot 2024-10-16 at 3 03 40 PM

Prototype

Screenshot 2024-10-17 at 9 52 39 AM

Testing/Proof

dev.tsx code
import { FeatureTag, Flex, Grid } from '@bigcommerce/big-design';
import { AutoAwesomeIcon } from '@bigcommerce/big-design-icons';
import React, { FunctionComponent } from 'react';

const PageFeatureTag: FunctionComponent = () => {
  return (
    <Flex flexDirection="column" flexGap="1rem" padding="large">
      <FeatureTag href="#" icon={<AutoAwesomeIcon />} label="Woot" />
      <FeatureTag
        href="https://bigcommerce.com"
        icon={<AutoAwesomeIcon />}
        label="BigCommerce"
        target="_blank"
      />
      <FeatureTag href="#" icon={<AutoAwesomeIcon />} isActive={true} label="BigCommerce" />
      <Grid gridColumns="100px">
        <FeatureTag href="#" icon={<AutoAwesomeIcon />} label="A really long text" />
      </Grid>
    </Flex>
  );
};

export default PageFeatureTag;

Copy link

changeset-bot bot commented Oct 16, 2024

⚠️ No Changeset found

Latest commit: 4cd6443

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

@davelinke davelinke changed the title feat(components): implement FeatureTag component feat(component): implement FeatureTag component Oct 16, 2024
Comment on lines +1 to +32
import { IconProps } from '@bigcommerce/big-design-icons';
import React, { AnchorHTMLAttributes, FunctionComponent, ReactElement } from 'react';

import { StyledFeatureTag, StyledFeatureTagIcon, StyledFeatureTagLabel } from './styled';

export interface FeatureTagProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
icon?: ReactElement<IconProps>;
isActive?: boolean;
label: string;
}

export const FeatureTag: FunctionComponent<FeatureTagProps> = ({
label,
icon,
isActive,
href,
target,
}) => {
return label ? (
<StyledFeatureTag
className={isActive ? 'active' : ''}
href={href}
target={target}
title={label}
>
{icon ? <StyledFeatureTagIcon>{icon}</StyledFeatureTagIcon> : null}
<StyledFeatureTagLabel>{label}</StyledFeatureTagLabel>
</StyledFeatureTag>
) : null;
};

FeatureTag.displayName = 'FeatureTag';
Copy link
Contributor

Choose a reason for hiding this comment

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

Ton's of things here:

  1. For icon props, we just use ReactNode as the type. Allows consumers to pass in high-order components/icons if needed.
  2. I know this might be a contention point, but let's remove the isActive prop. It feels like it's a link state, rather than prop state here.
  3. Use ComponentPropsWithoutRef instead of HTML<Element>Attributes to extend from.
  4. Avoid using FunctionComponent, instead use ({ some, prop, ...props }: Props) => {...} as React now implies it's a functional component.
  5. You'll see in styled.tsx some simplification there, which simplifies the HTML and styles in the component.
  6. Spread the rest of the props as we are extending a link component.
Suggested change
import { IconProps } from '@bigcommerce/big-design-icons';
import React, { AnchorHTMLAttributes, FunctionComponent, ReactElement } from 'react';
import { StyledFeatureTag, StyledFeatureTagIcon, StyledFeatureTagLabel } from './styled';
export interface FeatureTagProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
icon?: ReactElement<IconProps>;
isActive?: boolean;
label: string;
}
export const FeatureTag: FunctionComponent<FeatureTagProps> = ({
label,
icon,
isActive,
href,
target,
}) => {
return label ? (
<StyledFeatureTag
className={isActive ? 'active' : ''}
href={href}
target={target}
title={label}
>
{icon ? <StyledFeatureTagIcon>{icon}</StyledFeatureTagIcon> : null}
<StyledFeatureTagLabel>{label}</StyledFeatureTagLabel>
</StyledFeatureTag>
) : null;
};
FeatureTag.displayName = 'FeatureTag';
import React, { ComponentPropsWithoutRef, forwardRef, ReactNode } from 'react';
import { StyledFeatureTag, StyledFeatureTagLabel } from './styled';
export interface FeatureTagProps extends ComponentPropsWithoutRef<'a'> {
icon?: ReactNode;
label: string;
}
export const FeatureTag = forwardRef<HTMLAnchorElement, FeatureTagProps>(
({ label, icon, ...props }, ref) => {
return label ? (
<StyledFeatureTag ref={ref} {...props}>
{icon}
<StyledFeatureTagLabel>{label}</StyledFeatureTagLabel>
</StyledFeatureTag>
) : null;
},
);
FeatureTag.displayName = 'FeatureTag';

Comment on lines +1 to +79
import { theme as defaultTheme } from '@bigcommerce/big-design-theme';
import styled from 'styled-components';

export const StyledFeatureTag = styled.a`
align-items: center;
background-color: ${({ theme }) => theme.colors.secondary20};
border-radius: ${({ theme }) => theme.borderRadius.normal};
display: inline-flex;
color: ${({ theme }) => theme.colors.secondary60};
cursor: pointer;
fill: ${({ theme }) => theme.colors.secondary60};
flex-wrap: nowrap;
font-size: ${({ theme }) => theme.helpers.remCalc(14)};
gap: ${({ theme }) => theme.spacing.xxSmall};
justify-content: center;
line-height: ${({ theme }) => theme.helpers.remCalc(20)};
max-width: fit-content;
outline: none;
padding-block: calc(${({ theme }) => theme.spacing.xSmall} / 4);
padding-inline: ${({ theme }) => theme.spacing.xSmall};
text-decoration: none;
user-select: none;

&.active {
background-color: ${({ theme }) => theme.colors.primary10};
color: ${({ theme }) => theme.colors.primary50};
fill: ${({ theme }) => theme.colors.primary};
}

&:hover,
&.active:hover {
background-color: ${({ theme }) => theme.colors.primary20};
color: ${({ theme }) => theme.colors.primary60};
fill: ${({ theme }) => theme.colors.primary40};
}

&:focus {
outline: 4px solid ${({ theme }) => theme.colors.primary20};
}

&:active,
&.active:active {
background-color: ${({ theme }) => theme.colors.primary30};
color: ${({ theme }) => theme.colors.primary60};
fill: ${({ theme }) => theme.colors.primary70};
}

&:focused:active {
background-color: ${({ theme }) => theme.colors.primary10};
color: ${({ theme }) => theme.colors.primary60};
fill: ${({ theme }) => theme.colors.primary60};
outline: 4px solid ${({ theme }) => theme.colors.primary30};
}
`;

StyledFeatureTag.defaultProps = { theme: defaultTheme };

export const StyledFeatureTagIcon = styled.div`
align-items: center;
display: flex;
flex: 0 0 auto;
height: ${({ theme }) => theme.helpers.remCalc(18)};
justify-content: center;
overflow: hidden;
width: ${({ theme }) => theme.helpers.remCalc(18)};

& > svg {
transform: scale(0.75);
transform-origin: center;
}
`;

StyledFeatureTagIcon.defaultProps = { theme: defaultTheme };

export const StyledFeatureTagLabel = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. We don't need fill set as our icons use color: currentColor for the stroke/fill it needs.
  2. Switch over to the theme.typography.fontSize + theme.lineHeight settings, instead of using remCalc (Use remCalc when we don't have the value on the theme).
  3. Use the & > svg to size the SVG instead of needing another HTML element.
  4. Use ellipsis from polished.
  5. Cleaned up link state styles.
Suggested change
import { theme as defaultTheme } from '@bigcommerce/big-design-theme';
import styled from 'styled-components';
export const StyledFeatureTag = styled.a`
align-items: center;
background-color: ${({ theme }) => theme.colors.secondary20};
border-radius: ${({ theme }) => theme.borderRadius.normal};
display: inline-flex;
color: ${({ theme }) => theme.colors.secondary60};
cursor: pointer;
fill: ${({ theme }) => theme.colors.secondary60};
flex-wrap: nowrap;
font-size: ${({ theme }) => theme.helpers.remCalc(14)};
gap: ${({ theme }) => theme.spacing.xxSmall};
justify-content: center;
line-height: ${({ theme }) => theme.helpers.remCalc(20)};
max-width: fit-content;
outline: none;
padding-block: calc(${({ theme }) => theme.spacing.xSmall} / 4);
padding-inline: ${({ theme }) => theme.spacing.xSmall};
text-decoration: none;
user-select: none;
&.active {
background-color: ${({ theme }) => theme.colors.primary10};
color: ${({ theme }) => theme.colors.primary50};
fill: ${({ theme }) => theme.colors.primary};
}
&:hover,
&.active:hover {
background-color: ${({ theme }) => theme.colors.primary20};
color: ${({ theme }) => theme.colors.primary60};
fill: ${({ theme }) => theme.colors.primary40};
}
&:focus {
outline: 4px solid ${({ theme }) => theme.colors.primary20};
}
&:active,
&.active:active {
background-color: ${({ theme }) => theme.colors.primary30};
color: ${({ theme }) => theme.colors.primary60};
fill: ${({ theme }) => theme.colors.primary70};
}
&:focused:active {
background-color: ${({ theme }) => theme.colors.primary10};
color: ${({ theme }) => theme.colors.primary60};
fill: ${({ theme }) => theme.colors.primary60};
outline: 4px solid ${({ theme }) => theme.colors.primary30};
}
`;
StyledFeatureTag.defaultProps = { theme: defaultTheme };
export const StyledFeatureTagIcon = styled.div`
align-items: center;
display: flex;
flex: 0 0 auto;
height: ${({ theme }) => theme.helpers.remCalc(18)};
justify-content: center;
overflow: hidden;
width: ${({ theme }) => theme.helpers.remCalc(18)};
& > svg {
transform: scale(0.75);
transform-origin: center;
}
`;
StyledFeatureTagIcon.defaultProps = { theme: defaultTheme };
export const StyledFeatureTagLabel = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
import { theme as defaultTheme } from '@bigcommerce/big-design-theme';
import { ellipsis } from 'polished';
import styled from 'styled-components';
export const StyledFeatureTag = styled.a`
align-items: center;
background-color: ${({ theme }) => theme.colors.secondary20};
border-radius: ${({ theme }) => theme.borderRadius.normal};
display: inline-flex;
color: ${({ theme }) => theme.colors.secondary60};
cursor: pointer;
flex-wrap: nowrap;
font-size: ${({ theme }) => theme.typography.fontSize.small};
gap: ${({ theme }) => theme.spacing.xxSmall};
justify-content: center;
line-height: ${({ theme }) => theme.lineHeight.small};
max-width: fit-content;
outline: none;
padding-block: ${({ theme }) => theme.helpers.remCalc(2)};
padding-inline: ${({ theme }) => theme.spacing.xSmall};
text-decoration: none;
user-select: none;
&:hover {
background-color: ${({ theme }) => theme.colors.primary20};
color: ${({ theme }) => theme.colors.primary60};
}
&:focus-visible {
outline: 4px solid ${({ theme }) => theme.colors.primary20};
}
&:active {
background-color: ${({ theme }) => theme.colors.primary10};
color: ${({ theme }) => theme.colors.primary50};
}
&:focus-visible:active {
background-color: ${({ theme }) => theme.colors.primary10};
color: ${({ theme }) => theme.colors.primary60};
}
& > svg {
height: ${({ theme }) => theme.spacing.medium};
flex-shrink: 0;
width: ${({ theme }) => theme.spacing.medium};
}
`;
StyledFeatureTag.defaultProps = { theme: defaultTheme };
export const StyledFeatureTagLabel = styled.span`
${ellipsis()}
`;

@chanceaclark
Copy link
Contributor

Before you convert this from draft to regular PR make sure to add documentation.

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

Successfully merging this pull request may close these issues.

2 participants