Skip to content

Commit 8a3a79f

Browse files
refactor: match StatusIndicator component with the new designs (#16458)
Reference: https://www.figma.com/design/WfqIgsTFXN2BscBSSyXWF8/Coder-kit?node-id=489-4278&m=dev
1 parent 15d5563 commit 8a3a79f

File tree

10 files changed

+177
-76
lines changed

10 files changed

+177
-76
lines changed
Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,55 @@
11
import type { Meta, StoryObj } from "@storybook/react";
2-
import { StatusIndicator } from "./StatusIndicator";
2+
import { StatusIndicator, StatusIndicatorDot } from "./StatusIndicator";
33

44
const meta: Meta<typeof StatusIndicator> = {
55
title: "components/StatusIndicator",
66
component: StatusIndicator,
7-
args: {},
7+
args: {
8+
children: (
9+
<>
10+
<StatusIndicatorDot />
11+
Status
12+
</>
13+
),
14+
},
815
};
916

1017
export default meta;
1118
type Story = StoryObj<typeof StatusIndicator>;
1219

1320
export const Success: Story = {
1421
args: {
15-
color: "success",
16-
},
17-
};
18-
19-
export const SuccessOutline: Story = {
20-
args: {
21-
color: "success",
22-
variant: "outlined",
23-
},
24-
};
25-
26-
export const Warning: Story = {
27-
args: {
28-
color: "warning",
22+
variant: "success",
2923
},
3024
};
3125

32-
export const WarningOutline: Story = {
26+
export const Failed: Story = {
3327
args: {
34-
color: "warning",
35-
variant: "outlined",
28+
variant: "failed",
3629
},
3730
};
3831

39-
export const Danger: Story = {
32+
export const Inactive: Story = {
4033
args: {
41-
color: "danger",
34+
variant: "inactive",
4235
},
4336
};
4437

45-
export const DangerOutline: Story = {
38+
export const Warning: Story = {
4639
args: {
47-
color: "danger",
48-
variant: "outlined",
40+
variant: "warning",
4941
},
5042
};
5143

52-
export const Inactive: Story = {
44+
export const Pending: Story = {
5345
args: {
54-
color: "inactive",
46+
variant: "pending",
5547
},
5648
};
5749

58-
export const InactiveOutline: Story = {
50+
export const Small: Story = {
5951
args: {
60-
color: "inactive",
61-
variant: "outlined",
52+
variant: "success",
53+
size: "sm",
6254
},
6355
};
Lines changed: 87 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,97 @@
1-
import { useTheme } from "@emotion/react";
2-
import type { FC } from "react";
3-
import type { ThemeRole } from "theme/roles";
1+
import { type VariantProps, cva } from "class-variance-authority";
2+
import { type FC, createContext, useContext } from "react";
3+
import { cn } from "utils/cn";
44

5-
interface StatusIndicatorProps {
6-
color: ThemeRole;
7-
variant?: "solid" | "outlined";
8-
}
5+
const statusIndicatorVariants = cva(
6+
"font-medium inline-flex items-center gap-2",
7+
{
8+
variants: {
9+
variant: {
10+
success: "text-content-success",
11+
failed: "text-content-destructive",
12+
inactive: "text-highlight-grey",
13+
warning: "text-content-warning",
14+
pending: "text-highlight-sky",
15+
},
16+
size: {
17+
sm: "text-xs",
18+
md: "text-sm",
19+
},
20+
},
21+
defaultVariants: {
22+
variant: "success",
23+
size: "md",
24+
},
25+
},
26+
);
27+
28+
type StatusIndicatorContextValue = VariantProps<typeof statusIndicatorVariants>;
29+
30+
const StatusIndicatorContext = createContext<StatusIndicatorContextValue>({});
31+
32+
export interface StatusIndicatorProps
33+
extends React.HTMLAttributes<HTMLDivElement>,
34+
StatusIndicatorContextValue {}
935

1036
export const StatusIndicator: FC<StatusIndicatorProps> = ({
11-
color,
12-
variant = "solid",
37+
size,
38+
variant,
39+
className,
40+
...props
1341
}) => {
14-
const theme = useTheme();
42+
return (
43+
<StatusIndicatorContext.Provider value={{ size, variant }}>
44+
<div
45+
className={cn(statusIndicatorVariants({ variant, size }), className)}
46+
{...props}
47+
/>
48+
</StatusIndicatorContext.Provider>
49+
);
50+
};
51+
52+
const dotVariants = cva("rounded-full inline-block border-4 border-solid", {
53+
variants: {
54+
variant: {
55+
success: "bg-content-success border-surface-green",
56+
failed: "bg-content-destructive border-surface-destructive",
57+
inactive: "bg-highlight-grey border-surface-grey",
58+
warning: "bg-content-warning border-surface-orange",
59+
pending: "bg-highlight-sky border-surface-sky",
60+
},
61+
size: {
62+
sm: "size-3 border-4",
63+
md: "size-4 border-4",
64+
},
65+
},
66+
defaultVariants: {
67+
variant: "success",
68+
size: "md",
69+
},
70+
});
71+
72+
export interface StatusIndicatorDotProps
73+
extends React.HTMLAttributes<HTMLDivElement>,
74+
VariantProps<typeof dotVariants> {}
75+
76+
export const StatusIndicatorDot: FC<StatusIndicatorDotProps> = ({
77+
className,
78+
// We allow the size and variant to be overridden directly by the component.
79+
// This allows StatusIndicatorDot to be used alone.
80+
size,
81+
variant,
82+
...props
83+
}) => {
84+
const { size: ctxSize, variant: ctxVariant } = useContext(
85+
StatusIndicatorContext,
86+
);
1587

1688
return (
1789
<div
18-
css={[
19-
{
20-
height: 8,
21-
width: 8,
22-
borderRadius: 4,
23-
},
24-
variant === "solid" && {
25-
backgroundColor: theme.roles[color].fill.solid,
26-
},
27-
variant === "outlined" && {
28-
border: `1px solid ${theme.roles[color].outline}`,
29-
},
30-
]}
90+
className={cn(
91+
dotVariants({ variant: variant ?? ctxVariant, size: size ?? ctxSize }),
92+
className,
93+
)}
94+
{...props}
3195
/>
3296
);
3397
};

site/src/index.css

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,28 @@
1515
--content-invert: 0 0% 98%;
1616
--content-disabled: 240 5% 65%;
1717
--content-success: 142 72% 29%;
18-
--content-danger: 0 84% 60%;
1918
--content-warning: 27 96% 61%;
19+
--content-destructive: 0 84% 60%;
2020
--surface-primary: 0 0% 98%;
2121
--surface-secondary: 240 5% 96%;
2222
--surface-tertiary: 240 6% 90%;
2323
--surface-quaternary: 240 5% 84%;
2424
--surface-invert-primary: 240 4% 16%;
2525
--surface-invert-secondary: 240 5% 26%;
2626
--surface-destructive: 0 93% 94%;
27+
--surface-green: 141 79% 85%;
28+
--surface-grey: 240 5% 96%;
29+
--surface-orange: 34 100% 92%;
30+
--surface-sky: 201 94% 86%;
2731
--border-default: 240 6% 90%;
2832
--border-success: 142 76% 36%;
2933
--border-destructive: 0 84% 60%;
3034
--overlay-default: 240 5% 84% / 80%;
3135
--radius: 0.5rem;
3236
--highlight-purple: 262 83% 58%;
3337
--highlight-green: 143 64% 24%;
38+
--highlight-grey: 240 5% 65%;
39+
--highlight-sky: 201 90% 27%;
3440
--border: 240 5.9% 90%;
3541
--input: 240 5.9% 90%;
3642
--ring: 240 10% 3.9%;
@@ -45,21 +51,27 @@
4551
--content-invert: 240 10% 4%;
4652
--content-disabled: 240 5% 26%;
4753
--content-success: 142 76% 36%;
48-
--content-danger: 0 91% 71%;
49-
--content-warning: 27 96% 61%;
54+
--content-warning: 31 97% 72%;
55+
--content-destructive: 0 91% 71%;
5056
--surface-primary: 240 10% 4%;
5157
--surface-secondary: 240 6% 10%;
5258
--surface-tertiary: 240 4% 16%;
5359
--surface-quaternary: 240 5% 26%;
5460
--surface-invert-primary: 240 6% 90%;
5561
--surface-invert-secondary: 240 5% 65%;
5662
--surface-destructive: 0 75% 15%;
63+
--surface-green: 145 80% 10%;
64+
--surface-grey: 240 6% 10%;
65+
--surface-orange: 13 81% 15%;
66+
--surface-sky: 204 80% 16%;
5767
--border-default: 240 4% 16%;
5868
--border-success: 142 76% 36%;
5969
--border-destructive: 0 91% 71%;
6070
--overlay-default: 240 10% 4% / 80%;
6171
--highlight-purple: 252 95% 85%;
6272
--highlight-green: 141 79% 85%;
73+
--highlight-grey: 240 4% 46%;
74+
--highlight-sky: 198 93% 60%;
6375
--border: 240 3.7% 15.9%;
6476
--input: 240 3.7% 15.9%;
6577
--ring: 240 4.9% 83.9%;

site/src/modules/provisioners/ProvisionerGroup.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from "components/HelpTooltip/HelpTooltip";
1717
import { Pill } from "components/Pill/Pill";
1818
import { Stack } from "components/Stack/Stack";
19-
import { StatusIndicator } from "components/StatusIndicator/StatusIndicator";
19+
import { StatusIndicatorDot } from "components/StatusIndicator/StatusIndicator";
2020
import {
2121
Popover,
2222
PopoverContent,
@@ -127,7 +127,7 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
127127
}}
128128
>
129129
<div css={{ display: "flex", alignItems: "center", gap: 16 }}>
130-
<StatusIndicator color={hasWarning ? "warning" : "success"} />
130+
<StatusIndicatorDot variant={hasWarning ? "warning" : "success"} />
131131
<div
132132
css={{
133133
display: "flex",

site/src/pages/CreateTokenPage/CreateTokenForm.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
8080
</FormFields>
8181
</FormSection>
8282
<FormSection
83+
data-chromatic="ignore"
8384
title="Expiration"
8485
description={
8586
form.values.lifetime

site/src/pages/CreateTokenPage/CreateTokenPage.stories.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,6 @@ const meta: Meta<typeof CreateTokenPage> = {
1212
},
1313
],
1414
},
15-
decorators: [
16-
(Story) => {
17-
Date.now = () => new Date("01/01/2014").getTime();
18-
return <Story />;
19-
},
20-
],
2115
};
2216

2317
export default meta;

site/src/pages/UsersPage/UsersFilter.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import {
77
type UseFilterMenuOptions,
88
useFilterMenu,
99
} from "components/Filter/menu";
10-
import { StatusIndicator } from "components/StatusIndicator/StatusIndicator";
10+
import {
11+
StatusIndicator,
12+
StatusIndicatorDot,
13+
} from "components/StatusIndicator/StatusIndicator";
1114
import type { FC } from "react";
1215
import { docs } from "utils/docs";
1316

@@ -24,17 +27,17 @@ export const useStatusFilterMenu = ({
2427
{
2528
value: "active",
2629
label: "Active",
27-
startIcon: <StatusIndicator color="success" />,
30+
startIcon: <StatusIndicatorDot variant="success" />,
2831
},
2932
{
3033
value: "dormant",
3134
label: "Dormant",
32-
startIcon: <StatusIndicator color="warning" />,
35+
startIcon: <StatusIndicatorDot variant="warning" />,
3336
},
3437
{
3538
value: "suspended",
3639
label: "Suspended",
37-
startIcon: <StatusIndicator color="inactive" />,
40+
startIcon: <StatusIndicatorDot variant="inactive" />,
3841
},
3942
];
4043
return useFilterMenu({

site/src/pages/WorkspacesPage/LastUsed.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useTheme } from "@emotion/react";
22
import { Stack } from "components/Stack/Stack";
3-
import { StatusIndicator } from "components/StatusIndicator/StatusIndicator";
3+
import { StatusIndicatorDot } from "components/StatusIndicator/StatusIndicator";
44
import dayjs from "dayjs";
55
import relativeTime from "dayjs/plugin/relativeTime";
66
import { useTime } from "hooks/useTime";
@@ -18,19 +18,19 @@ export const LastUsed: FC<LastUsedProps> = ({ lastUsedAt }) => {
1818
const t = dayjs(lastUsedAt);
1919
const now = dayjs();
2020
let message = t.fromNow();
21-
let circle = <StatusIndicator color="info" variant="outlined" />;
21+
let circle = <StatusIndicatorDot variant="inactive" />;
2222

2323
if (t.isAfter(now.subtract(1, "hour"))) {
24-
circle = <StatusIndicator color="success" />;
24+
circle = <StatusIndicatorDot variant="success" />;
2525
// Since the agent reports on a 10m interval,
2626
// the last_used_at can be inaccurate when recent.
2727
message = "Now";
2828
} else if (t.isAfter(now.subtract(3, "day"))) {
29-
circle = <StatusIndicator color="info" />;
29+
circle = <StatusIndicatorDot variant="pending" />;
3030
} else if (t.isAfter(now.subtract(1, "month"))) {
31-
circle = <StatusIndicator color="warning" />;
31+
circle = <StatusIndicatorDot variant="warning" />;
3232
} else if (t.isAfter(now.subtract(100, "year"))) {
33-
circle = <StatusIndicator color="error" />;
33+
circle = <StatusIndicatorDot variant="failed" />;
3434
} else {
3535
message = "Never";
3636
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy