mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor(site): refactor pill component API (#11329)
Refactor the Pill API to make it easier to extend and reuse.
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 />,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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…</>} type="warning" />
|
||||
<Pill role="status" type="warning">
|
||||
Pending…
|
||||
</Pill>
|
||||
)}
|
||||
{jobStatus === "running" && (
|
||||
<Pill text={<>Building…</>} type="warning" />
|
||||
<Pill role="status" type="warning">
|
||||
Building…
|
||||
</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>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user