refactor(site): refactor pill component API (#11329)

Refactor the Pill API to make it easier to extend and reuse.
This commit is contained in:
Bruno Quaresma
2024-01-02 09:28:51 -03:00
committed by GitHub
parent c9b7d61769
commit 8717fdfc20
13 changed files with 112 additions and 84 deletions
@@ -55,7 +55,7 @@ export const LicenseBannerView: FC<LicenseBannerViewProps> = ({
if (messages.length === 1) {
return (
<div css={containerStyles}>
<Pill text={Language.licenseIssue} type={type} />
<Pill type={type}>{Language.licenseIssue}</Pill>
<div css={styles.leftContent}>
<span>{messages[0]}</span>
&nbsp;
@@ -73,7 +73,7 @@ export const LicenseBannerView: FC<LicenseBannerViewProps> = ({
return (
<div css={containerStyles}>
<Pill text={Language.licenseIssues(messages.length)} type={type} />
<Pill type={type}>{Language.licenseIssues(messages.length)}</Pill>
<div css={styles.leftContent}>
<div>
{Language.exceeded}
@@ -17,7 +17,7 @@ export const ServiceBannerView: FC<ServiceBannerViewProps> = ({
}) => {
return (
<div css={[styles.banner, { backgroundColor }]}>
{isPreview && <Pill text="Preview" type="info" />}
{isPreview && <Pill type="info">Preview</Pill>}
<div
css={[
styles.wrapper,
+17 -8
View File
@@ -1,5 +1,6 @@
import { Pill } from "./Pill";
import type { Meta, StoryObj } from "@storybook/react";
import InfoOutlined from "@mui/icons-material/InfoOutlined";
const meta: Meta<typeof Pill> = {
title: "components/Pill",
@@ -11,55 +12,63 @@ type Story = StoryObj<typeof Pill>;
export const Default: Story = {
args: {
text: "Default",
children: "Default",
},
};
export const Danger: Story = {
args: {
text: "Danger",
children: "Danger",
type: "danger",
},
};
export const Error: Story = {
args: {
text: "Error",
children: "Error",
type: "error",
},
};
export const Warning: Story = {
args: {
text: "Warning",
children: "Warning",
type: "warning",
},
};
export const Notice: Story = {
args: {
text: "Notice",
children: "Notice",
type: "notice",
},
};
export const Info: Story = {
args: {
text: "Information",
children: "Information",
type: "info",
},
};
export const Success: Story = {
args: {
text: "Success",
children: "Success",
type: "success",
},
};
export const Active: Story = {
args: {
text: "Active",
children: "Active",
type: "active",
},
};
export const WithIcon: Story = {
args: {
children: "Information",
type: "info",
icon: <InfoOutlined />,
},
};
+31 -39
View File
@@ -1,16 +1,19 @@
import { type FC, type ReactNode, useMemo, forwardRef } from "react";
import { css, useTheme, type Interpolation, type Theme } from "@emotion/react";
import {
type FC,
type ReactNode,
useMemo,
forwardRef,
HTMLAttributes,
} from "react";
import { useTheme, type Interpolation, type Theme } from "@emotion/react";
import type { ThemeRole } from "theme/experimental";
export type PillType = ThemeRole | keyof typeof themeOverrides;
export interface PillProps {
className?: string;
export type PillProps = HTMLAttributes<HTMLDivElement> & {
icon?: ReactNode;
text: ReactNode;
type?: PillType;
title?: string;
}
};
const themeOverrides = {
neutral: (theme) => ({
@@ -27,11 +30,14 @@ const themeStyles = (type: ThemeRole) => (theme: Theme) => {
};
};
const PILL_HEIGHT = 24;
const PILL_ICON_SIZE = 14;
const PILL_ICON_SPACING = (PILL_HEIGHT - PILL_ICON_SIZE) / 2;
export const Pill: FC<PillProps> = forwardRef<HTMLDivElement, PillProps>(
(props, ref) => {
const { icon, text = null, type = "neutral", ...attrs } = props;
const { icon, type = "neutral", children, ...divProps } = props;
const theme = useTheme();
const typeStyles = useMemo(() => {
if (type in themeOverrides) {
return themeOverrides[type as keyof typeof themeOverrides];
@@ -44,47 +50,33 @@ export const Pill: FC<PillProps> = forwardRef<HTMLDivElement, PillProps>(
ref={ref}
css={[
{
fontSize: 12,
color: theme.experimental.l1.text,
cursor: "default",
display: "inline-flex",
alignItems: "center",
whiteSpace: "nowrap",
fontWeight: 400,
borderWidth: 1,
borderStyle: "solid",
borderRadius: 99999,
fontSize: 12,
color: theme.experimental.l1.text,
height: 24,
paddingLeft: icon ? 6 : 12,
lineHeight: 1,
height: PILL_HEIGHT,
gap: PILL_ICON_SPACING,
paddingRight: 12,
whiteSpace: "nowrap",
fontWeight: 400,
paddingLeft: icon ? PILL_ICON_SPACING : 12,
"& svg": {
width: PILL_ICON_SIZE,
height: PILL_ICON_SIZE,
},
},
typeStyles,
]}
role="status"
{...attrs}
{...divProps}
>
{icon && (
<div
css={css`
margin-right: 4px;
width: 14px;
height: 14px;
line-height: 0;
display: flex;
align-items: center;
justify-content: center;
& > img,
& > svg {
width: 14px;
height: 14px;
}
`}
>
{icon}
</div>
)}
{text}
{icon}
{children}
</div>
);
},
@@ -57,7 +57,6 @@ export const TemplateExampleCard = (props: TemplateExampleCardProps) => {
return (
<RouterLink key={tag} to={`/starter-templates?tag=${tag}`}>
<Pill
text={tag}
css={(theme) => ({
borderColor: isActive
? theme.palette.primary.main
@@ -70,7 +69,9 @@ export const TemplateExampleCard = (props: TemplateExampleCardProps) => {
borderColor: theme.palette.primary.main,
},
})}
/>
>
{tag}
</Pill>
</RouterLink>
);
})}
@@ -47,13 +47,15 @@ export const WorkspaceStatusBadge: FC<WorkspaceStatusBadgeProps> = ({
}
placement="top"
>
<div>
<Pill className={className} icon={icon} text={text} type={type} />
</div>
<Pill role="status" className={className} icon={icon} type={type}>
{text}
</Pill>
</FailureTooltip>
</Cond>
<Cond>
<Pill className={className} icon={icon} text={text} type={type} />
<Pill role="status" className={className} icon={icon} type={type}>
{text}
</Pill>
</Cond>
</ChooseOne>
);
@@ -95,11 +97,13 @@ export const DormantStatusBadge: FC<DormantStatusBadgeProps> = ({
}
>
<Pill
role="status"
className={className}
icon={<AutoDeleteIcon />}
text="Deletion Pending"
type="error"
/>
>
Deletion Pending
</Pill>
</Tooltip>
) : (
<Tooltip
@@ -113,11 +117,13 @@ export const DormantStatusBadge: FC<DormantStatusBadgeProps> = ({
}
>
<Pill
role="status"
className={className}
icon={<RecyclingIcon />}
text="Dormant"
type="warning"
/>
>
Dormant
</Pill>
</Tooltip>
);
};
@@ -138,8 +138,9 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
<Pill
css={styles.httpStatusPill}
type={httpStatusColor(auditLog.status_code)}
text={auditLog.status_code.toString()}
/>
>
{auditLog.status_code.toString()}
</Pill>
</Stack>
</Stack>
</Stack>
@@ -86,7 +86,9 @@ export const LicenseCard = ({
new Date(license.claims.license_expires * 1000),
new Date(),
) < 1 ? (
<Pill css={styles.expiredBadge} text="Expired" type="error" />
<Pill css={styles.expiredBadge} type="error">
Expired
</Pill>
) : (
<span css={styles.secondaryMaincolor}>Valid Until</span>
)}
@@ -216,7 +216,7 @@ export const TemplatePageHeader: FC<TemplatePageHeaderProps> = ({
)}
</div>
{template.deprecated && <Pill text="Deprecated" type="warning" />}
{template.deprecated && <Pill type="warning">Deprecated</Pill>}
</Stack>
</PageHeader>
</Margins>
@@ -75,19 +75,36 @@ export const VersionRow: FC<VersionRowProps> = ({
</Stack>
<Stack direction="row" alignItems="center" spacing={2}>
{isActive && <Pill text="Active" type="success" />}
{isLatest && <Pill text="Newest" type="info" />}
{isActive && (
<Pill role="status" type="success">
Active
</Pill>
)}
{isLatest && (
<Pill role="status" type="info">
Newest
</Pill>
)}
{jobStatus === "pending" && (
<Pill text={<>Pending&hellip;</>} type="warning" />
<Pill role="status" type="warning">
Pending&hellip;
</Pill>
)}
{jobStatus === "running" && (
<Pill text={<>Building&hellip;</>} type="warning" />
<Pill role="status" type="warning">
Building&hellip;
</Pill>
)}
{(jobStatus === "canceling" || jobStatus === "canceled") && (
<Pill text="Canceled" type="neutral" />
<Pill role="status" type="neutral">
Canceled
</Pill>
)}
{jobStatus === "failed" && (
<Pill role="status" type="error">
Failed
</Pill>
)}
{jobStatus === "failed" && <Pill text="Failed" type="error" />}
{showActions && (
<>
@@ -10,12 +10,9 @@ export const TemplateVersionStatusBadge: FC<{
}> = ({ version }) => {
const { text, icon, type } = getStatus(version);
return (
<Pill
icon={icon}
text={text}
type={type}
title={`Build status is ${text}`}
/>
<Pill icon={icon} type={type} title={`Build status is ${text}`}>
{text}
</Pill>
);
};
@@ -71,7 +71,6 @@ export const UserRoleCell: FC<UserRoleCellProps> = ({
)}
<Pill
text={mainDisplayRole.display_name}
css={{
backgroundColor: hasOwnerRole
? theme.experimental.roles.info.background
@@ -80,7 +79,9 @@ export const UserRoleCell: FC<UserRoleCellProps> = ({
? theme.experimental.roles.info.outline
: theme.experimental.l2.outline,
}}
/>
>
{mainDisplayRole.display_name}
</Pill>
{extraRoles.length > 0 && <OverflowRolePill roles={extraRoles} />}
</Stack>
@@ -99,12 +100,13 @@ const OverflowRolePill: FC<OverflowRolePillProps> = ({ roles }) => {
<Popover mode="hover">
<PopoverTrigger>
<Pill
text={`+${roles.length} more`}
css={{
backgroundColor: theme.palette.background.paper,
borderColor: theme.palette.divider,
}}
/>
>
{`+${roles.length} more`}
</Pill>
</PopoverTrigger>
<PopoverContent
@@ -133,12 +135,13 @@ const OverflowRolePill: FC<OverflowRolePillProps> = ({ roles }) => {
{roles.map((role) => (
<Pill
key={role.name}
text={role.display_name || role.name}
css={{
backgroundColor: theme.palette.background.paper,
borderColor: theme.palette.divider,
}}
/>
>
{role.display_name || role.name}
</Pill>
))}
</PopoverContent>
</Popover>
@@ -106,7 +106,7 @@ export const ChangeVersionDialog: FC<ChangeVersionDialogProps> = ({
)}
</Stack>
{template?.active_version_id === option.id && (
<Pill text="Active" type="success" />
<Pill type="success">Active</Pill>
)}
</Stack>
}